diff options
author | Alfred E. Heggestad <aeh@db.org> | 2014-02-09 11:50:07 +0100 |
---|---|---|
committer | Alfred E. Heggestad <aeh@db.org> | 2014-02-09 11:50:07 +0100 |
commit | 98bf08bdcf2edd9d397f32650a8bfe62186fbecf (patch) | |
tree | ebc6ec71f44bff8c42e4eefced61948623df02fc /modules/rst | |
parent | e6ad5cf4401b860ba402d4b7b3c7c254bc87a019 (diff) |
baresip 0.4.10
Diffstat (limited to 'modules/rst')
-rw-r--r-- | modules/rst/audio.c | 263 | ||||
-rw-r--r-- | modules/rst/module.mk | 14 | ||||
-rw-r--r-- | modules/rst/rst.c | 408 | ||||
-rw-r--r-- | modules/rst/rst.h | 26 | ||||
-rw-r--r-- | modules/rst/video.c | 280 |
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); +} |