From af6fc8437781c9ce3bb623ae5fa330b1dce868c3 Mon Sep 17 00:00:00 2001 From: Mark Nauwelaerts Date: Fri, 7 May 2010 15:42:23 +0200 Subject: [PATCH] rtptheorapay: add config-interval parameter to re-insert config in stream Add a new config-interval property to instruct the payloader to insert configuration headers at periodic intervals in the stream (when a keyframe is countered). --- gst/rtp/gstrtptheorapay.c | 144 ++++++++++++++++++++++++++++++++++++-- gst/rtp/gstrtptheorapay.h | 7 ++ 2 files changed, 144 insertions(+), 7 deletions(-) diff --git a/gst/rtp/gstrtptheorapay.c b/gst/rtp/gstrtptheorapay.c index 94a1e2187b..0bdee39b35 100644 --- a/gst/rtp/gstrtptheorapay.c +++ b/gst/rtp/gstrtptheorapay.c @@ -68,6 +68,14 @@ GST_STATIC_PAD_TEMPLATE ("sink", GST_STATIC_CAPS ("video/x-theora") ); +#define DEFAULT_CONFIG_INTERVAL 0 + +enum +{ + PROP_0, + PROP_CONFIG_INTERVAL +}; + GST_BOILERPLATE (GstRtpTheoraPay, gst_rtp_theora_pay, GstBaseRTPPayload, GST_TYPE_BASE_RTP_PAYLOAD); @@ -78,6 +86,11 @@ static GstStateChangeReturn gst_rtp_theora_pay_change_state (GstElement * static GstFlowReturn gst_rtp_theora_pay_handle_buffer (GstBaseRTPPayload * pad, GstBuffer * buffer); +static void gst_rtp_theora_pay_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_rtp_theora_pay_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + static void gst_rtp_theora_pay_base_init (gpointer klass) { @@ -97,9 +110,11 @@ gst_rtp_theora_pay_base_init (gpointer klass) static void gst_rtp_theora_pay_class_init (GstRtpTheoraPayClass * klass) { + GObjectClass *gobject_class; GstElementClass *gstelement_class; GstBaseRTPPayloadClass *gstbasertppayload_class; + gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; gstbasertppayload_class = (GstBaseRTPPayloadClass *) klass; @@ -108,15 +123,26 @@ gst_rtp_theora_pay_class_init (GstRtpTheoraPayClass * klass) gstbasertppayload_class->set_caps = gst_rtp_theora_pay_setcaps; gstbasertppayload_class->handle_buffer = gst_rtp_theora_pay_handle_buffer; + gobject_class->set_property = gst_rtp_theora_pay_set_property; + gobject_class->get_property = gst_rtp_theora_pay_get_property; + GST_DEBUG_CATEGORY_INIT (rtptheorapay_debug, "rtptheorapay", 0, "Theora 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_theora_pay_init (GstRtpTheoraPay * rtptheorapay, GstRtpTheoraPayClass * klass) { - /* needed because of GST_BOILERPLATE */ + rtptheorapay->last_config = GST_CLOCK_TIME_NONE; } static void @@ -129,6 +155,11 @@ gst_rtp_theora_pay_cleanup (GstRtpTheoraPay * rtptheorapay) if (rtptheorapay->packet) gst_buffer_unref (rtptheorapay->packet); rtptheorapay->packet = NULL; + + if (rtptheorapay->config_data) + g_free (rtptheorapay->config_data); + rtptheorapay->config_data = NULL; + rtptheorapay->last_config = GST_CLOCK_TIME_NONE; } static gboolean @@ -230,7 +261,7 @@ gst_rtp_theora_pay_finish_headers (GstBaseRTPPayload * basepayload) { GstRtpTheoraPay *rtptheorapay = GST_RTP_THEORA_PAY (basepayload); GList *walk; - guint length, size, n_headers, configlen; + guint length, size, n_headers, configlen, extralen; gchar *wstr, *hstr, *configuration; guint8 *data, *config; guint32 ident; @@ -291,6 +322,7 @@ gst_rtp_theora_pay_finish_headers (GstBaseRTPPayload * basepayload) length = 0; n_headers = 0; ident = fnv1_hash_32_new (); + extralen = 1; for (walk = rtptheorapay->headers; walk; walk = g_list_next (walk)) { GstBuffer *buf = GST_BUFFER_CAST (walk->data); @@ -305,6 +337,7 @@ gst_rtp_theora_pay_finish_headers (GstBaseRTPPayload * basepayload) if (g_list_next (walk)) { do { size++; + extralen++; bsize >>= 7; } while (bsize); } @@ -383,6 +416,14 @@ gst_rtp_theora_pay_finish_headers (GstBaseRTPPayload * basepayload) /* serialize to base64 */ configuration = g_base64_encode (config, configlen); + + /* store for later re-sending */ + rtptheorapay->config_size = configlen - 4 - 3 - 2; + rtptheorapay->config_data = g_malloc (rtptheorapay->config_size); + rtptheorapay->config_extra_len = extralen; + memcpy (rtptheorapay->config_data, config + 4 + 3 + 2, + rtptheorapay->config_size); + g_free (config); /* configure payloader settings */ @@ -471,7 +512,8 @@ invalid_version: static GstFlowReturn gst_rtp_theora_pay_payload_buffer (GstRtpTheoraPay * rtptheorapay, guint8 TDT, - guint8 * data, guint size, GstClockTime timestamp, GstClockTime duration) + guint8 * data, guint size, GstClockTime timestamp, GstClockTime duration, + guint not_in_length) { GstFlowReturn ret = GST_FLOW_OK; guint newsize; @@ -518,10 +560,14 @@ gst_rtp_theora_pay_payload_buffer (GstRtpTheoraPay * rtptheorapay, guint8 TDT, GST_DEBUG_OBJECT (rtptheorapay, "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); + ppos[0] = ((plen - not_in_length) >> 8) & 0xff; + ppos[1] = ((plen - not_in_length) & 0xff); 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; @@ -577,6 +623,7 @@ gst_rtp_theora_pay_handle_buffer (GstBaseRTPPayload * basepayload, guint8 *data; GstClockTime duration, timestamp; guint8 TDT; + gboolean keyframe = FALSE; rtptheorapay = GST_RTP_THEORA_PAY (basepayload); @@ -608,9 +655,11 @@ gst_rtp_theora_pay_handle_buffer (GstBaseRTPPayload * basepayload, TDT = 1; } else goto unknown_header; - } else + } else { /* data */ TDT = 0; + keyframe = ((data[0] & 0x40) == 0); + } if (rtptheorapay->need_headers) { /* we need to collect the headers and construct a config string from them */ @@ -627,8 +676,55 @@ gst_rtp_theora_pay_handle_buffer (GstBaseRTPPayload * basepayload, } } + /* there is a config request, see if we need to insert it */ + if (keyframe && (rtptheorapay->config_interval > 0) && + rtptheorapay->config_data) { + gboolean send_config = FALSE; + + if (rtptheorapay->last_config != -1) { + guint64 diff; + + GST_LOG_OBJECT (rtptheorapay, + "now %" GST_TIME_FORMAT ", last VOP-I %" GST_TIME_FORMAT, + GST_TIME_ARGS (timestamp), GST_TIME_ARGS (rtptheorapay->last_config)); + + /* calculate diff between last config in milliseconds */ + if (timestamp > rtptheorapay->last_config) { + diff = timestamp - rtptheorapay->last_config; + } else { + diff = 0; + } + + GST_DEBUG_OBJECT (rtptheorapay, + "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) >= rtptheorapay->config_interval) { + GST_DEBUG_OBJECT (rtptheorapay, "time to send config"); + send_config = TRUE; + } + } else { + /* no known previous config time, send now */ + GST_DEBUG_OBJECT (rtptheorapay, "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_theora_pay_payload_buffer (rtptheorapay, 1, + rtptheorapay->config_data, rtptheorapay->config_size, + timestamp, GST_CLOCK_TIME_NONE, rtptheorapay->config_extra_len); + + if (timestamp != -1) { + rtptheorapay->last_config = timestamp; + } + } + } + ret = gst_rtp_theora_pay_payload_buffer (rtptheorapay, TDT, data, size, - timestamp, duration); + timestamp, duration, 0); gst_buffer_unref (buffer); done: @@ -690,6 +786,40 @@ gst_rtp_theora_pay_change_state (GstElement * element, return ret; } +static void +gst_rtp_theora_pay_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstRtpTheoraPay *rtptheorapay; + + rtptheorapay = GST_RTP_THEORA_PAY (object); + + switch (prop_id) { + case PROP_CONFIG_INTERVAL: + rtptheorapay->config_interval = g_value_get_uint (value); + break; + default: + break; + } +} + +static void +gst_rtp_theora_pay_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstRtpTheoraPay *rtptheorapay; + + rtptheorapay = GST_RTP_THEORA_PAY (object); + + switch (prop_id) { + case PROP_CONFIG_INTERVAL: + g_value_set_uint (value, rtptheorapay->config_interval); + break; + default: + break; + } +} + gboolean gst_rtp_theora_pay_plugin_init (GstPlugin * plugin) { diff --git a/gst/rtp/gstrtptheorapay.h b/gst/rtp/gstrtptheorapay.h index d3db359eb6..198e37533f 100644 --- a/gst/rtp/gstrtptheorapay.h +++ b/gst/rtp/gstrtptheorapay.h @@ -59,6 +59,13 @@ struct _GstRtpTheoraPay 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 width; gint height; };