shithub: mcfs

ref: 74533c8ff1ab84ff0949d52c3726ccfcfc1bf76a
dir: mcfs/iso.c

View raw version
#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, k, len, sz;
	u32int samplelast, sample, rawsz, samplesz;
	u64int ts;
	u8int *raw, h[4];

	raw = nil;
	rawsz = 0;
	sample = samplelast = 0;
	stc = t->stc;
	ch = 0;
	k = t->video.avc.nallen;
	h[0] = 0;
	h[1] = 0;
	h[2] = 0;
	h[3] = 1;
	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 */
					if(t->video.format == FmtAvc1){
						for(sz = 0, n = 0; n < samplesz; n += k+len){
							len = raw[n+0];
							if(k > 1) len = len<<8 | raw[n+1];
							if(k > 2) len = len<<8 | raw[n+2];
							if(k > 3) len = len<<8 | raw[n+3];
							sz += 4+len;
						}
					}else
						sz = samplesz;
					ts = tts(t, sample);
					frame[0] = sz;
					frame[1] = sz >> 8;
					frame[2] = sz >> 16;
					frame[3] = sz >> 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 += k+len){
						len = raw[n+0];
						if(k > 1) len = len<<8 | raw[n+1];
						if(k > 2) len = len<<8 | raw[n+2];
						if(k > 3) len = len<<8 | raw[n+3];
						Bwrite(out, h, 4);
						Bwrite(out, raw+n+k, len);
					}
				}else 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, k;
	u64int ts;
	u8int *raw, h[4];
	int maxsz, sz;
	RunSample *s;
	Moof *m;

	USED(frame);

	ts = 0;
	maxsz = 0;
	raw = nil;
	k = t->video.avc.nallen;
	h[0] = 0;
	h[1] = 0;
	h[2] = 0;
	h[3] = 1;
	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(sz = 0, n = 12; n < 12+s->size; n += len+k){
						len = raw[n+0];
						if(k > 1) len = len<<8 | raw[n+1];
						if(k > 2) len = len<<8 | raw[n+2];
						if(k > 3) len = len<<8 | raw[n+3];
						sz += 4+len;
					}
					raw[0] = sz;
					raw[1] = sz >> 8;
					raw[2] = sz >> 16;
					raw[3] = sz >> 24;
					Bwrite(out, raw, 12);
					for(n = 12; n < 12+s->size; n += len+k){
						len = raw[n+0];
						if(k > 1) len = len<<8 | raw[n+1];
						if(k > 2) len = len<<8 | raw[n+2];
						if(k > 3) len = len<<8 | raw[n+3];
						Bwrite(out, h, 4);
						Bwrite(out, raw+n+k, len);
					}
				}else 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 < 1 || t->video.avc.nallen > 4){
				werrstr("avc1: invalid nallen %d", t->video.avc.nallen);
				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;
}