shithub: mcfs

ref: e4ab603fe6e5875691319f04acbd44c5f51d372b
dir: mcfs/matroska.c

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

typedef struct Ebml Ebml;

struct Ebml {
	Packetctx;

	packet_f fpacket;

	vlong timestampscale;
	vlong perframe;
	int tracknum;
	int tracktype;
	vlong codecdelay;
	char lang[8];
};

static Packet packets[256];
static vlong stack[32];

#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; \
	}

static char *
format(Ebml *e)
{
	static char t[16];
	char *s;
	int n;

	if(e->tracktype == Etracksubtitles){
		if(strcmp(e->codec.name, "S_TEXT/UTF8") == 0)
			return "srt";
	}else if(e->tracktype == Etrackaudio){
		if(strcmp(e->codec.name, "A_MPEG/L3") == 0)
			return "mp3";
	}else if(e->tracktype == Etrackvideo){
		if(strcmp(e->codec.name, "V_MPEG4/ISO/AVC") == 0)
			return "h264";
	}

	n = snprint(t, sizeof(t), "%s", e->codec.name+2);
	for(n -= 1; n >= 0; n--)
		t[n] = tolower(t[n]);
	if((s = strchr(t, '/')) != nil)
		*s = 0;

	return t;
}

static int
asispacket(Biobuf *out, Packetctx *, Packet *p, int np, uvlong)
{
	int i;

	for(i = 0; i < np; i++, p++)
		Bwrite(out, p->data, p->sz);

	return 0;
}

static int
initctx(Ebml *e, double duration)
{
	char *c;

	e->duration = duration;
	e->duration *= e->timestampscale;
	c = format(e);
	if(e->tracktype == Etrackvideo){
		if(strcmp(c, "vp9") == 0)
			e->fmt = FmtVp09;
		else if(strcmp(c, "vp8") == 0)
			e->fmt = FmtVp08;
		else if(strcmp(c, "h264") == 0)
			e->fmt = FmtAvc1;
		else
			goto err;
		e->fpacket = ivfpacket;
		return 0;
	}else if(e->tracktype == Etrackaudio){
		if(strcmp(c, "vorbis") == 0){
			e->fmt = FmtVorbis;
			e->fpacket = oggpacket;
		}else if(strcmp(c, "opus") == 0){
			e->fmt = FmtOpus;
			e->fpacket = oggpacket;
		}else if(strcmp(c, "mp3") == 0){
			e->fmt = FmtMp3;
			e->fpacket = asispacket;
		}else if(strcmp(c, "aac") == 0){
			e->fmt = FmtMp4a;
			e->fpacket = aacpacket;
		}else
			goto err;
		return 0;
	}else if(e->tracktype == Etracksubtitles){
		if(strcmp(c, "srt") == 0){
			e->fmt = FmtSrt;
			e->fpacket = srtpacket;
		}else
			goto err;
		return 0;
	}

err:
	werrstr("don't know how to remux %s (track type %d)", e->codec.name, e->tracktype);

	return -1;
}

static void
trackinfo(Biobuf *o, Ebml *e)
{
	char *t;

	t = ebmltracktype(e->tracktype);
	Bprint(o, "%d\t%s\t%s\t", e->tracknum, t, format(e));
	if(e->tracktype == Etrackvideo)
		Bprint(o, "%d\t%d", e->video.width, e->video.height);
	else if(e->tracktype == Etrackaudio)
		Bprint(o, "%d\t%d", e->audio.channels, (int)e->audio.samplerate);
	else if(e->tracktype == Etracksubtitles)
		Bprint(o, "%s", e->lang);
	else
		Bprint(o, "???");
	Bprint(o, "\n");
}

