~ft/mcfs

8110553efd2b7b7d92284fbeb1886233fcffc07e — Sigrid Haflínudóttir 6 months ago 1cd6fa1
AAC stream extraction working for tracks with defined stsd/stco/stsz
1 files changed, 254 insertions(+), 81 deletions(-)

M iso.c
M iso.c => iso.c +254 -81
@@ 2,13 2,28 @@
#include <libc.h>
#include <bio.h>

typedef struct Audio Audio;
typedef struct Box Box;
typedef struct RunSample RunSample;
typedef struct Track Track;
typedef struct SampleToChunk SampleToChunk;
typedef struct Track Track;
typedef struct Video Video;

struct Audio {
	u32int format;
	int channels;
	int samplerate;
};

struct Video {
	u32int format;
	int width;
	int height;
};

struct Box {
	vlong dsz;
	vlong offset;
	vlong dstart;
	char extended[16];
	u32int type;


@@ 73,9 88,9 @@ struct Box {
		}trun;

		struct {
			u32int handlertype;
			u32int entrycount;
			/* FIXME entries? */
			Video video;
			Audio audio;
		}stsd;

		struct {


@@ 99,9 114,9 @@ struct Box {
		}stco_co64;

		struct {
			u32int samplesize;
			u32int samplesizeeach;
			u32int samplecount;
			u32int *entrysize;
			u32int *samplesize;
		}stsz; /* FIXME need stz2 as well */

		struct {


@@ 127,26 142,37 @@ struct RunSample {
	vlong timeoffset;
};

struct SampleToChunk {
	u32int firstchunk;
	u32int samplesperchunk;
	u32int sdt;
};

struct Track {
	u32int handlertype;

	u64int *chunkoffset;
	u32int numchunks;

	u32int samplesizeeach;
	u32int *samplesize;
	u32int numsamples;

	SampleToChunk *stc;
	u32int numstc;

	int id;
};
	Audio audio;
	Video video;

struct SampleToChunk {
	u32int firstchunk;
	u32int samplesperchunk;
	u32int sdt;
	int id;
};

enum {
	HandlerVideo = 0x76696465u,
	HandlerAudio = 0x736f756eu,

	FmtMp4a = 0x6d703461u,

	BoxUuid = 0x75756964u,
	BoxFtyp = 0x66747970u,
	BoxMoov = 0x6d6f6f76u,


@@ 182,7 208,7 @@ enum {
	BoxMdat = 0x6d646174u,
};

#define bu16(x) ((x)[0]<<8 | (x)[1]<<16)
#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])



@@ 198,10 224,11 @@ enum {

static int dflag;
static int dind;
static int trackdump = -1;
static u32int defsamplesize;

static Track track;
static Track *tracks;
static int ntracks;

static int parsebox(Biobuf *f, Box *b, int *eof);



@@ 239,6 266,11 @@ printbox(Box *b)
		fprint(2, "\t%.*sversion\t%d\n", dind, ind, b->version);
		fprint(2, "\t%.*sflags\t0x%ux\n", dind, ind, b->flags);
	}
	if(dflag > 1){
		fprint(2, "\t%.*soffset\t%zd\n", dind, ind, b->offset);
		fprint(2, "\t%.*sdstart\t%zd\n", dind, ind, b->dstart);
		fprint(2, "\t%.*sdsize\t%zd\n", dind, ind, b->dsz);
	}

