shithub: rtmp

ref: c296529d65687f301af4f36aa4db5a0978a6f059
dir: /rtmp.c/

View raw version
#include <u.h>
#include <libc.h>
#include <thread.h>
#include <bio.h>
#include <libsec.h>
#include "amf.h"
#include "ivf.h"
#include "rtmp.h"

enum {
	Port = 1935,

	Sigsz = 1536,
	Chunk = 128,

	ChanCtl = 3,

	SzTiny = 1,
	SzSmall = 4,
	SzMedium = 8,
	SzLarge = 12,

	PktInvoke = 20,

	Biobufsz = 64*1024, /* FIXME don't know if it helps with anything */
	Bufsz = 64*1024,
};

typedef struct Packet Packet;

struct Packet {
	int type;
	int hsz;
	int chan;
	u32int ts;
	u8int *data;
	int sz;
};

struct RTMP {
	Biobufhdr;
	char *app;
	char *path;
	char *tcurl;
	Packet pk;
	u8int *b, *p, *e;
	int mode;
	int bsz;
	int invokes;
	int i;
	u8int biobuf[Biobufsz];
};

#define puti16(i) do{ r->p = amfi16(r->p, r->e, i); }while(0)
#define puti24(i) do{ r->p = amfi24(r->p, r->e, i); }while(0)
#define puti32(i) do{ r->p = amfi32(r->p, r->e, i); }while(0)
#define putnum(v) do{ r->p = amfnum(r->p, r->e, v); }while(0)
#define putstr(s) do{ r->p = amfstr(r->p, r->e, s); }while(0)
#define putarr() do{ r->p = amfarr(r->p, r->e); }while(0)
#define putobj() do{ r->p = amfobj(r->p, r->e); }while(0)
#define putend() do{ r->p = amfend(r->p, r->e); }while(0)
#define putkvnum(name, v) do{ r->p = amfkvnum(r->p, r->e, name, v); }while(0)
#define putkvstr(name, s) do{ r->p = amfkvstr(r->p, r->e, name, s); }while(0)
#define putkvbool(name, s) do{ r->p = amfkvbool(r->p, r->e, name, s); }while(0)

static void
newpacket(RTMP *r, int type, int hsz, int chan)
{
	memset(&r->pk, 0, sizeof(r->pk));

	r->pk.type = type;
	r->pk.hsz = hsz;
	r->pk.chan = chan;
	r->p = r->b + hsz;
	r->b[0] = chan;

	switch(hsz){
	case SzTiny: r->b[0] |= 3<<6; break;
	case SzSmall: r->b[0] |= 2<<6; break;
	case SzMedium: r->b[0] |= 1<<6; break;
	}
}

static int
handshake(int f)
{
	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 0;

err:
	werrstr("handshake: %r");
	return -1;
}

static int
rtmpsend(RTMP *r)
{
	u8int *h, *e;
	int bodysz;

	bodysz = r->p - r->b - r->pk.hsz;
	/* FIXME special case when bodysz is 0 */
	h = r->b + 1;
	e = h + r->pk.hsz;
	if(r->pk.hsz >= SzSmall){
		h = amfi24(h, e, 0); /* FIXME proper timestamps? */
		if(r->pk.hsz >= SzMedium){
			h = amfi24(h, e, bodysz);
			h = amfbyte(h, e, r->pk.type);
			if(r->pk.hsz >= SzLarge)
				h = amfi32(h, e, 0); /* FIXME seems to be always 0? */
		}
	}
	if(h == nil)
		goto err;
	memset(h, 0, e-h);

	return 0;
err:
	werrstr("rtmpsend: %r");
	return -1;
}

static int
connect(RTMP *r)
{
	newpacket(r, PktInvoke, SzLarge, ChanCtl);
	putstr("connect");
		putnum(++r->invokes);
	putobj();
		putkvstr("app", r->app);
		putkvstr("tcUrl", r->tcurl);
		if(r->mode & OWRITE)
			putkvstr("type", "nonprivate");
		else{
			putkvbool("fpad", 0);
			putkvnum("capabilities", 15);
			putkvnum("audioCodecs", 3191);
			putkvnum("videoCodecs", 252);
			putkvnum("videoFunction", 1);
		}
	putend();

	return rtmpsend(r);
}

RTMP *
rtmpdial(char *url, int w, int h, int withaudio)
{
	char *s, *e, *path, *app;
	int f, port, ctl;
	RTMP *r;

	r = nil;
	f = -1;
	url = strdup(url); /* since we're changing it in-place */
	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;
	}
	while(*(++path) == '/');

	s = smprint("tcp!%.*s!%d", (int)(e-s), s, port);
	f = dial(s, nil, nil, &ctl);
	free(s);
	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;
		}
		*s = 0;
		path = s+1;
	}else{
		path = nil;
	}

	if(handshake(f) != 0)
		goto err;
	if((r = calloc(1, sizeof(*r))) == nil || (r->b = malloc(Bufsz)) == nil)
		sysfatal("memory");
	if((r->app = strdup(app)) == nil || (path != nil && (r->path = strdup(path)) == nil))
		sysfatal("memory");
	r->tcurl = url;
	r->bsz = Bufsz;
	r->e = r->b + r->bsz;
	Binits(r, f, OWRITE, r->biobuf, sizeof(r->biobuf));
	r->i = f;
	if(connect(r) != 0)
		goto err;

	return r;

err:
	werrstr("rtmpdial: %r");
	if(r != nil)
		rtmpclose(r);
	else if(f >= 0)
		close(f);
	free(url);
	return nil;
}

void
rtmpclose(RTMP *r)
{
	if(r == nil)
		return;
	free(r->path);
	free(r->b);
	close(r->i);
	Bterm(r);
	free(r);
}