diff --git a/gst/rtp/gstrtpvorbispay.c b/gst/rtp/gstrtpvorbispay.c index 1be1c49d59..b7ff2ccf2b 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 */ @@ -550,24 +593,128 @@ invalid_channels: } } +static GstFlowReturn +gst_rtp_vorbis_pay_payload_buffer (GstRtpVorbisPay * rtpvorbispay, guint8 VDT, + guint8 * data, guint size, GstClockTime timestamp, GstClockTime duration, + guint not_in_length) +{ + GstFlowReturn ret = GST_FLOW_OK; + guint newsize; + guint packet_len; + GstClockTime newduration; + gboolean flush; + guint plen; + guint8 *ppos, *payload; + gboolean fragmented; + GstRTPBuffer rtp = { NULL }; + + /* size increases with packet length and 2 bytes size eader. */ + newduration = rtpvorbispay->payload_duration; + if (duration != GST_CLOCK_TIME_NONE) + newduration += duration; + + newsize = rtpvorbispay->payload_pos + 2 + size; + 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 (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) + ret = gst_rtp_vorbis_pay_flush_packet (rtpvorbispay); + + /* create new packet if we must */ + if (!rtpvorbispay->packet) { + gst_rtp_vorbis_pay_init_packet (rtpvorbispay, VDT, timestamp); + } + + gst_rtp_buffer_map (rtpvorbispay->packet, GST_MAP_WRITE, &rtp); + payload = gst_rtp_buffer_get_payload (&rtp); + ppos = payload + rtpvorbispay->payload_pos; + fragmented = FALSE; + + /* put buffer in packet, it either fits completely or needs to be fragmented + * over multiple RTP packets. */ + 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 - 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; + + rtpvorbispay->payload_pos += plen + 2; + rtpvorbispay->payload_left -= plen + 2; + + if (fragmented) { + if (size == 0) + /* last fragment, set F to 0x3. */ + rtpvorbispay->payload_F = 0x3; + else + /* fragment continues, set F to 0x2. */ + rtpvorbispay->payload_F = 0x2; + } else { + if (size > 0) { + /* fragmented packet starts, set F to 0x1, mark ourselves as + * fragmented. */ + rtpvorbispay->payload_F = 0x1; + fragmented = TRUE; + } + } + if (fragmented) { + gst_rtp_buffer_unmap (&rtp); + /* fragmented packets are always flushed and have ptks of 0 */ + rtpvorbispay->payload_pkts = 0; + ret = gst_rtp_vorbis_pay_flush_packet (rtpvorbispay); + + if (size > 0) { + /* start new packet and get pointers. VDT stays the same. */ + gst_rtp_vorbis_pay_init_packet (rtpvorbispay, + rtpvorbispay->payload_VDT, timestamp); + gst_rtp_buffer_map (rtpvorbispay->packet, GST_MAP_WRITE, &rtp); + payload = gst_rtp_buffer_get_payload (&rtp); + ppos = payload + rtpvorbispay->payload_pos; + } + } else { + /* unfragmented packet, update stats for next packet, size == 0 and we + * exit the while loop */ + rtpvorbispay->payload_pkts++; + 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; - guint newsize; GstMapInfo map; gsize size; guint8 *data; - guint packet_len; - GstClockTime duration, newduration, timestamp; - gboolean flush; + GstClockTime duration, timestamp; guint8 VDT; - guint plen; - guint8 *ppos, *payload; - gboolean fragmented; - GstRTPBuffer rtp = { NULL }; rtpvorbispay = GST_RTP_VORBIS_PAY (basepayload); @@ -616,94 +763,54 @@ gst_rtp_vorbis_pay_handle_buffer (GstRTPBasePayload * basepayload, goto header_error; } - /* size increases with packet length and 2 bytes size eader. */ - newduration = rtpvorbispay->payload_duration; - if (duration != GST_CLOCK_TIME_NONE) - newduration += duration; + /* there is a config request, see if we need to insert it */ + if (rtpvorbispay->config_interval > 0 && rtpvorbispay->config_data) { + gboolean send_config = FALSE; - newsize = rtpvorbispay->payload_pos + 2 + size; - packet_len = gst_rtp_buffer_calc_packet_len (newsize, 0, 0); + if (rtpvorbispay->last_config != -1) { + guint64 diff; - /* check buffer filled against length and max latency */ - flush = gst_rtp_base_payload_is_filled (basepayload, 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); + GST_LOG_OBJECT (rtpvorbispay, + "now %" GST_TIME_FORMAT ", last config %" GST_TIME_FORMAT, + GST_TIME_ARGS (timestamp), GST_TIME_ARGS (rtpvorbispay->last_config)); - /* create new packet if we must */ - if (!rtpvorbispay->packet) { - gst_rtp_vorbis_pay_init_packet (rtpvorbispay, VDT, timestamp); - } - - gst_rtp_buffer_map (rtpvorbispay->packet, GST_MAP_WRITE, &rtp); - payload = gst_rtp_buffer_get_payload (&rtp); - 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) { - 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); - - size -= plen; - data += plen; - - rtpvorbispay->payload_pos += plen + 2; - rtpvorbispay->payload_left -= plen + 2; - - if (fragmented) { - if (size == 0) - /* last fragment, set F to 0x3. */ - rtpvorbispay->payload_F = 0x3; - else - /* fragment continues, set F to 0x2. */ - rtpvorbispay->payload_F = 0x2; - } else { - if (size > 0) { - /* fragmented packet starts, set F to 0x1, mark ourselves as - * fragmented. */ - rtpvorbispay->payload_F = 0x1; - fragmented = TRUE; + /* 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 (fragmented) { - gst_rtp_buffer_unmap (&rtp); - /* fragmented packets are always flushed and have ptks of 0 */ - rtpvorbispay->payload_pkts = 0; - ret = gst_rtp_vorbis_pay_flush_packet (rtpvorbispay); - if (size > 0) { - /* start new packet and get pointers. VDT stays the same. */ - gst_rtp_vorbis_pay_init_packet (rtpvorbispay, - rtpvorbispay->payload_VDT, timestamp); - gst_rtp_buffer_map (rtpvorbispay->packet, GST_MAP_WRITE, &rtp); - payload = gst_rtp_buffer_get_payload (&rtp); - ppos = payload + rtpvorbispay->payload_pos; + 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; } - } else { - /* unfragmented packet, update stats for next packet, size == 0 and we - * exit the while loop */ - rtpvorbispay->payload_pkts++; - if (duration != GST_CLOCK_TIME_NONE) - rtpvorbispay->payload_duration += duration; } } - if (rtp.buffer) - gst_rtp_buffer_unmap (&rtp); + 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 a75abd46a1..8f0974a0b3 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; };