shithub: mcfs

ref: c2d2a672624e73d18c32a25ba66cad39d7691070
dir: /ebml.c/

View raw version
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ctype.h>
#include "common.h"

static Packet packets[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, double duration)
{
	char *c;

	e->duration = duration;
	e->duration *= e->timestampscale;
	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;
		}
	}

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, npackets, i;
	vlong left, id, n, sz, bufsz, track, off, packetsz, x, endtracks;
	uvlong ts, timestamp, timestampscale;
	uchar *buf;
	double duration;
	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;
	duration = 0;
	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, duration) != 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;
				npackets = buf[3]+1;
				switch((buf[2] >> 1) & 3){ /* lacing */
				case 0: /* none */
					packets[0].data = buf+3;
					packets[0].sz = sz;
					npackets = 1;
					break;
				case 1: /* xiph */
					sz--;
					off = 4;
					for(i = 0; i < npackets-1; i++){
						packets[i].sz = 0;
						do{
							packets[i].sz += buf[off];
						}while(buf[off++] == 0xff);
					}
					for(i = 0; i < npackets-1; i++){
						packets[i].data = buf+off;
						off += packets[i].sz;
						sz -= packets[i].sz;
					}
					packets[i].data = buf+off;
					packets[i].sz = sz;
					break;
				case 2: /* fixed-size */
					sz--;
					if((sz % npackets) != 0){
						werrstr("invalid number of frames with fixed-size lacing");
						goto err;
					}
					packets[0].sz = sz / npackets;
					for(i = 0; i < npackets; i++){
						packets[i].data = buf+4 + i*packets[0].sz;
						packets[i].sz = packets[0].sz;
						sz -= packets[0].sz;
					}
					break;
				case 3: /* ebml */
					sz--;
					packetsz = 0;
					off = 4;
					for(i = 0; i < npackets-1; i++){
						if((n = (i == 0 ? ebmluintb : ebmlsintb)(buf+off, sz, &x)) < 0)
							goto err;
						packetsz += x;
						if(packetsz < 0){
							werrstr("invalid frame size %zd", packetsz);
							goto err;
						}
						packets[i].sz = packetsz;
						off += n;
						sz -= n;
					}
					for(i = 0; i < npackets-1; i++){
						if(packets[i].sz > sz){
							werrstr("frame %d/%d out of bounds: %d > %zd", i, npackets-1, packets[i].sz, sz);
							goto err;
						}
						packets[i].data = buf+off;
						off += packets[i].sz;
						sz -= packets[i].sz;
					}
					packets[i].data = buf+off;
					packets[i].sz = sz;
					break;
				}

				/* ns timestamp */
				ts = (timestamp + (s16int)(buf[0]<<8 | buf[1])) * timestampscale - te.codec.delay;
				if(te.fpacket(&out, &te, packets, npackets, ts) != 0)
					goto err;
				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, duration)
			else
				ebmlgetnumber(0x73c5, e.trackuid)
			else
				ebmlgetnumber(0x75a2, te.discardpad)
			//else
			//	ebmlgetnumber(0x9b, blockdur)
		}

		if(Bseek(f, sz, 1) < 0)
			return -1;
		left -= sz;
	}

	if(isebml == 2 && left == 0){
		if(e.tracknum > 0){
			if(trackdump == Nodump)
				trackinfo(&out, &e);
		}
		return 0;
	}

err:
	return -1;
}