	if(b->type == BoxFtyp){
		fprint(2, "\t%.*sbrand\t%T\n", dind, ind, b->ftyp.brand);


@@ 268,26 300,26 @@ printbox(Box *b)
		);
	}else if(b->type == BoxTrex){
		fprint(2, "\t%.*strackid\t0x%ux\n", dind, ind, b->trex.trackid);
		fprint(2, "\t%.*sdefsample.\n", dind, ind);
		fprint(2, "\t\t%.*s.descrindex\t0x%ux\n", dind, ind, b->trex.defsample.descrindex);
		fprint(2, "\t\t%.*s.duration\t%ud\n", dind, ind, b->trex.defsample.duration);
		fprint(2, "\t\t%.*s.size\t0x%ux\n", dind, ind, b->trex.defsample.size);
		fprint(2, "\t\t%.*s.flags\t0x%ux\n", dind, ind, b->trex.defsample.flags);
		fprint(2, "\t%.*sdefsample\n", dind, ind);
		fprint(2, "\t\t%.*sdescrindex\t0x%ux\n", dind, ind, b->trex.defsample.descrindex);
		fprint(2, "\t\t%.*sduration\t%ud\n", dind, ind, b->trex.defsample.duration);
		fprint(2, "\t\t%.*ssize\t0x%ux\n", dind, ind, b->trex.defsample.size);
		fprint(2, "\t\t%.*sflags\t0x%ux\n", dind, ind, b->trex.defsample.flags);
	}else if(b->type == BoxMfhd){
		fprint(2, "\t%.*sseqnumber\t%ud\n", dind, ind, b->mfhd.seqnumber);
	}else if(b->type == BoxTfhd){
		fprint(2, "\t%.*strackid\t0x%ux\n", dind, ind, b->tfhd.trackid);
		if(b->flags & 1)
			fprint(2, "\t%.*sbaseoffset\t%zd\n", dind, ind, b->tfhd.baseoffset);
		fprint(2, "\t%.*sdefsample.\n", dind, ind);
		fprint(2, "\t%.*sdefsample\n", dind, ind);
		if(b->flags & 2)
			fprint(2, "\t\t%.*s.descrindex\t0x%ux\n", dind, ind, b->tfhd.defsample.descrindex);
			fprint(2, "\t\t%.*sdescrindex\t0x%ux\n", dind, ind, b->tfhd.defsample.descrindex);
		if(b->flags & 8)
			fprint(2, "\t\t%.*s.duration\t%ud\n", dind, ind, b->tfhd.defsample.duration);
			fprint(2, "\t\t%.*sduration\t%ud\n", dind, ind, b->tfhd.defsample.duration);
		if(b->flags & 16)
			fprint(2, "\t\t%.*s.size\t0x%ux\n", dind, ind, b->tfhd.defsample.size);
			fprint(2, "\t\t%.*ssize\t0x%ux\n", dind, ind, b->tfhd.defsample.size);
		if(b->flags & 32)
			fprint(2, "\t\t%.*s.flags\t0x%ux\n", dind, ind, b->tfhd.defsample.flags);
			fprint(2, "\t\t%.*sflags\t0x%ux\n", dind, ind, b->tfhd.defsample.flags);
		if(b->flags & 0x10000)
			fprint(2, "\t%.*sduration is empty\n", dind, ind);
		if(b->flags & 0x20000)


@@ 300,38 332,47 @@ printbox(Box *b)
			fprint(2, "\t%.*sdataoffset\t%d\n", dind, ind, b->trun.dataoffset);
		if(b->flags & 2)
			fprint(2, "\t%.*sfirstsampleflags\t0x%ux\n", dind, ind, b->trun.firstsampleflags);
		for(u = 0; u < b->trun.samplecount; u++){
		for(u = 0; dflag > 1 && u < b->trun.samplecount; u++){
			fprint(2, "\t%.*ssamples[%zd]\n", dind, ind, u);
			if(b->flags & 0x100)
				fprint(2, "\t\t%.*s.duration\t%ud\n", dind, ind, b->trun.samples[u].duration);
				fprint(2, "\t\t%.*sduration\t%ud\n", dind, ind, b->trun.samples[u].duration);
			if(b->flags & 0x200)
				fprint(2, "\t\t%.*s.size\t%ud\n", dind, ind, b->trun.samples[u].size);
				fprint(2, "\t\t%.*ssize\t%ud\n", dind, ind, b->trun.samples[u].size);
			if(b->flags & 0x400)
				fprint(2, "\t\t%.*s.flags\t0x%ux\n", dind, ind, b->trun.samples[u].flags);
				fprint(2, "\t\t%.*sflags\t0x%ux\n", dind, ind, b->trun.samples[u].flags);
			if(b->flags & 0x800)
				fprint(2, "\t\t%.*s.timeoffset\t%zd\n", dind, ind, b->trun.samples[u].timeoffset);
				fprint(2, "\t\t%.*stimeoffset\t%zd\n", dind, ind, b->trun.samples[u].timeoffset);
		}
	}else if(b->type == BoxStsd){
		fprint(2, "\t%.*shandler_type\t%08x\n", dind, ind, b->stsd.handlertype);
		fprint(2, "\t%.*sentry_count\t%ud\n", dind, ind, b->stsd.entrycount);
		if(b->stsd.video.format != 0){
			fprint(2, "\t\t%.*svideo\t%T\n", dind, ind, b->stsd.video.format);
			fprint(2, "\t\t\t%.*swidth\t%d\n", dind, ind, b->stsd.video.width);
			fprint(2, "\t\t\t%.*sheight\t%d\n", dind, ind, b->stsd.video.height);
		}
		if(b->stsd.audio.format != 0){
			fprint(2, "\t\t%.*saudio\t%T\n", dind, ind, b->stsd.audio.format);
			fprint(2, "\t\t\t%.*schannels\t%d\n", dind, ind, b->stsd.audio.channels);
			fprint(2, "\t\t\t%.*ssample_rate\t%d\n", dind, ind, b->stsd.audio.samplerate);
		}
	}else if(b->type == BoxStts){
		fprint(2, "\t%.*sentry_count\t%ud\n", dind, ind, b->stts.entrycount);
	}else if(b->type == BoxStss){
		fprint(2, "\t%.*sentry_count\t%ud\n", dind, ind, b->stss.entrycount);
	}else if(b->type == BoxStsc){
		fprint(2, "\t%.*sentry_count\t%ud\n", dind, ind, b->stss.entrycount);
		for(u = 0; u < b->stsc.entrycount; u++){
		for(u = 0; dflag > 1 && u < b->stsc.entrycount; u++){
			fprint(2, "\t%.*sentry[%zd]\n", dind, ind, u);
			fprint(2, "\t\t%.*sfirst_chunk\t%ud\n", dind, ind, b->stsc.entry[u].firstchunk);
			fprint(2, "\t\t%.*ssamples_per_chunk\t%ud\n", dind, ind, b->stsc.entry[u].samplesperchunk);
			fprint(2, "\t\t%.*ssample_description_table\t%ud\n", dind, ind, b->stsc.entry[u].sdt);
		}
	}else if(b->type == BoxStsz){
		fprint(2, "\t%.*ssample_size\t%ud\n", dind, ind, b->stsz.samplesize);
		fprint(2, "\t%.*ssample_size\t%ud\n", dind, ind, b->stsz.samplesizeeach);
		fprint(2, "\t%.*ssample_count\t%ud\n", dind, ind, b->stsz.samplecount);
		if(b->stsz.samplesize == 0){
		if(dflag > 1 && b->stsz.samplesizeeach == 0){
			for(u = 0; u < b->stsz.samplecount; u++)
				fprint(2, "\t%.*sentrysize[%zd]\t%ud\n", dind, ind, u, b->stsz.entrysize[u]);
				fprint(2, "\t%.*ssamplesize[%zd]\t%ud\n", dind, ind, u, b->stsz.samplesize[u]);
		}
	}else if(b->type == BoxTkhd){
		fprint(2, "\t%.*screation_time\t%zd\n", dind, ind, b->tkhd.creattime);


@@ 341,59 382,182 @@ printbox(Box *b)
		fprint(2, "\t%.*swidth\t%ud\n", dind, ind, b->tkhd.width);
		fprint(2, "\t%.*sheight\t%ud\n", dind, ind, b->tkhd.height);
	}else if(b->type == BoxHdlr){
		fprint(2, "\t%.*shandler_type\t%c%c%c%c\n", dind, ind, b->hdlr.handlertype>>24, b->hdlr.handlertype>>16&0xff, b->hdlr.handlertype>>8&0xff, b->hdlr.handlertype&0xff);
		fprint(2, "\t%.*shandler_type\t%T\n", dind, ind, b->hdlr.handlertype);
		fprint(2, "\t%.*sname\t%s\n", dind, ind, b->hdlr.name);
	}else if(b->type == BoxStco || b->type == BoxCo64){
		fprint(2, "\t%.*sentry_count\t%ud\n", dind, ind, b->stss.entrycount);
		for(u = 0; u < b->stco_co64.entrycount; u++)
		for(u = 0; dflag > 1 && u < b->stco_co64.entrycount; u++)
			fprint(2, "\t%.*schunkoffset[%zd]\t%zd\n", dind, ind, u, b->stco_co64.chunkoffset[u]);
	}else{
		fprint(2, "\t%.*sstart\t%zd\n", dind, ind, b->dstart);
		fprint(2, "\t%.*ssize\t%zd\n", dind, ind, b->dsz);
	}else if(dflag < 2){
		fprint(2, "\t%.*soffset\t%zd\n", dind, ind, b->offset);
		fprint(2, "\t%.*sdstart\t%zd\n", dind, ind, b->dstart);
		fprint(2, "\t%.*sdsize\t%zd\n", dind, ind, b->dsz);
	}
}

static void
dumptrack(Biobuf *f)
addtrack(void)
{
	tracks = realloc(tracks, (ntracks+1)*sizeof(*tracks));
	memmove(&tracks[ntracks++], &track, sizeof(track));
}

static int srate2mpeg4fi[] = {
	96000,
	88200,
	64000,
	48000,
	44100,
	32000,
	24000,
	22050,
	16000,
	12000,
	11025,
	8000,
	7350,
};

static int
dumptrack(Biobuf *f, int id)
{
	SampleToChunk *stc;
	u32int si, ch, lastch;
	u32int samplelast, sample, rawsz;
	vlong o;
	u32int si, ch, nextch;
	u32int samplelast, sample, rawsz, samplesz;
	u64int o, wo;
	int i;
	u8int *raw;
	Biobuf out;
	Track *t;
	u8int frame[7];

	for(t = tracks, i = 0; i < ntracks && t->id != id; i++, t++);
	if(i >= ntracks){
		werrstr("no track %d", id);
		return -1;
	}

	fprint(
		2,
		"track %d: handler=%T numstc=%ud chunks=%ud samples=%ud\n", t->id, t->handlertype, t->numstc, t->numchunks, t->numsamples
	);
	if(t->audio.format != 0){
		fprint(2, "audio: format=%T channels=%d samplerate=%d\n", t->audio.format, t->audio.channels, t->audio.samplerate);
		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] = 1<<6 | i<<2 | t->audio.channels>>2; /* AAC(LC)??? FIXME, frequency index, channels */
		}else{
			werrstr("audio: unknown format %T\n", t->audio.format);
			return -1;
		}
	}
	if(t->video.format != 0){
		fprint(2, "video: format=%T resolution=%dx%d\n", t->video.format, t->video.width, t->video.height);
		werrstr("video: unknown format %T", t->video.format);
		return -1;
	}

	Binit(&out, 1, OWRITE);
	raw = nil;
	rawsz = 0;
	sample = samplelast = 0;
	stc = track.stc;
	for(si = 0; si < track.numstc; si++, stc++){
		lastch = si+1 < track.numstc ? stc[1].firstchunk : track.numchunks;
		for(ch = stc->firstchunk-1; ch < lastch && ch < track.numchunks; ch++){
			o = track.chunkoffset[ch];
			if(Bseek(f, o, 0) != o)
				sysfatal("chunk %ud: %r", ch);
			for(; sample < samplelast+stc->samplesperchunk && sample < track.numsamples; sample++){
				if(track.samplesize[sample] == 0)
	stc = t->stc;
	wo = 0;
	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++){
			o = t->chunkoffset[ch];
			if(Bseek(f, o, 0) != o){
				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 < track.samplesize[sample]){
					rawsz = track.samplesize[sample] * 2;
				if(rawsz < samplesz){
					rawsz = samplesz * 2;
					raw = realloc(raw, rawsz);
				}
				if(Bread(f, raw, track.samplesize[sample]) != track.samplesize[sample])
					sysfatal("chunk %ud sample %ud size %ud: %r", ch, sample, track.samplesize[sample]);
				if(Bwrite(&out, raw, track.samplesize[sample]) != track.samplesize[sample])
					exits(nil);

				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)&0xff; /* 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;
					}
					wo += 7;
					o += 7;
				}
				if(Bwrite(&out, raw, samplesz) != samplesz){ /* EOF? */
					werrstr("eof");
					break;
				}
				wo += samplesz;
				o += samplesz;
			}
			samplelast = sample;
		}
	}
	fprint(2, "%ud samples\n", sample);
	Bterm(&out);
	free(raw);

