gstreamer/gst/dtmf/gstrtpdtmfsrc.c
Olivier Crête 3a5d14993a rtpdtmfsrc: Put the inter digit interval at the end, not at the start
The reason is to let rtpdtmfmux drop buffers during the inter digit interval,
this way, there will be more silence around the DTMF tones so IVFs will have
a better chance recognizing them.
2011-08-24 12:24:17 -04:00

1084 lines
33 KiB
C

/* GStreamer RTP DTMF source
*
* gstrtpdtmfsrc.c:
*
* Copyright (C) <2007> Nokia Corporation.
* Contact: Zeeshan Ali <zeeshan.ali@nokia.com>
* Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
* 2000,2005 Wim Taymans <wim@fluendo.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/**
* SECTION:element-rtpdtmfsrc
* @see_also: dtmfsrc, rtpdtmfdepay, rtpdtmfmux
*
* The RTPDTMFSrc element generates RTP DTMF (RFC 2833) event packets on request
* from application. The application communicates the beginning and end of a
* DTMF event using custom upstream gstreamer events. To report a DTMF event, an
* application must send an event of type GST_EVENT_CUSTOM_UPSTREAM, having a
* structure of name "dtmf-event" with fields set according to the following
* table:
*
* <informaltable>
* <tgroup cols='4'>
* <colspec colname='Name' />
* <colspec colname='Type' />
* <colspec colname='Possible values' />
* <colspec colname='Purpose' />
* <thead>
* <row>
* <entry>Name</entry>
* <entry>GType</entry>
* <entry>Possible values</entry>
* <entry>Purpose</entry>
* </row>
* </thead>
* <tbody>
* <row>
* <entry>type</entry>
* <entry>G_TYPE_INT</entry>
* <entry>0-1</entry>
* <entry>The application uses this field to specify which of the two methods
* specified in RFC 2833 to use. The value should be 0 for tones and 1 for
* named events. Tones are specified by their frequencies and events are specied
* by their number. This element can only take events as input. Do not confuse
* with "method" which specified the output.
* </entry>
* </row>
* <row>
* <entry>number</entry>
* <entry>G_TYPE_INT</entry>
* <entry>0-16</entry>
* <entry>The event number.</entry>
* </row>
* <row>
* <entry>volume</entry>
* <entry>G_TYPE_INT</entry>
* <entry>0-36</entry>
* <entry>This field describes the power level of the tone, expressed in dBm0
* after dropping the sign. Power levels range from 0 to -63 dBm0. The range of
* valid DTMF is from 0 to -36 dBm0. Can be omitted if start is set to FALSE.
* </entry>
* </row>
* <row>
* <entry>start</entry>
* <entry>G_TYPE_BOOLEAN</entry>
* <entry>True or False</entry>
* <entry>Whether the event is starting or ending.</entry>
* </row>
* <row>
* <entry>method</entry>
* <entry>G_TYPE_INT</entry>
* <entry>1</entry>
* <entry>The method used for sending event, this element will react if this
* field is absent or 1.
* </entry>
* </row>
* </tbody>
* </tgroup>
* </informaltable>
*
* For example, the following code informs the pipeline (and in turn, the
* RTPDTMFSrc element inside the pipeline) about the start of an RTP DTMF named
* event '1' of volume -25 dBm0:
*
* <programlisting>
* structure = gst_structure_new ("dtmf-event",
* "type", G_TYPE_INT, 1,
* "number", G_TYPE_INT, 1,
* "volume", G_TYPE_INT, 25,
* "start", G_TYPE_BOOLEAN, TRUE, NULL);
*
* event = gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, structure);
* gst_element_send_event (pipeline, event);
* </programlisting>
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include "gstrtpdtmfsrc.h"
#define GST_RTP_DTMF_TYPE_EVENT 1
#define DEFAULT_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 ], "
"ssrc = (int) [ 0, MAX ], "
"encoding-name = (string) \"TELEPHONE-EVENT\"")
/* "events = (string) \"0-15\" */
);
GST_BOILERPLATE (GstRTPDTMFSrc, gst_rtp_dtmf_src, GstBaseSrc,
GST_TYPE_BASE_SRC);
static void gst_rtp_dtmf_src_finalize (GObject * object);
static void gst_rtp_dtmf_src_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_rtp_dtmf_src_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static gboolean gst_rtp_dtmf_src_handle_event (GstBaseSrc * basesrc,
GstEvent * event);
static GstStateChangeReturn gst_rtp_dtmf_src_change_state (GstElement * element,
GstStateChange transition);
static void gst_rtp_dtmf_src_add_start_event (GstRTPDTMFSrc * dtmfsrc,
gint event_number, gint event_volume);
static void gst_rtp_dtmf_src_add_stop_event (GstRTPDTMFSrc * dtmfsrc);
static gboolean gst_rtp_dtmf_src_unlock (GstBaseSrc * src);
static gboolean gst_rtp_dtmf_src_unlock_stop (GstBaseSrc * src);
static GstFlowReturn gst_rtp_dtmf_src_create (GstBaseSrc * basesrc,
guint64 offset, guint length, GstBuffer ** buffer);
static gboolean gst_rtp_dtmf_src_negotiate (GstBaseSrc * basesrc);
static void
gst_rtp_dtmf_src_base_init (gpointer g_class)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
GST_DEBUG_CATEGORY_INIT (gst_rtp_dtmf_src_debug,
"rtpdtmfsrc", 0, "rtpdtmfsrc element");
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&gst_rtp_dtmf_src_template));
gst_element_class_set_details_simple (element_class,
"RTP DTMF packet generator", "Source/Network",
"Generates RTP DTMF packets", "Zeeshan Ali <zeeshan.ali@nokia.com>");
}
static void
gst_rtp_dtmf_src_class_init (GstRTPDTMFSrcClass * klass)
{
GObjectClass *gobject_class;
GstBaseSrcClass *gstbasesrc_class;
GstElementClass *gstelement_class;
gobject_class = G_OBJECT_CLASS (klass);
gstbasesrc_class = GST_BASE_SRC_CLASS (klass);
gstelement_class = GST_ELEMENT_CLASS (klass);
parent_class = g_type_class_peek_parent (klass);
gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_rtp_dtmf_src_finalize);
gobject_class->set_property =
GST_DEBUG_FUNCPTR (gst_rtp_dtmf_src_set_property);
gobject_class->get_property =
GST_DEBUG_FUNCPTR (gst_rtp_dtmf_src_get_property);
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TIMESTAMP,
g_param_spec_uint ("timestamp", "Timestamp",
"The RTP timestamp of the last processed packet",
0, G_MAXUINT, 0, G_PARAM_READABLE | G_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, GstRTPDTMFSrcClass * g_class)
{
gst_base_src_set_format (GST_BASE_SRC (object), GST_FORMAT_TIME);
gst_base_src_set_live (GST_BASE_SRC (object), TRUE);
object->ssrc = DEFAULT_SSRC;
object->seqnum_offset = DEFAULT_SEQNUM_OFFSET;
object->ts_offset = DEFAULT_TIMESTAMP_OFFSET;
object->pt = DEFAULT_PT;
object->clock_rate = DEFAULT_CLOCK_RATE;
object->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 (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;
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;
}
}
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;
GST_OBJECT_UNLOCK (dtmfsrc);
if (start) {
gint event_number;
gint event_volume;
if (!gst_structure_get_int (event_structure, "number", &event_number) ||
!gst_structure_get_int (event_structure, "volume", &event_volume))
goto failure;
GST_DEBUG_OBJECT (dtmfsrc, "Received start event %d with volume %d",
event_number, event_volume);
gst_rtp_dtmf_src_add_start_event (dtmfsrc, event_number, event_volume);
}
else {
GST_DEBUG_OBJECT (dtmfsrc, "Received stop event");
gst_rtp_dtmf_src_add_stop_event (dtmfsrc);
}
return TRUE;
failure:
return FALSE;
}
static gboolean
gst_rtp_dtmf_src_handle_custom_upstream (GstRTPDTMFSrc * dtmfsrc,
GstEvent * event)
{
gboolean result = FALSE;
gchar *struct_str;
const GstStructure *structure;
GstState state;
GstStateChangeReturn ret;
ret = gst_element_get_state (GST_ELEMENT (dtmfsrc), &state, NULL, 0);
if (ret != GST_STATE_CHANGE_SUCCESS || state != GST_STATE_PLAYING) {
GST_DEBUG_OBJECT (dtmfsrc, "Received event while not in PLAYING state");
goto ret;
}
GST_DEBUG_OBJECT (dtmfsrc, "Received event is of our interest");
structure = gst_event_get_structure (event);
struct_str = gst_structure_to_string (structure);
GST_DEBUG_OBJECT (dtmfsrc, "Event has structure %s", struct_str);
g_free (struct_str);
if (structure && gst_structure_has_name (structure, "dtmf-event"))
result = gst_rtp_dtmf_src_handle_dtmf_event (dtmfsrc, structure);
ret:
return result;
}
static gboolean
gst_rtp_dtmf_src_handle_event (GstBaseSrc * basesrc, GstEvent * event)
{
GstRTPDTMFSrc *dtmfsrc;
gboolean result = FALSE;
dtmfsrc = GST_RTP_DTMF_SRC (basesrc);
GST_DEBUG_OBJECT (dtmfsrc, "Received an event on the src pad");
if (GST_EVENT_TYPE (event) == GST_EVENT_CUSTOM_UPSTREAM) {
result = gst_rtp_dtmf_src_handle_custom_upstream (dtmfsrc, event);
}
return result;
}
static void
gst_rtp_dtmf_src_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstRTPDTMFSrc *dtmfsrc;
dtmfsrc = GST_RTP_DTMF_SRC (object);
switch (prop_id) {
case PROP_TIMESTAMP_OFFSET:
dtmfsrc->ts_offset = g_value_get_int (value);
break;
case PROP_SEQNUM_OFFSET:
dtmfsrc->seqnum_offset = g_value_get_int (value);
break;
case PROP_CLOCK_RATE:
dtmfsrc->clock_rate = g_value_get_uint (value);
dtmfsrc->dirty = TRUE;
break;
case PROP_SSRC:
dtmfsrc->ssrc = g_value_get_uint (value);
break;
case PROP_PT:
dtmfsrc->pt = g_value_get_uint (value);
dtmfsrc->dirty = TRUE;
break;
case PROP_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->timestamp = 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));
dtmfsrc->start_timestamp = dtmfsrc->timestamp;
gst_object_unref (clock);
}
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);
event->payload->duration = dtmfsrc->ptime * dtmfsrc->clock_rate / 1000;
g_async_queue_push (dtmfsrc->event_queue, event);
}
static void
gst_rtp_dtmf_src_add_stop_event (GstRTPDTMFSrc * dtmfsrc)
{
GstRTPDTMFSrcEvent *event = g_slice_new0 (GstRTPDTMFSrcEvent);
event->event_type = RTP_DTMF_EVENT_TYPE_STOP;
g_async_queue_push (dtmfsrc->event_queue, event);
}
static void
gst_rtp_dtmf_prepare_rtp_headers (GstRTPDTMFSrc * dtmfsrc, GstBuffer * buf)
{
gst_rtp_buffer_set_ssrc (buf, dtmfsrc->current_ssrc);
gst_rtp_buffer_set_payload_type (buf, dtmfsrc->pt);
/* Only the very first packet gets a marker */
if (dtmfsrc->first_packet) {
gst_rtp_buffer_set_marker (buf, TRUE);
} else if (dtmfsrc->last_packet) {
dtmfsrc->payload->e = 1;
}
dtmfsrc->seqnum++;
gst_rtp_buffer_set_seq (buf, dtmfsrc->seqnum);
/* timestamp of RTP header */
gst_rtp_buffer_set_timestamp (buf, dtmfsrc->rtp_timestamp);
}
static void
gst_rtp_dtmf_prepare_buffer_data (GstRTPDTMFSrc * dtmfsrc, GstBuffer * buf)
{
GstRTPDTMFPayload *payload;
gst_rtp_dtmf_prepare_rtp_headers (dtmfsrc, buf);
/* timestamp and duration of GstBuffer */
/* Redundant buffer have no duration ... */
if (dtmfsrc->redundancy_count > 1)
GST_BUFFER_DURATION (buf) = 0;
else
GST_BUFFER_DURATION (buf) = dtmfsrc->ptime * GST_MSECOND;
GST_BUFFER_TIMESTAMP (buf) = dtmfsrc->timestamp;
payload = (GstRTPDTMFPayload *) gst_rtp_buffer_get_payload (buf);
/* copy payload and convert to network-byte order */
g_memmove (payload, dtmfsrc->payload, sizeof (GstRTPDTMFPayload));
payload->duration = g_htons (payload->duration);
/* duration of DTMF payloadfor the NEXT packet */
/* not updated for redundant packets */
if (dtmfsrc->redundancy_count == 0)
dtmfsrc->payload->duration += dtmfsrc->ptime * dtmfsrc->clock_rate / 1000;
if (dtmfsrc->redundancy_count == 0 && 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;
}
if (GST_CLOCK_TIME_IS_VALID (dtmfsrc->timestamp))
dtmfsrc->timestamp += GST_BUFFER_DURATION (buf);
}
static GstBuffer *
gst_rtp_dtmf_src_create_next_rtp_packet (GstRTPDTMFSrc * dtmfsrc)
{
GstBuffer *buf = NULL;
/* create buffer to hold the payload */
buf = gst_rtp_buffer_new_allocate (sizeof (GstRTPDTMFPayload), 0, 0);
gst_rtp_dtmf_prepare_buffer_data (dtmfsrc, buf);
/* Set caps on the buffer before pushing it */
gst_buffer_set_caps (buf, GST_PAD_CAPS (GST_BASE_SRC_PAD (dtmfsrc)));
return buf;
}
static GstFlowReturn
gst_rtp_dtmf_src_create (GstBaseSrc * basesrc, guint64 offset,
guint length, GstBuffer ** buffer)
{
GstRTPDTMFSrcEvent *event;
GstRTPDTMFSrc *dtmfsrc;
GstClock *clock;
GstClockID *clockid;
GstClockReturn clockret;
dtmfsrc = GST_RTP_DTMF_SRC (basesrc);
do {
if (dtmfsrc->payload == NULL) {
GST_DEBUG_OBJECT (dtmfsrc, "popping");
event = g_async_queue_pop (dtmfsrc->event_queue);
GST_DEBUG_OBJECT (dtmfsrc, "popped %d", event->event_type);
switch (event->event_type) {
case RTP_DTMF_EVENT_TYPE_STOP:
GST_WARNING_OBJECT (dtmfsrc,
"Received a DTMF stop event when already stopped");
break;
case RTP_DTMF_EVENT_TYPE_START:
dtmfsrc->first_packet = TRUE;
dtmfsrc->last_packet = FALSE;
/* Set the redundancy on the first packet */
dtmfsrc->redundancy_count = dtmfsrc->packet_redundancy;
if (!gst_rtp_dtmf_prepare_timestamps (dtmfsrc))
goto no_clock;
dtmfsrc->payload = event->payload;
event->payload = NULL;
break;
case RTP_DTMF_EVENT_TYPE_PAUSE_TASK:
/*
* We're pushing it back because it has to stay in there until
* the task is really paused (and the queue will then be flushed
*/
GST_OBJECT_LOCK (dtmfsrc);
if (dtmfsrc->paused) {
g_async_queue_push (dtmfsrc->event_queue, event);
goto paused_locked;
}
GST_OBJECT_UNLOCK (dtmfsrc);
break;
}
gst_rtp_dtmf_src_event_free (event);
} else if (!dtmfsrc->first_packet && !dtmfsrc->last_packet &&
(dtmfsrc->timestamp - dtmfsrc->start_timestamp) / GST_MSECOND >=
MIN_PULSE_DURATION) {
GST_DEBUG_OBJECT (dtmfsrc, "try popping");
event = g_async_queue_try_pop (dtmfsrc->event_queue);
if (event != NULL) {
GST_DEBUG_OBJECT (dtmfsrc, "try popped %d", event->event_type);
switch (event->event_type) {
case RTP_DTMF_EVENT_TYPE_START:
GST_WARNING_OBJECT (dtmfsrc,
"Received two consecutive DTMF start events");
break;
case RTP_DTMF_EVENT_TYPE_STOP:
dtmfsrc->first_packet = FALSE;
dtmfsrc->last_packet = TRUE;
/* Set the redundancy on the last packet */
dtmfsrc->redundancy_count = dtmfsrc->packet_redundancy;
break;
case RTP_DTMF_EVENT_TYPE_PAUSE_TASK:
/*
* We're pushing it back because it has to stay in there until
* the task is really paused (and the queue will then be flushed)
*/
GST_DEBUG_OBJECT (dtmfsrc, "pushing pause_task...");
GST_OBJECT_LOCK (dtmfsrc);
if (dtmfsrc->paused) {
g_async_queue_push (dtmfsrc->event_queue, event);
goto paused_locked;
}
GST_OBJECT_UNLOCK (dtmfsrc);
break;
}
gst_rtp_dtmf_src_event_free (event);
}
}
} while (dtmfsrc->payload == NULL);
GST_DEBUG_OBJECT (dtmfsrc, "Processed events, now lets wait on the clock");
clock = gst_element_get_clock (GST_ELEMENT (basesrc));
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);
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_WRONG_STATE;
}
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_get_caps (GST_BASE_SRC_PAD (basesrc));
if (peercaps == NULL) {
/* no peer caps, just add the other properties */
gst_caps_set_simple (srccaps,
"payload", G_TYPE_INT, dtmfsrc->pt,
"ssrc", G_TYPE_UINT, dtmfsrc->current_ssrc,
"clock-base", G_TYPE_UINT, dtmfsrc->ts_base,
"clock-rate", G_TYPE_INT, dtmfsrc->clock_rate,
"seqnum-base", G_TYPE_UINT, dtmfsrc->seqnum_base, NULL);
GST_DEBUG_OBJECT (dtmfsrc, "no peer caps: %" GST_PTR_FORMAT, srccaps);
} else {
GstCaps *temp;
GstStructure *s;
const GValue *value;
gint pt;
gint clock_rate;
/* peer provides caps we can use to fixate, intersect. This always returns a
* writable caps. */
temp = gst_caps_intersect (srccaps, peercaps);
gst_caps_unref (srccaps);
gst_caps_unref (peercaps);
if (!temp) {
GST_DEBUG_OBJECT (dtmfsrc, "Could not get intersection with peer caps");
return FALSE;
}
if (gst_caps_is_empty (temp)) {
GST_DEBUG_OBJECT (dtmfsrc, "Intersection with peer caps is empty");
gst_caps_unref (temp);
return FALSE;
}
/* now fixate, start by taking the first caps */
gst_caps_truncate (temp);
srccaps = temp;
/* get first structure */
s = gst_caps_get_structure (srccaps, 0);
if (gst_structure_get_int (s, "payload", &pt)) {
/* use peer pt */
dtmfsrc->pt = pt;
GST_LOG_OBJECT (dtmfsrc, "using peer pt %d", pt);
} else {
if (gst_structure_has_field (s, "payload")) {
/* can only fixate if there is a field */
gst_structure_fixate_field_nearest_int (s, "payload", dtmfsrc->pt);
gst_structure_get_int (s, "payload", &pt);
GST_LOG_OBJECT (dtmfsrc, "using peer pt %d", pt);
} else {
/* no pt field, use the internal pt */
pt = dtmfsrc->pt;
gst_structure_set (s, "payload", G_TYPE_INT, pt, NULL);
GST_LOG_OBJECT (dtmfsrc, "using internal pt %d", pt);
}
}
if (gst_structure_get_int (s, "clock-rate", &clock_rate)) {
dtmfsrc->clock_rate = clock_rate;
GST_LOG_OBJECT (dtmfsrc, "using clock-rate from caps %d",
dtmfsrc->clock_rate);
} else {
GST_LOG_OBJECT (dtmfsrc, "using existing clock-rate %d",
dtmfsrc->clock_rate);
}
gst_structure_set (s, "clock-rate", G_TYPE_INT, dtmfsrc->clock_rate, NULL);
if (gst_structure_has_field_typed (s, "ssrc", G_TYPE_UINT)) {
value = gst_structure_get_value (s, "ssrc");
dtmfsrc->current_ssrc = g_value_get_uint (value);
GST_LOG_OBJECT (dtmfsrc, "using peer ssrc %08x", dtmfsrc->current_ssrc);
} else {
/* FIXME, fixate_nearest_uint would be even better */
gst_structure_set (s, "ssrc", G_TYPE_UINT, dtmfsrc->current_ssrc, NULL);
GST_LOG_OBJECT (dtmfsrc, "using internal ssrc %08x",
dtmfsrc->current_ssrc);
}
if (gst_structure_has_field_typed (s, "clock-base", G_TYPE_UINT)) {
value = gst_structure_get_value (s, "clock-base");
dtmfsrc->ts_base = g_value_get_uint (value);
GST_LOG_OBJECT (dtmfsrc, "using peer clock-base %u", dtmfsrc->ts_base);
} else {
/* FIXME, fixate_nearest_uint would be even better */
gst_structure_set (s, "clock-base", G_TYPE_UINT, dtmfsrc->ts_base, NULL);
GST_LOG_OBJECT (dtmfsrc, "using internal clock-base %u",
dtmfsrc->ts_base);
}
if (gst_structure_has_field_typed (s, "seqnum-base", G_TYPE_UINT)) {
value = gst_structure_get_value (s, "seqnum-base");
dtmfsrc->seqnum_base = g_value_get_uint (value);
GST_LOG_OBJECT (dtmfsrc, "using peer seqnum-base %u",
dtmfsrc->seqnum_base);
} else {
/* FIXME, fixate_nearest_uint would be even better */
gst_structure_set (s, "seqnum-base", G_TYPE_UINT, dtmfsrc->seqnum_base,
NULL);
GST_LOG_OBJECT (dtmfsrc, "using internal seqnum-base %u",
dtmfsrc->seqnum_base);
}
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;
}
static GstStateChangeReturn
gst_rtp_dtmf_src_change_state (GstElement * element, GstStateChange transition)
{
GstRTPDTMFSrc *dtmfsrc;
GstStateChangeReturn result;
gboolean no_preroll = FALSE;
GstRTPDTMFSrcEvent *event = NULL;
dtmfsrc = GST_RTP_DTMF_SRC (element);
switch (transition) {
case GST_STATE_CHANGE_READY_TO_PAUSED:
gst_rtp_dtmf_src_ready_to_paused (dtmfsrc);
/* Flushing the event queue */
while ((event = g_async_queue_try_pop (dtmfsrc->event_queue)) != NULL)
gst_rtp_dtmf_src_event_free (event);
no_preroll = TRUE;
break;
default:
break;
}
if ((result =
GST_ELEMENT_CLASS (parent_class)->change_state (element,
transition)) == GST_STATE_CHANGE_FAILURE)
goto failure;
switch (transition) {
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
no_preroll = TRUE;
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
/* Flushing the event queue */
while ((event = g_async_queue_try_pop (dtmfsrc->event_queue)) != NULL)
gst_rtp_dtmf_src_event_free (event);
/* Indicate that we don't do PRE_ROLL */
break;
default:
break;
}
if (no_preroll && result == GST_STATE_CHANGE_SUCCESS)
result = GST_STATE_CHANGE_NO_PREROLL;
return result;
/* ERRORS */
failure:
{
GST_ERROR_OBJECT (dtmfsrc, "parent failed state change");
return result;
}
}
static gboolean
gst_rtp_dtmf_src_unlock (GstBaseSrc * src)
{
GstRTPDTMFSrc *dtmfsrc = GST_RTP_DTMF_SRC (src);
GstRTPDTMFSrcEvent *event = NULL;
GST_DEBUG_OBJECT (dtmfsrc, "Called unlock");
GST_OBJECT_LOCK (dtmfsrc);
dtmfsrc->paused = TRUE;
if (dtmfsrc->clockid) {
gst_clock_id_unschedule (dtmfsrc->clockid);
}
GST_OBJECT_UNLOCK (dtmfsrc);
GST_DEBUG_OBJECT (dtmfsrc, "Pushing the PAUSE_TASK event on unlock request");
event = g_slice_new0 (GstRTPDTMFSrcEvent);
event->event_type = RTP_DTMF_EVENT_TYPE_PAUSE_TASK;
g_async_queue_push (dtmfsrc->event_queue, event);
return TRUE;
}
static gboolean
gst_rtp_dtmf_src_unlock_stop (GstBaseSrc * src)
{
GstRTPDTMFSrc *dtmfsrc = GST_RTP_DTMF_SRC (src);
GST_DEBUG_OBJECT (dtmfsrc, "Unlock stopped");
GST_OBJECT_LOCK (dtmfsrc);
dtmfsrc->paused = FALSE;
GST_OBJECT_UNLOCK (dtmfsrc);
return TRUE;
}
gboolean
gst_rtp_dtmf_src_plugin_init (GstPlugin * plugin)
{
return gst_element_register (plugin, "rtpdtmfsrc",
GST_RANK_NONE, GST_TYPE_RTP_DTMF_SRC);
}