#include #include #include #include #include #include #include extern const char *__progname; typedef struct track TRACK; typedef struct stts_data STTS_DATA; typedef struct stsc_data STSC_DATA; 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; 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; } ; static int file_fd; static const char *filename; static const unsigned char *file; static unsigned int filesize; 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 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;idata = 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); 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); /* * At least one other implementation doesn't check for * duplicates and does the analog of * 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"); break; case FOURCHAR('v','m','h','d'): printf("\t\tTrack is video\n"); 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 void scan_mdat(void) { unsigned int o; unsigned int l; o = 0; while (1) { if (o == mdat_len) break; if (o+4 > mdat_len) { printf("*** mdat overrun (%u+4 > %u)\n",o,mdat_len); exit(1); } l = get4be(mdat_at+o); if (o+4+l > mdat_len) { printf("*** mdat overrun (%u+4+%u > %u)\n",o,l,mdat_len); exit(1); } printf("mdat block: %08x %08x\n",mdat_at+o+4,l); o += 4 + l; } } 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); } scan_mdat(); } } return(0); }