From 8e4bf45af244adf60260a467d75ca4d2bfac0ff0 Mon Sep 17 00:00:00 2001
From: Bastien Nocera <hadess@hadess.net>
Date: Mon, 22 Jun 2009 16:09:04 +0100
Subject: [PATCH] Move farsight plugins from -bad to -good
---
Makefile.am | 1 +
common | 2 +-
configure.ac | 6 +
farsight/Makefile.am | 1 +
farsight/autoconvert/Makefile.am | 9 +
farsight/autoconvert/gstautoconvert.c | 1566 +++++++++++++++++++++++++++++++++
farsight/autoconvert/gstautoconvert.h | 69 ++
farsight/dtmf/Makefile.am | 23 +
farsight/dtmf/gstdtmf.c | 33 +
farsight/dtmf/gstdtmfdetect.c | 308 +++++++
farsight/dtmf/gstdtmfdetect.h | 73 ++
farsight/dtmf/gstdtmfsrc.c | 905 +++++++++++++++++++
farsight/dtmf/gstdtmfsrc.h | 98 ++
farsight/dtmf/gstrtpdtmfcommon.h | 23 +
farsight/dtmf/gstrtpdtmfdepay.c | 555 ++++++++++++
farsight/dtmf/gstrtpdtmfdepay.h | 66 ++
farsight/dtmf/gstrtpdtmfsrc.c | 1099 +++++++++++++++++++++++
farsight/dtmf/gstrtpdtmfsrc.h | 112 +++
farsight/dtmf/tone_detect.c | 499 +++++++++++
farsight/dtmf/tone_detect.h | 93 ++
farsight/liveadder/Makefile.am | 11 +
farsight/liveadder/liveadder.c | 1548 ++++++++++++++++++++++++++++++++
farsight/liveadder/liveadder.h | 108 +++
farsight/rtpmux/Makefile.am | 13 +
farsight/rtpmux/gstrtpdtmfmux.c | 327 +++++++
farsight/rtpmux/gstrtpdtmfmux.h | 68 ++
farsight/rtpmux/gstrtpmux.c | 662 ++++++++++++++
farsight/rtpmux/gstrtpmux.h | 78 ++
farsight/rtpmux/gstrtpmuxer.c | 48 +
farsight/valve/Makefile.am | 9 +
farsight/valve/gstvalve.c | 311 +++++++
farsight/valve/gstvalve.h | 82 ++
32 files changed, 8805 insertions(+), 1 deletions(-)
create mode 100644 farsight/Makefile.am
create mode 100644 farsight/autoconvert/Makefile.am
create mode 100644 farsight/autoconvert/gstautoconvert.c
create mode 100644 farsight/autoconvert/gstautoconvert.h
create mode 100644 farsight/dtmf/Makefile.am
create mode 100644 farsight/dtmf/gstdtmf.c
create mode 100644 farsight/dtmf/gstdtmfdetect.c
create mode 100644 farsight/dtmf/gstdtmfdetect.h
create mode 100644 farsight/dtmf/gstdtmfsrc.c
create mode 100644 farsight/dtmf/gstdtmfsrc.h
create mode 100644 farsight/dtmf/gstrtpdtmfcommon.h
create mode 100644 farsight/dtmf/gstrtpdtmfdepay.c
create mode 100644 farsight/dtmf/gstrtpdtmfdepay.h
create mode 100644 farsight/dtmf/gstrtpdtmfsrc.c
create mode 100644 farsight/dtmf/gstrtpdtmfsrc.h
create mode 100644 farsight/dtmf/tone_detect.c
create mode 100644 farsight/dtmf/tone_detect.h
create mode 100644 farsight/liveadder/Makefile.am
create mode 100644 farsight/liveadder/liveadder.c
create mode 100644 farsight/liveadder/liveadder.h
create mode 100644 farsight/rtpmux/Makefile.am
create mode 100644 farsight/rtpmux/gstrtpdtmfmux.c
create mode 100644 farsight/rtpmux/gstrtpdtmfmux.h
create mode 100644 farsight/rtpmux/gstrtpmux.c
create mode 100644 farsight/rtpmux/gstrtpmux.h
create mode 100644 farsight/rtpmux/gstrtpmuxer.c
create mode 100644 farsight/valve/Makefile.am
create mode 100644 farsight/valve/gstvalve.c
create mode 100644 farsight/valve/gstvalve.h
diff --git a/Makefile.am b/Makefile.am
index 4247a97..997881b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -8,6 +8,7 @@ endif
ALWAYS_SUBDIRS = \
gst sys ext \
+ farsight \
tests \
docs \
po \
diff --git a/configure.ac b/configure.ac
index baa92bb..ea7f999 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1120,6 +1120,12 @@ sys/osxvideo/Makefile
sys/v4l2/Makefile
sys/waveform/Makefile
sys/ximage/Makefile
+farsight/Makefile
+farsight/valve/Makefile
+farsight/liveadder/Makefile
+farsight/dtmf/Makefile
+farsight/rtpmux/Makefile
+farsight/autoconvert/Makefile
po/Makefile.in
tests/Makefile
tests/check/Makefile
diff --git a/farsight/Makefile.am b/farsight/Makefile.am
new file mode 100644
index 0000000..a5fac23
--- /dev/null
+++ b/farsight/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = autoconvert dtmf liveadder rtpmux valve
diff --git a/farsight/autoconvert/Makefile.am b/farsight/autoconvert/Makefile.am
new file mode 100644
index 0000000..28fc015
--- /dev/null
+++ b/farsight/autoconvert/Makefile.am
@@ -0,0 +1,9 @@
+plugin_LTLIBRARIES = libgstautoconvert.la
+
+libgstautoconvert_la_SOURCES = gstautoconvert.c gstautoconvert.h
+
+libgstautoconvert_la_CFLAGS = $(GST_CFLAGS)
+libgstautoconvert_la_LIBADD = $(GST_LIBS)
+libgstautoconvert_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+libgstautoconvert_la_LIBTOOLFLAGS = --tag=disable-static
+
diff --git a/farsight/autoconvert/gstautoconvert.c b/farsight/autoconvert/gstautoconvert.c
new file mode 100644
index 0000000..4fb0a41
--- /dev/null
+++ b/farsight/autoconvert/gstautoconvert.c
@@ -0,0 +1,1566 @@
+/* GStreamer
+ *
+ * Copyright 2007-2008 Collabora Ltd
+ * @author: Olivier Crete <olivier.crete@collabora.co.uk>
+ * Copyright 2007-2008 Nokia
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+/**
+ * SECTION:element-autoconvert
+ *
+ * The #autoconvert element has one sink and one source pad. It will look for
+ * other elements that also have one sink and one source pad.
+ * It will then pick an element that matches the caps on both sides.
+ * If the caps change, it may change the selected element if the current one
+ * no longer matches the caps.
+ *
+ * The list of element it will look into can be specified in the
+ * #GstAutoConvert::factories property, otherwise it will look at all available
+ * elements.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstautoconvert.h"
+
+#include <string.h>
+
+GST_DEBUG_CATEGORY (autoconvert_debug);
+#define GST_CAT_DEFAULT (autoconvert_debug)
+
+#define DEFAULT_INITIAL_IDENTITY FALSE
+
+#define GST_AUTOCONVERT_LOCK(ac) GST_OBJECT_LOCK (ac)
+#define GST_AUTOCONVERT_UNLOCK(ac) GST_OBJECT_UNLOCK (ac)
+
+/* elementfactory information */
+static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS_ANY);
+
+static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS_ANY);
+
+static GstStaticPadTemplate sink_internal_template =
+GST_STATIC_PAD_TEMPLATE ("sink_internal",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS_ANY);
+
+static GstStaticPadTemplate src_internal_template =
+GST_STATIC_PAD_TEMPLATE ("src_internal",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS_ANY);
+
+/* GstAutoConvert signals and args */
+enum
+{
+ /* FILL ME */
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_FACTORIES,
+ PROP_INITIAL_IDENTITY
+};
+
+static void gst_auto_convert_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec);
+static void gst_auto_convert_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec);
+static void gst_auto_convert_dispose (GObject * object);
+
+static GstStateChangeReturn gst_auto_convert_change_state (GstElement * element,
+ GstStateChange transition);
+
+static GstElement *gst_auto_convert_get_subelement (GstAutoConvert *
+ autoconvert, gboolean query_only);
+static GstPad *gst_auto_convert_get_internal_sinkpad (GstAutoConvert *
+ autoconvert);
+static GstPad *gst_auto_convert_get_internal_srcpad (GstAutoConvert *
+ autoconvert);
+
+static gboolean gst_auto_convert_sink_setcaps (GstPad * pad, GstCaps * caps);
+static GstCaps *gst_auto_convert_sink_getcaps (GstPad * pad);
+static GstFlowReturn gst_auto_convert_sink_chain (GstPad * pad,
+ GstBuffer * buffer);
+static gboolean gst_auto_convert_sink_event (GstPad * pad, GstEvent * event);
+static gboolean gst_auto_convert_sink_query (GstPad * pad, GstQuery * query);
+static const GstQueryType *gst_auto_convert_sink_query_type (GstPad * pad);
+static GstFlowReturn gst_auto_convert_sink_buffer_alloc (GstPad * pad,
+ guint64 offset, guint size, GstCaps * caps, GstBuffer ** buf);
+static void gst_auto_convert_sink_fixatecaps (GstPad * pad, GstCaps * caps);
+
+static gboolean gst_auto_convert_src_event (GstPad * pad, GstEvent * event);
+static gboolean gst_auto_convert_src_query (GstPad * pad, GstQuery * query);
+static const GstQueryType *gst_auto_convert_src_query_type (GstPad * pad);
+
+static GstFlowReturn gst_auto_convert_internal_sink_chain (GstPad * pad,
+ GstBuffer * buffer);
+static gboolean gst_auto_convert_internal_sink_event (GstPad * pad,
+ GstEvent * event);
+static gboolean gst_auto_convert_internal_sink_query (GstPad * pad,
+ GstQuery * query);
+static const GstQueryType *gst_auto_convert_internal_sink_query_type (GstPad *
+ pad);
+static GstCaps *gst_auto_convert_internal_sink_getcaps (GstPad * pad);
+static GstFlowReturn gst_auto_convert_internal_sink_buffer_alloc (GstPad * pad,
+ guint64 offset, guint size, GstCaps * caps, GstBuffer ** buf);
+static void gst_auto_convert_internal_sink_fixatecaps (GstPad * pad,
+ GstCaps * caps);
+
+static gboolean gst_auto_convert_internal_src_event (GstPad * pad,
+ GstEvent * event);
+static gboolean gst_auto_convert_internal_src_query (GstPad * pad,
+ GstQuery * query);
+static const GstQueryType *gst_auto_convert_internal_src_query_type (GstPad *
+ pad);
+
+static GList *gst_auto_convert_load_factories (GstAutoConvert * autoconvert);
+static GstElement
+ * gst_auto_convert_get_or_make_element_from_factory (GstAutoConvert *
+ autoconvert, GstElementFactory * factory);
+static gboolean gst_auto_convert_activate_element (GstAutoConvert * autoconvert,
+ GstElement * element, GstCaps * caps);
+
+static GQuark internal_srcpad_quark = 0;
+static GQuark internal_sinkpad_quark = 0;
+static GQuark parent_quark = 0;
+
+static void
+gst_auto_convert_do_init (GType type)
+{
+ GST_DEBUG_CATEGORY_INIT (autoconvert_debug, "autoconvert", 0,
+ "Auto convert based on caps");
+
+ internal_srcpad_quark = g_quark_from_static_string ("internal_srcpad");
+ internal_sinkpad_quark = g_quark_from_static_string ("internal_sinkpad");
+ parent_quark = g_quark_from_static_string ("parent");
+}
+
+GST_BOILERPLATE_FULL (GstAutoConvert, gst_auto_convert, GstBin,
+ GST_TYPE_BIN, gst_auto_convert_do_init);
+
+static void
+gst_auto_convert_base_init (gpointer klass)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&srctemplate));
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&sinktemplate));
+
+ gst_element_class_set_details_simple (element_class,
+ "Select convertor based on caps", "Generic/Bin",
+ "Selects the right transform element based on the caps",
+ "Olivier Crete <olivier.crete@collabora.co.uk>");
+}
+
+static void
+gst_auto_convert_class_init (GstAutoConvertClass * klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+ GstElementClass *gstelement_class = (GstElementClass *) klass;
+
+ gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_auto_convert_dispose);
+
+ gobject_class->set_property = gst_auto_convert_set_property;
+ gobject_class->get_property = gst_auto_convert_get_property;
+
+ g_object_class_install_property (gobject_class, PROP_FACTORIES,
+ g_param_spec_pointer ("factories",
+ "GList of GstElementFactory",
+ "GList of GstElementFactory objects to pick from (the element takes"
+ " ownership of the list (NULL means it will go through all possible"
+ " elements), can only be set once",
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_INITIAL_IDENTITY,
+ g_param_spec_boolean ("initial-identity",
+ "Install identity initially",
+ "If true, then the identity element will be installed initially "
+ "and used for event passing until the first data buffer arrives ",
+ DEFAULT_INITIAL_IDENTITY, G_PARAM_READWRITE));
+
+ gstelement_class->change_state =
+ GST_DEBUG_FUNCPTR (gst_auto_convert_change_state);
+}
+
+static void
+gst_auto_convert_init (GstAutoConvert * autoconvert,
+ GstAutoConvertClass * klass)
+{
+ autoconvert->sinkpad =
+ gst_pad_new_from_static_template (&sinktemplate, "sink");
+ autoconvert->srcpad = gst_pad_new_from_static_template (&srctemplate, "src");
+
+ gst_pad_set_setcaps_function (autoconvert->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_auto_convert_sink_setcaps));
+ gst_pad_set_getcaps_function (autoconvert->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_auto_convert_sink_getcaps));
+ gst_pad_set_chain_function (autoconvert->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_auto_convert_sink_chain));
+ gst_pad_set_event_function (autoconvert->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_auto_convert_sink_event));
+ gst_pad_set_query_function (autoconvert->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_auto_convert_sink_query));
+ gst_pad_set_query_type_function (autoconvert->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_auto_convert_sink_query_type));
+ gst_pad_set_bufferalloc_function (autoconvert->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_auto_convert_sink_buffer_alloc));
+
+ gst_pad_set_event_function (autoconvert->srcpad,
+ GST_DEBUG_FUNCPTR (gst_auto_convert_src_event));
+ gst_pad_set_query_function (autoconvert->srcpad,
+ GST_DEBUG_FUNCPTR (gst_auto_convert_src_query));
+ gst_pad_set_query_type_function (autoconvert->srcpad,
+ GST_DEBUG_FUNCPTR (gst_auto_convert_src_query_type));
+
+ gst_element_add_pad (GST_ELEMENT (autoconvert), autoconvert->sinkpad);
+ gst_element_add_pad (GST_ELEMENT (autoconvert), autoconvert->srcpad);
+
+ gst_segment_init (&autoconvert->sink_segment, GST_FORMAT_UNDEFINED);
+
+ autoconvert->initial_identity = DEFAULT_INITIAL_IDENTITY;
+}
+
+static void
+gst_auto_convert_dispose (GObject * object)
+{
+ GstAutoConvert *autoconvert = GST_AUTO_CONVERT (object);
+
+ gst_pad_set_fixatecaps_function (autoconvert->sinkpad, NULL);
+
+ GST_AUTOCONVERT_LOCK (autoconvert);
+ if (autoconvert->current_subelement) {
+ gst_object_unref (autoconvert->current_subelement);
+ autoconvert->current_subelement = NULL;
+ autoconvert->current_internal_sinkpad = NULL;
+ autoconvert->current_internal_srcpad = NULL;
+ }
+
+ g_list_foreach (autoconvert->cached_events, (GFunc) gst_mini_object_unref,
+ NULL);
+ g_list_free (autoconvert->cached_events);
+ autoconvert->cached_events = NULL;
+
+ if (autoconvert->factories) {
+ gst_plugin_feature_list_free (autoconvert->factories);
+ autoconvert->factories = NULL;
+ }
+ GST_AUTOCONVERT_UNLOCK (autoconvert);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gst_auto_convert_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+ GstAutoConvert *autoconvert = GST_AUTO_CONVERT (object);
+
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ case PROP_FACTORIES:
+ GST_AUTOCONVERT_LOCK (autoconvert);
+ if (autoconvert->factories == NULL) {
+ GList *factories = g_value_get_pointer (value);
+ autoconvert->factories = g_list_copy (factories);
+ g_list_foreach (autoconvert->factories, (GFunc) g_object_ref, NULL);
+ } else
+ GST_WARNING_OBJECT (object, "Can not reset factories after they"
+ " have been set or auto-discovered");
+ GST_AUTOCONVERT_UNLOCK (autoconvert);
+ break;
+ case PROP_INITIAL_IDENTITY:
+ GST_AUTOCONVERT_LOCK (autoconvert);
+ autoconvert->initial_identity = g_value_get_boolean (value);
+ GST_AUTOCONVERT_UNLOCK (autoconvert);
+ break;
+ }
+}
+
+static void
+gst_auto_convert_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec)
+{
+ GstAutoConvert *autoconvert = GST_AUTO_CONVERT (object);
+
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ case PROP_FACTORIES:
+ GST_AUTOCONVERT_LOCK (autoconvert);
+ g_value_set_pointer (value, &autoconvert->factories);
+ GST_AUTOCONVERT_UNLOCK (autoconvert);
+ break;
+ case PROP_INITIAL_IDENTITY:
+ GST_AUTOCONVERT_LOCK (autoconvert);
+ g_value_set_boolean (value, autoconvert->initial_identity);
+ GST_AUTOCONVERT_UNLOCK (autoconvert);
+ break;
+ }
+}
+
+static GstStateChangeReturn
+gst_auto_convert_change_state (GstElement * element, GstStateChange transition)
+{
+ GstAutoConvert *autoconvert = GST_AUTO_CONVERT (element);
+ GstStateChangeReturn ret;
+
+ switch (transition) {
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ return ret;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ g_list_foreach (autoconvert->cached_events, (GFunc) gst_mini_object_unref,
+ NULL);
+ g_list_free (autoconvert->cached_events);
+ autoconvert->cached_events = NULL;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static GstElement *
+gst_auto_convert_get_element_by_type (GstAutoConvert * autoconvert, GType type)
+{
+ GstIterator *iter = NULL;
+ GstElement *elem = NULL;
+ gboolean done;
+
+ g_return_val_if_fail (type != 0, NULL);
+
+ iter = gst_bin_iterate_elements (GST_BIN (autoconvert));
+
+ if (!iter)
+ return NULL;
+
+ done = FALSE;
+ while (!done) {
+ switch (gst_iterator_next (iter, (gpointer) & elem)) {
+ case GST_ITERATOR_OK:
+ if (G_OBJECT_TYPE (elem) == type)
+ done = TRUE;
+ else
+ gst_object_unref (elem);
+ break;
+ case GST_ITERATOR_RESYNC:
+ gst_iterator_resync (iter);
+ elem = NULL;
+ break;
+ case GST_ITERATOR_ERROR:
+ GST_ERROR ("Error iterating elements in bin");
+ elem = NULL;
+ done = TRUE;
+ break;
+ case GST_ITERATOR_DONE:
+ elem = NULL;
+ done = TRUE;
+ break;
+ }
+ }
+ gst_iterator_free (iter);
+
+ return elem;
+}
+
+/**
+ * get_pad_by_direction:
+ * @element: The Element
+ * @direction: The direction
+ *
+ * Gets a #GstPad that goes in the requested direction. I will return NULL
+ * if there is no pad or if there is more than one pad in this direction
+ */
+
+static GstPad *
+get_pad_by_direction (GstElement * element, GstPadDirection direction)
+{
+ GstIterator *iter = gst_element_iterate_pads (element);
+ GstPad *pad = NULL;
+ GstPad *selected_pad = NULL;
+ gboolean done;
+
+ if (!iter)
+ return NULL;
+
+ done = FALSE;
+ while (!done) {
+ switch (gst_iterator_next (iter, (gpointer) & pad)) {
+ case GST_ITERATOR_OK:
+ if (gst_pad_get_direction (pad) == direction) {
+ /* We check if there is more than one pad in this direction,
+ * if there is, we return NULL so that the element is refused
+ */
+ if (selected_pad) {
+ done = TRUE;
+ gst_object_unref (selected_pad);
+ selected_pad = NULL;
+ } else {
+ selected_pad = pad;
+ }
+ } else {
+ gst_object_unref (pad);
+ }
+ break;
+ case GST_ITERATOR_RESYNC:
+ if (selected_pad) {
+ gst_object_unref (selected_pad);
+ selected_pad = NULL;
+ }
+ gst_iterator_resync (iter);
+ break;
+ case GST_ITERATOR_ERROR:
+ GST_ERROR ("Error iterating pads of element %s",
+ GST_OBJECT_NAME (element));
+ gst_object_unref (selected_pad);
+ selected_pad = NULL;
+ done = TRUE;
+ break;
+ case GST_ITERATOR_DONE:
+ done = TRUE;
+ break;
+ }
+ }
+ gst_iterator_free (iter);
+
+ if (!selected_pad)
+ GST_ERROR ("Did not find pad of direction %d in %s",
+ direction, GST_OBJECT_NAME (element));
+
+ return selected_pad;
+}
+
+static GstElement *
+gst_auto_convert_get_subelement (GstAutoConvert * autoconvert,
+ gboolean query_only)
+{
+ GstElement *element = NULL;
+ gboolean initial_identity;
+
+ GST_AUTOCONVERT_LOCK (autoconvert);
+ if (autoconvert->current_subelement)
+ element = gst_object_ref (autoconvert->current_subelement);
+ initial_identity = autoconvert->initial_identity;
+ GST_AUTOCONVERT_UNLOCK (autoconvert);
+
+ if (G_UNLIKELY (!query_only && element == NULL && initial_identity)) {
+ /* No current sub-element - create an identity and install it */
+ GstElementFactory *identity_feature;
+ GstElement *identity;
+
+ GST_INFO_OBJECT (autoconvert,
+ "No existing child element - instantiating identity");
+ /* if the identity feature doesn't exist - something is very wrong */
+ identity_feature =
+ GST_ELEMENT_FACTORY_CAST (gst_default_registry_find_feature ("identity",
+ GST_TYPE_ELEMENT_FACTORY));
+ identity =
+ gst_auto_convert_get_or_make_element_from_factory (autoconvert,
+ identity_feature);
+ if (identity
+ && gst_auto_convert_activate_element (autoconvert, identity, NULL)) {
+ GST_AUTOCONVERT_LOCK (autoconvert);
+ if (autoconvert->current_subelement)
+ element = gst_object_ref (autoconvert->current_subelement);
+ GST_AUTOCONVERT_UNLOCK (autoconvert);
+ }
+ }
+
+ return element;
+}
+
+static GstPad *
+gst_auto_convert_get_internal_sinkpad (GstAutoConvert * autoconvert)
+{
+ GstPad *pad = NULL;
+
+ GST_AUTOCONVERT_LOCK (autoconvert);
+ if (autoconvert->current_internal_sinkpad)
+ pad = gst_object_ref (autoconvert->current_internal_sinkpad);
+ GST_AUTOCONVERT_UNLOCK (autoconvert);
+
+ return pad;
+}
+
+static GstPad *
+gst_auto_convert_get_internal_srcpad (GstAutoConvert * autoconvert)
+{
+ GstPad *pad = NULL;
+
+ GST_AUTOCONVERT_LOCK (autoconvert);
+ if (autoconvert->current_internal_srcpad)
+ pad = gst_object_ref (autoconvert->current_internal_srcpad);
+ GST_AUTOCONVERT_UNLOCK (autoconvert);
+
+ return pad;
+}
+
+/*
+ * This function creates and adds an element to the GstAutoConvert
+ * it then creates the internal pads and links them
+ *
+ */
+
+static GstElement *
+gst_auto_convert_add_element (GstAutoConvert * autoconvert,
+ GstElementFactory * factory)
+{
+ GstElement *element = NULL;
+ GstPad *internal_sinkpad = NULL;
+ GstPad *internal_srcpad = NULL;
+ GstPad *sinkpad;
+ GstPad *srcpad;
+ GstPadLinkReturn padlinkret;
+
+ GST_DEBUG_OBJECT (autoconvert, "Adding element %s to the autoconvert bin",
+ gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)));
+
+ element = gst_element_factory_create (factory, NULL);
+ if (!element)
+ return NULL;
+
+ if (!gst_bin_add (GST_BIN (autoconvert), element)) {
+ GST_ERROR_OBJECT (autoconvert, "Could not add element %s to the bin",
+ GST_OBJECT_NAME (element));
+ gst_object_unref (element);
+ return NULL;
+ }
+
+ srcpad = get_pad_by_direction (element, GST_PAD_SRC);
+ if (!srcpad) {
+ GST_ERROR_OBJECT (autoconvert, "Could not find source in %s",
+ GST_OBJECT_NAME (element));
+ goto error;
+ }
+
+ sinkpad = get_pad_by_direction (element, GST_PAD_SINK);
+ if (!sinkpad) {
+ GST_ERROR_OBJECT (autoconvert, "Could not find sink in %s",
+ GST_OBJECT_NAME (element));
+ goto error;
+ }
+
+ internal_sinkpad =
+ gst_pad_new_from_static_template (&sink_internal_template,
+ "sink_internal");
+ internal_srcpad =
+ gst_pad_new_from_static_template (&src_internal_template, "src_internal");
+
+ if (!internal_sinkpad || !internal_srcpad) {
+ GST_ERROR_OBJECT (autoconvert, "Could not create internal pads");
+ goto error;
+ }
+
+ g_object_weak_ref (G_OBJECT (element), (GWeakNotify) gst_object_unref,
+ internal_sinkpad);
+ g_object_weak_ref (G_OBJECT (element), (GWeakNotify) gst_object_unref,
+ internal_srcpad);
+
+ gst_pad_set_active (internal_sinkpad, TRUE);
+ gst_pad_set_active (internal_srcpad, TRUE);
+
+ g_object_set_qdata (G_OBJECT (internal_srcpad), parent_quark, autoconvert);
+ g_object_set_qdata (G_OBJECT (internal_sinkpad), parent_quark, autoconvert);
+
+ gst_pad_set_chain_function (internal_sinkpad,
+ GST_DEBUG_FUNCPTR (gst_auto_convert_internal_sink_chain));
+ gst_pad_set_event_function (internal_sinkpad,
+ GST_DEBUG_FUNCPTR (gst_auto_convert_internal_sink_event));
+ gst_pad_set_query_function (internal_sinkpad,
+ GST_DEBUG_FUNCPTR (gst_auto_convert_internal_sink_query));
+ gst_pad_set_query_type_function (internal_sinkpad,
+ GST_DEBUG_FUNCPTR (gst_auto_convert_internal_sink_query_type));
+ gst_pad_set_getcaps_function (internal_sinkpad,
+ GST_DEBUG_FUNCPTR (gst_auto_convert_internal_sink_getcaps));
+ gst_pad_set_bufferalloc_function (internal_sinkpad,
+ GST_DEBUG_FUNCPTR (gst_auto_convert_internal_sink_buffer_alloc));
+ gst_pad_set_fixatecaps_function (internal_sinkpad,
+ GST_DEBUG_FUNCPTR (gst_auto_convert_internal_sink_fixatecaps));
+
+ gst_pad_set_event_function (internal_srcpad,
+ GST_DEBUG_FUNCPTR (gst_auto_convert_internal_src_event));
+ gst_pad_set_query_function (internal_srcpad,
+ GST_DEBUG_FUNCPTR (gst_auto_convert_internal_src_query));
+ gst_pad_set_query_type_function (internal_srcpad,
+ GST_DEBUG_FUNCPTR (gst_auto_convert_internal_src_query_type));
+
+ padlinkret = gst_pad_link (internal_srcpad, sinkpad);
+ if (GST_PAD_LINK_FAILED (padlinkret)) {
+ GST_WARNING_OBJECT (autoconvert, "Could not links pad %s:%s to %s:%s"
+ " for reason %d",
+ GST_DEBUG_PAD_NAME (internal_srcpad),
+ GST_DEBUG_PAD_NAME (sinkpad), padlinkret);
+ goto error;
+ }
+
+ padlinkret = gst_pad_link (srcpad, internal_sinkpad);
+ if (GST_PAD_LINK_FAILED (padlinkret)) {
+ GST_WARNING_OBJECT (autoconvert, "Could not links pad %s:%s to %s:%s"
+ " for reason %d",
+ GST_DEBUG_PAD_NAME (internal_srcpad),
+ GST_DEBUG_PAD_NAME (sinkpad), padlinkret);
+ goto error;
+ }
+
+ g_object_set_qdata (G_OBJECT (element),
+ internal_srcpad_quark, internal_srcpad);
+ g_object_set_qdata (G_OBJECT (element),
+ internal_sinkpad_quark, internal_sinkpad);
+
+ /* Iffy */
+ gst_element_sync_state_with_parent (element);
+
+ /* Increment the reference count we will return to the caller */
+ gst_object_ref (element);
+
+ return element;
+
+error:
+ gst_bin_remove (GST_BIN (autoconvert), element);
+
+ return NULL;
+}
+
+static GstElement *
+gst_auto_convert_get_or_make_element_from_factory (GstAutoConvert * autoconvert,
+ GstElementFactory * factory)
+{
+ GstElement *element = NULL;
+ GstElementFactory *loaded_factory =
+ GST_ELEMENT_FACTORY (gst_plugin_feature_load (GST_PLUGIN_FEATURE
+ (factory)));
+
+ if (!loaded_factory)
+ return NULL;
+
+ element = gst_auto_convert_get_element_by_type (autoconvert,
+ gst_element_factory_get_element_type (loaded_factory));
+
+ if (!element) {
+ element = gst_auto_convert_add_element (autoconvert, loaded_factory);
+ }
+
+ gst_object_unref (loaded_factory);
+
+ return element;
+}
+
+/*
+ * This function checks if there is one and only one pad template on the
+ * factory that can accept the given caps. If there is one and only one,
+ * it returns TRUE, otherwise, its FALSE
+ */
+
+static gboolean
+factory_can_intersect (GstAutoConvert * autoconvert,
+ GstElementFactory * factory, GstPadDirection direction, GstCaps * caps)
+{
+ GList *templates;
+ gint has_direction = FALSE;
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail (factory != NULL, FALSE);
+ g_return_val_if_fail (caps != NULL, FALSE);
+
+ templates = factory->staticpadtemplates;
+
+ while (templates) {
+ GstStaticPadTemplate *template = (GstStaticPadTemplate *) templates->data;
+
+ if (template->direction == direction) {
+ GstCaps *intersect = NULL;
+ GstCaps *tmpl_caps = NULL;
+
+ /* If there is more than one pad in this direction, we return FALSE
+ * Only transform elements (with one sink and one source pad)
+ * are accepted
+ */
+ if (has_direction) {
+ GST_DEBUG_OBJECT (autoconvert, "Factory %" GST_PTR_FORMAT
+ " has more than one static template with dir %d",
+ template, direction);
+ return FALSE;
+ }
+ has_direction = TRUE;
+
+ tmpl_caps = gst_static_caps_get (&template->static_caps);
+ intersect = gst_caps_intersect (tmpl_caps, caps);
+ GST_DEBUG_OBJECT (autoconvert, "Intersection of factory %" GST_PTR_FORMAT
+ " static caps %" GST_PTR_FORMAT " and caps %" GST_PTR_FORMAT
+ " is %" GST_PTR_FORMAT, factory, tmpl_caps, caps, intersect);
+ gst_caps_unref (tmpl_caps);
+
+ if (intersect) {
+ if (!gst_caps_is_empty (intersect))
+ ret = TRUE;
+
+ gst_caps_unref (intersect);
+ }
+ }
+ templates = g_list_next (templates);
+ }
+
+ return ret;
+}
+
+static gboolean
+gst_auto_convert_activate_element (GstAutoConvert * autoconvert,
+ GstElement * element, GstCaps * caps)
+{
+ GstPad *internal_srcpad = g_object_get_qdata (G_OBJECT (element),
+ internal_srcpad_quark);
+ GstPad *internal_sinkpad = g_object_get_qdata (G_OBJECT (element),
+ internal_sinkpad_quark);
+
+ if (caps) {
+ /* check if the element can really accept said caps */
+ if (!gst_pad_peer_accept_caps (internal_srcpad, caps)) {
+ GST_DEBUG_OBJECT (autoconvert, "Could not set %s:%s to %"
+ GST_PTR_FORMAT, GST_DEBUG_PAD_NAME (internal_srcpad), caps);
+ return FALSE;
+ }
+ }
+
+ gst_pad_set_fixatecaps_function (autoconvert->sinkpad,
+ gst_auto_convert_sink_fixatecaps);
+ GST_AUTOCONVERT_LOCK (autoconvert);
+ autoconvert->current_subelement = element;
+ autoconvert->current_internal_srcpad = internal_srcpad;
+ autoconvert->current_internal_sinkpad = internal_sinkpad;
+ GST_AUTOCONVERT_UNLOCK (autoconvert);
+
+ GST_INFO_OBJECT (autoconvert,
+ "Selected element %s",
+ GST_OBJECT_NAME (GST_OBJECT (autoconvert->current_subelement)));
+
+ /* Send new-segment event if we have one */
+ if (autoconvert->sink_segment.format != GST_FORMAT_UNDEFINED) {
+ GstEvent *event;
+ GstSegment *seg = &autoconvert->sink_segment;
+ event = gst_event_new_new_segment_full (TRUE,
+ seg->rate, seg->applied_rate, seg->format, seg->start,
+ seg->stop, seg->time);
+
+ autoconvert->drop_newseg = TRUE;
+ gst_pad_push_event (internal_srcpad, event);
+ autoconvert->drop_newseg = FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * If there is already an internal element, it will try to call set_caps on it
+ *
+ * If there isn't an internal element or if the set_caps() on the internal
+ * element failed, it will try to find another element where it would succeed
+ * and will change the internal element.
+ */
+
+static gboolean
+gst_auto_convert_sink_setcaps (GstPad * pad, GstCaps * caps)
+{
+ GList *elem;
+ GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad));
+ GstElement *subelement;
+ GstCaps *other_caps = NULL;
+ GstPad *peer;
+ GList *factories;
+
+ g_return_val_if_fail (autoconvert != NULL, FALSE);
+
+ subelement = gst_auto_convert_get_subelement (autoconvert, TRUE);
+ if (subelement) {
+ if (gst_pad_peer_accept_caps (autoconvert->current_internal_srcpad, caps)) {
+ /* If we can set the new caps on the current element,
+ * then we just get out
+ */
+ GST_DEBUG_OBJECT (autoconvert, "Could set %s:%s to %" GST_PTR_FORMAT,
+ GST_DEBUG_PAD_NAME (autoconvert->current_internal_srcpad), caps);
+ gst_object_unref (subelement);
+ goto get_out;
+ } else {
+ /* If the current element doesn't work,
+ * then we remove the current element before finding a new one.
+ * By unsetting the fixatecaps function, we go back to the default one
+ */
+ gst_pad_set_fixatecaps_function (autoconvert->sinkpad, NULL);
+ GST_AUTOCONVERT_LOCK (autoconvert);
+ if (autoconvert->current_subelement == subelement) {
+ gst_object_unref (autoconvert->current_subelement);
+ autoconvert->current_subelement = NULL;
+ autoconvert->current_internal_srcpad = NULL;
+ autoconvert->current_internal_sinkpad = NULL;
+ }
+ GST_AUTOCONVERT_UNLOCK (autoconvert);
+ gst_object_unref (subelement);
+ }
+ }
+
+ peer = gst_pad_get_peer (autoconvert->srcpad);
+ if (peer) {
+ other_caps = gst_pad_get_caps (peer);
+ gst_object_unref (peer);
+ }
+
+ GST_AUTOCONVERT_LOCK (autoconvert);
+ factories = autoconvert->factories;
+ GST_AUTOCONVERT_UNLOCK (autoconvert);
+
+ if (!factories)
+ factories = gst_auto_convert_load_factories (autoconvert);
+
+ for (elem = factories; elem; elem = g_list_next (elem)) {
+ GstElementFactory *factory = GST_ELEMENT_FACTORY (elem->data);
+ GstElement *element;
+
+ /* Lets first check if according to the static pad templates on the factory
+ * these caps have any chance of success
+ */
+ if (!factory_can_intersect (autoconvert, factory, GST_PAD_SINK, caps)) {
+ GST_LOG_OBJECT (autoconvert, "Factory %s does not accept sink caps %"
+ GST_PTR_FORMAT,
+ gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)), caps);
+ continue;
+ }
+ if (other_caps != NULL) {
+ if (!factory_can_intersect (autoconvert, factory, GST_PAD_SRC,
+ other_caps)) {
+ GST_LOG_OBJECT (autoconvert,
+ "Factory %s does not accept src caps %" GST_PTR_FORMAT,
+ gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)),
+ other_caps);
+ continue;
+ }
+ }
+
+ /* The element had a chance of success, lets make it */
+ element =
+ gst_auto_convert_get_or_make_element_from_factory (autoconvert,
+ factory);
+ if (!element)
+ continue;
+
+ /* And make it the current child */
+ if (gst_auto_convert_activate_element (autoconvert, element, caps))
+ break;
+ }
+
+get_out:
+ if (other_caps)
+ gst_caps_unref (other_caps);
+ gst_object_unref (autoconvert);
+
+ if (autoconvert->current_subelement) {
+ return TRUE;
+ } else {
+ GST_WARNING_OBJECT (autoconvert,
+ "Could not find a matching element for caps");
+ return FALSE;
+ }
+}
+
+/*
+ * This function filters the pad pad templates, taking only transform element
+ * (with one sink and one src pad)
+ */
+
+static gboolean
+gst_auto_convert_default_filter_func (GstPluginFeature * feature,
+ gpointer user_data)
+{
+ GstElementFactory *factory = NULL;
+ const GList *static_pad_templates, *tmp;
+ GstStaticPadTemplate *src = NULL, *sink = NULL;
+
+ if (!GST_IS_ELEMENT_FACTORY (feature))
+ return FALSE;
+
+ factory = GST_ELEMENT_FACTORY (feature);
+
+ static_pad_templates = gst_element_factory_get_static_pad_templates (factory);
+
+ for (tmp = static_pad_templates; tmp; tmp = g_list_next (tmp)) {
+ GstStaticPadTemplate *template = tmp->data;
+ GstCaps *caps;
+
+ if (template->presence == GST_PAD_SOMETIMES)
+ return FALSE;
+
+ if (template->presence != GST_PAD_ALWAYS)
+ continue;
+
+ switch (template->direction) {
+ case GST_PAD_SRC:
+ if (src)
+ return FALSE;
+ src = template;
+ break;
+ case GST_PAD_SINK:
+ if (sink)
+ return FALSE;
+ sink = template;
+ break;
+ default:
+ return FALSE;
+ }
+
+ caps = gst_static_pad_template_get_caps (template);
+
+ if (gst_caps_is_any (caps) || gst_caps_is_empty (caps))
+ return FALSE;
+ }
+
+ if (!src || !sink)
+ return FALSE;
+
+ return TRUE;
+}
+
+/* function used to sort element features
+ * Copy-pasted from decodebin */
+static gint
+compare_ranks (GstPluginFeature * f1, GstPluginFeature * f2)
+{
+ gint diff;
+ const gchar *rname1, *rname2;
+
+ diff = gst_plugin_feature_get_rank (f2) - gst_plugin_feature_get_rank (f1);
+ if (diff != 0)
+ return diff;
+
+ rname1 = gst_plugin_feature_get_name (f1);
+ rname2 = gst_plugin_feature_get_name (f2);
+
+ diff = strcmp (rname2, rname1);
+
+ return diff;
+}
+
+static GList *
+gst_auto_convert_load_factories (GstAutoConvert * autoconvert)
+{
+ GList *all_factories;
+ GList *out_factories;
+
+ all_factories =
+ gst_default_registry_feature_filter (gst_auto_convert_default_filter_func,
+ FALSE, NULL);
+
+ all_factories = g_list_sort (all_factories, (GCompareFunc) compare_ranks);
+
+ g_assert (all_factories);
+
+ GST_AUTOCONVERT_LOCK (autoconvert);
+ if (autoconvert->factories == NULL) {
+ autoconvert->factories = all_factories;
+ all_factories = NULL;
+ }
+ out_factories = autoconvert->factories;
+ GST_AUTOCONVERT_UNLOCK (autoconvert);
+
+ if (all_factories) {
+ /* In this case, someone set the property while we were looking! */
+ gst_plugin_feature_list_free (all_factories);
+ }
+
+ return out_factories;
+}
+
+/* In this case, we should almost always have an internal element, because
+ * set_caps() should have been called first
+ */
+
+static GstFlowReturn
+gst_auto_convert_sink_chain (GstPad * pad, GstBuffer * buffer)
+{
+ GstFlowReturn ret = GST_FLOW_NOT_NEGOTIATED;
+ GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad));
+ GstPad *internal_srcpad;
+
+ internal_srcpad = gst_auto_convert_get_internal_srcpad (autoconvert);
+ if (internal_srcpad) {
+ GList *events = NULL;
+ GList *l;
+
+ GST_AUTOCONVERT_LOCK (autoconvert);
+ if (autoconvert->cached_events) {
+ events = g_list_reverse (autoconvert->cached_events);
+ autoconvert->cached_events = NULL;
+ }
+ GST_AUTOCONVERT_UNLOCK (autoconvert);
+
+ if (events) {
+ GST_DEBUG_OBJECT (autoconvert, "Sending cached events downstream");
+ for (l = events; l; l = l->next)
+ gst_pad_push_event (internal_srcpad, l->data);
+ g_list_free (events);
+ }
+
+ ret = gst_pad_push (internal_srcpad, buffer);
+ gst_object_unref (internal_srcpad);
+ if (GST_FLOW_IS_FATAL (ret)) {
+ GstElement *child = gst_auto_convert_get_subelement (autoconvert, TRUE);
+ GST_DEBUG_OBJECT (autoconvert,
+ "Child element %" GST_PTR_FORMAT "returned flow %s", child,
+ gst_flow_get_name (ret));
+ if (child)
+ gst_object_unref (child);
+ }
+ } else {
+ GST_ERROR_OBJECT (autoconvert, "Got buffer without an negotiated element,"
+ " returning not-negotiated");
+ }
+
+ gst_object_unref (autoconvert);
+
+ return ret;
+}
+
+static gboolean
+gst_auto_convert_sink_event (GstPad * pad, GstEvent * event)
+{
+ gboolean ret = TRUE;
+ GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad));
+ GstPad *internal_srcpad;
+
+ if (GST_EVENT_TYPE (event) == GST_EVENT_NEWSEGMENT) {
+ GstFormat format;
+ gdouble rate, arate;
+ gint64 start, stop, time;
+ gboolean update;
+
+ gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format,
+ &start, &stop, &time);
+
+ GST_DEBUG_OBJECT (autoconvert,
+ "newsegment: update %d, rate %g, arate %g, start %" GST_TIME_FORMAT
+ ", stop %" GST_TIME_FORMAT ", time %" GST_TIME_FORMAT,
+ update, rate, arate, GST_TIME_ARGS (start), GST_TIME_ARGS (stop),
+ GST_TIME_ARGS (time));
+
+ /* Store the values for feeding to sub-elements */
+ gst_segment_set_newsegment_full (&autoconvert->sink_segment, update,
+ rate, arate, format, start, stop, time);
+ }
+
+ internal_srcpad = gst_auto_convert_get_internal_srcpad (autoconvert);
+ if (internal_srcpad == NULL) {
+ /* Query the subelement - triggers creation of an identity if necessary */
+ GstElement *subelement =
+ gst_auto_convert_get_subelement (autoconvert, FALSE);
+ if (subelement)
+ gst_object_unref (subelement);
+ internal_srcpad = gst_auto_convert_get_internal_srcpad (autoconvert);
+ }
+
+ if (internal_srcpad) {
+ ret = gst_pad_push_event (internal_srcpad, event);
+ gst_object_unref (internal_srcpad);
+ } else {
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_FLUSH_STOP:
+ GST_AUTOCONVERT_LOCK (autoconvert);
+ g_list_foreach (autoconvert->cached_events,
+ (GFunc) gst_mini_object_unref, NULL);
+ g_list_free (autoconvert->cached_events);
+ autoconvert->cached_events = NULL;
+ GST_AUTOCONVERT_UNLOCK (autoconvert);
+ /* fall through */
+ case GST_EVENT_FLUSH_START:
+ ret = gst_pad_push_event (autoconvert->srcpad, event);
+ break;
+ default:
+ GST_AUTOCONVERT_LOCK (autoconvert);
+ autoconvert->cached_events =
+ g_list_prepend (autoconvert->cached_events, event);
+ ret = TRUE;
+ GST_AUTOCONVERT_UNLOCK (autoconvert);
+ break;
+ }
+ }
+
+ gst_object_unref (autoconvert);
+
+ return ret;
+}
+
+/* TODO Properly test that this code works well for queries */
+static gboolean
+gst_auto_convert_sink_query (GstPad * pad, GstQuery * query)
+{
+ gboolean ret = TRUE;
+ GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad));
+ GstElement *subelement;
+
+ subelement = gst_auto_convert_get_subelement (autoconvert, TRUE);
+ if (subelement) {
+ GstPad *sub_sinkpad = get_pad_by_direction (subelement, GST_PAD_SINK);
+
+ ret = gst_pad_query (sub_sinkpad, query);
+
+ gst_object_unref (sub_sinkpad);
+ gst_object_unref (subelement);
+ } else {
+ GST_WARNING_OBJECT (autoconvert, "Got query while no element was selected,"
+ "letting through");
+ ret = gst_pad_peer_query (autoconvert->srcpad, query);
+ }
+
+ gst_object_unref (autoconvert);
+
+ return ret;
+}
+
+/* TODO Test that this code works properly for queries */
+static const GstQueryType *
+gst_auto_convert_sink_query_type (GstPad * pad)
+{
+ const GstQueryType *ret = NULL;
+ GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad));
+ GstElement *subelement;
+
+ subelement = gst_auto_convert_get_subelement (autoconvert, TRUE);
+ if (subelement) {
+ GstPad *sub_sinkpad = get_pad_by_direction (subelement, GST_PAD_SINK);
+
+ ret = gst_pad_get_query_types (sub_sinkpad);
+
+ gst_object_unref (sub_sinkpad);
+ gst_object_unref (subelement);
+ } else {
+ ret = gst_pad_get_query_types_default (pad);
+ }
+
+ gst_object_unref (autoconvert);
+
+ return ret;
+}
+
+static void
+gst_auto_convert_sink_fixatecaps (GstPad * pad, GstCaps * caps)
+{
+ GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad));
+ GstElement *subelement;
+
+ subelement = gst_auto_convert_get_subelement (autoconvert, TRUE);
+ if (subelement) {
+ GstPad *sinkpad = get_pad_by_direction (subelement, GST_PAD_SINK);
+ gst_pad_fixate_caps (sinkpad, caps);
+ gst_object_unref (sinkpad);
+ gst_object_unref (subelement);
+ }
+}
+
+/**
+ * gst_auto_convert_sink_getcaps:
+ * @pad: the sink #GstPad
+ *
+ * This function returns the union of the caps of all the possible element
+ * factories, based on the static pad templates.
+ * It also checks does a getcaps on the downstream element and ignores all
+ * factories whose static caps can not satisfy it.
+ *
+ * It does not try to use each elements getcaps() function
+ */
+
+static GstCaps *
+gst_auto_convert_sink_getcaps (GstPad * pad)
+{
+ GstCaps *caps = NULL, *other_caps = NULL;
+ GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad));
+ GstPad *peer;
+ GList *elem, *factories;
+
+ caps = gst_caps_new_empty ();
+
+ peer = gst_pad_get_peer (autoconvert->srcpad);
+ if (peer) {
+ other_caps = gst_pad_get_caps (peer);
+ gst_object_unref (peer);
+ }
+
+ GST_DEBUG_OBJECT (autoconvert,
+ "Lets find all the element that can fit here with src caps %"
+ GST_PTR_FORMAT, other_caps);
+
+ if (other_caps && gst_caps_is_empty (other_caps)) {
+ goto out;
+ }
+
+ GST_AUTOCONVERT_LOCK (autoconvert);
+ factories = autoconvert->factories;
+ GST_AUTOCONVERT_UNLOCK (autoconvert);
+
+ if (!factories)
+ factories = gst_auto_convert_load_factories (autoconvert);
+
+ for (elem = factories; elem; elem = g_list_next (elem)) {
+ GstElementFactory *factory = GST_ELEMENT_FACTORY (elem->data);
+ GstElement *element = NULL;
+ GstCaps *element_caps;
+ GstPad *internal_srcpad = NULL;
+
+ if (other_caps != NULL) {
+ if (!factory_can_intersect (autoconvert, factory, GST_PAD_SRC,
+ other_caps)) {
+ GST_LOG_OBJECT (autoconvert,
+ "Factory %s does not accept src caps %" GST_PTR_FORMAT,
+ gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)),
+ other_caps);
+ continue;
+ }
+ }
+
+ if (other_caps) {
+
+ element =
+ gst_auto_convert_get_or_make_element_from_factory (autoconvert,
+ factory);
+ if (!element)
+ continue;
+
+ internal_srcpad = g_object_get_qdata (G_OBJECT (element),
+ internal_srcpad_quark);
+
+ element_caps = gst_pad_peer_get_caps (internal_srcpad);
+
+ if (element_caps) {
+ if (!gst_caps_is_any (element_caps) &&
+ !gst_caps_is_empty (element_caps)) {
+ GstCaps *tmpcaps = NULL;
+
+ tmpcaps = gst_caps_union (caps, element_caps);
+ gst_caps_unref (caps);
+ caps = tmpcaps;
+
+ }
+ gst_caps_unref (element_caps);
+ }
+
+ gst_object_unref (element);
+ } else {
+ const GList *tmp;
+
+ for (tmp = gst_element_factory_get_static_pad_templates (factory);
+ tmp; tmp = g_list_next (tmp)) {
+ GstStaticPadTemplate *template = tmp->data;
+ GstCaps *static_caps = gst_static_pad_template_get_caps (template);
+
+ if (static_caps && !gst_caps_is_any (static_caps) &&
+ !gst_caps_is_empty (static_caps)) {
+ GstCaps *tmpcaps = NULL;
+
+ tmpcaps = gst_caps_union (caps, static_caps);
+ gst_caps_unref (caps);
+ caps = tmpcaps;
+ }
+ }
+ }
+ }
+
+ GST_DEBUG_OBJECT (autoconvert, "Returning unioned caps %" GST_PTR_FORMAT,
+ caps);
+
+out:
+ gst_object_unref (autoconvert);
+
+ if (other_caps)
+ gst_caps_unref (other_caps);
+
+ return caps;
+}
+
+
+static GstFlowReturn
+gst_auto_convert_sink_buffer_alloc (GstPad * pad, guint64 offset,
+ guint size, GstCaps * caps, GstBuffer ** buf)
+{
+ GstFlowReturn ret = GST_FLOW_OK;
+ GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad));
+ GstPad *internal_srcpad;
+
+ g_return_val_if_fail (autoconvert != NULL, GST_FLOW_ERROR);
+
+ internal_srcpad = gst_auto_convert_get_internal_srcpad (autoconvert);
+ if (internal_srcpad) {
+ ret = gst_pad_alloc_buffer (internal_srcpad, offset, size, caps, buf);
+ gst_object_unref (internal_srcpad);
+ } else
+ /* Fallback to the default */
+ *buf = NULL;
+
+ gst_object_unref (autoconvert);
+
+ return ret;
+}
+
+static gboolean
+gst_auto_convert_src_event (GstPad * pad, GstEvent * event)
+{
+ gboolean ret = TRUE;
+ GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad));
+ GstPad *internal_sinkpad;
+
+ internal_sinkpad = gst_auto_convert_get_internal_sinkpad (autoconvert);
+ if (internal_sinkpad) {
+ ret = gst_pad_push_event (internal_sinkpad, event);
+ gst_object_unref (internal_sinkpad);
+ } else {
+ GST_WARNING_OBJECT (autoconvert,
+ "Got upstream event while no element was selected," "forwarding.");
+ ret = gst_pad_push_event (autoconvert->sinkpad, event);
+ }
+
+ gst_object_unref (autoconvert);
+
+ return ret;
+}
+
+/* TODO Properly test that this code works well for queries */
+static gboolean
+gst_auto_convert_src_query (GstPad * pad, GstQuery * query)
+{
+ gboolean ret = TRUE;
+ GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad));
+ GstElement *subelement;
+
+ subelement = gst_auto_convert_get_subelement (autoconvert, TRUE);
+ if (subelement) {
+ GstPad *sub_srcpad = get_pad_by_direction (subelement, GST_PAD_SRC);
+
+ ret = gst_pad_query (sub_srcpad, query);
+
+ gst_object_unref (sub_srcpad);
+ gst_object_unref (subelement);
+ } else {
+ GST_WARNING_OBJECT (autoconvert,
+ "Got upstream query while no element was selected," "forwarding.");
+ ret = gst_pad_peer_query (autoconvert->sinkpad, query);
+ }
+
+ gst_object_unref (autoconvert);
+
+ return ret;
+}
+
+/* TODO Properly test that this code works well for queries */
+static const GstQueryType *
+gst_auto_convert_src_query_type (GstPad * pad)
+{
+ const GstQueryType *ret = NULL;
+ GstAutoConvert *autoconvert = GST_AUTO_CONVERT (gst_pad_get_parent (pad));
+ GstElement *subelement;
+
+ subelement = gst_auto_convert_get_subelement (autoconvert, TRUE);
+ if (subelement) {
+ GstPad *sub_srcpad = get_pad_by_direction (subelement, GST_PAD_SRC);
+
+ ret = gst_pad_get_query_types (sub_srcpad);
+
+ gst_object_unref (sub_srcpad);
+ gst_object_unref (subelement);
+ } else {
+ ret = gst_pad_get_query_types_default (pad);
+ }
+
+ gst_object_unref (autoconvert);
+
+ return ret;
+}
+
+static GstFlowReturn
+gst_auto_convert_internal_sink_chain (GstPad * pad, GstBuffer * buffer)
+{
+ GstAutoConvert *autoconvert =
+ GST_AUTO_CONVERT (g_object_get_qdata (G_OBJECT (pad),
+ parent_quark));
+
+ return gst_pad_push (autoconvert->srcpad, buffer);
+}
+
+static gboolean
+gst_auto_convert_internal_sink_event (GstPad * pad, GstEvent * event)
+{
+ GstAutoConvert *autoconvert =
+ GST_AUTO_CONVERT (g_object_get_qdata (G_OBJECT (pad),
+ parent_quark));
+
+ if (GST_EVENT_TYPE (event) == GST_EVENT_NEWSEGMENT) {
+ if (autoconvert->drop_newseg) {
+ GST_DEBUG_OBJECT (autoconvert, "Dropping primer newsegment event");
+ gst_event_unref (event);
+ return TRUE;
+ }
+ }
+
+ return gst_pad_push_event (autoconvert->srcpad, event);
+}
+
+static gboolean
+gst_auto_convert_internal_sink_query (GstPad * pad, GstQuery * query)
+{
+ GstAutoConvert *autoconvert =
+ GST_AUTO_CONVERT (g_object_get_qdata (G_OBJECT (pad),
+ parent_quark));
+ GstPad *peerpad = gst_pad_get_peer (autoconvert->srcpad);
+ gboolean ret = FALSE;
+
+ if (peerpad) {
+ ret = gst_pad_query (peerpad, query);
+ gst_object_unref (peerpad);
+ }
+
+ return ret;
+}
+
+static const GstQueryType *
+gst_auto_convert_internal_sink_query_type (GstPad * pad)
+{
+ GstAutoConvert *autoconvert =
+ GST_AUTO_CONVERT (g_object_get_qdata (G_OBJECT (pad),
+ parent_quark));
+ GstPad *peerpad = gst_pad_get_peer (autoconvert->srcpad);
+ const GstQueryType *ret = NULL;
+
+ if (peerpad) {
+ ret = gst_pad_get_query_types (peerpad);
+ gst_object_unref (peerpad);
+ } else
+ ret = gst_pad_get_query_types_default (pad);
+
+ return ret;
+}
+
+static GstCaps *
+gst_auto_convert_internal_sink_getcaps (GstPad * pad)
+{
+ GstAutoConvert *autoconvert =
+ GST_AUTO_CONVERT (g_object_get_qdata (G_OBJECT (pad),
+ parent_quark));
+
+ return gst_pad_peer_get_caps (autoconvert->srcpad);
+}
+
+static void
+gst_auto_convert_internal_sink_fixatecaps (GstPad * pad, GstCaps * caps)
+{
+ GstAutoConvert *autoconvert =
+ GST_AUTO_CONVERT (g_object_get_qdata (G_OBJECT (pad),
+ parent_quark));
+ GstPad *peerpad = gst_pad_get_peer (autoconvert->sinkpad);
+
+ if (peerpad) {
+ gst_pad_fixate_caps (peerpad, caps);
+ gst_object_unref (peerpad);
+ }
+}
+
+static GstFlowReturn
+gst_auto_convert_internal_sink_buffer_alloc (GstPad * pad, guint64 offset,
+ guint size, GstCaps * caps, GstBuffer ** buf)
+{
+ GstAutoConvert *autoconvert =
+ GST_AUTO_CONVERT (g_object_get_qdata (G_OBJECT (pad),
+ parent_quark));
+
+ return gst_pad_alloc_buffer (autoconvert->srcpad, offset, size, caps, buf);
+}
+
+static gboolean
+gst_auto_convert_internal_src_event (GstPad * pad, GstEvent * event)
+{
+ GstAutoConvert *autoconvert =
+ GST_AUTO_CONVERT (g_object_get_qdata (G_OBJECT (pad),
+ parent_quark));
+
+ return gst_pad_push_event (autoconvert->sinkpad, event);
+}
+
+static gboolean
+gst_auto_convert_internal_src_query (GstPad * pad, GstQuery * query)
+{
+ GstAutoConvert *autoconvert =
+ GST_AUTO_CONVERT (g_object_get_qdata (G_OBJECT (pad),
+ parent_quark));
+ GstPad *peerpad = gst_pad_get_peer (autoconvert->sinkpad);
+ gboolean ret = FALSE;
+
+ if (peerpad) {
+ ret = gst_pad_query (peerpad, query);
+ gst_object_unref (peerpad);
+ }
+
+ return ret;
+}
+
+static const GstQueryType *
+gst_auto_convert_internal_src_query_type (GstPad * pad)
+{
+ GstAutoConvert *autoconvert =
+ GST_AUTO_CONVERT (g_object_get_qdata (G_OBJECT (pad),
+ parent_quark));
+ GstPad *peerpad = gst_pad_get_peer (autoconvert->sinkpad);
+ const GstQueryType *ret = NULL;
+
+ if (peerpad) {
+ ret = gst_pad_get_query_types (peerpad);
+ gst_object_unref (peerpad);
+ } else {
+ ret = gst_pad_get_query_types_default (pad);
+ }
+
+ return ret;
+}
+
+gboolean
+gst_auto_convert_plugin_init (GstPlugin * plugin)
+{
+ return gst_element_register (plugin, "autoconvert",
+ GST_RANK_NONE, GST_TYPE_AUTO_CONVERT);
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ "autoconvert",
+ "Selects convertor element based on caps",
+ gst_auto_convert_plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME,
+ GST_PACKAGE_ORIGIN)
diff --git a/farsight/autoconvert/gstautoconvert.h b/farsight/autoconvert/gstautoconvert.h
new file mode 100644
index 0000000..4375255
--- /dev/null
+++ b/farsight/autoconvert/gstautoconvert.h
@@ -0,0 +1,69 @@
+/* GStreamer
+ *
+ * Copyright 2007 Collabora Ltd
+ * @author: Olivier Crete <olivier.crete@collabora.co.uk>
+ * Copyright 2007-2008 Nokia
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+#ifndef __GST_AUTO_CONVERT_H__
+#define __GST_AUTO_CONVERT_H__
+
+#include <gst/gst.h>
+#include <gst/gstbin.h>
+
+G_BEGIN_DECLS
+#define GST_TYPE_AUTO_CONVERT (gst_auto_convert_get_type())
+#define GST_AUTO_CONVERT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_AUTO_CONVERT,GstAutoConvert))
+#define GST_AUTO_CONVERT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_AUTO_CONVERT,GstAutoConvertClass))
+#define GST_IS_AUTO_CONVERT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_AUTO_CONVERT))
+#define GST_IS_AUTO_CONVERT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_AUTO_CONVERT))
+typedef struct _GstAutoConvert GstAutoConvert;
+typedef struct _GstAutoConvertClass GstAutoConvertClass;
+
+struct _GstAutoConvert
+{
+ /*< private >*/
+ GstBin bin; /* we extend GstBin */
+
+ /* Protected by the object lock too */
+ GList *factories;
+
+ GstPad *sinkpad;
+ GstPad *srcpad;
+
+ /* Have to be set all at once
+ * Protected by the object lock */
+ GstElement *current_subelement;
+ GstPad *current_internal_srcpad;
+ GstPad *current_internal_sinkpad;
+
+ GList *cached_events;
+ GstSegment sink_segment;
+ gboolean drop_newseg;
+
+ gboolean initial_identity;
+};
+
+struct _GstAutoConvertClass
+{
+ GstBinClass parent_class;
+};
+
+G_END_DECLS
+#endif /* __GST_AUTO_CONVERT_H__ */
diff --git a/farsight/dtmf/Makefile.am b/farsight/dtmf/Makefile.am
new file mode 100644
index 0000000..049518e
--- /dev/null
+++ b/farsight/dtmf/Makefile.am
@@ -0,0 +1,23 @@
+plugin_LTLIBRARIES = libgstdtmf.la
+
+libgstdtmf_la_SOURCES = gstdtmfsrc.c \
+ gstdtmfdetect.c \
+ gstrtpdtmfsrc.c \
+ gstrtpdtmfdepay.c \
+ tone_detect.c \
+ gstdtmf.c
+
+noinst_HEADERS = gstdtmfsrc.h \
+ gstdtmfdetect.h \
+ gstrtpdtmfsrc.h \
+ gstrtpdtmfdepay.h \
+ gstrtpdtmfcommon.h \
+ tone_detect.h
+
+libgstdtmf_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) \
+ -DEXTERN_BUF -DRTP_SUPPORT
+libgstdtmf_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) -lgstrtp-@GST_MAJORMINOR@ \
+ $(GST_BASE_LIBS) $(GST_LIBS) $(LIBM)
+libgstdtmf_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+libgstdtmf_la_LIBTOOLFLAGS = --tag=disable-static
+
diff --git a/farsight/dtmf/gstdtmf.c b/farsight/dtmf/gstdtmf.c
new file mode 100644
index 0000000..9d5854f
--- /dev/null
+++ b/farsight/dtmf/gstdtmf.c
@@ -0,0 +1,33 @@
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstdtmfdetect.h"
+#include "gstdtmfsrc.h"
+#include "gstrtpdtmfsrc.h"
+#include "gstrtpdtmfdepay.h"
+
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+ if (!gst_dtmf_detect_plugin_init (plugin))
+ return FALSE;
+
+ if (!gst_dtmf_src_plugin_init (plugin))
+ return FALSE;
+
+ if (!gst_rtp_dtmf_src_plugin_init (plugin))
+ return FALSE;
+
+ if (!gst_rtp_dtmf_depay_plugin_init (plugin))
+ return FALSE;
+
+ return TRUE;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ "dtmf", "DTMF plugins",
+ plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
diff --git a/farsight/dtmf/gstdtmfdetect.c b/farsight/dtmf/gstdtmfdetect.c
new file mode 100644
index 0000000..5cb7c2c
--- /dev/null
+++ b/farsight/dtmf/gstdtmfdetect.c
@@ -0,0 +1,308 @@
+/*
+ * GStreamer - DTMF Detection
+ *
+ * Copyright 2009 Nokia Corporation
+ * Copyright 2009 Collabora Ltd,
+ * @author: Olivier Crete <olivier.crete@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+/**
+ * SECTION:element-dtmfdetect
+ * @short_description: Detects DTMF tones
+ *
+ * This element will detect DTMF tones and emit messages
+ *
+ * The message is called "dtmf-event" and has the following fields
+ * <informaltable>
+ * <tgroup cols='4'>
+ * <colspec colname='Name' />
+ * <colspec colname='Type' />
+ * <colspec colname='Possible values' />
+ * <colspec colname='Purpose' />
+ * <thead>
+ * <row>
+ * <entry>Name</entry>
+ * <entry>GType</entry>
+ * <entry>Possible values</entry>
+ * <entry>Purpose</entry>
+ * </row>
+ * </thead>
+ * <tbody>
+ * <row>
+ * <entry>type</entry>
+ * <entry>G_TYPE_INT</entry>
+ * <entry>0-1</entry>
+ * <entry>The application uses this field to specify which of the two methods
+ * specified in RFC 2833 to use. The value should be 0 for tones and 1 for
+ * named events. Tones are specified by their frequencies and events are specied
+ * by their number. This element can only take events as input. Do not confuse
+ * with "method" which specified the output.
+ * </entry>
+ * </row>
+ * <row>
+ * <entry>number</entry>
+ * <entry>G_TYPE_INT</entry>
+ * <entry>0-16</entry>
+ * <entry>The event number.</entry>
+ * </row>
+ * <row>
+ * <entry>method</entry>
+ * <entry>G_TYPE_INT</entry>
+ * <entry>2</entry>
+ * <entry>This field will always been 2 (ie sound) from this element.
+ * </entry>
+ * </row>
+ * </tbody>
+ * </tgroup>
+ * </informaltable>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstdtmfdetect.h"
+
+#include <string.h>
+
+GST_DEBUG_CATEGORY (dtmf_detect_debug);
+#define GST_CAT_DEFAULT (dtmf_detect_debug)
+
+/* elementfactory information */
+static const GstElementDetails gst_dtmf_detect_details =
+GST_ELEMENT_DETAILS ("DTMF detector element",
+ "Detect",
+ "This element detects DTMF tones",
+ "Olivier Crete <olivier.crete@collabora.co.uk>");
+
+
+static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("audio/x-raw-int, "
+ "width = (int) 16, "
+ "depth = (int) 16, "
+ "endianness = (int) " G_STRINGIFY (G_BYTE_ORDER) ", "
+ "signed = (bool) true, rate = (int) [1, MAX], channels = (int) 1"));
+
+static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("audio/x-raw-int, "
+ "width = (int) 16, "
+ "depth = (int) 16, "
+ "endianness = (int) " G_STRINGIFY (G_BYTE_ORDER) ", "
+ "signed = (bool) true, rate = (int) [1, MAX], channels = (int) 1"));
+
+/* signals and args */
+enum
+{
+ /* FILL ME */
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+};
+
+static gboolean gst_dtmf_detect_set_caps (GstBaseTransform * trans,
+ GstCaps * incaps, GstCaps * outcaps);
+static GstFlowReturn gst_dtmf_detect_transform_ip (GstBaseTransform * trans,
+ GstBuffer * buf);
+static gboolean gst_dtmf_detect_event (GstBaseTransform * trans,
+ GstEvent * event);
+
+static void
+_do_init (GType type)
+{
+ GST_DEBUG_CATEGORY_INIT (dtmf_detect_debug, "dtmfdetect", 0, "dtmfdetect");
+}
+
+GST_BOILERPLATE_FULL (GstDtmfDetect, gst_dtmf_detect, GstBaseTransform,
+ GST_TYPE_BASE_TRANSFORM, _do_init);
+
+static void
+gst_dtmf_detect_base_init (gpointer klass)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&srctemplate));
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&sinktemplate));
+
+ gst_element_class_set_details (element_class, &gst_dtmf_detect_details);
+}
+
+static void
+gst_dtmf_detect_class_init (GstDtmfDetectClass * klass)
+{
+ GstBaseTransformClass *gstbasetransform_class;
+
+ gstbasetransform_class = (GstBaseTransformClass *) klass;
+
+ gstbasetransform_class->set_caps =
+ GST_DEBUG_FUNCPTR (gst_dtmf_detect_set_caps);
+ gstbasetransform_class->transform_ip =
+ GST_DEBUG_FUNCPTR (gst_dtmf_detect_transform_ip);
+ gstbasetransform_class->event = GST_DEBUG_FUNCPTR (gst_dtmf_detect_event);
+}
+
+static void
+gst_dtmf_detect_init (GstDtmfDetect * dtmfdetect, GstDtmfDetectClass * klass)
+{
+ gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (dtmfdetect), TRUE);
+ gst_base_transform_set_gap_aware (GST_BASE_TRANSFORM (dtmfdetect), TRUE);
+}
+
+static gboolean
+gst_dtmf_detect_set_caps (GstBaseTransform * trans, GstCaps * incaps,
+ GstCaps * outcaps)
+{
+ GstDtmfDetect *self = GST_DTMF_DETECT (trans);
+ GstStructure *s = gst_caps_get_structure (incaps, 0);
+
+ if (!gst_structure_get_int (s, "rate", &self->rate))
+ return FALSE;
+
+ zap_dtmf_detect_init (&self->dtmf_state);
+
+ return TRUE;
+}
+
+
+static GstFlowReturn
+gst_dtmf_detect_transform_ip (GstBaseTransform * trans, GstBuffer * buf)
+{
+ GstDtmfDetect *self = GST_DTMF_DETECT (trans);
+ int dtmf_count;
+ char dtmfbuf[MAX_DTMF_DIGITS] = "";
+ int i;
+
+ if (GST_BUFFER_IS_DISCONT (buf))
+ zap_dtmf_detect_init (&self->dtmf_state);
+
+
+ zap_dtmf_detect (&self->dtmf_state, (int16_t *) GST_BUFFER_DATA (buf),
+ GST_BUFFER_SIZE (buf) / 2, FALSE);
+
+ dtmf_count = zap_dtmf_get (&self->dtmf_state, dtmfbuf, MAX_DTMF_DIGITS);
+
+ if (dtmf_count)
+ GST_DEBUG_OBJECT (self, "Got %d DTMF events: %s", dtmf_count, dtmfbuf);
+ else
+ GST_LOG_OBJECT (self, "Got no DTMF events");
+
+ for (i = 0; i < dtmf_count; i++) {
+ GstMessage *dtmf_message = NULL;
+ GstStructure *structure;
+ gint dtmf_payload_event;
+
+ GST_DEBUG_OBJECT (self, "Got DTMF event %c", dtmfbuf[i]);
+
+ switch (dtmfbuf[i]) {
+ case '0':
+ dtmf_payload_event = 0;
+ break;
+ case '1':
+ dtmf_payload_event = 1;
+ break;
+ case '2':
+ dtmf_payload_event = 2;
+ break;
+ case '3':
+ dtmf_payload_event = 3;
+ break;
+ case '4':
+ dtmf_payload_event = 4;
+ break;
+ case '5':
+ dtmf_payload_event = 5;
+ break;
+ case '6':
+ dtmf_payload_event = 6;
+ break;
+ case '7':
+ dtmf_payload_event = 7;
+ break;
+ case '8':
+ dtmf_payload_event = 8;
+ break;
+ case '9':
+ dtmf_payload_event = 9;
+ break;
+ case '*':
+ dtmf_payload_event = 10;
+ break;
+ case '#':
+ dtmf_payload_event = 11;
+ break;
+ case 'A':
+ dtmf_payload_event = 12;
+ break;
+ case 'B':
+ dtmf_payload_event = 13;
+ break;
+ case 'C':
+ dtmf_payload_event = 14;
+ break;
+ case 'D':
+ dtmf_payload_event = 15;
+ break;
+ default:
+ continue;
+ }
+
+ structure = gst_structure_new ("dtmf-event",
+ "type", G_TYPE_INT, 1,
+ "number", G_TYPE_INT, dtmf_payload_event,
+ "method", G_TYPE_INT, 2, NULL);
+ dtmf_message = gst_message_new_element (GST_OBJECT (self), structure);
+ gst_element_post_message (GST_ELEMENT (self), dtmf_message);
+ }
+
+ return GST_FLOW_OK;
+}
+
+
+static gboolean
+gst_dtmf_detect_event (GstBaseTransform * trans, GstEvent * event)
+{
+ GstDtmfDetect *self = GST_DTMF_DETECT (trans);
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_FLUSH_STOP:
+ zap_dtmf_detect_init (&self->dtmf_state);
+ break;
+ default:
+ break;
+ }
+
+ return GST_CALL_PARENT_WITH_DEFAULT (GST_BASE_TRANSFORM_CLASS, event,
+ (trans, event), TRUE);
+}
+
+
+gboolean
+gst_dtmf_detect_plugin_init (GstPlugin * plugin)
+{
+ return gst_element_register (plugin, "dtmfdetect",
+ GST_RANK_MARGINAL, GST_TYPE_DTMF_DETECT);
+}
diff --git a/farsight/dtmf/gstdtmfdetect.h b/farsight/dtmf/gstdtmfdetect.h
new file mode 100644
index 0000000..b3bfab9
--- /dev/null
+++ b/farsight/dtmf/gstdtmfdetect.h
@@ -0,0 +1,73 @@
+/*
+ * GStreamer - DTMF Detection
+ *
+ * Copyright 2009 Nokia Corporation
+ * Copyright 2009 Collabora Ltd,
+ * @author: Olivier Crete <olivier.crete@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __GST_DTMF_DETECT_H__
+#define __GST_DTMF_DETECT_H__
+
+#include <gst/gst.h>
+#include <gst/base/gstbasetransform.h>
+
+#include "tone_detect.h"
+
+G_BEGIN_DECLS
+
+/* #define's don't like whitespacey bits */
+#define GST_TYPE_DTMF_DETECT \
+ (gst_dtmf_detect_get_type())
+#define GST_DTMF_DETECT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+ GST_TYPE_DTMF_DETECT,GstDtmfDetect))
+#define GST_DTMF_DETECT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ GST_TYPE_DTMF_DETECT,GstDtmfDetectClass))
+#define GST_IS_DTMF_DETECT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_DTMF_DETECT))
+#define GST_IS_DTMF_DETECT_CLASS(obj) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_DTMF_DETECT))
+
+typedef struct _GstDtmfDetect GstDtmfDetect;
+typedef struct _GstDtmfDetectClass GstDtmfDetectClass;
+typedef struct _GstDtmfDetectPrivate GstDtmfDetectPrivate;
+
+struct _GstDtmfDetect
+{
+ GstBaseTransform parent;
+
+ gint rate;
+
+ dtmf_detect_state_t dtmf_state;
+};
+
+struct _GstDtmfDetectClass
+{
+ GstBaseTransformClass parent_class;
+};
+
+GType gst_dtmf_detect_get_type (void);
+
+gboolean gst_dtmf_detect_plugin_init (GstPlugin *plugin);
+
+G_END_DECLS
+
+#endif /* __GST_DTMF_DETECT_H__ */
diff --git a/farsight/dtmf/gstdtmfsrc.c b/farsight/dtmf/gstdtmfsrc.c
new file mode 100644
index 0000000..39699d8
--- /dev/null
+++ b/farsight/dtmf/gstdtmfsrc.c
@@ -0,0 +1,905 @@
+/* GStreamer DTMF source
+ *
+ * gstdtmfsrc.c:
+ *
+ * Copyright (C) <2007> Collabora.
+ * Contact: Youness Alaoui <youness.alaoui@collabora.co.uk>
+ * Copyright (C) <2007> Nokia Corporation.
+ * Contact: Zeeshan Ali <zeeshan.ali@nokia.com>
+ * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
+ * 2000,2005 Wim Taymans <wim@fluendo.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/**
+ * SECTION:element-dtmfsrc
+ * @see_also: rtpdtmsrc, rtpdtmfmuxx
+ *
+ * The DTMFSrc element generates DTMF (ITU-T Q.23 Specification) tone packets on request
+ * from application. The application communicates the beginning and end of a
+ * DTMF event using custom upstream gstreamer events. To report a DTMF event, an
+ * application must send an event of type GST_EVENT_CUSTOM_UPSTREAM, having a
+ * structure of name "dtmf-event" with fields set according to the following
+ * table:
+ *
+ * <informaltable>
+ * <tgroup cols='4'>
+ * <colspec colname='Name' />
+ * <colspec colname='Type' />
+ * <colspec colname='Possible values' />
+ * <colspec colname='Purpose' />
+ * <thead>
+ * <row>
+ * <entry>Name</entry>
+ * <entry>GType</entry>
+ * <entry>Possible values</entry>
+ * <entry>Purpose</entry>
+ * </row>
+ * </thead>
+ * <tbody>
+ * <row>
+ * <entry>type</entry>
+ * <entry>G_TYPE_INT</entry>
+ * <entry>0-1</entry>
+ * <entry>The application uses this field to specify which of the two methods
+ * specified in RFC 2833 to use. The value should be 0 for tones and 1 for
+ * named events. Tones are specified by their frequencies and events are specied
+ * by their number. This element can only take events as input. Do not confuse
+ * with "method" which specified the output.
+ * </entry>
+ * </row>
+ * <row>
+ * <entry>number</entry>
+ * <entry>G_TYPE_INT</entry>
+ * <entry>0-16</entry>
+ * <entry>The event number.</entry>
+ * </row>
+ * <row>
+ * <entry>volume</entry>
+ * <entry>G_TYPE_INT</entry>
+ * <entry>0-36</entry>
+ * <entry>This field describes the power level of the tone, expressed in dBm0
+ * after dropping the sign. Power levels range from 0 to -63 dBm0. The range of
+ * valid DTMF is from 0 to -36 dBm0. Can be omitted if start is set to FALSE.
+ * </entry>
+ * </row>
+ * <row>
+ * <entry>start</entry>
+ * <entry>G_TYPE_BOOLEAN</entry>
+ * <entry>True or False</entry>
+ * <entry>Whether the event is starting or ending.</entry>
+ * </row>
+ * <row>
+ * <entry>method</entry>
+ * <entry>G_TYPE_INT</entry>
+ * <entry>2</entry>
+ * <entry>The method used for sending event, this element will react if this
+ * field is absent or 2.
+ * </entry>
+ * </row>
+ * </tbody>
+ * </tgroup>
+ * </informaltable>
+ *
+ * For example, the following code informs the pipeline (and in turn, the
+ * DTMFSrc element inside the pipeline) about the start of a DTMF named
+ * event '1' of volume -25 dBm0:
+ *
+ * <programlisting>
+ * structure = gst_structure_new ("dtmf-event",
+ * "type", G_TYPE_INT, 1,
+ * "number", G_TYPE_INT, 1,
+ * "volume", G_TYPE_INT, 25,
+ * "start", G_TYPE_BOOLEAN, TRUE, NULL);
+ *
+ * event = gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, structure);
+ * gst_element_send_event (pipeline, event);
+ * </programlisting>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include <glib.h>
+
+#ifndef M_PI
+# define M_PI 3.14159265358979323846 /* pi */
+#endif
+
+
+#include "gstdtmfsrc.h"
+
+#define GST_TONE_DTMF_TYPE_EVENT 1
+#define DEFAULT_PACKET_INTERVAL 50 /* ms */
+#define MIN_PACKET_INTERVAL 10 /* ms */
+#define MAX_PACKET_INTERVAL 50 /* ms */
+#define DEFAULT_SAMPLE_RATE 8000
+#define SAMPLE_SIZE 16
+#define CHANNELS 1
+#define MIN_EVENT 0
+#define MAX_EVENT 16
+#define MIN_VOLUME 0
+#define MAX_VOLUME 36
+#define MIN_INTER_DIGIT_INTERVAL 100
+#define MIN_PULSE_DURATION 250
+#define MIN_DUTY_CYCLE (MIN_INTER_DIGIT_INTERVAL + MIN_PULSE_DURATION)
+
+
+typedef struct st_dtmf_key
+{
+ char *event_name;
+ int event_encoding;
+ float low_frequency;
+ float high_frequency;
+} DTMF_KEY;
+
+static const DTMF_KEY DTMF_KEYS[] = {
+ {"DTMF_KEY_EVENT_0", 0, 941, 1336},
+ {"DTMF_KEY_EVENT_1", 1, 697, 1209},
+ {"DTMF_KEY_EVENT_2", 2, 697, 1336},
+ {"DTMF_KEY_EVENT_3", 3, 697, 1477},
+ {"DTMF_KEY_EVENT_4", 4, 770, 1209},
+ {"DTMF_KEY_EVENT_5", 5, 770, 1336},
+ {"DTMF_KEY_EVENT_6", 6, 770, 1477},
+ {"DTMF_KEY_EVENT_7", 7, 852, 1209},
+ {"DTMF_KEY_EVENT_8", 8, 852, 1336},
+ {"DTMF_KEY_EVENT_9", 9, 852, 1477},
+ {"DTMF_KEY_EVENT_S", 10, 941, 1209},
+ {"DTMF_KEY_EVENT_P", 11, 941, 1477},
+ {"DTMF_KEY_EVENT_A", 12, 697, 1633},
+ {"DTMF_KEY_EVENT_B", 13, 770, 1633},
+ {"DTMF_KEY_EVENT_C", 14, 852, 1633},
+ {"DTMF_KEY_EVENT_D", 15, 941, 1633},
+};
+
+#define MAX_DTMF_EVENTS 16
+
+enum
+{
+ DTMF_KEY_EVENT_1 = 1,
+ DTMF_KEY_EVENT_2 = 2,
+ DTMF_KEY_EVENT_3 = 3,
+ DTMF_KEY_EVENT_4 = 4,
+ DTMF_KEY_EVENT_5 = 5,
+ DTMF_KEY_EVENT_6 = 6,
+ DTMF_KEY_EVENT_7 = 7,
+ DTMF_KEY_EVENT_8 = 8,
+ DTMF_KEY_EVENT_9 = 9,
+ DTMF_KEY_EVENT_0 = 0,
+ DTMF_KEY_EVENT_STAR = 10,
+ DTMF_KEY_EVENT_POUND = 11,
+ DTMF_KEY_EVENT_A = 12,
+ DTMF_KEY_EVENT_B = 13,
+ DTMF_KEY_EVENT_C = 14,
+ DTMF_KEY_EVENT_D = 15,
+};
+
+/* elementfactory information */
+static const GstElementDetails gst_dtmf_src_details =
+GST_ELEMENT_DETAILS ("DTMF tone generator",
+ "Source/Audio",
+ "Generates DTMF tones",
+ "Youness Alaoui <youness.alaoui@collabora.co.uk>");
+
+GST_DEBUG_CATEGORY_STATIC (gst_dtmf_src_debug);
+#define GST_CAT_DEFAULT gst_dtmf_src_debug
+
+enum
+{
+ PROP_0,
+ PROP_INTERVAL,
+};
+
+static GstStaticPadTemplate gst_dtmf_src_template =
+GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("audio/x-raw-int, "
+ "width = (int) 16, "
+ "depth = (int) 16, "
+ "endianness = (int) " G_STRINGIFY (G_BYTE_ORDER) ", "
+ "signed = (bool) true, rate = (int) [1, MAX], channels = (int) 1")
+ );
+
+GST_BOILERPLATE (GstDTMFSrc, gst_dtmf_src, GstBaseSrc, GST_TYPE_BASE_SRC);
+
+static void gst_dtmf_src_finalize (GObject * object);
+
+static void gst_dtmf_src_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_dtmf_src_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+static gboolean gst_dtmf_src_handle_event (GstBaseSrc * src, GstEvent * event);
+static GstStateChangeReturn gst_dtmf_src_change_state (GstElement * element,
+ GstStateChange transition);
+static GstFlowReturn gst_dtmf_src_create (GstBaseSrc * basesrc,
+ guint64 offset, guint length, GstBuffer ** buffer);
+static void gst_dtmf_src_add_start_event (GstDTMFSrc * dtmfsrc,
+ gint event_number, gint event_volume);
+static void gst_dtmf_src_add_stop_event (GstDTMFSrc * dtmfsrc);
+
+static gboolean gst_dtmf_src_unlock (GstBaseSrc * src);
+
+static gboolean gst_dtmf_src_unlock_stop (GstBaseSrc * src);
+static gboolean gst_dtmf_src_negotiate (GstBaseSrc * basesrc);
+
+static void
+gst_dtmf_src_base_init (gpointer g_class)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
+
+ GST_DEBUG_CATEGORY_INIT (gst_dtmf_src_debug, "dtmfsrc", 0, "dtmfsrc element");
+
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&gst_dtmf_src_template));
+
+ gst_element_class_set_details (element_class, &gst_dtmf_src_details);
+}
+
+static void
+gst_dtmf_src_class_init (GstDTMFSrcClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstBaseSrcClass *gstbasesrc_class;
+ GstElementClass *gstelement_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gstbasesrc_class = GST_BASE_SRC_CLASS (klass);
+ gstelement_class = GST_ELEMENT_CLASS (klass);
+
+
+ gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_dtmf_src_finalize);
+ gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_dtmf_src_set_property);
+ gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_dtmf_src_get_property);
+
+ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_INTERVAL,
+ g_param_spec_uint ("interval", "Interval between tone packets",
+ "Interval in ms between two tone packets", MIN_PACKET_INTERVAL,
+ MAX_PACKET_INTERVAL, DEFAULT_PACKET_INTERVAL, G_PARAM_READWRITE));
+
+ gstelement_class->change_state =
+ GST_DEBUG_FUNCPTR (gst_dtmf_src_change_state);
+ gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_dtmf_src_unlock);
+ gstbasesrc_class->unlock_stop = GST_DEBUG_FUNCPTR (gst_dtmf_src_unlock_stop);
+
+ gstbasesrc_class->event = GST_DEBUG_FUNCPTR (gst_dtmf_src_handle_event);
+ gstbasesrc_class->create = GST_DEBUG_FUNCPTR (gst_dtmf_src_create);
+ gstbasesrc_class->negotiate = GST_DEBUG_FUNCPTR (gst_dtmf_src_negotiate);
+}
+
+static void
+event_free (GstDTMFSrcEvent * event)
+{
+ if (event)
+ g_slice_free (GstDTMFSrcEvent, event);
+}
+
+static void
+gst_dtmf_src_init (GstDTMFSrc * dtmfsrc, GstDTMFSrcClass * g_class)
+{
+ /* we operate in time */
+ gst_base_src_set_format (GST_BASE_SRC (dtmfsrc), GST_FORMAT_TIME);
+ gst_base_src_set_live (GST_BASE_SRC (dtmfsrc), TRUE);
+
+ dtmfsrc->interval = DEFAULT_PACKET_INTERVAL;
+
+ dtmfsrc->event_queue = g_async_queue_new_full ((GDestroyNotify) event_free);
+ dtmfsrc->last_event = NULL;
+
+ dtmfsrc->sample_rate = DEFAULT_SAMPLE_RATE;
+
+ GST_DEBUG_OBJECT (dtmfsrc, "init done");
+}
+
+static void
+gst_dtmf_src_finalize (GObject * object)
+{
+ GstDTMFSrc *dtmfsrc;
+
+ dtmfsrc = GST_DTMF_SRC (object);
+
+ if (dtmfsrc->event_queue) {
+ g_async_queue_unref (dtmfsrc->event_queue);
+ dtmfsrc->event_queue = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+gst_dtmf_src_handle_dtmf_event (GstDTMFSrc * dtmfsrc,
+ const GstStructure * event_structure)
+{
+ gint event_type;
+ gboolean start;
+ gint method;
+
+ if (!gst_structure_get_int (event_structure, "type", &event_type) ||
+ !gst_structure_get_boolean (event_structure, "start", &start) ||
+ (start == TRUE && event_type != GST_TONE_DTMF_TYPE_EVENT))
+ goto failure;
+
+ if (gst_structure_get_int (event_structure, "method", &method)) {
+ if (method != 2) {
+ goto failure;
+ }
+ }
+
+ if (start) {
+ gint event_number;
+ gint event_volume;
+
+ if (!gst_structure_get_int (event_structure, "number", &event_number) ||
+ !gst_structure_get_int (event_structure, "volume", &event_volume))
+ goto failure;
+
+ GST_DEBUG_OBJECT (dtmfsrc, "Received start event %d with volume %d",
+ event_number, event_volume);
+ gst_dtmf_src_add_start_event (dtmfsrc, event_number, event_volume);
+ }
+
+ else {
+ GST_DEBUG_OBJECT (dtmfsrc, "Received stop event");
+ gst_dtmf_src_add_stop_event (dtmfsrc);
+ }
+
+ return TRUE;
+failure:
+ return FALSE;
+}
+
+static gboolean
+gst_dtmf_src_handle_custom_upstream (GstDTMFSrc * dtmfsrc, GstEvent * event)
+{
+ gboolean result = FALSE;
+ const GstStructure *structure;
+ GstState state;
+ GstStateChangeReturn ret;
+
+ ret = gst_element_get_state (GST_ELEMENT (dtmfsrc), &state, NULL, 0);
+ if (ret != GST_STATE_CHANGE_SUCCESS || state != GST_STATE_PLAYING) {
+ GST_DEBUG_OBJECT (dtmfsrc, "Received event while not in PLAYING state");
+ goto ret;
+ }
+
+ GST_DEBUG_OBJECT (dtmfsrc, "Received event is of our interest");
+ structure = gst_event_get_structure (event);
+ if (structure && gst_structure_has_name (structure, "dtmf-event"))
+ result = gst_dtmf_src_handle_dtmf_event (dtmfsrc, structure);
+
+ret:
+ return result;
+}
+
+static gboolean
+gst_dtmf_src_handle_event (GstBaseSrc * src, GstEvent * event)
+{
+ GstDTMFSrc *dtmfsrc;
+ gboolean result = FALSE;
+
+ dtmfsrc = GST_DTMF_SRC (src);
+
+ GST_DEBUG_OBJECT (dtmfsrc, "Received an event on the src pad");
+ if (GST_EVENT_TYPE (event) == GST_EVENT_CUSTOM_UPSTREAM) {
+ result = gst_dtmf_src_handle_custom_upstream (dtmfsrc, event);
+ }
+
+ return result;
+}
+
+static void
+gst_dtmf_src_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstDTMFSrc *dtmfsrc;
+
+ dtmfsrc = GST_DTMF_SRC (object);
+
+ switch (prop_id) {
+ case PROP_INTERVAL:
+ dtmfsrc->interval = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_dtmf_src_get_property (GObject * object, guint prop_id, GValue * value,
+ GParamSpec * pspec)
+{
+ GstDTMFSrc *dtmfsrc;
+
+ dtmfsrc = GST_DTMF_SRC (object);
+
+ switch (prop_id) {
+ case PROP_INTERVAL:
+ g_value_set_uint (value, dtmfsrc->interval);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_dtmf_src_set_stream_lock (GstDTMFSrc * dtmfsrc, gboolean lock)
+{
+ GstPad *srcpad = GST_BASE_SRC_PAD (dtmfsrc);
+ GstEvent *event;
+ GstStructure *structure;
+
+ structure = gst_structure_new ("stream-lock",
+ "lock", G_TYPE_BOOLEAN, lock, NULL);
+
+ event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_OOB, structure);
+ if (!gst_pad_push_event (srcpad, event)) {
+ GST_WARNING_OBJECT (dtmfsrc, "stream-lock event not handled");
+ }
+}
+
+static void
+gst_dtmf_prepare_timestamps (GstDTMFSrc * dtmfsrc)
+{
+ GstClock *clock;
+ GstClockTime base_time;
+
+ base_time = gst_element_get_base_time (GST_ELEMENT (dtmfsrc));
+
+ clock = gst_element_get_clock (GST_ELEMENT (dtmfsrc));
+ if (clock != NULL) {
+#ifdef MAEMO_BROKEN
+ dtmfsrc->timestamp = gst_clock_get_time (clock);
+#else
+ dtmfsrc->timestamp = gst_clock_get_time (clock) - base_time;
+#endif
+ gst_object_unref (clock);
+ } else {
+ gchar *dtmf_name = gst_element_get_name (dtmfsrc);
+ GST_ERROR_OBJECT (dtmfsrc, "No clock set for element %s", dtmf_name);
+ dtmfsrc->timestamp = GST_CLOCK_TIME_NONE;
+ g_free (dtmf_name);
+ }
+}
+
+static void
+gst_dtmf_src_add_start_event (GstDTMFSrc * dtmfsrc, gint event_number,
+ gint event_volume)
+{
+
+ GstDTMFSrcEvent *event = g_slice_new0 (GstDTMFSrcEvent);
+ event->event_type = DTMF_EVENT_TYPE_START;
+ event->sample = 0;
+ event->event_number = CLAMP (event_number, MIN_EVENT, MAX_EVENT);
+ event->volume = CLAMP (event_volume, MIN_VOLUME, MAX_VOLUME);
+
+ g_async_queue_push (dtmfsrc->event_queue, event);
+}
+
+static void
+gst_dtmf_src_add_stop_event (GstDTMFSrc * dtmfsrc)
+{
+
+ GstDTMFSrcEvent *event = g_slice_new0 (GstDTMFSrcEvent);
+ event->event_type = DTMF_EVENT_TYPE_STOP;
+ event->sample = 0;
+ event->event_number = 0;
+ event->volume = 0;
+
+ g_async_queue_push (dtmfsrc->event_queue, event);
+}
+
+static void
+gst_dtmf_src_generate_silence (GstBuffer * buffer, float duration,
+ gint sample_rate)
+{
+ gint buf_size;
+
+ /* Create a buffer with data set to 0 */
+ buf_size = ((duration / 1000) * sample_rate * SAMPLE_SIZE * CHANNELS) / 8;
+ GST_BUFFER_SIZE (buffer) = buf_size;
+ GST_BUFFER_MALLOCDATA (buffer) = g_malloc0 (buf_size);
+ GST_BUFFER_DATA (buffer) = GST_BUFFER_MALLOCDATA (buffer);
+
+}
+
+static void
+gst_dtmf_src_generate_tone (GstDTMFSrcEvent * event, DTMF_KEY key,
+ float duration, GstBuffer * buffer, gint sample_rate)
+{
+ gint16 *p;
+ gint tone_size;
+ double i = 0;
+ double amplitude, f1, f2;
+ double volume_factor;
+
+ /* Create a buffer for the tone */
+ tone_size = ((duration / 1000) * sample_rate * SAMPLE_SIZE * CHANNELS) / 8;
+ GST_BUFFER_SIZE (buffer) = tone_size;
+ GST_BUFFER_MALLOCDATA (buffer) = g_malloc (tone_size);
+ GST_BUFFER_DATA (buffer) = GST_BUFFER_MALLOCDATA (buffer);
+
+ p = (gint16 *) GST_BUFFER_MALLOCDATA (buffer);
+
+ volume_factor = pow (10, (-event->volume) / 20);
+
+ /*
+ * For each sample point we calculate 'x' as the
+ * the amplitude value.
+ */
+ for (i = 0; i < (tone_size / (SAMPLE_SIZE / 8)); i++) {
+ /*
+ * We add the fundamental frequencies together.
+ */
+ f1 = sin (2 * M_PI * key.low_frequency * (event->sample / sample_rate));
+ f2 = sin (2 * M_PI * key.high_frequency * (event->sample / sample_rate));
+
+ amplitude = (f1 + f2) / 2;
+
+ /* Adjust the volume */
+ amplitude *= volume_factor;
+
+ /* Make the [-1:1] interval into a [-32767:32767] interval */
+ amplitude *= 32767;
+
+ /* Store it in the data buffer */
+ *(p++) = (gint16) amplitude;
+
+ (event->sample)++;
+ }
+}
+
+
+
+static GstBuffer *
+gst_dtmf_src_create_next_tone_packet (GstDTMFSrc * dtmfsrc,
+ GstDTMFSrcEvent * event)
+{
+ GstBuffer *buf = NULL;
+ gboolean send_silence = FALSE;
+ GstPad *srcpad = GST_BASE_SRC_PAD (dtmfsrc);
+
+ GST_LOG_OBJECT (dtmfsrc, "Creating buffer for tone %s",
+ DTMF_KEYS[event->event_number].event_name);
+
+ /* create buffer to hold the tone */
+ buf = gst_buffer_new ();
+
+ if (event->packet_count * dtmfsrc->interval < MIN_INTER_DIGIT_INTERVAL) {
+ send_silence = TRUE;
+ }
+
+ if (send_silence) {
+ GST_LOG_OBJECT (dtmfsrc, "Generating silence");
+ gst_dtmf_src_generate_silence (buf, dtmfsrc->interval,
+ dtmfsrc->sample_rate);
+ } else {
+ GST_LOG_OBJECT (dtmfsrc, "Generating tone");
+ gst_dtmf_src_generate_tone (event, DTMF_KEYS[event->event_number],
+ dtmfsrc->interval, buf, dtmfsrc->sample_rate);
+ }
+ event->packet_count++;
+
+
+ /* timestamp and duration of GstBuffer */
+ GST_BUFFER_DURATION (buf) = dtmfsrc->interval * GST_MSECOND;
+ GST_BUFFER_TIMESTAMP (buf) = dtmfsrc->timestamp;
+ dtmfsrc->timestamp += GST_BUFFER_DURATION (buf);
+
+ /* Set caps on the buffer before pushing it */
+ gst_buffer_set_caps (buf, GST_PAD_CAPS (srcpad));
+
+ return buf;
+}
+
+static GstFlowReturn
+gst_dtmf_src_create (GstBaseSrc * basesrc, guint64 offset,
+ guint length, GstBuffer ** buffer)
+{
+ GstBuffer *buf = NULL;
+ GstDTMFSrcEvent *event;
+ GstDTMFSrc *dtmfsrc;
+ GstClock *clock;
+ GstClockID *clockid;
+ GstClockReturn clockret;
+
+ dtmfsrc = GST_DTMF_SRC (basesrc);
+
+ do {
+
+ if (dtmfsrc->last_event == NULL) {
+ GST_DEBUG_OBJECT (dtmfsrc, "popping");
+ event = g_async_queue_pop (dtmfsrc->event_queue);
+
+ GST_DEBUG_OBJECT (dtmfsrc, "popped %d", event->event_type);
+
+ switch (event->event_type) {
+ case DTMF_EVENT_TYPE_STOP:
+ GST_WARNING_OBJECT (dtmfsrc,
+ "Received a DTMF stop event when already stopped");
+ break;
+ case DTMF_EVENT_TYPE_START:
+ gst_dtmf_prepare_timestamps (dtmfsrc);
+
+ /* Don't forget to get exclusive access to the stream */
+ gst_dtmf_src_set_stream_lock (dtmfsrc, TRUE);
+
+ event->packet_count = 0;
+ dtmfsrc->last_event = event;
+ event = NULL;
+ break;
+ case DTMF_EVENT_TYPE_PAUSE_TASK:
+ /*
+ * We're pushing it back because it has to stay in there until
+ * the task is really paused (and the queue will then be flushed)
+ */
+ GST_DEBUG_OBJECT (dtmfsrc, "pushing pause_task...");
+ GST_OBJECT_LOCK (dtmfsrc);
+ if (dtmfsrc->paused) {
+ g_async_queue_push (dtmfsrc->event_queue, event);
+ goto paused_locked;
+ }
+ GST_OBJECT_UNLOCK (dtmfsrc);
+ break;
+ }
+ if (event)
+ g_slice_free (GstDTMFSrcEvent, event);
+ } else if (dtmfsrc->last_event->packet_count * dtmfsrc->interval >=
+ MIN_DUTY_CYCLE) {
+ event = g_async_queue_try_pop (dtmfsrc->event_queue);
+
+ if (event != NULL) {
+
+ switch (event->event_type) {
+ case DTMF_EVENT_TYPE_START:
+ GST_WARNING_OBJECT (dtmfsrc,
+ "Received two consecutive DTMF start events");
+ break;
+ case DTMF_EVENT_TYPE_STOP:
+ gst_dtmf_src_set_stream_lock (dtmfsrc, FALSE);
+
+ g_slice_free (GstDTMFSrcEvent, dtmfsrc->last_event);
+ dtmfsrc->last_event = NULL;
+ break;
+ case DTMF_EVENT_TYPE_PAUSE_TASK:
+ /*
+ * We're pushing it back because it has to stay in there until
+ * the task is really paused (and the queue will then be flushed)
+ */
+ GST_DEBUG_OBJECT (dtmfsrc, "pushing pause_task...");
+
+ GST_OBJECT_LOCK (dtmfsrc);
+ if (dtmfsrc->paused) {
+ g_async_queue_push (dtmfsrc->event_queue, event);
+ goto paused_locked;
+ }
+ GST_OBJECT_UNLOCK (dtmfsrc);
+
+ break;
+ }
+ g_slice_free (GstDTMFSrcEvent, event);
+ }
+ }
+ } while (dtmfsrc->last_event == NULL);
+
+ GST_LOG_OBJECT (dtmfsrc, "end event check, now wait for the proper time");
+
+ clock = gst_element_get_clock (GST_ELEMENT (basesrc));
+
+#ifdef MAEMO_BROKEN
+ clockid = gst_clock_new_single_shot_id (clock, dtmfsrc->timestamp);
+#else
+ clockid = gst_clock_new_single_shot_id (clock, dtmfsrc->timestamp +
+ gst_element_get_base_time (GST_ELEMENT (dtmfsrc)));
+#endif
+ gst_object_unref (clock);
+
+ GST_OBJECT_LOCK (dtmfsrc);
+ if (!dtmfsrc->paused) {
+ dtmfsrc->clockid = clockid;
+ GST_OBJECT_UNLOCK (dtmfsrc);
+
+ clockret = gst_clock_id_wait (clockid, NULL);
+
+ GST_OBJECT_LOCK (dtmfsrc);
+ if (dtmfsrc->paused)
+ clockret = GST_CLOCK_UNSCHEDULED;
+ } else {
+ clockret = GST_CLOCK_UNSCHEDULED;
+ }
+ gst_clock_id_unref (clockid);
+ dtmfsrc->clockid = NULL;
+ GST_OBJECT_UNLOCK (dtmfsrc);
+
+ if (clockret == GST_CLOCK_UNSCHEDULED) {
+ goto paused;
+ }
+
+ buf = gst_dtmf_src_create_next_tone_packet (dtmfsrc, dtmfsrc->last_event);
+
+ GST_LOG_OBJECT (dtmfsrc, "Created buffer of size %d", GST_BUFFER_SIZE (buf));
+ *buffer = buf;
+
+ return GST_FLOW_OK;
+
+paused_locked:
+ GST_OBJECT_UNLOCK (dtmfsrc);
+
+paused:
+
+ if (dtmfsrc->last_event) {
+ GST_DEBUG_OBJECT (dtmfsrc, "Stopping current event");
+ /* Don't forget to release the stream lock */
+ gst_dtmf_src_set_stream_lock (dtmfsrc, FALSE);
+ g_slice_free (GstDTMFSrcEvent, dtmfsrc->last_event);
+ dtmfsrc->last_event = NULL;
+ }
+
+ return GST_FLOW_WRONG_STATE;
+
+}
+
+static gboolean
+gst_dtmf_src_unlock (GstBaseSrc * src)
+{
+ GstDTMFSrc *dtmfsrc = GST_DTMF_SRC (src);
+ GstDTMFSrcEvent *event = NULL;
+
+ GST_DEBUG_OBJECT (dtmfsrc, "Called unlock");
+
+ GST_OBJECT_LOCK (dtmfsrc);
+ dtmfsrc->paused = TRUE;
+ if (dtmfsrc->clockid) {
+ gst_clock_id_unschedule (dtmfsrc->clockid);
+ }
+ GST_OBJECT_UNLOCK (dtmfsrc);
+
+ GST_DEBUG_OBJECT (dtmfsrc, "Pushing the PAUSE_TASK event on unlock request");
+ event = g_slice_new0 (GstDTMFSrcEvent);
+ event->event_type = DTMF_EVENT_TYPE_PAUSE_TASK;
+ g_async_queue_push (dtmfsrc->event_queue, event);
+
+ return TRUE;
+}
+
+
+static gboolean
+gst_dtmf_src_unlock_stop (GstBaseSrc * src)
+{
+ GstDTMFSrc *dtmfsrc = GST_DTMF_SRC (src);
+
+ GST_DEBUG_OBJECT (dtmfsrc, "Unlock stopped");
+
+ GST_OBJECT_LOCK (dtmfsrc);
+ dtmfsrc->paused = FALSE;
+ GST_OBJECT_UNLOCK (dtmfsrc);
+
+ return TRUE;
+}
+
+
+static gboolean
+gst_dtmf_src_negotiate (GstBaseSrc * basesrc)
+{
+ GstDTMFSrc *dtmfsrc = GST_DTMF_SRC (basesrc);
+ GstCaps *caps;
+ GstStructure *s;
+ gboolean ret;
+
+ caps = gst_pad_get_allowed_caps (GST_BASE_SRC_PAD (basesrc));
+
+ if (!caps)
+ caps =
+ gst_caps_copy (gst_pad_get_pad_template_caps (GST_BASE_SRC_PAD
+ (basesrc)));
+
+ if (gst_caps_is_empty (caps))
+ return FALSE;
+
+ gst_caps_truncate (caps);
+ s = gst_caps_get_structure (caps, 0);
+
+ gst_structure_fixate_field_nearest_int (s, "rate", DEFAULT_SAMPLE_RATE);
+
+ if (!gst_structure_get_int (s, "rate", &dtmfsrc->sample_rate)) {
+ GST_ERROR_OBJECT (dtmfsrc, "Could not get rate");
+ gst_caps_unref (caps);
+ return FALSE;
+ }
+
+ ret = gst_pad_set_caps (GST_BASE_SRC_PAD (basesrc), caps);
+
+ gst_caps_unref (caps);
+
+ return ret;
+}
+
+static GstStateChangeReturn
+gst_dtmf_src_change_state (GstElement * element, GstStateChange transition)
+{
+ GstDTMFSrc *dtmfsrc;
+ GstStateChangeReturn result;
+ gboolean no_preroll = FALSE;
+ GstDTMFSrcEvent *event = NULL;
+
+ dtmfsrc = GST_DTMF_SRC (element);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ /* Flushing the event queue */
+ event = g_async_queue_try_pop (dtmfsrc->event_queue);
+
+ while (event != NULL) {
+ g_slice_free (GstDTMFSrcEvent, event);
+ event = g_async_queue_try_pop (dtmfsrc->event_queue);
+ }
+ no_preroll = TRUE;
+ break;
+ default:
+ break;
+ }
+
+ if ((result =
+ GST_ELEMENT_CLASS (parent_class)->change_state (element,
+ transition)) == GST_STATE_CHANGE_FAILURE)
+ goto failure;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ no_preroll = TRUE;
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ GST_DEBUG_OBJECT (dtmfsrc, "Flushing event queue");
+ /* Flushing the event queue */
+ event = g_async_queue_try_pop (dtmfsrc->event_queue);
+
+ while (event != NULL) {
+ g_slice_free (GstDTMFSrcEvent, event);
+ event = g_async_queue_try_pop (dtmfsrc->event_queue);
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ if (no_preroll && result == GST_STATE_CHANGE_SUCCESS)
+ result = GST_STATE_CHANGE_NO_PREROLL;
+
+ return result;
+
+ /* ERRORS */
+failure:
+ {
+ GST_ERROR_OBJECT (dtmfsrc, "parent failed state change");
+ return result;
+ }
+}
+
+gboolean
+gst_dtmf_src_plugin_init (GstPlugin * plugin)
+{
+ return gst_element_register (plugin, "dtmfsrc",
+ GST_RANK_NONE, GST_TYPE_DTMF_SRC);
+}
diff --git a/farsight/dtmf/gstdtmfsrc.h b/farsight/dtmf/gstdtmfsrc.h
new file mode 100644
index 0000000..aa5d35a
--- /dev/null
+++ b/farsight/dtmf/gstdtmfsrc.h
@@ -0,0 +1,98 @@
+/* GStreamer DTMF source
+ *
+ * gstdtmfsrc.h:
+ *
+ * Copyright (C) <2007> Collabora.
+ * Contact: Youness Alaoui <youness.alaoui@collabora.co.uk>
+ * Copyright (C) <2007> Nokia Corporation.
+ * Contact: Zeeshan Ali <zeeshan.ali@nokia.com>
+ * Copyright (C) <2005> Wim Taymans <wim@fluendo.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GST_DTMF_SRC_H__
+#define __GST_DTMF_SRC_H__
+
+#include <gst/gst.h>
+#include <gst/gstbuffer.h>
+#include <gst/base/gstbasesrc.h>
+
+G_BEGIN_DECLS
+#define GST_TYPE_DTMF_SRC (gst_dtmf_src_get_type())
+#define GST_DTMF_SRC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_DTMF_SRC,GstDTMFSrc))
+#define GST_DTMF_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_DTMF_SRC,GstDTMFSrcClass))
+#define GST_DTMF_SRC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_DTMF_SRC, GstDTMFSrcClass))
+#define GST_IS_DTMF_SRC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_DTMF_SRC))
+#define GST_IS_DTMF_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_DTMF_SRC))
+#define GST_DTMF_SRC_CAST(obj) ((GstDTMFSrc *)(obj))
+typedef struct _GstDTMFSrc GstDTMFSrc;
+typedef struct _GstDTMFSrcClass GstDTMFSrcClass;
+
+enum _GstDTMFEventType
+{
+ DTMF_EVENT_TYPE_START,
+ DTMF_EVENT_TYPE_STOP,
+ DTMF_EVENT_TYPE_PAUSE_TASK
+};
+
+typedef enum _GstDTMFEventType GstDTMFEventType;
+
+struct _GstDTMFSrcEvent
+{
+ GstDTMFEventType event_type;
+ double sample;
+ guint16 event_number;
+ guint16 volume;
+ guint32 packet_count;
+};
+
+typedef struct _GstDTMFSrcEvent GstDTMFSrcEvent;
+
+/**
+ * GstDTMFSrc:
+ * @element: the parent element.
+ *
+ * The opaque #GstDTMFSrc data structure.
+ */
+struct _GstDTMFSrc
+{
+ /*< private >*/
+ GstBaseSrc parent;
+ GAsyncQueue *event_queue;
+ GstDTMFSrcEvent *last_event;
+
+ guint16 interval;
+ GstClockTime timestamp;
+
+ gboolean paused;
+ GstClockID clockid;
+
+ gint sample_rate;
+};
+
+
+struct _GstDTMFSrcClass
+{
+ GstBaseSrcClass parent_class;
+};
+
+GType gst_dtmf_src_get_type (void);
+
+gboolean gst_dtmf_src_plugin_init (GstPlugin * plugin);
+
+G_END_DECLS
+#endif /* __GST_DTMF_SRC_H__ */
diff --git a/farsight/dtmf/gstrtpdtmfcommon.h b/farsight/dtmf/gstrtpdtmfcommon.h
new file mode 100644
index 0000000..c1ab82e
--- /dev/null
+++ b/farsight/dtmf/gstrtpdtmfcommon.h
@@ -0,0 +1,23 @@
+
+#ifndef __GST_RTP_DTMF_COMMON_H__
+#define __GST_RTP_DTMF_COMMON_H__
+
+
+typedef struct
+{
+ unsigned event:8; /* Current DTMF event */
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ unsigned volume:6; /* power level of the tone, in dBm0 */
+ unsigned r:1; /* Reserved-bit */
+ unsigned e:1; /* End-bit */
+#elif G_BYTE_ORDER == G_BIG_ENDIAN
+ unsigned e:1; /* End-bit */
+ unsigned r:1; /* Reserved-bit */
+ unsigned volume:6; /* power level of the tone, in dBm0 */
+#else
+#error "G_BYTE_ORDER should be big or little endian."
+#endif
+ unsigned duration:16; /* Duration of digit, in timestamp units */
+} GstRTPDTMFPayload;
+
+#endif /* __GST_RTP_DTMF_COMMON_H__ */
diff --git a/farsight/dtmf/gstrtpdtmfdepay.c b/farsight/dtmf/gstrtpdtmfdepay.c
new file mode 100644
index 0000000..7cdfabf
--- /dev/null
+++ b/farsight/dtmf/gstrtpdtmfdepay.c
@@ -0,0 +1,555 @@
+/* GstRtpDtmfDepay
+ *
+ * Copyright (C) 2008 Collabora Limited
+ * Copyright (C) 2008 Nokia Corporation
+ * Contact: Youness Alaoui <youness.alaoui@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+/**
+ * SECTION:element-rtpdtmfdepay
+ * @see_also: rtpdtmfsrc, rtpdtmfmux
+ *
+ * This element takes RTP DTMF packets and produces sound. It also emits a
+ * message on the #GstBus.
+ *
+ * The message is called "dtmf-event" and has the following fields
+ * <informaltable>
+ * <tgroup cols='4'>
+ * <colspec colname='Name' />
+ * <colspec colname='Type' />
+ * <colspec colname='Possible values' />
+ * <colspec colname='Purpose' />
+ * <thead>
+ * <row>
+ * <entry>Name</entry>
+ * <entry>GType</entry>
+ * <entry>Possible values</entry>
+ * <entry>Purpose</entry>
+ * </row>
+ * </thead>
+ * <tbody>
+ * <row>
+ * <entry></entry>
+ * <entry>G_TYPE_INT</entry>
+ * <entry>0-1</entry>
+ * <entry>Which of the two methods
+ * specified in RFC 2833 to use. The value should be 0 for tones and 1 for
+ * named events. Tones are specified by their frequencies and events are specied
+ * by their number. This element currently only recognizes events.
+ * Do not confuse with "method" which specified the output.
+ * </entry>
+ * </row>
+ * <row>
+ * <entry>number</entry>
+ * <entry>G_TYPE_INT</entry>
+ * <entry>0-16</entry>
+ * <entry>The event number.</entry>
+ * </row>
+ * <row>
+ * <entry>volume</entry>
+ * <entry>G_TYPE_INT</entry>
+ * <entry>0-36</entry>
+ * <entry>This field describes the power level of the tone, expressed in dBm0
+ * after dropping the sign. Power levels range from 0 to -63 dBm0. The range of
+ * valid DTMF is from 0 to -36 dBm0.
+ * </entry>
+ * </row>
+ * <row>
+ * <entry>method</entry>
+ * <entry>G_TYPE_INT</entry>
+ * <entry>1</entry>
+ * <entry>This field will always been 1 (ie RTP event) from this element.
+ * </entry>
+ * </row>
+ * </tbody>
+ * </tgroup>
+ * </informaltable>
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <string.h>
+#include <math.h>
+
+#include <gst/rtp/gstrtpbuffer.h>
+#include "gstrtpdtmfdepay.h"
+
+#ifndef M_PI
+# define M_PI 3.14159265358979323846 /* pi */
+#endif
+
+
+#define DEFAULT_PACKET_INTERVAL 50 /* ms */
+#define MIN_PACKET_INTERVAL 10 /* ms */
+#define MAX_PACKET_INTERVAL 50 /* ms */
+#define SAMPLE_RATE 8000
+#define SAMPLE_SIZE 16
+#define CHANNELS 1
+#define MIN_EVENT 0
+#define MAX_EVENT 16
+#define MIN_VOLUME 0
+#define MAX_VOLUME 36
+#define MIN_INTER_DIGIT_INTERVAL 100
+#define MIN_PULSE_DURATION 250
+#define MIN_DUTY_CYCLE (MIN_INTER_DIGIT_INTERVAL + MIN_PULSE_DURATION)
+
+#define MIN_UNIT_TIME 0
+#define MAX_UNIT_TIME 1000
+#define DEFAULT_UNIT_TIME 0
+
+#define DEFAULT_MAX_DURATION 0
+
+typedef struct st_dtmf_key
+{
+ char *event_name;
+ int event_encoding;
+ float low_frequency;
+ float high_frequency;
+} DTMF_KEY;
+
+static const DTMF_KEY DTMF_KEYS[] = {
+ {"DTMF_KEY_EVENT_0", 0, 941, 1336},
+ {"DTMF_KEY_EVENT_1", 1, 697, 1209},
+ {"DTMF_KEY_EVENT_2", 2, 697, 1336},
+ {"DTMF_KEY_EVENT_3", 3, 697, 1477},
+ {"DTMF_KEY_EVENT_4", 4, 770, 1209},
+ {"DTMF_KEY_EVENT_5", 5, 770, 1336},
+ {"DTMF_KEY_EVENT_6", 6, 770, 1477},
+ {"DTMF_KEY_EVENT_7", 7, 852, 1209},
+ {"DTMF_KEY_EVENT_8", 8, 852, 1336},
+ {"DTMF_KEY_EVENT_9", 9, 852, 1477},
+ {"DTMF_KEY_EVENT_S", 10, 941, 1209},
+ {"DTMF_KEY_EVENT_P", 11, 941, 1477},
+ {"DTMF_KEY_EVENT_A", 12, 697, 1633},
+ {"DTMF_KEY_EVENT_B", 13, 770, 1633},
+ {"DTMF_KEY_EVENT_C", 14, 852, 1633},
+ {"DTMF_KEY_EVENT_D", 15, 941, 1633},
+};
+
+#define MAX_DTMF_EVENTS 16
+
+enum
+{
+ DTMF_KEY_EVENT_1 = 1,
+ DTMF_KEY_EVENT_2 = 2,
+ DTMF_KEY_EVENT_3 = 3,
+ DTMF_KEY_EVENT_4 = 4,
+ DTMF_KEY_EVENT_5 = 5,
+ DTMF_KEY_EVENT_6 = 6,
+ DTMF_KEY_EVENT_7 = 7,
+ DTMF_KEY_EVENT_8 = 8,
+ DTMF_KEY_EVENT_9 = 9,
+ DTMF_KEY_EVENT_0 = 0,
+ DTMF_KEY_EVENT_STAR = 10,
+ DTMF_KEY_EVENT_POUND = 11,
+ DTMF_KEY_EVENT_A = 12,
+ DTMF_KEY_EVENT_B = 13,
+ DTMF_KEY_EVENT_C = 14,
+ DTMF_KEY_EVENT_D = 15,
+};
+
+/* elementfactory information */
+static const GstElementDetails gst_rtp_dtmfdepay_details =
+GST_ELEMENT_DETAILS ("RTP DTMF packet depayloader",
+ "Codec/Depayloader/Network",
+ "Generates DTMF Sound from telephone-event RTP packets",
+ "Youness Alaoui <youness.alaoui@collabora.co.uk>");
+
+GST_DEBUG_CATEGORY_STATIC (gst_rtp_dtmf_depay_debug);
+#define GST_CAT_DEFAULT gst_rtp_dtmf_depay_debug
+
+enum
+{
+
+
+ /* FILL ME */
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_UNIT_TIME,
+ PROP_MAX_DURATION
+};
+
+enum
+{
+ ARG_0
+};
+
+static GstStaticPadTemplate gst_rtp_dtmf_depay_src_template =
+GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("audio/x-raw-int, "
+ "width = (int) 16, "
+ "depth = (int) 16, "
+ "endianness = (int) " G_STRINGIFY (G_BYTE_ORDER) ", "
+ "signed = (boolean) true, "
+ "rate = (int) [0, MAX], " "channels = (int) 1")
+ );
+
+static GstStaticPadTemplate gst_rtp_dtmf_depay_sink_template =
+GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("application/x-rtp, "
+ "media = (string) \"audio\", "
+ "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
+ "clock-rate = (int) [ 0, MAX ], "
+ "encoding-name = (string) \"TELEPHONE-EVENT\"")
+ );
+
+GST_BOILERPLATE (GstRtpDTMFDepay, gst_rtp_dtmf_depay, GstBaseRTPDepayload,
+ GST_TYPE_BASE_RTP_DEPAYLOAD);
+
+static void gst_rtp_dtmf_depay_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_rtp_dtmf_depay_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+static GstBuffer *gst_rtp_dtmf_depay_process (GstBaseRTPDepayload * depayload,
+ GstBuffer * buf);
+gboolean gst_rtp_dtmf_depay_setcaps (GstBaseRTPDepayload * filter,
+ GstCaps * caps);
+
+static void
+gst_rtp_dtmf_depay_base_init (gpointer klass)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&gst_rtp_dtmf_depay_src_template));
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&gst_rtp_dtmf_depay_sink_template));
+
+
+ GST_DEBUG_CATEGORY_INIT (gst_rtp_dtmf_depay_debug,
+ "rtpdtmfdepay", 0, "rtpdtmfdepay element");
+ gst_element_class_set_details (element_class, &gst_rtp_dtmfdepay_details);
+}
+
+static void
+gst_rtp_dtmf_depay_class_init (GstRtpDTMFDepayClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+ GstBaseRTPDepayloadClass *gstbasertpdepayload_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+ gstbasertpdepayload_class = (GstBaseRTPDepayloadClass *) klass;
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ gobject_class->set_property =
+ GST_DEBUG_FUNCPTR (gst_rtp_dtmf_depay_set_property);
+ gobject_class->get_property =
+ GST_DEBUG_FUNCPTR (gst_rtp_dtmf_depay_get_property);
+
+ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_UNIT_TIME,
+ g_param_spec_uint ("unit-time", "Duration unittime",
+ "The smallest unit (ms) the duration must be a multiple of (0 disables it)",
+ MIN_UNIT_TIME, MAX_UNIT_TIME, DEFAULT_UNIT_TIME, G_PARAM_READWRITE));
+
+ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_MAX_DURATION,
+ g_param_spec_uint ("max-duration", "Maximum duration",
+ "The maxumimum duration (ms) of the outgoing soundpacket. "
+ "(0 = no limit)", 0, G_MAXUINT, DEFAULT_MAX_DURATION,
+ G_PARAM_READWRITE));
+
+ gstbasertpdepayload_class->process =
+ GST_DEBUG_FUNCPTR (gst_rtp_dtmf_depay_process);
+ gstbasertpdepayload_class->set_caps =
+ GST_DEBUG_FUNCPTR (gst_rtp_dtmf_depay_setcaps);
+
+}
+
+static void
+gst_rtp_dtmf_depay_init (GstRtpDTMFDepay * rtpdtmfdepay,
+ GstRtpDTMFDepayClass * klass)
+{
+ rtpdtmfdepay->unit_time = DEFAULT_UNIT_TIME;
+}
+
+static void
+gst_rtp_dtmf_depay_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstRtpDTMFDepay *rtpdtmfdepay;
+
+ rtpdtmfdepay = GST_RTP_DTMF_DEPAY (object);
+
+ switch (prop_id) {
+ case PROP_UNIT_TIME:
+ rtpdtmfdepay->unit_time = g_value_get_uint (value);
+ break;
+ case PROP_MAX_DURATION:
+ rtpdtmfdepay->max_duration = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_rtp_dtmf_depay_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstRtpDTMFDepay *rtpdtmfdepay;
+
+ rtpdtmfdepay = GST_RTP_DTMF_DEPAY (object);
+
+ switch (prop_id) {
+ case PROP_UNIT_TIME:
+ g_value_set_uint (value, rtpdtmfdepay->unit_time);
+ break;
+ case PROP_MAX_DURATION:
+ g_value_set_uint (value, rtpdtmfdepay->max_duration);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+gboolean
+gst_rtp_dtmf_depay_setcaps (GstBaseRTPDepayload * filter, GstCaps * caps)
+{
+ GstCaps *srccaps;
+ GstStructure *structure = gst_caps_get_structure (caps, 0);
+ gint clock_rate = 8000; /* default */
+
+ gst_structure_get_int (structure, "clock-rate", &clock_rate);
+ filter->clock_rate = clock_rate;
+
+ srccaps = gst_caps_new_simple ("audio/x-raw-int",
+ "width", G_TYPE_INT, 16,
+ "depth", G_TYPE_INT, 16,
+ "endianness", G_TYPE_INT, G_BYTE_ORDER,
+ "signed", G_TYPE_BOOLEAN, TRUE,
+ "channels", G_TYPE_INT, 1, "rate", G_TYPE_INT, clock_rate, NULL);
+ gst_pad_set_caps (GST_BASE_RTP_DEPAYLOAD_SRCPAD (filter), srccaps);
+ gst_caps_unref (srccaps);
+
+ return TRUE;
+}
+
+static void
+gst_dtmf_src_generate_tone (GstRtpDTMFDepay * rtpdtmfdepay,
+ GstRTPDTMFPayload payload, GstBuffer * buffer)
+{
+ gint16 *p;
+ gint tone_size;
+ double i = 0;
+ double amplitude, f1, f2;
+ double volume_factor;
+ DTMF_KEY key = DTMF_KEYS[payload.event];
+ guint32 clock_rate = 8000 /* default */ ;
+ GstBaseRTPDepayload *depayload = GST_BASE_RTP_DEPAYLOAD (rtpdtmfdepay);
+ gint volume;
+
+ clock_rate = depayload->clock_rate;
+
+ /* Create a buffer for the tone */
+ tone_size = (payload.duration * SAMPLE_SIZE * CHANNELS) / 8;
+ GST_BUFFER_SIZE (buffer) = tone_size;
+ GST_BUFFER_MALLOCDATA (buffer) = g_malloc (tone_size);
+ GST_BUFFER_DATA (buffer) = GST_BUFFER_MALLOCDATA (buffer);
+ GST_BUFFER_DURATION (buffer) = payload.duration * GST_SECOND / clock_rate;
+ volume = payload.volume;
+
+ p = (gint16 *) GST_BUFFER_MALLOCDATA (buffer);
+
+ volume_factor = pow (10, (-volume) / 20);
+
+ /*
+ * For each sample point we calculate 'x' as the
+ * the amplitude value.
+ */
+ for (i = 0; i < (tone_size / (SAMPLE_SIZE / 8)); i++) {
+ /*
+ * We add the fundamental frequencies together.
+ */
+ f1 = sin (2 * M_PI * key.low_frequency * (rtpdtmfdepay->sample /
+ clock_rate));
+ f2 = sin (2 * M_PI * key.high_frequency * (rtpdtmfdepay->sample /
+ clock_rate));
+
+ amplitude = (f1 + f2) / 2;
+
+ /* Adjust the volume */
+ amplitude *= volume_factor;
+
+ /* Make the [-1:1] interval into a [-32767:32767] interval */
+ amplitude *= 32767;
+
+ /* Store it in the data buffer */
+ *(p++) = (gint16) amplitude;
+
+ (rtpdtmfdepay->sample)++;
+ }
+}
+
+
+static GstBuffer *
+gst_rtp_dtmf_depay_process (GstBaseRTPDepayload * depayload, GstBuffer * buf)
+{
+
+ GstRtpDTMFDepay *rtpdtmfdepay = NULL;
+ GstBuffer *outbuf = NULL;
+ gint payload_len;
+ guint8 *payload = NULL;
+ guint32 timestamp;
+ GstRTPDTMFPayload dtmf_payload;
+ gboolean marker;
+ GstStructure *structure = NULL;
+ GstMessage *dtmf_message = NULL;
+
+ rtpdtmfdepay = GST_RTP_DTMF_DEPAY (depayload);
+
+ if (!gst_rtp_buffer_validate (buf))
+ goto bad_packet;
+
+ payload_len = gst_rtp_buffer_get_payload_len (buf);
+ payload = gst_rtp_buffer_get_payload (buf);
+
+ if (payload_len != sizeof (GstRTPDTMFPayload))
+ goto bad_packet;
+
+ memcpy (&dtmf_payload, payload, sizeof (GstRTPDTMFPayload));
+
+ if (dtmf_payload.event > MAX_EVENT)
+ goto bad_packet;
+
+
+ marker = gst_rtp_buffer_get_marker (buf);
+
+ timestamp = gst_rtp_buffer_get_timestamp (buf);
+
+ dtmf_payload.duration = g_ntohs (dtmf_payload.duration);
+
+ /* clip to whole units of unit_time */
+ if (rtpdtmfdepay->unit_time) {
+ guint unit_time_clock =
+ (rtpdtmfdepay->unit_time * depayload->clock_rate) / 1000;
+ if (dtmf_payload.duration % unit_time_clock) {
+ /* Make sure we don't overflow the duration */
+ if (dtmf_payload.duration < G_MAXUINT16 - unit_time_clock)
+ dtmf_payload.duration += unit_time_clock -
+ (dtmf_payload.duration % unit_time_clock);
+ else
+ dtmf_payload.duration -= dtmf_payload.duration % unit_time_clock;
+ }
+ }
+
+ /* clip to max duration */
+ if (rtpdtmfdepay->max_duration) {
+ guint max_duration_clock =
+ (rtpdtmfdepay->max_duration * depayload->clock_rate) / 1000;
+
+ if (max_duration_clock < G_MAXUINT16 &&
+ dtmf_payload.duration > max_duration_clock)
+ dtmf_payload.duration = max_duration_clock;
+ }
+
+ GST_DEBUG_OBJECT (depayload, "Received new RTP DTMF packet : "
+ "marker=%d - timestamp=%u - event=%d - duration=%d",
+ marker, timestamp, dtmf_payload.event, dtmf_payload.duration);
+
+ GST_DEBUG_OBJECT (depayload,
+ "Previous information : timestamp=%u - duration=%d",
+ rtpdtmfdepay->previous_ts, rtpdtmfdepay->previous_duration);
+
+ /* First packet */
+ if (marker || rtpdtmfdepay->previous_ts != timestamp) {
+ rtpdtmfdepay->sample = 0;
+ rtpdtmfdepay->previous_ts = timestamp;
+ rtpdtmfdepay->previous_duration = dtmf_payload.duration;
+ rtpdtmfdepay->first_gst_ts = GST_BUFFER_TIMESTAMP (buf);
+
+ structure = gst_structure_new ("dtmf-event",
+ "number", G_TYPE_INT, dtmf_payload.event,
+ "volume", G_TYPE_INT, dtmf_payload.volume,
+ "type", G_TYPE_INT, 1, "method", G_TYPE_INT, 1, NULL);
+ if (structure) {
+ dtmf_message =
+ gst_message_new_element (GST_OBJECT (depayload), structure);
+ if (dtmf_message) {
+ if (!gst_element_post_message (GST_ELEMENT (depayload), dtmf_message)) {
+ GST_ERROR_OBJECT (depayload,
+ "Unable to send dtmf-event message to bus");
+ }
+ } else {
+ GST_ERROR_OBJECT (depayload, "Unable to create dtmf-event message");
+ }
+ } else {
+ GST_ERROR_OBJECT (depayload, "Unable to create dtmf-event structure");
+ }
+ } else {
+ guint16 duration = dtmf_payload.duration;
+ dtmf_payload.duration -= rtpdtmfdepay->previous_duration;
+ /* If late buffer, ignore */
+ if (duration > rtpdtmfdepay->previous_duration)
+ rtpdtmfdepay->previous_duration = duration;
+ }
+
+ GST_DEBUG_OBJECT (depayload, "new previous duration : %d - new duration : %d"
+ " - diff : %d - clock rate : %d - timestamp : %" G_GUINT64_FORMAT,
+ rtpdtmfdepay->previous_duration, dtmf_payload.duration,
+ (rtpdtmfdepay->previous_duration - dtmf_payload.duration),
+ depayload->clock_rate, GST_BUFFER_TIMESTAMP (buf));
+
+ /* If late or duplicate packet (like the redundant end packet). Ignore */
+ if (dtmf_payload.duration > 0) {
+ outbuf = gst_buffer_new ();
+ gst_dtmf_src_generate_tone (rtpdtmfdepay, dtmf_payload, outbuf);
+
+
+ GST_BUFFER_TIMESTAMP (outbuf) = rtpdtmfdepay->first_gst_ts +
+ (rtpdtmfdepay->previous_duration - dtmf_payload.duration) *
+ GST_SECOND / depayload->clock_rate;
+ GST_BUFFER_OFFSET (outbuf) =
+ (rtpdtmfdepay->previous_duration - dtmf_payload.duration) *
+ GST_SECOND / depayload->clock_rate;
+ GST_BUFFER_OFFSET_END (outbuf) = rtpdtmfdepay->previous_duration *
+ GST_SECOND / depayload->clock_rate;
+
+ GST_DEBUG_OBJECT (depayload,
+ "timestamp : %" G_GUINT64_FORMAT " - time %" GST_TIME_FORMAT,
+ GST_BUFFER_TIMESTAMP (buf), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
+
+ }
+
+ return outbuf;
+
+
+bad_packet:
+ GST_ELEMENT_WARNING (rtpdtmfdepay, STREAM, DECODE,
+ ("Packet did not validate"), (NULL));
+ return NULL;
+}
+
+gboolean
+gst_rtp_dtmf_depay_plugin_init (GstPlugin * plugin)
+{
+ return gst_element_register (plugin, "rtpdtmfdepay",
+ GST_RANK_MARGINAL, GST_TYPE_RTP_DTMF_DEPAY);
+}
diff --git a/farsight/dtmf/gstrtpdtmfdepay.h b/farsight/dtmf/gstrtpdtmfdepay.h
new file mode 100644
index 0000000..dfbcc4a
--- /dev/null
+++ b/farsight/dtmf/gstrtpdtmfdepay.h
@@ -0,0 +1,66 @@
+/* GstRtpDtmfDepay
+ *
+ * Copyright (C) 2008 Collabora Limited
+ * Copyright (C) 2008 Nokia Corporation
+ * Contact: Youness Alaoui <youness.alaoui@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GST_RTP_DTMF_DEPAY_H__
+#define __GST_RTP_DTMF_DEPAY_H__
+
+#include <gst/gst.h>
+#include <gst/base/gstadapter.h>
+#include <gst/rtp/gstbasertpdepayload.h>
+
+#include "gstrtpdtmfcommon.h"
+
+G_BEGIN_DECLS
+#define GST_TYPE_RTP_DTMF_DEPAY \
+ (gst_rtp_dtmf_depay_get_type())
+#define GST_RTP_DTMF_DEPAY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_DTMF_DEPAY,GstRtpDTMFDepay))
+#define GST_RTP_DTMF_DEPAY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_DTMF_DEPAY,GstRtpDTMFDepayClass))
+#define GST_IS_RTP_DTMF_DEPAY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_DTMF_DEPAY))
+#define GST_IS_RTP_DTMF_DEPAY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_DTMF_DEPAY))
+typedef struct _GstRtpDTMFDepay GstRtpDTMFDepay;
+typedef struct _GstRtpDTMFDepayClass GstRtpDTMFDepayClass;
+
+struct _GstRtpDTMFDepay
+{
+ /*< private >*/
+ GstBaseRTPDepayload depayload;
+ double sample;
+ guint32 previous_ts;
+ guint16 previous_duration;
+ GstClockTime first_gst_ts;
+ guint unit_time;
+ guint max_duration;
+};
+
+struct _GstRtpDTMFDepayClass
+{
+ GstBaseRTPDepayloadClass parent_class;
+};
+
+gboolean gst_rtp_dtmf_depay_plugin_init (GstPlugin * plugin);
+
+G_END_DECLS
+#endif /* __GST_RTP_DTMF_DEPAY_H__ */
diff --git a/farsight/dtmf/gstrtpdtmfsrc.c b/farsight/dtmf/gstrtpdtmfsrc.c
new file mode 100644
index 0000000..65d0ce8
--- /dev/null
+++ b/farsight/dtmf/gstrtpdtmfsrc.c
@@ -0,0 +1,1099 @@
+/* GStreamer RTP DTMF source
+ *
+ * gstrtpdtmfsrc.c:
+ *
+ * Copyright (C) <2007> Nokia Corporation.
+ * Contact: Zeeshan Ali <zeeshan.ali@nokia.com>
+ * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
+ * 2000,2005 Wim Taymans <wim@fluendo.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/**
+ * SECTION:element-rtpdtmfsrc
+ * @see_also: dtmfsrc, rtpdtmfdepay, rtpdtmfmux
+ *
+ * The RTPDTMFSrc element generates RTP DTMF (RFC 2833) event packets on request
+ * from application. The application communicates the beginning and end of a
+ * DTMF event using custom upstream gstreamer events. To report a DTMF event, an
+ * application must send an event of type GST_EVENT_CUSTOM_UPSTREAM, having a
+ * structure of name "dtmf-event" with fields set according to the following
+ * table:
+ *
+ * <informaltable>
+ * <tgroup cols='4'>
+ * <colspec colname='Name' />
+ * <colspec colname='Type' />
+ * <colspec colname='Possible values' />
+ * <colspec colname='Purpose' />
+ * <thead>
+ * <row>
+ * <entry>Name</entry>
+ * <entry>GType</entry>
+ * <entry>Possible values</entry>
+ * <entry>Purpose</entry>
+ * </row>
+ * </thead>
+ * <tbody>
+ * <row>
+ * <entry>type</entry>
+ * <entry>G_TYPE_INT</entry>
+ * <entry>0-1</entry>
+ * <entry>The application uses this field to specify which of the two methods
+ * specified in RFC 2833 to use. The value should be 0 for tones and 1 for
+ * named events. Tones are specified by their frequencies and events are specied
+ * by their number. This element can only take events as input. Do not confuse
+ * with "method" which specified the output.
+ * </entry>
+ * </row>
+ * <row>
+ * <entry>number</entry>
+ * <entry>G_TYPE_INT</entry>
+ * <entry>0-16</entry>
+ * <entry>The event number.</entry>
+ * </row>
+ * <row>
+ * <entry>volume</entry>
+ * <entry>G_TYPE_INT</entry>
+ * <entry>0-36</entry>
+ * <entry>This field describes the power level of the tone, expressed in dBm0
+ * after dropping the sign. Power levels range from 0 to -63 dBm0. The range of
+ * valid DTMF is from 0 to -36 dBm0. Can be omitted if start is set to FALSE.
+ * </entry>
+ * </row>
+ * <row>
+ * <entry>start</entry>
+ * <entry>G_TYPE_BOOLEAN</entry>
+ * <entry>True or False</entry>
+ * <entry>Whether the event is starting or ending.</entry>
+ * </row>
+ * <row>
+ * <entry>method</entry>
+ * <entry>G_TYPE_INT</entry>
+ * <entry>1</entry>
+ * <entry>The method used for sending event, this element will react if this
+ * field is absent or 1.
+ * </entry>
+ * </row>
+ * </tbody>
+ * </tgroup>
+ * </informaltable>
+ *
+ * For example, the following code informs the pipeline (and in turn, the
+ * RTPDTMFSrc element inside the pipeline) about the start of an RTP DTMF named
+ * event '1' of volume -25 dBm0:
+ *
+ * <programlisting>
+ * structure = gst_structure_new ("dtmf-event",
+ * "type", G_TYPE_INT, 1,
+ * "number", G_TYPE_INT, 1,
+ * "volume", G_TYPE_INT, 25,
+ * "start", G_TYPE_BOOLEAN, TRUE, NULL);
+ *
+ * event = gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, structure);
+ * gst_element_send_event (pipeline, event);
+ * </programlisting>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include "gstrtpdtmfsrc.h"
+
+#define GST_RTP_DTMF_TYPE_EVENT 1
+#define DEFAULT_PACKET_INTERVAL 50 /* ms */
+#define MIN_PACKET_INTERVAL 10 /* ms */
+#define MAX_PACKET_INTERVAL 50 /* ms */
+#define DEFAULT_SSRC -1
+#define DEFAULT_PT 96
+#define DEFAULT_TIMESTAMP_OFFSET -1
+#define DEFAULT_SEQNUM_OFFSET -1
+#define DEFAULT_CLOCK_RATE 8000
+#define MIN_EVENT 0
+#define MAX_EVENT 16
+#define MIN_EVENT_STRING "0"
+#define MAX_EVENT_STRING "16"
+#define MIN_VOLUME 0
+#define MAX_VOLUME 36
+
+#define MIN_INTER_DIGIT_INTERVAL 50 /* ms */
+#define MIN_PULSE_DURATION 70 /* ms */
+
+#define DEFAULT_PACKET_REDUNDANCY 1
+#define MIN_PACKET_REDUNDANCY 1
+#define MAX_PACKET_REDUNDANCY 5
+
+/* elementfactory information */
+static const GstElementDetails gst_rtp_dtmf_src_details =
+GST_ELEMENT_DETAILS ("RTP DTMF packet generator",
+ "Source/Network",
+ "Generates RTP DTMF packets",
+ "Zeeshan Ali <zeeshan.ali@nokia.com>");
+
+GST_DEBUG_CATEGORY_STATIC (gst_rtp_dtmf_src_debug);
+#define GST_CAT_DEFAULT gst_rtp_dtmf_src_debug
+
+/* signals and args */
+enum
+{
+ /* FILL ME */
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_SSRC,
+ PROP_TIMESTAMP_OFFSET,
+ PROP_SEQNUM_OFFSET,
+ PROP_PT,
+ PROP_CLOCK_RATE,
+ PROP_TIMESTAMP,
+ PROP_SEQNUM,
+ PROP_INTERVAL,
+ PROP_REDUNDANCY
+};
+
+static GstStaticPadTemplate gst_rtp_dtmf_src_template =
+GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("application/x-rtp, "
+ "media = (string) \"audio\", "
+ "payload = (int) [ 96, 127 ], "
+ "clock-rate = (int) [ 0, MAX ], "
+ "ssrc = (int) [ 0, MAX ], "
+ "encoding-name = (string) \"TELEPHONE-EVENT\"")
+ /* "events = (string) \"0-15\" */
+ );
+
+
+GST_BOILERPLATE (GstRTPDTMFSrc, gst_rtp_dtmf_src, GstBaseSrc,
+ GST_TYPE_BASE_SRC);
+
+
+static void gst_rtp_dtmf_src_base_init (gpointer g_class);
+static void gst_rtp_dtmf_src_class_init (GstRTPDTMFSrcClass * klass);
+static void gst_rtp_dtmf_src_finalize (GObject * object);
+
+
+static void gst_rtp_dtmf_src_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_rtp_dtmf_src_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+static gboolean gst_rtp_dtmf_src_handle_event (GstBaseSrc * basesrc,
+ GstEvent * event);
+static GstStateChangeReturn gst_rtp_dtmf_src_change_state (GstElement * element,
+ GstStateChange transition);
+static void gst_rtp_dtmf_src_add_start_event (GstRTPDTMFSrc * dtmfsrc,
+ gint event_number, gint event_volume);
+static void gst_rtp_dtmf_src_add_stop_event (GstRTPDTMFSrc * dtmfsrc);
+
+static gboolean gst_rtp_dtmf_src_unlock (GstBaseSrc * src);
+static gboolean gst_rtp_dtmf_src_unlock_stop (GstBaseSrc * src);
+static GstFlowReturn gst_rtp_dtmf_src_create (GstBaseSrc * basesrc,
+ guint64 offset, guint length, GstBuffer ** buffer);
+static gboolean gst_rtp_dtmf_src_negotiate (GstBaseSrc * basesrc);
+
+
+static void
+gst_rtp_dtmf_src_base_init (gpointer g_class)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
+
+ GST_DEBUG_CATEGORY_INIT (gst_rtp_dtmf_src_debug,
+ "rtpdtmfsrc", 0, "rtpdtmfsrc element");
+
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&gst_rtp_dtmf_src_template));
+
+ gst_element_class_set_details (element_class, &gst_rtp_dtmf_src_details);
+}
+
+static void
+gst_rtp_dtmf_src_class_init (GstRTPDTMFSrcClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstBaseSrcClass *gstbasesrc_class;
+ GstElementClass *gstelement_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gstbasesrc_class = GST_BASE_SRC_CLASS (klass);
+ gstelement_class = GST_ELEMENT_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_rtp_dtmf_src_finalize);
+ gobject_class->set_property =
+ GST_DEBUG_FUNCPTR (gst_rtp_dtmf_src_set_property);
+ gobject_class->get_property =
+ GST_DEBUG_FUNCPTR (gst_rtp_dtmf_src_get_property);
+
+ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TIMESTAMP,
+ g_param_spec_uint ("timestamp", "Timestamp",
+ "The RTP timestamp of the last processed packet",
+ 0, G_MAXUINT, 0, G_PARAM_READABLE));
+ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SEQNUM,
+ g_param_spec_uint ("seqnum", "Sequence number",
+ "The RTP sequence number of the last processed packet",
+ 0, G_MAXUINT, 0, G_PARAM_READABLE));
+ g_object_class_install_property (G_OBJECT_CLASS (klass),
+ PROP_TIMESTAMP_OFFSET, g_param_spec_int ("timestamp-offset",
+ "Timestamp Offset",
+ "Offset to add to all outgoing timestamps (-1 = random)", -1,
+ G_MAXINT, DEFAULT_TIMESTAMP_OFFSET, G_PARAM_READWRITE));
+ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SEQNUM_OFFSET,
+ g_param_spec_int ("seqnum-offset", "Sequence number Offset",
+ "Offset to add to all outgoing seqnum (-1 = random)", -1, G_MAXINT,
+ DEFAULT_SEQNUM_OFFSET, G_PARAM_READWRITE));
+ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_CLOCK_RATE,
+ g_param_spec_uint ("clock-rate", "clockrate",
+ "The clock-rate at which to generate the dtmf packets",
+ 0, G_MAXUINT, DEFAULT_CLOCK_RATE, G_PARAM_READWRITE));
+ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SSRC,
+ g_param_spec_uint ("ssrc", "SSRC",
+ "The SSRC of the packets (-1 == random)",
+ 0, G_MAXUINT, DEFAULT_SSRC, G_PARAM_READWRITE));
+ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PT,
+ g_param_spec_uint ("pt", "payload type",
+ "The payload type of the packets",
+ 0, 0x80, DEFAULT_PT, G_PARAM_READWRITE));
+ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_INTERVAL,
+ g_param_spec_uint ("interval", "Interval between rtp packets",
+ "Interval in ms between two rtp packets", MIN_PACKET_INTERVAL,
+ MAX_PACKET_INTERVAL, DEFAULT_PACKET_INTERVAL, G_PARAM_READWRITE));
+ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_REDUNDANCY,
+ g_param_spec_uint ("packet-redundancy", "Packet Redundancy",
+ "Number of packets to send to indicate start and stop dtmf events",
+ MIN_PACKET_REDUNDANCY, MAX_PACKET_REDUNDANCY,
+ DEFAULT_PACKET_REDUNDANCY, G_PARAM_READWRITE));
+
+ gstelement_class->change_state =
+ GST_DEBUG_FUNCPTR (gst_rtp_dtmf_src_change_state);
+
+ gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_rtp_dtmf_src_unlock);
+ gstbasesrc_class->unlock_stop =
+ GST_DEBUG_FUNCPTR (gst_rtp_dtmf_src_unlock_stop);
+
+ gstbasesrc_class->event = GST_DEBUG_FUNCPTR (gst_rtp_dtmf_src_handle_event);
+ gstbasesrc_class->create = GST_DEBUG_FUNCPTR (gst_rtp_dtmf_src_create);
+ gstbasesrc_class->negotiate = GST_DEBUG_FUNCPTR (gst_rtp_dtmf_src_negotiate);
+}
+
+static void
+gst_rtp_dtmf_src_event_free (GstRTPDTMFSrcEvent * event)
+{
+ if (event) {
+ if (event->payload)
+ g_slice_free (GstRTPDTMFPayload, event->payload);
+ g_slice_free (GstRTPDTMFSrcEvent, event);
+ }
+}
+
+static void
+gst_rtp_dtmf_src_init (GstRTPDTMFSrc * object, GstRTPDTMFSrcClass * g_class)
+{
+ gst_base_src_set_format (GST_BASE_SRC (object), GST_FORMAT_TIME);
+ gst_base_src_set_live (GST_BASE_SRC (object), TRUE);
+
+ object->ssrc = DEFAULT_SSRC;
+ object->seqnum_offset = DEFAULT_SEQNUM_OFFSET;
+ object->ts_offset = DEFAULT_TIMESTAMP_OFFSET;
+ object->pt = DEFAULT_PT;
+ object->clock_rate = DEFAULT_CLOCK_RATE;
+ object->interval = DEFAULT_PACKET_INTERVAL;
+ object->packet_redundancy = DEFAULT_PACKET_REDUNDANCY;
+
+ object->event_queue =
+ g_async_queue_new_full ((GDestroyNotify) gst_rtp_dtmf_src_event_free);
+ object->payload = NULL;
+
+ GST_DEBUG_OBJECT (object, "init done");
+}
+
+static void
+gst_rtp_dtmf_src_finalize (GObject * object)
+{
+ GstRTPDTMFSrc *dtmfsrc;
+
+ dtmfsrc = GST_RTP_DTMF_SRC (object);
+
+ if (dtmfsrc->event_queue) {
+ g_async_queue_unref (dtmfsrc->event_queue);
+ dtmfsrc->event_queue = NULL;
+ }
+
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+gst_rtp_dtmf_src_handle_dtmf_event (GstRTPDTMFSrc * dtmfsrc,
+ const GstStructure * event_structure)
+{
+ gint event_type;
+ gboolean start;
+ gint method;
+
+ if (!gst_structure_get_int (event_structure, "type", &event_type) ||
+ !gst_structure_get_boolean (event_structure, "start", &start) ||
+ event_type != GST_RTP_DTMF_TYPE_EVENT)
+ goto failure;
+
+ if (gst_structure_get_int (event_structure, "method", &method)) {
+ if (method != 1) {
+ goto failure;
+ }
+ }
+
+ if (start) {
+ gint event_number;
+ gint event_volume;
+
+ if (!gst_structure_get_int (event_structure, "number", &event_number) ||
+ !gst_structure_get_int (event_structure, "volume", &event_volume))
+ goto failure;
+
+ GST_DEBUG_OBJECT (dtmfsrc, "Received start event %d with volume %d",
+ event_number, event_volume);
+ gst_rtp_dtmf_src_add_start_event (dtmfsrc, event_number, event_volume);
+ }
+
+ else {
+ GST_DEBUG_OBJECT (dtmfsrc, "Received stop event");
+ gst_rtp_dtmf_src_add_stop_event (dtmfsrc);
+ }
+
+ return TRUE;
+failure:
+ return FALSE;
+}
+
+static gboolean
+gst_rtp_dtmf_src_handle_custom_upstream (GstRTPDTMFSrc * dtmfsrc,
+ GstEvent * event)
+{
+ gboolean result = FALSE;
+ gchar *struct_str;
+ const GstStructure *structure;
+
+ GstState state;
+ GstStateChangeReturn ret;
+
+ ret = gst_element_get_state (GST_ELEMENT (dtmfsrc), &state, NULL, 0);
+ if (ret != GST_STATE_CHANGE_SUCCESS || state != GST_STATE_PLAYING) {
+ GST_DEBUG_OBJECT (dtmfsrc, "Received event while not in PLAYING state");
+ goto ret;
+ }
+
+ GST_DEBUG_OBJECT (dtmfsrc, "Received event is of our interest");
+ structure = gst_event_get_structure (event);
+ struct_str = gst_structure_to_string (structure);
+ GST_DEBUG_OBJECT (dtmfsrc, "Event has structure %s", struct_str);
+ g_free (struct_str);
+ if (structure && gst_structure_has_name (structure, "dtmf-event"))
+ result = gst_rtp_dtmf_src_handle_dtmf_event (dtmfsrc, structure);
+
+ret:
+ return result;
+}
+
+static gboolean
+gst_rtp_dtmf_src_handle_event (GstBaseSrc * basesrc, GstEvent * event)
+{
+ GstRTPDTMFSrc *dtmfsrc;
+ gboolean result = FALSE;
+
+ dtmfsrc = GST_RTP_DTMF_SRC (basesrc);
+
+ GST_DEBUG_OBJECT (dtmfsrc, "Received an event on the src pad");
+ if (GST_EVENT_TYPE (event) == GST_EVENT_CUSTOM_UPSTREAM) {
+ result = gst_rtp_dtmf_src_handle_custom_upstream (dtmfsrc, event);
+ }
+
+ return result;
+}
+
+static void
+gst_rtp_dtmf_src_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstRTPDTMFSrc *dtmfsrc;
+
+ dtmfsrc = GST_RTP_DTMF_SRC (object);
+
+ switch (prop_id) {
+ case PROP_TIMESTAMP_OFFSET:
+ dtmfsrc->ts_offset = g_value_get_int (value);
+ break;
+ case PROP_SEQNUM_OFFSET:
+ dtmfsrc->seqnum_offset = g_value_get_int (value);
+ break;
+ case PROP_CLOCK_RATE:
+ dtmfsrc->clock_rate = g_value_get_uint (value);
+ dtmfsrc->dirty = TRUE;
+ break;
+ case PROP_SSRC:
+ dtmfsrc->ssrc = g_value_get_uint (value);
+ break;
+ case PROP_PT:
+ dtmfsrc->pt = g_value_get_uint (value);
+ dtmfsrc->dirty = TRUE;
+ break;
+ case PROP_INTERVAL:
+ dtmfsrc->interval = g_value_get_uint (value);
+ break;
+ case PROP_REDUNDANCY:
+ dtmfsrc->packet_redundancy = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_rtp_dtmf_src_get_property (GObject * object, guint prop_id, GValue * value,
+ GParamSpec * pspec)
+{
+ GstRTPDTMFSrc *dtmfsrc;
+
+ dtmfsrc = GST_RTP_DTMF_SRC (object);
+
+ switch (prop_id) {
+ case PROP_TIMESTAMP_OFFSET:
+ g_value_set_int (value, dtmfsrc->ts_offset);
+ break;
+ case PROP_SEQNUM_OFFSET:
+ g_value_set_int (value, dtmfsrc->seqnum_offset);
+ break;
+ case PROP_CLOCK_RATE:
+ g_value_set_uint (value, dtmfsrc->clock_rate);
+ break;
+ case PROP_SSRC:
+ g_value_set_uint (value, dtmfsrc->ssrc);
+ break;
+ case PROP_PT:
+ g_value_set_uint (value, dtmfsrc->pt);
+ break;
+ case PROP_TIMESTAMP:
+ g_value_set_uint (value, dtmfsrc->rtp_timestamp);
+ break;
+ case PROP_SEQNUM:
+ g_value_set_uint (value, dtmfsrc->seqnum);
+ break;
+ case PROP_INTERVAL:
+ g_value_set_uint (value, dtmfsrc->interval);
+ break;
+ case PROP_REDUNDANCY:
+ g_value_set_uint (value, dtmfsrc->packet_redundancy);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_rtp_dtmf_src_set_stream_lock (GstRTPDTMFSrc * dtmfsrc, gboolean lock)
+{
+ GstEvent *event;
+ GstStructure *structure;
+
+ structure = gst_structure_new ("stream-lock",
+ "lock", G_TYPE_BOOLEAN, lock, NULL);
+
+ event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_OOB, structure);
+ if (!gst_pad_push_event (GST_BASE_SRC_PAD (dtmfsrc), event)) {
+ GST_WARNING_OBJECT (dtmfsrc, "stream-lock event not handled");
+ }
+
+}
+
+static void
+gst_rtp_dtmf_prepare_timestamps (GstRTPDTMFSrc * dtmfsrc)
+{
+ GstClock *clock;
+ GstClockTime base_time;
+
+#ifdef MAEMO_BROKEN
+ base_time = 0;
+#else
+ base_time = gst_element_get_base_time (GST_ELEMENT (dtmfsrc));
+#endif
+
+ clock = gst_element_get_clock (GST_ELEMENT (dtmfsrc));
+ if (clock != NULL) {
+ dtmfsrc->timestamp = gst_clock_get_time (clock)
+ + (MIN_INTER_DIGIT_INTERVAL * GST_MSECOND) - base_time;
+ dtmfsrc->start_timestamp = dtmfsrc->timestamp;
+ gst_object_unref (clock);
+ } else {
+ gchar *dtmf_name = gst_element_get_name (dtmfsrc);
+ GST_ERROR_OBJECT (dtmfsrc, "No clock set for element %s", dtmf_name);
+ dtmfsrc->timestamp = GST_CLOCK_TIME_NONE;
+ g_free (dtmf_name);
+ }
+
+ dtmfsrc->rtp_timestamp = dtmfsrc->ts_base +
+ gst_util_uint64_scale_int (gst_segment_to_running_time (&GST_BASE_SRC
+ (dtmfsrc)->segment, GST_FORMAT_TIME, dtmfsrc->timestamp),
+ dtmfsrc->clock_rate, GST_SECOND);
+}
+
+
+static void
+gst_rtp_dtmf_src_add_start_event (GstRTPDTMFSrc * dtmfsrc, gint event_number,
+ gint event_volume)
+{
+
+ GstRTPDTMFSrcEvent *event = g_slice_new0 (GstRTPDTMFSrcEvent);
+ event->event_type = RTP_DTMF_EVENT_TYPE_START;
+
+ event->payload = g_slice_new0 (GstRTPDTMFPayload);
+ event->payload->event = CLAMP (event_number, MIN_EVENT, MAX_EVENT);
+ event->payload->volume = CLAMP (event_volume, MIN_VOLUME, MAX_VOLUME);
+ event->payload->duration = dtmfsrc->interval * dtmfsrc->clock_rate / 1000;
+
+ g_async_queue_push (dtmfsrc->event_queue, event);
+}
+
+static void
+gst_rtp_dtmf_src_add_stop_event (GstRTPDTMFSrc * dtmfsrc)
+{
+
+ GstRTPDTMFSrcEvent *event = g_slice_new0 (GstRTPDTMFSrcEvent);
+ event->event_type = RTP_DTMF_EVENT_TYPE_STOP;
+
+ g_async_queue_push (dtmfsrc->event_queue, event);
+}
+
+
+static void
+gst_rtp_dtmf_prepare_rtp_headers (GstRTPDTMFSrc * dtmfsrc, GstBuffer * buf)
+{
+ gst_rtp_buffer_set_ssrc (buf, dtmfsrc->current_ssrc);
+ gst_rtp_buffer_set_payload_type (buf, dtmfsrc->pt);
+ /* Only the very first packet gets a marker */
+ if (dtmfsrc->first_packet) {
+ gst_rtp_buffer_set_marker (buf, TRUE);
+ } else if (dtmfsrc->last_packet) {
+ dtmfsrc->payload->e = 1;
+ }
+
+ dtmfsrc->seqnum++;
+ gst_rtp_buffer_set_seq (buf, dtmfsrc->seqnum);
+
+ /* timestamp of RTP header */
+ gst_rtp_buffer_set_timestamp (buf, dtmfsrc->rtp_timestamp);
+}
+
+static void
+gst_rtp_dtmf_prepare_buffer_data (GstRTPDTMFSrc * dtmfsrc, GstBuffer * buf)
+{
+ GstRTPDTMFPayload *payload;
+
+ gst_rtp_dtmf_prepare_rtp_headers (dtmfsrc, buf);
+
+ /* timestamp and duration of GstBuffer */
+ /* Redundant buffer have no duration ... */
+ if (dtmfsrc->redundancy_count > 1)
+ GST_BUFFER_DURATION (buf) = 0;
+ else
+ GST_BUFFER_DURATION (buf) = dtmfsrc->interval * GST_MSECOND;
+ GST_BUFFER_TIMESTAMP (buf) = dtmfsrc->timestamp;
+
+ dtmfsrc->timestamp += GST_BUFFER_DURATION (buf);
+
+ payload = (GstRTPDTMFPayload *) gst_rtp_buffer_get_payload (buf);
+
+ /* copy payload and convert to network-byte order */
+ g_memmove (payload, dtmfsrc->payload, sizeof (GstRTPDTMFPayload));
+ /* Force the packet duration to a certain minumum
+ * if its the end of the event
+ */
+ if (payload->e &&
+ payload->duration < MIN_PULSE_DURATION * dtmfsrc->clock_rate / 1000)
+ payload->duration = MIN_PULSE_DURATION * dtmfsrc->clock_rate / 1000;
+
+ payload->duration = g_htons (payload->duration);
+
+
+ /* duration of DTMF payloadfor the NEXT packet */
+ /* not updated for redundant packets */
+ if (dtmfsrc->redundancy_count == 0)
+ dtmfsrc->payload->duration +=
+ dtmfsrc->interval * dtmfsrc->clock_rate / 1000;
+
+}
+
+static GstBuffer *
+gst_rtp_dtmf_src_create_next_rtp_packet (GstRTPDTMFSrc * dtmfsrc)
+{
+ GstBuffer *buf = NULL;
+
+ /* create buffer to hold the payload */
+ buf = gst_rtp_buffer_new_allocate (sizeof (GstRTPDTMFPayload), 0, 0);
+
+ gst_rtp_dtmf_prepare_buffer_data (dtmfsrc, buf);
+
+ /* Set caps on the buffer before pushing it */
+ gst_buffer_set_caps (buf, GST_PAD_CAPS (GST_BASE_SRC_PAD (dtmfsrc)));
+
+ return buf;
+}
+
+static GstFlowReturn
+gst_rtp_dtmf_src_create (GstBaseSrc * basesrc, guint64 offset,
+ guint length, GstBuffer ** buffer)
+{
+ GstRTPDTMFSrcEvent *event;
+ GstRTPDTMFSrc *dtmfsrc;
+ GstClock *clock;
+ GstClockID *clockid;
+ GstClockReturn clockret;
+
+ dtmfsrc = GST_RTP_DTMF_SRC (basesrc);
+
+ do {
+
+ if (dtmfsrc->payload == NULL) {
+ GST_DEBUG_OBJECT (dtmfsrc, "popping");
+ event = g_async_queue_pop (dtmfsrc->event_queue);
+
+ GST_DEBUG_OBJECT (dtmfsrc, "popped %d", event->event_type);
+
+ switch (event->event_type) {
+ case RTP_DTMF_EVENT_TYPE_STOP:
+ GST_WARNING_OBJECT (dtmfsrc,
+ "Received a DTMF stop event when already stopped");
+ break;
+
+ case RTP_DTMF_EVENT_TYPE_START:
+ dtmfsrc->first_packet = TRUE;
+ dtmfsrc->last_packet = FALSE;
+ /* Set the redundancy on the first packet */
+ dtmfsrc->redundancy_count = dtmfsrc->packet_redundancy;
+ gst_rtp_dtmf_prepare_timestamps (dtmfsrc);
+
+ /* Don't forget to get exclusive access to the stream */
+ gst_rtp_dtmf_src_set_stream_lock (dtmfsrc, TRUE);
+
+ dtmfsrc->payload = event->payload;
+ event->payload = NULL;
+ break;
+
+ case RTP_DTMF_EVENT_TYPE_PAUSE_TASK:
+ /*
+ * We're pushing it back because it has to stay in there until
+ * the task is really paused (and the queue will then be flushed
+ */
+ GST_OBJECT_LOCK (dtmfsrc);
+ if (dtmfsrc->paused) {
+ g_async_queue_push (dtmfsrc->event_queue, event);
+ goto paused_locked;
+ }
+ GST_OBJECT_UNLOCK (dtmfsrc);
+ break;
+ }
+
+ gst_rtp_dtmf_src_event_free (event);
+ } else if (!dtmfsrc->first_packet && !dtmfsrc->last_packet &&
+ (dtmfsrc->timestamp - dtmfsrc->start_timestamp) / GST_MSECOND >=
+ MIN_PULSE_DURATION) {
+ GST_DEBUG_OBJECT (dtmfsrc, "try popping");
+ event = g_async_queue_try_pop (dtmfsrc->event_queue);
+
+
+ if (event != NULL) {
+ GST_DEBUG_OBJECT (dtmfsrc, "try popped %d", event->event_type);
+
+ switch (event->event_type) {
+ case RTP_DTMF_EVENT_TYPE_START:
+ GST_WARNING_OBJECT (dtmfsrc,
+ "Received two consecutive DTMF start events");
+ break;
+
+ case RTP_DTMF_EVENT_TYPE_STOP:
+ dtmfsrc->first_packet = FALSE;
+ dtmfsrc->last_packet = TRUE;
+ /* Set the redundancy on the last packet */
+ dtmfsrc->redundancy_count = dtmfsrc->packet_redundancy;
+ break;
+
+ case RTP_DTMF_EVENT_TYPE_PAUSE_TASK:
+ /*
+ * We're pushing it back because it has to stay in there until
+ * the task is really paused (and the queue will then be flushed)
+ */
+ GST_DEBUG_OBJECT (dtmfsrc, "pushing pause_task...");
+ GST_OBJECT_LOCK (dtmfsrc);
+ if (dtmfsrc->paused) {
+ g_async_queue_push (dtmfsrc->event_queue, event);
+ goto paused_locked;
+ }
+ GST_OBJECT_UNLOCK (dtmfsrc);
+ break;
+ }
+ gst_rtp_dtmf_src_event_free (event);
+ }
+ }
+ } while (dtmfsrc->payload == NULL);
+
+
+ GST_DEBUG_OBJECT (dtmfsrc, "Processed events, now lets wait on the clock");
+
+ clock = gst_element_get_clock (GST_ELEMENT (basesrc));
+
+#ifdef MAEMO_BROKEN
+ clockid = gst_clock_new_single_shot_id (clock, dtmfsrc->timestamp);
+#else
+ clockid = gst_clock_new_single_shot_id (clock, dtmfsrc->timestamp +
+ gst_element_get_base_time (GST_ELEMENT (dtmfsrc)));
+#endif
+ gst_object_unref (clock);
+
+ GST_OBJECT_LOCK (dtmfsrc);
+ if (!dtmfsrc->paused) {
+ dtmfsrc->clockid = clockid;
+ GST_OBJECT_UNLOCK (dtmfsrc);
+
+ clockret = gst_clock_id_wait (clockid, NULL);
+
+ GST_OBJECT_LOCK (dtmfsrc);
+ if (dtmfsrc->paused)
+ clockret = GST_CLOCK_UNSCHEDULED;
+ } else {
+ clockret = GST_CLOCK_UNSCHEDULED;
+ }
+ gst_clock_id_unref (clockid);
+ dtmfsrc->clockid = NULL;
+ GST_OBJECT_UNLOCK (dtmfsrc);
+
+ if (clockret == GST_CLOCK_UNSCHEDULED) {
+ goto paused;
+ }
+
+send_last:
+
+ if (dtmfsrc->dirty)
+ if (!gst_rtp_dtmf_src_negotiate (basesrc))
+ return GST_FLOW_NOT_NEGOTIATED;
+
+ /* create buffer to hold the payload */
+ *buffer = gst_rtp_dtmf_src_create_next_rtp_packet (dtmfsrc);
+
+ if (dtmfsrc->redundancy_count)
+ dtmfsrc->redundancy_count--;
+
+ /* Only the very first one has a marker */
+ dtmfsrc->first_packet = FALSE;
+
+ /* This is the end of the event */
+ if (dtmfsrc->last_packet == TRUE && dtmfsrc->redundancy_count == 0) {
+
+ /* Don't forget to release the stream lock */
+ gst_rtp_dtmf_src_set_stream_lock (dtmfsrc, FALSE);
+
+ g_slice_free (GstRTPDTMFPayload, dtmfsrc->payload);
+ dtmfsrc->payload = NULL;
+
+ dtmfsrc->last_packet = FALSE;
+ }
+
+ return GST_FLOW_OK;
+
+paused_locked:
+
+ GST_OBJECT_UNLOCK (dtmfsrc);
+
+paused:
+
+ if (dtmfsrc->payload) {
+ dtmfsrc->first_packet = FALSE;
+ dtmfsrc->last_packet = TRUE;
+ /* Set the redundanc on the last packet */
+ dtmfsrc->redundancy_count = dtmfsrc->packet_redundancy;
+ goto send_last;
+ } else {
+ return GST_FLOW_WRONG_STATE;
+ }
+}
+
+
+static gboolean
+gst_rtp_dtmf_src_negotiate (GstBaseSrc * basesrc)
+{
+ GstCaps *srccaps, *peercaps;
+ GstRTPDTMFSrc *dtmfsrc = GST_RTP_DTMF_SRC (basesrc);
+ gboolean ret;
+
+ /* fill in the defaults, there properties cannot be negotiated. */
+ srccaps = gst_caps_new_simple ("application/x-rtp",
+ "media", G_TYPE_STRING, "audio",
+ "encoding-name", G_TYPE_STRING, "TELEPHONE-EVENT", NULL);
+
+ /* the peer caps can override some of the defaults */
+ peercaps = gst_pad_peer_get_caps (GST_BASE_SRC_PAD (basesrc));
+ if (peercaps == NULL) {
+ /* no peer caps, just add the other properties */
+ gst_caps_set_simple (srccaps,
+ "payload", G_TYPE_INT, dtmfsrc->pt,
+ "ssrc", G_TYPE_UINT, dtmfsrc->current_ssrc,
+ "clock-base", G_TYPE_UINT, dtmfsrc->ts_base,
+ "clock-rate", G_TYPE_INT, dtmfsrc->clock_rate,
+ "seqnum-base", G_TYPE_UINT, dtmfsrc->seqnum_base, NULL);
+
+ GST_DEBUG_OBJECT (dtmfsrc, "no peer caps: %" GST_PTR_FORMAT, srccaps);
+ } else {
+ GstCaps *temp;
+ GstStructure *s;
+ const GValue *value;
+ gint pt;
+ gint clock_rate;
+
+ /* peer provides caps we can use to fixate, intersect. This always returns a
+ * writable caps. */
+ temp = gst_caps_intersect (srccaps, peercaps);
+ gst_caps_unref (srccaps);
+ gst_caps_unref (peercaps);
+
+ if (!temp) {
+ GST_DEBUG_OBJECT (dtmfsrc, "Could not get intersection with peer caps");
+ return FALSE;
+ }
+
+ if (gst_caps_is_empty (temp)) {
+ GST_DEBUG_OBJECT (dtmfsrc, "Intersection with peer caps is empty");
+ gst_caps_unref (temp);
+ return FALSE;
+ }
+
+ /* now fixate, start by taking the first caps */
+ gst_caps_truncate (temp);
+ srccaps = temp;
+
+ /* get first structure */
+ s = gst_caps_get_structure (srccaps, 0);
+
+ if (gst_structure_get_int (s, "payload", &pt)) {
+ /* use peer pt */
+ dtmfsrc->pt = pt;
+ GST_LOG_OBJECT (dtmfsrc, "using peer pt %d", pt);
+ } else {
+ if (gst_structure_has_field (s, "payload")) {
+ /* can only fixate if there is a field */
+ gst_structure_fixate_field_nearest_int (s, "payload", dtmfsrc->pt);
+ gst_structure_get_int (s, "payload", &pt);
+ GST_LOG_OBJECT (dtmfsrc, "using peer pt %d", pt);
+ } else {
+ /* no pt field, use the internal pt */
+ pt = dtmfsrc->pt;
+ gst_structure_set (s, "payload", G_TYPE_INT, pt, NULL);
+ GST_LOG_OBJECT (dtmfsrc, "using internal pt %d", pt);
+ }
+ }
+
+ if (gst_structure_get_int (s, "clock-rate", &clock_rate)) {
+ dtmfsrc->clock_rate = clock_rate;
+ GST_LOG_OBJECT (dtmfsrc, "using clock-rate from caps %d",
+ dtmfsrc->clock_rate);
+ } else {
+ GST_LOG_OBJECT (dtmfsrc, "using existing clock-rate %d",
+ dtmfsrc->clock_rate);
+ }
+ gst_structure_set (s, "clock-rate", G_TYPE_INT, dtmfsrc->clock_rate, NULL);
+
+
+ if (gst_structure_has_field_typed (s, "ssrc", G_TYPE_UINT)) {
+ value = gst_structure_get_value (s, "ssrc");
+ dtmfsrc->current_ssrc = g_value_get_uint (value);
+ GST_LOG_OBJECT (dtmfsrc, "using peer ssrc %08x", dtmfsrc->current_ssrc);
+ } else {
+ /* FIXME, fixate_nearest_uint would be even better */
+ gst_structure_set (s, "ssrc", G_TYPE_UINT, dtmfsrc->current_ssrc, NULL);
+ GST_LOG_OBJECT (dtmfsrc, "using internal ssrc %08x",
+ dtmfsrc->current_ssrc);
+ }
+
+ if (gst_structure_has_field_typed (s, "clock-base", G_TYPE_UINT)) {
+ value = gst_structure_get_value (s, "clock-base");
+ dtmfsrc->ts_base = g_value_get_uint (value);
+ GST_LOG_OBJECT (dtmfsrc, "using peer clock-base %u", dtmfsrc->ts_base);
+ } else {
+ /* FIXME, fixate_nearest_uint would be even better */
+ gst_structure_set (s, "clock-base", G_TYPE_UINT, dtmfsrc->ts_base, NULL);
+ GST_LOG_OBJECT (dtmfsrc, "using internal clock-base %u",
+ dtmfsrc->ts_base);
+ }
+ if (gst_structure_has_field_typed (s, "seqnum-base", G_TYPE_UINT)) {
+ value = gst_structure_get_value (s, "seqnum-base");
+ dtmfsrc->seqnum_base = g_value_get_uint (value);
+ GST_LOG_OBJECT (dtmfsrc, "using peer seqnum-base %u",
+ dtmfsrc->seqnum_base);
+ } else {
+ /* FIXME, fixate_nearest_uint would be even better */
+ gst_structure_set (s, "seqnum-base", G_TYPE_UINT, dtmfsrc->seqnum_base,
+ NULL);
+ GST_LOG_OBJECT (dtmfsrc, "using internal seqnum-base %u",
+ dtmfsrc->seqnum_base);
+ }
+ GST_DEBUG_OBJECT (dtmfsrc, "with peer caps: %" GST_PTR_FORMAT, srccaps);
+ }
+
+ ret = gst_pad_set_caps (GST_BASE_SRC_PAD (basesrc), srccaps);
+ gst_caps_unref (srccaps);
+
+ dtmfsrc->dirty = FALSE;
+
+ return ret;
+
+}
+
+
+static void
+gst_rtp_dtmf_src_ready_to_paused (GstRTPDTMFSrc * dtmfsrc)
+{
+ if (dtmfsrc->ssrc == -1)
+ dtmfsrc->current_ssrc = g_random_int ();
+ else
+ dtmfsrc->current_ssrc = dtmfsrc->ssrc;
+
+ if (dtmfsrc->seqnum_offset == -1)
+ dtmfsrc->seqnum_base = g_random_int_range (0, G_MAXUINT16);
+ else
+ dtmfsrc->seqnum_base = dtmfsrc->seqnum_offset;
+ dtmfsrc->seqnum = dtmfsrc->seqnum_base;
+
+ if (dtmfsrc->ts_offset == -1)
+ dtmfsrc->ts_base = g_random_int ();
+ else
+ dtmfsrc->ts_base = dtmfsrc->ts_offset;
+
+}
+
+static GstStateChangeReturn
+gst_rtp_dtmf_src_change_state (GstElement * element, GstStateChange transition)
+{
+ GstRTPDTMFSrc *dtmfsrc;
+ GstStateChangeReturn result;
+ gboolean no_preroll = FALSE;
+ GstRTPDTMFSrcEvent *event = NULL;
+
+ dtmfsrc = GST_RTP_DTMF_SRC (element);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ gst_rtp_dtmf_src_ready_to_paused (dtmfsrc);
+
+ /* Flushing the event queue */
+ while ((event = g_async_queue_try_pop (dtmfsrc->event_queue)) != NULL)
+ gst_rtp_dtmf_src_event_free (event);
+
+ no_preroll = TRUE;
+ break;
+ default:
+ break;
+ }
+
+ if ((result =
+ GST_ELEMENT_CLASS (parent_class)->change_state (element,
+ transition)) == GST_STATE_CHANGE_FAILURE)
+ goto failure;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ no_preroll = TRUE;
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+
+ /* Flushing the event queue */
+ while ((event = g_async_queue_try_pop (dtmfsrc->event_queue)) != NULL)
+ gst_rtp_dtmf_src_event_free (event);
+
+ /* Indicate that we don't do PRE_ROLL */
+ break;
+
+ default:
+ break;
+ }
+
+ if (no_preroll && result == GST_STATE_CHANGE_SUCCESS)
+ result = GST_STATE_CHANGE_NO_PREROLL;
+
+ return result;
+
+ /* ERRORS */
+failure:
+ {
+ GST_ERROR_OBJECT (dtmfsrc, "parent failed state change");
+ return result;
+ }
+}
+
+
+static gboolean
+gst_rtp_dtmf_src_unlock (GstBaseSrc * src)
+{
+ GstRTPDTMFSrc *dtmfsrc = GST_RTP_DTMF_SRC (src);
+ GstRTPDTMFSrcEvent *event = NULL;
+
+ GST_DEBUG_OBJECT (dtmfsrc, "Called unlock");
+
+ GST_OBJECT_LOCK (dtmfsrc);
+ dtmfsrc->paused = TRUE;
+ if (dtmfsrc->clockid) {
+ gst_clock_id_unschedule (dtmfsrc->clockid);
+ }
+ GST_OBJECT_UNLOCK (dtmfsrc);
+
+ GST_DEBUG_OBJECT (dtmfsrc, "Pushing the PAUSE_TASK event on unlock request");
+ event = g_slice_new0 (GstRTPDTMFSrcEvent);
+ event->event_type = RTP_DTMF_EVENT_TYPE_PAUSE_TASK;
+ g_async_queue_push (dtmfsrc->event_queue, event);
+
+ return TRUE;
+}
+
+
+static gboolean
+gst_rtp_dtmf_src_unlock_stop (GstBaseSrc * src)
+{
+ GstRTPDTMFSrc *dtmfsrc = GST_RTP_DTMF_SRC (src);
+
+ GST_DEBUG_OBJECT (dtmfsrc, "Unlock stopped");
+
+ GST_OBJECT_LOCK (dtmfsrc);
+ dtmfsrc->paused = FALSE;
+ GST_OBJECT_UNLOCK (dtmfsrc);
+
+ return TRUE;
+}
+
+gboolean
+gst_rtp_dtmf_src_plugin_init (GstPlugin * plugin)
+{
+ return gst_element_register (plugin, "rtpdtmfsrc",
+ GST_RANK_NONE, GST_TYPE_RTP_DTMF_SRC);
+}
diff --git a/farsight/dtmf/gstrtpdtmfsrc.h b/farsight/dtmf/gstrtpdtmfsrc.h
new file mode 100644
index 0000000..b1a483a
--- /dev/null
+++ b/farsight/dtmf/gstrtpdtmfsrc.h
@@ -0,0 +1,112 @@
+/* GStreamer RTP DTMF source
+ *
+ * gstrtpdtmfsrc.h:
+ *
+ * Copyright (C) <2007> Nokia Corporation.
+ * Contact: Zeeshan Ali <zeeshan.ali@nokia.com>
+ * Copyright (C) <2005> Wim Taymans <wim@fluendo.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GST_RTP_DTMF_SRC_H__
+#define __GST_RTP_DTMF_SRC_H__
+
+#include <gst/gst.h>
+#include <gst/base/gstbasesrc.h>
+#include <gst/rtp/gstrtpbuffer.h>
+
+#include "gstrtpdtmfcommon.h"
+
+G_BEGIN_DECLS
+#define GST_TYPE_RTP_DTMF_SRC (gst_rtp_dtmf_src_get_type())
+#define GST_RTP_DTMF_SRC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_DTMF_SRC,GstRTPDTMFSrc))
+#define GST_RTP_DTMF_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_DTMF_SRC,GstRTPDTMFSrcClass))
+#define GST_RTP_DTMF_SRC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTP_DTMF_SRC, GstRTPDTMFSrcClass))
+#define GST_IS_RTP_DTMF_SRC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_DTMF_SRC))
+#define GST_IS_RTP_DTMF_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_DTMF_SRC))
+#define GST_RTP_DTMF_SRC_CAST(obj) ((GstRTPDTMFSrc *)(obj))
+typedef struct _GstRTPDTMFSrc GstRTPDTMFSrc;
+typedef struct _GstRTPDTMFSrcClass GstRTPDTMFSrcClass;
+
+
+
+enum _GstRTPDTMFEventType
+{
+ RTP_DTMF_EVENT_TYPE_START,
+ RTP_DTMF_EVENT_TYPE_STOP,
+ RTP_DTMF_EVENT_TYPE_PAUSE_TASK
+};
+
+typedef enum _GstRTPDTMFEventType GstRTPDTMFEventType;
+
+struct _GstRTPDTMFSrcEvent
+{
+ GstRTPDTMFEventType event_type;
+ GstRTPDTMFPayload *payload;
+};
+
+typedef struct _GstRTPDTMFSrcEvent GstRTPDTMFSrcEvent;
+
+/**
+ * GstRTPDTMFSrc:
+ * @element: the parent element.
+ *
+ * The opaque #GstRTPDTMFSrc data structure.
+ */
+struct _GstRTPDTMFSrc
+{
+ /*< private >*/
+ GstBaseSrc basesrc;
+
+ GAsyncQueue *event_queue;
+ GstClockID clockid;
+ gboolean paused;
+ GstRTPDTMFPayload *payload;
+
+ GstClockTime timestamp;
+ GstClockTime start_timestamp;
+ gboolean first_packet;
+ gboolean last_packet;
+ guint32 ts_base;
+ guint16 seqnum_base;
+ gint16 seqnum_offset;
+ guint16 seqnum;
+ gint32 ts_offset;
+ guint32 rtp_timestamp;
+ guint pt;
+ guint ssrc;
+ guint current_ssrc;
+ guint16 interval;
+ guint16 packet_redundancy;
+ guint32 clock_rate;
+
+ gboolean dirty;
+ guint16 redundancy_count;
+};
+
+struct _GstRTPDTMFSrcClass
+{
+ GstBaseSrcClass parent_class;
+};
+
+GType gst_rtp_dtmf_src_get_type (void);
+
+gboolean gst_rtp_dtmf_src_plugin_init (GstPlugin * plugin);
+
+
+G_END_DECLS
+#endif /* __GST_RTP_DTMF_SRC_H__ */
diff --git a/farsight/dtmf/tone_detect.c b/farsight/dtmf/tone_detect.c
new file mode 100644
index 0000000..d8f1df1
--- /dev/null
+++ b/farsight/dtmf/tone_detect.c
@@ -0,0 +1,499 @@
+/*
+ * DTMF Receiver module, part of:
+ * BSD Telephony Of Mexico "Zapata" Telecom Library, version 1.10 12/9/01
+ *
+ * Part of the "Zapata" Computer Telephony Technology.
+ *
+ * See http://www.bsdtelephony.com.mx
+ *
+ *
+ * The technologies, software, hardware, designs, drawings, scheumatics, board
+ * layouts and/or artwork, concepts, methodologies (including the use of all
+ * of these, and that which is derived from the use of all of these), all other
+ * intellectual properties contained herein, and all intellectual property
+ * rights have been and shall continue to be expressly for the benefit of all
+ * mankind, and are perpetually placed in the public domain, and may be used,
+ * copied, and/or modified by anyone, in any manner, for any legal purpose,
+ * without restriction.
+ *
+ * This module written by Stephen Underwood.
+ */
+
+/*
+ tone_detect.c - General telephony tone detection, and specific
+ detection of DTMF.
+
+ Copyright (C) 2001 Steve Underwood <steveu@coppice.org>
+
+ Despite my general liking of the GPL, I place this code in the
+ public domain for the benefit of all mankind - even the slimy
+ ones who might try to proprietize my work and use it to my
+ detriment.
+*/
+
+#include <math.h>
+#include <string.h>
+#include <stdio.h>
+#include <time.h>
+#include <fcntl.h>
+#include "tone_detect.h"
+
+#define FALSE 0
+#define TRUE (!FALSE)
+
+//#define USE_3DNOW
+
+/* Basic DTMF specs:
+ *
+ * Minimum tone on = 40ms
+ * Minimum tone off = 50ms
+ * Maximum digit rate = 10 per second
+ * Normal twist <= 8dB accepted
+ * Reverse twist <= 4dB accepted
+ * S/N >= 15dB will detect OK
+ * Attenuation <= 26dB will detect OK
+ * Frequency tolerance +- 1.5% will detect, +-3.5% will reject
+ */
+
+#define SAMPLE_RATE 8000.0
+
+#define DTMF_THRESHOLD 8.0e7
+#define FAX_THRESHOLD 8.0e7
+#define FAX_2ND_HARMONIC 2.0 /* 4dB */
+#define DTMF_NORMAL_TWIST 6.3 /* 8dB */
+#define DTMF_REVERSE_TWIST ((isradio) ? 4.0 : 2.5) /* 4dB normal */
+#define DTMF_RELATIVE_PEAK_ROW 6.3 /* 8dB */
+#define DTMF_RELATIVE_PEAK_COL 6.3 /* 8dB */
+#define DTMF_2ND_HARMONIC_ROW ((isradio) ? 1.7 : 2.5) /* 4dB normal */
+#define DTMF_2ND_HARMONIC_COL 63.1 /* 18dB */
+
+static tone_detection_descriptor_t dtmf_detect_row[4];
+static tone_detection_descriptor_t dtmf_detect_col[4];
+static tone_detection_descriptor_t dtmf_detect_row_2nd[4];
+static tone_detection_descriptor_t dtmf_detect_col_2nd[4];
+static tone_detection_descriptor_t fax_detect;
+static tone_detection_descriptor_t fax_detect_2nd;
+
+static float dtmf_row[] = {
+ 697.0, 770.0, 852.0, 941.0
+};
+
+static float dtmf_col[] = {
+ 1209.0, 1336.0, 1477.0, 1633.0
+};
+
+static float fax_freq = 1100.0;
+
+static char dtmf_positions[] = "123A" "456B" "789C" "*0#D";
+
+static void
+goertzel_init (goertzel_state_t * s, tone_detection_descriptor_t * t)
+{
+ s->v2 = s->v3 = 0.0;
+ s->fac = t->fac;
+}
+
+/*- End of function --------------------------------------------------------*/
+
+#if defined(USE_3DNOW)
+static inline void
+_dtmf_goertzel_update (goertzel_state_t * s, float x[], int samples)
+{
+ int n;
+ float v;
+ int i;
+ float vv[16];
+
+ vv[4] = s[0].v2;
+ vv[5] = s[1].v2;
+ vv[6] = s[2].v2;
+ vv[7] = s[3].v2;
+ vv[8] = s[0].v3;
+ vv[9] = s[1].v3;
+ vv[10] = s[2].v3;
+ vv[11] = s[3].v3;
+ vv[12] = s[0].fac;
+ vv[13] = s[1].fac;
+ vv[14] = s[2].fac;
+ vv[15] = s[3].fac;
+
+ //v1 = s->v2;
+ //s->v2 = s->v3;
+ //s->v3 = s->fac*s->v2 - v1 + x[0];
+
+ __asm__ __volatile__ (" femms;\n"
+ " movq 16(%%edx),%%mm2;\n"
+ " movq 24(%%edx),%%mm3;\n"
+ " movq 32(%%edx),%%mm4;\n"
+ " movq 40(%%edx),%%mm5;\n"
+ " movq 48(%%edx),%%mm6;\n"
+ " movq 56(%%edx),%%mm7;\n"
+ " jmp 1f;\n"
+ " .align 32;\n"
+ " 1: ;\n"
+ " prefetch (%%eax);\n"
+ " movq %%mm3,%%mm1;\n"
+ " movq %%mm2,%%mm0;\n"
+ " movq %%mm5,%%mm3;\n"
+ " movq %%mm4,%%mm2;\n"
+ " pfmul %%mm7,%%mm5;\n"
+ " pfmul %%mm6,%%mm4;\n"
+ " pfsub %%mm1,%%mm5;\n"
+ " pfsub %%mm0,%%mm4;\n"
+ " movq (%%eax),%%mm0;\n"
+ " movq %%mm0,%%mm1;\n"
+ " punpckldq %%mm0,%%mm1;\n"
+ " add $4,%%eax;\n"
+ " pfadd %%mm1,%%mm5;\n"
+ " pfadd %%mm1,%%mm4;\n"
+ " dec %%ecx;\n"
+ " jnz 1b;\n"
+ " movq %%mm2,16(%%edx);\n"
+ " movq %%mm3,24(%%edx);\n"
+ " movq %%mm4,32(%%edx);\n"
+ " movq %%mm5,40(%%edx);\n"
+ " femms;\n"::"c" (samples), "a" (x), "d" (vv)
+ :"memory", "eax", "ecx");
+
+ s[0].v2 = vv[4];
+ s[1].v2 = vv[5];
+ s[2].v2 = vv[6];
+ s[3].v2 = vv[7];
+ s[0].v3 = vv[8];
+ s[1].v3 = vv[9];
+ s[2].v3 = vv[10];
+ s[3].v3 = vv[11];
+}
+#endif
+/*- End of function --------------------------------------------------------*/
+
+void
+zap_goertzel_update (goertzel_state_t * s, int16_t x[], int samples)
+{
+ int i;
+ float v1;
+
+ for (i = 0; i < samples; i++) {
+ v1 = s->v2;
+ s->v2 = s->v3;
+ s->v3 = s->fac * s->v2 - v1 + x[i];
+ }
+}
+
+/*- End of function --------------------------------------------------------*/
+
+float
+zap_goertzel_result (goertzel_state_t * s)
+{
+ return s->v3 * s->v3 + s->v2 * s->v2 - s->v2 * s->v3 * s->fac;
+}
+
+/*- End of function --------------------------------------------------------*/
+
+void
+zap_dtmf_detect_init (dtmf_detect_state_t * s)
+{
+ int i;
+ float theta;
+
+ s->hit1 = s->hit2 = 0;
+
+ for (i = 0; i < 4; i++) {
+ theta = 2.0 * M_PI * (dtmf_row[i] / SAMPLE_RATE);
+ dtmf_detect_row[i].fac = 2.0 * cos (theta);
+
+ theta = 2.0 * M_PI * (dtmf_col[i] / SAMPLE_RATE);
+ dtmf_detect_col[i].fac = 2.0 * cos (theta);
+
+ theta = 2.0 * M_PI * (dtmf_row[i] * 2.0 / SAMPLE_RATE);
+ dtmf_detect_row_2nd[i].fac = 2.0 * cos (theta);
+
+ theta = 2.0 * M_PI * (dtmf_col[i] * 2.0 / SAMPLE_RATE);
+ dtmf_detect_col_2nd[i].fac = 2.0 * cos (theta);
+
+ goertzel_init (&s->row_out[i], &dtmf_detect_row[i]);
+ goertzel_init (&s->col_out[i], &dtmf_detect_col[i]);
+ goertzel_init (&s->row_out2nd[i], &dtmf_detect_row_2nd[i]);
+ goertzel_init (&s->col_out2nd[i], &dtmf_detect_col_2nd[i]);
+
+ s->energy = 0.0;
+ }
+
+ /* Same for the fax dector */
+ theta = 2.0 * M_PI * (fax_freq / SAMPLE_RATE);
+ fax_detect.fac = 2.0 * cos (theta);
+ goertzel_init (&s->fax_tone, &fax_detect);
+
+ /* Same for the fax dector 2nd harmonic */
+ theta = 2.0 * M_PI * (fax_freq * 2.0 / SAMPLE_RATE);
+ fax_detect_2nd.fac = 2.0 * cos (theta);
+ goertzel_init (&s->fax_tone2nd, &fax_detect_2nd);
+
+ s->current_sample = 0;
+ s->detected_digits = 0;
+ s->lost_digits = 0;
+ s->digits[0] = '\0';
+ s->mhit = 0;
+}
+
+/*- End of function --------------------------------------------------------*/
+
+int
+zap_dtmf_detect (dtmf_detect_state_t * s,
+ int16_t amp[], int samples, int isradio)
+{
+
+ float row_energy[4];
+ float col_energy[4];
+ float fax_energy;
+ float fax_energy_2nd;
+ float famp;
+ float v1;
+ int i;
+ int j;
+ int sample;
+ int best_row;
+ int best_col;
+ int hit;
+ int limit;
+
+ hit = 0;
+ for (sample = 0; sample < samples; sample = limit) {
+ /* 102 is optimised to meet the DTMF specs. */
+ if ((samples - sample) >= (102 - s->current_sample))
+ limit = sample + (102 - s->current_sample);
+ else
+ limit = samples;
+#if defined(USE_3DNOW)
+ _dtmf_goertzel_update (s->row_out, amp + sample, limit - sample);
+ _dtmf_goertzel_update (s->col_out, amp + sample, limit - sample);
+ _dtmf_goertzel_update (s->row_out2nd, amp + sample, limit2 - sample);
+ _dtmf_goertzel_update (s->col_out2nd, amp + sample, limit2 - sample);
+ /* XXX Need to fax detect for 3dnow too XXX */
+#warning "Fax Support Broken"
+#else
+ /* The following unrolled loop takes only 35% (rough estimate) of the
+ time of a rolled loop on the machine on which it was developed */
+ for (j = sample; j < limit; j++) {
+ famp = amp[j];
+
+ s->energy += famp * famp;
+
+ /* With GCC 2.95, the following unrolled code seems to take about 35%
+ (rough estimate) as long as a neat little 0-3 loop */
+ v1 = s->row_out[0].v2;
+ s->row_out[0].v2 = s->row_out[0].v3;
+ s->row_out[0].v3 = s->row_out[0].fac * s->row_out[0].v2 - v1 + famp;
+
+ v1 = s->col_out[0].v2;
+ s->col_out[0].v2 = s->col_out[0].v3;
+ s->col_out[0].v3 = s->col_out[0].fac * s->col_out[0].v2 - v1 + famp;
+
+ v1 = s->row_out[1].v2;
+ s->row_out[1].v2 = s->row_out[1].v3;
+ s->row_out[1].v3 = s->row_out[1].fac * s->row_out[1].v2 - v1 + famp;
+
+ v1 = s->col_out[1].v2;
+ s->col_out[1].v2 = s->col_out[1].v3;
+ s->col_out[1].v3 = s->col_out[1].fac * s->col_out[1].v2 - v1 + famp;
+
+ v1 = s->row_out[2].v2;
+ s->row_out[2].v2 = s->row_out[2].v3;
+ s->row_out[2].v3 = s->row_out[2].fac * s->row_out[2].v2 - v1 + famp;
+
+ v1 = s->col_out[2].v2;
+ s->col_out[2].v2 = s->col_out[2].v3;
+ s->col_out[2].v3 = s->col_out[2].fac * s->col_out[2].v2 - v1 + famp;
+
+ v1 = s->row_out[3].v2;
+ s->row_out[3].v2 = s->row_out[3].v3;
+ s->row_out[3].v3 = s->row_out[3].fac * s->row_out[3].v2 - v1 + famp;
+
+ v1 = s->col_out[3].v2;
+ s->col_out[3].v2 = s->col_out[3].v3;
+ s->col_out[3].v3 = s->col_out[3].fac * s->col_out[3].v2 - v1 + famp;
+
+ v1 = s->col_out2nd[0].v2;
+ s->col_out2nd[0].v2 = s->col_out2nd[0].v3;
+ s->col_out2nd[0].v3 =
+ s->col_out2nd[0].fac * s->col_out2nd[0].v2 - v1 + famp;
+
+ v1 = s->row_out2nd[0].v2;
+ s->row_out2nd[0].v2 = s->row_out2nd[0].v3;
+ s->row_out2nd[0].v3 =
+ s->row_out2nd[0].fac * s->row_out2nd[0].v2 - v1 + famp;
+
+ v1 = s->col_out2nd[1].v2;
+ s->col_out2nd[1].v2 = s->col_out2nd[1].v3;
+ s->col_out2nd[1].v3 =
+ s->col_out2nd[1].fac * s->col_out2nd[1].v2 - v1 + famp;
+
+ v1 = s->row_out2nd[1].v2;
+ s->row_out2nd[1].v2 = s->row_out2nd[1].v3;
+ s->row_out2nd[1].v3 =
+ s->row_out2nd[1].fac * s->row_out2nd[1].v2 - v1 + famp;
+
+ v1 = s->col_out2nd[2].v2;
+ s->col_out2nd[2].v2 = s->col_out2nd[2].v3;
+ s->col_out2nd[2].v3 =
+ s->col_out2nd[2].fac * s->col_out2nd[2].v2 - v1 + famp;
+
+ v1 = s->row_out2nd[2].v2;
+ s->row_out2nd[2].v2 = s->row_out2nd[2].v3;
+ s->row_out2nd[2].v3 =
+ s->row_out2nd[2].fac * s->row_out2nd[2].v2 - v1 + famp;
+
+ v1 = s->col_out2nd[3].v2;
+ s->col_out2nd[3].v2 = s->col_out2nd[3].v3;
+ s->col_out2nd[3].v3 =
+ s->col_out2nd[3].fac * s->col_out2nd[3].v2 - v1 + famp;
+
+ v1 = s->row_out2nd[3].v2;
+ s->row_out2nd[3].v2 = s->row_out2nd[3].v3;
+ s->row_out2nd[3].v3 =
+ s->row_out2nd[3].fac * s->row_out2nd[3].v2 - v1 + famp;
+
+ /* Update fax tone */
+ v1 = s->fax_tone.v2;
+ s->fax_tone.v2 = s->fax_tone.v3;
+ s->fax_tone.v3 = s->fax_tone.fac * s->fax_tone.v2 - v1 + famp;
+
+ v1 = s->fax_tone.v2;
+ s->fax_tone2nd.v2 = s->fax_tone2nd.v3;
+ s->fax_tone2nd.v3 = s->fax_tone2nd.fac * s->fax_tone2nd.v2 - v1 + famp;
+ }
+#endif
+ s->current_sample += (limit - sample);
+ if (s->current_sample < 102)
+ continue;
+
+ /* Detect the fax energy, too */
+ fax_energy = zap_goertzel_result (&s->fax_tone);
+
+ /* We are at the end of a DTMF detection block */
+ /* Find the peak row and the peak column */
+ row_energy[0] = zap_goertzel_result (&s->row_out[0]);
+ col_energy[0] = zap_goertzel_result (&s->col_out[0]);
+
+ for (best_row = best_col = 0, i = 1; i < 4; i++) {
+ row_energy[i] = zap_goertzel_result (&s->row_out[i]);
+ if (row_energy[i] > row_energy[best_row])
+ best_row = i;
+ col_energy[i] = zap_goertzel_result (&s->col_out[i]);
+ if (col_energy[i] > col_energy[best_col])
+ best_col = i;
+ }
+ hit = 0;
+ /* Basic signal level test and the twist test */
+ if (row_energy[best_row] >= DTMF_THRESHOLD
+ &&
+ col_energy[best_col] >= DTMF_THRESHOLD
+ &&
+ col_energy[best_col] < row_energy[best_row] * DTMF_REVERSE_TWIST
+ && col_energy[best_col] * DTMF_NORMAL_TWIST > row_energy[best_row]) {
+ /* Relative peak test */
+ for (i = 0; i < 4; i++) {
+ if ((i != best_col
+ && col_energy[i] * DTMF_RELATIVE_PEAK_COL >
+ col_energy[best_col])
+ || (i != best_row
+ && row_energy[i] * DTMF_RELATIVE_PEAK_ROW >
+ row_energy[best_row])) {
+ break;
+ }
+ }
+ /* ... and second harmonic test */
+ if (i >= 4
+ &&
+ (row_energy[best_row] + col_energy[best_col]) > 42.0 * s->energy
+ &&
+ zap_goertzel_result (&s->col_out2nd[best_col]) *
+ DTMF_2ND_HARMONIC_COL < col_energy[best_col]
+ && zap_goertzel_result (&s->row_out2nd[best_row]) *
+ DTMF_2ND_HARMONIC_ROW < row_energy[best_row]) {
+ hit = dtmf_positions[(best_row << 2) + best_col];
+ /* Look for two successive similar results */
+ /* The logic in the next test is:
+ We need two successive identical clean detects, with
+ something different preceeding it. This can work with
+ back to back differing digits. More importantly, it
+ can work with nasty phones that give a very wobbly start
+ to a digit. */
+ if (hit == s->hit3 && s->hit3 != s->hit2) {
+ s->mhit = hit;
+ s->digit_hits[(best_row << 2) + best_col]++;
+ s->detected_digits++;
+ if (s->current_digits < MAX_DTMF_DIGITS) {
+ s->digits[s->current_digits++] = hit;
+ s->digits[s->current_digits] = '\0';
+ } else {
+ s->lost_digits++;
+ }
+ }
+ }
+ }
+ if (!hit && (fax_energy >= FAX_THRESHOLD)
+ && (fax_energy > s->energy * 21.0)) {
+ fax_energy_2nd = zap_goertzel_result (&s->fax_tone2nd);
+ if (fax_energy_2nd * FAX_2ND_HARMONIC < fax_energy) {
+#if 0
+ printf ("Fax energy/Second Harmonic: %f/%f\n", fax_energy,
+ fax_energy_2nd);
+#endif
+ /* XXX Probably need better checking than just this the energy XXX */
+ hit = 'f';
+ s->fax_hits++;
+ } /* Don't reset fax hits counter */
+ } else {
+ if (s->fax_hits > 5) {
+ s->mhit = 'f';
+ s->detected_digits++;
+ if (s->current_digits < MAX_DTMF_DIGITS) {
+ s->digits[s->current_digits++] = hit;
+ s->digits[s->current_digits] = '\0';
+ } else {
+ s->lost_digits++;
+ }
+ }
+ s->fax_hits = 0;
+ }
+ s->hit1 = s->hit2;
+ s->hit2 = s->hit3;
+ s->hit3 = hit;
+ /* Reinitialise the detector for the next block */
+ for (i = 0; i < 4; i++) {
+ goertzel_init (&s->row_out[i], &dtmf_detect_row[i]);
+ goertzel_init (&s->col_out[i], &dtmf_detect_col[i]);
+ goertzel_init (&s->row_out2nd[i], &dtmf_detect_row_2nd[i]);
+ goertzel_init (&s->col_out2nd[i], &dtmf_detect_col_2nd[i]);
+ }
+ goertzel_init (&s->fax_tone, &fax_detect);
+ goertzel_init (&s->fax_tone2nd, &fax_detect_2nd);
+ s->energy = 0.0;
+ s->current_sample = 0;
+ }
+ if ((!s->mhit) || (s->mhit != hit)) {
+ s->mhit = 0;
+ return (0);
+ }
+ return (hit);
+}
+
+/*- End of function --------------------------------------------------------*/
+
+int
+zap_dtmf_get (dtmf_detect_state_t * s, char *buf, int max)
+{
+ if (max > s->current_digits)
+ max = s->current_digits;
+ if (max > 0) {
+ memcpy (buf, s->digits, max);
+ memmove (s->digits, s->digits + max, s->current_digits - max);
+ s->current_digits -= max;
+ }
+ buf[max] = '\0';
+ return max;
+}
+
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/
diff --git a/farsight/dtmf/tone_detect.h b/farsight/dtmf/tone_detect.h
new file mode 100644
index 0000000..909c5ef
--- /dev/null
+++ b/farsight/dtmf/tone_detect.h
@@ -0,0 +1,93 @@
+/*
+ * Header file for DTMF Receiver module, part of:
+ * BSD Telephony Of Mexico "Zapata" Telecom Library, version 1.10 12/9/01
+ *
+ * Part of the "Zapata" Computer Telephony Technology.
+ *
+ * See http://www.bsdtelephony.com.mx
+ *
+ *
+ * The technologies, software, hardware, designs, drawings, scheumatics, board
+ * layouts and/or artwork, concepts, methodologies (including the use of all
+ * of these, and that which is derived from the use of all of these), all other
+ * intellectual properties contained herein, and all intellectual property
+ * rights have been and shall continue to be expressly for the benefit of all
+ * mankind, and are perpetually placed in the public domain, and may be used,
+ * copied, and/or modified by anyone, in any manner, for any legal purpose,
+ * without restriction.
+ *
+ * This module written by Stephen Underwood.
+ */
+/*
+ tone_detect.h - General telephony tone detection, and specific
+ detection of DTMF.
+
+ Copyright (C) 2001 Steve Underwood <steveu@coppice.org>
+
+ Despite my general liking of the GPL, I place this code in the
+ public domain for the benefit of all mankind - even the slimy
+ ones who might try to proprietize my work and use it to my
+ detriment.
+*/
+
+#ifndef __TONE_DETECT_H__
+#define __TONE_DETECT_H__
+
+#include "_stdint.h"
+
+typedef struct
+{
+ float v2;
+ float v3;
+ float fac;
+} goertzel_state_t;
+
+#define MAX_DTMF_DIGITS 128
+
+typedef struct
+{
+ int hit1;
+ int hit2;
+ int hit3;
+ int hit4;
+ int mhit;
+
+ goertzel_state_t row_out[4];
+ goertzel_state_t col_out[4];
+ goertzel_state_t row_out2nd[4];
+ goertzel_state_t col_out2nd[4];
+ goertzel_state_t fax_tone;
+ goertzel_state_t fax_tone2nd;
+ float energy;
+
+ int current_sample;
+ char digits[MAX_DTMF_DIGITS + 1];
+ int current_digits;
+ int detected_digits;
+ int lost_digits;
+ int digit_hits[16];
+ int fax_hits;
+} dtmf_detect_state_t;
+
+typedef struct
+{
+ float fac;
+} tone_detection_descriptor_t;
+
+void zap_goertzel_update(goertzel_state_t *s,
+ int16_t x[],
+ int samples);
+float zap_goertzel_result (goertzel_state_t *s);
+
+void zap_dtmf_detect_init (dtmf_detect_state_t *s);
+int zap_dtmf_detect (dtmf_detect_state_t *s,
+ int16_t amp[],
+ int samples,
+ int isradio);
+int zap_dtmf_get (dtmf_detect_state_t *s,
+ char *buf,
+ int max);
+
+#endif /* __TONE_DETECT_H__ */
+
+/*- End of file ------------------------------------------------------------*/
diff --git a/farsight/liveadder/Makefile.am b/farsight/liveadder/Makefile.am
new file mode 100644
index 0000000..fcc971b
--- /dev/null
+++ b/farsight/liveadder/Makefile.am
@@ -0,0 +1,11 @@
+plugin_LTLIBRARIES = libgstliveadder.la
+
+libgstliveadder_la_SOURCES = liveadder.c
+libgstliveadder_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS)
+libgstliveadder_la_LIBADD = \
+ $(GST_PLUGINS_BASE_LIBS) -lgstaudio-@GST_MAJORMINOR@ \
+ $(GST_BASE_LIBS) $(GST_LIBS)
+libgstliveadder_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+libgstliveadder_la_LIBTOOLFLAGS = --tag=disable-static
+
+noinst_HEADERS = liveadder.h
diff --git a/farsight/liveadder/liveadder.c b/farsight/liveadder/liveadder.c
new file mode 100644
index 0000000..25fa406
--- /dev/null
+++ b/farsight/liveadder/liveadder.c
@@ -0,0 +1,1548 @@
+/*
+ * Farsight Voice+Video library
+ *
+ * Copyright 2008 Collabora Ltd
+ * Copyright 2008 Nokia Corporation
+ * @author: Olivier Crete <olivier.crete@collabora.co.uk>
+ *
+ * With parts copied from the adder plugin which is
+ * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
+ * 2001 Thomas <thomas@apestaart.org>
+ * 2005,2006 Wim Taymans <wim@fluendo.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+/**
+ * SECTION:element-liveadder
+ * @see_also: adder
+ *
+ * The live adder allows to mix several streams into one by adding the data.
+ * Mixed data is clamped to the min/max values of the data format.
+ *
+ * Unlike the adder, the liveadder mixes the streams according the their
+ * timestamps and waits for some milli-seconds before trying doing the mixing.
+ *
+ * Last reviewed on 2008-02-10 (0.10.11)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "liveadder.h"
+
+#include <gst/audio/audio.h>
+
+#include <string.h>
+
+#define DEFAULT_LATENCY_MS 60
+
+GST_DEBUG_CATEGORY_STATIC (live_adder_debug);
+#define GST_CAT_DEFAULT (live_adder_debug)
+
+/* elementfactory information */
+static const GstElementDetails gst_live_adder_details =
+GST_ELEMENT_DETAILS ("Live Adder element",
+ "Generic/Audio",
+ "Mixes live/discontinuous audio streams",
+ "Olivier Crete <olivier.crete@collabora.co.uk>");
+
+
+static GstStaticPadTemplate gst_live_adder_sink_template =
+ GST_STATIC_PAD_TEMPLATE ("sink%d",
+ GST_PAD_SINK,
+ GST_PAD_REQUEST,
+ GST_STATIC_CAPS (GST_AUDIO_INT_PAD_TEMPLATE_CAPS "; "
+ GST_AUDIO_FLOAT_PAD_TEMPLATE_CAPS)
+ );
+
+static GstStaticPadTemplate gst_live_adder_src_template =
+ GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (GST_AUDIO_INT_PAD_TEMPLATE_CAPS "; "
+ GST_AUDIO_FLOAT_PAD_TEMPLATE_CAPS)
+ );
+
+/* Valve signals and args */
+enum
+{
+ /* FILL ME */
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_LATENCY,
+};
+
+typedef struct _GstLiveAdderPadPrivate
+{
+ GstSegment segment;
+ gboolean eos;
+
+ GstClockTime expected_timestamp;
+
+} GstLiveAdderPadPrivate;
+
+
+GST_BOILERPLATE (GstLiveAdder, gst_live_adder, GstElement, GST_TYPE_ELEMENT);
+
+
+static void gst_live_adder_finalize (GObject * object);
+static void
+gst_live_adder_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec);
+static void
+gst_live_adder_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec);
+
+static GstPad *gst_live_adder_request_new_pad (GstElement * element,
+ GstPadTemplate * templ, const gchar * unused);
+static void gst_live_adder_release_pad (GstElement * element, GstPad * pad);
+static GstStateChangeReturn
+gst_live_adder_change_state (GstElement * element, GstStateChange transition);
+
+static gboolean gst_live_adder_setcaps (GstPad * pad, GstCaps * caps);
+static GstCaps *gst_live_adder_sink_getcaps (GstPad * pad);
+static gboolean
+gst_live_adder_src_activate_push (GstPad * pad, gboolean active);
+static gboolean gst_live_adder_src_event (GstPad * pad, GstEvent * event);
+
+static void gst_live_adder_loop (gpointer data);
+static gboolean gst_live_adder_query (GstPad * pad, GstQuery * query);
+static gboolean gst_live_adder_sink_event (GstPad * pad, GstEvent * event);
+
+
+static void reset_pad_private (GstPad * pad);
+
+/* clipping versions */
+#define MAKE_FUNC(name,type,ttype,min,max) \
+static void name (type *out, type *in, gint bytes) { \
+ gint i; \
+ for (i = 0; i < bytes / sizeof (type); i++) \
+ out[i] = CLAMP ((ttype)out[i] + (ttype)in[i], min, max); \
+}
+
+/* non-clipping versions (for float) */
+#define MAKE_FUNC_NC(name,type,ttype) \
+static void name (type *out, type *in, gint bytes) { \
+ gint i; \
+ for (i = 0; i < bytes / sizeof (type); i++) \
+ out[i] = (ttype)out[i] + (ttype)in[i]; \
+}
+
+/* *INDENT-OFF* */
+MAKE_FUNC (add_int32, gint32, gint64, G_MININT32, G_MAXINT32)
+MAKE_FUNC (add_int16, gint16, gint32, G_MININT16, G_MAXINT16)
+MAKE_FUNC (add_int8, gint8, gint16, G_MININT8, G_MAXINT8)
+MAKE_FUNC (add_uint32, guint32, guint64, 0, G_MAXUINT32)
+MAKE_FUNC (add_uint16, guint16, guint32, 0, G_MAXUINT16)
+MAKE_FUNC (add_uint8, guint8, guint16, 0, G_MAXUINT8)
+MAKE_FUNC_NC (add_float64, gdouble, gdouble)
+MAKE_FUNC_NC (add_float32, gfloat, gfloat)
+/* *INDENT-ON* */
+
+
+static void
+gst_live_adder_base_init (gpointer klass)
+{
+}
+
+static void
+gst_live_adder_class_init (GstLiveAdderClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+
+ gobject_class = (GObjectClass *) klass;
+
+ gobject_class->finalize = gst_live_adder_finalize;
+ gobject_class->set_property = gst_live_adder_set_property;
+ gobject_class->get_property = gst_live_adder_get_property;
+
+ gstelement_class = (GstElementClass *) klass;
+
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&gst_live_adder_src_template));
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&gst_live_adder_sink_template));
+ gst_element_class_set_details (gstelement_class, &gst_live_adder_details);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ gstelement_class->request_new_pad = gst_live_adder_request_new_pad;
+ gstelement_class->release_pad = gst_live_adder_release_pad;
+ gstelement_class->change_state = gst_live_adder_change_state;
+
+ g_object_class_install_property (gobject_class, PROP_LATENCY,
+ g_param_spec_uint ("latency", "Buffer latency in ms",
+ "Amount of data to buffer", 0, G_MAXUINT, DEFAULT_LATENCY_MS,
+ G_PARAM_READWRITE));
+
+ GST_DEBUG_CATEGORY_INIT (live_adder_debug, "liveadder", 0, "Live Adder");
+
+}
+
+static void
+gst_live_adder_init (GstLiveAdder * adder, GstLiveAdderClass * klass)
+{
+ GstPadTemplate *template;
+
+ template = gst_static_pad_template_get (&gst_live_adder_src_template);
+ adder->srcpad = gst_pad_new_from_template (template, "src");
+ gst_object_unref (template);
+ gst_pad_set_getcaps_function (adder->srcpad,
+ GST_DEBUG_FUNCPTR (gst_pad_proxy_getcaps));
+ gst_pad_set_setcaps_function (adder->srcpad,
+ GST_DEBUG_FUNCPTR (gst_live_adder_setcaps));
+ gst_pad_set_query_function (adder->srcpad,
+ GST_DEBUG_FUNCPTR (gst_live_adder_query));
+ gst_pad_set_event_function (adder->srcpad,
+ GST_DEBUG_FUNCPTR (gst_live_adder_src_event));
+ gst_pad_set_activatepush_function (adder->srcpad,
+ GST_DEBUG_FUNCPTR (gst_live_adder_src_activate_push));
+ gst_element_add_pad (GST_ELEMENT (adder), adder->srcpad);
+
+ adder->format = GST_LIVE_ADDER_FORMAT_UNSET;
+ adder->padcount = 0;
+ adder->func = NULL;
+ adder->not_empty_cond = g_cond_new ();
+
+ adder->next_timestamp = GST_CLOCK_TIME_NONE;
+
+ adder->latency_ms = DEFAULT_LATENCY_MS;
+
+ adder->buffers = g_queue_new ();
+}
+
+
+
+static void
+gst_live_adder_finalize (GObject * object)
+{
+ GstLiveAdder *adder = GST_LIVE_ADDER (object);
+
+ g_cond_free (adder->not_empty_cond);
+
+ g_queue_foreach (adder->buffers, (GFunc) gst_mini_object_unref, NULL);
+ while (g_queue_pop_head (adder->buffers)) {
+ }
+ g_queue_free (adder->buffers);
+
+ g_list_free (adder->sinkpads);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gst_live_adder_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+ GstLiveAdder *adder = GST_LIVE_ADDER (object);
+
+ switch (prop_id) {
+ case PROP_LATENCY:
+ {
+ guint64 new_latency, old_latency;
+
+ new_latency = g_value_get_uint (value);
+
+ GST_OBJECT_LOCK (adder);
+ old_latency = adder->latency_ms;
+ adder->latency_ms = new_latency;
+ GST_OBJECT_UNLOCK (adder);
+
+ /* post message if latency changed, this will inform the parent pipeline
+ * that a latency reconfiguration is possible/needed. */
+ if (new_latency != old_latency) {
+ GST_DEBUG_OBJECT (adder, "latency changed to: %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (new_latency));
+
+ gst_element_post_message (GST_ELEMENT_CAST (adder),
+ gst_message_new_latency (GST_OBJECT_CAST (adder)));
+ }
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static void
+gst_live_adder_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec)
+{
+ GstLiveAdder *adder = GST_LIVE_ADDER (object);
+
+ switch (prop_id) {
+ case PROP_LATENCY:
+ GST_OBJECT_LOCK (adder);
+ g_value_set_uint (value, adder->latency_ms);
+ GST_OBJECT_UNLOCK (adder);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+/* we can only accept caps that we and downstream can handle. */
+static GstCaps *
+gst_live_adder_sink_getcaps (GstPad * pad)
+{
+ GstLiveAdder *adder;
+ GstCaps *result, *peercaps, *sinkcaps;
+
+ adder = GST_LIVE_ADDER (GST_PAD_PARENT (pad));
+
+ /* get the downstream possible caps */
+ peercaps = gst_pad_peer_get_caps (adder->srcpad);
+ /* get the allowed caps on this sinkpad, we use the fixed caps function so
+ * that it does not call recursively in this function. */
+ sinkcaps = gst_pad_get_fixed_caps_func (pad);
+ if (peercaps) {
+ /* if the peer has caps, intersect */
+ GST_DEBUG_OBJECT (adder, "intersecting peer and template caps");
+ result = gst_caps_intersect (peercaps, sinkcaps);
+ gst_caps_unref (peercaps);
+ gst_caps_unref (sinkcaps);
+ } else {
+ /* the peer has no caps (or there is no peer), just use the allowed caps
+ * of this sinkpad. */
+ GST_DEBUG_OBJECT (adder, "no peer caps, using sinkcaps");
+ result = sinkcaps;
+ }
+
+ return result;
+}
+
+/* the first caps we receive on any of the sinkpads will define the caps for all
+ * the other sinkpads because we can only mix streams with the same caps.
+ * */
+static gboolean
+gst_live_adder_setcaps (GstPad * pad, GstCaps * caps)
+{
+ GstLiveAdder *adder;
+ GList *pads;
+ GstStructure *structure;
+ const char *media_type;
+
+ adder = GST_LIVE_ADDER (GST_PAD_PARENT (pad));
+
+ GST_LOG_OBJECT (adder, "setting caps on pad %p,%s to %" GST_PTR_FORMAT, pad,
+ GST_PAD_NAME (pad), caps);
+
+ /* FIXME, see if the other pads can accept the format. Also lock the
+ * format on the other pads to this new format. */
+ GST_OBJECT_LOCK (adder);
+ pads = GST_ELEMENT (adder)->pads;
+ while (pads) {
+ GstPad *otherpad = GST_PAD (pads->data);
+
+ if (otherpad != pad)
+ gst_caps_replace (&GST_PAD_CAPS (otherpad), caps);
+
+ pads = g_list_next (pads);
+ }
+
+ /* parse caps now */
+ structure = gst_caps_get_structure (caps, 0);
+ media_type = gst_structure_get_name (structure);
+ if (strcmp (media_type, "audio/x-raw-int") == 0) {
+ GST_DEBUG_OBJECT (adder, "parse_caps sets adder to format int");
+ adder->format = GST_LIVE_ADDER_FORMAT_INT;
+ gst_structure_get_int (structure, "width", &adder->width);
+ gst_structure_get_int (structure, "depth", &adder->depth);
+ gst_structure_get_int (structure, "endianness", &adder->endianness);
+ gst_structure_get_boolean (structure, "signed", &adder->is_signed);
+
+ if (adder->endianness != G_BYTE_ORDER)
+ goto not_supported;
+
+ switch (adder->width) {
+ case 8:
+ adder->func = (adder->is_signed ?
+ (GstLiveAdderFunction) add_int8 : (GstLiveAdderFunction) add_uint8);
+ break;
+ case 16:
+ adder->func = (adder->is_signed ?
+ (GstLiveAdderFunction) add_int16 : (GstLiveAdderFunction)
+ add_uint16);
+ break;
+ case 32:
+ adder->func = (adder->is_signed ?
+ (GstLiveAdderFunction) add_int32 : (GstLiveAdderFunction)
+ add_uint32);
+ break;
+ default:
+ goto not_supported;
+ }
+ } else if (strcmp (media_type, "audio/x-raw-float") == 0) {
+ GST_DEBUG_OBJECT (adder, "parse_caps sets adder to format float");
+ adder->format = GST_LIVE_ADDER_FORMAT_FLOAT;
+ gst_structure_get_int (structure, "width", &adder->width);
+
+ switch (adder->width) {
+ case 32:
+ adder->func = (GstLiveAdderFunction) add_float32;
+ break;
+ case 64:
+ adder->func = (GstLiveAdderFunction) add_float64;
+ break;
+ default:
+ goto not_supported;
+ }
+ } else {
+ goto not_supported;
+ }
+
+ gst_structure_get_int (structure, "channels", &adder->channels);
+ gst_structure_get_int (structure, "rate", &adder->rate);
+ /* precalc bps */
+ adder->bps = (adder->width / 8) * adder->channels;
+
+ GST_OBJECT_UNLOCK (adder);
+ return TRUE;
+
+ /* ERRORS */
+not_supported:
+ {
+ GST_OBJECT_UNLOCK (adder);
+ GST_DEBUG_OBJECT (adder, "unsupported format set as caps");
+ return FALSE;
+ }
+}
+
+static void
+gst_live_adder_flush_start (GstLiveAdder * adder)
+{
+ GST_DEBUG_OBJECT (adder, "Disabling pop on queue");
+
+ GST_OBJECT_LOCK (adder);
+ /* mark ourselves as flushing */
+ adder->srcresult = GST_FLOW_WRONG_STATE;
+
+ /* Empty the queue */
+ g_queue_foreach (adder->buffers, (GFunc) gst_mini_object_unref, NULL);
+ while (g_queue_pop_head (adder->buffers));
+
+ /* unlock clock, we just unschedule, the entry will be released by the
+ * locking streaming thread. */
+ if (adder->clock_id)
+ gst_clock_id_unschedule (adder->clock_id);
+
+ g_cond_broadcast (adder->not_empty_cond);
+ GST_OBJECT_UNLOCK (adder);
+}
+
+static gboolean
+gst_live_adder_src_activate_push (GstPad * pad, gboolean active)
+{
+ gboolean result = TRUE;
+ GstLiveAdder *adder = NULL;
+
+ adder = GST_LIVE_ADDER (gst_pad_get_parent (pad));
+
+ if (active) {
+ /* Mark as non flushing */
+ GST_OBJECT_LOCK (adder);
+ adder->srcresult = GST_FLOW_OK;
+ GST_OBJECT_UNLOCK (adder);
+
+ /* start pushing out buffers */
+ GST_DEBUG_OBJECT (adder, "Starting task on srcpad");
+ gst_pad_start_task (adder->srcpad,
+ (GstTaskFunction) gst_live_adder_loop, adder);
+ } else {
+ /* make sure all data processing stops ASAP */
+ gst_live_adder_flush_start (adder);
+
+ /* NOTE this will hardlock if the state change is called from the src pad
+ * task thread because we will _join() the thread. */
+ GST_DEBUG_OBJECT (adder, "Stopping task on srcpad");
+ result = gst_pad_stop_task (pad);
+ }
+
+ gst_object_unref (adder);
+
+ return result;
+}
+
+static gboolean
+gst_live_adder_sink_event (GstPad * pad, GstEvent * event)
+{
+ gboolean ret = TRUE;
+ GstLiveAdder *adder = NULL;
+ GstLiveAdderPadPrivate *padprivate = NULL;
+
+ adder = GST_LIVE_ADDER (gst_pad_get_parent (pad));
+
+ padprivate = gst_pad_get_element_private (pad);
+
+ if (!padprivate)
+ return FALSE;
+
+ GST_LOG_OBJECT (adder, "received %s", GST_EVENT_TYPE_NAME (event));
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_NEWSEGMENT:
+ {
+ GstFormat format;
+ gdouble rate, arate;
+ gint64 start, stop, time;
+ gboolean update;
+
+ gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format,
+ &start, &stop, &time);
+
+ gst_event_unref (event);
+
+ /* we need time for now */
+ if (format != GST_FORMAT_TIME)
+ goto newseg_wrong_format;
+
+ GST_DEBUG_OBJECT (adder,
+ "newsegment: update %d, rate %g, arate %g, start %" GST_TIME_FORMAT
+ ", stop %" GST_TIME_FORMAT ", time %" GST_TIME_FORMAT,
+ update, rate, arate, GST_TIME_ARGS (start), GST_TIME_ARGS (stop),
+ GST_TIME_ARGS (time));
+
+ /* now configure the values, we need these to time the release of the
+ * buffers on the srcpad. */
+ GST_OBJECT_LOCK (adder);
+ gst_segment_set_newsegment_full (&padprivate->segment, update,
+ rate, arate, format, start, stop, time);
+ GST_OBJECT_UNLOCK (adder);
+ break;
+ }
+ case GST_EVENT_FLUSH_START:
+ gst_live_adder_flush_start (adder);
+ ret = gst_pad_push_event (adder->srcpad, event);
+ break;
+ case GST_EVENT_FLUSH_STOP:
+ GST_OBJECT_LOCK (adder);
+ adder->segment_pending = TRUE;
+ adder->next_timestamp = GST_CLOCK_TIME_NONE;
+ reset_pad_private (pad);
+ adder->segment_pending = TRUE;
+ GST_OBJECT_UNLOCK (adder);
+ ret = gst_pad_push_event (adder->srcpad, event);
+ ret = gst_live_adder_src_activate_push (adder->srcpad, TRUE);
+ break;
+ case GST_EVENT_EOS:
+ {
+ GST_OBJECT_LOCK (adder);
+
+ ret = adder->srcresult == GST_FLOW_OK;
+ if (ret && !padprivate->eos) {
+ GST_DEBUG_OBJECT (adder, "queuing EOS");
+ padprivate->eos = TRUE;
+ g_cond_broadcast (adder->not_empty_cond);
+ } else if (padprivate->eos) {
+ GST_DEBUG_OBJECT (adder, "dropping EOS, we are already EOS");
+ } else {
+ GST_DEBUG_OBJECT (adder, "dropping EOS, reason %s",
+ gst_flow_get_name (adder->srcresult));
+ }
+
+ GST_OBJECT_UNLOCK (adder);
+
+ gst_event_unref (event);
+ break;
+ }
+ default:
+ ret = gst_pad_push_event (adder->srcpad, event);
+ break;
+ }
+
+done:
+ gst_object_unref (adder);
+
+ return ret;
+
+ /* ERRORS */
+newseg_wrong_format:
+ {
+ GST_DEBUG_OBJECT (adder, "received non TIME newsegment");
+ ret = FALSE;
+ goto done;
+ }
+}
+
+static gboolean
+gst_live_adder_query_pos_dur (GstLiveAdder * adder, GstFormat informat,
+ gboolean position, gint64 * outvalue)
+{
+ gint64 max = G_MININT64;
+ gboolean res = TRUE;
+ GstIterator *it;
+ gboolean done = FALSE;
+
+
+ it = gst_element_iterate_sink_pads (GST_ELEMENT_CAST (adder));
+ while (!done) {
+ GstIteratorResult ires;
+ gpointer item;
+ GstFormat format = informat;
+
+ ires = gst_iterator_next (it, &item);
+ switch (ires) {
+ case GST_ITERATOR_DONE:
+ done = TRUE;
+ break;
+ case GST_ITERATOR_OK:
+ {
+ GstPad *pad = GST_PAD_CAST (item);
+ gint64 value;
+ gboolean curres;
+
+ /* ask sink peer for duration */
+ if (position)
+ curres = gst_pad_query_peer_position (pad, &format, &value);
+ else
+ curres = gst_pad_query_peer_duration (pad, &format, &value);
+
+ /* take max from all valid return values */
+ /* Only if the format is the one we requested, otherwise ignore it ?
+ */
+
+ if (curres && format == informat) {
+ res &= curres;
+
+ /* valid unknown length, stop searching */
+ if (value == -1) {
+ max = value;
+ done = TRUE;
+ } else if (value > max) {
+ max = value;
+ }
+ }
+ break;
+ }
+ case GST_ITERATOR_RESYNC:
+ max = -1;
+ res = TRUE;
+ break;
+ default:
+ res = FALSE;
+ done = TRUE;
+ break;
+ }
+ }
+ gst_iterator_free (it);
+
+ if (res)
+ *outvalue = max;
+
+ return res;
+}
+
+/* FIXME:
+ *
+ * When we add a new stream (or remove a stream) the duration might
+ * also become invalid again and we need to post a new DURATION
+ * message to notify this fact to the parent.
+ * For now we take the max of all the upstream elements so the simple
+ * cases work at least somewhat.
+ */
+static gboolean
+gst_live_adder_query_duration (GstLiveAdder * adder, GstQuery * query)
+{
+ GstFormat format;
+ gint64 max;
+ gboolean res;
+
+ /* parse format */
+ gst_query_parse_duration (query, &format, NULL);
+
+ res = gst_live_adder_query_pos_dur (adder, format, FALSE, &max);
+
+ if (res) {
+ /* and store the max */
+ gst_query_set_duration (query, format, max);
+ }
+
+ return res;
+}
+
+static gboolean
+gst_live_adder_query_position (GstLiveAdder * adder, GstQuery * query)
+{
+ GstFormat format;
+ gint64 max;
+ gboolean res;
+
+ /* parse format */
+ gst_query_parse_position (query, &format, NULL);
+
+ res = gst_live_adder_query_pos_dur (adder, format, TRUE, &max);
+
+ if (res) {
+ /* and store the max */
+ gst_query_set_position (query, format, max);
+ }
+
+ return res;
+}
+
+
+
+static gboolean
+gst_live_adder_query (GstPad * pad, GstQuery * query)
+{
+ GstLiveAdder *adder;
+ gboolean res = FALSE;
+
+ adder = GST_LIVE_ADDER (gst_pad_get_parent (pad));
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_LATENCY:
+ {
+ /* We need to send the query upstream and add the returned latency to our
+ * own */
+ GstClockTime min_latency = 0, max_latency = G_MAXUINT64;
+ gpointer item;
+ GstIterator *iter = NULL;
+ gboolean done = FALSE;
+
+ iter = gst_element_iterate_sink_pads (GST_ELEMENT (adder));
+
+ while (!done) {
+ switch (gst_iterator_next (iter, &item)) {
+ case GST_ITERATOR_OK:
+ {
+ GstPad *sinkpad = item;
+ GstClockTime pad_min_latency, pad_max_latency;
+ gboolean pad_us_live;
+
+ if (gst_pad_peer_query (sinkpad, query)) {
+ gst_query_parse_latency (query, &pad_us_live, &pad_min_latency,
+ &pad_max_latency);
+
+ res = TRUE;
+
+ GST_DEBUG_OBJECT (adder, "Peer latency for pad %s: min %"
+ GST_TIME_FORMAT " max %" GST_TIME_FORMAT,
+ GST_PAD_NAME (sinkpad),
+ GST_TIME_ARGS (pad_min_latency),
+ GST_TIME_ARGS (pad_max_latency));
+
+ min_latency = MAX (pad_min_latency, min_latency);
+ max_latency = MIN (pad_max_latency, max_latency);
+ }
+ gst_object_unref (item);
+ }
+ break;
+ case GST_ITERATOR_RESYNC:
+ min_latency = 0;
+ max_latency = G_MAXUINT64;
+
+ gst_iterator_resync (iter);
+ break;
+ case GST_ITERATOR_ERROR:
+ GST_ERROR_OBJECT (adder, "Error looping sink pads");
+ done = TRUE;
+ break;
+ case GST_ITERATOR_DONE:
+ done = TRUE;
+ break;
+ }
+ }
+ gst_iterator_free (iter);
+
+ if (res) {
+ GstClockTime my_latency = adder->latency_ms * GST_MSECOND;
+ GST_OBJECT_LOCK (adder);
+ adder->peer_latency = min_latency;
+ min_latency += my_latency;
+ GST_OBJECT_UNLOCK (adder);
+
+ /* Make sure we don't risk an overflow */
+ if (max_latency < G_MAXUINT64 - my_latency)
+ max_latency += my_latency;
+ else
+ max_latency = G_MAXUINT64;
+ gst_query_set_latency (query, TRUE, min_latency, max_latency);
+ GST_DEBUG_OBJECT (adder, "Calculated total latency : min %"
+ GST_TIME_FORMAT " max %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency));
+ }
+ break;
+ }
+ case GST_QUERY_DURATION:
+ res = gst_live_adder_query_duration (adder, query);
+ break;
+ case GST_QUERY_POSITION:
+ res = gst_live_adder_query_position (adder, query);
+ break;
+ default:
+ res = gst_pad_query_default (pad, query);
+ break;
+ }
+
+ gst_object_unref (adder);
+
+ return res;
+}
+
+static gboolean
+forward_event_func (GstPad * pad, GValue * ret, GstEvent * event)
+{
+ gst_event_ref (event);
+ GST_LOG_OBJECT (pad, "About to send event %s", GST_EVENT_TYPE_NAME (event));
+ if (!gst_pad_push_event (pad, event)) {
+ g_value_set_boolean (ret, FALSE);
+ GST_WARNING_OBJECT (pad, "Sending event %p (%s) failed.",
+ event, GST_EVENT_TYPE_NAME (event));
+ } else {
+ GST_LOG_OBJECT (pad, "Sent event %p (%s).",
+ event, GST_EVENT_TYPE_NAME (event));
+ }
+
+ /* unref the pad because of a FIXME in gst_iterator_unfold
+ * it does a gst_iterator_next which refs the pad, but it never unrefs it
+ */
+ gst_object_unref (pad);
+ return TRUE;
+}
+
+/* forwards the event to all sinkpads, takes ownership of the
+ * event
+ *
+ * Returns: TRUE if the event could be forwarded on all
+ * sinkpads.
+ */
+static gboolean
+forward_event (GstLiveAdder * adder, GstEvent * event)
+{
+ gboolean ret;
+ GstIterator *it;
+ GValue vret = { 0 };
+
+ GST_LOG_OBJECT (adder, "Forwarding event %p (%s)", event,
+ GST_EVENT_TYPE_NAME (event));
+
+ ret = TRUE;
+
+ g_value_init (&vret, G_TYPE_BOOLEAN);
+ g_value_set_boolean (&vret, TRUE);
+ it = gst_element_iterate_sink_pads (GST_ELEMENT_CAST (adder));
+ gst_iterator_fold (it, (GstIteratorFoldFunction) forward_event_func, &vret,
+ event);
+ gst_iterator_free (it);
+
+ ret = g_value_get_boolean (&vret);
+
+ return ret;
+}
+
+
+static gboolean
+gst_live_adder_src_event (GstPad * pad, GstEvent * event)
+{
+ GstLiveAdder *adder;
+ gboolean result;
+
+ adder = GST_LIVE_ADDER (gst_pad_get_parent (pad));
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_QOS:
+ /* TODO : QoS might be tricky */
+ result = FALSE;
+ break;
+ case GST_EVENT_NAVIGATION:
+ /* TODO : navigation is rather pointless. */
+ result = FALSE;
+ break;
+ default:
+ /* just forward the rest for now */
+ result = forward_event (adder, event);
+ break;
+ }
+
+ gst_event_unref (event);
+ gst_object_unref (adder);
+
+ return result;
+}
+
+static guint
+gst_live_adder_length_from_duration (GstLiveAdder * adder,
+ GstClockTime duration)
+{
+ guint64 ret = (duration * adder->rate / GST_SECOND) * adder->bps;
+
+ return (guint) ret;
+}
+
+static GstFlowReturn
+gst_live_live_adder_chain (GstPad * pad, GstBuffer * buffer)
+{
+ GstLiveAdder *adder = GST_LIVE_ADDER (gst_pad_get_parent_element (pad));
+ GstLiveAdderPadPrivate *padprivate = NULL;
+ GstFlowReturn ret = GST_FLOW_OK;
+ GList *item = NULL;
+ GstClockTime skip = 0;
+ gint64 drift = 0; /* Positive if new buffer after old buffer */
+
+ GST_OBJECT_LOCK (adder);
+
+ ret = adder->srcresult;
+
+ GST_DEBUG ("Incoming buffer time:%" GST_TIME_FORMAT " duration:%"
+ GST_TIME_FORMAT, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
+ GST_TIME_ARGS (GST_BUFFER_DURATION (buffer)));
+
+ if (ret != GST_FLOW_OK) {
+ GST_DEBUG_OBJECT (adder, "Passing non-ok result from src: %s",
+ gst_flow_get_name (ret));
+ gst_buffer_unref (buffer);
+ goto out;
+ }
+
+ padprivate = gst_pad_get_element_private (pad);
+
+ if (!padprivate) {
+ ret = GST_FLOW_NOT_LINKED;
+ gst_buffer_unref (buffer);
+ goto out;
+ }
+
+ if (padprivate->eos) {
+ GST_DEBUG_OBJECT (adder, "Received buffer after EOS");
+ ret = GST_FLOW_UNEXPECTED;
+ gst_buffer_unref (buffer);
+ goto out;
+ }
+
+ if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
+ goto invalid_timestamp;
+
+ if (padprivate->segment.format == GST_FORMAT_UNDEFINED) {
+ GST_WARNING_OBJECT (adder, "No new-segment received,"
+ " initializing segment with time 0..-1");
+ gst_segment_init (&padprivate->segment, GST_FORMAT_TIME);
+ gst_segment_set_newsegment (&padprivate->segment,
+ FALSE, 1.0, GST_FORMAT_TIME, 0, -1, 0);
+ }
+
+ if (padprivate->segment.format != GST_FORMAT_TIME)
+ goto invalid_segment;
+
+ buffer = gst_buffer_make_metadata_writable (buffer);
+
+ drift = GST_BUFFER_TIMESTAMP (buffer) - padprivate->expected_timestamp;
+
+ /* Just see if we receive invalid timestamp/durations */
+ if (GST_CLOCK_TIME_IS_VALID (padprivate->expected_timestamp) &&
+ !GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT) &&
+ (drift != 0)) {
+ GST_LOG_OBJECT (adder,
+ "Timestamp discontinuity without the DISCONT flag set"
+ " (expected %" GST_TIME_FORMAT ", got %" GST_TIME_FORMAT
+ " drift:%" G_GINT64_FORMAT "ms)",
+ GST_TIME_ARGS (padprivate->expected_timestamp),
+ GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), drift / GST_MSECOND);
+
+ /* We accept drifts of 10ms */
+ if (ABS (drift) < (10 * GST_MSECOND)) {
+ GST_DEBUG ("Correcting minor drift");
+ GST_BUFFER_TIMESTAMP (buffer) = padprivate->expected_timestamp;
+ }
+ }
+
+
+ /* If there is no duration, lets set one */
+ if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
+ GST_BUFFER_DURATION (buffer) =
+ gst_audio_duration_from_pad_buffer (pad, buffer);
+ padprivate->expected_timestamp = GST_CLOCK_TIME_NONE;
+ } else {
+ padprivate->expected_timestamp = GST_BUFFER_TIMESTAMP (buffer) +
+ GST_BUFFER_DURATION (buffer);
+ }
+
+
+ /*
+ * Lets clip the buffer to the segment (so we don't have to worry about
+ * cliping afterwards).
+ * This should also guarantee us that we'll have valid timestamps and
+ * durations afterwards
+ */
+
+ buffer = gst_audio_buffer_clip (buffer, &padprivate->segment, adder->rate,
+ adder->bps);
+
+ /* buffer can be NULL if it's completely outside of the segment */
+ if (!buffer) {
+ GST_DEBUG ("Buffer completely outside of configured segment, dropping it");
+ goto out;
+ }
+
+ /*
+ * Make sure all incoming buffers share the same timestamping
+ */
+ GST_BUFFER_TIMESTAMP (buffer) =
+ gst_segment_to_running_time (&padprivate->segment,
+ padprivate->segment.format, GST_BUFFER_TIMESTAMP (buffer));
+
+
+ if (GST_CLOCK_TIME_IS_VALID (adder->next_timestamp) &&
+ GST_BUFFER_TIMESTAMP (buffer) < adder->next_timestamp) {
+ if (GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer) <
+ adder->next_timestamp) {
+ GST_DEBUG_OBJECT (adder, "Buffer is late, dropping (ts: %" GST_TIME_FORMAT
+ " duration: %" GST_TIME_FORMAT ")",
+ GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
+ GST_TIME_ARGS (GST_BUFFER_DURATION (buffer)));
+ gst_buffer_unref (buffer);
+ goto out;
+ } else {
+ skip = adder->next_timestamp - GST_BUFFER_TIMESTAMP (buffer);
+ GST_DEBUG_OBJECT (adder, "Buffer is partially late, skipping %"
+ GST_TIME_FORMAT, GST_TIME_ARGS (skip));
+ }
+ }
+
+ /* If our new buffer's head is higher than the queue's head, lets wake up,
+ * we may not have to wait for as long
+ */
+ if (adder->clock_id &&
+ g_queue_peek_head (adder->buffers) != NULL &&
+ GST_BUFFER_TIMESTAMP (buffer) + skip <
+ GST_BUFFER_TIMESTAMP (g_queue_peek_head (adder->buffers)))
+ gst_clock_id_unschedule (adder->clock_id);
+
+ for (item = g_queue_peek_head_link (adder->buffers);
+ item; item = g_list_next (item)) {
+ GstBuffer *oldbuffer = item->data;
+ GstClockTime old_skip = 0;
+ GstClockTime mix_duration = 0;
+ GstClockTime mix_start = 0;
+ GstClockTime mix_end = 0;
+
+ /* We haven't reached our place yet */
+ if (GST_BUFFER_TIMESTAMP (buffer) + skip >=
+ GST_BUFFER_TIMESTAMP (oldbuffer) + GST_BUFFER_DURATION (oldbuffer))
+ continue;
+
+ /* We're past our place, lets insert ouselves here */
+ if (GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer) <=
+ GST_BUFFER_TIMESTAMP (oldbuffer))
+ break;
+
+ /* if we reach this spot, we have overlap, so we must mix */
+
+ /* First make a subbuffer with the non-overlapping part */
+ if (GST_BUFFER_TIMESTAMP (buffer) + skip < GST_BUFFER_TIMESTAMP (oldbuffer)) {
+ GstBuffer *subbuffer = NULL;
+ GstClockTime subbuffer_duration = GST_BUFFER_TIMESTAMP (oldbuffer) -
+ (GST_BUFFER_TIMESTAMP (buffer) + skip);
+
+ subbuffer = gst_buffer_create_sub (buffer,
+ gst_live_adder_length_from_duration (adder, skip),
+ gst_live_adder_length_from_duration (adder, subbuffer_duration));
+
+ GST_BUFFER_TIMESTAMP (subbuffer) = GST_BUFFER_TIMESTAMP (buffer) + skip;
+ GST_BUFFER_DURATION (subbuffer) = subbuffer_duration;
+
+ skip += subbuffer_duration;
+
+ g_queue_insert_before (adder->buffers, item, subbuffer);
+ }
+
+ /* Now we are on the overlapping part */
+ oldbuffer = gst_buffer_make_writable (oldbuffer);
+ item->data = oldbuffer;
+
+ old_skip = GST_BUFFER_TIMESTAMP (buffer) + skip -
+ GST_BUFFER_TIMESTAMP (oldbuffer);
+
+ mix_start = GST_BUFFER_TIMESTAMP (oldbuffer) + old_skip;
+
+ if (GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer) <
+ GST_BUFFER_TIMESTAMP (oldbuffer) + GST_BUFFER_DURATION (oldbuffer))
+ mix_end = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
+ else
+ mix_end = GST_BUFFER_TIMESTAMP (oldbuffer) +
+ GST_BUFFER_DURATION (oldbuffer);
+
+ mix_duration = mix_end - mix_start;
+
+ adder->func (GST_BUFFER_DATA (oldbuffer) +
+ gst_live_adder_length_from_duration (adder, old_skip),
+ GST_BUFFER_DATA (buffer) +
+ gst_live_adder_length_from_duration (adder, skip),
+ gst_live_adder_length_from_duration (adder, mix_duration));
+
+ skip += mix_duration;
+ }
+
+ g_cond_broadcast (adder->not_empty_cond);
+
+ if (skip == GST_BUFFER_DURATION (buffer)) {
+ gst_buffer_unref (buffer);
+ } else {
+ if (skip) {
+ GstClockTime subbuffer_duration = GST_BUFFER_DURATION (buffer) - skip;
+ GstClockTime subbuffer_ts = GST_BUFFER_TIMESTAMP (buffer) + skip;
+
+ buffer = gst_buffer_create_sub (buffer,
+ gst_live_adder_length_from_duration (adder, skip),
+ gst_live_adder_length_from_duration (adder, subbuffer_duration));
+ GST_BUFFER_TIMESTAMP (buffer) = subbuffer_ts;
+ GST_BUFFER_DURATION (buffer) = subbuffer_duration;
+ }
+
+ if (item)
+ g_queue_insert_before (adder->buffers, item, buffer);
+ else
+ g_queue_push_tail (adder->buffers, buffer);
+ }
+
+out:
+
+ GST_OBJECT_UNLOCK (adder);
+ gst_object_unref (adder);
+
+ return ret;
+
+invalid_timestamp:
+
+ GST_OBJECT_UNLOCK (adder);
+ gst_buffer_unref (buffer);
+ GST_ELEMENT_ERROR (adder, STREAM, FAILED,
+ ("Buffer without a valid timestamp received"),
+ ("Invalid timestamp received on buffer"));
+
+ return GST_FLOW_ERROR;
+
+invalid_segment:
+ {
+ const gchar *format = gst_format_get_name (padprivate->segment.format);
+ GST_OBJECT_UNLOCK (adder);
+ gst_buffer_unref (buffer);
+ GST_ELEMENT_ERROR (adder, STREAM, FAILED,
+ ("This element only supports TIME segments, received other type"),
+ ("Received a segment of type %s, only support time segment", format));
+
+ return GST_FLOW_ERROR;
+ }
+
+}
+
+/*
+ * This only works because the GstObject lock is taken
+ *
+ * It checks if all sink pads are EOS
+ */
+static gboolean
+check_eos_locked (GstLiveAdder * adder)
+{
+ GList *item;
+
+ /* We can't be EOS if we have no sinkpads */
+ if (adder->sinkpads == NULL)
+ return FALSE;
+
+ for (item = adder->sinkpads; item; item = g_list_next (item)) {
+ GstPad *pad = item->data;
+ GstLiveAdderPadPrivate *padprivate = gst_pad_get_element_private (pad);
+
+ if (padprivate && padprivate->eos != TRUE)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+gst_live_adder_loop (gpointer data)
+{
+ GstLiveAdder *adder = GST_LIVE_ADDER (data);
+ GstClockTime buffer_timestamp = 0;
+ GstClockTime sync_time = 0;
+ GstClock *clock = NULL;
+ GstClockID id = NULL;
+ GstClockReturn ret;
+ GstBuffer *buffer = NULL;
+ GstFlowReturn result;
+ GstEvent *newseg_event = NULL;
+
+ GST_OBJECT_LOCK (adder);
+
+again:
+
+ for (;;) {
+ if (adder->srcresult != GST_FLOW_OK)
+ goto flushing;
+ if (!g_queue_is_empty (adder->buffers))
+ break;
+ if (check_eos_locked (adder))
+ goto eos;
+ g_cond_wait (adder->not_empty_cond, GST_OBJECT_GET_LOCK (adder));
+ }
+
+ buffer_timestamp = GST_BUFFER_TIMESTAMP (g_queue_peek_head (adder->buffers));
+
+ clock = GST_ELEMENT_CLOCK (adder);
+
+ /* If we have no clock, then we can't do anything.. error */
+ if (!clock) {
+ if (adder->playing)
+ goto no_clock;
+ else
+ goto push_buffer;
+ }
+
+ GST_DEBUG_OBJECT (adder, "sync to timestamp %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (buffer_timestamp));
+
+ sync_time = buffer_timestamp + GST_ELEMENT_CAST (adder)->base_time;
+ /* add latency, this includes our own latency and the peer latency. */
+ sync_time += adder->latency_ms * GST_MSECOND;
+ sync_time += adder->peer_latency;
+
+ /* create an entry for the clock */
+ id = adder->clock_id = gst_clock_new_single_shot_id (clock, sync_time);
+ GST_OBJECT_UNLOCK (adder);
+
+ ret = gst_clock_id_wait (id, NULL);
+
+ GST_OBJECT_LOCK (adder);
+
+ /* and free the entry */
+ gst_clock_id_unref (id);
+ adder->clock_id = NULL;
+
+ /* at this point, the clock could have been unlocked by a timeout, a new
+ * head element was added to the queue or because we are shutting down. Check
+ * for shutdown first. */
+
+ if (adder->srcresult != GST_FLOW_OK)
+ goto flushing;
+
+ if (ret == GST_CLOCK_UNSCHEDULED) {
+ GST_DEBUG_OBJECT (adder,
+ "Wait got unscheduled, will retry to push with new buffer");
+ goto again;
+ }
+
+ if (ret != GST_CLOCK_OK && ret != GST_CLOCK_EARLY)
+ goto clock_error;
+
+push_buffer:
+
+ buffer = g_queue_pop_head (adder->buffers);
+
+ if (!buffer)
+ goto again;
+
+ /*
+ * We make sure the timestamps are exactly contiguous
+ * If its only small skew (due to rounding errors), we correct it
+ * silently. Otherwise we put the discont flag
+ */
+ if (GST_CLOCK_TIME_IS_VALID (adder->next_timestamp) &&
+ GST_BUFFER_TIMESTAMP (buffer) != adder->next_timestamp) {
+ GstClockTimeDiff diff = GST_CLOCK_DIFF (GST_BUFFER_TIMESTAMP (buffer),
+ adder->next_timestamp);
+ if (diff < 0)
+ diff = -diff;
+
+ if (diff < GST_SECOND / adder->rate) {
+ GST_BUFFER_TIMESTAMP (buffer) = adder->next_timestamp;
+ GST_DEBUG_OBJECT (adder, "Correcting slight skew");
+ GST_BUFFER_FLAG_UNSET (buffer, GST_BUFFER_FLAG_DISCONT);
+ } else {
+ GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT);
+ GST_DEBUG_OBJECT (adder, "Expected buffer at %" GST_TIME_FORMAT
+ ", but is at %" GST_TIME_FORMAT ", setting discont",
+ GST_TIME_ARGS (adder->next_timestamp),
+ GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)));
+ }
+ } else {
+ GST_BUFFER_FLAG_UNSET (buffer, GST_BUFFER_FLAG_DISCONT);
+ }
+
+ GST_BUFFER_OFFSET (buffer) = GST_BUFFER_OFFSET_NONE;
+ GST_BUFFER_OFFSET_END (buffer) = GST_BUFFER_OFFSET_NONE;
+
+ if (GST_BUFFER_DURATION_IS_VALID (buffer))
+ adder->next_timestamp = GST_BUFFER_TIMESTAMP (buffer) +
+ GST_BUFFER_DURATION (buffer);
+ else
+ adder->next_timestamp = GST_CLOCK_TIME_NONE;
+
+ if (adder->segment_pending) {
+ /*
+ * We set the start at 0, because we re-timestamps to the running time
+ */
+ newseg_event = gst_event_new_new_segment_full (FALSE, 1.0, 1.0,
+ GST_FORMAT_TIME, 0, -1, 0);
+
+ adder->segment_pending = FALSE;
+ }
+
+ GST_OBJECT_UNLOCK (adder);
+
+ if (newseg_event)
+ gst_pad_push_event (adder->srcpad, newseg_event);
+
+ GST_LOG_OBJECT (adder, "About to push buffer time:%" GST_TIME_FORMAT
+ " duration:%" GST_TIME_FORMAT,
+ GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
+ GST_TIME_ARGS (GST_BUFFER_DURATION (buffer)));
+
+ result = gst_pad_push (adder->srcpad, buffer);
+ if (result != GST_FLOW_OK)
+ goto pause;
+
+ return;
+
+flushing:
+ {
+ GST_DEBUG_OBJECT (adder, "we are flushing");
+ gst_pad_pause_task (adder->srcpad);
+ GST_OBJECT_UNLOCK (adder);
+ return;
+ }
+
+clock_error:
+ {
+ gst_pad_pause_task (adder->srcpad);
+ GST_OBJECT_UNLOCK (adder);
+ GST_ELEMENT_ERROR (adder, STREAM, MUX, ("Error with the clock"),
+ ("Error with the clock: %d", ret));
+ GST_ERROR_OBJECT (adder, "Error with the clock: %d", ret);
+ return;
+ }
+
+no_clock:
+ {
+ gst_pad_pause_task (adder->srcpad);
+ GST_OBJECT_UNLOCK (adder);
+ GST_ELEMENT_ERROR (adder, STREAM, MUX, ("No available clock"),
+ ("No available clock"));
+ GST_ERROR_OBJECT (adder, "No available clock");
+ return;
+ }
+
+pause:
+ {
+ GST_DEBUG_OBJECT (adder, "pausing task, reason %s",
+ gst_flow_get_name (result));
+
+ GST_OBJECT_LOCK (adder);
+
+ /* store result */
+ adder->srcresult = result;
+ /* we don't post errors or anything because upstream will do that for us
+ * when we pass the return value upstream. */
+ gst_pad_pause_task (adder->srcpad);
+ GST_OBJECT_UNLOCK (adder);
+ return;
+ }
+
+eos:
+ {
+ /* store result, we are flushing now */
+ GST_DEBUG_OBJECT (adder, "We are EOS, pushing EOS downstream");
+ adder->srcresult = GST_FLOW_UNEXPECTED;
+ gst_pad_pause_task (adder->srcpad);
+ GST_OBJECT_UNLOCK (adder);
+ gst_pad_push_event (adder->srcpad, gst_event_new_eos ());
+ return;
+ }
+}
+
+static GstPad *
+gst_live_adder_request_new_pad (GstElement * element, GstPadTemplate * templ,
+ const gchar * unused)
+{
+ gchar *name;
+ GstLiveAdder *adder;
+ GstPad *newpad;
+ gint padcount;
+ GstLiveAdderPadPrivate *padprivate = NULL;
+
+ if (templ->direction != GST_PAD_SINK)
+ goto not_sink;
+
+ adder = GST_LIVE_ADDER (element);
+
+ /* increment pad counter */
+ padcount = g_atomic_int_exchange_and_add (&adder->padcount, 1);
+
+ name = g_strdup_printf ("sink%d", padcount);
+ newpad = gst_pad_new_from_template (templ, name);
+ GST_DEBUG_OBJECT (adder, "request new pad %s", name);
+ g_free (name);
+
+ gst_pad_set_getcaps_function (newpad,
+ GST_DEBUG_FUNCPTR (gst_live_adder_sink_getcaps));
+ gst_pad_set_setcaps_function (newpad,
+ GST_DEBUG_FUNCPTR (gst_live_adder_setcaps));
+ gst_pad_set_event_function (newpad,
+ GST_DEBUG_FUNCPTR (gst_live_adder_sink_event));
+
+ padprivate = g_new0 (GstLiveAdderPadPrivate, 1);
+
+ gst_segment_init (&padprivate->segment, GST_FORMAT_UNDEFINED);
+ padprivate->eos = FALSE;
+ padprivate->expected_timestamp = GST_CLOCK_TIME_NONE;
+
+ gst_pad_set_element_private (newpad, padprivate);
+
+ gst_pad_set_chain_function (newpad, gst_live_live_adder_chain);
+
+
+ if (!gst_pad_set_active (newpad, TRUE))
+ goto could_not_activate;
+
+ /* takes ownership of the pad */
+ if (!gst_element_add_pad (GST_ELEMENT (adder), newpad))
+ goto could_not_add;
+
+ GST_OBJECT_LOCK (adder);
+ adder->sinkpads = g_list_prepend (adder->sinkpads, newpad);
+ GST_OBJECT_UNLOCK (adder);
+
+ return newpad;
+
+ /* errors */
+not_sink:
+ {
+ g_warning ("gstadder: request new pad that is not a SINK pad\n");
+ return NULL;
+ }
+could_not_add:
+ {
+ GST_DEBUG_OBJECT (adder, "could not add pad");
+ g_free (padprivate);
+ gst_object_unref (newpad);
+ return NULL;
+ }
+could_not_activate:
+ {
+ GST_DEBUG_OBJECT (adder, "could not activate new pad");
+ g_free (padprivate);
+ gst_object_unref (newpad);
+ return NULL;
+ }
+}
+
+static void
+gst_live_adder_release_pad (GstElement * element, GstPad * pad)
+{
+ GstLiveAdder *adder;
+ GstLiveAdderPadPrivate *padprivate;
+
+ adder = GST_LIVE_ADDER (element);
+
+ GST_DEBUG_OBJECT (adder, "release pad %s:%s", GST_DEBUG_PAD_NAME (pad));
+
+ GST_OBJECT_LOCK (element);
+ padprivate = gst_pad_get_element_private (pad);
+ gst_pad_set_element_private (pad, NULL);
+ adder->sinkpads = g_list_remove_all (adder->sinkpads, pad);
+ GST_OBJECT_UNLOCK (element);
+
+ g_free (padprivate);
+
+ gst_element_remove_pad (element, pad);
+}
+
+static void
+reset_pad_private (GstPad * pad)
+{
+ GstLiveAdderPadPrivate *padprivate;
+
+ padprivate = gst_pad_get_element_private (pad);
+
+ if (!padprivate)
+ return;
+
+ gst_segment_init (&padprivate->segment, GST_FORMAT_UNDEFINED);
+
+ padprivate->expected_timestamp = GST_CLOCK_TIME_NONE;
+ padprivate->eos = FALSE;
+}
+
+static GstStateChangeReturn
+gst_live_adder_change_state (GstElement * element, GstStateChange transition)
+{
+ GstLiveAdder *adder;
+ GstStateChangeReturn ret;
+
+ adder = GST_LIVE_ADDER (element);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ GST_OBJECT_LOCK (adder);
+ adder->segment_pending = TRUE;
+ adder->peer_latency = 0;
+ adder->next_timestamp = GST_CLOCK_TIME_NONE;
+ g_list_foreach (adder->sinkpads, (GFunc) reset_pad_private, NULL);
+ GST_OBJECT_UNLOCK (adder);
+ break;
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ GST_OBJECT_LOCK (adder);
+ adder->playing = FALSE;
+ GST_OBJECT_UNLOCK (adder);
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+ GST_OBJECT_LOCK (adder);
+ adder->playing = TRUE;
+ GST_OBJECT_UNLOCK (adder);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+ if (!gst_element_register (plugin, "liveadder", GST_RANK_NONE,
+ GST_TYPE_LIVE_ADDER)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ "liveadder",
+ "Adds multiple live discontinuous streams",
+ plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
diff --git a/farsight/liveadder/liveadder.h b/farsight/liveadder/liveadder.h
new file mode 100644
index 0000000..7448601
--- /dev/null
+++ b/farsight/liveadder/liveadder.h
@@ -0,0 +1,108 @@
+/*
+ * Farsight Voice+Video library
+ *
+ * Copyright 2008 Collabora Ltd
+ * Copyright 2008 Nokia Corporation
+ * @author: Olivier Crete <olivier.crete@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+
+
+#ifndef __GST_LIVE_ADDER_H__
+#define __GST_LIVE_ADDER_H__
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+#define GST_TYPE_LIVE_ADDER (gst_live_adder_get_type())
+#define GST_LIVE_ADDER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_LIVE_ADDER,GstLiveAdder))
+#define GST_IS_LIVE_ADDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_LIVE_ADDER))
+#define GST_LIVE_ADDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_LIVE_ADDER,GstLiveAdderClass))
+#define GST_IS_LIVE_ADDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_LIVE_ADDER))
+#define GST_LIVE_ADDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_LIVE_ADDER,GstLiveAdderClass))
+typedef struct _GstLiveAdder GstLiveAdder;
+typedef struct _GstLiveAdderClass GstLiveAdderClass;
+
+typedef enum
+{
+ GST_LIVE_ADDER_FORMAT_UNSET,
+ GST_LIVE_ADDER_FORMAT_INT,
+ GST_LIVE_ADDER_FORMAT_FLOAT
+} GstLiveAdderFormat;
+
+typedef void (*GstLiveAdderFunction) (gpointer out, gpointer in, guint size);
+
+/**
+ * GstLiveAdder:
+ *
+ * The adder object structure.
+ */
+struct _GstLiveAdder
+{
+ /*< private >*/
+ GstElement element;
+
+ GstPad *srcpad;
+ /* pad counter, used for creating unique request pads */
+ gint padcount;
+ GList *sinkpads;
+
+ GstFlowReturn srcresult;
+ GstClockID clock_id;
+
+ /* the queue is ordered head to tail */
+ GQueue *buffers;
+ GCond *not_empty_cond;
+
+ GstClockTime next_timestamp;
+
+ /* the next are valid for both int and float */
+ GstLiveAdderFormat format;
+ gint rate;
+ gint channels;
+ gint width;
+ gint endianness;
+
+ /* the next are valid only for format == GST_LIVE_ADDER_FORMAT_INT */
+ gint depth;
+ gboolean is_signed;
+
+ /* number of bytes per sample, actually width/8 * channels */
+ gint bps;
+
+ /* function to add samples */
+ GstLiveAdderFunction func;
+
+ GstClockTime latency_ms;
+ GstClockTime peer_latency;
+
+ gboolean segment_pending;
+
+ gboolean playing;
+};
+
+struct _GstLiveAdderClass
+{
+ GstElementClass parent_class;
+};
+
+GType gst_live_adder_get_type (void);
+
+G_END_DECLS
+#endif /* __GST_LIVE_ADDER_H__ */
diff --git a/farsight/rtpmux/Makefile.am b/farsight/rtpmux/Makefile.am
new file mode 100644
index 0000000..b77deeb
--- /dev/null
+++ b/farsight/rtpmux/Makefile.am
@@ -0,0 +1,13 @@
+plugin_LTLIBRARIES = libgstrtpmux.la
+
+libgstrtpmux_la_SOURCES = gstrtpmuxer.c gstrtpmux.c gstrtpdtmfmux.c
+
+libgstrtpmux_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) \
+ -DEXTERN_BUF -DRTP_SUPPORT
+libgstrtpmux_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) -lgstrtp-@GST_MAJORMINOR@ \
+ $(GST_BASE_LIBS) $(GST_LIBS)
+libgstrtpmux_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+libgstrtpmux_la_LIBTOOLFLAGS = --tag=disable-static
+
+noinst_HEADERS = gstrtpmux.h gstrtpdtmfmux.h
+
diff --git a/farsight/rtpmux/gstrtpdtmfmux.c b/farsight/rtpmux/gstrtpdtmfmux.c
new file mode 100644
index 0000000..9622c26
--- /dev/null
+++ b/farsight/rtpmux/gstrtpdtmfmux.c
@@ -0,0 +1,327 @@
+/* RTP DTMF muxer element for GStreamer
+ *
+ * gstrtpdtmfmux.c:
+ *
+ * Copyright (C) <2007> Nokia Corporation.
+ * Contact: Zeeshan Ali <zeeshan.ali@nokia.com>
+ * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
+ * 2000,2005 Wim Taymans <wim@fluendo.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/**
+ * SECTION:element-rtpdtmfmux
+ * @see_also: rtpdtmfsrc, dtmfsrc
+ *
+ * The RTPDTMFMuxer mixes/muxes RTP DTMF stream(s) into other RTP
+ * streams. It does exactly what it's parent (RTPMuxer) does, except
+ * that it allows upstream peer elements to request exclusive access
+ * to the stream, which is required by the RTP DTMF standards (see RFC
+ * 2833, section 3.2, para 1 for details). The peer upstream element
+ * requests the acquisition and release of a stream lock beginning
+ * using custom downstream gstreamer events. To request the acquisition
+ * of the lock, the peer element must send an event of type
+ * GST_EVENT_CUSTOM_DOWNSTREAM_OOB, having a
+ * structure of name "stream-lock" with only one boolean field:
+ * "lock". If this field is set to TRUE, the request is for the
+ * acquisition of the lock, otherwise it is for release of the lock.
+ *
+ * For example, the following code in an upstream peer element
+ * requests the acquisition of the stream lock:
+ *
+ * <programlisting>
+ * GstEvent *event;
+ * GstStructure *structure;
+ * GstPad *srcpad;
+ *
+ * ... /\* srcpad initialization goes here \*\/
+ *
+ * structure = gst_structure_new ("stream-lock",
+ * "lock", G_TYPE_BOOLEAN, TRUE, NULL);
+ *
+ * event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_OOB, structure);
+ * gst_pad_push_event (dtmfsrc->srcpad, event);
+ * </programlisting>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gst.h>
+#include <string.h>
+
+#include "gstrtpdtmfmux.h"
+
+GST_DEBUG_CATEGORY_STATIC (gst_rtp_dtmf_mux_debug);
+#define GST_CAT_DEFAULT gst_rtp_dtmf_mux_debug
+
+/* elementfactory information */
+static const GstElementDetails gst_rtp_dtmf_mux_details =
+GST_ELEMENT_DETAILS ("RTP muxer",
+ "Codec/Muxer",
+ "mixes RTP DTMF streams into other RTP streams",
+ "Zeeshan Ali <first.last@nokia.com>");
+
+enum
+{
+ SIGNAL_LOCKING_STREAM,
+ SIGNAL_UNLOCKED_STREAM,
+ LAST_SIGNAL
+};
+
+static guint gst_rtpdtmfmux_signals[LAST_SIGNAL] = { 0 };
+
+static void gst_rtp_dtmf_mux_dispose (GObject * object);
+
+static void gst_rtp_dtmf_mux_release_pad (GstElement * element, GstPad * pad);
+
+static gboolean gst_rtp_dtmf_mux_sink_event (GstPad * pad, GstEvent * event);
+static GstFlowReturn gst_rtp_dtmf_mux_chain (GstPad * pad, GstBuffer * buffer);
+
+GST_BOILERPLATE (GstRTPDTMFMux, gst_rtp_dtmf_mux, GstRTPMux, GST_TYPE_RTP_MUX);
+
+static void
+gst_rtp_dtmf_mux_init (GstRTPDTMFMux * object, GstRTPDTMFMuxClass * g_class)
+{
+}
+
+static void
+gst_rtp_dtmf_mux_base_init (gpointer g_class)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
+
+ gst_element_class_set_details (element_class, &gst_rtp_dtmf_mux_details);
+}
+
+static void
+gst_rtp_dtmf_mux_class_init (GstRTPDTMFMuxClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+ GstRTPMuxClass *gstrtpmux_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+ gstrtpmux_class = (GstRTPMuxClass *) klass;
+
+ gst_rtpdtmfmux_signals[SIGNAL_LOCKING_STREAM] =
+ g_signal_new ("locking", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GstRTPDTMFMuxClass, locking), NULL, NULL,
+ gst_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GST_TYPE_PAD);
+
+ gst_rtpdtmfmux_signals[SIGNAL_UNLOCKED_STREAM] =
+ g_signal_new ("unlocked", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GstRTPDTMFMuxClass, unlocked), NULL, NULL,
+ gst_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GST_TYPE_PAD);
+
+ gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_rtp_dtmf_mux_dispose);
+ gstelement_class->release_pad =
+ GST_DEBUG_FUNCPTR (gst_rtp_dtmf_mux_release_pad);
+ gstrtpmux_class->chain_func = GST_DEBUG_FUNCPTR (gst_rtp_dtmf_mux_chain);
+ gstrtpmux_class->sink_event_func =
+ GST_DEBUG_FUNCPTR (gst_rtp_dtmf_mux_sink_event);
+}
+
+static void
+gst_rtp_dtmf_mux_dispose (GObject * object)
+{
+ GstRTPDTMFMux *mux;
+
+ mux = GST_RTP_DTMF_MUX (object);
+
+ GST_OBJECT_LOCK (mux);
+ if (mux->special_pad != NULL) {
+ gst_object_unref (mux->special_pad);
+ mux->special_pad = NULL;
+ }
+ GST_OBJECT_UNLOCK (mux);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static GstFlowReturn
+gst_rtp_dtmf_mux_chain (GstPad * pad, GstBuffer * buffer)
+{
+ GstRTPDTMFMux *mux;
+ GstFlowReturn ret;
+
+ mux = GST_RTP_DTMF_MUX (gst_pad_get_parent (pad));
+
+ GST_OBJECT_LOCK (mux);
+ if (mux->special_pad != NULL && mux->special_pad != pad) {
+ /* Drop the buffer */
+ gst_buffer_unref (buffer);
+ ret = GST_FLOW_OK;
+ GST_OBJECT_UNLOCK (mux);
+ }
+
+ else {
+ GST_OBJECT_UNLOCK (mux);
+ if (parent_class->chain_func)
+ ret = parent_class->chain_func (pad, buffer);
+ else {
+ gst_buffer_unref (buffer);
+ ret = GST_FLOW_ERROR;
+ }
+ }
+
+ gst_object_unref (mux);
+ return ret;
+}
+
+static void
+gst_rtp_dtmf_mux_lock_stream (GstRTPDTMFMux * mux, GstPad * pad)
+{
+ if (mux->special_pad != NULL)
+ GST_WARNING_OBJECT (mux,
+ "Stream lock already acquired by pad %s",
+ GST_ELEMENT_NAME (mux->special_pad));
+
+ else {
+ GST_DEBUG_OBJECT (mux,
+ "Stream lock acquired by pad %s", GST_ELEMENT_NAME (pad));
+ mux->special_pad = gst_object_ref (pad);
+ }
+}
+
+static void
+gst_rtp_dtmf_mux_unlock_stream (GstRTPDTMFMux * mux, GstPad * pad)
+{
+ if (mux->special_pad == NULL)
+ GST_WARNING_OBJECT (mux, "Stream lock not acquired, can't release it");
+
+ else if (pad != mux->special_pad)
+ GST_WARNING_OBJECT (mux,
+ "pad %s attempted to release Stream lock"
+ " which was acquired by pad %s", GST_ELEMENT_NAME (pad),
+ GST_ELEMENT_NAME (mux->special_pad));
+ else {
+ GST_DEBUG_OBJECT (mux,
+ "Stream lock released by pad %s", GST_ELEMENT_NAME (mux->special_pad));
+ gst_object_unref (mux->special_pad);
+ mux->special_pad = NULL;
+ }
+}
+
+static gboolean
+gst_rtp_dtmf_mux_handle_stream_lock_event (GstRTPDTMFMux * mux, GstPad * pad,
+ const GstStructure * event_structure)
+{
+ gboolean lock;
+
+ if (!gst_structure_get_boolean (event_structure, "lock", &lock))
+ return FALSE;
+
+ if (lock)
+ g_signal_emit (G_OBJECT (mux),
+ gst_rtpdtmfmux_signals[SIGNAL_LOCKING_STREAM], 0, pad);
+
+ GST_OBJECT_LOCK (mux);
+ if (lock)
+ gst_rtp_dtmf_mux_lock_stream (mux, pad);
+ else
+ gst_rtp_dtmf_mux_unlock_stream (mux, pad);
+ GST_OBJECT_UNLOCK (mux);
+
+ if (!lock)
+ g_signal_emit (G_OBJECT (mux),
+ gst_rtpdtmfmux_signals[SIGNAL_UNLOCKED_STREAM], 0, pad);
+
+ return TRUE;
+}
+
+static gboolean
+gst_rtp_dtmf_mux_handle_downstream_event (GstRTPDTMFMux * mux,
+ GstPad * pad, GstEvent * event)
+{
+ const GstStructure *structure;
+ gboolean ret = FALSE;
+
+ structure = gst_event_get_structure (event);
+ /* FIXME: is this event generic enough to be given a generic name? */
+ if (structure && gst_structure_has_name (structure, "stream-lock"))
+ ret = gst_rtp_dtmf_mux_handle_stream_lock_event (mux, pad, structure);
+
+ return ret;
+}
+
+static gboolean
+gst_rtp_dtmf_mux_ignore_event (GstPad * pad, GstEvent * event)
+{
+ gboolean ret;
+
+ if (parent_class->sink_event_func)
+ /* Give the parent a chance to handle the event first */
+ ret = parent_class->sink_event_func (pad, event);
+
+ else
+ ret = gst_pad_event_default (pad, event);
+
+ return ret;
+}
+
+static gboolean
+gst_rtp_dtmf_mux_sink_event (GstPad * pad, GstEvent * event)
+{
+ GstRTPDTMFMux *mux;
+ GstEventType type;
+ gboolean ret = FALSE;
+
+ type = event ? GST_EVENT_TYPE (event) : GST_EVENT_UNKNOWN;
+
+ mux = (GstRTPDTMFMux *) gst_pad_get_parent (pad);
+
+ switch (type) {
+ case GST_EVENT_CUSTOM_DOWNSTREAM_OOB:
+ ret = gst_rtp_dtmf_mux_handle_downstream_event (mux, pad, event);
+ gst_event_unref (event);
+ break;
+ default:
+ ret = gst_rtp_dtmf_mux_ignore_event (pad, event);
+ break;
+ }
+
+ gst_object_unref (mux);
+ return ret;
+}
+
+static void
+gst_rtp_dtmf_mux_release_pad (GstElement * element, GstPad * pad)
+{
+ GstRTPDTMFMux *mux = GST_RTP_DTMF_MUX (element);
+
+ GST_OBJECT_LOCK (mux);
+ if (mux->special_pad == pad) {
+ gst_object_unref (mux->special_pad);
+ mux->special_pad = NULL;
+ }
+ GST_OBJECT_UNLOCK (mux);
+
+ GST_CALL_PARENT (GST_ELEMENT_CLASS, release_pad, (element, pad));
+}
+
+gboolean
+gst_rtp_dtmf_mux_plugin_init (GstPlugin * plugin)
+{
+ GST_DEBUG_CATEGORY_INIT (gst_rtp_dtmf_mux_debug, "rtpdtmfmux", 0,
+ "rtp dtmf muxer");
+
+ return gst_element_register (plugin, "rtpdtmfmux", GST_RANK_NONE,
+ GST_TYPE_RTP_DTMF_MUX);
+}
diff --git a/farsight/rtpmux/gstrtpdtmfmux.h b/farsight/rtpmux/gstrtpdtmfmux.h
new file mode 100644
index 0000000..343be5c
--- /dev/null
+++ b/farsight/rtpmux/gstrtpdtmfmux.h
@@ -0,0 +1,68 @@
+/* RTP muxer element for GStreamer
+ *
+ * gstrtpdtmfmux.h:
+ *
+ * Copyright (C) <2007> Nokia Corporation.
+ * Contact: Zeeshan Ali <zeeshan.ali@nokia.com>
+ * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
+ * 2000,2005 Wim Taymans <wim@fluendo.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GST_RTP_DTMF_MUX_H__
+#define __GST_RTP_DTMF_MUX_H__
+
+#include <gst/gst.h>
+#include "gstrtpmux.h"
+
+G_BEGIN_DECLS
+#define GST_TYPE_RTP_DTMF_MUX (gst_rtp_dtmf_mux_get_type())
+#define GST_RTP_DTMF_MUX(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_DTMF_MUX, GstRTPDTMFMux))
+#define GST_RTP_DTMF_MUX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_DTMF_MUX, GstRTPDTMFMux))
+#define GST_IS_RTP_DTMF_MUX(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_DTMF_MUX))
+#define GST_IS_RTP_DTMF_MUX_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_DTMF_MUX))
+typedef struct _GstRTPDTMFMux GstRTPDTMFMux;
+typedef struct _GstRTPDTMFMuxClass GstRTPDTMFMuxClass;
+
+/**
+ * GstRTPDTMFMux:
+ *
+ * The opaque #GstRTPDTMFMux structure.
+ */
+struct _GstRTPDTMFMux
+{
+ GstRTPMux mux;
+
+ /* Protected by object lock */
+ /* our special pad */
+ GstPad *special_pad;
+};
+
+struct _GstRTPDTMFMuxClass
+{
+ GstRTPMuxClass parent_class;
+
+ /* signals */
+ void (*locking) (GstElement * element, GstPad * pad);
+ void (*unlocked) (GstElement * element, GstPad * pad);
+};
+
+GType gst_rtp_dtmf_mux_get_type (void);
+gboolean gst_rtp_dtmf_mux_plugin_init (GstPlugin * plugin);
+
+G_END_DECLS
+#endif /* __GST_RTP_DTMF_MUX_H__ */
diff --git a/farsight/rtpmux/gstrtpmux.c b/farsight/rtpmux/gstrtpmux.c
new file mode 100644
index 0000000..7785889
--- /dev/null
+++ b/farsight/rtpmux/gstrtpmux.c
@@ -0,0 +1,662 @@
+/* RTP muxer element for GStreamer
+ *
+ * gstrtpmux.c:
+ *
+ * Copyright (C) <2007> Nokia Corporation.
+ * Contact: Zeeshan Ali <zeeshan.ali@nokia.com>
+ * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
+ * 2000,2005 Wim Taymans <wim@fluendo.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/**
+ * SECTION:element-rtpmux
+ *
+ * The rtp muxer takes multiple RTP streams having the same clock-rate and
+ * muxes into a single stream with a single SSRC.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gst.h>
+#include <gst/rtp/gstrtpbuffer.h>
+#include <string.h>
+
+#include "gstrtpmux.h"
+
+GST_DEBUG_CATEGORY_STATIC (gst_rtp_mux_debug);
+#define GST_CAT_DEFAULT gst_rtp_mux_debug
+
+/* elementfactory information */
+static const GstElementDetails gst_rtp_mux_details =
+GST_ELEMENT_DETAILS ("RTP muxer",
+ "Codec/Muxer",
+ "multiplex N rtp streams into one",
+ "Zeeshan Ali <first.last@nokia.com>");
+
+enum
+{
+ ARG_0,
+ PROP_TIMESTAMP_OFFSET,
+ PROP_SEQNUM_OFFSET,
+ PROP_SEQNUM,
+ PROP_SSRC
+};
+
+#define DEFAULT_TIMESTAMP_OFFSET -1
+#define DEFAULT_SEQNUM_OFFSET -1
+#define DEFAULT_SSRC -1
+
+typedef struct
+{
+ gboolean have_clock_base;
+ guint clock_base;
+
+ GstCaps *out_caps;
+} GstRTPMuxPadPrivate;
+
+static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("application/x-rtp")
+ );
+
+static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink_%d",
+ GST_PAD_SINK,
+ GST_PAD_REQUEST,
+ GST_STATIC_CAPS ("application/x-rtp")
+ );
+
+static GstPad *gst_rtp_mux_request_new_pad (GstElement * element,
+ GstPadTemplate * templ, const gchar * name);
+static void gst_rtp_mux_release_pad (GstElement * element, GstPad * pad);
+static GstFlowReturn gst_rtp_mux_chain (GstPad * pad, GstBuffer * buffer);
+static gboolean gst_rtp_mux_setcaps (GstPad * pad, GstCaps * caps);
+static GstCaps *gst_rtp_mux_getcaps (GstPad * pad);
+
+static GstStateChangeReturn gst_rtp_mux_change_state (GstElement *
+ element, GstStateChange transition);
+
+static void gst_rtp_mux_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_rtp_mux_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+static void gst_rtp_mux_dispose (GObject * object);
+
+GST_BOILERPLATE (GstRTPMux, gst_rtp_mux, GstElement, GST_TYPE_ELEMENT);
+
+static void
+gst_rtp_mux_base_init (gpointer g_class)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
+
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&src_factory));
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&sink_factory));
+
+ gst_element_class_set_details (element_class, &gst_rtp_mux_details);
+}
+
+static void
+gst_rtp_mux_class_init (GstRTPMuxClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+
+ gobject_class->get_property = gst_rtp_mux_get_property;
+ gobject_class->set_property = gst_rtp_mux_set_property;
+ gobject_class->dispose = gst_rtp_mux_dispose;
+
+ g_object_class_install_property (G_OBJECT_CLASS (klass),
+ PROP_TIMESTAMP_OFFSET, g_param_spec_int ("timestamp-offset",
+ "Timestamp Offset",
+ "Offset to add to all outgoing timestamps (-1 = random)", -1,
+ G_MAXINT, DEFAULT_TIMESTAMP_OFFSET, G_PARAM_READWRITE));
+ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SEQNUM_OFFSET,
+ g_param_spec_int ("seqnum-offset", "Sequence number Offset",
+ "Offset to add to all outgoing seqnum (-1 = random)", -1, G_MAXINT,
+ DEFAULT_SEQNUM_OFFSET, G_PARAM_READWRITE));
+ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SEQNUM,
+ g_param_spec_uint ("seqnum", "Sequence number",
+ "The RTP sequence number of the last processed packet",
+ 0, G_MAXUINT, 0, G_PARAM_READABLE));
+ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SSRC,
+ g_param_spec_uint ("ssrc", "SSRC",
+ "The SSRC of the packets (-1 == random)",
+ 0, G_MAXUINT, DEFAULT_SSRC, G_PARAM_READWRITE));
+
+ gstelement_class->request_new_pad =
+ GST_DEBUG_FUNCPTR (gst_rtp_mux_request_new_pad);
+ gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_rtp_mux_release_pad);
+ gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_rtp_mux_change_state);
+
+ klass->chain_func = gst_rtp_mux_chain;
+}
+
+static void
+gst_rtp_mux_dispose (GObject * object)
+{
+ GList *item;
+
+restart:
+ for (item = GST_ELEMENT_PADS (object); item; item = g_list_next (item)) {
+ GstPad *pad = GST_PAD (item->data);
+ if (GST_PAD_IS_SINK (pad)) {
+ gst_element_release_request_pad (GST_ELEMENT (object), pad);
+ goto restart;
+ }
+ }
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static gboolean
+gst_rtp_mux_src_event (GstPad * pad, GstEvent * event)
+{
+ GstElement *rtp_mux;
+ GstIterator *iter;
+ GstPad *sinkpad;
+ gboolean result = FALSE;
+ gboolean done = FALSE;
+
+ rtp_mux = gst_pad_get_parent_element (pad);
+ g_return_val_if_fail (rtp_mux != NULL, FALSE);
+
+ iter = gst_element_iterate_sink_pads (rtp_mux);
+
+ while (!done) {
+ switch (gst_iterator_next (iter, (gpointer) & sinkpad)) {
+ case GST_ITERATOR_OK:
+ gst_event_ref (event);
+ result |= gst_pad_push_event (sinkpad, event);
+ gst_object_unref (sinkpad);
+ break;
+ case GST_ITERATOR_RESYNC:
+ gst_iterator_resync (iter);
+ result = FALSE;
+ break;
+ case GST_ITERATOR_ERROR:
+ GST_WARNING_OBJECT (rtp_mux, "Error iterating sinkpads");
+ case GST_ITERATOR_DONE:
+ done = TRUE;
+ break;
+ }
+ }
+ gst_iterator_free (iter);
+ gst_object_unref (rtp_mux);
+ gst_event_unref (event);
+
+ return result;
+}
+
+static void
+gst_rtp_mux_init (GstRTPMux * object, GstRTPMuxClass * g_class)
+{
+ GstElementClass *klass = GST_ELEMENT_GET_CLASS (object);
+
+ object->srcpad =
+ gst_pad_new_from_template (gst_element_class_get_pad_template (klass,
+ "src"), "src");
+ gst_pad_set_event_function (object->srcpad,
+ GST_DEBUG_FUNCPTR (gst_rtp_mux_src_event));
+ gst_element_add_pad (GST_ELEMENT (object), object->srcpad);
+
+ object->ssrc = DEFAULT_SSRC;
+ object->ts_offset = DEFAULT_TIMESTAMP_OFFSET;
+ object->seqnum_offset = DEFAULT_SEQNUM_OFFSET;
+}
+
+static GstPad *
+gst_rtp_mux_create_sinkpad (GstRTPMux * rtp_mux, GstPadTemplate * templ,
+ const gchar * req_name)
+{
+ GstPad *newpad = NULL;
+ GstPadTemplate *class_templ;
+
+ class_templ =
+ gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (rtp_mux),
+ "sink_%d");
+
+ if (templ == class_templ) {
+ gchar *tmpname = NULL;
+ const gchar *name = NULL;
+
+ /* create new pad with the name */
+ if (req_name)
+ name = req_name;
+ else
+ name = tmpname = g_strdup_printf ("sink_%02d", rtp_mux->numpads);
+ newpad = gst_pad_new_from_template (templ, name);
+ g_free (tmpname);
+
+ rtp_mux->numpads++;
+ } else {
+ GST_WARNING_OBJECT (rtp_mux, "this is not our template!\n");
+ }
+
+ return newpad;
+}
+
+static void
+gst_rtp_mux_setup_sinkpad (GstRTPMux * rtp_mux, GstPad * sinkpad)
+{
+ GstRTPMuxClass *klass;
+ GstRTPMuxPadPrivate *padpriv = g_slice_new0 (GstRTPMuxPadPrivate);
+
+ klass = GST_RTP_MUX_GET_CLASS (rtp_mux);
+
+ /* setup some pad functions */
+ gst_pad_set_setcaps_function (sinkpad, gst_rtp_mux_setcaps);
+ gst_pad_set_getcaps_function (sinkpad, gst_rtp_mux_getcaps);
+ if (klass->chain_func)
+ gst_pad_set_chain_function (sinkpad, klass->chain_func);
+ if (klass->sink_event_func)
+ gst_pad_set_event_function (sinkpad, klass->sink_event_func);
+
+ /* This could break with gstreamer 0.10.9 */
+ gst_pad_set_active (sinkpad, TRUE);
+
+ gst_pad_set_element_private (sinkpad, padpriv);
+
+ /* dd the pad to the element */
+ gst_element_add_pad (GST_ELEMENT (rtp_mux), sinkpad);
+}
+
+static GstPad *
+gst_rtp_mux_request_new_pad (GstElement * element,
+ GstPadTemplate * templ, const gchar * req_name)
+{
+ GstRTPMux *rtp_mux;
+ GstPad *newpad;
+
+ g_return_val_if_fail (templ != NULL, NULL);
+ g_return_val_if_fail (GST_IS_RTP_MUX (element), NULL);
+
+ rtp_mux = GST_RTP_MUX (element);
+
+ if (templ->direction != GST_PAD_SINK) {
+ GST_WARNING_OBJECT (rtp_mux, "request pad that is not a SINK pad");
+ return NULL;
+ }
+
+ newpad = gst_rtp_mux_create_sinkpad (rtp_mux, templ, req_name);
+ if (newpad)
+ gst_rtp_mux_setup_sinkpad (rtp_mux, newpad);
+ else
+ GST_WARNING_OBJECT (rtp_mux, "failed to create request pad");
+
+ return newpad;
+}
+
+static void
+gst_rtp_mux_release_pad (GstElement * element, GstPad * pad)
+{
+ GstRTPMuxPadPrivate *padpriv;
+
+ GST_OBJECT_LOCK (element);
+ padpriv = gst_pad_get_element_private (pad);
+ gst_pad_set_element_private (pad, NULL);
+ GST_OBJECT_UNLOCK (element);
+
+ gst_element_remove_pad (element, pad);
+
+ if (padpriv) {
+ gst_caps_replace (&padpriv->out_caps, NULL);
+ g_slice_free (GstRTPMuxPadPrivate, padpriv);
+ }
+}
+
+/* Put our own clock-base on the buffer */
+static void
+gst_rtp_mux_readjust_rtp_timestamp (GstRTPMux * rtp_mux, GstPad * pad,
+ GstBuffer * buffer)
+{
+ guint32 ts;
+ guint32 sink_ts_base = 0;
+ GstRTPMuxPadPrivate *padpriv;
+
+
+ GST_OBJECT_LOCK (rtp_mux);
+ padpriv = gst_pad_get_element_private (pad);
+ if (padpriv && padpriv->have_clock_base)
+ sink_ts_base = padpriv->clock_base;
+ GST_OBJECT_UNLOCK (rtp_mux);
+
+ ts = gst_rtp_buffer_get_timestamp (buffer) - sink_ts_base + rtp_mux->ts_base;
+ GST_LOG_OBJECT (rtp_mux, "Re-adjusting RTP ts %u to %u",
+ gst_rtp_buffer_get_timestamp (buffer), ts);
+ gst_rtp_buffer_set_timestamp (buffer, ts);
+}
+
+static GstFlowReturn
+gst_rtp_mux_chain (GstPad * pad, GstBuffer * buffer)
+{
+ GstRTPMux *rtp_mux;
+ GstFlowReturn ret;
+ GstRTPMuxPadPrivate *padpriv;
+
+ rtp_mux = GST_RTP_MUX (gst_pad_get_parent (pad));
+
+ if (!gst_rtp_buffer_validate (buffer)) {
+ GST_ERROR_OBJECT (rtp_mux, "Invalid RTP buffer");
+ gst_object_unref (rtp_mux);
+ return GST_FLOW_ERROR;
+ }
+
+ buffer = gst_buffer_make_writable (buffer);
+
+ GST_OBJECT_LOCK (rtp_mux);
+ rtp_mux->seqnum++;
+ gst_rtp_buffer_set_seq (buffer, rtp_mux->seqnum);
+ padpriv = gst_pad_get_element_private (pad);
+ if (padpriv)
+ gst_buffer_set_caps (buffer, padpriv->out_caps);
+ GST_OBJECT_UNLOCK (rtp_mux);
+ gst_rtp_buffer_set_ssrc (buffer, rtp_mux->current_ssrc);
+ gst_rtp_mux_readjust_rtp_timestamp (rtp_mux, pad, buffer);
+ GST_LOG_OBJECT (rtp_mux, "Pushing packet size %d, seq=%d, ts=%u",
+ GST_BUFFER_SIZE (buffer), rtp_mux->seqnum,
+ gst_rtp_buffer_get_timestamp (buffer));
+
+ if (!padpriv) {
+ ret = GST_FLOW_NOT_LINKED;
+ gst_buffer_unref (buffer);
+ goto out;
+ }
+
+ ret = gst_pad_push (rtp_mux->srcpad, buffer);
+
+out:
+
+ gst_object_unref (rtp_mux);
+ return ret;
+}
+
+static gboolean
+gst_rtp_mux_setcaps (GstPad * pad, GstCaps * caps)
+{
+ GstRTPMux *rtp_mux;
+ GstStructure *structure;
+ gboolean ret = FALSE;
+ GstRTPMuxPadPrivate *padpriv;
+
+ rtp_mux = GST_RTP_MUX (gst_pad_get_parent (pad));
+
+ structure = gst_caps_get_structure (caps, 0);
+
+ if (!structure)
+ goto out;
+
+ GST_OBJECT_LOCK (rtp_mux);
+ padpriv = gst_pad_get_element_private (pad);
+ if (padpriv &&
+ gst_structure_get_uint (structure, "clock-base", &padpriv->clock_base)) {
+ padpriv->have_clock_base = TRUE;
+ }
+ GST_OBJECT_UNLOCK (rtp_mux);
+
+ caps = gst_caps_copy (caps);
+
+ gst_caps_set_simple (caps,
+ "clock-base", G_TYPE_UINT, rtp_mux->ts_base,
+ "seqnum-base", G_TYPE_UINT, rtp_mux->seqnum_base, NULL);
+
+ GST_DEBUG_OBJECT (rtp_mux,
+ "setting caps %" GST_PTR_FORMAT " on src pad..", caps);
+ ret = gst_pad_set_caps (rtp_mux->srcpad, caps);
+
+ if (rtp_mux->ssrc == -1) {
+ if (gst_structure_has_field_typed (structure, "ssrc", G_TYPE_UINT)) {
+ rtp_mux->current_ssrc = g_value_get_uint
+ (gst_structure_get_value (structure, "ssrc"));
+ }
+ }
+
+ if (ret) {
+ GST_OBJECT_LOCK (rtp_mux);
+ padpriv = gst_pad_get_element_private (pad);
+ if (padpriv)
+ gst_caps_replace (&padpriv->out_caps, caps);
+ GST_OBJECT_UNLOCK (rtp_mux);
+ }
+ gst_caps_unref (caps);
+
+out:
+ gst_object_unref (rtp_mux);
+
+ return ret;
+}
+
+static void
+clear_caps (GstCaps * caps, gboolean only_clock_rate)
+{
+ gint i, j;
+
+ /* Lets only match on the clock-rate */
+ for (i = 0; i < gst_caps_get_size (caps); i++) {
+ GstStructure *s = gst_caps_get_structure (caps, i);
+
+ for (j = 0; j < gst_structure_n_fields (s); j++) {
+ const gchar *name = gst_structure_nth_field_name (s, j);
+
+ if (strcmp (name, "clock-rate") && (only_clock_rate ||
+ (strcmp (name, "ssrc")))) {
+ gst_structure_remove_field (s, name);
+ j--;
+ }
+ }
+ }
+}
+
+static gboolean
+same_clock_rate_fold (gpointer item, GValue * ret, gpointer user_data)
+{
+ GstPad *mypad = user_data;
+ GstPad *pad = item;
+ GstCaps *peercaps;
+ GstCaps *othercaps;
+ const GstCaps *accumcaps;
+ GstCaps *intersect;
+
+ if (pad == mypad) {
+ gst_object_unref (pad);
+ return TRUE;
+ }
+
+ peercaps = gst_pad_peer_get_caps (pad);
+ if (!peercaps) {
+ gst_object_unref (pad);
+ return TRUE;
+ }
+
+ othercaps = gst_caps_intersect (peercaps,
+ gst_pad_get_pad_template_caps (pad));
+ gst_caps_unref (peercaps);
+
+ accumcaps = gst_value_get_caps (ret);
+
+ clear_caps (othercaps, TRUE);
+
+ intersect = gst_caps_intersect (accumcaps, othercaps);
+
+ g_value_take_boxed (ret, intersect);
+
+ gst_caps_unref (othercaps);
+ gst_object_unref (pad);
+
+ return !gst_caps_is_empty (intersect);
+}
+
+static GstCaps *
+gst_rtp_mux_getcaps (GstPad * pad)
+{
+ GstRTPMux *mux = GST_RTP_MUX (gst_pad_get_parent (pad));
+ GstCaps *caps = NULL;
+ GstIterator *iter = NULL;
+ GValue v = { 0 };
+ GstIteratorResult res;
+ GstCaps *peercaps = gst_pad_peer_get_caps (mux->srcpad);
+ GstCaps *othercaps = NULL;
+
+ if (peercaps) {
+ othercaps = gst_caps_intersect (peercaps,
+ gst_pad_get_pad_template_caps (pad));
+ gst_caps_unref (peercaps);
+ } else {
+ othercaps = gst_caps_copy (gst_pad_get_pad_template_caps (mux->srcpad));
+ }
+
+ clear_caps (othercaps, FALSE);
+
+ g_value_init (&v, GST_TYPE_CAPS);
+
+ iter = gst_element_iterate_sink_pads (GST_ELEMENT (mux));
+ do {
+ gst_value_set_caps (&v, othercaps);
+ res = gst_iterator_fold (iter, same_clock_rate_fold, &v, pad);
+ } while (res == GST_ITERATOR_RESYNC);
+ gst_iterator_free (iter);
+
+ caps = (GstCaps *) gst_value_get_caps (&v);
+
+ if (res == GST_ITERATOR_ERROR) {
+ gst_caps_unref (caps);
+ caps = gst_caps_new_empty ();
+ }
+
+ if (othercaps)
+ gst_caps_unref (othercaps);
+ gst_object_unref (mux);
+
+ return caps;
+}
+
+
+static void
+gst_rtp_mux_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec)
+{
+ GstRTPMux *rtp_mux;
+
+ rtp_mux = GST_RTP_MUX (object);
+
+ switch (prop_id) {
+ case PROP_TIMESTAMP_OFFSET:
+ g_value_set_int (value, rtp_mux->ts_offset);
+ break;
+ case PROP_SEQNUM_OFFSET:
+ g_value_set_int (value, rtp_mux->seqnum_offset);
+ break;
+ case PROP_SEQNUM:
+ GST_OBJECT_LOCK (rtp_mux);
+ g_value_set_uint (value, rtp_mux->seqnum);
+ GST_OBJECT_UNLOCK (rtp_mux);
+ break;
+ case PROP_SSRC:
+ g_value_set_uint (value, rtp_mux->ssrc);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_rtp_mux_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+ GstRTPMux *rtp_mux;
+
+ rtp_mux = GST_RTP_MUX (object);
+
+ switch (prop_id) {
+ case PROP_TIMESTAMP_OFFSET:
+ rtp_mux->ts_offset = g_value_get_int (value);
+ break;
+ case PROP_SEQNUM_OFFSET:
+ rtp_mux->seqnum_offset = g_value_get_int (value);
+ break;
+ case PROP_SSRC:
+ rtp_mux->ssrc = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_rtp_mux_ready_to_paused (GstRTPMux * rtp_mux)
+{
+ GST_OBJECT_LOCK (rtp_mux);
+
+ if (rtp_mux->ssrc == -1)
+ rtp_mux->current_ssrc = g_random_int ();
+ else
+ rtp_mux->current_ssrc = rtp_mux->ssrc;
+
+ if (rtp_mux->seqnum_offset == -1)
+ rtp_mux->seqnum_base = g_random_int_range (0, G_MAXUINT16);
+ else
+ rtp_mux->seqnum_base = rtp_mux->seqnum_offset;
+ rtp_mux->seqnum = rtp_mux->seqnum_base;
+
+ if (rtp_mux->ts_offset == -1)
+ rtp_mux->ts_base = g_random_int ();
+ else
+ rtp_mux->ts_base = rtp_mux->ts_offset;
+ GST_DEBUG_OBJECT (rtp_mux, "set clock-base to %u", rtp_mux->ts_base);
+
+ GST_OBJECT_UNLOCK (rtp_mux);
+}
+
+static GstStateChangeReturn
+gst_rtp_mux_change_state (GstElement * element, GstStateChange transition)
+{
+ GstRTPMux *rtp_mux;
+
+ rtp_mux = GST_RTP_MUX (element);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ break;
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ gst_rtp_mux_ready_to_paused (rtp_mux);
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ break;
+ default:
+ break;
+ }
+
+ return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+}
+
+gboolean
+gst_rtp_mux_plugin_init (GstPlugin * plugin)
+{
+ GST_DEBUG_CATEGORY_INIT (gst_rtp_mux_debug, "rtpmux", 0, "rtp muxer");
+
+ return gst_element_register (plugin, "rtpmux", GST_RANK_NONE,
+ GST_TYPE_RTP_MUX);
+}
diff --git a/farsight/rtpmux/gstrtpmux.h b/farsight/rtpmux/gstrtpmux.h
new file mode 100644
index 0000000..a3fc133
--- /dev/null
+++ b/farsight/rtpmux/gstrtpmux.h
@@ -0,0 +1,78 @@
+/* RTP muxer element for GStreamer
+ *
+ * gstrtpmux.h:
+ *
+ * Copyright (C) <2007> Nokia Corporation.
+ * Contact: Zeeshan Ali <zeeshan.ali@nokia.com>
+ * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
+ * 2000,2005 Wim Taymans <wim@fluendo.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GST_RTP_MUX_H__
+#define __GST_RTP_MUX_H__
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+#define GST_TYPE_RTP_MUX (gst_rtp_mux_get_type())
+#define GST_RTP_MUX(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_MUX, GstRTPMux))
+#define GST_RTP_MUX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_MUX, GstRTPMux))
+#define GST_RTP_MUX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_RTP_MUX, GstRTPMuxClass))
+#define GST_IS_RTP_MUX(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_MUX))
+#define GST_IS_RTP_MUX_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_MUX))
+typedef struct _GstRTPMux GstRTPMux;
+typedef struct _GstRTPMuxClass GstRTPMuxClass;
+
+/**
+ * GstRTPMux:
+ *
+ * The opaque #GstRTPMux structure.
+ */
+struct _GstRTPMux
+{
+ GstElement element;
+
+ /* pad */
+ GstPad *srcpad;
+
+ /* sinkpads */
+ gint numpads;
+
+ guint32 ts_base;
+ guint16 seqnum_base;
+
+ gint32 ts_offset;
+ gint16 seqnum_offset;
+ guint16 seqnum; /* protected by object lock */
+ guint ssrc;
+ guint current_ssrc;
+};
+
+struct _GstRTPMuxClass
+{
+ GstElementClass parent_class;
+
+ GstFlowReturn (*chain_func) (GstPad * pad, GstBuffer * buffer);
+ gboolean (*sink_event_func) (GstPad * pad, GstEvent * event);
+};
+
+GType gst_rtp_mux_get_type (void);
+gboolean gst_rtp_mux_plugin_init (GstPlugin * plugin);
+
+G_END_DECLS
+#endif /* __GST_RTP_MUX_H__ */
diff --git a/farsight/rtpmux/gstrtpmuxer.c b/farsight/rtpmux/gstrtpmuxer.c
new file mode 100644
index 0000000..619418b
--- /dev/null
+++ b/farsight/rtpmux/gstrtpmuxer.c
@@ -0,0 +1,48 @@
+/* GStreamer RTP Muxer Plugins
+ *
+ * gstrtpdtmf.c:
+ *
+ * Copyright (C) <2007> Nokia Corporation.
+ * Contact: Zeeshan Ali <zeeshan.ali@nokia.com>
+ * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
+ * 2000,2005 Wim Taymans <wim@fluendo.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "gstrtpmux.h"
+#include "gstrtpdtmfmux.h"
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+ if (!gst_rtp_mux_plugin_init (plugin))
+ return FALSE;
+ if (!gst_rtp_dtmf_mux_plugin_init (plugin))
+ return FALSE;
+
+ return TRUE;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ "rtpmux",
+ "RTP Muxer plugins",
+ plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
diff --git a/farsight/valve/Makefile.am b/farsight/valve/Makefile.am
new file mode 100644
index 0000000..4662a13
--- /dev/null
+++ b/farsight/valve/Makefile.am
@@ -0,0 +1,9 @@
+plugin_LTLIBRARIES = libgstvalve.la
+
+libgstvalve_la_SOURCES = gstvalve.c gstvalve.h
+
+libgstvalve_la_CFLAGS = $(GST_CFLAGS)
+libgstvalve_la_LIBADD = $(GST_LIBS)
+libgstvalve_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+libgstvalve_la_LIBTOOLFLAGS = --tag=disable-static
+
diff --git a/farsight/valve/gstvalve.c b/farsight/valve/gstvalve.c
new file mode 100644
index 0000000..702b3e5
--- /dev/null
+++ b/farsight/valve/gstvalve.c
@@ -0,0 +1,311 @@
+/*
+ * Farsight Voice+Video library
+ *
+ * Copyright 2007 Collabora Ltd,
+ * Copyright 2007 Nokia Corporation
+ * @author: Olivier Crete <olivier.crete@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+/**
+ * SECTION:element-valve
+ *
+ * The valve is a simple element that drops buffers when the #GstValve::drop
+ * property is set to %TRUE and lets then through otherwise.
+ *
+ * Any downstream error received while the #GstValve::drop property is %FALSE
+ * is ignored. So downstream element can be set to %GST_STATE_NULL and removed,
+ * without using pad blocking.
+ *
+ * Last reviewed on 2008-02-10 (0.10.11)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstvalve.h"
+
+#include <string.h>
+
+GST_DEBUG_CATEGORY (valve_debug);
+#define GST_CAT_DEFAULT (valve_debug)
+
+/* elementfactory information */
+static const GstElementDetails gst_valve_details =
+GST_ELEMENT_DETAILS ("Valve element",
+ "Filter",
+ "This element drops all packets when drop is TRUE",
+ "Olivier Crete <olivier.crete@collabora.co.uk>");
+
+
+static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS_ANY);
+
+static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS_ANY);
+
+/* Valve signals and args */
+enum
+{
+ /* FILL ME */
+ LAST_SIGNAL
+};
+
+enum
+{
+ ARG_0,
+ ARG_DROP,
+};
+
+
+
+
+static void gst_valve_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec);
+static void gst_valve_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec);
+
+static gboolean gst_valve_event (GstPad * pad, GstEvent * event);
+static GstFlowReturn gst_valve_buffer_alloc (GstPad * pad, guint64 offset,
+ guint size, GstCaps * caps, GstBuffer ** buf);
+static GstFlowReturn gst_valve_chain (GstPad * pad, GstBuffer * buffer);
+static GstCaps *gst_valve_getcaps (GstPad * pad);
+
+static void
+_do_init (GType type)
+{
+ GST_DEBUG_CATEGORY_INIT (valve_debug, "valve", 0, "Valve");
+}
+
+GST_BOILERPLATE_FULL (GstValve, gst_valve, GstElement,
+ GST_TYPE_ELEMENT, _do_init);
+
+static void
+gst_valve_base_init (gpointer klass)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&srctemplate));
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&sinktemplate));
+
+ gst_element_class_set_details (element_class, &gst_valve_details);
+}
+
+static void
+gst_valve_class_init (GstValveClass * klass)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = (GObjectClass *) klass;
+
+ gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_valve_set_property);
+ gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_valve_get_property);
+
+ g_object_class_install_property (gobject_class, ARG_DROP,
+ g_param_spec_boolean ("drop",
+ "Drops all buffers if TRUE",
+ "If this property if TRUE, the element will drop all buffers, if its FALSE, it will let them through",
+ FALSE, G_PARAM_READWRITE));
+
+ parent_class = g_type_class_peek_parent (klass);
+}
+
+static void
+gst_valve_init (GstValve * valve, GstValveClass * klass)
+{
+ valve->drop = FALSE;
+ valve->discont = FALSE;
+
+ valve->srcpad = gst_pad_new_from_static_template (&srctemplate, "src");
+ gst_pad_set_getcaps_function (valve->srcpad,
+ GST_DEBUG_FUNCPTR (gst_valve_getcaps));
+ gst_element_add_pad (GST_ELEMENT (valve), valve->srcpad);
+
+ valve->sinkpad = gst_pad_new_from_static_template (&sinktemplate, "sink");
+ gst_pad_set_chain_function (valve->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_valve_chain));
+ gst_pad_set_event_function (valve->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_valve_event));
+ gst_pad_set_bufferalloc_function (valve->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_valve_buffer_alloc));
+ gst_pad_set_getcaps_function (valve->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_valve_getcaps));
+ gst_element_add_pad (GST_ELEMENT (valve), valve->sinkpad);
+}
+
+
+static void
+gst_valve_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+ GstValve *valve = GST_VALVE (object);
+
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ case ARG_DROP:
+ GST_OBJECT_LOCK (object);
+ valve->drop = g_value_get_boolean (value);
+ GST_OBJECT_UNLOCK (object);
+ break;
+ }
+}
+
+static void
+gst_valve_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec)
+{
+ GstValve *valve = GST_VALVE (object);
+
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ case ARG_DROP:
+ GST_OBJECT_LOCK (object);
+ g_value_set_boolean (value, valve->drop);
+ GST_OBJECT_UNLOCK (object);
+ break;
+ }
+}
+
+static GstFlowReturn
+gst_valve_chain (GstPad * pad, GstBuffer * buffer)
+{
+ GstValve *valve = GST_VALVE (gst_pad_get_parent_element (pad));
+ GstFlowReturn ret = GST_FLOW_OK;
+ gboolean drop;
+
+ GST_OBJECT_LOCK (GST_OBJECT (valve));
+ drop = valve->drop;
+
+ if (!drop && valve->discont) {
+ buffer = gst_buffer_make_metadata_writable (buffer);
+ GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT);
+ valve->discont = FALSE;
+ }
+ GST_OBJECT_UNLOCK (GST_OBJECT (valve));
+
+ if (drop)
+ gst_buffer_unref (buffer);
+ else
+ ret = gst_pad_push (valve->srcpad, buffer);
+
+
+ GST_OBJECT_LOCK (GST_OBJECT (valve));
+ if (valve->drop)
+ ret = GST_FLOW_OK;
+ GST_OBJECT_UNLOCK (GST_OBJECT (valve));
+
+ gst_object_unref (valve);
+
+ return ret;
+}
+
+
+static gboolean
+gst_valve_event (GstPad * pad, GstEvent * event)
+{
+ GstValve *valve = GST_VALVE (gst_pad_get_parent_element (pad));
+ gboolean ret = TRUE;
+ gboolean drop;
+
+ GST_OBJECT_LOCK (GST_OBJECT (valve));
+ drop = valve->drop;
+ GST_OBJECT_UNLOCK (GST_OBJECT (valve));
+
+ if (drop)
+ gst_event_unref (event);
+ else
+ ret = gst_pad_push_event (valve->srcpad, event);
+
+ GST_OBJECT_LOCK (GST_OBJECT (valve));
+ if (valve->drop)
+ ret = TRUE;
+ GST_OBJECT_UNLOCK (GST_OBJECT (valve));
+
+ gst_object_unref (valve);
+ return ret;
+}
+
+static GstFlowReturn
+gst_valve_buffer_alloc (GstPad * pad, guint64 offset, guint size,
+ GstCaps * caps, GstBuffer ** buf)
+{
+ GstValve *valve = GST_VALVE (gst_pad_get_parent_element (pad));
+ GstFlowReturn ret = GST_FLOW_OK;
+ gboolean drop;
+
+ GST_OBJECT_LOCK (GST_OBJECT (valve));
+ drop = valve->drop;
+ GST_OBJECT_UNLOCK (GST_OBJECT (valve));
+
+ if (drop)
+ *buf = NULL;
+ else
+ ret = gst_pad_alloc_buffer (valve->srcpad, offset, size, caps, buf);
+
+ GST_OBJECT_LOCK (GST_OBJECT (valve));
+ if (valve->drop)
+ ret = GST_FLOW_OK;
+ GST_OBJECT_UNLOCK (GST_OBJECT (valve));
+
+ gst_object_unref (valve);
+
+ return ret;
+}
+
+static GstCaps *
+gst_valve_getcaps (GstPad * pad)
+{
+ GstValve *valve = GST_VALVE (gst_pad_get_parent (pad));
+ GstCaps *caps;
+
+ if (pad == valve->sinkpad)
+ caps = gst_pad_peer_get_caps (valve->srcpad);
+ else
+ caps = gst_pad_peer_get_caps (valve->sinkpad);
+
+ if (caps == NULL)
+ caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad));
+
+ gst_object_unref (valve);
+
+ return caps;
+}
+
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+ return gst_element_register (plugin, "valve",
+ GST_RANK_MARGINAL, GST_TYPE_VALVE);
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ "valve",
+ "Valve",
+ plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
diff --git a/farsight/valve/gstvalve.h b/farsight/valve/gstvalve.h
new file mode 100644
index 0000000..cc7cd38
--- /dev/null
+++ b/farsight/valve/gstvalve.h
@@ -0,0 +1,82 @@
+/*
+ * Farsight Voice+Video library
+ *
+ * Copyright 2007 Collabora Ltd,
+ * Copyright 2007 Nokia Corporation
+ * @author: Olivier Crete <olivier.crete@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __GST_VALVE_H__
+#define __GST_VALVE_H__
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+/* #define's don't like whitespacey bits */
+#define GST_TYPE_VALVE \
+ (gst_valve_get_type())
+#define GST_VALVE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+ GST_TYPE_VALVE,GstValve))
+#define GST_VALVE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ GST_TYPE_VALVE,GstValveClass))
+#define GST_IS_VALVE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_VALVE))
+#define GST_IS_VALVE_CLASS(obj) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_VALVE))
+typedef struct _GstValve GstValve;
+typedef struct _GstValveClass GstValveClass;
+typedef struct _GstValvePrivate GstValvePrivate;
+
+/**
+ * GstValve:
+ *
+ * The private valve structure
+ */
+struct _GstValve
+{
+ /*< private >*/
+ GstElement parent;
+
+ /* Protected by the object lock */
+ gboolean drop;
+
+ /* Protected by the stream lock */
+ gboolean discont;
+
+ GstPad *srcpad;
+ GstPad *sinkpad;
+
+ /*< private > */
+ gpointer _gst_reserved[GST_PADDING];
+};
+
+struct _GstValveClass
+{
+ GstElementClass parent_class;
+
+ /*< private > */
+ gpointer _gst_reserved[GST_PADDING];
+};
+
+GType gst_valve_get_type (void);
+
+G_END_DECLS
+#endif /* __GST_VALVE_H__ */
--
1.6.6