From c469434ea861d94da57ed6b402a3a382a58dd1a5 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 28 Jun 2013 15:21:56 +0200 Subject: [PATCH] vorbispay: add support for config-interval Align code with the theora payloader and add support for the config-interval to periodically send out the config headers. --- gst/rtp/gstrtpvorbispay.c | 273 +++++++++++++++++++++++++++++++++++----------- gst/rtp/gstrtpvorbispay.h | 7 ++ 2 files changed, 214 insertions(+), 66 deletions(-) diff --git a/gst/rtp/gstrtpvorbispay.c b/gst/rtp/gstrtpvorbispay.c index 1be1c49..b7ff2cc 100644 --- a/gst/rtp/gstrtpvorbispay.c +++ b/gst/rtp/gstrtpvorbispay.c @@ -58,6 +58,14 @@ GST_STATIC_PAD_TEMPLATE ("sink", GST_STATIC_CAPS ("audio/x-vorbis") ); +#define DEFAULT_CONFIG_INTERVAL 0 + +enum +{ + PROP_0, + PROP_CONFIG_INTERVAL +}; + #define gst_rtp_vorbis_pay_parent_class parent_class G_DEFINE_TYPE (GstRtpVorbisPay, gst_rtp_vorbis_pay, GST_TYPE_RTP_BASE_PAYLOAD); @@ -75,12 +83,19 @@ static gboolean gst_rtp_vorbis_pay_parse_id (GstRTPBasePayload * basepayload, static gboolean gst_rtp_vorbis_pay_finish_headers (GstRTPBasePayload * basepayload); +static void gst_rtp_vorbis_pay_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_rtp_vorbis_pay_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + static void gst_rtp_vorbis_pay_class_init (GstRtpVorbisPayClass * klass) { + GObjectClass *gobject_class; GstElementClass *gstelement_class; GstRTPBasePayloadClass *gstrtpbasepayload_class; + gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; gstrtpbasepayload_class = (GstRTPBasePayloadClass *) klass; @@ -90,6 +105,9 @@ gst_rtp_vorbis_pay_class_init (GstRtpVorbisPayClass * klass) gstrtpbasepayload_class->handle_buffer = gst_rtp_vorbis_pay_handle_buffer; gstrtpbasepayload_class->sink_event = gst_rtp_vorbis_pay_sink_event; + gobject_class->set_property = gst_rtp_vorbis_pay_set_property; + gobject_class->get_property = gst_rtp_vorbis_pay_get_property; + gst_element_class_add_pad_template (gstelement_class, gst_static_pad_template_get (&gst_rtp_vorbis_pay_src_template)); gst_element_class_add_pad_template (gstelement_class, @@ -103,11 +121,20 @@ gst_rtp_vorbis_pay_class_init (GstRtpVorbisPayClass * klass) GST_DEBUG_CATEGORY_INIT (rtpvorbispay_debug, "rtpvorbispay", 0, "Vorbis RTP Payloader"); + + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_CONFIG_INTERVAL, + g_param_spec_uint ("config-interval", "Config Send Interval", + "Send Config Insertion Interval in seconds (configuration headers " + "will be multiplexed in the data stream when detected.) (0 = disabled)", + 0, 3600, DEFAULT_CONFIG_INTERVAL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS) + ); } static void gst_rtp_vorbis_pay_init (GstRtpVorbisPay * rtpvorbispay) { + rtpvorbispay->last_config = GST_CLOCK_TIME_NONE; } static void @@ -126,6 +153,11 @@ gst_rtp_vorbis_pay_cleanup (GstRtpVorbisPay * rtpvorbispay) rtpvorbispay->headers = NULL; gst_rtp_vorbis_pay_clear_packet (rtpvorbispay); + + if (rtpvorbispay->config_data) + g_free (rtpvorbispay->config_data); + rtpvorbispay->config_data = NULL; + rtpvorbispay->last_config = GST_CLOCK_TIME_NONE; } static gboolean @@ -247,6 +279,7 @@ gst_rtp_vorbis_pay_init_packet (GstRtpVorbisPay * rtpvorbispay, guint8 VDT, gst_rtp_buffer_new_allocate_len (GST_RTP_BASE_PAYLOAD_MTU (rtpvorbispay), 0, 0); gst_rtp_vorbis_pay_reset_packet (rtpvorbispay, VDT); + GST_BUFFER_TIMESTAMP (rtpvorbispay->packet) = timestamp; } @@ -308,7 +341,7 @@ gst_rtp_vorbis_pay_finish_headers (GstRTPBasePayload * basepayload) { GstRtpVorbisPay *rtpvorbispay = GST_RTP_VORBIS_PAY (basepayload); GList *walk; - guint length, size, n_headers, configlen; + guint length, size, n_headers, configlen, extralen; gchar *cstr, *configuration; guint8 *data, *config; guint32 ident; @@ -367,6 +400,7 @@ gst_rtp_vorbis_pay_finish_headers (GstRTPBasePayload * basepayload) length = 0; n_headers = 0; ident = fnv1_hash_32_new (); + extralen = 1; for (walk = rtpvorbispay->headers; walk; walk = g_list_next (walk)) { GstBuffer *buf = GST_BUFFER_CAST (walk->data); GstMapInfo map; @@ -381,6 +415,7 @@ gst_rtp_vorbis_pay_finish_headers (GstRTPBasePayload * basepayload) if (g_list_next (walk)) { do { size++; + extralen++; bsize >>= 7; } while (bsize); } @@ -463,6 +498,14 @@ gst_rtp_vorbis_pay_finish_headers (GstRTPBasePayload * basepayload) /* serialize to base64 */ configuration = g_base64_encode (config, configlen); + + /* store for later re-sending */ + rtpvorbispay->config_size = configlen - 4 - 3 - 2; + rtpvorbispay->config_data = g_malloc (rtpvorbispay->config_size); + rtpvorbispay->config_extra_len = extralen; + memcpy (rtpvorbispay->config_data, config + 4 + 3 + 2, + rtpvorbispay->config_size); + g_free (config); /* configure payloader settings */ @@ -551,71 +594,20 @@ invalid_channels: } static GstFlowReturn -gst_rtp_vorbis_pay_handle_buffer (GstRTPBasePayload * basepayload, - GstBuffer * buffer) +gst_rtp_vorbis_pay_payload_buffer (GstRtpVorbisPay * rtpvorbispay, guint8 VDT, + guint8 * data, guint size, GstClockTime timestamp, GstClockTime duration, + guint not_in_length) { - GstRtpVorbisPay *rtpvorbispay; - GstFlowReturn ret; + GstFlowReturn ret = GST_FLOW_OK; guint newsize; - GstMapInfo map; - gsize size; - guint8 *data; guint packet_len; - GstClockTime duration, newduration, timestamp; + GstClockTime newduration; gboolean flush; - guint8 VDT; guint plen; guint8 *ppos, *payload; gboolean fragmented; GstRTPBuffer rtp = { NULL }; - rtpvorbispay = GST_RTP_VORBIS_PAY (basepayload); - - gst_buffer_map (buffer, &map, GST_MAP_READ); - data = map.data; - size = map.size; - duration = GST_BUFFER_DURATION (buffer); - timestamp = GST_BUFFER_TIMESTAMP (buffer); - - GST_LOG_OBJECT (rtpvorbispay, "size %" G_GSIZE_FORMAT - ", duration %" GST_TIME_FORMAT, size, GST_TIME_ARGS (duration)); - - if (G_UNLIKELY (size < 1 || size > 0xffff)) - goto wrong_size; - - /* find packet type */ - if (data[0] & 1) { - /* header */ - if (data[0] == 1) { - /* identification, we need to parse this in order to get the clock rate. */ - if (G_UNLIKELY (!gst_rtp_vorbis_pay_parse_id (basepayload, data, size))) - goto parse_id_failed; - VDT = 1; - } else if (data[0] == 3) { - /* comment */ - VDT = 2; - } else if (data[0] == 5) { - /* setup */ - VDT = 1; - } else - goto unknown_header; - } else - /* data */ - VDT = 0; - - /* we need to collect the headers and construct a config string from them */ - if (VDT != 0) { - GST_DEBUG_OBJECT (rtpvorbispay, "collecting header"); - /* append header to the list of headers */ - gst_buffer_unmap (buffer, &map); - rtpvorbispay->headers = g_list_append (rtpvorbispay->headers, buffer); - ret = GST_FLOW_OK; - goto done; - } else if (rtpvorbispay->headers) { - if (!gst_rtp_vorbis_pay_finish_headers (basepayload)) - goto header_error; - } - /* size increases with packet length and 2 bytes size eader. */ newduration = rtpvorbispay->payload_duration; if (duration != GST_CLOCK_TIME_NONE) @@ -625,14 +617,15 @@ gst_rtp_vorbis_pay_handle_buffer (GstRTPBasePayload * basepayload, packet_len = gst_rtp_buffer_calc_packet_len (newsize, 0, 0); /* check buffer filled against length and max latency */ - flush = gst_rtp_base_payload_is_filled (basepayload, packet_len, newduration); + flush = gst_rtp_base_payload_is_filled (GST_RTP_BASE_PAYLOAD (rtpvorbispay), + packet_len, newduration); /* we can store up to 15 vorbis packets in one RTP packet. */ flush |= (rtpvorbispay->payload_pkts == 15); /* flush if we have a new VDT */ if (rtpvorbispay->packet) flush |= (rtpvorbispay->payload_VDT != VDT); if (flush) - gst_rtp_vorbis_pay_flush_packet (rtpvorbispay); + ret = gst_rtp_vorbis_pay_flush_packet (rtpvorbispay); /* create new packet if we must */ if (!rtpvorbispay->packet) { @@ -644,19 +637,22 @@ gst_rtp_vorbis_pay_handle_buffer (GstRTPBasePayload * basepayload, ppos = payload + rtpvorbispay->payload_pos; fragmented = FALSE; - ret = GST_FLOW_OK; - /* put buffer in packet, it either fits completely or needs to be fragmented * over multiple RTP packets. */ - while (size) { + do { plen = MIN (rtpvorbispay->payload_left - 2, size); GST_LOG_OBJECT (rtpvorbispay, "append %u bytes", plen); /* data is copied in the payload with a 2 byte length header */ - ppos[0] = (plen >> 8) & 0xff; - ppos[1] = (plen & 0xff); - memcpy (&ppos[2], data, plen); + ppos[0] = ((plen - not_in_length) >> 8) & 0xff; + ppos[1] = ((plen - not_in_length) & 0xff); + if (plen) + memcpy (&ppos[2], data, plen); + + /* only first (only) configuration cuts length field */ + /* NOTE: spec (if any) is not clear on this ... */ + not_in_length = 0; size -= plen; data += plen; @@ -700,11 +696,122 @@ gst_rtp_vorbis_pay_handle_buffer (GstRTPBasePayload * basepayload, if (duration != GST_CLOCK_TIME_NONE) rtpvorbispay->payload_duration += duration; } - } + } while (size); if (rtp.buffer) gst_rtp_buffer_unmap (&rtp); + return ret; +} + +static GstFlowReturn +gst_rtp_vorbis_pay_handle_buffer (GstRTPBasePayload * basepayload, + GstBuffer * buffer) +{ + GstRtpVorbisPay *rtpvorbispay; + GstFlowReturn ret; + GstMapInfo map; + gsize size; + guint8 *data; + GstClockTime duration, timestamp; + guint8 VDT; + + rtpvorbispay = GST_RTP_VORBIS_PAY (basepayload); + + gst_buffer_map (buffer, &map, GST_MAP_READ); + data = map.data; + size = map.size; + duration = GST_BUFFER_DURATION (buffer); + timestamp = GST_BUFFER_TIMESTAMP (buffer); + + GST_LOG_OBJECT (rtpvorbispay, "size %" G_GSIZE_FORMAT + ", duration %" GST_TIME_FORMAT, size, GST_TIME_ARGS (duration)); + + if (G_UNLIKELY (size < 1 || size > 0xffff)) + goto wrong_size; + + /* find packet type */ + if (data[0] & 1) { + /* header */ + if (data[0] == 1) { + /* identification, we need to parse this in order to get the clock rate. */ + if (G_UNLIKELY (!gst_rtp_vorbis_pay_parse_id (basepayload, data, size))) + goto parse_id_failed; + VDT = 1; + } else if (data[0] == 3) { + /* comment */ + VDT = 2; + } else if (data[0] == 5) { + /* setup */ + VDT = 1; + } else + goto unknown_header; + } else + /* data */ + VDT = 0; + + /* we need to collect the headers and construct a config string from them */ + if (VDT != 0) { + GST_DEBUG_OBJECT (rtpvorbispay, "collecting header"); + /* append header to the list of headers */ + gst_buffer_unmap (buffer, &map); + rtpvorbispay->headers = g_list_append (rtpvorbispay->headers, buffer); + ret = GST_FLOW_OK; + goto done; + } else if (rtpvorbispay->headers) { + if (!gst_rtp_vorbis_pay_finish_headers (basepayload)) + goto header_error; + } + + /* there is a config request, see if we need to insert it */ + if (rtpvorbispay->config_interval > 0 && rtpvorbispay->config_data) { + gboolean send_config = FALSE; + + if (rtpvorbispay->last_config != -1) { + guint64 diff; + + GST_LOG_OBJECT (rtpvorbispay, + "now %" GST_TIME_FORMAT ", last config %" GST_TIME_FORMAT, + GST_TIME_ARGS (timestamp), GST_TIME_ARGS (rtpvorbispay->last_config)); + + /* calculate diff between last config in milliseconds */ + if (timestamp > rtpvorbispay->last_config) { + diff = timestamp - rtpvorbispay->last_config; + } else { + diff = 0; + } + + GST_DEBUG_OBJECT (rtpvorbispay, + "interval since last config %" GST_TIME_FORMAT, GST_TIME_ARGS (diff)); + + /* bigger than interval, queue config */ + /* FIXME should convert timestamps to running time */ + if (GST_TIME_AS_SECONDS (diff) >= rtpvorbispay->config_interval) { + GST_DEBUG_OBJECT (rtpvorbispay, "time to send config"); + send_config = TRUE; + } + } else { + /* no known previous config time, send now */ + GST_DEBUG_OBJECT (rtpvorbispay, "no previous config time, send now"); + send_config = TRUE; + } + + if (send_config) { + /* we need to send config now first */ + /* different TDT type forces flush */ + gst_rtp_vorbis_pay_payload_buffer (rtpvorbispay, 1, + rtpvorbispay->config_data, rtpvorbispay->config_size, + timestamp, GST_CLOCK_TIME_NONE, rtpvorbispay->config_extra_len); + + if (timestamp != -1) { + rtpvorbispay->last_config = timestamp; + } + } + } + + ret = gst_rtp_vorbis_pay_payload_buffer (rtpvorbispay, VDT, data, size, + timestamp, duration, 0); + gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); @@ -787,6 +894,40 @@ gst_rtp_vorbis_pay_change_state (GstElement * element, return ret; } +static void +gst_rtp_vorbis_pay_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstRtpVorbisPay *rtpvorbispay; + + rtpvorbispay = GST_RTP_VORBIS_PAY (object); + + switch (prop_id) { + case PROP_CONFIG_INTERVAL: + rtpvorbispay->config_interval = g_value_get_uint (value); + break; + default: + break; + } +} + +static void +gst_rtp_vorbis_pay_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstRtpVorbisPay *rtpvorbispay; + + rtpvorbispay = GST_RTP_VORBIS_PAY (object); + + switch (prop_id) { + case PROP_CONFIG_INTERVAL: + g_value_set_uint (value, rtpvorbispay->config_interval); + break; + default: + break; + } +} + gboolean gst_rtp_vorbis_pay_plugin_init (GstPlugin * plugin) { diff --git a/gst/rtp/gstrtpvorbispay.h b/gst/rtp/gstrtpvorbispay.h index a75abd4..8f0974a 100644 --- a/gst/rtp/gstrtpvorbispay.h +++ b/gst/rtp/gstrtpvorbispay.h @@ -59,6 +59,13 @@ struct _GstRtpVorbisPay GstClockTime payload_timestamp; GstClockTime payload_duration; + /* config (re-sending) */ + guint8 *config_data; + guint config_size; + guint config_extra_len; + guint config_interval; + GstClockTime last_config; + gint rate; gint channels; }; -- 2.7.4