int
matroskarun(Biobuf *f)
{
	int isebml, npackets, i, sti, skipdata;
	vlong left, id, n, sz, bufsz, track, off, packetsz, x, endtracks;
	uvlong ts, timestamp, timestampscale;
	uchar *buf;
	double duration;
	Ebml e, te;
	Elspec *el;

	buf = nil;
	bufsz = 0;
	track = -1;
	timestamp = 0;
	left = (1ULL<<63)-1;
	endtracks = -1;
	te.tracknum = -1;
	timestampscale = 1000000;
	e.tracknum = -1;
	duration = 0;
	ts = 0;
	sti = 0;
	skipdata = trackdump == Nodump && dflag < 2;
	for(isebml = 0; left != 0;){
		if(id == EBlockDuration)
			te.blockdur *= timestampscale;
		if(endtracks > 0 && left < endtracks && skipdata){
			/* early exit */
			left = 0;
			break;
		}

		off = Boffset(f);
		if((n = ebmlel(f, left, &id, &sz)) < 0){
			werrstr("invalid ebml: %r at %#zx (size %zd)", off, sz);
			goto err;
		}
		if(n == 0) /* eof */
			break;
		while(sti > 0 && off >= stack[sti-1])
			sti--;
		left -= n;
		if(dflag > 1){
			if((el = ebmlelspec(id)) != nil){
				for(i = 0; i < sti; i++)
					Bputc(&stderr, '\t');
				Bprint(&stderr, "%s\n", el->name);
				if(el->type == Emaster && sti < nelem(stack))
					stack[sti++] = off+n+sz;
			}
			else
				Bprint(&stderr, "%#llx\n", id);
		}

		if(id == EEBML){ /* EBML comes first */
			if(isebml != 0){
				werrstr("double EBML?");
				goto err;
			}
			isebml++;
		}else if(id == ESegment){
			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 == EInfo){ /* segment info */
			continue;
		}else if(id == ETracks){
			endtracks = left - sz; /* to skip early in case track dump not required */
			continue;
		}else if(id == ECluster){
			if(!skipdata) /* skip it entirely if no dump required */
				continue;
		}else if(id == ETrackEntry){ /* track entry */
			if(e.tracknum > 0){
				if(trackdump == Nodump)
					trackinfo(&out, &e);
				else if(trackdump == Dumpvideo && e.tracktype == Etrackvideo)
					trackdump = e.tracknum;
				else if(trackdump == Dumpaudio && e.tracktype == Etrackaudio)
					trackdump = e.tracknum;
			}
			if(e.tracknum == trackdump)
				memmove(&te, &e, sizeof(e));
			memset(&e, 0, sizeof(e));
			e.timestampscale = timestampscale;
			continue;
		}else if(id == EVideo || id == EAudio){
			continue;
		}else if(id == EBlockGroup && !skipdata){
			continue;
		}else if((id == ESimpleBlock || id == EBlock) && !skipdata){
			if(te.tracknum == -1)
				memmove(&te, &e, sizeof(e));
			if((n = ebmluint(f, &track)) < 0){
				werrstr("block: %r");
				goto err;
			}
			left -= n;
			sz -= n;
			if(trackdump == Dumpvideo && e.tracktype == Etrackvideo)
				trackdump = te.tracknum;
			else if(trackdump == Dumpaudio && e.tracktype == Etrackaudio)
				trackdump = te.tracknum;
			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;
				}

				if(te.fpacket(&out, &te, packets, npackets, ts) != 0)
					goto err;
				/* ns timestamp */
				ts = (timestamp + (s16int)(buf[0]<<8 | buf[1])) * timestampscale - te.codec.delay;
				continue;
			}
		}else{
				ebmlgetnumber(ETimestampScale, timestampscale)
			else
				ebmlgetfloat(ESamplingFrequency, e.audio.samplerate)
			else
				ebmlgetnumber(EChannels, e.audio.channels)
			else
				ebmlgetnumber(EBitDepth, e.audio.bps)
			else
				ebmlgetnumber(ETrackNumber, e.tracknum)
			else
				ebmlgetnumber(ETrackType, e.tracktype)
			else
				ebmlgetstring(ECodecID, e.codec.name)
			else
				ebmlgetbytes(ECodecPrivate, e.codec.priv)
			else
				ebmlgetnumber(ECodecDelay, e.codec.delay)
			else
				ebmlgetnumber(EPixelWidth, e.video.width)
			else
				ebmlgetnumber(EPixelHeight, e.video.height)
			else
				ebmlgetnumber(ETimestamp, timestamp)
			else
				ebmlgetnumber(EDefaultDuration, e.perframe)
			else
				ebmlgetnumber(ESeekPreRoll, e.seekpreroll)
			else
				ebmlgetfloat(EDuration, duration)
			else
				ebmlgetnumber(ETrackUID, e.trackuid)
			else
				ebmlgetnumber(EDiscardPadding, te.discardpad)
			else
				ebmlgetnumber(EBlockDuration, te.blockdur)
			else
				ebmlgetstring(ELanguage, e.lang)
		}

		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;
}