/* This file is in the public domain. */ #include #include #include #include #include #include #include #include #include "codecintf.h" extern const char *__progname; typedef enum { TT_UNSET = 1, TT_AUDIO, TT_VIDEO, } TRACKTYPE; typedef struct track TRACK; typedef struct stts_data STTS_DATA; typedef struct stsc_data STSC_DATA; typedef struct chunk CHUNK; typedef struct sample SAMPLE; struct sample { unsigned int cue; /* time */ unsigned int len; /* bytes */ unsigned int loc; } ; struct chunk { unsigned int pos; unsigned int id; unsigned int len; } ; struct stsc_data { unsigned int first; unsigned int space; unsigned int id; } ; struct stts_data { unsigned int n; unsigned int dur; } ; struct track { TRACK *link; int serial; TRACKTYPE type; unsigned char codec[4]; unsigned int data; unsigned int len; unsigned int timescale; unsigned long long int length; unsigned int stsd_type_at; unsigned int stsd_at; unsigned int stsd_len; unsigned int stts_n; STTS_DATA *stts_tbl; unsigned int stss_n; unsigned int *stss_tbl; unsigned int stsc_n; STSC_DATA *stsc_tbl; unsigned int samplesize; unsigned int stsz_n; unsigned int *stsz_tbl; unsigned int stco_n; unsigned int *stco_tbl; int nchunks; CHUNK *chunks; unsigned int fixeddur; int nsamples; SAMPLE *samples; struct { unsigned int avc_config_loc; unsigned int avc_config_len; } v; struct { unsigned int setup_loc; unsigned int setup_len; } a; } ; static int file_fd; static const char *filename; static const unsigned char *file; static unsigned int filesize; static int trackserial = 0; static unsigned int moov_at = 0; static unsigned int moov_len; static unsigned int mdat_at = 0; static unsigned int mdat_len; static TRACK *tracks = 0; #define FOURCHAR(a,b,c,d) ( ((a) * 0x01000000) + \ ((b) * 0x010000) + \ ((c) * 0x0100) + \ ((d) * 0x01) ) /* * Comments below that talk about "ver 1" refer here. * * There are various places where the file contains a version number * which specifies, for example, whether another value is 32 bits or * 64 bits. In at least some of these cases, my reference specifies * that one thing happens for version 1 and something else for all * other versions. I do not know whether the spec really does specify * that, or "0 means this, 1 means that, other values reserved", or * what. (The actual spec appears to be a pay-to-play * pseudo-standard; apparently they'd rather have people implementing * based on collections of miscellanous tidbits, as I'm doing, rather * than referring to the real spec.) */ static void usage(void) { fprintf(stderr,"Usage: %s filename\n",__progname); exit(1); } static void map_file(const char *name) { struct stat stb; void *mmrv; filename = name; file_fd = open(name,O_RDONLY,0); if (file_fd < 0) { fprintf(stderr,"%s: %s: %s\n",__progname,name,strerror(errno)); exit(1); } fstat(file_fd,&stb); filesize = stb.st_size; if (filesize != stb.st_size) { fprintf(stderr,"%s: %s: too large\n",__progname,name); exit(1); } mmrv = mmap(0,stb.st_size,PROT_READ,MAP_FILE|MAP_SHARED,file_fd,0); if (mmrv == MAP_FAILED) { fprintf(stderr,"%s: mmap %s: %s\n",__progname,name,strerror(errno)); exit(1); } file = mmrv; } static unsigned int get2be(unsigned int o) { return( (file[o ] * 0x0100) + (file[o+1] * 0x01) ); } static unsigned int get3be(unsigned int o) { return( (file[o ] * 0x010000) + (file[o+1] * 0x0100) + (file[o+2] * 0x01) ); } static unsigned int get4be(unsigned int o) { return( (file[o ] * 0x01000000) + (file[o+1] * 0x010000) + (file[o+2] * 0x0100) + (file[o+3] * 0x01) ); } static unsigned long long int get8be(unsigned int o) { return( (file[o ] * 0x0100000000000000ULL) + (file[o+1] * 0x01000000000000ULL) + (file[o+2] * 0x010000000000ULL) + (file[o+3] * 0x0100000000ULL) + (file[o+4] * 0x01000000ULL) + (file[o+5] * 0x010000ULL) + (file[o+6] * 0x0100ULL) + (file[o+7] * 0x01ULL) ); } static void print_str_vis(FILE *to, const unsigned char *d, int len) { int i; for (i=len;i>0;i--) { if (*d < 32) { fprintf(to,"^%c",64+*d); } else if (*d < 127) { putc(*d,to); } else if (*d == 127) { fprintf(to,"^?"); } else if (*d < 160) { fprintf(to,"^%c",64+*d); } else { putc(*d,to); } d ++; } } static void scan_chunks(int depth, unsigned int start, unsigned int len, void (*chunk)(const unsigned char [4], unsigned int, unsigned int, void *), void *arg) { unsigned int o; unsigned int l; int i; o = 0; while (1) { if (o == len) break; if (o+8 > len) { printf("*** chunk overrun 1 (%u+8 > %u)\n",o,len); exit(1); } l = get4be(start+o); if (l == 1) { printf("*** 64-bit chunk length at %u+%u\n",start,o); exit(1); } if (l < 8) { printf("*** runt chunk (%u) at %u+%u\n",l,start,o); exit(1); } for (i=depth;i>0;i--) putchar('\t'); printf("%08x+%08x: chunk len=%x type=",start,o,l); print_str_vis(stdout,file+start+o+4,4); printf("\n"); if (o+l > len) { printf("*** chunk overrun 2 (%u+%u > %u)\n",o,l,len); exit(1); } (*chunk)(file+start+o+4,start+o+8,l-8,arg); o += l; } } static void file_chunk(const unsigned char type[4], unsigned int data, unsigned int len, void *arg __attribute__((__unused__))) { int i; switch (FOURCHAR(type[0],type[1],type[2],type[3])) { default: printf("skipping unknown chunk\n"); return; break; case FOURCHAR('f','t','y','p'): if (len < 8) { printf("*** ftyp chunk too small (%u)\n",len); exit(1); } if (len % 4) { printf("*** ftyp chunk length (%u) isn't a multiple of 4\n",len); exit(1); } printf("ftyp: major `"); print_str_vis(stdout,file+data,4); printf("' minor %08x compat:",get4be(data+4)); for (i=8;iserial = trackserial ++; t->type = TT_UNSET; t->data = data; t->len = len; t->stsd_at = 0; t->stts_n = 0; t->stss_n = 0; t->stsc_n = 0; t->stsz_n = 0; t->stco_n = 0; t->link = tracks; tracks = t; } static void moov_chunk(const unsigned char type[4], unsigned int data, unsigned int len, void *arg __attribute__((__unused__))) { switch (FOURCHAR(type[0],type[1],type[2],type[3])) { default: printf("skipping unknown chunk\n"); return; break; case FOURCHAR('m','v','h','d'): { unsigned char ver; unsigned long long int dur; unsigned int ts; if (len < 1) { printf("*** mvhd chunk too small 1 (%u)\n",len); exit(1); } /* XXX what are the rest of the values here? */ ver = file[data]; /* XXX see "ver 1" comment at file top */ if (len < ((ver==1) ? 32 : 20)) { printf("*** mvhd chunk too small 2 (%u, ver=%d)\n",len,ver); exit(1); } if (ver == 1) { ts = get4be(data+20); dur = get8be(data+24); } else { ts = get4be(data+12); dur = get4be(data+16); } printf("movie: ts=%u dur=%llu\n",ts,dur); } break; case FOURCHAR('t','r','a','k'): printf("saving trak chunk location\n"); save_track(data,len); break; case FOURCHAR('u','d','t','a'): printf("skipping udta chunk\n"); break; } } static void stbl_chunk(const unsigned char type[4], unsigned int data, unsigned int len, void *tv) { TRACK *t; t = tv; switch (FOURCHAR(type[0],type[1],type[2],type[3])) { default: printf("\t\t\tskipping unknown chunk\n"); return; break; case FOURCHAR('s','t','s','d'): { unsigned int n; unsigned int o; unsigned int l; if (len < 8) { printf("*** stsd chunk too small (%u)\n",len); exit(1); } /* XXX what are the first four bytes? */ n = get4be(data+4); printf("\t\t\tDescription list, n = %u\n",n); o = 8; for (;n>0;n--) { if (len < o+8) { printf("*** stsd chunk overrun 1 (%u < %u+8)\n",len,o); exit(1); } l = get4be(data+o); if (l < 8) { printf("*** stsd runt (%u)\n",l); exit(1); } if (len < o+l) { printf("*** stsd chunk overrun 2 (%u < %u+%u)\n",len,o,l); exit(1); } printf("\t\t\t\ttype = `"); print_str_vis(stdout,file+data+o+4,4); printf("', len = %u\n",l); if (t->stsd_at) { printf("*** extra stsd for this track\n"); exit(1); } t->stsd_type_at = data + o + 4; t->stsd_at = data + o + 8; t->stsd_len = l - 8; o += l; } } break; case FOURCHAR('s','t','t','s'): { unsigned int n; unsigned int o; unsigned long long int total; int i; if (t->stts_n) { printf("*** extra stts for this track\n"); exit(1); } if (len < 8) { printf("*** stts chunk too small 1 (%u)\n",len); exit(1); } /* XXX what are the first four bytes? */ n = get4be(data+4); printf("\t\t\tSample duration table, n = %u\n",n); if (len < 8+(n*8)) { printf("*** stts chunk too small 2 (%u < %u)\n",len,8*(n*8)); exit(1); } t->stts_n = n; t->stts_tbl = n ? malloc(n*sizeof(*t->stts_tbl)) : 0; i = 0; o = 8; for (i=0;istts_tbl[i].n = get4be(data+o); t->stts_tbl[i].dur = get4be(data+o+4); o += 8; } total = 0; for (i=0;istts_n;i++) { // printf("\t\t\t\t[%d] n=%u dur=%u\n",i,t->stts_tbl[i].n,t->stts_tbl[i].dur); total += t->stts_tbl[i].n * 1ULL * t->stts_tbl[i].dur; } if (total != t->length) { printf("*** warning: track length mismatch: %llu != %llu\n",t->length,total); /* no exit here */ } } break; case FOURCHAR('s','t','s','s'): { unsigned int n; unsigned int o; int i; if (t->stss_n) { printf("*** extra stss for this track\n"); exit(1); } if (len < 8) { printf("*** stss chunk too small 1 (%u)\n",len); exit(1); } n = get4be(data+4); printf("\t\t\tSyncing samples table, n = %u, ver = %u, flags = %02x %02x %02x\n", n,file[data+3],file[data],file[data+1],file[data+2]); if (len < 8+(n*4)) { printf("*** stss chunk too small 2 (%u < %u)\n",len,8+(n*4)); exit(1); } t->stss_n = n; t->stss_tbl = n ? malloc(n*sizeof(*t->stss_tbl)) : 0; i = 0; o = 8; for (i=0;istss_tbl[i] = get4be(data+o); o += 4; } // for (i=0;istss_n;i++) printf("\t\t\t\t[%d] %u\n",i,t->stss_tbl[i]); } break; case FOURCHAR('s','t','s','c'): { unsigned int n; unsigned int o; int i; if (t->stsc_n) { printf("*** extra stsc for this track\n"); exit(1); } if (len < 8) { printf("*** stsc chunk too small 1 (%u)\n",len); exit(1); } n = get4be(data+4); printf("\t\t\tSample->chunk table, n = %u, ver = %u, flags = %02x %02x %02x\n", n,file[data+3],file[data],file[data+1],file[data+2]); if (len < 8+(n*12)) { printf("*** stsc chunk too small 2 (%u < %u)\n",len,8+(n*12)); exit(1); } t->stsc_n = n; t->stsc_tbl = n ? malloc(n*sizeof(*t->stsc_tbl)) : 0; i = 0; o = 8; for (i=0;istsc_tbl[i].first = get4be(data+o) - 1; t->stsc_tbl[i].space = get4be(data+o+4); t->stsc_tbl[i].id = get4be(data+o+8); o += 12; } // for (i=0;istsc_n;i++) // { printf("\t\t\t\t[%d] first=%u space=%u id=%u\n", // i,t->stsc_tbl[i].first,t->stsc_tbl[i].space,t->stsc_tbl[i].id); // } } break; case FOURCHAR('s','t','s','z'): { unsigned int n; unsigned int o; int i; if (t->stsz_n) { printf("*** extra stsz for this track\n"); exit(1); } if (len < 12) { printf("*** stsz chunk too small 1 (%u)\n",len); exit(1); } n = get4be(data+8); t->samplesize = get4be(data+4); printf("\t\t\tSample size table, n = %u, ver = %u, flags = %02x %02x %02x", n,file[data+3],file[data],file[data+1],file[data+2]); if (t->samplesize == 0) { printf(", ss variable\n"); if (len < 12+(n*4)) { printf("*** stsz chunk too small 2 (%u < %u)\n",len,12+(n*4)); exit(1); } t->stsz_n = n; t->stsz_tbl = n ? malloc(n*sizeof(*t->stsz_tbl)) : 0; i = 0; o = 12; for (i=0;istsz_tbl[i] = get4be(data+o); o += 4; } // for (i=0;istsz_n;i++) // { printf("\t\t\t\t[%d] %08x\n",i,t->stsz_tbl[i]); // } } else { printf(", ss = %u\n",t->samplesize); } } break; case FOURCHAR('s','t','c','o'): { unsigned int n; unsigned int o; int i; if (t->stco_n) { printf("*** extra stco for this track\n"); exit(1); } if (len < 8) { printf("*** stco chunk too small 1 (%u)\n",len); exit(1); } n = get4be(data+4); printf("\t\t\tChunk offset table, n = %u\n",n); /* * mplayer doesn't check for duplicates and does * if (n > t->stco_n) * { free and reallocate t->stco_tbl; * t->stco_n = n; * } * and then expects to find t->stco_n entries in the file, * even if that's more than n. This looks weird to me, but, * because we fall over in the presence of a second stco, I * don't have to worry about it. */ if (len < 8+(n*4)) { printf("*** stco chunk too small 2 (%u < %u)\n",len,8+(n*4)); exit(1); } t->stco_n = n; t->stco_tbl = n ? malloc(n*sizeof(*t->stco_tbl)) : 0; i = 0; o = 8; for (i=0;istco_tbl[i] = get4be(data+o); o += 4; } // for (i=0;istco_n;i++) // { printf("\t\t\t\t[%d] %08x\n",i,t->stco_tbl[i]); // } } break; } } static void minf_chunk(const unsigned char type[4], unsigned int data, unsigned int len, void *tv) { TRACK *t; t = tv; switch (FOURCHAR(type[0],type[1],type[2],type[3])) { default: printf("\t\tskipping unknown chunk\n"); return; break; case FOURCHAR('s','m','h','d'): printf("\t\tTrack is audio\n"); t->type = TT_AUDIO; t->a.setup_len = 0; break; case FOURCHAR('v','m','h','d'): printf("\t\tTrack is video\n"); t->type = TT_VIDEO; t->v.avc_config_len = 0; break; case FOURCHAR('d','i','n','f'): printf("\t\tUnknown\n"); break; case FOURCHAR('s','t','b','l'): printf("\t\tUnknown\n"); scan_chunks(3,data,len,&stbl_chunk,t); break; } } static void mdia_chunk(const unsigned char type[4], unsigned int data, unsigned int len, void *tv) { TRACK *t; t = tv; switch (FOURCHAR(type[0],type[1],type[2],type[3])) { default: printf("\tskipping unknown chunk\n"); return; break; case FOURCHAR('m','d','h','d'): { int ver; /* XXX what are the rest of the values here? */ ver = file[data]; /* XXX see "ver 1" comment at file top */ if (len < ((ver==1) ? 32 : 20)) { printf("*** mdhd chunk too small (%u, ver=%d)\n",len,ver); exit(1); } if (ver == 1) { t->timescale = get4be(data+20); t->length = get8be(data+24); } else { t->timescale = get4be(data+12); t->length = get4be(data+16); } printf("\ttimescale = %u, length = %llu\n",t->timescale,t->length); } break; case FOURCHAR('h','d','l','r'): { int slen; if (len < 25) { printf("*** hdlr chunk too small 1 (%u)\n",len); exit(1); } slen = file[data+24]; if (len < 25+slen) { printf("*** hdlr chunk too small 2 (%u, slen=%d)\n",len,slen); exit(1); } printf("\tHandler block: len=%u\n",len); printf("\t\tword0 %08x\n",get4be(data)); printf("\t\ttype "); print_str_vis(stdout,file+data+4,4); printf("\n"); printf("\t\tsubtype "); print_str_vis(stdout,file+data+8,4); printf("\n"); printf("\t\tmfg "); print_str_vis(stdout,file+data+12,4); printf("\n"); printf("\t\tcflags %08x\n",get4be(data+16)); printf("\t\tcmask %08x\n",get4be(data+20)); printf("\t\tstring "); print_str_vis(stdout,file+data+25,slen); printf("\n"); /* type=mhlr, type=dhlr - set media and data handler types */ } break; case FOURCHAR('m','i','n','f'): printf("\tMedia info\n"); scan_chunks(2,data,len,&minf_chunk,t); break; } } static void trak_chunk(const unsigned char type[4], unsigned int data, unsigned int len, void *tv) { TRACK *t; t = tv; switch (FOURCHAR(type[0],type[1],type[2],type[3])) { default: printf("skipping unknown chunk\n"); return; break; case FOURCHAR('t','k','h','d'): printf("Track header: len=%u\n",len); if (len < 84) { printf("*** tkhd chunk too short (%u)\n",len); exit(1); } printf("\tver %d\n",file[data]); printf("\tflags %02x %02x %02x\n",file[data+1],file[data+2],file[data+3]); printf("\tcreate %u\n",get4be(data+4)); printf("\tmodify %u\n",get4be(data+8)); printf("\tid %08x\n",get4be(data+12)); printf("\t(resv) %08x\n",get4be(data+16)); printf("\tdur %u\n",get4be(data+20)); printf("\t(resv) %016llx\n",get8be(data+24)); printf("\tlayer %u\n",get2be(data+32)); printf("\taltgrp %u\n",get2be(data+34)); printf("\tvolume %u\n",get2be(data+36)); printf("\t(resv) %04x\n",get2be(data+38)); printf("\tmatrix %02x ... %02x\n",file[data+40],file[data+75]); printf("\twidth %u\n",get4be(data+76)); printf("\theight %u\n",get4be(data+80)); break; case FOURCHAR('m','d','i','a'): printf("Media stream\n"); scan_chunks(1,data,len,&mdia_chunk,t); break; } } static unsigned int mp4_len(unsigned int base, unsigned int *op, unsigned int maxo) { unsigned int o; unsigned int l; unsigned char b; int i; o = *op; l = 0; for (i=4;i>0;i--) /* XXX is the 4 limit correct? */ { if (o >= maxo) { printf("*** overrun in mp4_len\n"); exit(1); } b = file[base+o++]; l = (l << 7) | (b & 0x7f); if (! (b & 0x80)) break; } *op = o; return(l); } static void audio_parse_esds(TRACK *t, unsigned int data, unsigned int len) { unsigned int o; unsigned int l; void overrun(void) { printf("*** esds data overrun\n"); exit(1); } #define REQUIRE(n) do { if (o+(n) > len) overrun(); } while (0) o = 0; REQUIRE(5); printf("ESDS version %u, flags %06x\n",file[data],get3be(data+1)); o = 4; if (file[data+o++] == 0x03) /* Descriptor tag */ { l = mp4_len(data,&o,len); REQUIRE(3); printf("ESDS (len %u) ID %04x, priority = %d\n",l,get2be(data+o),file[data+o+2]); o += 3; } else { REQUIRE(2); printf("ESDS (len 2) ID %04x\n",get2be(data+o)); o += 2; } REQUIRE(1); if (file[data+o++] != 0x04) /* decoder config descr */ { printf("*** esds data missing decoder config descr\n"); exit(1); } l = mp4_len(data,&o,len); REQUIRE(13); printf("ESDS decoder config (len %d):\n",l); printf(" object type ID = %u\n",file[data+o]); printf(" stream type = %u\n",file[data+o+1]); printf(" buffer size db = %u\n",get3be(data+o+2)); printf(" max bitrate = %u\n",get4be(data+o+5)); printf(" avg bitrate = %u\n",get4be(data+o+9)); o += 13; REQUIRE(1); if (file[data+o++] != 0x05) return; /* decoder-specific descr */ l = mp4_len(data,&o,len); t->a.setup_loc = data + o; t->a.setup_len = l; printf("ESDS decoder-specific config data len %u\n",l); printf(" data:"); for (;l>0;l--) { REQUIRE(1); printf(" %02x",file[data+o++]); } printf("\n"); REQUIRE(1); if (file[data+o++] != 0x06) return; /* SL config descr */ l = mp4_len(data,&o,len); printf("ESDS SL config data len %u\n",l); printf(" data:"); for (;l>0;l--) { REQUIRE(1); printf(" %02x",file[data+o++]); } printf("\n"); #undef REQUIRE } static void stsd_audio_chunk(const unsigned char type[4], unsigned int data, unsigned int len, void *tv) { TRACK *t; t = tv; switch (FOURCHAR(type[0],type[1],type[2],type[3])) { default: printf("skipping unknown chunk\n"); return; break; case FOURCHAR('e','s','d','s'): audio_parse_esds(t,data,len); break; } } static void crack_stsd_audio(TRACK *t) { printf("version = %u\n",get2be(t->stsd_at+8)); printf("revision = %u\n",get2be(t->stsd_at+10)); printf("vendor ID = %08x\n",get4be(t->stsd_at+12)); printf("channels = %u\n",get2be(t->stsd_at+16)); printf("sample size = %u\n",get2be(t->stsd_at+18)); printf("compression ID = %u\n",get2be(t->stsd_at+20)); printf("packet size = %u\n",get2be(t->stsd_at+22)); printf("sample rate = %u\n",get2be(t->stsd_at+24)); if (t->stsd_len > 28) scan_chunks(0,t->stsd_at+28,t->stsd_len-28,&stsd_audio_chunk,t); } static void stsd_video_chunk(const unsigned char type[4], unsigned int data, unsigned int len, void *tv) { TRACK *t; t = tv; switch (FOURCHAR(type[0],type[1],type[2],type[3])) { default: printf("skipping unknown chunk\n"); return; break; case FOURCHAR('a','v','c','C'): printf("saving AVC config data\n"); t->v.avc_config_loc = data; t->v.avc_config_len = len; break; } } static void crack_stsd_video(TRACK *t) { printf("version = %u\n",get2be(t->stsd_at+8)); printf("revision = %u\n",get2be(t->stsd_at+10)); printf("vendor ID = %08x\n",get4be(t->stsd_at+12)); printf("temporal quality = %u\n",get4be(t->stsd_at+16)); printf("spatial quality = %u\n",get4be(t->stsd_at+20)); printf("size = %ux%u\n",get2be(t->stsd_at+24),get2be(t->stsd_at+26)); /* XXX what are the bytes at 30, 31, 34, and 35? */ printf("DPI = %ux%u\n",get2be(t->stsd_at+28),get2be(t->stsd_at+32)); /* XXX what are the bytes at 36...39? */ printf("frames per sample = %u\n",get2be(t->stsd_at+40)); printf("compressor name = "); print_str_vis(stdout,file+t->stsd_at+42,32); printf("\n"); printf("depth = %u\n",get2be(t->stsd_at+74)); printf("CLUT ID = %04x\n",get2be(t->stsd_at+76)); if (t->stsd_len > 78) scan_chunks(0,t->stsd_at+78,t->stsd_len-78,&stsd_video_chunk,t); } static void post_trak(TRACK *t) { int i; int j; int k; int l; unsigned int loc; printf("track #%d: type ",t->serial); switch (t->type) { default: abort(); break; case TT_UNSET: printf("UNSET\n"); return; break; case TT_AUDIO: printf("AUDIO"); break; case TT_VIDEO: printf("VIDEO"); break; } printf(" chunks=%u",t->stco_n); printf(" samples=%u",t->stsz_n); printf(" length=%llu",t->length); printf(" timescale=%u",t->timescale); printf(" (duration %g)",t->length/(double)t->timescale); printf("\n"); if (! t->stco_n) { printf("no chunks\n"); return; } t->nchunks = t->stco_n; t->chunks = malloc(t->nchunks*sizeof(CHUNK)); j = t->stsc_n - 1; for (i=t->nchunks-1;i>=0;i--) { if (i < t->stsc_tbl[j].first) { j --; if (j < 0) { printf("*** stsc table underflow\n"); exit(1); } if (i < t->stsc_tbl[j].first) /* still */ { printf("*** stsc table redundancy\n"); exit(1); } } t->chunks[i].pos = t->stco_tbl[i]; t->chunks[i].id = t->stsc_tbl[j].id; t->chunks[i].len = t->stsc_tbl[j].space; } printf("Chunk table:\n"); for (i=0;inchunks;i++) printf("[%d] pos=%08x id=%u len=%u\n",i,t->chunks[i].pos,t->chunks[i].id,t->chunks[i].len); j = 0; for (i=0;inchunks;i++) j += t->chunks[i].len; k = 0; for (i=t->stts_n-1;i>=0;i--) k += t->stts_tbl[i].n; if (j != k) { printf("*** chunks total (%d) != duration table total (%d)\n",j,k); exit(1); } /* maybe build a fake stsz table here if samplesize != 0? mplayer does. */ if (t->samplesize) { /* * mplayer, at this point, checks to see if the stts table is size * 1 (or size 2 with n==1 in its second element) and either sets a * `duration' field (if so) or coughs and dies (if not). However, * I can't see any setting of duration in any other cases, meaning * it goes uninitialized if this code doesn't run. Fortunately, * all uses of that field run only if stsz_n is nonzero. It's a * bit confusing to read bceause their names for what we call * samplesize and stsz_n are confusingly similar (sample_size and * samplesize), but with attention to that the logic is clear, if * slightly confused because the same datum ("is the sample size * fixed?") is tested in two different ways (by testing * sample_size and samplesize). We call their "duration" field * "fixeddur" to emphasize that it has meaning only for fixed-size * samples. */ if ((t->stts_n == 1) || ((t->stts_n == 2) && (t->stts_tbl[1].n == 1))) { t->fixeddur = t->stts_tbl[0].dur; } else { printf("*** can't handle constant sample size with variable duration\n"); exit(1); } } if (t->stsz_n != k) { printf("*** chunk/duration length (%d) != sample count (%u)\n",k,t->stsz_n); exit(1); } t->nsamples = t->stsz_n; t->samples = malloc(t->nsamples*sizeof(SAMPLE)); for (i=t->nsamples-1;i>=0;i--) t->samples[i].len = t->stsz_tbl[i]; j = 0; k = 0; loc = 0; for (i=0;insamples;i++) { if (k < 1) { if (j >= t->stts_n) { printf("*** stts overrun setting sample durations\n"); exit(1); } k = t->stts_tbl[j].n; l = t->stts_tbl[j].dur; } t->samples[i].cue = loc; loc += l; k --; } if (k || (j != t->stts_n-1)) { printf("*** warning: stts underrun setting sample starts (k=%d j=%d n=%d)\n",k,j,t->stts_n); /* no exit here */ } k = 0; for (i=0;inchunks;i++) { loc = t->chunks[i].pos; for (j=t->chunks[i].len;j>0;j--) { if (k >= t->nsamples) abort(); t->samples[k].loc = loc; loc += t->samples[k].len; k ++; } } if (k != t->nsamples) abort(); printf("Sample table:\n"); for (i=0;insamples;i++) { printf("[%d] loc=%08x len=%08x cue=%u\n",i,t->samples[i].loc,t->samples[i].len,t->samples[i].cue); } printf("stsd data, type "); print_str_vis(stdout,file+t->stsd_type_at,4); printf(" at %08x len %08x\n",t->stsd_at,t->stsd_len); for (i=0;istsd_len;i++) { switch (i & 15) { case 0: printf("%8x: ",i); break; case 8: printf(" "); break; } printf(" %02x",file[t->stsd_at+i]); switch (i & 15) { case 15: printf("\n"); break; } } if (t->stsd_len & 15) printf("\n"); switch (t->type) { default: abort(); break; case TT_AUDIO: if (t->stsd_len < 26) { printf("*** stsd data too short for audio (len %u)\n",t->stsd_len); } else { crack_stsd_audio(t); } break; case TT_VIDEO: if (t->stsd_len < 78) { printf("*** stsd data too short for video (len %u)\n",t->stsd_len); } else { crack_stsd_video(t); } break; } } static void dump_audio_track(TRACK *t) { char *fn; FILE *f; int sx; int frameno; CODEC *c; void gotsound(void *arg __attribute__((__unused__)), const unsigned char *data, int len) { char *ffn; FILE *ff; printf("got sound, %d bytes\n",len); asprintf(&ffn,"FRAME/%d.%d",t->serial,frameno++); ff = fopen(ffn,"w"); if (ff) { fwrite(data,1,len,ff); fclose(ff); } } c = codec_init_audio(file+t->stsd_type_at,&gotsound,0); asprintf(&fn,"TRACK/%d.rawa",t->serial); f = fopen(fn,"w"); if (! f) { printf("*** can't open %s\n",fn); exit(1); } codec_prefix(c,file+t->a.setup_loc,t->a.setup_len); fwrite(file+t->a.setup_loc,1,t->a.setup_len,f); for (sx=0;sxnsamples;sx++) { codec_data(c,file+t->samples[sx].loc,t->samples[sx].len); fwrite(file+t->samples[sx].loc,1,t->samples[sx].len,f); } codec_done(c); fclose(f); } static void dump_video_track(TRACK *t) { char *fn; FILE *f; int sx; int frameno; CODEC *c; void gotpic(void *arg __attribute__((__unused__)), int w, int h, const unsigned char *bits) { char *ffn; FILE *ff; printf("got frame %dx%d\n",w,h); asprintf(&ffn,"FRAME/%d.%d",t->serial,frameno++); ff = fopen(ffn,"w"); if (ff) { fprintf(ff,"P6\n%d %d\n255\n",w,h); fwrite(bits,1,w*h*3,ff); fclose(ff); } } frameno = 0; c = codec_init_video(file+t->stsd_type_at,&gotpic,0); asprintf(&fn,"TRACK/%d.rawv",t->serial); f = fopen(fn,"w"); if (! f) { printf("*** can't open %s\n",fn); exit(1); } codec_prefix(c,file+t->v.avc_config_loc,t->v.avc_config_len); fwrite(file+t->v.avc_config_loc,1,t->v.avc_config_len,f); for (sx=0;sxnsamples;sx++) { codec_data(c,file+t->samples[sx].loc,t->samples[sx].len); fwrite(file+t->samples[sx].loc,1,t->samples[sx].len,f); } codec_done(c); fclose(f); } static void dump_trak(TRACK *t) { switch (t->type) { default: abort(); break; case TT_AUDIO: dump_audio_track(t); break; case TT_VIDEO: dump_video_track(t); break; } } int main(int, char **); int main(int ac, char **av) { if (ac != 2) usage(); map_file(av[1]); printf("scanning file\n"); scan_chunks(0,0,filesize,&file_chunk,0); if (moov_at == 0) printf("no `moov' chunk\n"); if (mdat_at == 0) printf("no `mdat' chunk\n"); if (moov_at && mdat_at) { printf("\nscanning moov chunk\n"); scan_chunks(0,moov_at,moov_len,&moov_chunk,0); if (tracks) { TRACK *t; for (t=tracks;t;t=t->link) { printf("\nscanning trak chunk\n"); scan_chunks(0,t->data,t->len,&trak_chunk,t); printf("trak postprocessing\n"); post_trak(t); dump_trak(t); } } } return(0); }