ref: 74213ea11514c175161cc1a61d48a429845d4624
dir: /rtmp.c/
#include <u.h> #include <libc.h> #include <thread.h> #include <bio.h> #include <libsec.h> enum { Port = 1935, Anum, Aarr = 8, Aend, Alstr = 12, Faudio = 8, Fvideo = 9, Fscript = 18, EncH264 = 7, EncAAC = 10, FlKey = 1<<0, FlHdr = 1<<1, Sigsz = 1536, }; typedef struct Frame Frame; typedef struct IVF IVF; struct Frame { u64int ts; u8int *buf; int bufsz; int sz; }; struct IVF { u16int w, h; u32int tbdenum, tbnum; }; int mainstacksize = 65536; static u8int * amfi16(u8int *p, u8int *e, s16int i) { if(p == nil) return nil; if(e-p < 2){ werrstr("buffer short"); return nil; } *p++ = i >> 8; *p++ = i; return p; } static u8int * amfi24(u8int *p, u8int *e, s32int i) { if(p == nil) return nil; if(e-p < 3){ werrstr("buffer short"); return nil; } *p++ = i >> 16; *p++ = i >> 8; *p++ = i; return p; } static u8int * amfi32(u8int *p, u8int *e, s32int i) { if(p == nil) return nil; if(e-p < 4){ werrstr("buffer short"); return nil; } *p++ = i >> 24; *p++ = i >> 16; *p++ = i >> 8; *p++ = i; return p; } static u8int * amfnum(u8int *p, u8int *e, double v) { union { double v; u64int u; }x; if(p == nil) return nil; if(p+8 > e){ werrstr("buffer short"); return nil; } x.v = v; *p++ = x.u >> 56; *p++ = x.u >> 48; *p++ = x.u >> 40; *p++ = x.u >> 32; *p++ = x.u >> 24; *p++ = x.u >> 16; *p++ = x.u >> 8; *p++ = x.u; return p; } static u8int * amfkvnum(u8int *p, u8int *e, char *name, double v) { int n; if(p == nil) return nil; if((n = strlen(name)) > 0xffff){ werrstr("string too long"); return nil; } if(p+2+n+8 > e){ werrstr("buffer short"); return nil; } p = amfi16(p, e, n); p = (u8int*)memmove(p, name, n) + n; return amfnum(p, e, v); } static u8int * amfstr(u8int *p, u8int *e, char *s) { int n; if(p == nil) return nil; n = strlen(s); if(p+1+4+n > e){ werrstr("string too long"); return nil; } *p++ = Alstr; return (u8int*)memmove(amfi32(p, e, n), s, n) + n; } static u8int * amfarr(u8int *p, u8int *e) { if(p == nil) return nil; if(p == e){ werrstr("buffer short"); return nil; } *p++ = Aarr; return p; } static u8int * amfend(u8int *p, u8int *e) { return amfi24(p, e, Aend); } static u8int * flvscript(u8int *p, u8int *e, int w, int h, int audio) { u8int *psz, *d, *p0; int stream; u32int ts; if(p+16 > e){ werrstr("buffer short"); return nil; } /* FIXME ever need to change these? */ stream = 0; ts = 0; p0 = p; *p++ = Fvideo; psz = p; p = amfi24(p, e, 0); /* sz set later */ p = amfi24(p, e, ts); *p++ = ts>>24; p = amfi24(p, e, stream); d = p; p = amfstr(p, e, "onMetaData"); p = amfarr(p, e); p = amfi32(p, e, audio ? 5 : 4); p = amfkvnum(p, e, "duration", 0.0); p = amfkvnum(p, e, "width", w); p = amfkvnum(p, e, "height", h); p = amfkvnum(p, e, "videocodecid", EncH264); if(audio) p = amfkvnum(p, e, "audiocodecid", EncAAC); p = amfend(p, e); amfi24(psz, e, p-d); return amfi32(p, e, p-p0); } static u8int * flvdata(u8int *p, u8int *e, u32int pts, u32int dts, void *data, int sz, int type, int fl) { u8int *p0, *psz, *d; int stream; /* FIXME ever need to change these? */ stream = 0; assert(type == Faudio || type == Fvideo); p0 = p; *p++ = type; psz = p; p = amfi24(p, e, 0); /* size to be set later */ p = amfi24(p, e, dts); *p++ = dts >> 24; p = amfi24(p, e, stream); d = p; if(type == Faudio){ *p++ = (EncAAC<<4) | 0x0f; *p++ = (fl & FlHdr) ? 0 : 1; } if(type == Fvideo){ *p++ = ((fl & FlKey) ? 0x10 : 0x20) | EncH264; *p++ = (fl & FlHdr) ? 0 : 1; pts = ((fl & FlHdr) || pts < dts) ? 0 : (pts - dts); p = amfi24(p, e, pts); if((fl & FlHdr) == 0) p = amfi32(p, e, sz); } p = (u8int*)memmove(p, data, sz) + sz; amfi24(psz, e, p-d); return amfi32(p, e, p-p0); } static int Bu16le(Biobuf *b, u16int *o) { int x; x = Bgetc(b); x |= Bgetc(b)<<8; *o = x; if(x < 0) werrstr("failed to read 2 bytes"); return x < 0 ? -1 : 0; } static int Bu32le(Biobuf *b, u32int *o) { int x, i; *o = 0; for(i = 0; i < 4; *o |= x<<(i*8), i++){ if((x = Bgetc(b)) < 0){ werrstr("failed to read 4 bytes"); return -1; } } return 0; } static int Bu64le(Biobuf *b, u64int *o) { int x, i; *o = 0; for(i = 0; i < 8; *o |= x<<(i*8), i++){ if((x = Bgetc(b)) < 0){ werrstr("failed to read 8 bytes"); return -1; } } return 0; } static int ivfopen(Biobuf *v, IVF *o) { u16int hlen; u8int b[6]; if(Bread(v, b, 6) != 6 || Bu16le(v, &hlen) < 0 || hlen < 0x20 || memcmp(b, "DKIF", 4) != 0 || Bread(v, b, 4) != 4){ werrstr("invalid header"); goto err; } if(memcmp(b, "AVC1", 4) != 0){ werrstr("not H.264"); goto err; } if(Bu16le(v, &o->w) < 0 || Bu16le(v, &o->h) < 0 || Bu32le(v, &o->tbdenum) < 0 || Bu32le(v, &o->tbnum) < 0){ werrstr("invalid data"); goto err; } if(Bseek(v, hlen, 0) != hlen){ werrstr("broken stream"); goto err; } return 0; err: werrstr("ivf: %r"); return -1; } static int ivfread(Biobuf *v, Frame *f) { u8int *buf; u64int ts; u32int sz; int n; if(Bu32le(v, &sz) < 0 || Bu64le(v, &ts) < 0 || (int)sz < 0){ /* eof */ f->sz = 0; return 0; } buf = f->buf; if(sz > f->bufsz){ if((buf = realloc(f->buf, sz)) == nil){ werrstr("frame is too big: %d bytes", sz); goto err; } f->buf = buf; } if((n = Bread(v, buf, sz)) != sz){ werrstr("short read (%d < %d)", n, sz); goto err; } f->buf = buf; f->sz = sz; f->ts = ts; return 0; err: werrstr("ivf: %r"); return -1; } static int handshake(int f, char *path) { u8int cl[1+Sigsz], sv[1+Sigsz]; cl[0] = 3; /* no encryption */ memset(cl+1, 0, 8); prng(cl+1+8, Sigsz-8); if(write(f, cl, sizeof(cl)) != sizeof(cl)) goto err; if(readn(f, sv, sizeof(sv)) != sizeof(sv)) goto err; if(cl[0] != sv[0]){ werrstr("expected %d (no encryption), got %d", cl[0], sv[0]); goto err; } if(write(f, sv+1, Sigsz) != Sigsz) goto err; if(readn(f, sv+1, Sigsz) != Sigsz) goto err; if(memcmp(cl, sv, sizeof(cl)) != 0){ werrstr("signature mismatch"); goto err; } return f; err: werrstr("handshake: %r"); close(f); return -1; } static int rtmpdial(char *url) { char *s, *e, *path; int f, port, ctl; if(memcmp(url, "rtmp://", 7) != 0){ werrstr("invalid url"); goto err; } s = url + 7; if((e = strpbrk(s, ":/")) == nil){ werrstr("no path"); goto err; } port = 1935; if(*e == ':'){ if((port = strtol(e+1, &path, 10)) < 1 || path == e+1 || *path != '/'){ werrstr("invalid port"); goto err; } }else{ path = e; } s = smprint("tcp!%.*s!%d", (int)(e-s), s, port); f = dial(s, nil, nil, &ctl); free(s); if(f < 0) goto err; return handshake(f, path); err: werrstr("rtmpdial: %r"); return -1; } static void usage(void) { fprint(2, "usage: %s [-a AUDIO] -v VIDEO [URL]\n", argv0); threadexitsall("usage"); } void threadmain(int argc, char **argv) { Biobuf *a, *v, o; u8int *b, *p, *e; int bufsz, fd; Frame f; IVF ivf; a = nil; v = nil; ARGBEGIN{ case 'a': if((a = Bopen(EARGF(usage()), OREAD)) == nil) sysfatal("%r"); break; case 'v': if((v = Bopen(EARGF(usage()), OREAD)) == nil) sysfatal("%r"); break; default: usage(); }ARGEND if(argc != 1) usage(); if(v == nil) sysfatal("no video specified"); if(ivfopen(v, &ivf) != 0) sysfatal("%r"); srand(time(nil)); if((fd = rtmpdial(argv[0])) < 0 || Binit(&o, fd, OWRITE) < 0) sysfatal("%r"); bufsz = 65536; if((b = malloc(bufsz)) == nil) sysfatal("memory"); e = b + bufsz; if((p = flvscript(b, e, ivf.w, ivf.h, 0)) == nil || Bwrite(&o, b, p-b) < 0) sysfatal("%r"); memset(&f, 0, sizeof(f)); for(;;){ if(ivfread(v, &f) != 0) sysfatal("%r"); if(f.sz == 0) break; if(bufsz < f.sz+64){ free(b); bufsz *= 2; if((b = malloc(bufsz)) == nil) sysfatal("memory"); e = b + bufsz; } if((p = flvdata(b, e, f.ts, f.ts, f.buf, f.sz, Fvideo, FlHdr)) == nil) sysfatal("video: flvdata: %r"); if(Bwrite(&o, b, p-b) < 0) sysfatal("%r"); Bflush(&o); } threadexitsall(nil); }