Last active
November 9, 2016 16:34
-
-
Save Upliner/e2e1d6de92fd484749e65ec831ab7e4f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <unistd.h> | |
#include <fcntl.h> | |
#include <string.h> | |
#include <endian.h> | |
#include <sys/stat.h> | |
#include <sys/mman.h> | |
#include <libavcodec/avcodec.h> | |
#include <stdint.h> | |
#include <sys/types.h> | |
#if __BYTE_ORDER == __LITTLE_ENDIAN | |
#define ATOM(n1,n2,n3,n4) (((uint32_t)n4<<24)|((uint32_t)n3<<16)|((uint32_t)n2<<8)|(uint32_t)n1) | |
#else | |
#define ATOM(n1,n2,n3,n4) (((uint32_t)n1<<24)|((uint32_t)n2<<16)|((uint32_t)n3<<8)|(uint32_t)n4) | |
#endif | |
#ifndef __packed | |
#define __packed __attribute__((packed)) | |
#endif | |
typedef struct { | |
uint32_t size; | |
uint32_t type; | |
union { | |
u_char data[0]; | |
uint32_t data32[0]; | |
uint64_t data64[0]; | |
} u; | |
} __packed mp4_atom_hdr_t; | |
typedef struct { | |
mp4_atom_hdr_t hdr; | |
u_char version; | |
u_char flags[3]; | |
uint32_t ctype; | |
uint32_t subtype; | |
uint32_t manufacturer; | |
uint32_t cflags; | |
uint32_t cflags_mask; | |
u_char name[0]; | |
} __packed mp4_atom_hdlr_t; | |
typedef struct { | |
mp4_atom_hdr_t hdr; | |
u_char version; | |
u_char prof_ind; | |
u_char prof_comp; | |
u_char level; | |
u_char sf_len; | |
u_char data[0]; | |
} __packed mp4_atom_avcC_t; | |
typedef struct { | |
mp4_atom_hdr_t hdr; | |
u_char version; | |
u_char flags[3]; | |
uint32_t entries; | |
u_char reserved[16]; | |
uint16_t width; | |
uint16_t height; | |
uint32_t hres; | |
uint32_t vres; | |
uint32_t data_size; | |
uint16_t fpsamp; | |
u_char codec_name[32]; | |
uint16_t bpcs; | |
uint16_t ct_id; | |
mp4_atom_avcC_t avcC; | |
} __packed mp4_atom_avc1_t; | |
typedef struct { | |
mp4_atom_hdr_t hdr; | |
u_char version; | |
u_char flags[3]; | |
uint32_t entries; | |
union { | |
mp4_atom_hdr_t hdr; | |
mp4_atom_avc1_t avc1; | |
} entry; | |
} __packed mp4_atom_stsd_t; | |
typedef struct { | |
mp4_atom_hdr_t hdr; | |
u_char version; | |
u_char flags[3]; | |
uint32_t sample_size; | |
uint32_t sample_cnt; | |
uint32_t tbl[0]; | |
} __packed mp4_atom_stsz_t; | |
typedef struct { | |
mp4_atom_hdr_t hdr; | |
u_char version; | |
u_char flags[3]; | |
uint32_t entries; | |
uint32_t tbl[0]; | |
} __packed mp4_atom_stss_t; | |
typedef struct { | |
uint32_t first_chunk; | |
uint32_t sample_cnt; | |
uint32_t desc_id; | |
} __packed mp4_stsc_entry_t; | |
typedef struct { | |
mp4_atom_hdr_t hdr; | |
u_char version; | |
u_char flags[3]; | |
uint32_t sample_cnt; | |
mp4_stsc_entry_t tbl[0]; | |
} __packed mp4_atom_stsc_t; | |
typedef struct { | |
mp4_atom_hdr_t hdr; | |
u_char version; | |
u_char flags[3]; | |
uint32_t chunk_cnt; | |
union { | |
uint32_t tbl[0]; | |
uint64_t tbl64[0]; | |
} u; | |
} __packed mp4_atom_stco_t; | |
typedef struct { | |
mp4_stsc_entry_t *entry, *end; | |
uint32_t chunk_count; | |
uint32_t chunk_no; | |
uint32_t next; | |
uint32_t samp_left; | |
uint32_t samp_cnt; | |
} mp4_stsc_ptr_t; | |
AVCodec *h264; | |
static int mp4_stsc_ptr_init(mp4_stsc_ptr_t *ptr, mp4_atom_stsc_t *atom, uint32_t chunk_count) | |
{ | |
uint32_t entry_cnt = be32toh(atom->sample_cnt); | |
if (be32toh(atom->hdr.size) != sizeof(mp4_atom_stsc_t) + sizeof(mp4_stsc_entry_t) * entry_cnt) { | |
fprintf(stderr, "mp4_stsc_ptr_init: stsc atom size doesn't match entry count\n"); | |
return -1; | |
} | |
if (entry_cnt == 0) { | |
fprintf(stderr, "mp4_stsc_ptr_init: stsc table is empty!\n"); | |
return -1; | |
} | |
ptr->chunk_count = chunk_count; | |
ptr->chunk_no = be32toh(atom->tbl[0].first_chunk); | |
ptr->samp_cnt = be32toh(atom->tbl[0].sample_cnt); | |
ptr->samp_left = be32toh(atom->tbl[0].sample_cnt); | |
ptr->entry = atom->tbl + 1; | |
ptr->end = atom->tbl + entry_cnt; | |
if (entry_cnt == 1) | |
ptr->next = ptr->chunk_count; | |
else | |
ptr->next = be32toh(atom->tbl[1].first_chunk); | |
return 0; | |
} | |
static int mp4_stsc_ptr_advance_entry2(mp4_stsc_ptr_t *ptr) { | |
if (ptr->entry >= ptr->end) { | |
ptr->samp_left = 1; | |
return -1; | |
} | |
ptr->samp_cnt = be32toh(ptr->entry++->sample_cnt); | |
if (ptr->entry == ptr->end) | |
ptr->next = ptr->chunk_count + 1; | |
else | |
ptr->next = be32toh(ptr->entry->first_chunk); | |
ptr->samp_left = ptr->samp_cnt; | |
return 0; | |
} | |
static int mp4_stsc_ptr_advance_entry(mp4_stsc_ptr_t *ptr) { | |
if (++ptr->chunk_no >= ptr->next) | |
return mp4_stsc_ptr_advance_entry2(ptr); | |
ptr->samp_left = ptr->samp_cnt; | |
return 0; | |
} | |
static int mp4_stsc_ptr_advance(mp4_stsc_ptr_t *ptr) { | |
if (--ptr->samp_left) | |
return 0; | |
if (mp4_stsc_ptr_advance_entry(ptr)) { | |
return -1; | |
} | |
return 0; | |
} | |
static int mp4_stsc_ptr_advance_n(mp4_stsc_ptr_t *ptr, uint32_t n) { | |
while (n >= ptr->samp_left) { | |
n -= ptr->samp_left; | |
if (mp4_stsc_ptr_advance_entry(ptr) != 0) | |
return -1; | |
} | |
ptr->samp_left -= n; | |
return 0; | |
} | |
mp4_atom_stsd_t *stsd = NULL; | |
mp4_atom_stsc_t *stsc = NULL; | |
mp4_atom_stco_t *stco = NULL; | |
mp4_atom_stss_t *stss = NULL; | |
mp4_atom_stsz_t *stsz = NULL; | |
char co64 = 0; | |
int parse_atoms(uint8_t *ptr, uint8_t *end) { | |
uint64_t size; | |
while (ptr < end) { | |
size = be32toh(*(uint32_t*)ptr); | |
if (size == 1) | |
size = be64toh(*(uint64_t*)(ptr + 8)); | |
if (size < 8) return -1; | |
//fprintf(stderr, "%li %c%c%c%c\n", size, ptr[4],ptr[5],ptr[6],ptr[7]); | |
if (ptr + size > end) return -1; | |
switch (*(uint32_t*)(ptr + 4)) { | |
case ATOM('m', 'o', 'o', 'v'): | |
case ATOM('t', 'r', 'a', 'k'): | |
case ATOM('m', 'd', 'i', 'a'): | |
case ATOM('m', 'i', 'n', 'f'): | |
case ATOM('s', 't', 'b', 'l'): | |
parse_atoms(ptr + 8, ptr + size); break; | |
case ATOM('m', 'p', '4', 'a'): | |
parse_atoms(ptr + 36, ptr + size); break; | |
case ATOM('h', 'd', 'l', 'r'): | |
if (((mp4_atom_hdlr_t*)ptr)->subtype != ATOM('v','i','d','e')) | |
return 0; | |
break; | |
case ATOM('s', 't', 's', 'd'): | |
stsd = (mp4_atom_stsd_t*)ptr; | |
if (stsd->entry.hdr.type != ATOM('a','v','c','1') && | |
stsd->entry.hdr.type != ATOM('h','2','6','4') && | |
stsd->entry.hdr.type != ATOM('H','2','6','4')) { | |
stsd = NULL; | |
return -1; | |
} | |
break; | |
case ATOM('s', 't', 's', 'c'): | |
stsc = (mp4_atom_stsc_t*)ptr; | |
break; | |
case ATOM('c', 'o', '6', '4'): | |
co64 = 1; | |
case ATOM('s', 't', 'c', 'o'): | |
stco = (mp4_atom_stco_t*)ptr; | |
break; | |
case ATOM('s', 't', 's', 's'): | |
stss = (mp4_atom_stss_t*)ptr; | |
break; | |
case ATOM('s', 't', 's', 'z'): | |
stsz = (mp4_atom_stsz_t*)ptr; | |
break; | |
} | |
ptr += size; | |
} | |
return 0; | |
} | |
int black_tresh = 32; | |
int frag_tresh = 20; | |
int process_file(char *filename) | |
{ | |
struct stat st; | |
uint8_t *map, *pixptr; | |
uint32_t *ptr, *end; | |
uint32_t fnum = 1; | |
int fd, i, len, mx; | |
int x,y; | |
int got_frame; | |
AVFrame frame; | |
AVPacket pkt; | |
AVCodecContext avctx; | |
mp4_stsc_ptr_t sp; | |
uint64_t co; | |
fd = open(filename, O_RDONLY); | |
fstat(fd, &st); | |
map = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); | |
if (map == MAP_FAILED) { | |
return -1; | |
} | |
parse_atoms(map, map + st.st_size); | |
if (!stsd || !stsc || !stco || !stss || !stsz) { | |
fprintf(stderr, "Invalid mp4\n"); | |
return -1; | |
} | |
av_init_packet(&pkt); | |
memset(&frame, 0, sizeof(frame)); | |
av_frame_unref(&frame); | |
avcodec_get_context_defaults3(&avctx, h264); | |
avctx.extradata = &stsd->entry.avc1.avcC.version; | |
avctx.extradata_size = be32toh(stsd->entry.avc1.avcC.hdr.size) - 8; | |
avcodec_open2(&avctx, h264, NULL); | |
mp4_stsc_ptr_init(&sp, stsc, be32toh(stco->chunk_cnt)); | |
ptr = stss->tbl; | |
end = ptr + be32toh(stss->entries); | |
for (;ptr < end; ptr++) { | |
if (mp4_stsc_ptr_advance_n(&sp, be32toh(*ptr) - fnum) != 0) { | |
fprintf(stderr, "stsc advance failed!\n"); | |
break; | |
} | |
fnum = be32toh(*ptr); | |
pkt.data = map + (co64 ? be64toh(stco->u.tbl64[sp.chunk_no - 1]) : be32toh(stco->u.tbl[sp.chunk_no - 1])); | |
if (fnum > be32toh(stsz->sample_cnt)){ | |
fprintf(stderr, "frame %u is out of range!\n", fnum); | |
break; | |
} | |
for (i = fnum - 1 - (be32toh(sp.entry[-1].sample_cnt) - sp.samp_left); i < (fnum - 1); i++) | |
pkt.data += be32toh(stsz->tbl[i]); | |
pkt.size = be32toh(stsz->tbl[i]); | |
while (pkt.size > 0) { | |
len = avcodec_decode_video2(&avctx, &frame, &got_frame, &pkt); | |
if (len < 0) { | |
fprintf(stderr, "Decode frame %i failed\n", i); | |
goto ex; | |
} | |
if (got_frame) { | |
if (!frame.data[0]) | |
break; | |
i = 0; | |
mx = (int64_t)frame.width * frame.height / frag_tresh; | |
for (y = 0; y < frame.height; y++) { | |
pixptr = frame.data[0] + y * frame.linesize[0]; | |
for (x = 0; x < frame.height; x++) | |
if (*pixptr++ > black_tresh) { | |
i += *pixptr; | |
if (i > mx) { | |
y = frame.height + 1; | |
break; | |
} | |
} | |
} | |
if (y == frame.height) | |
fprintf(stderr, "%i\n", fnum - 1); | |
} | |
pkt.data += len; | |
pkt.size -= len; | |
} | |
} | |
ex: | |
avcodec_close(&avctx); | |
munmap(map, st.st_size); | |
close(fd); | |
} | |
int main(int argc, char **argv) | |
{ | |
int i; | |
avcodec_register_all(); | |
h264 = avcodec_find_decoder_by_name("h264"); | |
//avcodec_register(&ff_h264_decoder); | |
for (i = 1; i < argc; i++) | |
process_file(argv[i]); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment