shithub: mcfs

Download patch

ref: 24a65311b538504923aaee04393fd363c2a63356
parent: c10d17a9f0c84746e55f939a2a2724c47090c96e
author: Sigrid Haflínudóttir <ftrvxmtrx@gmail.com>
date: Sun Sep 20 17:15:15 EDT 2020

ebml support

--- /dev/null
+++ b/common.h
@@ -1,0 +1,144 @@
+enum {
+	Nodump = -1,
+	Dumpaudio = -2,
+	Dumpvideo = -3,
+
+	FmtAv01 = 0x61763031u,
+	FmtAvc1 = 0x61766331u,
+	FmtMp4a = 0x6d703461u,
+	FmtOpus = 0x6f707573u,
+	FmtVp08 = 0x76703038u,
+	FmtVp09 = 0x76703039u,
+
+	/* a fake one, not supposed to show up in a mp4 */
+	FmtVorbis = 0x766f7262u,
+};
+
+typedef struct Ebml Ebml;
+typedef struct Framectx Framectx;
+typedef int (*packet_f)(Biobuf *out, Framectx *ctx, u8int *buf, int sz, uvlong ts);
+
+struct Framectx {
+	uvlong frid;
+	uvlong duration;
+	struct {
+		char name[16];
+		vlong delay;
+		struct {
+			uchar *data;
+			int sz;
+		}priv;
+	}codec;
+	u32int trackuid;
+	u32int fmt;
+
+	struct {
+		int width, height;
+		struct {
+			int left, right, top, bottom;
+		}crop;
+		struct {
+			int width, height;
+			int unit;
+			int aspectmode;
+		}display;
+	}video;
+	struct {
+		float samplerate;
+		int channels;
+		int bps;
+	}audio;
+};
+
+struct Ebml {
+	Framectx;
+
+	packet_f fpacket;
+
+	double ebmldur;
+	vlong timestampscale;
+	vlong seekpreroll;
+	vlong perframe;
+	int tracknum;
+	int tracktype;
+	vlong codecdelay;
+};
+
+extern Biobuf stderr, out;
+extern int dflag, trackdump;
+
+#pragma varargck type "T" u32int
+int isotypefmt(Fmt *f);
+
+int isorun(Biobuf *f);
+int ebmlrun(Biobuf *f);
+
+u32int crc32(u32int init, u8int *d, ulong len);
+int ivfpacket(Biobuf *out, Framectx *ctx, u8int *buf, int sz, uvlong ts);
+int oggpacket(Biobuf *out, Framectx *ctx, u8int *buf, int sz, uvlong ts);
+int ebmlint(Biobuf *f, vlong *out, int isid);
+vlong ebmlel(Biobuf *f, vlong sz, vlong *id, vlong *esz);
+vlong ebmlrawint(Biobuf *f, vlong sz, vlong *dst);
+
+#define ebmlgetnumber(expid, dest) \
+	if(id == expid){ \
+		vlong x; \
+		if(ebmlrawint(f, sz, &x) < 0) \
+			return -1; \
+		dest = x; \
+		left -= sz; \
+		continue; \
+	}
+
+#define ebmlgetstring(expid, dest) \
+	if(id == expid){ \
+		n = min(sizeof(dest)-1, sz); \
+		if(Bread(f, dest, n) != n) \
+			return -1; \
+		dest[n] = 0; \
+		if(n != sz) \
+			Bseek(f, sz-n, 1); \
+		left -= sz; \
+		continue; \
+	}
+
+#define ebmlgetbytes(expid, dest) \
+	if(id == expid){ \
+		dest.data = malloc(sz); \
+		if(Bread(f, dest.data, sz) != sz) \
+			return -1; \
+		dest.sz = sz; \
+		left -= sz; \
+		continue; \
+	}
+
+#define ebmlgetfloat(expid, dest) \
+	if(id == expid){ \
+		u32int u; \
+		union { \
+			uchar b[8]; \
+			u32int u[2]; \
+			float f; \
+			double d; \
+		}x; \
+		if(sz == 4){ \
+			if(Bread(f, x.b, 4) != 4) \
+				return -1; \
+			x.u[0] = (x.u[0]&0xff000000)>>24 | (x.u[0]&0x00ff0000)>>8 | (x.u[0]&0x0000ff00)<<8 | (x.u[0]&0x000000ff)<<24; \
+			dest = x.f; \
+		}else if(sz == 8){ \
+			if(Bread(f, x.b, 8) != 8) \
+				return -1; \
+			u = (x.u[0]&0xff000000)>>24 | (x.u[0]&0x00ff0000)>>8 | (x.u[0]&0x0000ff00)<<8 | (x.u[0]&0x000000ff)<<24; \
+			x.u[0] = (x.u[1]&0xff000000)>>24 | (x.u[1]&0x00ff0000)>>8 | (x.u[1]&0x0000ff00)<<8 | (x.u[1]&0x000000ff)<<24; \
+			x.u[1] = u; \
+			dest = x.d; \
+		}else{ \
+			werrstr("invalid float size"); \
+			break; \
+		} \
+		left -= sz; \
+		continue; \
+	}
+
+#define min(a,b) ((a)<=(b)?(a):(b))
--- /dev/null
+++ b/crc32.c
@@ -1,0 +1,61 @@
+#include <u.h>
+#include <libc.h>
+
+static u32int table[] = {
+        0x00000000, 0xb71dc104, 0x6e3b8209, 0xd926430d, 0xdc760413, 0x6b6bc517,
+        0xb24d861a, 0x0550471e, 0xb8ed0826, 0x0ff0c922, 0xd6d68a2f, 0x61cb4b2b,
+        0x649b0c35, 0xd386cd31, 0x0aa08e3c, 0xbdbd4f38, 0x70db114c, 0xc7c6d048,
+        0x1ee09345, 0xa9fd5241, 0xacad155f, 0x1bb0d45b, 0xc2969756, 0x758b5652,
+        0xc836196a, 0x7f2bd86e, 0xa60d9b63, 0x11105a67, 0x14401d79, 0xa35ddc7d,
+        0x7a7b9f70, 0xcd665e74, 0xe0b62398, 0x57abe29c, 0x8e8da191, 0x39906095,
+        0x3cc0278b, 0x8bdde68f, 0x52fba582, 0xe5e66486, 0x585b2bbe, 0xef46eaba,
+        0x3660a9b7, 0x817d68b3, 0x842d2fad, 0x3330eea9, 0xea16ada4, 0x5d0b6ca0,
+        0x906d32d4, 0x2770f3d0, 0xfe56b0dd, 0x494b71d9, 0x4c1b36c7, 0xfb06f7c3,
+        0x2220b4ce, 0x953d75ca, 0x28803af2, 0x9f9dfbf6, 0x46bbb8fb, 0xf1a679ff,
+        0xf4f63ee1, 0x43ebffe5, 0x9acdbce8, 0x2dd07dec, 0x77708634, 0xc06d4730,
+        0x194b043d, 0xae56c539, 0xab068227, 0x1c1b4323, 0xc53d002e, 0x7220c12a,
+        0xcf9d8e12, 0x78804f16, 0xa1a60c1b, 0x16bbcd1f, 0x13eb8a01, 0xa4f64b05,
+        0x7dd00808, 0xcacdc90c, 0x07ab9778, 0xb0b6567c, 0x69901571, 0xde8dd475,
+        0xdbdd936b, 0x6cc0526f, 0xb5e61162, 0x02fbd066, 0xbf469f5e, 0x085b5e5a,
+        0xd17d1d57, 0x6660dc53, 0x63309b4d, 0xd42d5a49, 0x0d0b1944, 0xba16d840,
+        0x97c6a5ac, 0x20db64a8, 0xf9fd27a5, 0x4ee0e6a1, 0x4bb0a1bf, 0xfcad60bb,
+        0x258b23b6, 0x9296e2b2, 0x2f2bad8a, 0x98366c8e, 0x41102f83, 0xf60dee87,
+        0xf35da999, 0x4440689d, 0x9d662b90, 0x2a7bea94, 0xe71db4e0, 0x500075e4,
+        0x892636e9, 0x3e3bf7ed, 0x3b6bb0f3, 0x8c7671f7, 0x555032fa, 0xe24df3fe,
+        0x5ff0bcc6, 0xe8ed7dc2, 0x31cb3ecf, 0x86d6ffcb, 0x8386b8d5, 0x349b79d1,
+        0xedbd3adc, 0x5aa0fbd8, 0xeee00c69, 0x59fdcd6d, 0x80db8e60, 0x37c64f64,
+        0x3296087a, 0x858bc97e, 0x5cad8a73, 0xebb04b77, 0x560d044f, 0xe110c54b,
+        0x38368646, 0x8f2b4742, 0x8a7b005c, 0x3d66c158, 0xe4408255, 0x535d4351,
+        0x9e3b1d25, 0x2926dc21, 0xf0009f2c, 0x471d5e28, 0x424d1936, 0xf550d832,
+        0x2c769b3f, 0x9b6b5a3b, 0x26d61503, 0x91cbd407, 0x48ed970a, 0xfff0560e,
+        0xfaa01110, 0x4dbdd014, 0x949b9319, 0x2386521d, 0x0e562ff1, 0xb94beef5,
+        0x606dadf8, 0xd7706cfc, 0xd2202be2, 0x653deae6, 0xbc1ba9eb, 0x0b0668ef,
+        0xb6bb27d7, 0x01a6e6d3, 0xd880a5de, 0x6f9d64da, 0x6acd23c4, 0xddd0e2c0,
+        0x04f6a1cd, 0xb3eb60c9, 0x7e8d3ebd, 0xc990ffb9, 0x10b6bcb4, 0xa7ab7db0,
+        0xa2fb3aae, 0x15e6fbaa, 0xccc0b8a7, 0x7bdd79a3, 0xc660369b, 0x717df79f,
+        0xa85bb492, 0x1f467596, 0x1a163288, 0xad0bf38c, 0x742db081, 0xc3307185,
+        0x99908a5d, 0x2e8d4b59, 0xf7ab0854, 0x40b6c950, 0x45e68e4e, 0xf2fb4f4a,
+        0x2bdd0c47, 0x9cc0cd43, 0x217d827b, 0x9660437f, 0x4f460072, 0xf85bc176,
+        0xfd0b8668, 0x4a16476c, 0x93300461, 0x242dc565, 0xe94b9b11, 0x5e565a15,
+        0x87701918, 0x306dd81c, 0x353d9f02, 0x82205e06, 0x5b061d0b, 0xec1bdc0f,
+        0x51a69337, 0xe6bb5233, 0x3f9d113e, 0x8880d03a, 0x8dd09724, 0x3acd5620,
+        0xe3eb152d, 0x54f6d429, 0x7926a9c5, 0xce3b68c1, 0x171d2bcc, 0xa000eac8,
+        0xa550add6, 0x124d6cd2, 0xcb6b2fdf, 0x7c76eedb, 0xc1cba1e3, 0x76d660e7,
+        0xaff023ea, 0x18ede2ee, 0x1dbda5f0, 0xaaa064f4, 0x738627f9, 0xc49be6fd,
+        0x09fdb889, 0xbee0798d, 0x67c63a80, 0xd0dbfb84, 0xd58bbc9a, 0x62967d9e,
+        0xbbb03e93, 0x0cadff97, 0xb110b0af, 0x060d71ab, 0xdf2b32a6, 0x6836f3a2,
+        0x6d66b4bc, 0xda7b75b8, 0x035d36b5, 0xb440f7b1, 0x00000001
+};
+static u32int poly = 0x04c11db7;
+
+u32int
+crc32(u32int init, u8int *d, ulong len)
+{
+	u32int c;
+	ulong i;
+
+	c = init;
+	for(i = 0; i < len; i++)
+		c = table[(c & 0xff) ^ d[i]] ^ (c >> 8);
+	return c;
+}
--- /dev/null
+++ b/ebml.c
@@ -1,0 +1,471 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include "common.h"
+
+static vlong frszs[256];
+
+int
+ebmluintb(u8int *b, int sz, vlong *out)
+{
+	uvlong v, m;
+	int c, n;
+
+	*out = 0;
+	for(n = 1, m = 0x80, v = 0; n <= 8; v <<= 8, m <<= 7, n++){
+		if(n-1 >= sz){
+			werrstr("eof");
+			return -1;
+		}
+		c = b[n-1];
+		if(n == 1 && c == 0){
+			werrstr("invalid number: %02x", c);
+			return -1;
+		}
+		v |= c;
+		if(v & m){
+			*out = v & ~m;
+			return n;
+		}
+		v &= ~m;
+	}
+	werrstr("number overflow");
+
+	return -1;
+}
+
+int
+ebmlsintb(u8int *b, int sz, vlong *out)
+{
+	int n;
+
+	if((n = ebmluintb(b, sz, out)) < 0)
+		return -1;
+	*out -= (1 << n*7-1) - 1;
+
+	return n;
+}
+
+int
+ebmluint(Biobuf *f, vlong *out, int isid)
+{
+	uvlong v, m;
+	int c, n;
+
+	*out = 0;
+	for(n = 1, m = 0x80, v = 0; n <= 8; v <<= 8, m <<= 7, n++){
+		if((c = Bgetc(f)) < 0){
+			werrstr("eof");
+			return -1;
+		}
+		if(n == 1 && c == 0){
+			werrstr("invalid number: %02x at 0x%zx", c, Boffset(f));
+			return -1;
+		}
+		v |= c;
+		if(v & m){
+			*out = isid ? v : (v & ~m);
+			return n;
+		}
+		if(!isid)
+			v &= ~m;
+	}
+	werrstr("number overflow");
+
+	return -1;
+}
+
+int
+ebmlsint(Biobuf *f, vlong *out)
+{
+	int n;
+
+	if((n = ebmluint(f, out, 0)) < 0)
+		return -1;
+	*out -= (1 << n*7-1) - 1;
+
+	return n;
+}
+
+vlong
+ebmlel(Biobuf *f, vlong sz, vlong *id, vlong *esz)
+{
+	vlong x, n, r;
+
+	if(sz < 2)
+		return -1;
+	n = 0;
+	if((n += (r = ebmluint(f, &x, 1))) >= sz || r < 0){
+		werrstr("id: %r");
+		return -1;
+	}
+	*id = x;
+	if((n += (r = ebmluint(f, &x, 0))) >= sz || r < 0 || sz-n < x){
+		werrstr("sz: (sz=%zd n=%zd r=%zd x=%zd): %r", sz, n, r, x);
+		return -1;
+	}
+	*esz = x;
+
+	return n;
+}
+
+vlong
+ebmlrawint(Biobuf *f, vlong sz, vlong *dst)
+{
+	vlong i;
+	int c;
+
+	*dst = 0;
+	for(i = 0; i < sz; i++){
+		*dst <<= 8;
+		if((c = Bgetc(f)) < 0){
+			werrstr("eof");
+			return -1;
+		}
+		*dst |= c;
+	}
+
+	return 0;
+}
+
+static char *
+codec(char *s)
+{
+	static char t[16];
+	int n;
+
+	n = snprint(t, sizeof(t), "%s", s+2);
+	for(n -= 1; n >= 0; n--)
+		t[n] = tolower(t[n]);
+	if((s = strchr(t, '/')) != nil)
+		*s = 0;
+
+	return t;
+}
+
+static int
+initctx(Ebml *e)
+{
+	char *c;
+
+	c = codec(e->codec.name);
+	if(e->tracktype == 1){
+		if(strcmp(c, "vp9") == 0)
+			e->fmt = FmtVp09;
+		else if(strcmp(c, "vp8") == 0)
+			e->fmt = FmtVp08;
+		else
+			goto err;
+		e->fpacket = ivfpacket;
+		return 0;
+	}
+	if(e->tracktype == 2){
+		if(strcmp(c, "vorbis") == 0){
+			e->fmt = FmtVorbis;
+			e->fpacket = oggpacket;
+			return 0;
+		}
+		if(strcmp(c, "opus") == 0){
+			e->fmt = FmtOpus;
+			e->fpacket = oggpacket;
+			return 0;
+		}
+	}
+	e->duration = e->ebmldur;
+	e->duration *= e->timestampscale;
+
+err:
+	werrstr("don't know how to remux %s", c);
+
+	return -1;
+}
+
+static char *
+tracktype(Ebml *e)
+{
+	static char *types[] = {
+		[1] = "video",
+		[2] = "audio",
+		[3] = "complex",
+		[16] = "logo",
+		[17] = "subtitle",
+		[18] = "buttons",
+		[32] = "control",
+		[33] = "metadata",
+	};
+	char *t;
+
+	t = e->tracktype < nelem(types) ? types[e->tracktype] : "???";
+	if(t == nil)
+		t = "???";
+
+	return t;
+}
+
+static void
+trackinfo(Biobuf *o, Ebml *e)
+{
+	char *t;
+
+	if(dflag){
+		Bprint(o, "per frame: %zd\n", e->perframe);
+		Bprint(o, "track number: %d\n", e->tracknum);
+		Bprint(o, "track type: %d\n", e->tracktype);
+		Bprint(o, "codec name: %s\n", e->codec.name);
+		Bprint(o, "codec priv data: %d bytes [%.*H]\n", e->codec.priv.sz, e->codec.priv.sz, e->codec.priv.data);
+		Bprint(o, "codec delay: %zdns\n", e->codec.delay);
+		if(e->tracktype == 1){
+			Bprint(o, "video: %dx%d [%d,%d,%d,%d]\n", e->video.width, e->video.height, e->video.crop.left, e->video.crop.top, e->video.crop.right, e->video.crop.bottom);
+			Bprint(o, "display: %dx%d unit=%d aspectmode=%d\n", e->video.display.width, e->video.display.height, e->video.display.unit, e->video.display.aspectmode);
+		}else if(e->tracktype == 2){
+			Bprint(o, "audio: samplerate=%g, channels=%d, bps=%d\n", e->audio.samplerate, e->audio.channels, e->audio.bps);
+		}
+		Bprint(o, "seek pre-roll: %zd\n", e->seekpreroll);
+	}
+
+	t = tracktype(e);
+	Bprint(o, "%d\t%s\t", e->tracknum, t);
+	if(e->tracktype == 1)
+		Bprint(o, "%s\t%d\t%d", codec(e->codec.name), e->video.width, e->video.height);
+	else if(e->tracktype == 2)
+		Bprint(o, "%s\t%d\t%d", codec(e->codec.name), e->audio.channels, (int)e->audio.samplerate);
+	else if(e->tracktype == 17)
+		Bprint(o, ""); /* FIXME */
+	else
+		Bprint(o, "???");
+	Bprint(o, "\n");
+}
+
+int
+ebmlrun(Biobuf *f)
+{
+	int isebml, nframes, i;
+	vlong left, id, n, sz, bufsz, track, off, frsz, x;
+	uvlong ts;
+	uchar *buf;
+	vlong timestamp, endtracks, timestampscale;
+	Ebml e, te;
+
+	buf = nil;
+	bufsz = 0;
+	track = -1;
+	timestamp = 0;
+	left = (1ULL<<63)-1;
+	endtracks = -1;
+	te.tracknum = -1;
+	timestampscale = 1000000;
+	e.tracknum = -1;
+	for(isebml = 0; left != 0;){
+		if(endtracks > 0 && left < endtracks && trackdump == Nodump){
+			/* early exit */
+			left = 0;
+			break;
+		}
+
+		off = Boffset(f);
+		if((n = ebmlel(f, left, &id, &sz)) < 0){
+			werrstr("invalid ebml: %r at 0x%zx", off);
+			goto err;
+		}
+		if(dflag > 1)
+			Bprint(&stderr, "0x%zux\t%zux\t%zd\t%zd\n", off, id, sz, left);
+		left -= n;
+
+		if(id == 0x1a45dfa3){ /* EBML comes first */
+			if(isebml != 0){
+				werrstr("double EBML?");
+				goto err;
+			}
+			isebml++;
+		}else if(id == 0x18538067){ /* segment */
+			left = sz;
+			if(isebml != 1){
+				werrstr("invalid ebml");
+				goto err;
+			}
+			isebml++; /* make sure we don't see more segments */
+			continue; /* go in */
+		}else if(id == 0x1549a966){ /* segment info */
+			continue;
+		}else if(id == 0x1654ae6b){ /* tracks */
+			endtracks = left - sz; /* to skip early in case track dump not required */
+			continue;
+		}else if(id == 0x1f43b675){ /* cluster */
+			if(trackdump != Nodump) /* skip it entirely if no dump required */
+				continue;
+		}else if(id == 0xae){ /* track entry */
+			if(e.tracknum > 0 && trackdump == Nodump)
+				trackinfo(&out, &e);
+			if(e.tracknum == trackdump)
+				memmove(&te, &e, sizeof(e));
+			memset(&e, 0, sizeof(e));
+			e.timestampscale = timestampscale;
+			continue;
+		}else if(id == 0xe0 || id == 0xe1){ /* video/audio settings */
+			continue;
+		}else if(id == 0xa0 && trackdump != Nodump){ /* block group */
+			continue;
+		}else if((id == 0xa3 || id == 0xa1) && trackdump != Nodump){ /* (simple) block */
+			if(te.tracknum == -1)
+				memmove(&te, &e, sizeof(e));
+			if((n = ebmluint(f, &track, 0)) < 0){
+				werrstr("block: %r");
+				goto err;
+			}
+			left -= n;
+			sz -= n;
+			if(track == trackdump && track == te.tracknum){
+				if(te.fpacket == nil && initctx(&te) != 0){
+					werrstr("packet: %r");
+					goto err;
+				}
+
+				if(bufsz < sz){
+					buf = realloc(buf, sz);
+					bufsz = sz;
+				}
+				if(Bread(f, buf, sz) != sz){
+					werrstr("short read");
+					goto err;
+				}
+				left -= sz;
+				sz -= 3;
+				/* ns timestamp */
+				ts = (timestamp + (s16int)(buf[0]<<8 | buf[1])) * timestampscale + te.codec.delay;
+				nframes = buf[3]+1;
+				switch((buf[2] >> 1) & 3){ /* lacing */
+				case 0: /* none */
+					if(te.fpacket(&out, &te, buf+3, sz, ts) != 0)
+						goto err;
+					break;
+				case 1: /* xiph */
+					sz--;
+					off = 4;
+					for(i = 0; i < nframes-1; i++){
+						frszs[i] = 0;
+						do{
+							frszs[i] += buf[off];
+						}while(buf[off++] == 0xff);
+					}
+					for(i = 0; i < nframes-1; i++){
+						if(te.fpacket(&out, &te, buf+off, frszs[i], ts) != 0)
+							goto err;
+						off += frszs[i];
+						sz -= frszs[i];
+					}
+					if(sz > 0 && te.fpacket(&out, &te, buf+off, sz, ts) != 0)
+						goto err;
+					break;
+				case 2: /* fixed-size */
+					sz--;
+					if((sz % nframes) != 0){
+						werrstr("invalid number of frames with fixed-size lacing");
+						goto err;
+					}
+					frsz = sz / nframes;
+					for(i = 0; i < nframes; i++){
+						if(te.fpacket(&out, &te, buf+4 + i*frsz, frsz, ts) != 0)
+							goto err;
+						sz -= frsz;
+					}
+					break;
+				case 3: /* ebml */
+					sz--;
+					frsz = 0;
+					off = 4;
+					for(i = 0; i < nframes-1; i++){
+						if((n = (i == 0 ? ebmluintb : ebmlsintb)(buf+off, sz, &x)) < 0)
+							goto err;
+						frsz += x;
+						if(frsz < 0){
+							werrstr("invalid frame size %zd", frsz);
+							goto err;
+						}
+						frszs[i] = frsz;
+						off += n;
+						sz -= n;
+					}
+					for(i = 0; i < nframes-1; i++){
+						frsz = frszs[i];
+						if(frsz > sz){
+							werrstr("frame %d/%d out of bounds: %zd > %zd", i, nframes-1, frsz, sz);
+							goto err;
+						}
+						if(frsz > 0 && te.fpacket(&out, &te, buf+off, frsz, ts) != 0)
+							goto err;
+						off += frsz;
+						sz -= frsz;
+					}
+					if(sz > 0 && te.fpacket(&out, &te, buf+off, sz, ts) != 0)
+						goto err;
+					break;
+				}
+				continue;
+			}
+		}else{
+				ebmlgetnumber(0x2ad7b1, timestampscale)
+			else
+				ebmlgetfloat(0xb5, e.audio.samplerate)
+			else
+				ebmlgetnumber(0x9f, e.audio.channels)
+			else
+				ebmlgetnumber(0x6264, e.audio.bps)
+			else
+				ebmlgetnumber(0xd7, e.tracknum)
+			else
+				ebmlgetnumber(0x83, e.tracktype)
+			else
+				ebmlgetstring(0x86, e.codec.name)
+			else
+				ebmlgetbytes(0x63a2, e.codec.priv)
+			else
+				ebmlgetnumber(0x56aa, e.codec.delay)
+			else
+				ebmlgetnumber(0xb0, e.video.width)
+			else
+				ebmlgetnumber(0xba, e.video.height)
+			else
+				ebmlgetnumber(0x54aa, e.video.crop.bottom)
+			else
+				ebmlgetnumber(0x54bb, e.video.crop.top)
+			else
+				ebmlgetnumber(0x54cc, e.video.crop.left)
+			else
+				ebmlgetnumber(0x54dd, e.video.crop.right)
+			else
+				ebmlgetnumber(0x54b0, e.video.display.width)
+			else
+				ebmlgetnumber(0x54ba, e.video.display.height)
+			else
+				ebmlgetnumber(0x54b2, e.video.display.unit)
+			else
+				ebmlgetnumber(0x54b3, e.video.display.aspectmode)
+			else
+				ebmlgetnumber(0xe7, timestamp)
+			else
+				ebmlgetnumber(0x23e383, e.perframe)
+			else
+				ebmlgetnumber(0x56bb, e.seekpreroll)
+			else
+				ebmlgetfloat(0x4489, e.duration)
+			else
+				ebmlgetnumber(0x73c5, e.trackuid)
+			//else
+			//	ebmlgetnumber(0x9b, blockdur)
+		}
+
+		if(Bseek(f, sz, 1) < 0)
+			return -1;
+		left -= sz;
+	}
+
+	if(isebml == 2 && left == 0){
+		if(e.tracknum > 0 && trackdump == Nodump)
+			trackinfo(&out, &e);
+		return 0;
+	}
+
+err:
+	return -1;
+}
--- a/iso.c
+++ b/iso.c
@@ -2,9 +2,8 @@
 #include <libc.h>
 #include <bio.h>
 #include <ctype.h>