	return 0;
};

int
sampleentry(Biobuf *f, Box *b, u32int fmt, int n)
{
	u8int d[96];

	if(track.handlertype == HandlerVideo){
		b->stsd.video.format = fmt;

		/* predefined+reserved+predefined, width+height, hres+vres, reserved, framecount, compressor */
		eBread(2+2+4*3 + 2+2 + 4+4 + 4 + 2 + 32, "SampleEntry: video");
		b->stsd.video.width = bu16(d+16);
		b->stsd.video.height = bu16(d+18);

		memmove(&track.video, &b->stsd.video, sizeof(Video));
	}else if(track.handlertype == HandlerAudio){
		b->stsd.audio.format = fmt;

		/* reserved+id, ver+rev+vendor, channels+bps, ?+?, sample rate */
		eBread(2+4+2 + 2+2 + 2+2 + 4, "SampleEntry: audio");
		b->stsd.audio.channels = bu16(d+8);
		b->stsd.audio.samplerate = bu32(d+16)>>16;

		memmove(&track.audio, &b->stsd.audio, sizeof(Audio));
		/* FIXME do we care about the rest? */
	}else{
		fprint(2, "SampleEntry: unknown handler type %T\n", track.handlertype);
	}

	return n - sizeof(d);
err:
	return -1;
}

static int
parseboxdata(Biobuf *f, Box *b)
{


@@ 432,14 596,7 @@ parseboxdata(Biobuf *f, Box *b)
				break;
		}
		if(b->type == BoxTrak){
			if(track.id == trackdump){
				u = Boffset(f);
				dumptrack(f);
				Bseek(f, u, 0);
			}
			free(track.chunkoffset);
			free(track.samplesize);
			free(track.stc);
			addtrack();
			memset(&track, 0, sizeof(track));
		}
		dind--;


@@ 560,11 717,17 @@ parseboxdata(Biobuf *f, Box *b)
		}
		printbox(b);
	}else if(b->type == BoxStsd){
		eBread(4, "handler_type");
		b->stsd.handlertype = bu32(d);
		eBread(4, "entry_count");
		b->stsd.entrycount = bu32(d);
		/* FIXME not reading actual entries here */
		for(u = 0; u < b->stsd.entrycount; u++){
			eBread(4, "size");
			n = bu32(d);
			eBread(4, "format");
			Bseek(f, 6+2, 1); /* skip reserved+id */
			n -= 8 + 6+2;
			n = sampleentry(f, b, bu32(d), n);
			Bseek(f, n, 1);
		}
		printbox(b);
	}else if(b->type == BoxStts){
		eBread(4, "entry_count");


@@ 604,17 767,18 @@ parseboxdata(Biobuf *f, Box *b)
		printbox(b);
	}else if(b->type == BoxStsz){
		eBread(4, "sample_size");
		b->stsz.samplesize = bu32(d);
		b->stsz.samplesizeeach = bu32(d);
		track.samplesizeeach = b->stsz.samplesizeeach;
		eBread(4, "sample_count");
		b->stsz.samplecount = bu32(d);
		if(b->stsz.samplesize == 0){
			b->stsz.entrysize = calloc(b->stsz.samplecount, sizeof(u32int));
		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.entrysize[u] = bu32(d);
				b->stsz.samplesize[u] = bu32(d);
			}
			track.numsamples = b->stsz.samplecount;
			track.samplesize = b->stsz.entrysize;
			track.samplesize = b->stsz.samplesize;
		}
		printbox(b);
	}else if(b->type == BoxTkhd){


@@ 653,6 817,7 @@ parseboxdata(Biobuf *f, Box *b)
		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){


@@ 683,7 848,7 @@ parsebox(Biobuf *f, Box *b, int *eof)
	int r;

	*eof = 0;
	b->dstart = start = Boffset(f);
	b->dstart = b->offset = start = Boffset(f);
	if((r = Bread(f, d, 8)) != 8){
		if(r == 0)
			*eof = 1;


@@ 739,17 904,19 @@ usage(void)
	exits("usage");
}

int
void
main(int argc, char **argv)
{
	Biobuf *f;
	Box b;
	int eof;
	Biobuf *f;
	char *status;
	int eof, trackdump;

	dflag = 0;
	trackdump = -1;
	ARGBEGIN{
	case 'd':
		dflag = 1;
		dflag++;
		break;
	case 't':
		trackdump = atoi(EARGF(usage()));


@@ 758,7 925,8 @@ main(int argc, char **argv)

	fmtinstall('T', typefmt);

	for(; *argv; argv++){
	status = nil;
	for(; *argv && status == nil; argv++){
		if((f = Bopen(*argv, OREAD)) == nil)
			sysfatal("%s: %r", *argv);



@@ 773,8 941,13 @@ main(int argc, char **argv)
			Bseek(f, b.dstart+b.dsz, 0);
		}

		if(trackdump >= 0 && dumptrack(f, trackdump) != 0){
			fprint(2, "%s: %r\n", *argv);
			status = "dump";
		}

		Bterm(f);
	}

	return 0;
	exits(status);
}