ref: 94d64d663d67e8553bb95a138574737147b38426
parent: 09b812b49d237cfdcd53273b33dea9ccc355fe9a
author: Sigrid Solveig Haflínudóttir <ftrvxmtrx@gmail.com>
date: Wed Aug 4 14:04:22 EDT 2021
fix video stream packing and url parsing (can stream to Twitch directly now)
--- a/amf0.c
+++ b/amf0.c
@@ -243,10 +243,13 @@
free(a->obj.k[i]);
a₀free(a->obj.v[i]);
}
+ free(a->obj.k);
+ free(a->obj.v);
break;
case Tarr:
for(i = 0; i < a->arr.n; i++)
a₀free(a->arr.v[i]);
+ free(a->arr.v);
case Tnull:
case Tnum:
case Tbool:
--- a/ivf.c
+++ b/ivf.c
@@ -2,6 +2,7 @@
#include <libc.h>
#include <bio.h>
#include "ivf.h"
+#include "util.h"
static int
Bu16le(Biobuf *b, u16int *o)
@@ -84,7 +85,6 @@
int
ivfread(Biobuf *v, IVFrame *f)
{
- u8int *buf;
u64int ts;
u32int sz;
int n;
@@ -94,19 +94,14 @@
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;
+ f->bufsz = sz*2;
+ f->buf = erealloc(f->buf, sz*2);
}
- if((n = Bread(v, buf, sz)) != sz){
+ if((n = Bread(v, f->buf, sz)) != sz){
werrstr("short read (%d < %d)", n, sz);
goto err;
}
- f->buf = buf;
f->sz = sz;
f->ts = ts;
--- a/main.c
+++ b/main.c
@@ -19,8 +19,9 @@
void
threadmain(int argc, char **argv)
{
- u64int ns, ons;
Biobuf *a, *v;
+ u64int ns;
+ ulong sid;
IVFrame f;
IVF ivf;
RTMP *r;
@@ -51,27 +52,17 @@
sysfatal("%r");
if(strcmp(ivf.type, "AVC1") != 0)
sysfatal("not H.264");
+
srand(time(nil));
if((r = rtmpdial(argv[0])) == nil)
sysfatal("%r");
- ulong sid;
- fprint(2, "asking for a stream\n");
- if(rtmpstream(r, &sid) == 0){
- fprint(2, "stream: %lud\n", sid);
- if(rtmppublish(r, sid, PubLive, "???") == 0){
- fprint(2, "stream published\n");
- if(rtmpmeta(r, sid, VcodecH264, 1920, 1088, -1) == 0)
- fprint(2, "metadata sent\n");
- else
- fprint(2, "metadata failed: %r\n");
- }else
- fprint(2, "stream publish failed: %r\n");
- }else{
- fprint(2, "stream failed\n");
+ if(rtmpstream(r, &sid) != 0 ||
+ rtmppublish(r, sid, PubLive, nil) != 0 ||
+ rtmpmeta(r, sid, VcodecH264, ivf.w, ivf.h, -1) != 0){
+ sysfatal("%r");
}
- ons = 0;
memset(&f, 0, sizeof(f));
for(;;){
if(ivfread(v, &f) != 0)
@@ -78,10 +69,9 @@
sysfatal("%r");
if(f.sz == 0)
break;
- ns = ivfns(&ivf, f.ts);
- if(rtmpdata(r, sid, (ns - ons) / 1000000ULL, Tvideo, f.buf, f.sz) != 0)
+ ns = ivfns(&ivf, f.ts)/1000000ULL;
+ if(rtmpdata(r, sid, ns, Tvideo, f.buf, f.sz) != 0)
sysfatal("video: flvdata: %r");
- ons = ns;
}
threadexitsall(nil);
--- a/rtmp.c
+++ b/rtmp.c
@@ -108,9 +108,10 @@
Buffer i;
Buffer o;
Channel *c;
- char *app;
- char *path;
char *tcurl;
+ char *app;
+ char *inst;
+ char *path; /* FIXME no idea what this is for */
int chunkin;
int chunkout;
int mode;
@@ -122,13 +123,7 @@
int tid;
Command *w;
}cmds;
- struct {
- int hsent;
- int spssz;
- int ppssz;
- u8int sps[128];
- u8int pps[128];
- }v;
+ int sps;
u8int biobuf[Biobufsz];
};
@@ -238,7 +233,7 @@
if(b->bsz >= bsz)
return;
ob = b->b;
- b->b = erealloc(b->b, bsz*2);
+ b->b = erealloc(ob, bsz*2);
if(ob != nil)
b->p = b->b + (intptr)(ob - b->p);
b->bsz = bsz*2;
@@ -411,11 +406,12 @@
static void
rtmpfree(RTMP *r)
{
- free(r->app);
free(r->i.b);
free(r->o.b);
- free(r->path);
free(r->tcurl);
+ free(r->app);
+ free(r->inst);
+ free(r->path);
if(r->c != nil){
sendp(r->c, "done");
chanfree(r->c);
@@ -569,7 +565,7 @@
}
break;
- case WindowAckSize:
+ case WindowAckSize: /* FIXME send acks too */
if(a₀i32get(s, e, &r->winacksz) == nil)
goto err;
if(debug)
@@ -715,8 +711,10 @@
werrstr("invalid publish type %d", type);
return -1;
}
- if(name == nil)
- name = "";
+ if(name == nil && (name = r->inst) == nil){
+ werrstr("no name to publish to");
+ return -1;
+ }
c = chancreate(sizeof(char*), 0);
@@ -763,8 +761,9 @@
putstr("onMetaData");
putarr();
- puti32(1 + (vcodec < 0 ? 0 : 3) + (acodec < 0 ? 0 : 1));
- putkvnum("duration", 0.0);
+ puti32(2 + (vcodec < 0 ? 0 : 3) + (acodec < 0 ? 0 : 1));
+ putkvnum("duration", 0);
+ putkvnum("filesize", 0);
if(vcodec >= 0){
putkvnum("videocodecid", vcodec);
putkvnum("width", w);
@@ -787,111 +786,116 @@
{
int n;
- for(n = 3; sz-n > 3; n++){
- if(p[n] == 0 && p[n+1] == 0 && p[n+2] == 1){
- *csz = 3;
- return n;
+ *csz = 0;
+ for(n = 0; n < sz-3;){
+ if(p[n] == 0 && p[n+1] == 0){
+ if(p[n+2] == 1){
+ *csz += 3;
+ n += 3;
+ }else if(p[n+2] == 0 && p[n+3] == 1){
+ *csz += 4;
+ n += 4;
+ }else
+ break;
+ }else
+ break;
+ }
+
+ for(; n < sz-3; n++){
+ if(p[n] == 0 && p[n+1] == 0){
+ if(p[n+2] == 1)
+ return n;
+ else if(p[n+2] == 0 && p[n+3] == 1)
+ return n;
}
- if(p[n] == 0 && p[n+1] == 0 && p[n+2] == 0 && p[n+3] == 1){
- *csz = 4;
- return n;
- }
}
- *csz = 0;
-
return sz;
}
static int
-mkps(RTMP *r, u8int *p)
+h264data(RTMP *r, ulong sid, u32int dt, u8int *p, int sz)
{
- u8int *p₀;
+ u8int *p₀, sps[128], pps[128], ps[16+sizeof(sps)+sizeof(pps)];
+ int sz₀, csz, nsz, ntype, spssz, ppssz, key, total;
+ sz₀ = sz;
p₀ = p;
+ spssz = 0;
+ ppssz = 0;
+ key = 0;
+ for(total = 0; sz > 0; total += 4+nsz, p += nsz, sz -= nsz){
+ nsz = nalsz(p, sz, &csz);
+ p += csz;
+ sz -= csz;
+ nsz -= csz;
- *p++ = 1; /* version */
- *p++ = r->v.sps[1]; /* profile */
- *p++ = r->v.sps[2]; /* compatibility */
- *p++ = r->v.sps[3]; /* level */
- *p++ = 0xfc | 3; /* reserved (6 bits), NULA length size - 1 (2 bits) */
- *p++ = 0xe0 | 1; /* reserved (3 bits), num of SPS (5 bits) */
- *p++ = r->v.spssz >> 8;
- *p++ = r->v.spssz;
- p = (u8int*)memmove(p, r->v.sps, r->v.spssz) + r->v.spssz;
+ ntype = *p & 0x1f;
+ if(ntype == 7){
+ memmove(sps, p, nsz);
+ spssz = nsz;
+ }
+ if(ntype == 8){
+ memmove(pps, p, nsz);
+ ppssz = nsz;
+ }
+ if(ntype == 5)
+ key = 1;
+ }
- *p++ = 1;
- *p++ = r->v.ppssz >> 8;
- *p++ = r->v.ppssz;
- p = (u8int*)memmove(p, r->v.pps, r->v.ppssz) + r->v.ppssz;
+ if(spssz > 0 && ppssz > 0 && !r->sps){
+ newmsg(r, Video, Type0, CSData);
+ r->o.msg.ts = dt;
+ r->o.msg.sid = sid;
- return p - p₀;
-}
+ putbyte(0x10 | VcodecH264);
+ putbyte(0);
+ puti24(0);
-static int
-data(RTMP *r, ulong sid, u32int dt, int type, int fl, void *data, int sz)
-{
- bextend(&r->o, 64 + sz);
+ p = ps;
+ *p++ = 1; /* version */
+ *p++ = sps[1]; /* profile */
+ *p++ = sps[2]; /* compatibility */
+ *p++ = sps[3]; /* level */
+ *p++ = 0xfc | 3; /* reserved (6 bits), NULA length size - 1 (2 bits) */
+ *p++ = 0xe0 | 1; /* reserved (3 bits), num of SPS (5 bits) */
+ *p++ = spssz >> 8;
+ *p++ = spssz;
+ p = (u8int*)memmove(p, sps, spssz) + spssz;
+ *p++ = 1;
+ *p++ = ppssz >> 8;
+ *p++ = ppssz;
+ p = (u8int*)memmove(p, pps, ppssz) + ppssz;
- newmsg(r, type == Taudio ? Audio : Video, Type0, CSData);
- r->o.msg.ts = dt;
- r->o.msg.sid = sid;
+ putdata(ps, p-ps);
- if(type == Taudio){
- putbyte(AcodecAAC<<4 | 0x0f);
- putbyte((fl & FlHdr) ? 0 : 1);
+ if(rtmpsend(r) < 0)
+ return -1;
+
+ r->sps = 1;
}
- if(type == Tvideo){
- putbyte(((fl & FlKey) ? 0x10 : 0x20) | VcodecH264);
- putbyte((fl & FlHdr) ? 0 : 1);
- puti24(0);
- if((fl & FlHdr) == 0)
- puti32(sz);
- }
- putdata(data, sz);
- return rtmpsend(r);
-}
+ bextend(&r->o, 64+total);
-static int
-h264data(RTMP *r, ulong sid, u32int dt, u8int *p, int sz)
-{
- int csz, nsz, ntype;
- u8int ps[32+256];
+ newmsg(r, Video, Type0, CSData);
+ r->o.msg.ts = dt;
+ r->o.msg.sid = sid;
- for(; sz > 0; p += nsz, sz -= nsz){
+ putbyte((key ? 0x10 : 0x20) | VcodecH264);
+ putbyte(1);
+ puti24(0);
+
+ for(p = p₀, sz = sz₀; sz > 0; p += nsz, sz -= nsz){
nsz = nalsz(p, sz, &csz);
p += csz;
sz -= csz;
nsz -= csz;
- ntype = *p & 0x1f;
- if(ntype == 7){
- memmove(r->v.sps, p, nsz);
- r->v.spssz = nsz;
- continue;
- }
- if(ntype == 8){
- memmove(r->v.pps, p, nsz);
- r->v.ppssz = nsz;
- continue;
- }
-
- if(r->v.spssz < 1 || r->v.ppssz < 1)
- continue;
-
- if(!r->v.hsent || ntype == 8){
- r->v.hsent = 1;
- if(data(r, sid, 0, Tvideo, FlKey|FlHdr, ps, mkps(r, ps)) < 0)
- return -1;
- }
- if(ntype != 7 && ntype != 8){
- if(data(r, sid, 100, Tvideo, ntype == 5 ? FlKey : 0, p-csz, nsz+csz) < 0)
- return -1;
- }
+ puti32(nsz);
+ putdata(p, nsz);
}
- return 0;
+ return rtmpsend(r);
}
int
@@ -974,7 +978,7 @@
RTMP *
rtmpdial(char *url)
{
- char *s, *e, *path, *app;
+ char *s, *e, *p, *app, *inst, *path;
int f, port, ctl;
RTMP *r;
@@ -997,14 +1001,14 @@
}
port = 1935;
if(*e == ':'){
- if((port = strtol(e+1, &path, 10)) < 1 || path == e+1 || *path != '/'){
+ if((port = strtol(e+1, &p, 10)) < 1 || p == e+1 || *p != '/'){
werrstr("invalid port");
goto err;
}
}else{
- path = e;
+ p = e;
}
- while(*(++path) == '/');
+ while(*(++p) == '/');
s = smprint("tcp!%.*s!%d", (int)(e-s), s, port);
f = dial(s, nil, nil, &ctl);
@@ -1012,21 +1016,17 @@
if(f < 0)
goto err;
- app = path;
- if((s = strchr(path, '/')) == nil){
- werrstr("no path");
- goto err;
- }
- if((e = strchr(s+1, '/')) != nil){
- /* at this point it can be app instance if there is another slash following */
- if((s = strchr(e+1, '/')) == nil){
- /* no, just path leftovers */
- s = e;
- }
+ /* rtmp://host:port/app[/inst[/path]] */
+ app = p;
+ inst = nil;
+ path = nil;
+ if((s = strchr(p, '/')) != nil){ /* app instance */
*s = 0;
- path = s+1;
- }else{
- path = nil;
+ inst = s+1;
+ if((s = strchr(s+1, '/')) != nil){ /* path */
+ *s = 0;
+ path = s+1;
+ }
}
if(handshake(f) != 0)
@@ -1040,6 +1040,7 @@
url = nil;
r->c = chancreate(sizeof(void*), 0);
r->app = estrdup(app);
+ r->inst = inst == nil ? nil : estrdup(inst);
r->path = path == nil ? nil : estrdup(path);
bextend(&r->i, Bufsz);
bextend(&r->o, Bufsz);