summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlfred E. Heggestad <aeh@db.org>2015-08-01 18:54:04 +0200
committerAlfred E. Heggestad <aeh@db.org>2015-08-01 18:54:04 +0200
commit5afea9730d64aacf3adcb822b34bbc810ed4dedb (patch)
tree36eeea381ea1d34eb8ba28c1f0531b96fdf5893d
parent9bebd147cc384b777e45e6236fa9aa680633e6eb (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
-rw-r--r--mk/modules.mk22
-rw-r--r--modules/gst1/dump.c65
-rw-r--r--modules/gst1/gst.c469
-rw-r--r--modules/gst1/gst.h9
-rw-r--r--modules/gst1/module.mk12
-rw-r--r--modules/gst_video1/encode.c623
-rw-r--r--modules/gst_video1/gst_video.c66
-rw-r--r--modules/gst_video1/gst_video.h32
-rw-r--r--modules/gst_video1/h264.c160
-rw-r--r--modules/gst_video1/module.mk12
-rw-r--r--modules/gst_video1/sdp.c54
11 files changed, 1523 insertions, 1 deletions
diff --git a/mk/modules.mk b/mk/modules.mk
index 2481f0e..dc93491 100644
--- a/mk/modules.mk
+++ b/mk/modules.mk
@@ -20,7 +20,10 @@
# USE_G722_1 G.722.1 audio codec
# USE_G726 G.726 audio codec
# USE_GSM GSM audio codec
-# USE_GST Gstreamer audio module
+# USE_GST Gstreamer 0.10 audio module
+# USE_GST1 Gstreamer 1.0 audio module
+# USE_GST_VIDEO Gstreamer 0.10 video module
+# USE_GST_VIDEO1 Gstreamer 1.0 video module
# USE_ILBC iLBC audio codec
# USE_ISAC iSAC audio codec
# USE_L16 L16 audio codec
@@ -97,6 +100,14 @@ USE_GSM := $(shell [ -f $(SYSROOT)/include/gsm.h ] || \
[ -f $(SYSROOT)/local/include/gsm/gsm.h ] && echo "yes")
USE_GST := $(shell [ -f $(SYSROOT)/include/gstreamer-0.10/gst/gst.h ] || \
[ -f $(SYSROOT_ALT)/include/gstreamer-0.10/gst/gst.h ] && echo "yes")
+USE_GST1 := $(shell [ -f $(SYSROOT)/include/gstreamer-1.0/gst/gst.h ] || \
+ [ -f $(SYSROOT_ALT)/include/gstreamer-1.0/gst/gst.h ] && echo "yes")
+USE_GST_VIDEO := \
+ $(shell [ -f $(SYSROOT)/include/gstreamer-0.10/gst/gst.h ] || \
+ [ -f $(SYSROOT_ALT)/include/gstreamer-0.10/gst/gst.h ] && echo "yes")
+USE_GST_VIDEO1 := \
+ $(shell [ -f $(SYSROOT)/include/gstreamer-1.0/gst/gst.h ] || \
+ [ -f $(SYSROOT_ALT)/include/gstreamer-1.0/gst/gst.h ] && echo "yes")
USE_ILBC := $(shell [ -f $(SYSROOT)/include/iLBC_define.h ] || \
[ -f $(SYSROOT)/local/include/iLBC_define.h ] && echo "yes")
USE_ISAC := $(shell [ -f $(SYSROOT)/include/isac.h ] || \
@@ -294,6 +305,15 @@ endif
ifneq ($(USE_GST),)
MODULES += gst
endif
+ifneq ($(USE_GST1),)
+MODULES += gst1
+endif
+ifneq ($(USE_GST_VIDEO),)
+MODULES += gst_video
+endif
+ifneq ($(USE_GST_VIDEO1),)
+MODULES += gst_video1
+endif
ifneq ($(USE_ILBC),)
MODULES += ilbc
endif
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);
+}