diff options
author | Alfred E. Heggestad <aeh@db.org> | 2015-08-01 18:54:04 +0200 |
---|---|---|
committer | Alfred E. Heggestad <aeh@db.org> | 2015-08-01 18:54:04 +0200 |
commit | 5afea9730d64aacf3adcb822b34bbc810ed4dedb (patch) | |
tree | 36eeea381ea1d34eb8ba28c1f0531b96fdf5893d /modules | |
parent | 9bebd147cc384b777e45e6236fa9aa680633e6eb (diff) |
add modules using gstreamer-1.0
- gst1.so -- module for audio-streaming using gstreamer 1.0
- gst_video1.so -- module for video encoding using gstreamer 1.0
It is now possible to combine but 0.10 and 1.0 modules,
but please be careful when loading a mix of modules with
different version of gstreamer!
you can also try to compile all of the statically into the
same binary:
$ make STATIC=1
we might rename the old gst.so to gst0.so at some point ...?
fixes issue #38
https://github.com/alfredh/baresip/issues/38
Diffstat (limited to 'modules')
-rw-r--r-- | modules/gst1/dump.c | 65 | ||||
-rw-r--r-- | modules/gst1/gst.c | 469 | ||||
-rw-r--r-- | modules/gst1/gst.h | 9 | ||||
-rw-r--r-- | modules/gst1/module.mk | 12 | ||||
-rw-r--r-- | modules/gst_video1/encode.c | 623 | ||||
-rw-r--r-- | modules/gst_video1/gst_video.c | 66 | ||||
-rw-r--r-- | modules/gst_video1/gst_video.h | 32 | ||||
-rw-r--r-- | modules/gst_video1/h264.c | 160 | ||||
-rw-r--r-- | modules/gst_video1/module.mk | 12 | ||||
-rw-r--r-- | modules/gst_video1/sdp.c | 54 |
10 files changed, 1502 insertions, 0 deletions
diff --git a/modules/gst1/dump.c b/modules/gst1/dump.c new file mode 100644 index 0000000..d1f9447 --- /dev/null +++ b/modules/gst1/dump.c @@ -0,0 +1,65 @@ +/** + * @file dump.c Gstreamer playbin pipeline - dump utilities + * + * Copyright (C) 2010 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include <gst/gst.h> +#include "gst.h" + + +void gst1_dump_props(GstElement *g) +{ + uint64_t u64; + gchar *strval; + double volume; + int n; + + debug("Gst properties:\n"); + + g_object_get(g, "delay", &u64, NULL); + debug(" delay: %lu ns\n", u64); + + g_object_get(g, "uri", &strval, NULL); + debug(" uri: %s\n", strval); + g_free(strval); + + g_object_get(g, "suburi", &strval, NULL); + debug(" suburi: %s\n", strval); + g_free(strval); + + g_object_get(g, "queue-size", &u64, NULL); + debug(" queue-size: %lu ns\n", u64); + + g_object_get(g, "queue-threshold", &u64, NULL); + debug(" queue-threshold: %lu ns\n", u64); + + g_object_get(g, "nstreams", &n, NULL); + debug(" nstreams: %d\n", n); + + g_object_get(g, "volume", &volume, NULL); + debug(" Volume: %f\n", volume); +} + + +void gst1_dump_caps(const GstCaps *caps) +{ + GstStructure *s; + int rate, channels, width; + + if (!caps) + return; + + if (!gst_caps_get_size(caps)) + return; + + s = gst_caps_get_structure(caps, 0); + + gst_structure_get_int(s, "rate", &rate); + gst_structure_get_int(s, "channels", &channels); + gst_structure_get_int(s, "width", &width); + + info("gst: caps dump: %d Hz, %d channels, width=%d\n", + rate, channels, width); +} diff --git a/modules/gst1/gst.c b/modules/gst1/gst.c new file mode 100644 index 0000000..c9ac920 --- /dev/null +++ b/modules/gst1/gst.c @@ -0,0 +1,469 @@ +/** + * @file gst.c Gstreamer playbin pipeline + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include <stdlib.h> +#include <string.h> +#define __USE_POSIX199309 +#include <time.h> +#include <pthread.h> +#include <gst/gst.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "gst.h" + + +/** + * @defgroup gst gst + * + * Audio source module using gstreamer as input + * + * The module 'gst' is using the Gstreamer framework to play external + * media and provide this as an internal audio source. + * + * Example config: + \verbatim + audio_source gst,http://relay.slayradio.org:8000/ + \endverbatim + */ + + +/** + * Defines the Gstreamer state + * + * <pre> + * ptime=variable ptime=20ms + * .-----------. N kHz .---------. N kHz + * | | 1-2 channels | | 1-2 channels + * | Gstreamer |--------------->|Packetize|-------------> [read handler] + * | | | | + * '-----------' '---------' + * + * </pre> + */ +struct ausrc_st { + const struct ausrc *as; /**< Inheritance */ + + pthread_t tid; /**< Thread ID */ + bool run; /**< Running flag */ + ausrc_read_h *rh; /**< Read handler */ + ausrc_error_h *errh; /**< Error handler */ + void *arg; /**< Handler argument */ + struct ausrc_prm prm; /**< Read parameters */ + struct aubuf *aubuf; /**< Packet buffer */ + size_t psize; /**< Packet size in bytes */ + size_t sampc; + + /* Gstreamer */ + char *uri; + GstElement *pipeline, *bin, *source, *capsfilt, *sink; + GMainLoop *loop; +}; + + +typedef struct _GstFakeSink GstFakeSink; +static char gst_uri[256] = "http://relay1.slayradio.org:8000/"; +static struct ausrc *ausrc; + + +static void *thread(void *arg) +{ + struct ausrc_st *st = arg; + + /* Now set to playing and iterate. */ + gst_element_set_state(st->pipeline, GST_STATE_PLAYING); + + while (st->run) { + g_main_loop_run(st->loop); + } + + return NULL; +} + + +static gboolean bus_watch_handler(GstBus *bus, GstMessage *msg, gpointer data) +{ + struct ausrc_st *st = data; + GMainLoop *loop = st->loop; + GstTagList *tag_list; + gchar *title; + GError *err; + gchar *d; + + (void)bus; + + switch (GST_MESSAGE_TYPE(msg)) { + + case GST_MESSAGE_EOS: + /* XXX decrementing repeat count? */ + + /* Re-start stream */ + if (st->run) { + gst_element_set_state(st->pipeline, GST_STATE_NULL); + gst_element_set_state(st->pipeline, GST_STATE_PLAYING); + } + else { + g_main_loop_quit(loop); + } + break; + + case GST_MESSAGE_ERROR: + gst_message_parse_error(msg, &err, &d); + + warning("gst: Error: %d(%m) message=\"%s\"\n", err->code, + err->code, err->message); + warning("gst: Debug: %s\n", d); + + g_free(d); + + /* Call error handler */ + if (st->errh) + st->errh(err->code, err->message, st->arg); + + g_error_free(err); + + st->run = false; + g_main_loop_quit(loop); + break; + + case GST_MESSAGE_TAG: + gst_message_parse_tag(msg, &tag_list); + + if (gst_tag_list_get_string(tag_list, GST_TAG_TITLE, &title)) { + info("gst: title: %s\n", title); + g_free(title); + } + break; + + default: + break; + } + + return TRUE; +} + + +static void format_check(struct ausrc_st *st, GstStructure *s) +{ + int rate, channels, width; + gboolean sign; + + if (!st || !s) + return; + + gst_structure_get_int(s, "rate", &rate); + gst_structure_get_int(s, "channels", &channels); + gst_structure_get_int(s, "width", &width); + gst_structure_get_boolean(s, "signed", &sign); + + re_printf(" format: rate=%d, channels=%d, width=%d, sign=%d\n", + rate, channels, width, sign); + + if ((int)st->prm.srate != rate) { + warning("gst: expected %u Hz (got %u Hz)\n", st->prm.srate, + rate); + } + if (st->prm.ch != channels) { + warning("gst: expected %d channels (got %d)\n", + st->prm.ch, channels); + } + if (16 != width) { + warning("gst: expected 16-bit width (got %d)\n", width); + } + if (!sign) { + warning("gst: expected signed 16-bit format\n"); + } +} + + +static void play_packet(struct ausrc_st *st) +{ + int16_t buf[st->sampc]; + + /* timed read from audio-buffer */ + if (aubuf_get_samp(st->aubuf, st->prm.ptime, buf, st->sampc)) + return; + + /* call read handler */ + if (st->rh) + st->rh(buf, st->sampc, st->arg); +} + + +/* Expected format: 16-bit signed PCM */ +static void packet_handler(struct ausrc_st *st, GstBuffer *buffer) +{ + GstMapInfo info; + int err; + + if (!st->run) + return; + + /* NOTE: When streaming from files, the buffer will be filled up + * pretty quickly.. + */ + + if (!gst_buffer_map(buffer, &info, GST_MAP_READ)) { + warning("gst: gst_buffer_map failed\n"); + return; + } + + err = aubuf_write(st->aubuf, info.data, info.size); + if (err) { + warning("gst: aubuf_write: %m\n", err); + } + + gst_buffer_unmap(buffer, &info); + + /* Empty buffer now */ + while (st->run) { + const struct timespec delay = {0, st->prm.ptime*1000000/2}; + + play_packet(st); + + if (aubuf_cur_size(st->aubuf) < st->psize) + break; + + (void)nanosleep(&delay, NULL); + } +} + + +static void handoff_handler(GstFakeSink *fakesink, GstBuffer *buffer, + GstPad *pad, gpointer user_data) +{ + struct ausrc_st *st = user_data; + GstCaps *caps; + + re_printf(" handoff handler\n"); + + (void)fakesink; + (void)pad; + + caps = gst_pad_get_current_caps(pad); + + format_check(st, gst_caps_get_structure(caps, 0)); + + packet_handler(st, buffer); +} + + +static void set_caps(struct ausrc_st *st) +{ + GstCaps *caps; + + /* Set the capabilities we want */ + caps = gst_caps_new_simple("audio/x-raw", + "rate", G_TYPE_INT, st->prm.srate, + "channels", G_TYPE_INT, st->prm.ch, + "width", G_TYPE_INT, 16, + "signed", G_TYPE_BOOLEAN,true, + NULL); +#if 1 + gst1_dump_caps(caps); +#endif + + g_object_set(G_OBJECT(st->capsfilt), "caps", caps, NULL); +} + + +/** + * Set up the Gstreamer pipeline. The playbin element is used to decode + * all kinds of different formats. The capsfilter is used to deliver the + * audio in a fixed format (X Hz, 1-2 channels, 16 bit signed) + * + * The pipeline looks like this: + * + * <pre> + * .--------------. .------------------------------------------. + * | playbin | |mybin .------------. .------------. | + * |----. .----| |-----. | capsfilter | | fakesink | | + * |sink| |src |--->|ghost| |----. .---| |----. .---| | handoff + * |----' '----| |pad |-->|sink| |src|-->|sink| |src|--+--> handler + * | | |-----' '------------' '------------' | + * '--------------' '------------------------------------------' + * </pre> + * + * @param st Audio source state + * + * @return 0 if success, otherwise errorcode + */ +static int gst_setup(struct ausrc_st *st) +{ + GstBus *bus; + GstPad *pad; + + st->loop = g_main_loop_new(NULL, FALSE); + + st->pipeline = gst_pipeline_new("pipeline"); + if (!st->pipeline) { + warning("gst: failed to create pipeline element\n"); + return ENOMEM; + } + + /********************* Player BIN **************************/ + + st->source = gst_element_factory_make("playbin", "source"); + if (!st->source) { + warning("gst: failed to create playbin source element\n"); + return ENOMEM; + } + + /********************* My BIN **************************/ + + st->bin = gst_bin_new("mybin"); + + st->capsfilt = gst_element_factory_make("capsfilter", NULL); + if (!st->capsfilt) { + warning("gst: failed to create capsfilter element\n"); + return ENOMEM; + } + + set_caps(st); + + st->sink = gst_element_factory_make("fakesink", "sink"); + if (!st->sink) { + warning("gst: failed to create sink element\n"); + return ENOMEM; + } + + gst_bin_add_many(GST_BIN(st->bin), st->capsfilt, st->sink, NULL); + gst_element_link_many(st->capsfilt, st->sink, NULL); + + /* add ghostpad */ + pad = gst_element_get_static_pad(st->capsfilt, "sink"); + gst_element_add_pad(st->bin, gst_ghost_pad_new("sink", pad)); + gst_object_unref(GST_OBJECT(pad)); + + /* put all elements in a bin */ + gst_bin_add_many(GST_BIN(st->pipeline), st->source, NULL); + + /* Override audio-sink handoff handler */ + g_object_set(G_OBJECT(st->sink), "signal-handoffs", TRUE, NULL); + g_signal_connect(st->sink, "handoff", G_CALLBACK(handoff_handler), st); + + g_object_set(G_OBJECT(st->source), "audio-sink", st->bin, NULL); + + /********************* Misc **************************/ + + /* Bus watch */ + bus = gst_pipeline_get_bus(GST_PIPELINE(st->pipeline)); + gst_bus_add_watch(bus, bus_watch_handler, st); + gst_object_unref(bus); + + /* Set URI */ + g_object_set(G_OBJECT(st->source), "uri", st->uri, NULL); + + return 0; +} + + +static void gst_destructor(void *arg) +{ + struct ausrc_st *st = arg; + + if (st->run) { + st->run = false; + g_main_loop_quit(st->loop); + pthread_join(st->tid, NULL); + } + + gst_element_set_state(st->pipeline, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(st->pipeline)); + + mem_deref(st->uri); + mem_deref(st->aubuf); +} + + +static int gst_alloc(struct ausrc_st **stp, const struct ausrc *as, + struct media_ctx **ctx, + struct ausrc_prm *prm, const char *device, + ausrc_read_h *rh, ausrc_error_h *errh, void *arg) +{ + struct ausrc_st *st; + int err; + + (void)ctx; + + if (!device) + device = gst_uri; + + if (!prm) + return EINVAL; + + st = mem_zalloc(sizeof(*st), gst_destructor); + if (!st) + return ENOMEM; + + st->as = as; + st->rh = rh; + st->errh = errh; + st->arg = arg; + + err = str_dup(&st->uri, device); + if (err) + goto out; + + st->prm = *prm; + + st->sampc = prm->srate * prm->ch * prm->ptime / 1000; + st->psize = 2 * st->sampc; + + err = aubuf_alloc(&st->aubuf, st->psize, 0); + if (err) + goto out; + + err = gst_setup(st); + if (err) + goto out; + + st->run = true; + err = pthread_create(&st->tid, NULL, thread, st); + if (err) { + st->run = false; + goto out; + } + + out: + if (err) + mem_deref(st); + else + *stp = st; + + return err; +} + + +static int mod_gst_init(void) +{ + gchar *s; + + gst_init(0, NULL); + + s = gst_version_string(); + + info("gst: init: %s\n", s); + + g_free(s); + + return ausrc_register(&ausrc, "gst", gst_alloc); +} + + +static int mod_gst_close(void) +{ + gst_deinit(); + ausrc = mem_deref(ausrc); + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(gst1) = { + "gst1", + "sound", + mod_gst_init, + mod_gst_close +}; diff --git a/modules/gst1/gst.h b/modules/gst1/gst.h new file mode 100644 index 0000000..373bed7 --- /dev/null +++ b/modules/gst1/gst.h @@ -0,0 +1,9 @@ +/** + * @file gst.h Gstreamer playbin pipeline -- internal interface + * + * Copyright (C) 2010 Creytiv.com + */ + + +void gst1_dump_props(GstElement *g); +void gst1_dump_caps(const GstCaps *caps); diff --git a/modules/gst1/module.mk b/modules/gst1/module.mk new file mode 100644 index 0000000..5e600d2 --- /dev/null +++ b/modules/gst1/module.mk @@ -0,0 +1,12 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := gst1 +$(MOD)_SRCS += gst.c dump.c +$(MOD)_LFLAGS += `pkg-config --libs gstreamer-1.0` +$(MOD)_CFLAGS += `pkg-config --cflags gstreamer-1.0` + +include mk/mod.mk diff --git a/modules/gst_video1/encode.c b/modules/gst_video1/encode.c new file mode 100644 index 0000000..5e3a61f --- /dev/null +++ b/modules/gst_video1/encode.c @@ -0,0 +1,623 @@ +/** + * @file gst_video/encode.c Video codecs using Gstreamer video pipeline + * + * Copyright (C) 2010 - 2013 Creytiv.com + * Copyright (C) 2014 Fadeev Alexander + * Copyright (C) 2015 Thomas Strobel + */ + +#define __USE_POSIX199309 +#define _DEFAULT_SOURCE 1 +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> +#include <unistd.h> +#include <pthread.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <gst/gst.h> +#include <gst/video/video.h> +#include <gst/app/gstappsrc.h> +#include <gst/app/gstappsink.h> +#include "gst_video.h" + + +struct videnc_state { + + struct { + struct vidsz size; + unsigned fps; + unsigned bitrate; + unsigned pktsize; + } encoder; + + struct { + uint32_t packetization_mode; + uint32_t profile_idc; + uint32_t profile_iop; + uint32_t level_idc; + uint32_t max_fs; + uint32_t max_smbps; + } h264; + + videnc_packet_h *pkth; + void *arg; + + /* Gstreamer */ + struct { + bool valid; + + GstElement *pipeline; + GstAppSrc *source; + + GstAppSrcCallbacks appsrcCallbacks; + GstAppSinkCallbacks appsinkCallbacks; + + struct { + pthread_mutex_t mutex; + pthread_cond_t cond; + int flag; + } eos; + + /* Thread synchronization. */ + struct { + pthread_mutex_t mutex; + pthread_cond_t cond; + /* 0: no-wait, 1: wait, -1: pipeline destroyed */ + int flag; + } wait; + } streamer; +}; + + +static void appsrc_need_data_cb(GstAppSrc *src, guint size, gpointer user_data) +{ + struct videnc_state *st = user_data; + (void)src; + (void)size; + + pthread_mutex_lock(&st->streamer.wait.mutex); + if (st->streamer.wait.flag == 1){ + st->streamer.wait.flag = 0; + pthread_cond_signal(&st->streamer.wait.cond); + } + pthread_mutex_unlock(&st->streamer.wait.mutex); +} + + +static void appsrc_enough_data_cb(GstAppSrc *src, gpointer user_data) +{ + struct videnc_state *st = user_data; + (void)src; + + re_printf(" ~~~~ enough data\n"); + + pthread_mutex_lock(&st->streamer.wait.mutex); + if (st->streamer.wait.flag == 0) + st->streamer.wait.flag = 1; + pthread_mutex_unlock(&st->streamer.wait.mutex); +} + + +static void appsrc_destroy_notify_cb(struct videnc_state *st) +{ + pthread_mutex_lock(&st->streamer.wait.mutex); + st->streamer.wait.flag = -1; + pthread_cond_signal(&st->streamer.wait.cond); + pthread_mutex_unlock(&st->streamer.wait.mutex); +} + + +/* The appsink has received a sample */ +static GstFlowReturn appsink_new_sample_cb(GstAppSink *sink, + gpointer user_data) +{ + struct videnc_state *st = user_data; + GstSample *sample; + GstBuffer *buffer; + GstMapInfo info; + guint8 *data; + guint size; + + /* Retrieve the sample */ + sample = gst_app_sink_pull_sample(sink); + + if (sample) { + buffer = gst_sample_get_buffer(sample); + gst_buffer_map( buffer, &info, (GstMapFlags)(GST_MAP_READ) ); + + data = info.data; + size = info.size; + + gst_video_h264_packetize(data, size, st->encoder.pktsize, + st->pkth, st->arg); + + gst_buffer_unmap(buffer, &info); + gst_sample_unref(sample); + } + + return GST_FLOW_OK; +} + + +static void appsink_end_of_stream_cb(GstAppSink *src, gpointer user_data) +{ + struct videnc_state *st = user_data; + (void)src; + + re_printf(" ~~~~ end-of-stream\n"); + + pthread_mutex_lock(&st->streamer.eos.mutex); + if (st->streamer.eos.flag == 0) { + st->streamer.eos.flag = 1; + pthread_cond_signal(&st->streamer.eos.cond); + } + pthread_mutex_unlock(&st->streamer.eos.mutex); +} + + +static void appsink_destroy_notify_cb(struct videnc_state *st) +{ + re_printf(" ~~~~ sink destroy notify\n"); + + pthread_mutex_lock(&st->streamer.eos.mutex); + st->streamer.eos.flag = -1; + pthread_cond_signal(&st->streamer.eos.cond); + pthread_mutex_unlock(&st->streamer.eos.mutex); +} + + +static GstBusSyncReply bus_sync_handler_cb(GstBus *bus, GstMessage *msg, + struct videnc_state *st) +{ + (void)bus; + + if ((GST_MESSAGE_TYPE (msg)) == GST_MESSAGE_ERROR) { + GError *err = NULL; + gchar *dbg_info = NULL; + gst_message_parse_error (msg, &err, &dbg_info); + warning("gst_video: Error: %d(%m) message=%s\n", + err->code, err->code, err->message); + warning("gst_video: Debug: %s\n", dbg_info); + g_error_free (err); + g_free (dbg_info); + + /* mark pipeline as broked */ + st->streamer.valid = false; + } + + gst_message_unref(msg); + return GST_BUS_DROP; +} + + +static void bus_destroy_notify_cb(struct videnc_state *st) +{ + (void)st; +} + + +/** + * Set up the Gstreamer pipeline. Appsrc gets raw frames, and appsink takes + * encoded frames. + * + * The pipeline looks like this: + * + * <pre> + * .--------. .-----------. .----------. + * | appsrc | | x264enc | | appsink | + * | .----| |----. .---| |----. | + * | |src |-->|sink| |src|-->|sink|-----+-->handoff + * | '----| |----' '---| |----' | handler + * '--------' '-----------' '----------' + * </pre> + */ +static int pipeline_init(struct videnc_state *st, const struct vidsz *size) +{ + GstAppSrc *source; + GstAppSink *sink; + GstBus *bus; + GError* gerror = NULL; + char pipeline[1024]; + GstStateChangeReturn ret; + int err = 0; + + if (!st || !size) + return EINVAL; + + re_printf(" ~~~~ pipeline_init (%d x %d)\n", size->w, size->h); + + snprintf(pipeline, sizeof(pipeline), + "appsrc name=source is-live=TRUE block=TRUE " + "do-timestamp=TRUE max-bytes=1000000 ! " + "videoparse width=%d height=%d format=i420 framerate=%d/1 ! " + "x264enc byte-stream=TRUE rc-lookahead=0 " + "sync-lookahead=0 bitrate=%d ! " + "appsink name=sink emit-signals=TRUE drop=TRUE", + size->w, size->h, + st->encoder.fps, st->encoder.bitrate / 1000 /* kbit/s */); + + re_printf( + "------------------------------------------------\n" + "%s\n" + "------------------------------------------------\n" + , + pipeline); + + /* Initialize pipeline. */ + st->streamer.pipeline = gst_parse_launch(pipeline, &gerror); + + if (gerror) { + warning("gst_video: launch error: %d: %s: %s\n", + gerror->code, gerror->message, pipeline); + err = gerror->code; + g_error_free(gerror); + return err; + } + + /* Configure appsource */ + source = GST_APP_SRC(gst_bin_get_by_name( + GST_BIN(st->streamer.pipeline), "source")); + gst_app_src_set_callbacks(source, &(st->streamer.appsrcCallbacks), + st, (GDestroyNotify)appsrc_destroy_notify_cb); + + /* Configure appsink. */ + sink = GST_APP_SINK(gst_bin_get_by_name( + GST_BIN(st->streamer.pipeline), "sink")); + gst_app_sink_set_callbacks(sink, &(st->streamer.appsinkCallbacks), + st, (GDestroyNotify)appsink_destroy_notify_cb); + gst_object_unref(GST_OBJECT(sink)); + + /* Bus watch */ + bus = gst_pipeline_get_bus(GST_PIPELINE(st->streamer.pipeline)); + gst_bus_set_sync_handler(bus, (GstBusSyncHandler)bus_sync_handler_cb, + st, (GDestroyNotify)bus_destroy_notify_cb); + gst_object_unref(GST_OBJECT(bus)); + + /* Set start values of locks */ + pthread_mutex_lock(&st->streamer.wait.mutex); + st->streamer.wait.flag = 0; + pthread_mutex_unlock(&st->streamer.wait.mutex); + + pthread_mutex_lock(&st->streamer.eos.mutex); + st->streamer.eos.flag = 0; + pthread_mutex_unlock(&st->streamer.eos.mutex); + + /* Start pipeline */ + re_printf(" ~~~~ pipeline_init -- starting pipeline\n"); + + ret = gst_element_set_state(st->streamer.pipeline, GST_STATE_PLAYING); + if (GST_STATE_CHANGE_FAILURE == ret) { + g_warning("set state returned GST_STATE_CHANGE_FAILURE\n"); + err = EPROTO; + goto out; + } + + st->streamer.source = source; + + /* Mark pipeline as working */ + st->streamer.valid = true; + + re_printf(" ~~~~ pipeline_init OK (source=%p, sink=%p)\n", + source, sink); + + out: + return err; +} + + +static void pipeline_close(struct videnc_state *st) +{ + if (!st) + return; + + st->streamer.valid = false; + + if (st->streamer.source) { + gst_object_unref(GST_OBJECT(st->streamer.source)); + st->streamer.source = NULL; + } + + if (st->streamer.pipeline) { + gst_element_set_state(st->streamer.pipeline, GST_STATE_NULL); + + /* pipeline */ + gst_object_unref(GST_OBJECT(st->streamer.pipeline)); + st->streamer.pipeline = NULL; + } +} + + +static void destruct_resources(void *data) +{ + struct videnc_state *st = data; + + /* close pipeline */ + pipeline_close(st); + + /* destroy locks */ + pthread_mutex_destroy(&st->streamer.eos.mutex); + pthread_cond_destroy(&st->streamer.eos.cond); + + pthread_mutex_destroy(&st->streamer.wait.mutex); + pthread_cond_destroy(&st->streamer.wait.cond); +} + + +static int allocate_resources(struct videnc_state **stp) +{ + struct videnc_state *st; + + st = mem_zalloc(sizeof(*st), destruct_resources); + if (!st) + return ENOMEM; + + *stp = st; + + /* initialize locks */ + pthread_mutex_init(&st->streamer.eos.mutex, NULL); + pthread_cond_init(&st->streamer.eos.cond, NULL); + + pthread_mutex_init(&st->streamer.wait.mutex, NULL); + pthread_cond_init(&st->streamer.wait.cond, NULL); + + + /* Set appsource callbacks. */ + st->streamer.appsrcCallbacks.need_data = &appsrc_need_data_cb; + st->streamer.appsrcCallbacks.enough_data = &appsrc_enough_data_cb; + + /* Set appsink callbacks. */ + st->streamer.appsinkCallbacks.new_sample = &appsink_new_sample_cb; + st->streamer.appsinkCallbacks.eos = &appsink_end_of_stream_cb; + + return 0; +} + + +/* + decode sdpparameter for h264 +*/ +static void param_handler(const struct pl *name, const struct pl *val, + void *arg) +{ + struct videnc_state *st = arg; + + if (0 == pl_strcasecmp(name, "packetization-mode")) { + st->h264.packetization_mode = pl_u32(val); + + if (st->h264.packetization_mode != 0) { + warning("gst_video: illegal packetization-mode %u\n", + st->h264.packetization_mode); + return; + } + } + else if (0 == pl_strcasecmp(name, "profile-level-id")) { + struct pl prof = *val; + if (prof.l != 6) { + warning("gst_video: invalid profile-level-id (%r)\n", + val); + return; + } + + prof.l = 2; + st->h264.profile_idc = pl_x32(&prof); prof.p += 2; + st->h264.profile_iop = pl_x32(&prof); prof.p += 2; + st->h264.level_idc = pl_x32(&prof); + } + else if (0 == pl_strcasecmp(name, "max-fs")) { + st->h264.max_fs = pl_u32(val); + } + else if (0 == pl_strcasecmp(name, "max-smbps")) { + st->h264.max_smbps = pl_u32(val); + } + + return; +} + + +int gst_video1_encoder_set(struct videnc_state **stp, + const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp, + videnc_packet_h *pkth, void *arg) +{ + struct videnc_state *st = *stp; + int err = 0; + + if (!stp || !vc || !prm || !pkth) + return EINVAL; + + if (!st) { + err = allocate_resources(stp); + if (err) { + warning("gst_video: resource allocation failed\n"); + return err; + } + st = *stp; + + st->pkth = pkth; + st->arg = arg; + } + else { + if (!st->streamer.valid) { + warning("gst_video codec: trying to work" + " with invalid pipeline\n"); + return EINVAL; + } + } + + if (!prm && (st->encoder.bitrate != prm->bitrate || + st->encoder.pktsize != prm->pktsize || + st->encoder.fps != prm->fps)) { + + /* store new parameters */ + st->encoder.bitrate = prm->bitrate; + st->encoder.pktsize = prm->pktsize; + st->encoder.fps = prm->fps; + + pipeline_close(st); + } + + st->encoder.bitrate = prm->bitrate; + st->encoder.pktsize = prm->pktsize; + st->encoder.fps = prm->fps; + + if (str_isset(fmtp)) { + struct pl sdp_fmtp; + pl_set_str(&sdp_fmtp, fmtp); + + /* store new parameters */ + fmt_param_apply(&sdp_fmtp, param_handler, st); + } + + info("gst_video: video encoder %s: %d fps, %d bit/s, pktsize=%u\n", + vc->name, st->encoder.fps, + st->encoder.bitrate, st->encoder.pktsize); + + return err; +} + + +/* + * couple gstreamer tightly by lock-stepping + */ +static int pipeline_push(struct videnc_state *st, const struct vidframe *frame) +{ + GstBuffer *buffer; + uint8_t *data; + size_t size; + GstFlowReturn ret; + int err = 0; + +#if 1 + /* XXX: should not block the function here */ + + /* + * Wait "start feed". + */ + pthread_mutex_lock(&st->streamer.wait.mutex); + if (st->streamer.wait.flag == 1) { + pthread_cond_wait(&st->streamer.wait.cond, + &st->streamer.wait.mutex); + } + if (st->streamer.eos.flag == -1) + /* error */ + err = ENODEV; + pthread_mutex_unlock(&st->streamer.wait.mutex); + if (err) + return err; +#endif + + /* + * Copy frame into buffer for gstreamer + */ + + /* NOTE: I420 (YUV420P): hardcoded. */ + size = frame->linesize[0] * frame->size.h + + frame->linesize[1] * frame->size.h * 0.5 + + frame->linesize[2] * frame->size.h * 0.5; + + /* allocate memory; memory is freed within callback of + gst_memory_new_wrapped of gst_video_push */ + data = g_try_malloc(size); + if (!data) + return ENOMEM; + + /* copy content of frame */ + size = 0; + memcpy(&data[size], frame->data[0], + frame->linesize[0] * frame->size.h); + size += frame->linesize[0] * frame->size.h; + memcpy(&data[size], frame->data[1], + frame->linesize[1] * frame->size.h * 0.5); + size += frame->linesize[1] * frame->size.h * 0.5; + memcpy(&data[size], frame->data[2], + frame->linesize[2] * frame->size.h * 0.5); + size += frame->linesize[2] * frame->size.h * 0.5; + + /* Wrap memory in a gstreamer buffer */ + buffer = gst_buffer_new(); + gst_buffer_insert_memory(buffer, -1, + gst_memory_new_wrapped (0, data, size, 0, + size, data, g_free)); + + /* + * Push data and EOS into gstreamer. + */ + + ret = gst_app_src_push_buffer(st->streamer.source, buffer); + if (ret != GST_FLOW_OK) { + warning("gst_video: pushing buffer failed\n"); + err = EPROTO; + goto out; + } + +#if 0 + ret = gst_app_src_end_of_stream(st->streamer.source); + if (ret != GST_FLOW_OK) { + warning("gst_video: pushing EOS failed\n"); + err = EPROTO; + goto out; + } +#endif + + +#if 0 + /* + * Wait "processing done". + */ + pthread_mutex_lock(&st->streamer.eos.mutex); + if (st->streamer.eos.flag == 0) + /* will returns with EOS (1) or error (-1) */ + pthread_cond_wait(&st->streamer.wait.cond, + &st->streamer.wait.mutex); + if (st->streamer.eos.flag == 1) + /* reset eos */ + st->streamer.eos.flag = 0; + else + /* error */ + err = -1; + pthread_mutex_unlock(&st->streamer.wait.mutex); +#endif + + + out: + return err; +} + + +int gst_video1_encode(struct videnc_state *st, bool update, + const struct vidframe *frame) +{ + int err; + + if (!st || !frame || frame->fmt != VID_FMT_YUV420P) + return EINVAL; + + if (!st->streamer.valid || + !vidsz_cmp(&st->encoder.size, &frame->size)) { + + pipeline_close(st); + + err = pipeline_init(st, &frame->size); + if (err) { + warning("gst_video: pipeline initialization failed\n"); + return err; + } + + st->encoder.size = frame->size; + } + + if (update) { + debug("gst_video: gstreamer picture update" + ", it's not implemented...\n"); + } + + /* + * Push frame into pipeline. + * Function call will return once frame has been processed completely. + */ + err = pipeline_push(st, frame); + + return err; +} diff --git a/modules/gst_video1/gst_video.c b/modules/gst_video1/gst_video.c new file mode 100644 index 0000000..56c90f5 --- /dev/null +++ b/modules/gst_video1/gst_video.c @@ -0,0 +1,66 @@ +/** + * @file gst_video.c Video codecs using Gstreamer 1.0 + * + * Copyright (C) 2010 Creytiv.com + * Copyright (C) 2014 Fadeev Alexander + */ + +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include <gst/gst.h> +#include "gst_video.h" + + +/** + * @defgroup gst_video gst_video + * + * This module implements video codecs using Gstreamer 1.0 + * + * Currently only H.264 encoding is supported, but this can be extended + * if needed. No decoding is done by this module, so that must be done by + * another video-codec module. + * + * Thanks to Victor Sergienko and Fadeev Alexander for the + * initial version, which was based on avcodec module. + */ + + +static struct vidcodec h264 = { + .name = "H264", + .variant = "packetization-mode=0", + .encupdh = gst_video1_encoder_set, + .ench = gst_video1_encode, + .fmtp_ench = gst_video1_fmtp_enc, + .fmtp_cmph = gst_video1_fmtp_cmp, +}; + + +static int module_init(void) +{ + gst_init(NULL, NULL); + + vidcodec_register(&h264); + + info("gst_video: using gstreamer (%s)\n", gst_version_string()); + + return 0; +} + + +static int module_close(void) +{ + vidcodec_unregister(&h264); + + gst_deinit(); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(gst_video1) = { + "gst_video1", + "vidcodec", + module_init, + module_close +}; diff --git a/modules/gst_video1/gst_video.h b/modules/gst_video1/gst_video.h new file mode 100644 index 0000000..83aa1ee --- /dev/null +++ b/modules/gst_video1/gst_video.h @@ -0,0 +1,32 @@ +/** + * @file gst_video.h Gstreamer video pipeline -- internal API + * + * Copyright (C) 2010 - 2014 Creytiv.com + * Copyright (C) 2014 Fadeev Alexander + */ + + +/* Encode */ +struct videnc_state; + +int gst_video1_encoder_set(struct videnc_state **stp, + const struct vidcodec *vc, + struct videnc_param *prm, const char *fmtp, + videnc_packet_h *pkth, void *arg); +int gst_video1_encode(struct videnc_state *st, bool update, + const struct vidframe *frame); + + +/* SDP */ +uint32_t gst_video1_h264_packetization_mode(const char *fmtp); +int gst_video1_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg); +bool gst_video1_fmtp_cmp(const char *fmtp1, const char *fmtp2, void *data); + + +/* H.264 */ +extern const uint8_t gst_video_h264_level_idc; + +int gst_video_h264_packetize(const uint8_t *buf, size_t len, + size_t pktsize, + videnc_packet_h *pkth, void *arg); diff --git a/modules/gst_video1/h264.c b/modules/gst_video1/h264.c new file mode 100644 index 0000000..85e3442 --- /dev/null +++ b/modules/gst_video1/h264.c @@ -0,0 +1,160 @@ +/** + * @file gst_video/h264.c H.264 Packetization + * + * Copyright (C) 2010 Creytiv.com + */ +#include <string.h> +#include <re.h> +#include <rem.h> +#include <baresip.h> +#include "gst_video.h" + + +/** NAL unit types (RFC 3984, Table 1) */ +enum { + H264_NAL_UNKNOWN = 0, + /* 1-23 NAL unit Single NAL unit packet per H.264 */ + H264_NAL_SLICE = 1, + H264_NAL_DPA = 2, + H264_NAL_DPB = 3, + H264_NAL_DPC = 4, + H264_NAL_IDR_SLICE = 5, + H264_NAL_SEI = 6, + H264_NAL_SPS = 7, + H264_NAL_PPS = 8, + H264_NAL_AUD = 9, + H264_NAL_END_SEQUENCE = 10, + H264_NAL_END_STREAM = 11, + H264_NAL_FILLER_DATA = 12, + H264_NAL_SPS_EXT = 13, + H264_NAL_AUX_SLICE = 19, + + H264_NAL_STAP_A = 24, /**< Single-time aggregation packet */ + H264_NAL_STAP_B = 25, /**< Single-time aggregation packet */ + H264_NAL_MTAP16 = 26, /**< Multi-time aggregation packet */ + H264_NAL_MTAP24 = 27, /**< Multi-time aggregation packet */ + H264_NAL_FU_A = 28, /**< Fragmentation unit */ + H264_NAL_FU_B = 29, /**< Fragmentation unit */ +}; + + +const uint8_t gst_video_h264_level_idc = 0x0c; + + +/* + * Find the NAL start sequence in a H.264 byte stream + * + * @note: copied from ffmpeg source + */ +static const uint8_t *h264_find_startcode(const uint8_t *p, const uint8_t *end) +{ + const uint8_t *a = p + 4 - ((long)p & 3); + + for (end -= 3; p < a && p < end; p++ ) { + if (p[0] == 0 && p[1] == 0 && p[2] == 1) + return p; + } + + for (end -= 3; p < end; p += 4) { + uint32_t x = *(const uint32_t*)(void *)p; + if ( (x - 0x01010101) & (~x) & 0x80808080 ) { + if (p[1] == 0 ) { + if ( p[0] == 0 && p[2] == 1 ) + return p; + if ( p[2] == 0 && p[3] == 1 ) + return p+1; + } + if ( p[3] == 0 ) { + if ( p[2] == 0 && p[4] == 1 ) + return p+2; + if ( p[4] == 0 && p[5] == 1 ) + return p+3; + } + } + } + + for (end += 3; p < end; p++) { + if (p[0] == 0 && p[1] == 0 && p[2] == 1) + return p; + } + + return end + 3; +} + + +static int rtp_send_data(const uint8_t *hdr, size_t hdr_sz, + const uint8_t *buf, size_t sz, bool eof, + videnc_packet_h *pkth, void *arg) +{ + return pkth(eof, hdr, hdr_sz, buf, sz, arg); +} + + +static int h264_nal_send(bool first, bool last, + bool marker, uint32_t ihdr, const uint8_t *buf, + size_t size, size_t maxsz, + videnc_packet_h *pkth, void *arg) +{ + uint8_t hdr = (uint8_t)ihdr; + int err = 0; + + if (first && last && size <= maxsz) { + err = rtp_send_data(&hdr, 1, buf, size, marker, + pkth, arg); + } + else { + uint8_t fu_hdr[2]; + const uint8_t type = hdr & 0x1f; + const uint8_t nri = hdr & 0x60; + const size_t sz = maxsz - 2; + + fu_hdr[0] = nri | H264_NAL_FU_A; + fu_hdr[1] = first ? (1<<7 | type) : type; + + while (size > sz) { + err |= rtp_send_data(fu_hdr, 2, buf, sz, false, + pkth, arg); + buf += sz; + size -= sz; + fu_hdr[1] &= ~(1 << 7); + } + + if (last) + fu_hdr[1] |= 1<<6; /* end bit */ + + err |= rtp_send_data(fu_hdr, 2, buf, size, marker && last, + pkth, arg); + } + + return err; +} + + +int gst_video_h264_packetize(const uint8_t *buf, size_t len, + size_t pktsize, + videnc_packet_h *pkth, void *arg) +{ + const uint8_t *start = buf; + const uint8_t *end = start + len; + const uint8_t *r; + int err = 0; + + r = h264_find_startcode(buf, end); + + while (r < end) { + const uint8_t *r1; + + /* skip zeros */ + while (!*(r++)) + ; + + r1 = h264_find_startcode(r, end); + + err |= h264_nal_send(true, true, (r1 >= end), r[0], + r+1, r1-r-1, pktsize, + pkth, arg); + r = r1; + } + + return err; +} diff --git a/modules/gst_video1/module.mk b/modules/gst_video1/module.mk new file mode 100644 index 0000000..b29fd7e --- /dev/null +++ b/modules/gst_video1/module.mk @@ -0,0 +1,12 @@ +# +# module.mk +# +# Copyright (C) 2010 Creytiv.com +# + +MOD := gst_video1 +$(MOD)_SRCS += gst_video.c h264.c encode.c sdp.c +$(MOD)_LFLAGS += `pkg-config --libs gstreamer-1.0 gstreamer-app-1.0` +$(MOD)_CFLAGS += `pkg-config --cflags gstreamer-1.0 gstreamer-app-1.0` + +include mk/mod.mk diff --git a/modules/gst_video1/sdp.c b/modules/gst_video1/sdp.c new file mode 100644 index 0000000..f28f8e7 --- /dev/null +++ b/modules/gst_video1/sdp.c @@ -0,0 +1,54 @@ +/** + * @file gst_video/sdp.c H.264 SDP Functions + * + * Copyright (C) 2010 Creytiv.com + */ + +#include <re.h> +#include <baresip.h> +#include "gst_video.h" + + +uint32_t gst_video1_h264_packetization_mode(const char *fmtp) +{ + struct pl pl, mode; + + if (!fmtp) + return 0; + + pl_set_str(&pl, fmtp); + + if (fmt_param_get(&pl, "packetization-mode", &mode)) + return pl_u32(&mode); + + return 0; +} + + +int gst_video1_fmtp_enc(struct mbuf *mb, const struct sdp_format *fmt, + bool offer, void *arg) +{ + struct vidcodec *vc = arg; + const uint8_t profile_idc = 0x42; /* baseline profile */ + const uint8_t profile_iop = 0x80; + (void)offer; + + if (!mb || !fmt || !vc) + return 0; + + return mbuf_printf(mb, "a=fmtp:%s" + " packetization-mode=0" + ";profile-level-id=%02x%02x%02x" + "\r\n", + fmt->id, profile_idc, profile_iop, + gst_video_h264_level_idc); +} + + +bool gst_video1_fmtp_cmp(const char *fmtp1, const char *fmtp2, void *data) +{ + (void)data; + + return gst_video1_h264_packetization_mode(fmtp1) == + gst_video1_h264_packetization_mode(fmtp2); +} |