ref: 041e2ada566d991e41dc0f626d1d20582276a6c0
dir: /ebml.c/
#include <u.h> #include <libc.h> #include <bio.h> #include <ctype.h> #include "common.h" enum { Trackvideo = 1, Trackaudio = 2, Trackcomplex = 3, Tracklogo = 16, Tracksubtitles = 17, Trackbuttons = 18, Trackcontrol = 32, Trackmetadata = 33, }; 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 * format(Ebml *e) { static char t[16]; char *s; int n; if(e->tracktype == Tracksubtitles){ if(strcmp(e->codec.name, "S_TEXT/UTF8") == 0) return "srt"; }else if(e->tracktype == Trackaudio){ if(strcmp(e->codec.name, "A_MPEG/L3") == 0) return "mp3"; }else if(e->tracktype == Trackvideo){ 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 == Trackvideo){ 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 == Trackaudio){ 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 = asispacket; }else goto err; return 0; }else if(e->tracktype == Tracksubtitles){ 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 char * tracktype(Ebml *e) { static char *types[] = { [Trackvideo] = "video", [Trackaudio] = "audio", [Trackcomplex] = "complex", [Tracklogo] = "logo", [Tracksubtitles] = "subtitle", [Trackbuttons] = "buttons", [Trackcontrol] = "control", [Trackmetadata] = "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 == Trackvideo){ 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 == Trackaudio){ 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%s\t", e->tracknum, t, format(e)); if(e->tracktype == 1) Bprint(o, "%d\t%d", e->video.width, e->video.height); else if(e->tracktype == 2) Bprint(o, "%d\t%d", e->audio.channels, (int)e->audio.samplerate); else if(e->tracktype == 17) Bprint(o, "%s", e->lang); 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; ts = 0; for(isebml = 0; left != 0;){ if(id == 0x9b) te.blockdur *= timestampscale; 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){ if(trackdump == Nodump) trackinfo(&out, &e); else if(trackdump == Dumpvideo && e.tracktype == Trackvideo) trackdump = e.tracknum; else if(trackdump == Dumpaudio && e.tracktype == Trackaudio) trackdump = e.tracknum; } 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(trackdump == Dumpvideo && e.tracktype == Trackvideo) trackdump = te.tracknum; else if(trackdump == Dumpaudio && e.tracktype == Trackaudio) 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(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, te.blockdur) else ebmlgetstring(0x22b59c, 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; }