+#include "common.h"
 
-#define MIN(a,b) ((a)<=(b)?(a):(b))
-
 typedef struct Audio Audio;
 typedef struct Box Box;
 typedef struct Moof Moof;
@@ -200,11 +199,6 @@
 	HandlerVideo = 0x76696465u,
 	HandlerAudio = 0x736f756eu,
 
-	FmtMp4a = 0x6d703461u,
-	FmtAv01 = 0x61763031u,
-	FmtAvc1 = 0x61766331u,
-	FmtVp09 = 0x76703039u,
-
 	BoxUuid = 0x75756964u,
 	BoxFtyp = 0x66747970u,
 	BoxMoov = 0x6d6f6f76u,
@@ -290,7 +284,6 @@
 	int id;
 };
 
-static int dflag;
 static int dind;
 static u32int defsamplesize;
 
@@ -297,7 +290,6 @@
 static Track track;
 static Track *tracks;
 static int ntracks;
-static Biobuf stderr;
 
 static Moof moof;
 static SampleEntry *sampleentry;
@@ -306,9 +298,8 @@
 
 static int parsebox(Biobuf *f, Box *b, int *eof);
 
-#pragma varargck type "T" u32int
-static int
-typefmt(Fmt *f)
+int
+isotypefmt(Fmt *f)
 {
 	char t[5];
 	int x;
@@ -330,7 +321,7 @@
 
 	dt = 0;
 	for(e = 0; e < t->numtts && s > 0; e++){
-		n = MIN(t->tts[e].samplecount, s);
+		n = min(t->tts[e].samplecount, s);
 		dt += t->tts[e].sampledelta * n;
 		s -= n;
 	}
@@ -526,7 +517,12 @@
 	Track *t;
 	int i;
 
-	for(t = tracks, i = 0; i < ntracks && t->id != id; i++, t++);
+	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;
@@ -1499,50 +1495,34 @@
 	return -1;
 }
 
