shithub: rtmp

Download patch

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