ref: 7996b424b3ec858adcf6507c424bc5e06773e3fa
dir: /iso.c/
#include <u.h> #include <libc.h> #include <bio.h> #include <ctype.h> #include "common.h" typedef struct Audio Audio; typedef struct Box Box; typedef struct Moof Moof; typedef struct ParamSet ParamSet; typedef struct RunSample RunSample; typedef struct SampleEntry SampleEntry; typedef struct SampleToChunk SampleToChunk; typedef struct TimeToSample TimeToSample; typedef struct Track Track; typedef struct Video Video; struct Audio { u32int format; int objtype; int channels; int bps; int samplerate; }; struct ParamSet { int len; u8int *data; }; struct Video { u32int format; int width; int height; u32int hres; u32int vres; int framecount; char compressor[32]; struct { u8int profile; u8int compat; u8int level; u8int nallen; /* in bytes (1, 2, 4), ie with 1 already added to it */ int nsps; ParamSet *sps; int npps; ParamSet *pps; }avc; }; struct SampleEntry { u32int format; u16int datarefid; Video video; Audio audio; }; struct Box { vlong dsz; vlong offset; vlong dstart; char extended[16]; u32int type; /* full box */ u8int version; u32int flags; union { struct { u32int brand; u32int version; u32int *compat; int ncompat; }ftyp; struct { u64int creation; u64int modification; u64int duration; u32int timescale; }mdhd; struct { u64int creation; u64int modification; u64int duration; u32int timescale; u32int rate; u32int matrix[9]; u32int nexttrack; u16int volume; }mvhd; struct { u32int trackid; struct { u32int descrindex; u32int duration; u32int size; u32int flags; }defsample; }trex; struct { u32int seqnumber; }mfhd; struct { u64int decodetime; }tfdt; struct { u32int trackid; u64int baseoffset; struct { u32int descrindex; u32int duration; u32int size; u32int flags; }defsample; }tfhd; struct { u32int samplecount; s32int dataoffset; u32int firstsampleflags; RunSample *samples; }trun; struct { u16int id; u32int entrycount; SampleEntry *entry; }stsd; struct { u32int entrycount; TimeToSample *entry; }stts; struct { u32int entrycount; /* FIXME entries */ }stss; struct { u32int entrycount; SampleToChunk *entry; }stsc; struct { u32int entrycount; u64int *chunkoffset; }stco_co64; struct { u32int samplesizeeach; u32int samplecount; u32int *samplesize; }stsz; /* FIXME need stz2 as well */ struct { u64int creattime; u64int modtime; u32int trackid; u64int duration; u32int width; u32int height; }tkhd; struct { u32int handlertype; char *name; }hdlr; }; }; struct RunSample { u32int duration; u32int size; u32int flags; vlong timeoffset; vlong offset; }; struct SampleToChunk { u32int firstchunk; u32int samplesperchunk; u32int sdt; }; struct TimeToSample { u32int samplecount; u32int sampledelta; }; enum { HandlerVideo = 0x76696465u, HandlerAudio = 0x736f756eu, BoxUuid = 0x75756964u, BoxFtyp = 0x66747970u, BoxMoov = 0x6d6f6f76u, BoxUdta = 0x75647461u, BoxMvhd = 0x6d766864u, BoxMvex = 0x6d766578u, BoxTrex = 0x74726578u, BoxTrak = 0x7472616bu, BoxEdts = 0x65647473u, BoxElst = 0x656c7374u, BoxTkhd = 0x746b6864u, BoxMdia = 0x6d646961u, BoxMdhd = 0x6d646864u, BoxHdlr = 0x68646c72u, BoxMinf = 0x6d696e66u, BoxDinf = 0x64696e66u, BoxStbl = 0x7374626cu, BoxCtts = 0x63747473u, BoxStsd = 0x73747364u, BoxAvcc = 0x61766343u, BoxEsds = 0x65736473u, BoxStts = 0x73747473u, BoxStsc = 0x73747363u, BoxStco = 0x7374636fu, BoxCo64 = 0x636f3634u, BoxStsz = 0x7374737au, BoxStss = 0x73747373u, BoxVmhd = 0x766d6864u, BoxSidx = 0x73696478u, BoxMoof = 0x6d6f6f66u, BoxMfhd = 0x6d666864u, BoxTraf = 0x74726166u, BoxTfdt = 0x74666474u, BoxTfhd = 0x74666864u, BoxTrun = 0x7472756eu, BoxMdat = 0x6d646174u, }; #define bu16(x) ((x)[0]<<8 | (x)[1]) #define bu32(x) ((x)[0]<<24 | (x)[1]<<16 | (x)[2]<<8 | (x)[3]) #define bu64(x) ((u64int)(x)[0]<<56 | (u64int)(x)[1]<<48 | (u64int)(x)[2]<<40 | (u64int)(x)[3]<<32 | (x)[4]<<24 | (x)[5]<<16 | (x)[6]<<8 | (x)[7]) #define isfullbox(b) ( \ b->type == BoxMvhd || b->type == BoxTrex || b->type == BoxMdhd || b->type == BoxHdlr || \ b->type == BoxMfhd || b->type == BoxTfhd || b->type == BoxTfdt || b->type == BoxTrun || \ b->type == BoxStsd || b->type == BoxStts || b->type == BoxStss || b->type == BoxTkhd || \ b->type == BoxElst || b->type == BoxStsc || b->type == BoxStco || b->type == BoxCo64 || \ b->type == BoxStsz || b->type == BoxCtts || b->type == BoxEsds \ ) #define eBread(sz, e) if(Bread(f, d, (sz)) != (sz)){ werrstr(e); goto err; } struct Moof { Box; Box mfhd; Box tfhd; Box trun; }; struct Track { u32int handlertype; u32int timescale; u64int *chunkoffset; u32int numchunks; u32int samplesizeeach; u32int *samplesize; u32int numsamples; SampleToChunk *stc; u32int numstc; TimeToSample *tts; u32int numtts; Moof *moof; int nmoof; Audio audio; Video video; int id; }; static int dind; static u32int defsamplesize; static Track track; static Track *tracks; static int ntracks; static Moof moof; static SampleEntry *sampleentry; static char ind[16] = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; static int parsebox(Biobuf *f, Box *b, int *eof); int isotypefmt(Fmt *f) { char t[5]; int x; x = va_arg(f->args, int); t[0] = x>>24; t[1] = x>>16; t[2] = x>>8; t[3] = x; t[4] = 0; return fmtstrcpy(f, t); } static u32int tts(Track *t, u32int s) { u32int dt, n, e; dt = 0; for(e = 0; e < t->numtts && s > 0; e++){ n = min(t->tts[e].samplecount, s); dt += t->tts[e].sampledelta * n; s -= n; } return dt; } static void printbox(Box *b) { int i; uvlong u; if(dflag == 0) return; Bprint(&stderr, "%.*s%T\n", dind, ind, b->type); /* full box */ if(isfullbox(b)){ Bprint(&stderr, "\t%.*sversion\t%d\n", dind, ind, b->version); Bprint(&stderr, "\t%.*sflags\t0x%ux\n", dind, ind, b->flags); } if(dflag > 1){ Bprint(&stderr, "\t%.*soffset\t%lld\n", dind, ind, b->offset); Bprint(&stderr, "\t%.*sdstart\t%lld\n", dind, ind, b->dstart); Bprint(&stderr, "\t%.*sdsize\t%lld\n", dind, ind, b->dsz); } if(b->type == BoxFtyp){ Bprint(&stderr, "\t%.*sbrand\t%T\n", dind, ind, b->ftyp.brand); Bprint(&stderr, "\t%.*sversion\t%d\n", dind, ind, b->ftyp.version); Bprint(&stderr, "\t%.*scompat", dind, ind); for(i = 0; i < b->ftyp.ncompat; i++) Bprint(&stderr, "\t%.*s%T", dind, ind, b->ftyp.compat[i]); Bprint(&stderr, "\n"); }else if(b->type == BoxMdhd){ Bprint(&stderr, "\t%.*screation\t%llud\n", dind, ind, b->mdhd.creation); Bprint(&stderr, "\t%.*smodification\t%llud\n", dind, ind, b->mdhd.modification); Bprint(&stderr, "\t%.*stimescale\t%ud\n", dind, ind, b->mdhd.timescale); Bprint(&stderr, "\t%.*sduration\t%llud\n", dind, ind, b->mdhd.duration); }else if(b->type == BoxMvhd){ Bprint(&stderr, "\t%.*screation\t%llud\n", dind, ind, b->mvhd.creation); Bprint(&stderr, "\t%.*smodification\t%llud\n", dind, ind, b->mvhd.modification); Bprint(&stderr, "\t%.*stimescale\t%ud\n", dind, ind, b->mvhd.timescale); Bprint(&stderr, "\t%.*sduration\t%llud\n", dind, ind, b->mvhd.duration); Bprint(&stderr, "\t%.*srate\t0x%ux\n", dind, ind, b->mvhd.rate); Bprint(&stderr, "\t%.*svolume\t0x%ux\n", dind, ind, b->mvhd.volume); Bprint(&stderr, "\t%.*snexttrack\t0x%ux\n", dind, ind, b->mvhd.nexttrack); Bprint(&stderr, "\t%.*smatrix\t0x%ux 0x%ux 0x%ux 0x%ux 0x%ux 0x%ux 0x%ux 0x%ux 0x%ux\n", dind, ind, b->mvhd.matrix[0], b->mvhd.matrix[1], b->mvhd.matrix[2], b->mvhd.matrix[3], b->mvhd.matrix[4], b->mvhd.matrix[5], b->mvhd.matrix[6], b->mvhd.matrix[7], b->mvhd.matrix[8] ); }else if(b->type == BoxTrex){ Bprint(&stderr, "\t%.*strackid\t0x%ux\n", dind, ind, b->trex.trackid); Bprint(&stderr, "\t%.*sdefsample\n", dind, ind); Bprint(&stderr, "\t\t%.*sdescrindex\t0x%ux\n", dind, ind, b->trex.defsample.descrindex); Bprint(&stderr, "\t\t%.*sduration\t%ud\n", dind, ind, b->trex.defsample.duration); Bprint(&stderr, "\t\t%.*ssize\t0x%ux\n", dind, ind, b->trex.defsample.size); Bprint(&stderr, "\t\t%.*sflags\t0x%ux\n", dind, ind, b->trex.defsample.flags); }else if(b->type == BoxMfhd){ Bprint(&stderr, "\t%.*sseqnumber\t%ud\n", dind, ind, b->mfhd.seqnumber); }else if(b->type == BoxTfhd){ Bprint(&stderr, "\t%.*strackid\t%d\n", dind, ind, b->tfhd.trackid); if(b->flags & 1) Bprint(&stderr, "\t%.*sbaseoffset\t%llud\n", dind, ind, b->tfhd.baseoffset); Bprint(&stderr, "\t%.*sdefsample\n", dind, ind); if(b->flags & 2) Bprint(&stderr, "\t\t%.*sdescrindex\t0x%ux\n", dind, ind, b->tfhd.defsample.descrindex); if(b->flags & 8) Bprint(&stderr, "\t\t%.*sduration\t%ud\n", dind, ind, b->tfhd.defsample.duration); if(b->flags & 16) Bprint(&stderr, "\t\t%.*ssize\t0x%ux\n", dind, ind, b->tfhd.defsample.size); if(b->flags & 32) Bprint(&stderr, "\t\t%.*sflags\t0x%ux\n", dind, ind, b->tfhd.defsample.flags); if(b->flags & 0x10000) Bprint(&stderr, "\t%.*sduration is empty\n", dind, ind); if(b->flags & 0x20000) Bprint(&stderr, "\t%.*sdefault base is moof\n", dind, ind); }else if(b->type == BoxTfdt){ Bprint(&stderr, "\t%.*sdecodetime\t%llud\n", dind, ind, b->tfdt.decodetime); }else if(b->type == BoxTrun){ Bprint(&stderr, "\t%.*ssamplecount\t%ud\n", dind, ind, b->trun.samplecount); if(b->flags & 1) Bprint(&stderr, "\t%.*sdataoffset\t%d\n", dind, ind, b->trun.dataoffset); if(b->flags & 2) Bprint(&stderr, "\t%.*sfirstsampleflags\t0x%ux\n", dind, ind, b->trun.firstsampleflags); for(u = 0; dflag > 1 && u < b->trun.samplecount; u++){ Bprint(&stderr, "\t%.*ssamples[%llud]\n", dind, ind, u); Bprint(&stderr, "\t\t%.*sduration\t%ud\n", dind, ind, b->trun.samples[u].duration); Bprint(&stderr, "\t\t%.*ssize\t%ud\n", dind, ind, b->trun.samples[u].size); Bprint(&stderr, "\t\t%.*sflags\t0x%ux\n", dind, ind, b->trun.samples[u].flags); if(b->flags & 0x800) Bprint(&stderr, "\t\t%.*stimeoffset\t%lld\n", dind, ind, b->trun.samples[u].timeoffset); } }else if(b->type == BoxStsd){ Bprint(&stderr, "\t%.*sid\t%d\n", dind, ind, b->stsd.id); Bprint(&stderr, "\t%.*sentry_count\t%ud\n", dind, ind, b->stsd.entrycount); for(u = 0; u < b->stsd.entrycount; u++){ Bprint(&stderr, "\t%.*sentry[%llud]\n", dind, ind, u); Bprint(&stderr, "\t\t%.*sdatarefid\t%d\n", dind, ind, b->stsd.entry[u].datarefid); Bprint(&stderr, "\t\t%.*sformat\t%T\n", dind, ind, b->stsd.entry[u].format); if(b->stsd.entry[u].video.format != 0){ Bprint(&stderr, "\t\t\t%.*swidth\t%d\n", dind, ind, b->stsd.entry[u].video.width); Bprint(&stderr, "\t\t\t%.*sheight\t%d\n", dind, ind, b->stsd.entry[u].video.height); Bprint(&stderr, "\t\t\t%.*shres\t%d\n", dind, ind, b->stsd.entry[u].video.hres); Bprint(&stderr, "\t\t\t%.*svres\t%d\n", dind, ind, b->stsd.entry[u].video.vres); Bprint(&stderr, "\t\t\t%.*sframecount\t%d\n", dind, ind, b->stsd.entry[u].video.framecount); Bprint(&stderr, "\t\t\t%.*scompressor\t%.*s\n", dind, ind, 32, b->stsd.entry[u].video.compressor); if(b->stsd.entry[u].video.format == FmtAvc1){ Bprint(&stderr, "\t\t\t%.*sprofile\t%d\n", dind, ind, b->stsd.entry[u].video.avc.profile); Bprint(&stderr, "\t\t\t%.*scompat\t%d\n", dind, ind, b->stsd.entry[u].video.avc.compat); Bprint(&stderr, "\t\t\t%.*slevel\t%d\n", dind, ind, b->stsd.entry[u].video.avc.level); Bprint(&stderr, "\t\t\t%.*snallen\t%d\n", dind, ind, b->stsd.entry[u].video.avc.nallen); Bprint(&stderr, "\t\t\t%.*snsps\t%d\n", dind, ind, b->stsd.entry[u].video.avc.nsps); for(i = 0; i < b->stsd.entry[u].video.avc.nsps; i++){ Bprint(&stderr, "\t\t\t\t%.*ssps[%d]\n", dind, ind, i); Bprint(&stderr, "\t\t\t\t\t%.*slen\t%d\n", dind, ind, b->stsd.entry[u].video.avc.sps[i].len); Bprint(&stderr, "\t\t\t\t\t%.*sdata\t%.*H\n", dind, ind, b->stsd.entry[u].video.avc.sps[i].len, b->stsd.entry[u].video.avc.sps[i].data); } Bprint(&stderr, "\t\t\t%.*snpps\t%d\n", dind, ind, b->stsd.entry[u].video.avc.npps); for(i = 0; i < b->stsd.entry[u].video.avc.npps; i++){ Bprint(&stderr, "\t\t\t\t%.*spps[%d]\n", dind, ind, i); Bprint(&stderr, "\t\t\t\t\t%.*slen\t%d\n", dind, ind, b->stsd.entry[u].video.avc.pps[i].len); Bprint(&stderr, "\t\t\t\t\t%.*sdata\t%.*H\n", dind, ind, b->stsd.entry[u].video.avc.pps[i].len, b->stsd.entry[u].video.avc.pps[i].data); } } } if(b->stsd.entry[u].audio.format != 0){ Bprint(&stderr, "\t\t\t%.*schannels\t%d\n", dind, ind, b->stsd.entry[u].audio.channels); Bprint(&stderr, "\t\t\t%.*sbps\t%d\n", dind, ind, b->stsd.entry[u].audio.bps); Bprint(&stderr, "\t\t\t%.*ssample_rate\t%d\n", dind, ind, b->stsd.entry[u].audio.samplerate); } } }else if(b->type == BoxStts){ Bprint(&stderr, "\t%.*sentry_count\t%ud\n", dind, ind, b->stts.entrycount); for(u = 0; dflag > 1 && u < b->stts.entrycount; u++){ Bprint(&stderr, "\t%.*sentry[%llud]\n", dind, ind, u); Bprint(&stderr, "\t\t%.*ssample_count\t%ud\n", dind, ind, b->stts.entry[u].samplecount); Bprint(&stderr, "\t\t%.*ssamples_delta\t%ud\n", dind, ind, b->stts.entry[u].sampledelta); } }else if(b->type == BoxStss){ Bprint(&stderr, "\t%.*sentry_count\t%ud\n", dind, ind, b->stss.entrycount); }else if(b->type == BoxStsc){ Bprint(&stderr, "\t%.*sentry_count\t%ud\n", dind, ind, b->stss.entrycount); for(u = 0; dflag > 1 && u < b->stsc.entrycount; u++){ Bprint(&stderr, "\t%.*sentry[%llud]\n", dind, ind, u); Bprint(&stderr, "\t\t%.*sfirst_chunk\t%ud\n", dind, ind, b->stsc.entry[u].firstchunk); Bprint(&stderr, "\t\t%.*ssamples_per_chunk\t%ud\n", dind, ind, b->stsc.entry[u].samplesperchunk); Bprint(&stderr, "\t\t%.*ssample_description_table\t%ud\n", dind, ind, b->stsc.entry[u].sdt); } }else if(b->type == BoxStsz){ Bprint(&stderr, "\t%.*ssample_size\t%ud\n", dind, ind, b->stsz.samplesizeeach); Bprint(&stderr, "\t%.*ssample_count\t%ud\n", dind, ind, b->stsz.samplecount); if(dflag > 1 && b->stsz.samplesizeeach == 0){ for(u = 0; u < b->stsz.samplecount; u++) Bprint(&stderr, "\t%.*ssamplesize[%llud]\t%ud\n", dind, ind, u, b->stsz.samplesize[u]); } }else if(b->type == BoxTkhd){ Bprint(&stderr, "\t%.*screation_time\t%llud\n", dind, ind, b->tkhd.creattime); Bprint(&stderr, "\t%.*smodification_time\t%llud\n", dind, ind, b->tkhd.modtime); Bprint(&stderr, "\t%.*strack_id\t%ud\n", dind, ind, b->tkhd.trackid); Bprint(&stderr, "\t%.*sduration\t%llud\n", dind, ind, b->tkhd.duration); Bprint(&stderr, "\t%.*swidth\t%ud\n", dind, ind, b->tkhd.width); Bprint(&stderr, "\t%.*sheight\t%ud\n", dind, ind, b->tkhd.height); }else if(b->type == BoxHdlr){ Bprint(&stderr, "\t%.*shandler_type\t%T\n", dind, ind, b->hdlr.handlertype); Bprint(&stderr, "\t%.*sname\t%s\n", dind, ind, b->hdlr.name); }else if(b->type == BoxStco || b->type == BoxCo64){ Bprint(&stderr, "\t%.*sentry_count\t%ud\n", dind, ind, b->stss.entrycount); for(u = 0; dflag > 1 && u < b->stco_co64.entrycount; u++) Bprint(&stderr, "\t%.*schunkoffset[%llud]\t%llud\n", dind, ind, u, b->stco_co64.chunkoffset[u]); } } static void addtrack(void) { tracks = realloc(tracks, (ntracks+1)*sizeof(*tracks)); memmove(&tracks[ntracks++], &track, sizeof(track)); } static Track * gettrack(int id) { Track *t; int i; for(t = tracks, i = 0; i < ntracks && t->id != id; i++, t++){ if(id == Dumpaudio && t->audio.format != 0) break; if(id == Dumpvideo && t->video.format != 0) break; } if(i >= ntracks){ werrstr("no track %d", id); t = nil; } return t; } static void addtrun(Box *trun) { Track *t; Moof *m; if((t = gettrack(moof.tfhd.tfhd.trackid)) == nil) sysfatal("trun without a track"); t->moof = realloc(t->moof, (t->nmoof+1)*sizeof(Moof)); m = &t->moof[t->nmoof++]; memmove(&moof.trun, trun, sizeof(moof.trun)); memmove(m, &moof, sizeof(moof)); } static int srate2mpeg4fi[] = { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350, }; static int dumpstc(Biobuf *f, Biobuf *out, Track *t, u8int *frame) { SampleToChunk *stc; u32int si, ch, nextch, n, len; u32int samplelast, sample, rawsz, samplesz; u64int ts; u8int *raw; raw = nil; rawsz = 0; sample = samplelast = 0; stc = t->stc; ch = 0; for(si = 0; si < t->numstc; si++, stc++){ nextch = t->numchunks; if(si+1 < t->numstc) nextch = stc[1].firstchunk - 1; for(; ch < nextch; ch++){ if(Bseek(f, t->chunkoffset[ch], 0) != t->chunkoffset[ch]){ werrstr("chunk %ud: %r", ch); return -1; } for(; sample < samplelast+stc->samplesperchunk && sample < t->numsamples; sample++){ if((samplesz = t->samplesizeeach) == 0) samplesz = t->samplesize[sample]; if(samplesz == 0) break; if(rawsz < samplesz){ rawsz = samplesz * 2; raw = realloc(raw, rawsz); } if(Bread(f, raw, samplesz) != samplesz){ werrstr("chunk %ud sample %ud size %ud: %r", ch, sample, samplesz); return -1; } if(t->audio.format == FmtMp4a){ samplesz += 7; frame[3] = (t->audio.channels&3)<<6 | (samplesz>>11)&3; /* channels, frame length */ frame[4] = (samplesz>>3); /* frame length */ frame[5] = (samplesz&7)<<5 | 0x1f; /* frame length, fullness */ frame[6] = 0xfc; /* fullness, number of frames */ samplesz -= 7; if(Bwrite(out, frame, 7) != 7){ /* EOF */ werrstr("eof"); break; } }else if(t->video.format != 0){ /* all video goes out as IVF */ ts = tts(t, sample); frame[0] = samplesz; frame[1] = samplesz >> 8; frame[2] = samplesz >> 16; frame[3] = samplesz >> 24; frame[4] = ts; frame[5] = ts >> 8; frame[6] = ts >> 16; frame[7] = ts >> 24; frame[8] = ts >> 32; frame[9] = ts >> 40; frame[10] = ts >> 48; frame[11] = ts >> 56; if(Bwrite(out, frame, 12) != 12){ /* EOF */ werrstr("eof"); break; } } if(t->video.format == FmtAvc1){ for(n = 0; n < samplesz; n += len+4){ len = raw[n+0]<<24 | raw[n+1]<<16 | raw[n+2]<<8 | raw[n+3]; raw[n+0] = 0; raw[n+1] = 0; raw[n+2] = 0; raw[n+3] = 1; } } if(Bwrite(out, raw, samplesz) != samplesz){ /* EOF? */ werrstr("eof"); break; } } samplelast = sample; } } free(raw); return 0; } static int dumptrun(Biobuf *f, Biobuf *out, Track *t, u8int *frame) { int i, j, len, n; u64int ts; u8int *raw; int maxsz, sz; RunSample *s; Moof *m; USED(frame); ts = 0; maxsz = 0; raw = nil; for(i = 0, m = t->moof; i < t->nmoof; i++, m++){ for(j = 0; j < m->trun.trun.samplecount; j++){ s = &m->trun.trun.samples[j]; if(12 + s->size > maxsz){ maxsz = 12 + s->size; raw = realloc(raw, maxsz); } if(t->audio.format == FmtMp4a){ sz = 7 + s->size; raw[0] = frame[0]; raw[1] = frame[1]; raw[2] = frame[2]; raw[3] = (t->audio.channels&3)<<6 | (sz>>11)&3; /* channels, frame length */ raw[4] = (sz>>3); /* frame length */ raw[5] = (sz&7)<<5 | 0x1f; /* frame length, fullness */ raw[6] = 0xfc; /* fullness, number of frames */ if(Bseek(f, s->offset, 0) != s->offset){ werrstr("couldn't seek to offset %lld", s->offset); return -1; } if(Bread(f, raw+7, s->size) != s->size){ werrstr("couldn't read sample (%d bytes)", s->size); return -1; } if(Bwrite(out, raw, 7 + s->size) != 7 + s->size) /* eof? */ break; }else if(t->video.format != 0){ raw[0] = s->size; raw[1] = s->size >> 8; raw[2] = s->size >> 16; raw[3] = s->size >> 24; raw[4] = ts; raw[5] = ts >> 8; raw[6] = ts >> 16; raw[7] = ts >> 24; raw[8] = ts >> 32; raw[9] = ts >> 40; raw[10] = ts >> 48; raw[11] = ts >> 56; if(Bseek(f, s->offset, 0) != s->offset){ werrstr("couldn't seek to offset %lld", s->offset); return -1; } if(Bread(f, raw+12, s->size) != s->size){ werrstr("couldn't read sample (%d bytes)", s->size); return -1; } if(t->video.format == FmtAvc1){ for(n = 12; n < 12+s->size; n += len+4){ len = raw[n+0]<<24 | raw[n+1]<<16 | raw[n+2]<<8 | raw[n+3]; raw[n+0] = 0; raw[n+1] = 0; raw[n+2] = 0; raw[n+3] = 1; } } if(Bwrite(out, raw, 12 + s->size) != 12 + s->size) /* eof? */ break; ts += s->duration; /* sample's "time offset" is ignored here */ }else{ sysfatal("don't know how to dump this one"); } } } return 0; } static int dumptrack(Biobuf *f, int id) { int i, j, x, res; Biobuf out; u64int dur; Track *t; Moof *m; u8int frame[0x20]; if((t = gettrack(id)) == nil) return -1; if(dflag) Bprint(&stderr, "track %d: handler=%T ", t->id, t->handlertype); if(t->numstc > 0){ if(dflag) Bprint(&stderr, "numstc=%ud chunks=%ud samples=%ud\n", t->numstc, t->numchunks, t->numsamples); }else if(t->nmoof > 0){ if(dflag) Bprint(&stderr, "numtrun: %d\n", t->nmoof); }else{ if(dflag) Bprint(&stderr, "no samples\n"); werrstr("track %d has no samples", t->id); return -1; } Binit(&out, 1, OWRITE); if(t->audio.format != 0){ if(t->audio.format == FmtMp4a){ for(i = 0; i < nelem(srate2mpeg4fi) && srate2mpeg4fi[i] != t->audio.samplerate; i++); if(i >= nelem(srate2mpeg4fi)){ werrstr("audio: mpeg4: invalid sample rate %d", t->audio.samplerate); return -1; } frame[0] = 0xff; /* syncword */ frame[1] = 0xf1; /* syncword, mpeg4, no crc */ frame[2] = (t->audio.objtype-1)<<6 | i<<2 | t->audio.channels>>2; /* object type, frequency index, channels */ }else{ werrstr("audio: unknown format %T\n", t->audio.format); return -1; } }else if(t->video.format != 0){ /* video is remuxed into IVF */ memset(frame, 0, 0x20); memmove(frame, "DKIF", 4); frame[4] = 0; frame[5] = 0; frame[6] = 0x20; frame[7] = 0; if(t->video.format == FmtAv01){ memmove(frame+8, "AV01", 4); }else if(t->video.format == FmtAvc1){ memmove(frame+8, "AVC1", 4); }else if(t->video.format == FmtVp09){ memmove(frame+8, "VP90", 4); }else{ werrstr("video: unsupported video format %T", t->video.format); return -1; } frame[12] = t->video.width; frame[13] = t->video.width >> 8; frame[14] = t->video.height; frame[15] = t->video.height >> 8; /* timebase denum */ frame[16] = t->timescale; frame[17] = t->timescale >> 8; frame[18] = t->timescale >> 16; frame[19] = t->timescale >> 24; /* timebase num */ frame[20] = 1; frame[21] = 0; frame[22] = 0; frame[23] = 0; /* FIXME is it a "number of frames" or "total duration?" */ dur = 0; for(i = 0, m = t->moof; i < t->nmoof; i++, m++){ for(j = 0; j < m->trun.trun.samplecount; j++){ dur += m->trun.trun.samples[j].duration; } } frame[24] = dur; frame[25] = dur >> 8; frame[26] = dur >> 16; frame[27] = dur >> 24; frame[28] = dur >> 32; /* FIXME is it 64 bits? */ frame[29] = dur >> 40; frame[30] = dur >> 48; frame[31] = dur >> 56; Bwrite(&out, frame, 0x20); if(t->video.format == FmtAvc1){ if(t->video.avc.nallen != 4){ werrstr("avc1 nallen != 4 isn't supported yet"); return -1; } memset(frame, 0, 4+8+4); frame[4+8+3] = 1; for(i = 0; i < t->video.avc.nsps; i++){ x = 4 + t->video.avc.sps[i].len; frame[0] = x; frame[1] = x >> 8; frame[2] = x >> 16; frame[3] = x >> 24; Bwrite(&out, frame, 4+8+4); Bwrite(&out, t->video.avc.sps[i].data, t->video.avc.sps[i].len); } for(i = 0; i < t->video.avc.npps; i++){ x = 4 + t->video.avc.pps[i].len; frame[0] = x; frame[1] = x >> 8; frame[2] = x >> 16; frame[3] = x >> 24; Bwrite(&out, frame, 4+8+4); Bwrite(&out, t->video.avc.pps[i].data, t->video.avc.pps[i].len); } } } res = -1; if(t->numstc > 0) res = dumpstc(f, &out, t, frame); else if(t->nmoof > 0) res = dumptrun(f, &out, t, frame); Bterm(&out); return res; }; int parsesampleentry(Biobuf *f, SampleEntry *e, int n) { u8int d[96]; int sz, eof; vlong off; Box b; if(track.handlertype == HandlerVideo){ e->video.format = e->format; /* predefined+reserved+predefined, width+height, hres+vres, reserved, framecount, compressor, depth, predefined */ sz = 2+2+4*3 + 2+2 + 4+4 + 4 + 2 + 32 + 2 + 2; eBread(sz, "SampleEntry: video"); e->video.width = bu16(d+16); e->video.height = bu16(d+18); e->video.hres = bu32(d+20) >> 16; e->video.vres = bu32(d+24) >> 16; e->video.framecount = bu16(d+32); memmove(e->video.compressor, d+34, 32); n -= sz; sampleentry = e; while(n > 8){ off = Boffset(f); if(parsebox(f, &b, &eof) != 0) return -1; Bseek(f, b.dstart+b.dsz, 0); n -= b.dstart+b.dsz - off; } sampleentry = nil; memmove(&track.video, &e->video, sizeof(Video)); }else if(track.handlertype == HandlerAudio){ e->audio.format = e->format; /* <depends on the version>, channels+bps, predefined+reserved, samplerate */ sz = 8 + 2+2 + 2+2 + 4; eBread(sz, "SampleEntry: audio"); if(bu16(d) == 0){ /* version */ e->audio.channels = bu16(d+8); e->audio.bps = bu16(d+10); e->audio.samplerate = bu32(d+16)>>16; }else{ sysfatal("AudioSampleEntryV1 unsupported"); } n -= sz; sampleentry = e; while(n > 8){ off = Boffset(f); if(parsebox(f, &b, &eof) != 0) return -1; Bseek(f, b.dstart+b.dsz, 0); n -= b.dstart+b.dsz - off; } sampleentry = nil; memmove(&track.audio, &e->audio, sizeof(Audio)); /* FIXME do we care about the rest? */ }else{ Bprint(&stderr, "SampleEntry: unknown handler type %T\n", track.handlertype); } return n; err: return -1; } static int parseavcc(Biobuf *f, SampleEntry *e, int n) { int i; u8int d[2]; u32int x; USED(n); /* FIXME we don't really validate anything here */ if((x = Bgetc(f)) != 1){ werrstr("unsupported config version %d", x); goto err; } e->video.avc.profile = Bgetc(f); e->video.avc.compat = Bgetc(f); e->video.avc.level = Bgetc(f); e->video.avc.nallen = (Bgetc(f) & 3) + 1; e->video.avc.nsps = Bgetc(f) & 0x1f; e->video.avc.sps = calloc(e->video.avc.nsps, sizeof(ParamSet)); for(i = 0; i < e->video.avc.nsps; i++){ eBread(2, "sps len"); x = bu16(d); e->video.avc.sps[i].len = x; e->video.avc.sps[i].data = malloc(x); Bread(f, e->video.avc.sps[i].data, x); } e->video.avc.npps = Bgetc(f) & 0x1f; e->video.avc.pps = calloc(e->video.avc.npps, sizeof(ParamSet)); for(i = 0; i < e->video.avc.npps; i++){ eBread(2, "pps len"); x = bu16(d); e->video.avc.pps[i].len = x; e->video.avc.pps[i].data = malloc(x); Bread(f, e->video.avc.pps[i].data, x); } return 0; err: return -1; } static int descrlen(Biobuf *f, int *len) { int i, c; *len = 0; for(i = 1; i <= 4; i++){ c = Bgetc(f); *len = *len << 7 | c & 0x7f; if((c & 0x80) == 0) break; } return i; } static int aacchans[] = {0, 1, 2, 3, 4, 5, 6, 8}; static int parseesds(Biobuf *f, SampleEntry *e, int n) { int tag, len, o, x, srate; o = -1; while(n > 1){ tag = Bgetc(f); n -= 1 + descrlen(f, &len); if(tag == 3){ /* es descriptor */ Bseek(f, 2, 1); /* id */ n -= 2; x = Bgetc(f); n--; if(x & 0x80){ Bseek(f, 2, 1); /* dep id */ n -= 2; } if(x & 0x40){ /* skip URL */ x = Bgetc(f); Bseek(f, x, 1); n -= x; } if(x & 0x20){ /* OCR */ Bseek(f, 2, 1); n -= 2; } }else if(tag == 4){ /* decoder config descriptor */ o = Bgetc(f); /* object type id */ n--; Bgetc(f); /* stream type (6 bits) */ n--; Bseek(f, 3, 1); /* buffer size */ n -= 3; Bseek(f, 4, 1); /* max bitrate */ n -= 4; Bseek(f, 4, 1); /* avg bitrate */ n -= 4; }else if(tag == 5){ /* decoder specific descriptor */ x = Bgetc(f)<<8; x |= Bgetc(f); n -= 2; if(o == 0x40){ /* AAC */ e->audio.objtype = x >> 11; if(e->audio.objtype == 0x1f){ werrstr("extended object type not supported"); return -1; } srate = (x >> 7) & 0xf; if(srate == 0xf){ werrstr("extended sample rate not supported"); return -1; } e->audio.samplerate = srate2mpeg4fi[srate]; e->audio.channels = aacchans[(x >> 3) & 0xf]; if(e->audio.channels == 0){ werrstr("zero channels?"); return -1; } break; } } } return 0; } static int parseboxdata(Biobuf *f, Box *b) { u8int d[128], *p; Box inner; int i, n, eof; u64int u, off; RunSample *s; if(b->type == BoxFtyp){ eBread(8, "brand and version"); b->ftyp.brand = bu32(d); b->ftyp.version = bu32(d+4); if(b->dsz % 4 != 0){ werrstr("compatible_brands size"); goto err; } b->ftyp.ncompat = (b->dsz - 8) / 4; b->ftyp.compat = calloc(b->ftyp.ncompat, 4); for(i = 0; i < b->ftyp.ncompat; i++){ eBread(4, "compatible_brands"); b->ftyp.compat[i] = bu32(d); } printbox(b); }else if(b->type == BoxMoov || b->type == BoxMvex || b->type == BoxTrak || b->type == BoxMdia || b->type == BoxMinf || b->type == BoxStbl || b->type == BoxMoof || b->type == BoxTraf || b->type == BoxEdts || b->type == BoxDinf || b->type == BoxUdta){ if(b->type == BoxMoof){ memset(&moof, 0, sizeof(moof)); memmove(&moof, b, sizeof(Box)); } printbox(b); dind++; for(;;){ memset(&inner, 0, sizeof(inner)); if(parsebox(f, &inner, &eof) != 0) goto err; Bseek(f, inner.dstart+inner.dsz, 0); if(inner.dstart+inner.dsz >= b->dstart+b->dsz) break; } if(b->type == BoxTrak){ addtrack(); memset(&track, 0, sizeof(track)); } dind--; if(b->type == BoxMoof) memset(&moof, 0, sizeof(moof)); }else if(b->type == BoxMdhd){ n = b->version == 0 ? 16 : 28; eBread(n, "short read"); p = d; b->mdhd.creation = bu32(p); p += 4; if(b->version == 1){ b->mdhd.creation = b->mdhd.creation<<32 | bu32(p); p += 4; } b->mdhd.modification = bu32(p); p += 4; if(b->version == 1){ b->mdhd.modification = b->mdhd.modification<<32 | bu32(p); p += 4; } b->mdhd.timescale = bu32(p); p += 4; track.timescale = b->mdhd.timescale; /* it's used for IVF timebase */ b->mdhd.duration = bu32(p); p += 4; if(b->version == 1){ b->mdhd.duration = b->mdhd.duration<<32 | bu32(p); } printbox(b); }else if(b->type == BoxMvhd){ n = b->version == 0 ? 96 : 108; eBread(n, "short read"); p = d; b->mvhd.creation = bu32(p); p += 4; if(b->version == 1){ b->mvhd.creation = b->mvhd.creation<<32 | bu32(p); p += 4; } b->mvhd.modification = bu32(p); p += 4; if(b->version == 1){ b->mvhd.modification = b->mvhd.modification<<32 | bu32(p); p += 4; } b->mvhd.timescale = bu32(p); p += 4; b->mvhd.duration = bu32(p); p += 4; if(b->version == 1){ b->mvhd.duration = b->mvhd.duration<<32 | bu32(p); p += 4; } b->mvhd.rate = bu32(p); p += 4; b->mvhd.volume = bu16(p); p += 2; p += 2; p += 4*2; for(i = 0; i < 9; i++){ b->mvhd.matrix[i] = bu32(p); p += 4; } p += 4*6; b->mvhd.nexttrack = bu32(p); printbox(b); }else if(b->type == BoxTrex){ eBread(20, "short read"); b->trex.trackid = bu32(d); b->trex.defsample.descrindex = bu32(d+4); b->trex.defsample.duration = bu32(d+8); b->trex.defsample.size = bu32(d+12); b->trex.defsample.flags = bu32(d+16); defsamplesize = b->trex.defsample.size; printbox(b); }else if(b->type == BoxMfhd){ eBread(4, "short read"); b->mfhd.seqnumber = bu32(d); memmove(&moof.mfhd, b, sizeof(*b)); printbox(b); }else if(b->type == BoxTfhd){ eBread(4, "track_id"); b->tfhd.trackid = bu32(d); if(b->flags & 1){ eBread(8, "base_data_offset"); b->tfhd.baseoffset = bu64(d); } if(b->flags & 2){ eBread(4, "sample_description_index"); b->tfhd.defsample.descrindex = bu32(d); } if(b->flags & 8){ eBread(4, "default_sample_duration"); b->tfhd.defsample.duration = bu32(d); } if(b->flags & 16){ eBread(4, "default_sample_size"); b->tfhd.defsample.size = bu32(d); defsamplesize = b->tfhd.defsample.size; } if(b->flags & 32){ eBread(4, "default_sample_flags"); b->tfhd.defsample.flags = bu32(d); } memmove(&moof.tfhd, b, sizeof(*b)); printbox(b); }else if(b->type == BoxTfdt){ if(b->version == 1){ eBread(8, "base_media_decode_time"); b->tfdt.decodetime = bu64(d); }else{ eBread(4, "base_media_decode_time"); b->tfdt.decodetime = bu32(d); } printbox(b); }else if(b->type == BoxTrun){ eBread(4, "sample_count"); b->trun.samplecount = bu32(d); if(b->flags & 1){ eBread(4, "data_offset"); b->trun.dataoffset = bu32(d); } if(b->flags & 4){ eBread(4, "first_sample_flags"); b->trun.firstsampleflags = bu32(d); } /* FIXME free those */ b->trun.samples = calloc(b->trun.samplecount, sizeof(RunSample)); off = 0; for(u = 0; u < b->trun.samplecount; u++){ s = &b->trun.samples[u]; if(b->flags & 0x100){ eBread(4, "sample_duration"); s->duration = bu32(d); }else if(moof.tfhd.flags & 8) s->duration = moof.tfhd.tfhd.defsample.duration; if(b->flags & 0x200){ eBread(4, "sample_size"); s->size = bu32(d); }else if(moof.tfhd.flags & 16) s->size = moof.tfhd.tfhd.defsample.size; if(b->flags & 0x400){ eBread(4, "sample_flags"); s->flags = bu32(d); }else if((b->flags & 4) && u == 0){ // FIXME default flags??? s->flags = b->trun.firstsampleflags; }else if(moof.tfhd.flags & 32) s->flags = moof.tfhd.tfhd.defsample.flags; if(b->flags & 0x800){ eBread(4, "sample_composition_time_offset"); if(b->version == 0) s->timeoffset = bu32(d); else s->timeoffset = (s32int)bu32(d); } s->offset = b->trun.dataoffset + off; if(moof.tfhd.flags & 1) Bprint(&stderr, "tfhd has base-data-offset-present set, this isn't supported atm\n"); else if(moof.tfhd.flags & 0x20000) s->offset += moof.offset + moof.tfhd.tfhd.baseoffset; else Bprint(&stderr, "couldn't calculate sample offset\n"); off += s->size; } addtrun(b); printbox(b); }else if(b->type == BoxStsd){ eBread(4, "entry_count"); b->stsd.entrycount = bu32(d); b->stsd.entry = calloc(b->stsd.entrycount, sizeof(SampleEntry)); for(u = 0; u < b->stsd.entrycount; u++){ eBread(4, "size"); n = bu32(d); n -= 4; eBread(4, "format"); b->stsd.entry[u].format = bu32(d); n -= 4; Bseek(f, 6, 1); /* skip reserved */ n -= 6; eBread(2, "id"); b->stsd.entry[u].datarefid = bu16(d); n -= 2; if((n = parsesampleentry(f, &b->stsd.entry[u], n)) < 0) goto err; Bseek(f, n, 1); } printbox(b); }else if(b->type == BoxAvcc){ assert(sampleentry != nil); if(parseavcc(f, sampleentry, b->dsz) != 0) goto err; /* print it with the sample entry */ }else if(b->type == BoxEsds){ assert(sampleentry != nil); if(parseesds(f, sampleentry, b->dsz) != 0) goto err; /* print it with the sample entry */ }else if(b->type == BoxStts){ eBread(4, "entry_count"); b->stts.entrycount = bu32(d); b->stts.entry = calloc(b->stts.entrycount, sizeof(TimeToSample)); for(u = 0; u < b->stts.entrycount; u++){ eBread(4, "sample_count"); b->stts.entry[u].samplecount = bu32(d); eBread(4, "sample_delta"); b->stts.entry[u].sampledelta = bu32(d); } track.tts = b->stts.entry; track.numtts = b->stts.entrycount; printbox(b); }else if(b->type == BoxStss){ eBread(4, "entry_count"); b->stss.entrycount = bu32(d); /* FIXME not reading actual entries here */ printbox(b); }else if(b->type == BoxStsc){ eBread(4, "entry_count"); b->stsc.entrycount = bu32(d); b->stsc.entry = calloc(b->stsc.entrycount, sizeof(SampleToChunk)); for(u = 0; u < b->stsc.entrycount; u++){ eBread(4, "first_chunk"); b->stsc.entry[u].firstchunk = bu32(d); eBread(4, "samples_per_chunk"); b->stsc.entry[u].samplesperchunk = bu32(d); eBread(4, "sample_description_table"); b->stsc.entry[u].sdt = bu32(d); } track.numstc = b->stsc.entrycount; track.stc = b->stsc.entry; printbox(b); }else if(b->type == BoxStco || b->type == BoxCo64){ eBread(4, "entry_count"); b->stco_co64.entrycount = bu32(d); b->stco_co64.chunkoffset = calloc(b->stco_co64.entrycount, sizeof(u64int)); for(u = 0; u < b->stco_co64.entrycount; u++){ eBread(b->type == BoxStco ? 4 : 8, "chunk_offset"); b->stco_co64.chunkoffset[u] = b->type == BoxStco ? bu32(d) : bu64(d); } track.numchunks = b->stco_co64.entrycount; track.chunkoffset = b->stco_co64.chunkoffset; printbox(b); }else if(b->type == BoxStsz){ eBread(4, "sample_size"); b->stsz.samplesizeeach = bu32(d); track.samplesizeeach = b->stsz.samplesizeeach; eBread(4, "sample_count"); b->stsz.samplecount = bu32(d); if(b->stsz.samplesizeeach == 0){ b->stsz.samplesize = calloc(b->stsz.samplecount, sizeof(u32int)); for(u = 0; u < b->stsz.samplecount; u++){ eBread(4, "chunk_offset"); b->stsz.samplesize[u] = bu32(d); } track.numsamples = b->stsz.samplecount; track.samplesize = b->stsz.samplesize; } printbox(b); }else if(b->type == BoxTkhd){ if(b->version == 1){ eBread(8, "creation_time"); b->tkhd.creattime = bu64(d); eBread(8, "modification_time"); b->tkhd.modtime = bu64(d); eBread(8, "track_id"); /* skipping 4 reserved as well */ b->tkhd.trackid = bu32(d); track.id = b->tkhd.trackid; eBread(8, "duration"); b->tkhd.duration = bu64(d); }else if(b->version == 0){ eBread(4, "creation_time"); b->tkhd.creattime = bu32(d); eBread(4, "modification_time"); b->tkhd.modtime = bu32(d); eBread(8, "track_id"); /* skipping 4 reserved as well */ b->tkhd.trackid = bu32(d); track.id = b->tkhd.trackid; eBread(4, "duration"); b->tkhd.duration = bu32(d); }else{ werrstr("uknown version %d", b->version); goto err; } eBread(8+2+2+2+2, "reserved, layer, alternate_group, volume, reserved"); eBread(9*4, "matrix"); eBread(4, "width"); b->tkhd.width = bu32(d)>>16; /* FIXME fixed-point 16.16 */ eBread(4, "height"); b->tkhd.height = bu32(d)>>16; /* FIXME fixed-point 16.16 */ printbox(b); }else if(b->type == BoxHdlr){ eBread(4, "pre_defined"); eBread(4, "handler_type"); b->hdlr.handlertype = bu32(d); track.handlertype = b->hdlr.handlertype; eBread(3*4, "reserved"); for(u = 0; u < sizeof(d)-1; u++){ if(Bread(f, d+u, 1) != 1){ werrstr("name"); goto err; } if(d[u] == 0) break; } d[u] = 0; b->hdlr.name = strdup((char*)d); printbox(b); }else{ printbox(b); } return 0; err: werrstr("%T: %r", b->type); return -1; } static int parsebox(Biobuf *f, Box *b, int *eof) { vlong start; u8int d[8]; int r; *eof = 0; start = Boffset(f); b->offset = start; b->dstart = start; if((r = Bread(f, d, 8)) != 8){ if(r == 0) *eof = 1; else werrstr("size and type"); goto err; } b->dstart += 8; b->dsz = d[0]<<24 | d[1]<<16 | d[2]<<8 | d[3]; b->type = d[4]<<24 | d[5]<<16 | d[6]<<8 | d[7]; if(b->dsz == 1){ if(Bread(f, d, 8) != 8){ werrstr("largesize"); goto err; } b->dstart += 8; b->dsz = (vlong)d[0]<<56 | (vlong)d[1]<<48 | (vlong)d[2]<<40 | (vlong)d[3]<<32 | d[4]<<24 | d[5]<<16 | d[6]<<8 | d[7]; }else if(b->dsz == 0){ b->dsz = (vlong)1<<63 - 1; } if(b->type == BoxUuid){ if(Bread(f, b->extended, 16) != 16){ werrstr("extended_type"); goto err; } b->dstart += 16; }else if(isfullbox(b)){ if(Bread(f, d, 4) != 4){ werrstr("full box"); goto err; } b->version = d[0]; b->flags = d[1]<<16 | d[2]<<8 | d[3]; b->dstart += 4; }else if(!isalnum(b->type&0xff) || !isalnum((b->type>>8)&0xff) || !isalnum((b->type>>16)&0xff) || !isalnum((b->type>>24)&0xff)){ werrstr("not an ISO container"); return -1; } b->dsz -= b->dstart - start; if(parseboxdata(f, b) == 0) return 0; err: werrstr("parsebox: %r"); return -1; } static char * codec(u32int fmt) { static char t[16]; if(fmt == FmtMp4a) return "aac"; if(fmt == FmtAv01) return "av1"; if(fmt == FmtAvc1) return "h264"; if(fmt == FmtVp08) return "vp8"; if(fmt == FmtVp09) return "vp9"; snprint(t, sizeof(t), "%T", fmt); return t; } int isorun(Biobuf *f) { Box b; Track *t; int eof, i; for(;;){ dind = 0; memset(&b, 0, sizeof(b)); if(parsebox(f, &b, &eof) != 0){ if(eof) break; return -1; } Bseek(f, b.dstart+b.dsz, 0); } if(trackdump != Nodump) return dumptrack(f, trackdump); for(i = 0; i < ntracks; i++){ t = &tracks[i]; Bprint(&out, "%d\t", t->id); if(t->handlertype == HandlerVideo) Bprint(&out, "video\t%s\t%d\t%d", codec(t->video.format), t->video.width, t->video.height); else if(t->handlertype == HandlerAudio) Bprint(&out, "audio\t%s\t%d\t%d", codec(t->audio.format), t->audio.channels, t->audio.samplerate); else Bprint(&out, "%T\t???", t->handlertype); Bprint(&out, "\n"); } return 0; }