-static void
-usage(void)
+static char *
+codec(u32int fmt)
 {
-	fprint(2, "usage: %s [-i | -t TRACK] FILE\n", argv0);
-	exits("usage");
+	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;
 }
 
-void
-main(int argc, char **argv)
+int
+isorun(Biobuf *f)
 {
 	Box b;
-	Biobuf *f;
-	char *status;
 	Track *t;
-	int eof, trackdump, iflag, i;
+	int eof, i;
 
-	dflag = 0;
-	iflag = 0;
-	trackdump = -1;
-	ARGBEGIN{
-	case 'i':
-		iflag++;
-		break;
-	case 'd':
-		dflag++;
-		break;
-	case 't':
-		trackdump = atoi(EARGF(usage()));
-		break;
-	default:
-		usage();
-	}ARGEND
-
-	if(argc != 1 || (iflag && trackdump >= 0))
-		usage();
-
-	fmtinstall('T', typefmt);
-	fmtinstall('H', encodefmt);
-
-	Binit(&stderr, 2, OWRITE);
-
-	if((f = Bopen(*argv, OREAD)) == nil)
-		sysfatal("%s: %r", *argv);
-
 	for(;;){
 		dind = 0;
 		memset(&b, 0, sizeof(b));
@@ -1549,31 +1529,26 @@
 		if(parsebox(f, &b, &eof) != 0){
 			if(eof)
 				break;
-			sysfatal("%s: %r", *argv);
+			return -1;
 		}
 		Bseek(f, b.dstart+b.dsz, 0);
 	}
 
-	status = nil;
-	if(iflag){
+	if(trackdump != Nodump && dumptrack(f, trackdump) != 0){
+		return -1;
+	}else{
 		for(i = 0; i < ntracks; i++){
 			t = &tracks[i];
-			print("%d	", t->id);
+			Bprint(&out, "%d\t", t->id);
 			if(t->handlertype == HandlerVideo)
-				print("video	%T	%d	%d", t->video.format, t->video.width, t->video.height);
+				Bprint(&out, "video\t%s\t%d\t%d", codec(t->video.format), t->video.width, t->video.height);
 			else if(t->handlertype == HandlerAudio)
-				print("audio	%T	%d	%d", t->audio.format, t->audio.channels, t->audio.samplerate);
+				Bprint(&out, "audio\t%s\t%d\t%d", codec(t->audio.format), t->audio.channels, t->audio.samplerate);
 			else
-				print("%T	???", t->handlertype);
-			print("\n");
+				Bprint(&out, "%T\t???", t->handlertype);
+			Bprint(&out, "\n");
 		}
-	}else if(trackdump >= 0 && dumptrack(f, trackdump) != 0){
-		Bprint(&stderr, "%r\n");
-		status = "dump";
 	}
 
-	Bterm(f);
-	Bterm(&stderr);
-
-	exits(status);
+	return 0;
 }
--- /dev/null
+++ b/ivf.c
@@ -1,0 +1,97 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "common.h"
+
+enum {
+	Timedenum = 1000ULL,
+};
+
+int
+ivfpacket(Biobuf *out, Framectx *ctx, u8int *buf, int sz, uvlong ts)
+{
+	u8int d[0x20];
+	uvlong dur;
+	int n, len;
+
+	dur = ctx->duration / 1000000ULL;
+	ts = ts / 1000000ULL;
+
+	if(ctx->frid == 0){
+		memmove(d, "DKIF", 4);
+		d[4] = 0;
+		d[5] = 0;
+		d[6] = 0x20;
+		d[7] = 0;
+		if(ctx->fmt == FmtAv01)
+			memmove(d+8, "AV01", 4);
+		else if(ctx->fmt == FmtAvc1)
+			memmove(d+8, "AVC1", 4);
+		else if(ctx->fmt == FmtVp08)
+			memmove(d+8, "VP80", 4);
+		else if(ctx->fmt == FmtVp09)
+			memmove(d+8, "VP90", 4);
+		else{
+			werrstr("unsupported video format %T", ctx->fmt);
+			goto err;
+		}
+		d[12] = ctx->video.width;
+		d[13] = ctx->video.width >> 8;
+		d[14] = ctx->video.height;
+		d[15] = ctx->video.height >> 8;
+		/* timebase denum */
+		d[16] = Timedenum;
+		d[17] = Timedenum >> 8;
+		d[18] = Timedenum >> 16;
+		d[19] = Timedenum >> 24;
+		/* timebase num */
+		d[20] = 1;
+		d[21] = 0;
+		d[22] = 0;
+		d[23] = 0;
+		/* FIXME is it a "number of frames" or "total duration?" */
+		d[24] = dur;
+		d[25] = dur >> 8;
+		d[26] = dur >> 16;
+		d[27] = dur >> 24;
+		d[28] = dur >> 32; /* FIXME is it 64 bits? */
+		d[29] = dur >> 40;
+		d[30] = dur >> 48;
+		d[31] = dur >> 56;
+
+		if(Bwrite(out, d, 0x20) != 0x20)
+			goto err;
+	}
+
+	d[0] = sz;
+	d[1] = sz >> 8;
+	d[2] = sz >> 16;
+	d[3] = sz >> 24;
+	d[4] = ts;
+	d[5] = ts >> 8;
+	d[6] = ts >> 16;
+	d[7] = ts >> 24;
+	d[8] = ts >> 32;
+	d[9] = ts >> 40;
+	d[10] = ts >> 48;
+	d[11] = ts >> 56;
+	if(Bwrite(out, d, 12) != 12)
+		goto err;
+	if(ctx->fmt == FmtAvc1){
+		for(n = 0; n < sz; n += len+4){
+			len = buf[n+0]<<24 | buf[n+1]<<16 | buf[n+2]<<8 | buf[n+3];
+			buf[n+0] = 0;
+			buf[n+1] = 0;
+			buf[n+2] = 0;
+			buf[n+3] = 1;
+		}
+	}
+	if(Bwrite(out, buf, sz) != sz)
+		goto err;
+	ctx->frid++;
+
+	return 0;
+err:
+	werrstr("ivfpacket: %r");
+	return -1;
+}
--- /dev/null
+++ b/main.c
@@ -1,0 +1,80 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "common.h"
+
+Biobuf stderr, out;
+int dflag, trackdump;
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-t INDEX|audio|video] [FILE]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	Biobuf *f;
+	char *s, *status;
+	u8int d[4];
+
+	dflag = 0;
+	trackdump = Nodump;
+	ARGBEGIN{
+	case 'd':
+		dflag++;
+		break;
+	case 't':
+		s = EARGF(usage());
+		if(strcmp(s, "audio") == 0)
+			trackdump = Dumpaudio;
+		else if(strcmp(s, "video") == 0)
+			trackdump = Dumpvideo;
+		else
+			trackdump = atoi(s);
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	f = nil;
+	if(argc == 1)
+		f = Bopen(*argv, OREAD);
+	else if(argc == 0)
+		f = Bfdopen(0, OREAD);
+	else
+		usage();
+
+	if(f == nil)
+		sysfatal("%r");
+
+	fmtinstall('H', encodefmt);
+	fmtinstall('T', isotypefmt);
+	Binit(&out, 1, OWRITE);
+	Binit(&stderr, 2, OWRITE);
+
+	status = nil;
+	if(Bread(f, d, 4) != 4){
+		Bprint(&stderr, "unknown format\n");
+		status = "unknown format";
+	}else if(Bungetc(f) < 0 || Bungetc(f) < 0 || Bungetc(f) < 0 || Bungetc(f) < 0){
+		Bprint(&stderr, "seek back failed: %r\n");
+		status = "seek failed";
+	}else if(memcmp(d, "\x1a\x45\xdf\xa3", 4) == 0){
+		if(ebmlrun(f) != 0){
+			Bprint(&stderr, "embl: %r\n");
+			status = "ebml failed";
+		}
+	}else if(isorun(f) != 0){
+		Bprint(&stderr, "iso: %r\n");
+		status = "iso failed";
+	}
+
+	Bterm(f);
+	Bterm(&out);
+	Bterm(&stderr);
+
+	exits(status);
+}
--- a/mkfile
+++ b/mkfile
@@ -6,9 +6,15 @@
 BIN=/$objtype/bin
 
 OFILES=\
