diff --git a/gst/dtmf/Makefile.am b/gst/dtmf/Makefile.am new file mode 100644 index 0000000000..56eda1669d --- /dev/null +++ b/gst/dtmf/Makefile.am @@ -0,0 +1,31 @@ +plugin_LTLIBRARIES = libgstdtmf.la + +libgstdtmf_la_SOURCES = gstdtmfsrc.c \ + gstrtpdtmfsrc.c \ + gstrtpdtmfdepay.c \ + gstdtmf.c + +noinst_HEADERS = gstdtmfsrc.h \ + gstrtpdtmfsrc.h \ + gstrtpdtmfdepay.h \ + gstdtmfcommon.h + +libgstdtmf_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) +libgstdtmf_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) -lgstrtp-@GST_API_VERSION@ \ + $(GST_BASE_LIBS) $(GST_LIBS) $(LIBM) +libgstdtmf_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstdtmf_la_LIBTOOLFLAGS = --tag=disable-static + +Android.mk: Makefile.am $(BUILT_SOURCES) + androgenizer \ + -:PROJECT libgstdtmf -:SHARED libgstdtmf \ + -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgstdtmf_la_SOURCES) \ + -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstdtmf_la_CFLAGS) \ + -:LDFLAGS $(libgstdtmf_la_LDFLAGS) \ + $(libgstdtmf_la_LIBADD) \ + -ldl \ + -:PASSTHROUGH LOCAL_ARM_MODE:=arm \ + LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \ + > $@ diff --git a/gst/dtmf/gstdtmf.c b/gst/dtmf/gstdtmf.c new file mode 100644 index 0000000000..7fd5ee84f1 --- /dev/null +++ b/gst/dtmf/gstdtmf.c @@ -0,0 +1,29 @@ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstdtmfsrc.h" +#include "gstrtpdtmfsrc.h" +#include "gstrtpdtmfdepay.h" + + +static gboolean +plugin_init (GstPlugin * plugin) +{ + 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/gst/dtmf/gstdtmfcommon.h b/gst/dtmf/gstdtmfcommon.h new file mode 100644 index 0000000000..82617d72fd --- /dev/null +++ b/gst/dtmf/gstdtmfcommon.h @@ -0,0 +1,37 @@ + +#ifndef __GST_RTP_DTMF_COMMON_H__ +#define __GST_RTP_DTMF_COMMON_H__ + +#define MIN_INTER_DIGIT_INTERVAL 100 /* ms */ +#define MIN_PULSE_DURATION 250 /* ms */ + +#define MIN_VOLUME 0 +#define MAX_VOLUME 36 + +#define MIN_EVENT 0 +#define MAX_EVENT 15 +#define MIN_EVENT_STRING "0" +#define MAX_EVENT_STRING "15" + +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#endif + +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/gst/dtmf/gstdtmfsrc.c b/gst/dtmf/gstdtmfsrc.c new file mode 100644 index 0000000000..a77e3ba8b8 --- /dev/null +++ b/gst/dtmf/gstdtmfsrc.c @@ -0,0 +1,969 @@ +/* GStreamer DTMF source + * + * gstdtmfsrc.c: + * + * Copyright (C) <2007> Collabora. + * Contact: Youness Alaoui + * Copyright (C) <2007> Nokia Corporation. + * Contact: Zeeshan Ali + * Copyright (C) 1999,2000 Erik Walthinsen + * 2000,2005 Wim Taymans + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, 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: + * + * + * + * + * + * + * + * + * + * Name + * GType + * Possible values + * Purpose + * + * + * + * + * type + * G_TYPE_INT + * 0-1 + * 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. + * + * + * + * number + * G_TYPE_INT + * 0-15 + * The event number. + * + * + * volume + * G_TYPE_INT + * 0-36 + * 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. + * + * + * + * start + * G_TYPE_BOOLEAN + * True or False + * Whether the event is starting or ending. + * + * + * method + * G_TYPE_INT + * 2 + * The method used for sending event, this element will react if this + * field is absent or 2. + * + * + * + * + * + * + * 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: + * + * + * 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); + * + * + * When a DTMF tone actually starts or stop, a "dtmf-event-processed" + * element #GstMessage with the same fields as the "dtmf-event" + * #GstEvent that was used to request the event. Also, if any event + * has not been processed when the element goes from the PAUSED to the + * READY state, then a "dtmf-event-dropped" message is posted on the + * #GstBus in the order that they were received. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include + +#include "gstdtmfcommon.h" + +#include "gstdtmfsrc.h" + +#include + +#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_DUTY_CYCLE (MIN_INTER_DIGIT_INTERVAL + MIN_PULSE_DURATION) + + +typedef struct st_dtmf_key +{ + const 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, +}; + +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, " + "format = (string) \"" GST_AUDIO_NE (S16) "\", " + "rate = " GST_AUDIO_RATE_RANGE ", " "channels = (int) 1") + ); + +#define parent_class gst_dtmf_src_parent_class +G_DEFINE_TYPE (GstDTMFSrc, gst_dtmf_src, 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 gboolean gst_dtmf_src_send_event (GstElement * 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_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); + + + GST_DEBUG_CATEGORY_INIT (gst_dtmf_src_debug, "dtmfsrc", 0, "dtmfsrc element"); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gst_dtmf_src_template)); + + gst_element_class_set_static_metadata (gstelement_class, + "DTMF tone generator", "Source/Audio", "Generates DTMF tones", + "Youness Alaoui "); + + + gobject_class->finalize = gst_dtmf_src_finalize; + gobject_class->set_property = gst_dtmf_src_set_property; + gobject_class->get_property = 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 | G_PARAM_STATIC_STRINGS)); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_dtmf_src_change_state); + gstelement_class->send_event = GST_DEBUG_FUNCPTR (gst_dtmf_src_send_event); + 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) +{ + /* 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 (gst_dtmf_src_parent_class)->finalize (object); +} + +static gboolean +gst_dtmf_src_handle_dtmf_event (GstDTMFSrc * dtmfsrc, GstEvent * event) +{ + const GstStructure *event_structure; + GstStateChangeReturn sret; + GstState state; + gint event_type; + gboolean start; + gint method; + GstClockTime last_stop; + gint event_number; + gint event_volume; + gboolean correct_order; + + sret = gst_element_get_state (GST_ELEMENT (dtmfsrc), &state, NULL, 0); + if (sret != GST_STATE_CHANGE_SUCCESS || state != GST_STATE_PLAYING) { + GST_DEBUG_OBJECT (dtmfsrc, "dtmf-event, but not in PLAYING state"); + goto failure; + } + + event_structure = gst_event_get_structure (event); + + 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) + if (!gst_structure_get_int (event_structure, "number", &event_number) || + !gst_structure_get_int (event_structure, "volume", &event_volume)) + goto failure; + + + GST_OBJECT_LOCK (dtmfsrc); + if (gst_structure_get_clock_time (event_structure, "last-stop", &last_stop)) + dtmfsrc->last_stop = last_stop; + else + dtmfsrc->last_stop = GST_CLOCK_TIME_NONE; + correct_order = (start != dtmfsrc->last_event_was_start); + dtmfsrc->last_event_was_start = start; + GST_OBJECT_UNLOCK (dtmfsrc); + + if (!correct_order) + goto failure; + + if (start) { + 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_event (GstBaseSrc * src, GstEvent * event) +{ + GstDTMFSrc *dtmfsrc; + gboolean result = FALSE; + + dtmfsrc = GST_DTMF_SRC (src); + + GST_LOG_OBJECT (dtmfsrc, "Received an %s event on the src pad", + GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_CUSTOM_UPSTREAM: + if (gst_event_has_name (event, "dtmf-event")) { + result = gst_dtmf_src_handle_dtmf_event (dtmfsrc, event); + break; + } + /* fall through */ + default: + result = GST_BASE_SRC_CLASS (parent_class)->event (src, event); + break; + } + + return result; +} + + +static gboolean +gst_dtmf_src_send_event (GstElement * element, GstEvent * event) +{ + GstDTMFSrc *dtmfsrc = GST_DTMF_SRC (element); + gboolean ret; + + GST_LOG_OBJECT (dtmfsrc, "Received an %s event via send_event", + GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_CUSTOM_BOTH: + case GST_EVENT_CUSTOM_BOTH_OOB: + case GST_EVENT_CUSTOM_UPSTREAM: + case GST_EVENT_CUSTOM_DOWNSTREAM: + case GST_EVENT_CUSTOM_DOWNSTREAM_OOB: + if (gst_event_has_name (event, "dtmf-event")) { + ret = gst_dtmf_src_handle_dtmf_event (dtmfsrc, event); + break; + } + /* fall through */ + default: + ret = GST_ELEMENT_CLASS (parent_class)->send_event (element, event); + break; + } + + return ret; +} + +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_prepare_timestamps (GstDTMFSrc * dtmfsrc) +{ + GstClockTime last_stop; + GstClockTime timestamp; + + GST_OBJECT_LOCK (dtmfsrc); + last_stop = dtmfsrc->last_stop; + GST_OBJECT_UNLOCK (dtmfsrc); + + if (GST_CLOCK_TIME_IS_VALID (last_stop)) { + timestamp = last_stop; + } else { + GstClock *clock; + + /* If there is no valid start time, lets use now as the start time */ + + clock = gst_element_get_clock (GST_ELEMENT (dtmfsrc)); + if (clock != NULL) { + timestamp = gst_clock_get_time (clock) + - gst_element_get_base_time (GST_ELEMENT (dtmfsrc)); + 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); + return; + } + } + + /* Make sure the timestamp always goes forward */ + if (timestamp > dtmfsrc->timestamp) + dtmfsrc->timestamp = timestamp; +} + +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 GstBuffer * +gst_dtmf_src_generate_silence (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; + + return gst_buffer_new_wrapped (g_malloc0 (buf_size), buf_size); +} + +static GstBuffer * +gst_dtmf_src_generate_tone (GstDTMFSrcEvent * event, DTMF_KEY key, + float duration, gint sample_rate) +{ + GstBuffer *buffer; + GstMapInfo map; + gint16 *p; + gint tone_size; + double i = 0; + double amplitude, f1, f2; + double volume_factor; + static GstAllocationParams params = { 0, 1, 0, 0, }; + + /* Create a buffer for the tone */ + tone_size = ((duration / 1000) * sample_rate * SAMPLE_SIZE * CHANNELS) / 8; + + buffer = gst_buffer_new_allocate (NULL, tone_size, ¶ms); + + gst_buffer_map (buffer, &map, GST_MAP_READWRITE); + p = (gint16 *) map.data; + + 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)++; + } + + gst_buffer_unmap (buffer, &map); + + return buffer; +} + + + +static GstBuffer * +gst_dtmf_src_create_next_tone_packet (GstDTMFSrc * dtmfsrc, + GstDTMFSrcEvent * event) +{ + GstBuffer *buf = NULL; + gboolean send_silence = FALSE; + + GST_LOG_OBJECT (dtmfsrc, "Creating buffer for tone %s", + DTMF_KEYS[event->event_number].event_name); + + if (event->packet_count * dtmfsrc->interval < MIN_INTER_DIGIT_INTERVAL) { + send_silence = TRUE; + } + + if (send_silence) { + GST_LOG_OBJECT (dtmfsrc, "Generating silence"); + buf = gst_dtmf_src_generate_silence (dtmfsrc->interval, + dtmfsrc->sample_rate); + } else { + GST_LOG_OBJECT (dtmfsrc, "Generating tone"); + buf = gst_dtmf_src_generate_tone (event, DTMF_KEYS[event->event_number], + dtmfsrc->interval, 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; + + GST_LOG_OBJECT (dtmfsrc, "Creating new buffer with event %u duration " + " gst: %" GST_TIME_FORMAT " at %" GST_TIME_FORMAT, + event->event_number, GST_TIME_ARGS (GST_BUFFER_DURATION (buf)), + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf))); + + dtmfsrc->timestamp += GST_BUFFER_DURATION (buf); + + return buf; +} + +static void +gst_dtmf_src_post_message (GstDTMFSrc * dtmfsrc, const gchar * message_name, + GstDTMFSrcEvent * event) +{ + GstStructure *s = NULL; + + switch (event->event_type) { + case DTMF_EVENT_TYPE_START: + s = gst_structure_new (message_name, + "type", G_TYPE_INT, 1, + "method", G_TYPE_INT, 2, + "start", G_TYPE_BOOLEAN, TRUE, + "number", G_TYPE_INT, event->event_number, + "volume", G_TYPE_INT, event->volume, NULL); + break; + case DTMF_EVENT_TYPE_STOP: + s = gst_structure_new (message_name, + "type", G_TYPE_INT, 1, "method", G_TYPE_INT, 2, + "start", G_TYPE_BOOLEAN, FALSE, NULL); + break; + case DTMF_EVENT_TYPE_PAUSE_TASK: + return; + } + + if (s) + gst_element_post_message (GST_ELEMENT (dtmfsrc), + gst_message_new_element (GST_OBJECT (dtmfsrc), s)); +} + +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"); + gst_dtmf_src_post_message (dtmfsrc, "dtmf-event-dropped", event); + break; + case DTMF_EVENT_TYPE_START: + gst_dtmf_prepare_timestamps (dtmfsrc); + + event->packet_count = 0; + dtmfsrc->last_event = event; + event = NULL; + gst_dtmf_src_post_message (dtmfsrc, "dtmf-event-processed", + dtmfsrc->last_event); + 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"); + gst_dtmf_src_post_message (dtmfsrc, "dtmf-event-dropped", event); + break; + case DTMF_EVENT_TYPE_STOP: + g_slice_free (GstDTMFSrcEvent, dtmfsrc->last_event); + dtmfsrc->last_event = NULL; + gst_dtmf_src_post_message (dtmfsrc, "dtmf-event-processed", event); + 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)); + + clockid = gst_clock_new_single_shot_id (clock, dtmfsrc->timestamp + + gst_element_get_base_time (GST_ELEMENT (dtmfsrc))); + 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 %" G_GSIZE_FORMAT, + gst_buffer_get_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 */ + g_slice_free (GstDTMFSrcEvent, dtmfsrc->last_event); + dtmfsrc->last_event = NULL; + } + + return GST_FLOW_FLUSHING; + +} + +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_pad_get_pad_template_caps (GST_BASE_SRC_PAD (basesrc)); + + if (gst_caps_is_empty (caps)) { + gst_caps_unref (caps); + return FALSE; + } + + caps = gst_caps_truncate (caps); + + caps = gst_caps_make_writable (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) { + gst_dtmf_src_post_message (dtmfsrc, "dtmf-event-dropped", event); + g_slice_free (GstDTMFSrcEvent, event); + event = g_async_queue_try_pop (dtmfsrc->event_queue); + } + dtmfsrc->last_event_was_start = FALSE; + dtmfsrc->timestamp = 0; + no_preroll = TRUE; + break; + default: + break; + } + + if ((result = + GST_ELEMENT_CLASS (gst_dtmf_src_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) { + gst_dtmf_src_post_message (dtmfsrc, "dtmf-event-dropped", event); + g_slice_free (GstDTMFSrcEvent, event); + event = g_async_queue_try_pop (dtmfsrc->event_queue); + } + dtmfsrc->last_event_was_start = FALSE; + + 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/gst/dtmf/gstdtmfsrc.h b/gst/dtmf/gstdtmfsrc.h new file mode 100644 index 0000000000..04440bc185 --- /dev/null +++ b/gst/dtmf/gstdtmfsrc.h @@ -0,0 +1,101 @@ +/* GStreamer DTMF source + * + * gstdtmfsrc.h: + * + * Copyright (C) <2007> Collabora. + * Contact: Youness Alaoui + * Copyright (C) <2007> Nokia Corporation. + * Contact: Zeeshan Ali + * Copyright (C) <2005> Wim Taymans + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_DTMF_SRC_H__ +#define __GST_DTMF_SRC_H__ + +#include +#include +#include + +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; + gboolean last_event_was_start; + + guint16 interval; + GstClockTime timestamp; + + gboolean paused; + GstClockID clockid; + + GstClockTime last_stop; + + 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/gst/dtmf/gstrtpdtmfdepay.c b/gst/dtmf/gstrtpdtmfdepay.c new file mode 100644 index 0000000000..d66f24a24d --- /dev/null +++ b/gst/dtmf/gstrtpdtmfdepay.c @@ -0,0 +1,543 @@ +/* GstRtpDtmfDepay + * + * Copyright (C) 2008 Collabora Limited + * Copyright (C) 2008 Nokia Corporation + * Contact: Youness Alaoui + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, 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 + * + * + * + * + * + * + * + * + * Name + * GType + * Possible values + * Purpose + * + * + * + * + * type + * G_TYPE_INT + * 0-1 + * 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. + * + * + * + * number + * G_TYPE_INT + * 0-16 + * The event number. + * + * + * volume + * G_TYPE_INT + * 0-36 + * 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. + * + * + * + * method + * G_TYPE_INT + * 1 + * This field will always been 1 (ie RTP event) from this element. + * + * + * + * + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstrtpdtmfdepay.h" + +#include +#include + +#include +#include + +#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_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 +{ + const 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, +}; + +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, " + "format = (string) \"" GST_AUDIO_NE (S16) "\", " + "rate = " GST_AUDIO_RATE_RANGE ", " "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\"") + ); + +G_DEFINE_TYPE (GstRtpDTMFDepay, gst_rtp_dtmf_depay, + GST_TYPE_RTP_BASE_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 (GstRTPBaseDepayload * depayload, + GstBuffer * buf); +gboolean gst_rtp_dtmf_depay_setcaps (GstRTPBaseDepayload * filter, + GstCaps * caps); + +static void +gst_rtp_dtmf_depay_class_init (GstRtpDTMFDepayClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstRTPBaseDepayloadClass *gstrtpbasedepayload_class; + + gobject_class = G_OBJECT_CLASS (klass); + gstelement_class = GST_ELEMENT_CLASS (klass); + gstrtpbasedepayload_class = GST_RTP_BASE_DEPAYLOAD_CLASS (klass); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gst_rtp_dtmf_depay_src_template)); + gst_element_class_add_pad_template (gstelement_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_static_metadata (gstelement_class, + "RTP DTMF packet depayloader", "Codec/Depayloader/Network", + "Generates DTMF Sound from telephone-event RTP packets", + "Youness Alaoui "); + + 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_PARAM_STATIC_STRINGS)); + + 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 | G_PARAM_STATIC_STRINGS)); + + gstrtpbasedepayload_class->process = + GST_DEBUG_FUNCPTR (gst_rtp_dtmf_depay_process); + gstrtpbasedepayload_class->set_caps = + GST_DEBUG_FUNCPTR (gst_rtp_dtmf_depay_setcaps); + +} + +static void +gst_rtp_dtmf_depay_init (GstRtpDTMFDepay * rtpdtmfdepay) +{ + 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 (GstRTPBaseDepayload * filter, GstCaps * caps) +{ + GstCaps *filtercaps, *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; + + filtercaps = + gst_pad_get_pad_template_caps (GST_RTP_BASE_DEPAYLOAD_SRCPAD (filter)); + + filtercaps = gst_caps_make_writable (filtercaps); + gst_caps_set_simple (filtercaps, "rate", G_TYPE_INT, clock_rate, NULL); + + srccaps = gst_pad_peer_query_caps (GST_RTP_BASE_DEPAYLOAD_SRCPAD (filter), + filtercaps); + gst_caps_unref (filtercaps); + + gst_pad_set_caps (GST_RTP_BASE_DEPAYLOAD_SRCPAD (filter), srccaps); + gst_caps_unref (srccaps); + + return TRUE; +} + +static GstBuffer * +gst_dtmf_src_generate_tone (GstRtpDTMFDepay * rtpdtmfdepay, + GstRTPDTMFPayload payload) +{ + GstBuffer *buf; + GstMapInfo map; + 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 */ ; + GstRTPBaseDepayload *depayload = GST_RTP_BASE_DEPAYLOAD (rtpdtmfdepay); + gint volume; + static GstAllocationParams params = { 0, 1, 0, 0, }; + + clock_rate = depayload->clock_rate; + + /* Create a buffer for the tone */ + tone_size = (payload.duration * SAMPLE_SIZE * CHANNELS) / 8; + buf = gst_buffer_new_allocate (NULL, tone_size, ¶ms); + GST_BUFFER_DURATION (buf) = payload.duration * GST_SECOND / clock_rate; + volume = payload.volume; + + gst_buffer_map (buf, &map, GST_MAP_WRITE); + p = (gint16 *) map.data; + + 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)++; + } + + gst_buffer_unmap (buf, &map); + + return buf; +} + + +static GstBuffer * +gst_rtp_dtmf_depay_process (GstRTPBaseDepayload * 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; + GstRTPBuffer rtpbuffer = GST_RTP_BUFFER_INIT; + + rtpdtmfdepay = GST_RTP_DTMF_DEPAY (depayload); + + gst_rtp_buffer_map (buf, GST_MAP_READ, &rtpbuffer); + + payload_len = gst_rtp_buffer_get_payload_len (&rtpbuffer); + payload = gst_rtp_buffer_get_payload (&rtpbuffer); + + 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 (&rtpbuffer); + + timestamp = gst_rtp_buffer_get_timestamp (&rtpbuffer); + + 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_PTS (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_dtmf_src_generate_tone (rtpdtmfdepay, dtmf_payload); + + + GST_BUFFER_PTS (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))); + + } + + gst_rtp_buffer_unmap (&rtpbuffer); + + return outbuf; + +bad_packet: + GST_ELEMENT_WARNING (rtpdtmfdepay, STREAM, DECODE, + ("Packet did not validate"), (NULL)); + + if (rtpbuffer.buffer != NULL) + gst_rtp_buffer_unmap (&rtpbuffer); + + 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/gst/dtmf/gstrtpdtmfdepay.h b/gst/dtmf/gstrtpdtmfdepay.h new file mode 100644 index 0000000000..c5ed189409 --- /dev/null +++ b/gst/dtmf/gstrtpdtmfdepay.h @@ -0,0 +1,68 @@ +/* GstRtpDtmfDepay + * + * Copyright (C) 2008 Collabora Limited + * Copyright (C) 2008 Nokia Corporation + * Contact: Youness Alaoui + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_RTP_DTMF_DEPAY_H__ +#define __GST_RTP_DTMF_DEPAY_H__ + +#include +#include +#include + +#include "gstdtmfcommon.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 >*/ + GstRTPBaseDepayload depayload; + double sample; + guint32 previous_ts; + guint16 previous_duration; + GstClockTime first_gst_ts; + guint unit_time; + guint max_duration; +}; + +struct _GstRtpDTMFDepayClass +{ + GstRTPBaseDepayloadClass parent_class; +}; + +GType gst_rtp_dtmf_depay_get_type (void); + +gboolean gst_rtp_dtmf_depay_plugin_init (GstPlugin * plugin); + +G_END_DECLS +#endif /* __GST_RTP_DTMF_DEPAY_H__ */ diff --git a/gst/dtmf/gstrtpdtmfsrc.c b/gst/dtmf/gstrtpdtmfsrc.c new file mode 100644 index 0000000000..2a0b4a3bb4 --- /dev/null +++ b/gst/dtmf/gstrtpdtmfsrc.c @@ -0,0 +1,1156 @@ +/* GStreamer RTP DTMF source + * + * gstrtpdtmfsrc.c: + * + * Copyright (C) <2007> Nokia Corporation. + * Contact: Zeeshan Ali + * Copyright (C) 1999,2000 Erik Walthinsen + * 2000,2005 Wim Taymans + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, 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: + * + * + * + * + * + * + * + * + * + * Name + * GType + * Possible values + * Purpose + * + * + * + * + * type + * G_TYPE_INT + * 0-1 + * 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. + * + * + * + * number + * G_TYPE_INT + * 0-15 + * The event number. + * + * + * volume + * G_TYPE_INT + * 0-36 + * 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. + * + * + * + * start + * G_TYPE_BOOLEAN + * True or False + * Whether the event is starting or ending. + * + * + * method + * G_TYPE_INT + * 1 + * The method used for sending event, this element will react if this + * field is absent or 1. + * + * + * + * + * + * + * 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: + * + * + * 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); + * + * + * When a DTMF tone actually starts or stop, a "dtmf-event-processed" + * element #GstMessage with the same fields as the "dtmf-event" + * #GstEvent that was used to request the event. Also, if any event + * has not been processed when the element goes from the PAUSED to the + * READY state, then a "dtmf-event-dropped" message is posted on the + * #GstBus in the order that they were received. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include + +#include "gstrtpdtmfsrc.h" + +#define GST_RTP_DTMF_TYPE_EVENT 1 +#define DEFAULT_PTIME 40 /* 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 DEFAULT_PACKET_REDUNDANCY 1 +#define MIN_PACKET_REDUNDANCY 1 +#define MAX_PACKET_REDUNDANCY 5 + +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_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 ], " + "encoding-name = (string) \"TELEPHONE-EVENT\"") + /* "events = (string) \"0-15\" */ + ); + + +G_DEFINE_TYPE (GstRTPDTMFSrc, gst_rtp_dtmf_src, GST_TYPE_BASE_SRC); + +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_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); + + GST_DEBUG_CATEGORY_INIT (gst_rtp_dtmf_src_debug, + "rtpdtmfsrc", 0, "rtpdtmfsrc element"); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gst_rtp_dtmf_src_template)); + + gst_element_class_set_static_metadata (gstelement_class, + "RTP DTMF packet generator", "Source/Network", + "Generates RTP DTMF packets", "Zeeshan Ali "); + + 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_PARAM_STATIC_STRINGS)); + 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_PARAM_STATIC_STRINGS)); + 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_PARAM_STATIC_STRINGS)); + 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_PARAM_STATIC_STRINGS)); + 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_PARAM_STATIC_STRINGS)); + 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_PARAM_STATIC_STRINGS)); + 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_PARAM_STATIC_STRINGS)); + 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 | G_PARAM_STATIC_STRINGS)); + + 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) +{ + 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->ptime = DEFAULT_PTIME; + 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 (gst_rtp_dtmf_src_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; + GstClockTime last_stop; + gint event_number; + gint event_volume; + gboolean correct_order; + + 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) + if (!gst_structure_get_int (event_structure, "number", &event_number) || + !gst_structure_get_int (event_structure, "volume", &event_volume)) + goto failure; + + GST_OBJECT_LOCK (dtmfsrc); + if (gst_structure_get_clock_time (event_structure, "last-stop", &last_stop)) + dtmfsrc->last_stop = last_stop; + else + dtmfsrc->last_stop = GST_CLOCK_TIME_NONE; + correct_order = (start != dtmfsrc->last_event_was_start); + dtmfsrc->last_event_was_start = start; + GST_OBJECT_UNLOCK (dtmfsrc); + + if (!correct_order) + goto failure; + + if (start) { + 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_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_REDUNDANCY: + g_value_set_uint (value, dtmfsrc->packet_redundancy); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +gst_rtp_dtmf_prepare_timestamps (GstRTPDTMFSrc * dtmfsrc) +{ + GstClockTime last_stop; + + GST_OBJECT_LOCK (dtmfsrc); + last_stop = dtmfsrc->last_stop; + GST_OBJECT_UNLOCK (dtmfsrc); + + if (GST_CLOCK_TIME_IS_VALID (last_stop)) { + dtmfsrc->start_timestamp = last_stop; + } else { + GstClock *clock = gst_element_get_clock (GST_ELEMENT (dtmfsrc)); + + if (clock == NULL) + return FALSE; + + dtmfsrc->start_timestamp = gst_clock_get_time (clock) + - gst_element_get_base_time (GST_ELEMENT (dtmfsrc)); + gst_object_unref (clock); + } + + /* If the last stop was in the past, then lets add the buffers together */ + if (dtmfsrc->start_timestamp < dtmfsrc->timestamp) + dtmfsrc->start_timestamp = dtmfsrc->timestamp; + + dtmfsrc->timestamp = dtmfsrc->start_timestamp; + + 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); + + return TRUE; +} + + +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); + + 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, + GstRTPBuffer * rtpbuf) +{ + gst_rtp_buffer_set_ssrc (rtpbuf, dtmfsrc->current_ssrc); + gst_rtp_buffer_set_payload_type (rtpbuf, dtmfsrc->pt); + /* Only the very first packet gets a marker */ + if (dtmfsrc->first_packet) { + gst_rtp_buffer_set_marker (rtpbuf, TRUE); + } else if (dtmfsrc->last_packet) { + dtmfsrc->payload->e = 1; + } + + dtmfsrc->seqnum++; + gst_rtp_buffer_set_seq (rtpbuf, dtmfsrc->seqnum); + + /* timestamp of RTP header */ + gst_rtp_buffer_set_timestamp (rtpbuf, dtmfsrc->rtp_timestamp); +} + +static GstBuffer * +gst_rtp_dtmf_src_create_next_rtp_packet (GstRTPDTMFSrc * dtmfsrc) +{ + GstBuffer *buf; + GstRTPDTMFPayload *payload; + GstRTPBuffer rtpbuffer = GST_RTP_BUFFER_INIT; + + buf = gst_rtp_buffer_new_allocate (sizeof (GstRTPDTMFPayload), 0, 0); + + gst_rtp_buffer_map (buf, GST_MAP_READWRITE, &rtpbuffer); + + gst_rtp_dtmf_prepare_rtp_headers (dtmfsrc, &rtpbuffer); + + /* 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->ptime * GST_MSECOND; + GST_BUFFER_PTS (buf) = dtmfsrc->timestamp; + + payload = (GstRTPDTMFPayload *) gst_rtp_buffer_get_payload (&rtpbuffer); + + /* copy payload and convert to network-byte order */ + g_memmove (payload, dtmfsrc->payload, sizeof (GstRTPDTMFPayload)); + + payload->duration = g_htons (payload->duration); + + if (dtmfsrc->redundancy_count <= 1 && dtmfsrc->last_packet) { + GstClockTime inter_digit_interval = MIN_INTER_DIGIT_INTERVAL; + + if (inter_digit_interval % dtmfsrc->ptime != 0) + inter_digit_interval += dtmfsrc->ptime - + (MIN_INTER_DIGIT_INTERVAL % dtmfsrc->ptime); + + GST_BUFFER_DURATION (buf) += inter_digit_interval * GST_MSECOND; + } + + GST_LOG_OBJECT (dtmfsrc, "Creating new buffer with event %u duration " + " gst: %" GST_TIME_FORMAT " at %" GST_TIME_FORMAT "(rtp ts:%u dur:%u)", + dtmfsrc->payload->event, GST_TIME_ARGS (GST_BUFFER_DURATION (buf)), + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), dtmfsrc->rtp_timestamp, + dtmfsrc->payload->duration); + + /* duration of DTMF payloadfor the NEXT packet */ + /* not updated for redundant packets */ + if (dtmfsrc->redundancy_count <= 1) + dtmfsrc->payload->duration += dtmfsrc->ptime * dtmfsrc->clock_rate / 1000; + + if (GST_CLOCK_TIME_IS_VALID (dtmfsrc->timestamp)) + dtmfsrc->timestamp += GST_BUFFER_DURATION (buf); + + gst_rtp_buffer_unmap (&rtpbuffer); + + return buf; +} + +static GstMessage * +gst_dtmf_src_prepare_message (GstRTPDTMFSrc * dtmfsrc, + const gchar * message_name, GstRTPDTMFSrcEvent * event) +{ + GstStructure *s; + + switch (event->event_type) { + case RTP_DTMF_EVENT_TYPE_START: + s = gst_structure_new (message_name, + "type", G_TYPE_INT, 1, + "method", G_TYPE_INT, 1, + "start", G_TYPE_BOOLEAN, TRUE, + "number", G_TYPE_INT, event->payload->event, + "volume", G_TYPE_INT, event->payload->volume, NULL); + break; + case RTP_DTMF_EVENT_TYPE_STOP: + s = gst_structure_new (message_name, + "type", G_TYPE_INT, 1, "method", G_TYPE_INT, 1, + "start", G_TYPE_BOOLEAN, FALSE, NULL); + break; + case RTP_DTMF_EVENT_TYPE_PAUSE_TASK: + return NULL; + default: + return NULL; + } + + return gst_message_new_element (GST_OBJECT (dtmfsrc), s); +} + +static void +gst_dtmf_src_post_message (GstRTPDTMFSrc * dtmfsrc, const gchar * message_name, + GstRTPDTMFSrcEvent * event) +{ + GstMessage *m = gst_dtmf_src_prepare_message (dtmfsrc, message_name, event); + + + if (m) + gst_element_post_message (GST_ELEMENT (dtmfsrc), m); +} + + +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; + GstMessage *message; + GQueue messages = G_QUEUE_INIT; + + 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"); + gst_dtmf_src_post_message (dtmfsrc, "dtmf-event-dropped", event); + 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; + if (!gst_rtp_dtmf_prepare_timestamps (dtmfsrc)) + goto no_clock; + + g_queue_push_tail (&messages, + gst_dtmf_src_prepare_message (dtmfsrc, "dtmf-event-processed", + event)); + dtmfsrc->payload = event->payload; + dtmfsrc->payload->duration = + dtmfsrc->ptime * dtmfsrc->clock_rate / 1000; + 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"); + gst_dtmf_src_post_message (dtmfsrc, "dtmf-event-dropped", event); + 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; + g_queue_push_tail (&messages, + gst_dtmf_src_prepare_message (dtmfsrc, "dtmf-event-processed", + event)); + 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)); + if (!clock) + goto no_clock; + clockid = gst_clock_new_single_shot_id (clock, dtmfsrc->timestamp + + gst_element_get_base_time (GST_ELEMENT (dtmfsrc))); + 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); + + while ((message = g_queue_pop_head (&messages)) != NULL) + gst_element_post_message (GST_ELEMENT (dtmfsrc), message); + + 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) { + + 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_FLUSHING; + } + +no_clock: + GST_ELEMENT_ERROR (dtmfsrc, STREAM, MUX, ("No available clock"), + ("No available clock")); + gst_pad_pause_task (GST_BASE_SRC_PAD (dtmfsrc)); + return GST_FLOW_ERROR; +} + + +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_query_caps (GST_BASE_SRC_PAD (basesrc), NULL); + 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 */ + temp = gst_caps_truncate (temp); + temp = gst_caps_make_writable (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); + } + + if (gst_structure_has_field_typed (s, "ptime", G_TYPE_UINT)) { + value = gst_structure_get_value (s, "ptime"); + dtmfsrc->ptime = g_value_get_uint (value); + GST_LOG_OBJECT (dtmfsrc, "using peer ptime %u", dtmfsrc->ptime); + } else if (gst_structure_has_field_typed (s, "maxptime", G_TYPE_UINT)) { + value = gst_structure_get_value (s, "maxptime"); + dtmfsrc->ptime = g_value_get_uint (value); + GST_LOG_OBJECT (dtmfsrc, "using peer maxptime as ptime %u", + dtmfsrc->ptime); + } else { + /* FIXME, fixate_nearest_uint would be even better */ + gst_structure_set (s, "ptime", G_TYPE_UINT, dtmfsrc->ptime, NULL); + GST_LOG_OBJECT (dtmfsrc, "using internal ptime %u", dtmfsrc->ptime); + } + + + 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; + + dtmfsrc->timestamp = 0; +} + +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_dtmf_src_post_message (dtmfsrc, "dtmf-event-dropped", event); + gst_rtp_dtmf_src_event_free (event); + } + dtmfsrc->last_event_was_start = FALSE; + + no_preroll = TRUE; + break; + default: + break; + } + + if ((result = + GST_ELEMENT_CLASS (gst_rtp_dtmf_src_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_dtmf_src_post_message (dtmfsrc, "dtmf-event-dropped", event); + gst_rtp_dtmf_src_event_free (event); + } + dtmfsrc->last_event_was_start = FALSE; + + /* 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/gst/dtmf/gstrtpdtmfsrc.h b/gst/dtmf/gstrtpdtmfsrc.h new file mode 100644 index 0000000000..3e9256ce58 --- /dev/null +++ b/gst/dtmf/gstrtpdtmfsrc.h @@ -0,0 +1,115 @@ +/* GStreamer RTP DTMF source + * + * gstrtpdtmfsrc.h: + * + * Copyright (C) <2007> Nokia Corporation. + * Contact: Zeeshan Ali + * Copyright (C) <2005> Wim Taymans + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_RTP_DTMF_SRC_H__ +#define __GST_RTP_DTMF_SRC_H__ + +#include +#include +#include + +#include "gstdtmfcommon.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 ptime; + guint16 packet_redundancy; + guint32 clock_rate; + gboolean last_event_was_start; + + GstClockTime last_stop; + + 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/tests/check/elements/dtmf.c b/tests/check/elements/dtmf.c new file mode 100644 index 0000000000..454a3209ed --- /dev/null +++ b/tests/check/elements/dtmf.c @@ -0,0 +1,588 @@ +/* GStreamer + * + * unit test for dtmf elements + * Copyright (C) 2013 Collabora Ltd + * @author: Olivier Crete + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + + +/* Include this from the plugin to get the defines */ + +#include "../../gst/dtmf/gstdtmfcommon.h" + +#define END_BIT (1<<7) + +static GstStaticPadTemplate audio_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) \"" GST_AUDIO_NE (S16) "\", " + "rate = " GST_AUDIO_RATE_RANGE ", " "channels = (int) 1") + ); + +static GstStaticPadTemplate 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) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", " + "clock-rate = (int) [ 0, MAX ], " + "encoding-name = (string) \"TELEPHONE-EVENT\"") + ); + + +static void +check_get_dtmf_event_message (GstBus * bus, gint number, gint volume) +{ + GstMessage *message; + gboolean have_message = FALSE; + + while (!have_message && + (message = gst_bus_pop_filtered (bus, GST_MESSAGE_ELEMENT)) != NULL) { + if (gst_message_has_name (message, "dtmf-event")) { + const GstStructure *s = gst_message_get_structure (message); + gint stype, snumber, smethod, svolume; + + fail_unless (gst_structure_get (s, + "type", G_TYPE_INT, &stype, + "number", G_TYPE_INT, &snumber, + "method", G_TYPE_INT, &smethod, + "volume", G_TYPE_INT, &svolume, NULL)); + + fail_unless (stype == 1); + fail_unless (smethod == 1); + fail_unless (snumber == number); + fail_unless (svolume == volume); + have_message = TRUE; + } + gst_message_unref (message); + } + + fail_unless (have_message); +} + +static void +check_no_dtmf_event_message (GstBus * bus) +{ + GstMessage *message; + gboolean have_message = FALSE; + + while (!have_message && + (message = gst_bus_pop_filtered (bus, GST_MESSAGE_ELEMENT)) != NULL) { + if (gst_message_has_name (message, "dtmf-event") || + gst_message_has_name (message, "dtmf-event-processed") || + gst_message_has_name (message, "dtmf-event-dropped")) { + have_message = TRUE; + } + gst_message_unref (message); + } + + fail_unless (!have_message); +} + +static void +check_buffers_duration (GstClockTime expected_duration) +{ + GstClockTime duration = 0; + + while (buffers) { + GstBuffer *buf = buffers->data; + + buffers = g_list_delete_link (buffers, buffers); + + fail_unless (GST_BUFFER_DURATION_IS_VALID (buf)); + duration += GST_BUFFER_DURATION (buf); + gst_buffer_unref (buf); + } + + fail_unless (duration == expected_duration); +} + +static void +send_rtp_packet (GstPad * src, guint timestamp, gboolean marker, gboolean end, + guint number, guint volume, guint duration) +{ + GstBuffer *buf; + GstRTPBuffer rtpbuf = GST_RTP_BUFFER_INIT; + gchar *payload; + static guint seqnum = 1; + + buf = gst_rtp_buffer_new_allocate (4, 0, 0); + fail_unless (gst_rtp_buffer_map (buf, GST_MAP_READWRITE, &rtpbuf)); + gst_rtp_buffer_set_seq (&rtpbuf, seqnum++); + gst_rtp_buffer_set_timestamp (&rtpbuf, timestamp); + gst_rtp_buffer_set_marker (&rtpbuf, marker); + payload = gst_rtp_buffer_get_payload (&rtpbuf); + payload[0] = number; + payload[1] = volume | (end ? END_BIT : 0); + GST_WRITE_UINT16_BE (payload + 2, duration); + gst_rtp_buffer_unmap (&rtpbuf); + fail_unless (gst_pad_push (src, buf) == GST_FLOW_OK); +} + +GST_START_TEST (test_rtpdtmfdepay) +{ + GstElement *dtmfdepay; + GstPad *src, *sink; + GstBus *bus; + GstCaps *caps_in; + GstCaps *expected_caps_out; + GstCaps *caps_out; + + dtmfdepay = gst_check_setup_element ("rtpdtmfdepay"); + sink = gst_check_setup_sink_pad (dtmfdepay, &audio_sink_template); + src = gst_check_setup_src_pad (dtmfdepay, &rtp_dtmf_src_template); + + bus = gst_bus_new (); + gst_element_set_bus (dtmfdepay, bus); + + gst_pad_set_active (src, TRUE); + gst_pad_set_active (sink, TRUE); + gst_element_set_state (dtmfdepay, GST_STATE_PLAYING); + + + caps_in = gst_caps_new_simple ("application/x-rtp", + "encoding-name", G_TYPE_STRING, "TELEPHONE-EVENT", + "media", G_TYPE_STRING, "audio", + "clock-rate", G_TYPE_INT, 1000, "payload", G_TYPE_INT, 99, NULL); + fail_unless (gst_pad_set_caps (src, caps_in)); + gst_caps_unref (caps_in); + + caps_out = gst_pad_get_current_caps (sink); + expected_caps_out = gst_caps_new_simple ("audio/x-raw", + "format", G_TYPE_STRING, GST_AUDIO_NE (S16), + "rate", G_TYPE_INT, 1000, "channels", G_TYPE_INT, 1, NULL); + fail_unless (gst_caps_is_equal_fixed (caps_out, expected_caps_out)); + gst_caps_unref (expected_caps_out); + gst_caps_unref (caps_out); + + /* Single packet DTMF */ + send_rtp_packet (src, 200, TRUE, TRUE, 1, 5, 250); + check_get_dtmf_event_message (bus, 1, 5); + check_buffers_duration (250 * GST_MSECOND); + + /* Two packet DTMF */ + send_rtp_packet (src, 800, TRUE, FALSE, 1, 5, 200); + send_rtp_packet (src, 800, FALSE, TRUE, 1, 5, 400); + check_buffers_duration (400 * GST_MSECOND); + check_get_dtmf_event_message (bus, 1, 5); + + /* Long DTMF */ + send_rtp_packet (src, 3000, TRUE, FALSE, 1, 5, 200); + check_get_dtmf_event_message (bus, 1, 5); + check_buffers_duration (200 * GST_MSECOND); + send_rtp_packet (src, 3000, FALSE, FALSE, 1, 5, 400); + check_no_dtmf_event_message (bus); + check_buffers_duration (200 * GST_MSECOND); + send_rtp_packet (src, 3000, FALSE, FALSE, 1, 5, 600); + check_no_dtmf_event_message (bus); + check_buffers_duration (200 * GST_MSECOND); + + /* New without end to last */ + send_rtp_packet (src, 4000, TRUE, TRUE, 1, 5, 250); + check_get_dtmf_event_message (bus, 1, 5); + check_buffers_duration (250 * GST_MSECOND); + + check_no_dtmf_event_message (bus); + fail_unless (buffers == NULL); + gst_element_set_bus (dtmfdepay, NULL); + gst_object_unref (bus); + + gst_pad_set_active (src, FALSE); + gst_pad_set_active (sink, FALSE); + gst_check_teardown_sink_pad (dtmfdepay); + gst_check_teardown_src_pad (dtmfdepay); + gst_check_teardown_element (dtmfdepay); +} + +GST_END_TEST; + + +static GstStaticPadTemplate rtp_dtmf_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp, " + "media = (string) \"audio\", " + "payload = (int) 99, " + "clock-rate = (int) 1000, " + "seqnum-base = (uint) 333, " + "clock-base = (uint) 666, " + "ssrc = (uint) 999, " + "maxptime = (uint) 20, encoding-name = (string) \"TELEPHONE-EVENT\"") + ); + +GstElement *dtmfsrc; +GstPad *sink; +GstClock *testclock; +GstBus *bus; + +static void +check_message_structure (GstStructure * expected_s) +{ + GstMessage *message; + gboolean have_message = FALSE; + + while (!have_message && + (message = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, + GST_MESSAGE_ELEMENT)) != NULL) { + if (gst_message_has_name (message, gst_structure_get_name (expected_s))) { + const GstStructure *s = gst_message_get_structure (message); + + fail_unless (gst_structure_is_equal (s, expected_s)); + have_message = TRUE; + } + gst_message_unref (message); + } + + fail_unless (have_message); + + gst_structure_free (expected_s); +} + +static void +check_rtp_buffer (GstClockTime ts, GstClockTime duration, gboolean start, + gboolean end, guint rtpts, guint ssrc, guint volume, guint number, + guint rtpduration) +{ + GstBuffer *buffer; + GstRTPBuffer rtpbuffer = GST_RTP_BUFFER_INIT; + gchar *payload; + + g_mutex_lock (&check_mutex); + while (buffers == NULL) + g_cond_wait (&check_cond, &check_mutex); + g_mutex_unlock (&check_mutex); + fail_unless (buffers != NULL); + + buffer = buffers->data; + buffers = g_list_delete_link (buffers, buffers); + + fail_unless (GST_BUFFER_PTS (buffer) == ts); + fail_unless (GST_BUFFER_DURATION (buffer) == duration); + + fail_unless (gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtpbuffer)); + fail_unless (gst_rtp_buffer_get_marker (&rtpbuffer) == start); + fail_unless (gst_rtp_buffer_get_timestamp (&rtpbuffer) == rtpts); + payload = gst_rtp_buffer_get_payload (&rtpbuffer); + + fail_unless (payload[0] == number); + fail_unless ((payload[1] & 0x7F) == volume); + fail_unless (! !(payload[1] & 0x80) == end); + fail_unless (GST_READ_UINT16_BE (payload + 2) == rtpduration); + + gst_rtp_buffer_unmap (&rtpbuffer); + gst_buffer_unref (buffer); +} + +gint method; + +static void +setup_rtpdtmfsrc (void) +{ + testclock = gst_test_clock_new (); + bus = gst_bus_new (); + + method = 1; + dtmfsrc = gst_check_setup_element ("rtpdtmfsrc"); + sink = gst_check_setup_sink_pad (dtmfsrc, &rtp_dtmf_sink_template); + gst_element_set_bus (dtmfsrc, bus); + fail_unless (gst_element_set_clock (dtmfsrc, testclock)); + + gst_pad_set_active (sink, TRUE); + fail_unless (gst_element_set_state (dtmfsrc, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS); +} + +static void +teardown_dtmfsrc (void) +{ + gst_object_unref (testclock); + gst_pad_set_active (sink, FALSE); + gst_element_set_bus (dtmfsrc, NULL); + gst_object_unref (bus); + gst_check_teardown_sink_pad (dtmfsrc); + gst_check_teardown_element (dtmfsrc); +} + +GST_START_TEST (test_dtmfsrc_invalid_events) +{ + GstStructure *s; + + /* Missing start */ + s = gst_structure_new ("dtmf-event", + "type", G_TYPE_INT, 1, "number", G_TYPE_INT, 3, + "method", G_TYPE_INT, method, "volume", G_TYPE_INT, 8, NULL); + fail_unless (gst_pad_push_event (sink, + gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, s)) == FALSE); + + /* Missing volume */ + s = gst_structure_new ("dtmf-event", + "type", G_TYPE_INT, 1, "number", G_TYPE_INT, 3, + "method", G_TYPE_INT, method, "start", G_TYPE_BOOLEAN, TRUE, NULL); + fail_unless (gst_pad_push_event (sink, + gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, s)) == FALSE); + + /* Missing number */ + s = gst_structure_new ("dtmf-event", + "type", G_TYPE_INT, 1, "method", G_TYPE_INT, method, + "volume", G_TYPE_INT, 8, "start", G_TYPE_BOOLEAN, TRUE, NULL); + fail_unless (gst_pad_push_event (sink, + gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, s)) == FALSE); + + /* Missing type */ + s = gst_structure_new ("dtmf-event", + "number", G_TYPE_INT, 3, "method", G_TYPE_INT, method, + "volume", G_TYPE_INT, 8, "start", G_TYPE_BOOLEAN, TRUE, NULL); + fail_unless (gst_pad_push_event (sink, + gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, s)) == FALSE); + + /* Stop before start */ + s = gst_structure_new ("dtmf-event", + "type", G_TYPE_INT, 1, "number", G_TYPE_INT, 3, + "method", G_TYPE_INT, method, "volume", G_TYPE_INT, 8, + "start", G_TYPE_BOOLEAN, FALSE, NULL); + fail_unless (gst_pad_push_event (sink, + gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, s)) == FALSE); + + gst_element_set_state (dtmfsrc, GST_STATE_NULL); +} + +GST_END_TEST; + +GST_START_TEST (test_rtpdtmfsrc_min_duration) +{ + GstStructure *s; + GstClockID id; + guint timestamp = 0; + GstCaps *expected_caps, *caps; + + /* Minimum duration dtmf */ + + s = gst_structure_new ("dtmf-event", + "type", G_TYPE_INT, 1, "number", G_TYPE_INT, 3, + "method", G_TYPE_INT, 1, "volume", G_TYPE_INT, 8, + "start", G_TYPE_BOOLEAN, TRUE, NULL); + fail_unless (gst_pad_push_event (sink, + gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, + gst_structure_copy (s)))); + check_no_dtmf_event_message (bus); + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (testclock), NULL); + fail_unless (buffers == NULL); + id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (testclock)); + fail_unless (gst_clock_id_get_time (id) == 0); + gst_clock_id_unref (id); + gst_structure_set_name (s, "dtmf-event-processed"); + check_message_structure (s); + + s = gst_structure_new ("dtmf-event", + "type", G_TYPE_INT, 1, "method", G_TYPE_INT, 1, + "start", G_TYPE_BOOLEAN, FALSE, NULL); + fail_unless (gst_pad_push_event (sink, + gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, + gst_structure_copy (s)))); + + check_rtp_buffer (0, 20 * GST_MSECOND, TRUE, FALSE, 666, 999, 8, 3, 20); + + for (timestamp = 20; timestamp < MIN_PULSE_DURATION + 20; timestamp += 20) { + gst_test_clock_advance_time (GST_TEST_CLOCK (testclock), + 20 * GST_MSECOND + 1); + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (testclock), NULL); + fail_unless (buffers == NULL); + id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (testclock)); + fail_unless (gst_clock_id_get_time (id) == timestamp * GST_MSECOND); + gst_clock_id_unref (id); + + if (timestamp < MIN_PULSE_DURATION) { + check_rtp_buffer (timestamp * GST_MSECOND, 20 * GST_MSECOND, FALSE, + FALSE, 666, 999, 8, 3, timestamp + 20); + check_no_dtmf_event_message (bus); + } else { + gst_structure_set_name (s, "dtmf-event-processed"); + check_message_structure (s); + check_rtp_buffer (timestamp * GST_MSECOND, + (20 + MIN_INTER_DIGIT_INTERVAL) * GST_MSECOND, FALSE, TRUE, 666, + 999, 8, 3, timestamp + 20); + } + + fail_unless (buffers == NULL); + } + + + fail_unless (gst_test_clock_peek_id_count (GST_TEST_CLOCK (testclock)) == 0); + + /* caps check */ + + expected_caps = gst_caps_new_simple ("application/x-rtp", + "encoding-name", G_TYPE_STRING, "TELEPHONE-EVENT", + "media", G_TYPE_STRING, "audio", + "clock-rate", G_TYPE_INT, 1000, "payload", G_TYPE_INT, 99, + "seqnum-base", G_TYPE_UINT, 333, + "clock-base", G_TYPE_UINT, 666, + "ssrc", G_TYPE_UINT, 999, "ptime", G_TYPE_UINT, 20, NULL); + caps = gst_pad_get_current_caps (sink); + fail_unless (gst_caps_can_intersect (caps, expected_caps)); + gst_caps_unref (caps); + gst_caps_unref (expected_caps); + + gst_element_set_state (dtmfsrc, GST_STATE_NULL); + + check_no_dtmf_event_message (bus); +} + +GST_END_TEST; + +static GstStaticPadTemplate audio_dtmfsrc_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = (string) \"" GST_AUDIO_NE (S16) "\", " + "rate = (int) 8003, " "channels = (int) 1") + ); +static void +setup_dtmfsrc (void) +{ + testclock = gst_test_clock_new (); + bus = gst_bus_new (); + + method = 2; + dtmfsrc = gst_check_setup_element ("dtmfsrc"); + sink = gst_check_setup_sink_pad (dtmfsrc, &audio_dtmfsrc_sink_template); + gst_element_set_bus (dtmfsrc, bus); + fail_unless (gst_element_set_clock (dtmfsrc, testclock)); + + gst_pad_set_active (sink, TRUE); + fail_unless (gst_element_set_state (dtmfsrc, GST_STATE_PLAYING) == + GST_STATE_CHANGE_SUCCESS); +} + + +GST_START_TEST (test_dtmfsrc_min_duration) +{ + GstStructure *s; + GstClockID id; + GstClockTime timestamp = 0; + GstCaps *expected_caps, *caps; + guint interval; + + g_object_get (dtmfsrc, "interval", &interval, NULL); + fail_unless (interval == 50); + + /* Minimum duration dtmf */ + gst_test_clock_set_time (GST_TEST_CLOCK (testclock), 0); + + s = gst_structure_new ("dtmf-event", + "type", G_TYPE_INT, 1, "number", G_TYPE_INT, 3, + "method", G_TYPE_INT, 2, "volume", G_TYPE_INT, 8, + "start", G_TYPE_BOOLEAN, TRUE, NULL); + fail_unless (gst_pad_push_event (sink, + gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, + gst_structure_copy (s)))); + + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (testclock), NULL); + id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (testclock)); + fail_unless (gst_clock_id_get_time (id) == 0); + gst_clock_id_unref (id); + + gst_structure_set_name (s, "dtmf-event-processed"); + check_message_structure (s); + + s = gst_structure_new ("dtmf-event", + "type", G_TYPE_INT, 1, "method", G_TYPE_INT, 2, + "start", G_TYPE_BOOLEAN, FALSE, NULL); + fail_unless (gst_pad_push_event (sink, + gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, + gst_structure_copy (s)))); + + for (timestamp = interval * GST_MSECOND; + timestamp < (MIN_PULSE_DURATION + MIN_INTER_DIGIT_INTERVAL) * + GST_MSECOND; timestamp += GST_MSECOND * interval) { + gst_test_clock_wait_for_next_pending_id (GST_TEST_CLOCK (testclock), NULL); + gst_test_clock_advance_time (GST_TEST_CLOCK (testclock), + interval * GST_MSECOND); + + id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (testclock)); + fail_unless (gst_clock_id_get_time (id) == timestamp); + gst_clock_id_unref (id); + } + + gst_structure_set_name (s, "dtmf-event-processed"); + check_message_structure (s); + + check_buffers_duration ((MIN_PULSE_DURATION + MIN_INTER_DIGIT_INTERVAL) * + GST_MSECOND); + + fail_unless (gst_test_clock_peek_id_count (GST_TEST_CLOCK (testclock)) == 0); + + /* caps check */ + + expected_caps = gst_caps_new_simple ("audio/x-raw", + "format", G_TYPE_STRING, GST_AUDIO_NE (S16), + "rate", G_TYPE_INT, 8003, "channels", G_TYPE_INT, 1, NULL); + caps = gst_pad_get_current_caps (sink); + fail_unless (gst_caps_can_intersect (caps, expected_caps)); + gst_caps_unref (caps); + gst_caps_unref (expected_caps); + + gst_element_set_state (dtmfsrc, GST_STATE_NULL); + + check_no_dtmf_event_message (bus); +} + +GST_END_TEST; + +static Suite * +dtmf_suite (void) +{ + Suite *s = suite_create ("dtmf"); + TCase *tc; + + tc = tcase_create ("rtpdtmfdepay"); + tcase_add_test (tc, test_rtpdtmfdepay); + suite_add_tcase (s, tc); + + tc = tcase_create ("rtpdtmfsrc"); + tcase_add_checked_fixture (tc, setup_rtpdtmfsrc, teardown_dtmfsrc); + tcase_add_test (tc, test_dtmfsrc_invalid_events); + tcase_add_test (tc, test_rtpdtmfsrc_min_duration); + suite_add_tcase (s, tc); + + tc = tcase_create ("dtmfsrc"); + tcase_add_checked_fixture (tc, setup_dtmfsrc, teardown_dtmfsrc); + tcase_add_test (tc, test_dtmfsrc_invalid_events); + tcase_add_test (tc, test_dtmfsrc_min_duration); + suite_add_tcase (s, tc); + + return s; +} + + +GST_CHECK_MAIN (dtmf);