summaryrefslogtreecommitdiff
path: root/modules/rst
diff options
context:
space:
mode:
authorAlfred E. Heggestad <aeh@db.org>2014-02-09 11:50:07 +0100
committerAlfred E. Heggestad <aeh@db.org>2014-02-09 11:50:07 +0100
commit98bf08bdcf2edd9d397f32650a8bfe62186fbecf (patch)
treeebc6ec71f44bff8c42e4eefced61948623df02fc /modules/rst
parente6ad5cf4401b860ba402d4b7b3c7c254bc87a019 (diff)
baresip 0.4.10
Diffstat (limited to 'modules/rst')
-rw-r--r--modules/rst/audio.c263
-rw-r--r--modules/rst/module.mk14
-rw-r--r--modules/rst/rst.c408
-rw-r--r--modules/rst/rst.h26
-rw-r--r--modules/rst/video.c280
5 files changed, 991 insertions, 0 deletions
diff --git a/modules/rst/audio.c b/modules/rst/audio.c
new file mode 100644
index 0000000..554ec21
--- /dev/null
+++ b/modules/rst/audio.c
@@ -0,0 +1,263 @@
+/**
+ * @file rst/audio.c MP3/ICY HTTP Audio Source
+ *
+ * Copyright (C) 2011 Creytiv.com
+ */
+
+#define _BSD_SOURCE 1
+#include <pthread.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <mpg123.h>
+#include "rst.h"
+
+
+struct ausrc_st {
+ struct ausrc *as;
+ pthread_t thread;
+ struct rst *rst;
+ mpg123_handle *mp3;
+ struct aubuf *aubuf;
+ ausrc_read_h *rh;
+ ausrc_error_h *errh;
+ void *arg;
+ bool run;
+ uint32_t psize;
+ uint32_t ptime;
+};
+
+
+static struct ausrc *ausrc;
+
+
+static void destructor(void *arg)
+{
+ struct ausrc_st *st = arg;
+
+ rst_set_audio(st->rst, NULL);
+ mem_deref(st->rst);
+
+ if (st->run) {
+ st->run = false;
+ pthread_join(st->thread, NULL);
+ }
+
+ if (st->mp3) {
+ mpg123_close(st->mp3);
+ mpg123_delete(st->mp3);
+ }
+
+ mem_deref(st->aubuf);
+ mem_deref(st->as);
+}
+
+
+static void *play_thread(void *arg)
+{
+ uint64_t now, ts = tmr_jiffies();
+ struct ausrc_st *st = arg;
+ uint8_t *buf;
+
+ buf = mem_alloc(st->psize, NULL);
+ if (!buf)
+ return NULL;
+
+ while (st->run) {
+
+ (void)usleep(4000);
+
+ now = tmr_jiffies();
+
+ if (ts > now)
+ continue;
+#if 1
+ if (now > ts + 100) {
+ re_printf("rst: cpu lagging behind (%u ms)\n",
+ now - ts);
+ }
+#endif
+
+ aubuf_read(st->aubuf, buf, st->psize);
+
+ st->rh(buf, st->psize, st->arg);
+
+ ts += st->ptime;
+ }
+
+ mem_deref(buf);
+
+ return NULL;
+}
+
+
+static inline int decode(struct ausrc_st *st)
+{
+ int err, ch, encoding;
+ struct mbuf *mb;
+ long srate;
+
+ mb = mbuf_alloc(4096);
+ if (!mb)
+ return ENOMEM;
+
+ err = mpg123_read(st->mp3, mb->buf, mb->size, &mb->end);
+
+ switch (err) {
+
+ case MPG123_NEW_FORMAT:
+ mpg123_getformat(st->mp3, &srate, &ch, &encoding);
+ re_printf("rst: new format: %i hz, %i ch, encoding 0x%04x\n",
+ srate, ch, encoding);
+ /*@fallthrough@*/
+
+ case MPG123_OK:
+ case MPG123_NEED_MORE:
+ if (mb->end == 0)
+ break;
+ aubuf_append(st->aubuf, mb);
+ break;
+
+ default:
+ re_printf("rst: mpg123_read error: %s\n",
+ mpg123_plain_strerror(err));
+ break;
+ }
+
+ mem_deref(mb);
+
+ return err;
+}
+
+
+void rst_audio_feed(struct ausrc_st *st, const uint8_t *buf, size_t sz)
+{
+ int err;
+
+ if (!st)
+ return;
+
+ err = mpg123_feed(st->mp3, buf, sz);
+ if (err)
+ return;
+
+ while (MPG123_OK == decode(st))
+ ;
+}
+
+
+static int alloc_handler(struct ausrc_st **stp, struct ausrc *as,
+ struct media_ctx **ctx,
+ struct ausrc_prm *prm, const char *dev,
+ ausrc_read_h *rh, ausrc_error_h *errh, void *arg)
+{
+ struct ausrc_st *st;
+ unsigned sampc;
+ int err;
+
+ if (!stp || !as || !prm || !rh)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->as = mem_ref(as);
+ st->rh = rh;
+ st->errh = errh;
+ st->arg = arg;
+
+ st->mp3 = mpg123_new(NULL, &err);
+ if (!st->mp3) {
+ err = ENODEV;
+ goto out;
+ }
+
+ err = mpg123_open_feed(st->mp3);
+ if (err != MPG123_OK) {
+ re_printf("rst: mpg123_open_feed: %s\n",
+ mpg123_strerror(st->mp3));
+ err = ENODEV;
+ goto out;
+ }
+
+ /* Set wanted output format */
+ mpg123_format_none(st->mp3);
+ mpg123_format(st->mp3, prm->srate, prm->ch, MPG123_ENC_SIGNED_16);
+ mpg123_volume(st->mp3, 0.3);
+
+ sampc = prm->srate * prm->ch * prm->ptime / 1000;
+
+ st->ptime = prm->ptime;
+ st->psize = sampc * 2;
+
+ prm->fmt = AUFMT_S16LE;
+
+ re_printf("rst: audio ptime=%u psize=%u aubuf=[%u:%u]\n",
+ st->ptime, st->psize,
+ prm->srate * prm->ch * 2,
+ prm->srate * prm->ch * 40);
+
+ /* 1 - 20 seconds of audio */
+ err = aubuf_alloc(&st->aubuf,
+ prm->srate * prm->ch * 2,
+ prm->srate * prm->ch * 40);
+ if (err)
+ goto out;
+
+ if (ctx && *ctx && (*ctx)->id && !strcmp((*ctx)->id, "rst")) {
+ st->rst = mem_ref(*ctx);
+ }
+ else {
+ err = rst_alloc(&st->rst, dev);
+ if (err)
+ goto out;
+
+ if (ctx)
+ *ctx = (struct media_ctx *)st->rst;
+ }
+
+ rst_set_audio(st->rst, st);
+
+ st->run = true;
+
+ err = pthread_create(&st->thread, NULL, play_thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+int rst_audio_init(void)
+{
+ int err;
+
+ err = mpg123_init();
+ if (err != MPG123_OK) {
+ re_printf("rst: mpg123_init: %s\n",
+ mpg123_plain_strerror(err));
+ return ENODEV;
+ }
+
+ return ausrc_register(&ausrc, "rst", alloc_handler);
+}
+
+
+void rst_audio_close(void)
+{
+ ausrc = mem_deref(ausrc);
+
+ mpg123_exit();
+}
diff --git a/modules/rst/module.mk b/modules/rst/module.mk
new file mode 100644
index 0000000..6026c22
--- /dev/null
+++ b/modules/rst/module.mk
@@ -0,0 +1,14 @@
+#
+# module.mk
+#
+# Copyright (C) 2011 Creytiv.com
+#
+
+MOD := rst
+$(MOD)_SRCS += audio.c
+$(MOD)_SRCS += rst.c
+$(MOD)_SRCS += video.c
+$(MOD)_LFLAGS += `pkg-config --libs cairo libmpg123`
+CFLAGS += `pkg-config --cflags cairo libmpg123`
+
+include mk/mod.mk
diff --git a/modules/rst/rst.c b/modules/rst/rst.c
new file mode 100644
index 0000000..21b5bfe
--- /dev/null
+++ b/modules/rst/rst.c
@@ -0,0 +1,408 @@
+/**
+ * @file rst.c MP3/ICY HTTP AV Source
+ *
+ * Copyright (C) 2011 Creytiv.com
+ */
+
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include "rst.h"
+
+
+enum {
+ RETRY_WAIT = 10000,
+};
+
+
+struct rst {
+ const char *id;
+ struct ausrc_st *ausrc_st;
+ struct vidsrc_st *vidsrc_st;
+ struct tmr tmr;
+ struct dns_query *dnsq;
+ struct tcp_conn *tc;
+ struct mbuf *mb;
+ char *host;
+ char *path;
+ char *name;
+ char *meta;
+ bool head_recv;
+ size_t metaint;
+ size_t metasz;
+ size_t bytec;
+ uint16_t port;
+};
+
+
+static int rst_connect(struct rst *rst);
+
+
+static void destructor(void *arg)
+{
+ struct rst *rst = arg;
+
+ tmr_cancel(&rst->tmr);
+ mem_deref(rst->dnsq);
+ mem_deref(rst->tc);
+ mem_deref(rst->mb);
+ mem_deref(rst->host);
+ mem_deref(rst->path);
+ mem_deref(rst->name);
+ mem_deref(rst->meta);
+}
+
+
+static void reconnect(void *arg)
+{
+ struct rst *rst = arg;
+ int err;
+
+ rst->mb = mem_deref(rst->mb);
+ rst->name = mem_deref(rst->name);
+ rst->meta = mem_deref(rst->meta);
+
+ rst->head_recv = false;
+ rst->metaint = 0;
+ rst->metasz = 0;
+ rst->bytec = 0;
+
+ err = rst_connect(rst);
+ if (err)
+ tmr_start(&rst->tmr, RETRY_WAIT, reconnect, rst);
+}
+
+
+static void recv_handler(struct mbuf *mb, void *arg)
+{
+ struct rst *rst = arg;
+ size_t n;
+
+ if (!rst->head_recv) {
+
+ struct pl hdr, name, metaint, eoh;
+
+ if (rst->mb) {
+ size_t pos;
+ int err;
+
+ pos = rst->mb->pos;
+
+ rst->mb->pos = rst->mb->end;
+
+ err = mbuf_write_mem(rst->mb, mbuf_buf(mb),
+ mbuf_get_left(mb));
+ if (err) {
+ re_printf("rst: buffer write error: %m\n",
+ err);
+ rst->tc = mem_deref(rst->tc);
+ tmr_start(&rst->tmr, RETRY_WAIT,
+ reconnect, rst);
+ return;
+ }
+
+ rst->mb->pos = pos;
+ }
+ else {
+ rst->mb = mem_ref(mb);
+ }
+
+ if (re_regex((const char *)mbuf_buf(rst->mb),
+ mbuf_get_left(rst->mb),
+ "[^\r\n]1\r\n\r\n", &eoh))
+ return;
+
+ rst->head_recv = true;
+
+ hdr.p = (const char *)mbuf_buf(rst->mb);
+ hdr.l = eoh.p + 5 - hdr.p;
+
+ if (!re_regex(hdr.p, hdr.l, "icy-name:[ \t]*[^\r\n]+\r\n",
+ NULL, &name))
+ (void)pl_strdup(&rst->name, &name);
+
+ if (!re_regex(hdr.p, hdr.l, "icy-metaint:[ \t]*[0-9]+\r\n",
+ NULL, &metaint))
+ rst->metaint = pl_u32(&metaint);
+
+ if (rst->metaint == 0) {
+ re_printf("rst: icy meta interval not available\n");
+ rst->tc = mem_deref(rst->tc);
+ tmr_start(&rst->tmr, RETRY_WAIT, reconnect, rst);
+ return;
+ }
+
+ rst_video_update(rst->vidsrc_st, rst->name, NULL);
+
+ rst->mb->pos += hdr.l;
+
+ re_printf("rst: name='%s' metaint=%zu\n",
+ rst->name, rst->metaint);
+
+ if (rst->mb->pos >= rst->mb->end)
+ return;
+
+ mb = rst->mb;
+ }
+
+ while (mb->pos < mb->end) {
+
+ if (rst->metasz > 0) {
+
+ n = min(mbuf_get_left(mb), rst->metasz - rst->bytec);
+
+ if (rst->meta)
+ mbuf_read_mem(mb,
+ (uint8_t *)&rst->meta[rst->bytec],
+ n);
+ else
+ mb->pos += n;
+
+ rst->bytec += n;
+#if 0
+ re_printf("rst: metadata %zu bytes\n", n);
+#endif
+ if (rst->bytec >= rst->metasz) {
+#if 0
+ re_printf("rst: metadata: [%s]\n", rst->meta);
+#endif
+ rst->metasz = 0;
+ rst->bytec = 0;
+
+ rst_video_update(rst->vidsrc_st, rst->name,
+ rst->meta);
+ }
+ }
+ else if (rst->bytec < rst->metaint) {
+
+ n = min(mbuf_get_left(mb), rst->metaint - rst->bytec);
+
+ rst_audio_feed(rst->ausrc_st, mbuf_buf(mb), n);
+
+ rst->bytec += n;
+ mb->pos += n;
+#if 0
+ re_printf("rst: mp3data %zu bytes\n", n);
+#endif
+ }
+ else {
+ rst->metasz = mbuf_read_u8(mb) * 16;
+ rst->bytec = 0;
+
+ rst->meta = mem_deref(rst->meta);
+ rst->meta = mem_zalloc(rst->metasz + 1, NULL);
+#if 0
+ re_printf("rst: metalength %zu bytes\n", rst->metasz);
+#endif
+ }
+ }
+}
+
+
+static void estab_handler(void *arg)
+{
+ struct rst *rst = arg;
+ struct mbuf *mb;
+ int err;
+
+ re_printf("rst: connection established\n");
+
+ mb = mbuf_alloc(512);
+ if (!mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ err = mbuf_printf(mb,
+ "GET %s HTTP/1.0\r\n"
+ "Icy-MetaData: 1\r\n"
+ "\r\n",
+ rst->path);
+ if (err)
+ goto out;
+
+ mb->pos = 0;
+
+ err = tcp_send(rst->tc, mb);
+ if (err)
+ goto out;
+
+ out:
+ if (err) {
+ re_printf("rst: error sending HTTP request: %m\n", err);
+ }
+
+ mem_deref(mb);
+}
+
+
+static void close_handler(int err, void *arg)
+{
+ struct rst *rst = arg;
+
+ re_printf("rst: tcp closed: %i\n", err);
+
+ rst->tc = mem_deref(rst->tc);
+
+ tmr_start(&rst->tmr, RETRY_WAIT, reconnect, rst);
+}
+
+
+static void dns_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+ struct list *authl, struct list *addl, void *arg)
+{
+ struct rst *rst = arg;
+ struct dnsrr *rr;
+ struct sa srv;
+
+ (void)err;
+ (void)hdr;
+ (void)authl;
+ (void)addl;
+
+ rr = dns_rrlist_find(ansl, rst->host, DNS_TYPE_A, DNS_CLASS_IN, true);
+ if (!rr) {
+ re_printf("rst: unable to resolve: %s\n", rst->host);
+ tmr_start(&rst->tmr, RETRY_WAIT, reconnect, rst);
+ return;
+ }
+
+ sa_set_in(&srv, rr->rdata.a.addr, rst->port);
+
+ err = tcp_connect(&rst->tc, &srv, estab_handler, recv_handler,
+ close_handler, rst);
+ if (err) {
+ re_printf("rst: tcp connect error: %m\n", err);
+ tmr_start(&rst->tmr, RETRY_WAIT, reconnect, rst);
+ return;
+ }
+}
+
+
+static int rst_connect(struct rst *rst)
+{
+ struct sa srv;
+ int err;
+
+ if (!sa_set_str(&srv, rst->host, rst->port)) {
+
+ err = tcp_connect(&rst->tc, &srv, estab_handler, recv_handler,
+ close_handler, rst);
+ if (err) {
+ re_printf("rst: tcp connect error: %m\n", err);
+ }
+ }
+ else {
+ err = dnsc_query(&rst->dnsq, net_dnsc(), rst->host, DNS_TYPE_A,
+ DNS_CLASS_IN, true, dns_handler, rst);
+ if (err) {
+ re_printf("rst: dns query error: %m\n", err);
+ }
+ }
+
+ return err;
+}
+
+
+int rst_alloc(struct rst **rstp, const char *dev)
+{
+ struct pl host, port, path;
+ struct rst *rst;
+ int err;
+
+ if (!rstp || !dev)
+ return EINVAL;
+
+ if (re_regex(dev, strlen(dev), "http://[^:/]+[:]*[0-9]*[^]+",
+ &host, NULL, &port, &path)) {
+ re_printf("rst: bad http url: %s\n", dev);
+ return EBADMSG;
+ }
+
+ rst = mem_zalloc(sizeof(*rst), destructor);
+ if (!rst)
+ return ENOMEM;
+
+ rst->id = "rst";
+
+ err = pl_strdup(&rst->host, &host);
+ if (err)
+ goto out;
+
+ err = pl_strdup(&rst->path, &path);
+ if (err)
+ goto out;
+
+ rst->port = pl_u32(&port);
+ rst->port = rst->port ? rst->port : 80;
+
+ err = rst_connect(rst);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(rst);
+ else
+ *rstp = rst;
+
+ return err;
+}
+
+
+void rst_set_audio(struct rst *rst, struct ausrc_st *st)
+{
+ if (!rst)
+ return;
+
+ rst->ausrc_st = st;
+}
+
+
+void rst_set_video(struct rst *rst, struct vidsrc_st *st)
+{
+ if (!rst)
+ return;
+
+ rst->vidsrc_st = st;
+}
+
+
+static int module_init(void)
+{
+ int err;
+
+ err = rst_audio_init();
+ if (err)
+ goto out;
+
+ err = rst_video_init();
+ if (err)
+ goto out;
+
+ out:
+ if (err) {
+ rst_audio_close();
+ rst_video_close();
+ }
+
+ return err;
+}
+
+
+static int module_close(void)
+{
+ rst_audio_close();
+ rst_video_close();
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(rst) = {
+ "rst",
+ "avsrc",
+ module_init,
+ module_close
+};
diff --git a/modules/rst/rst.h b/modules/rst/rst.h
new file mode 100644
index 0000000..950a7de
--- /dev/null
+++ b/modules/rst/rst.h
@@ -0,0 +1,26 @@
+/**
+ * @file rst.h MP3/ICY HTTP AV Source
+ *
+ * Copyright (C) 2011 Creytiv.com
+ */
+
+
+/* Shared AV state */
+struct rst;
+
+int rst_alloc(struct rst **rstp, const char *dev);
+void rst_set_audio(struct rst *rst, struct ausrc_st *st);
+void rst_set_video(struct rst *rst, struct vidsrc_st *st);
+
+
+/* Audio */
+void rst_audio_feed(struct ausrc_st *st, const uint8_t *buf, size_t sz);
+int rst_audio_init(void);
+void rst_audio_close(void);
+
+
+/* Video */
+void rst_video_update(struct vidsrc_st *st, const char *name,
+ const char *meta);
+int rst_video_init(void);
+void rst_video_close(void);
diff --git a/modules/rst/video.c b/modules/rst/video.c
new file mode 100644
index 0000000..db6bfc9
--- /dev/null
+++ b/modules/rst/video.c
@@ -0,0 +1,280 @@
+/**
+ * @file rst/video.c MP3/ICY HTTP Video Source
+ *
+ * Copyright (C) 2011 Creytiv.com
+ */
+
+#define _BSD_SOURCE 1
+#include <pthread.h>
+#include <string.h>
+#include <re.h>
+#include <rem.h>
+#include <baresip.h>
+#include <cairo/cairo.h>
+#include "rst.h"
+
+
+struct vidsrc_st {
+ struct vidsrc *vs;
+ pthread_mutex_t mutex;
+ pthread_t thread;
+ struct vidsrc_prm prm;
+ struct vidsz size;
+ struct rst *rst;
+ cairo_surface_t *surface;
+ cairo_t *cairo;
+ struct vidframe *frame;
+ vidsrc_frame_h *frameh;
+ void *arg;
+ bool run;
+};
+
+
+static struct vidsrc *vidsrc;
+
+
+static void destructor(void *arg)
+{
+ struct vidsrc_st *st = arg;
+
+ rst_set_video(st->rst, NULL);
+ mem_deref(st->rst);
+
+ if (st->run) {
+ st->run = false;
+ pthread_join(st->thread, NULL);
+ }
+
+ if (st->cairo)
+ cairo_destroy(st->cairo);
+
+ if (st->surface)
+ cairo_surface_destroy(st->surface);
+
+ mem_deref(st->frame);
+ mem_deref(st->vs);
+}
+
+
+static void *video_thread(void *arg)
+{
+ uint64_t now, ts = tmr_jiffies();
+ struct vidsrc_st *st = arg;
+
+ while (st->run) {
+
+ (void)usleep(4000);
+
+ now = tmr_jiffies();
+
+ if (ts > now)
+ continue;
+
+ pthread_mutex_lock(&st->mutex);
+ st->frameh(st->frame, st->arg);
+ pthread_mutex_unlock(&st->mutex);
+
+ ts += 1000/st->prm.fps;
+ }
+
+ return NULL;
+}
+
+
+static void background(cairo_t *cr, unsigned width, unsigned height)
+{
+ cairo_pattern_t *pat;
+ double r, g, b;
+
+ pat = cairo_pattern_create_linear(0.0, 0.0, 0.0, height);
+ if (!pat)
+ return;
+
+ r = 0.0;
+ g = 0.0;
+ b = 0.8;
+
+ cairo_pattern_add_color_stop_rgba(pat, 1, r, g, b, 1);
+ cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0.2, 1);
+ cairo_rectangle(cr, 0, 0, width, height);
+ cairo_set_source(cr, pat);
+ cairo_fill(cr);
+
+ cairo_pattern_destroy(pat);
+}
+
+
+static void icy_printf(cairo_t *cr, int x, int y, double size,
+ const char *fmt, ...)
+{
+ char buf[4096] = "";
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void)re_vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ /* Draw text */
+ cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL,
+ CAIRO_FONT_WEIGHT_NORMAL);
+ cairo_set_font_size(cr, size);
+ cairo_move_to(cr, x, y);
+ cairo_text_path(cr, buf);
+ cairo_set_source_rgb(cr, 1, 1, 1);
+ cairo_fill(cr);
+}
+
+
+static size_t linelen(const struct pl *pl)
+{
+ size_t len = 72, i;
+
+ if (pl->l <= len)
+ return pl->l;
+
+ for (i=len; i>1; i--) {
+
+ if (pl->p[i-1] == ' ') {
+ len = i;
+ break;
+ }
+ }
+
+ return len;
+}
+
+
+void rst_video_update(struct vidsrc_st *st, const char *name, const char *meta)
+{
+ struct vidframe frame;
+
+ if (!st)
+ return;
+
+ background(st->cairo, st->size.w, st->size.h);
+
+ icy_printf(st->cairo, 50, 100, 40.0, "%s", name);
+
+ if (meta) {
+
+ struct pl title;
+
+ if (!re_regex(meta, strlen(meta),
+ "StreamTitle='[ \t]*[^;]+;", NULL, &title)) {
+
+ unsigned i;
+
+ title.l--;
+
+ for (i=0; title.l; i++) {
+
+ const size_t len = linelen(&title);
+
+ icy_printf(st->cairo, 50, 150 + 25*i, 18.0,
+ "%b", title.p, len);
+
+ title.p += len;
+ title.l -= len;
+ }
+ }
+ }
+
+ vidframe_init_buf(&frame, VID_FMT_RGB32, &st->size,
+ cairo_image_surface_get_data(st->surface));
+
+ pthread_mutex_lock(&st->mutex);
+ vidconv(st->frame, &frame, NULL);
+ pthread_mutex_unlock(&st->mutex);
+}
+
+
+static int alloc_handler(struct vidsrc_st **stp, struct vidsrc *vs,
+ struct media_ctx **ctx, struct vidsrc_prm *prm,
+ const struct vidsz *size, const char *fmt,
+ const char *dev, vidsrc_frame_h *frameh,
+ vidsrc_error_h *errorh, void *arg)
+{
+ struct vidsrc_st *st;
+ int err;
+
+ (void)fmt;
+ (void)errorh;
+
+ if (!stp || !vs || !prm || !size || !frameh)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ err = pthread_mutex_init(&st->mutex, NULL);
+ if (err)
+ goto out;
+
+ st->vs = mem_ref(vs);
+ st->prm = *prm;
+ st->size = *size;
+ st->frameh = frameh;
+ st->arg = arg;
+
+ st->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+ size->w, size->h);
+ if (!st->surface) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ st->cairo = cairo_create(st->surface);
+ if (!st->cairo) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ err = vidframe_alloc(&st->frame, VID_FMT_YUV420P, size);
+ if (err)
+ goto out;
+
+ vidframe_fill(st->frame, 0, 0, 0);
+
+ if (ctx && *ctx && (*ctx)->id && !strcmp((*ctx)->id, "rst")) {
+ st->rst = mem_ref(*ctx);
+ }
+ else {
+ err = rst_alloc(&st->rst, dev);
+ if (err)
+ goto out;
+
+ if (ctx)
+ *ctx = (struct media_ctx *)st->rst;
+ }
+
+ rst_set_video(st->rst, st);
+
+ st->run = true;
+
+ err = pthread_create(&st->thread, NULL, video_thread, st);
+ if (err) {
+ st->run = false;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+int rst_video_init(void)
+{
+ return vidsrc_register(&vidsrc, "rst", alloc_handler, NULL);
+}
+
+
+void rst_video_close(void)
+{
+ vidsrc = mem_deref(vidsrc);
+}