+	crc32.$O\
+	ebml.$O\
 	iso.$O\
+	ivf.$O\
+	main.$O\
+	ogg.$O\
 
 HFILES=\
+	common.h\
 
 default:V:	all
 
--- /dev/null
+++ b/ogg.c
@@ -1,0 +1,126 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "common.h"
+
+static int sgszs[256];
+
+static int
+packet(Biobuf *out, Framectx *ctx, int htype, u8int *buf, int sz, uvlong granule)
+{
+	/*                 magic            ver  htype  granule          serial   seq      checksum  page segments  segsz */
+	/*         segsz   0                4    5      6                14       18       22        26             27 */
+	u8int h[28+255] = {'O','g','g','S', 0,   0,     0,0,0,0,0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  0,             0};
+	u32int crc;
+	int psz, i, n;
+
+	h[5] = htype;
+	h[6] = granule;
+	h[7] = granule >> 8;
+	h[8] = granule >> 16;
+	h[9] = granule >> 24;
+	h[10] = granule >> 32;
+	h[11] = granule >> 40;
+	h[12] = granule >> 48;
+	h[13] = granule >> 56;
+	h[14] = ctx->trackuid;
+	h[15] = ctx->trackuid >> 8;
+	h[16] = ctx->trackuid >> 16;
+	h[17] = ctx->trackuid >> 24;
+	h[18] = ctx->frid;
+	h[19] = ctx->frid >> 8;
+	h[20] = ctx->frid >> 16;
+	h[21] = ctx->frid >> 24;
+	ctx->frid++;
+	h[22] = 0;
+	h[23] = 0;
+	h[24] = 0;
+	h[25] = 0;
+	n = 1 + (sz > 0 ? (sz-1)/255 : 0);
+	h[26] = n;
+	if(n+27 >= nelem(h)){
+		werrstr("frame is too large");
+		return -1;
+	}
+	for(i = 0, psz = sz; i < n && i < nelem(h)-27; i++){
+		h[27+i] = min(255, psz);
+		psz -= h[27+i];
+		if(psz == 0)
+			break;
+	}
+	n += 27;
+	crc = crc32(crc32(0, h, n), buf, sz);
+	h[22] = crc >> 24;
+	h[23] = crc >> 16;
+	h[24] = crc >> 8;
+	h[25] = crc;
+	if(Bwrite(out, h, n) != n || Bwrite(out, buf, sz) != sz)
+		return -1;
+
+	return 0;
+}
+
+int
+oggpacket(Biobuf *out, Framectx *ctx, u8int *buf, int sz, uvlong ts)
+{
+	/*                     magic                            vendor len   list len */
+	u8int opuscomment[] = {'O','p','u','s','T','a','g','s', 0,0,0,0,     0,0,0,0};
+	int r, i, nsg, dsz, total;
+	u8int *d;
+	u64int gr;
+
+	gr = ts * 48 / 1000000;
+	r = 0;
+	if(ctx->frid == 0){ /* first packet? */
+		ctx->frid;
+
+		d = ctx->codec.priv.data;
+		dsz = ctx->codec.priv.sz;
+
+		/* id/codec setup header always goes first */
+		if(ctx->codec.priv.sz > 0){ /* if we have codec setup data, write it */
+			if(ctx->fmt == FmtVorbis){
+				nsg = *d++;
+				dsz--;
+				for(i = 0, total = 0; i < nsg && dsz > 0; i++, total += sgszs[i]){
+					sgszs[i] = 0;
+					do{
+						sgszs[i] += *d;
+						dsz--;
+					}while(*d++ == 0xff);
+				}
+				if(total > dsz){
+					werrstr("vorbis setup data out of bounds");
+					goto err;
+				}
+				for(i = 0; i < nsg && dsz > 0; d += sgszs[i], dsz -= sgszs[i], i++){
+					if(packet(out, ctx, i == 0 ? 2 : 0, d, sgszs[i], 0) != 0)
+						goto err;
+				}
+				if(dsz > 0)
+					r = packet(out, ctx, 0, d, dsz, 0);
+			}else{
+				r = packet(out, ctx, 2, d, dsz, 0);
+			}
+			sz = 0; /* don't duplicate it */
+		}else{ /* otherwise let's hope the first packet has that data itself */
+			r = packet(out, ctx, 2, buf, sz, 0);
+		}
+		if(r != 0)
+			goto err;
+
+		/* comment */
+		if(ctx->fmt == FmtOpus)
+			r = packet(out, ctx, 0, opuscomment, sizeof(opuscomment), 0);
+		if(r != 0)
+			goto err;
+	}
+
+	if(sz > 0 && packet(out, ctx, 0, buf, sz, gr) != 0)
+		goto err;
+
+	return 0;
+err:
+	werrstr("oggpacket: %r");
+	return -1;
+}