rtp/basepayload: implement support for rtp header extensions

New signals are added for managing the internal list of rtp header
extension implementations read by a specific depayloader instance.

If the 'extmap-$NUM' field is present in the src caps, then an
extension implementation will be requested but is not required to be able
to negotiate correctly.  An extension will be requested using the
'request-extension' signal if none could be found internally.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/merge_requests/748>
This commit is contained in:
Matthew Waters 2020-07-10 15:33:46 +10:00 committed by GStreamer Merge Bot
parent 092ea647bb
commit 7a53fbad68
2 changed files with 698 additions and 94 deletions

View file

@ -30,6 +30,7 @@
#include "gstrtpbasepayload.h"
#include "gstrtpmeta.h"
#include "gstrtphdrext.h"
GST_DEBUG_CATEGORY_STATIC (rtpbasepayload_debug);
#define GST_CAT_DEFAULT (rtpbasepayload_debug)
@ -49,7 +50,6 @@ struct _GstRTPBasePayloadPrivate
gboolean source_info;
GstBuffer *input_meta_buffer;
guint8 twcc_ext_id;
guint64 base_offset;
gint64 base_rtime;
@ -69,15 +69,23 @@ struct _GstRTPBasePayloadPrivate
GstCaps *subclass_srccaps;
GstCaps *sinkcaps;
/* array of GstRTPHeaderExtension's * */
GPtrArray *header_exts;
};
/* RTPBasePayload signals and args */
enum
{
/* FILL ME */
SIGNAL_0,
SIGNAL_REQUEST_EXTENSION,
SIGNAL_ADD_EXTENSION,
SIGNAL_CLEAR_EXTENSIONS,
LAST_SIGNAL
};
static guint gst_rtp_base_payload_signals[LAST_SIGNAL] = { 0 };
/* FIXME 0.11, a better default is the Ethernet MTU of
* 1500 - sizeof(headers) as pointed out by marcelm in IRC:
* So an Ethernet MTU of 1500, minus 60 for the max IP, minus 8 for UDP, gives
@ -96,9 +104,13 @@ enum
#define DEFAULT_RUNNING_TIME GST_CLOCK_TIME_NONE
#define DEFAULT_SOURCE_INFO FALSE
#define DEFAULT_ONVIF_NO_RATE_CONTROL FALSE
#define DEFAULT_TWCC_EXT_ID 0
#define DEFAULT_SCALE_RTPTIME TRUE
#define RTP_HEADER_EXT_ONE_BYTE_MAX_SIZE 16
#define RTP_HEADER_EXT_TWO_BYTE_MAX_SIZE 256
#define RTP_HEADER_EXT_ONE_BYTE_MAX_ID 14
#define RTP_HEADER_EXT_TWO_BYTE_MAX_ID 255
enum
{
PROP_0,
@ -116,7 +128,6 @@ enum
PROP_STATS,
PROP_SOURCE_INFO,
PROP_ONVIF_NO_RATE_CONTROL,
PROP_TWCC_EXT_ID,
PROP_SCALE_RTPTIME,
PROP_LAST
};
@ -154,6 +165,9 @@ static GstStateChangeReturn gst_rtp_base_payload_change_state (GstElement *
static gboolean gst_rtp_base_payload_negotiate (GstRTPBasePayload * payload);
static void gst_rtp_base_payload_add_extension (GstRTPBasePayload * payload,
GstRTPHeaderExtension * ext);
static void gst_rtp_base_payload_clear_extensions (GstRTPBasePayload * payload);
static GstElementClass *parent_class = NULL;
static gint private_offset = 0;
@ -348,32 +362,6 @@ gst_rtp_base_payload_class_init (GstRTPBasePayloadClass * klass)
DEFAULT_ONVIF_NO_RATE_CONTROL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstRTPBasePayload:twcc-ext-id:
*
* The RTP header-extension ID used for tagging buffers with Transport-Wide
* Congestion Control sequence-numbers.
*
* To use this across multiple bundled streams (transport wide), the
* GstRTPFunnel can mux TWCC sequence-numbers together.
*
* This is experimental and requires setting the
* 'GST_RTP_ENABLE_EXPERIMENTAL_TWCC_PROPERTY' environment variable as it is
* still a draft and not yet a standard. This property may also be removed
* in the future for 1.20.
*
* Since: 1.18
*/
if (enable_experimental_twcc) {
g_object_class_install_property (gobject_class, PROP_TWCC_EXT_ID,
g_param_spec_uint ("twcc-ext-id",
"Transport-wide Congestion Control Extension ID (experimental)",
"The RTP header-extension ID to use for tagging buffers with "
"Transport-wide Congestion Control sequencenumbers (0 = disable)",
0, 15, DEFAULT_TWCC_EXT_ID,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
/**
* GstRTPBasePayload:scale-rtptime:
*
@ -393,6 +381,54 @@ gst_rtp_base_payload_class_init (GstRTPBasePayloadClass * klass)
"Whether the RTP timestamp should be scaled with the rate (speed)",
DEFAULT_SCALE_RTPTIME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstRTPBasePayload::add-extension:
* @object: the #GstRTPBasePayload
* @ext: (transfer full): the #GstRTPHeaderExtension
*
* Add @ext as an extension for writing part of an RTP header extension onto
* outgoing RTP packets.
*
* Since: 1.20
*/
gst_rtp_base_payload_signals[SIGNAL_ADD_EXTENSION] =
g_signal_new_class_handler ("add-extension", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_CALLBACK (gst_rtp_base_payload_add_extension), NULL, NULL, NULL,
G_TYPE_NONE, 1, GST_TYPE_RTP_HEADER_EXTENSION);
/**
* GstRTPBasePayload::request-extension:
* @object: the #GstRTPBasePayload
* @ext_id: the extension id being requested
* @ext_uri: the extension URI being requested
*
* The returned @ext must be configured with the correct @ext_id and with the
* necessary attributes as required by the extension implementation.
*
* Returns: (transfer full): the #GstRTPHeaderExtension for @ext_id, or %NULL
*
* Since: 1.20
*/
gst_rtp_base_payload_signals[SIGNAL_REQUEST_EXTENSION] =
g_signal_new_class_handler ("request-extension",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, NULL, NULL, NULL, NULL,
GST_TYPE_RTP_HEADER_EXTENSION, 2, G_TYPE_UINT, G_TYPE_STRING);
/**
* GstRTPBasePayload::clear-extensions:
* @object: the #GstRTPBasePayload
*
* Clear all RTP header extensions used by this payloader.
*
* Since: 1.20
*/
gst_rtp_base_payload_signals[SIGNAL_ADD_EXTENSION] =
g_signal_new_class_handler ("clear-extensions", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_CALLBACK (gst_rtp_base_payload_clear_extensions), NULL, NULL, NULL,
G_TYPE_NONE, 0);
gstelement_class->change_state = gst_rtp_base_payload_change_state;
klass->get_caps = gst_rtp_base_payload_getcaps_default;
@ -463,6 +499,8 @@ gst_rtp_base_payload_init (GstRTPBasePayload * rtpbasepayload, gpointer g_class)
rtpbasepayload->priv->caps_max_ptime = DEFAULT_MAX_PTIME;
rtpbasepayload->priv->prop_max_ptime = DEFAULT_MAX_PTIME;
rtpbasepayload->priv->header_exts =
g_ptr_array_new_with_free_func ((GDestroyNotify) gst_object_unref);
}
static void
@ -480,6 +518,9 @@ gst_rtp_base_payload_finalize (GObject * object)
gst_caps_replace (&rtpbasepayload->priv->subclass_srccaps, NULL);
gst_caps_replace (&rtpbasepayload->priv->sinkcaps, NULL);
g_ptr_array_unref (rtpbasepayload->priv->header_exts);
rtpbasepayload->priv->header_exts = NULL;
G_OBJECT_CLASS (parent_class)->finalize (object);
}
@ -741,7 +782,8 @@ gst_rtp_base_payload_chain (GstPad * pad, GstObject * parent,
if (!rtpbasepayload->priv->negotiated)
goto not_negotiated;
if (rtpbasepayload->priv->source_info) {
if (rtpbasepayload->priv->source_info
|| rtpbasepayload->priv->header_exts->len > 0) {
/* Save a copy of meta (instead of taking an extra reference before
* handle_buffer) to make the meta available when allocating a output
* buffer. */
@ -893,12 +935,38 @@ gst_rtp_base_payload_set_outcaps (GstRTPBasePayload * payload,
return gst_rtp_base_payload_negotiate (payload);
}
static void
add_and_ref_item (GstRTPHeaderExtension * ext, GPtrArray * ret)
{
g_ptr_array_add (ret, gst_object_ref (ext));
}
static void
remove_item_from (GstRTPHeaderExtension * ext, GPtrArray * ret)
{
g_ptr_array_remove_fast (ret, ext);
}
static void
add_item_to (GstRTPHeaderExtension * ext, GPtrArray * ret)
{
g_ptr_array_add (ret, ext);
}
static void
add_header_ext_to_caps (GstRTPHeaderExtension * ext, GstCaps * caps)
{
if (!gst_rtp_header_extension_set_caps_from_attributes (ext, caps)) {
GST_WARNING ("Failed to set caps from rtp header extension");
}
}
static gboolean
gst_rtp_base_payload_negotiate (GstRTPBasePayload * payload)
{
GstCaps *templ, *peercaps, *srccaps;
GstStructure *s, *d;
gboolean res;
gboolean res = TRUE;
payload->priv->caps_max_ptime = DEFAULT_MAX_PTIME;
payload->ptime = 0;
@ -1183,17 +1251,136 @@ gst_rtp_base_payload_negotiate (GstRTPBasePayload * payload)
update_max_ptime (payload);
{
/* try to find header extension implementations for the list in the
* caps */
GstStructure *s = gst_caps_get_structure (srccaps, 0);
guint i, j, n_fields = gst_structure_n_fields (s);
GPtrArray *header_exts = g_ptr_array_new_with_free_func (gst_object_unref);
GPtrArray *to_add = g_ptr_array_new ();
GPtrArray *to_remove = g_ptr_array_new ();
if (enable_experimental_twcc && payload->priv->twcc_ext_id > 0) {
/* TODO: put this as a separate utility-function for RTP extensions */
gchar *name = g_strdup_printf ("extmap-%u", payload->priv->twcc_ext_id);
gst_caps_set_simple (srccaps, name, G_TYPE_STRING,
"http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01",
NULL);
g_free (name);
GST_OBJECT_LOCK (payload);
g_ptr_array_foreach (payload->priv->header_exts,
(GFunc) add_and_ref_item, header_exts);
GST_OBJECT_UNLOCK (payload);
for (i = 0; i < n_fields; i++) {
const gchar *field_name = gst_structure_nth_field_name (s, i);
if (g_str_has_prefix (field_name, "extmap-")) {
const GValue *val;
const gchar *uri = NULL;
gchar *nptr;
guint64 ext_id;
GstRTPHeaderExtension *ext = NULL;
errno = 0;
ext_id = g_ascii_strtoull (&field_name[strlen ("extmap-")], &nptr, 10);
if (errno != 0 || (ext_id == 0 && field_name == nptr)) {
GST_WARNING_OBJECT (payload, "could not parse id from %s",
field_name);
res = FALSE;
goto ext_out;
}
val = gst_structure_get_value (s, field_name);
if (G_VALUE_HOLDS_STRING (val)) {
uri = g_value_get_string (val);
} else if (GST_VALUE_HOLDS_ARRAY (val)) {
/* the uri is the second value in the array */
const GValue *str = gst_value_array_get_value (val, 1);
if (G_VALUE_HOLDS_STRING (str)) {
uri = g_value_get_string (str);
}
}
if (!uri) {
GST_WARNING_OBJECT (payload, "could not get extmap uri for "
"field %s", field_name);
res = FALSE;
goto ext_out;
}
/* try to find if this extension mapping already exists */
for (j = 0; j < header_exts->len; j++) {
ext = g_ptr_array_index (header_exts, j);
if (gst_rtp_header_extension_get_id (ext) == ext_id) {
if (g_strcmp0 (uri, gst_rtp_header_extension_get_uri (ext)) == 0) {
/* still matching, we're good, set attributes from caps in case
* the caps have been updated */
if (!gst_rtp_header_extension_set_attributes_from_caps (ext,
srccaps)) {
GST_WARNING_OBJECT (payload,
"Failed to configure rtp header " "extension %"
GST_PTR_FORMAT " attributes from caps %" GST_PTR_FORMAT,
ext, srccaps);
res = FALSE;
goto ext_out;
}
break;
} else {
GST_DEBUG_OBJECT (payload, "extension id %" G_GUINT64_FORMAT
"was replaced with a different extension uri "
"original:\'%s' vs \'%s\'", ext_id,
gst_rtp_header_extension_get_uri (ext), uri);
g_ptr_array_add (to_remove, ext);
ext = NULL;
break;
}
} else {
ext = NULL;
}
}
/* if no extension, attempt to request one */
if (!ext) {
GST_DEBUG_OBJECT (payload, "requesting extension for id %"
G_GUINT64_FORMAT " and uri %s", ext_id, uri);
g_signal_emit (payload,
gst_rtp_base_payload_signals[SIGNAL_REQUEST_EXTENSION], 0,
ext_id, uri, &ext);
GST_DEBUG_OBJECT (payload, "request returned extension %p \'%s\' "
"for id %" G_GUINT64_FORMAT " and uri %s", ext,
ext ? GST_OBJECT_NAME (ext) : "", ext_id, uri);
if (ext && gst_rtp_header_extension_get_id (ext) != ext_id) {
g_warning ("\'request-extension\' signal provided an rtp header "
"extension for uri \'%s\' that does not match the requested "
"extension id %" G_GUINT64_FORMAT, uri, ext_id);
gst_clear_object (&ext);
}
/* it is the signal handler's responsibility to set attributes if
* required */
/* We don't create an extension implementation by default and require
* the caller to set the appropriate extension if it's required */
if (ext) {
g_ptr_array_add (to_add, ext);
}
}
}
}
GST_OBJECT_LOCK (payload);
g_ptr_array_foreach (to_remove, (GFunc) remove_item_from,
payload->priv->header_exts);
g_ptr_array_foreach (to_add, (GFunc) add_item_to,
payload->priv->header_exts);
/* add extension information to srccaps */
g_ptr_array_foreach (payload->priv->header_exts,
(GFunc) add_header_ext_to_caps, srccaps);
GST_OBJECT_UNLOCK (payload);
ext_out:
g_ptr_array_unref (to_add);
g_ptr_array_unref (to_remove);
g_ptr_array_unref (header_exts);
}
res = gst_pad_set_caps (GST_RTP_BASE_PAYLOAD_SRCPAD (payload), srccaps);
GST_DEBUG_OBJECT (payload, "configuring caps %" GST_PTR_FORMAT, srccaps);
if (res)
res = gst_pad_set_caps (GST_RTP_BASE_PAYLOAD_SRCPAD (payload), srccaps);
gst_caps_unref (srccaps);
gst_caps_unref (templ);
@ -1240,7 +1427,6 @@ typedef struct
GstClockTime pts;
guint64 offset;
guint32 rtptime;
guint8 twcc_ext_id;
} HeaderData;
static gboolean
@ -1260,19 +1446,141 @@ find_timestamp (GstBuffer ** buffer, guint idx, gpointer user_data)
}
static void
_set_twcc_seq (GstRTPBuffer * rtp, guint16 seq, guint8 ext_id)
gst_rtp_base_payload_add_extension (GstRTPBasePayload * payload,
GstRTPHeaderExtension * ext)
{
guint16 data;
if (ext_id == 0 || ext_id > 14)
g_return_if_fail (GST_IS_RTP_HEADER_EXTENSION (ext));
g_return_if_fail (gst_rtp_header_extension_get_id (ext) > 0);
/* XXX: check for duplicate ids? */
GST_OBJECT_LOCK (payload);
g_ptr_array_add (payload->priv->header_exts, gst_object_ref (ext));
gst_pad_mark_reconfigure (GST_RTP_BASE_PAYLOAD_SRCPAD (payload));
GST_OBJECT_UNLOCK (payload);
}
static void
gst_rtp_base_payload_clear_extensions (GstRTPBasePayload * payload)
{
GST_OBJECT_LOCK (payload);
g_ptr_array_set_size (payload->priv->header_exts, 0);
GST_OBJECT_UNLOCK (payload);
}
typedef struct
{
GstRTPBasePayload *payload;
GstRTPHeaderExtensionFlags flags;
GstBuffer *output;
guint8 *data;
gsize allocated_size;
gsize written_size;
gsize hdr_unit_size;
gboolean abort;
} HeaderExt;
static void
determine_header_extension_flags_size (GstRTPHeaderExtension * ext,
gpointer user_data)
{
HeaderExt *hdr = user_data;
guint ext_id;
gsize max_size;
hdr->flags &= gst_rtp_header_extension_get_supported_flags (ext);
max_size =
gst_rtp_header_extension_get_max_size (ext,
hdr->payload->priv->input_meta_buffer);
if (max_size > RTP_HEADER_EXT_ONE_BYTE_MAX_SIZE)
hdr->flags &= ~GST_RTP_HEADER_EXTENSION_ONE_BYTE;
if (max_size > RTP_HEADER_EXT_TWO_BYTE_MAX_SIZE)
hdr->flags &= ~GST_RTP_HEADER_EXTENSION_TWO_BYTE;
ext_id = gst_rtp_header_extension_get_id (ext);
if (ext_id > RTP_HEADER_EXT_ONE_BYTE_MAX_ID)
hdr->flags &= ~GST_RTP_HEADER_EXTENSION_ONE_BYTE;
if (ext_id > RTP_HEADER_EXT_TWO_BYTE_MAX_ID)
hdr->flags &= ~GST_RTP_HEADER_EXTENSION_TWO_BYTE;
hdr->allocated_size += max_size;
}
static void
write_header_extension (GstRTPHeaderExtension * ext, gpointer user_data)
{
HeaderExt *hdr = user_data;
gsize remaining =
hdr->allocated_size - hdr->written_size - hdr->hdr_unit_size;
gsize offset = hdr->written_size + hdr->hdr_unit_size;
gsize written;
guint ext_id;
if (hdr->abort)
return;
GST_WRITE_UINT16_BE (&data, seq);
gst_rtp_buffer_add_extension_onebyte_header (rtp, ext_id, &data, 2);
written = gst_rtp_header_extension_write (ext,
hdr->payload->priv->input_meta_buffer, hdr->flags, hdr->output,
&hdr->data[offset], remaining);
if (written == 0) {
/* extension wrote no data */
return;
} else if (written < 0) {
GST_WARNING_OBJECT (hdr->payload, "%s failed to write extension data",
GST_OBJECT_NAME (ext));
goto error;
} else if (written > remaining) {
/* wrote too much! */
g_error ("Overflow detected writing rtp header extensions. One of the "
"instances likely did not report a large enough maximum size. "
"Memory corruption has occured. Aborting");
goto error;
}
ext_id = gst_rtp_header_extension_get_id (ext);
/* move to the beginning of the extension header */
offset -= hdr->hdr_unit_size;
/* write extension header */
if (hdr->flags & GST_RTP_HEADER_EXTENSION_ONE_BYTE) {
if (written > RTP_HEADER_EXT_ONE_BYTE_MAX_SIZE) {
g_critical ("Amount of data written by %s is larger than allowed with "
"a one byte header.", GST_OBJECT_NAME (ext));
goto error;
}
hdr->data[offset] = ((ext_id & 0x0F) << 4) | ((written - 1) & 0x0F);
} else if (hdr->flags & GST_RTP_HEADER_EXTENSION_TWO_BYTE) {
if (written > RTP_HEADER_EXT_TWO_BYTE_MAX_SIZE) {
g_critical ("Amount of data written by %s is larger than allowed with "
"a two byte header.", GST_OBJECT_NAME (ext));
goto error;
}
hdr->data[offset] = ext_id & 0xFF;
hdr->data[offset + 1] = written & 0xFF;
} else {
g_critical ("Don't know how to write extension data with flags 0x%x!",
hdr->flags);
goto error;
}
hdr->written_size += written + hdr->hdr_unit_size;
return;
error:
hdr->abort = TRUE;
return;
}
static gboolean
set_headers (GstBuffer ** buffer, guint idx, gpointer user_data)
{
HeaderData *data = user_data;
HeaderExt hdrext = { NULL, };
GstRTPBuffer rtp = { NULL, };
if (!gst_rtp_buffer_map (*buffer, GST_MAP_WRITE, &rtp))
@ -1282,8 +1590,56 @@ set_headers (GstBuffer ** buffer, guint idx, gpointer user_data)
gst_rtp_buffer_set_payload_type (&rtp, data->pt);
gst_rtp_buffer_set_seq (&rtp, data->seqnum);
gst_rtp_buffer_set_timestamp (&rtp, data->rtptime);
if (enable_experimental_twcc)
_set_twcc_seq (&rtp, data->seqnum, data->twcc_ext_id);
GST_OBJECT_LOCK (data->payload);
if (data->payload->priv->header_exts->len > 0) {
guint wordlen;
gsize extlen;
guint16 bit_pattern;
/* write header extensions */
hdrext.payload = data->payload;
hdrext.output = *buffer;
/* XXX: pre-calculate these flags and sizes? */
hdrext.flags =
GST_RTP_HEADER_EXTENSION_ONE_BYTE | GST_RTP_HEADER_EXTENSION_TWO_BYTE;
g_ptr_array_foreach (data->payload->priv->header_exts,
(GFunc) determine_header_extension_flags_size, &hdrext);
hdrext.hdr_unit_size = 0;
if (hdrext.flags & GST_RTP_HEADER_EXTENSION_ONE_BYTE) {
/* prefer the one byte header */
hdrext.hdr_unit_size = 1;
/* TODO: support mixed size writing modes, i.e. RFC8285 */
hdrext.flags &= ~GST_RTP_HEADER_EXTENSION_TWO_BYTE;
bit_pattern = 0xBEDE;
} else if (hdrext.flags & GST_RTP_HEADER_EXTENSION_TWO_BYTE) {
hdrext.hdr_unit_size = 2;
bit_pattern = 0x1000;
} else {
goto unsupported_flags;
}
extlen =
hdrext.hdr_unit_size * data->payload->priv->header_exts->len +
hdrext.allocated_size;
wordlen = extlen / 4 + (extlen % 4) ? 1 : 0;
/* XXX: do we need to add to any existing extension data instead of
* overwriting everything? */
gst_rtp_buffer_set_extension_data (&rtp, bit_pattern, wordlen);
gst_rtp_buffer_get_extension_data (&rtp, NULL, (gpointer) & hdrext.data,
&wordlen);
/* from 32-bit words to bytes */
hdrext.allocated_size = wordlen * 4;
g_ptr_array_foreach (data->payload->priv->header_exts,
(GFunc) write_header_extension, &hdrext);
wordlen = hdrext.written_size / 4 + (hdrext.written_size % 4) ? 1 : 0;
gst_rtp_buffer_set_extension_data (&rtp, bit_pattern, wordlen);
}
GST_OBJECT_UNLOCK (data->payload);
gst_rtp_buffer_unmap (&rtp);
/* increment the seqnum for each buffer */
@ -1296,6 +1652,14 @@ map_failed:
GST_ERROR ("failed to map buffer %p", *buffer);
return FALSE;
}
unsupported_flags:
{
GST_OBJECT_UNLOCK (data->payload);
gst_rtp_buffer_unmap (&rtp);
GST_ERROR ("Cannot add rtp header extensions with mixed header types");
return FALSE;
}
}
static gboolean
@ -1340,7 +1704,6 @@ gst_rtp_base_payload_prepare_push (GstRTPBasePayload * payload,
data.seqnum = payload->seqnum;
data.ssrc = payload->current_ssrc;
data.pt = payload->pt;
data.twcc_ext_id = priv->twcc_ext_id;
/* find the first buffer with a timestamp */
if (is_list) {
@ -1658,9 +2021,6 @@ gst_rtp_base_payload_set_property (GObject * object, guint prop_id,
case PROP_ONVIF_NO_RATE_CONTROL:
priv->onvif_no_rate_control = g_value_get_boolean (value);
break;
case PROP_TWCC_EXT_ID:
priv->twcc_ext_id = g_value_get_uint (value);
break;
case PROP_SCALE_RTPTIME:
priv->scale_rtptime = g_value_get_boolean (value);
break;
@ -1734,9 +2094,6 @@ gst_rtp_base_payload_get_property (GObject * object, guint prop_id,
case PROP_ONVIF_NO_RATE_CONTROL:
g_value_set_boolean (value, priv->onvif_no_rate_control);
break;
case PROP_TWCC_EXT_ID:
g_value_set_uint (value, priv->twcc_ext_id);
break;
case PROP_SCALE_RTPTIME:
g_value_set_boolean (value, priv->scale_rtptime);
break;

View file

@ -26,6 +26,8 @@
#include <gst/check/gstharness.h>
#include <gst/rtp/rtp.h>
#include "rtpdummyhdrextimpl.c"
#define DEFAULT_CLOCK_RATE (42)
#define BUFFER_BEFORE_LIST (10)
@ -300,6 +302,17 @@ validate_event (guint index, const gchar * name, const gchar * field, ...)
framerate = gst_structure_get_string (gst_caps_get_structure (caps, 0),
"a-framerate");
fail_unless_equals_string (framerate, expected);
} else if (!g_strcmp0 (field, "extmap-str")) {
guint ext_id = va_arg (var_args, guint);
const gchar *expected_ext_str = va_arg (var_args, const gchar *);
GstCaps *caps;
const gchar *ext_str;
gchar *ext_field = g_strdup_printf ("extmap-%u", ext_id);
gst_event_parse_caps (event, &caps);
ext_str = gst_structure_get_string (gst_caps_get_structure (caps, 0),
ext_field);
fail_unless_equals_string (ext_str, expected_ext_str);
g_free (ext_field);
} else {
fail ("test cannot validate unknown event field '%s'", field);
}
@ -322,8 +335,8 @@ validate_normal_start_events (guint index)
#define push_buffer(state, field, ...) \
push_buffer_full ((state), GST_FLOW_OK, (field), __VA_ARGS__)
#define push_buffer_fails(state, field, ...) \
push_buffer_full ((state), GST_FLOW_FLUSHING, (field), __VA_ARGS__)
#define push_buffer_fails(state, error, field, ...) \
push_buffer_full ((state), (error), (field), __VA_ARGS__)
static void
push_buffer_full (State * state, GstFlowReturn expected,
@ -1969,53 +1982,280 @@ GST_START_TEST (rtp_base_payload_segment_time)
GST_END_TEST;
#define TWCC_EXTMAP_STR "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01"
GST_START_TEST (rtp_base_payload_property_twcc_ext_id_test)
GST_START_TEST (rtp_base_payload_one_byte_hdr_ext)
{
GstHarness *h;
GstRtpDummyPay *pay;
GstBuffer *buf;
GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
guint8 ext_id = 10;
gpointer data;
guint size;
guint16 seqnum, twcc_seqnum;
GstCaps *caps, *expected_caps;
GstRTPHeaderExtension *ext;
State *state;
pay = rtp_dummy_pay_new ();
g_object_set (pay, "twcc-ext-id", ext_id, NULL);
state = create_payloader ("application/x-rtp", &sinktmpl, NULL);
ext = rtp_dummy_hdr_ext_new ();
GST_RTP_DUMMY_HDR_EXT (ext)->supported_flags =
GST_RTP_HEADER_EXTENSION_ONE_BYTE;
gst_rtp_header_extension_set_id (ext, 1);
h = gst_harness_new_with_element (GST_ELEMENT_CAST (pay), "sink", "src");
gst_harness_set_src_caps_str (h, "application/x-rtp");
g_signal_emit_by_name (state->element, "add-extension", ext);
/* verify the presence of the twcc-seqnum */
buf = gst_harness_push_and_pull (h, gst_buffer_new ());
gst_rtp_buffer_map (buf, GST_MAP_READWRITE, &rtp);
fail_unless (gst_rtp_buffer_get_extension_onebyte_header (&rtp, ext_id,
0, &data, &size));
fail_unless_equals_int (2, size);
twcc_seqnum = GST_READ_UINT16_BE (data);
seqnum = gst_rtp_buffer_get_seq (&rtp);
fail_unless_equals_int (twcc_seqnum, seqnum);
gst_rtp_buffer_unmap (&rtp);
gst_buffer_unref (buf);
set_state (state, GST_STATE_PLAYING);
/* verify the presence of the twcc in caps */
caps = gst_pad_get_current_caps (GST_PAD_PEER (h->sinkpad));
expected_caps = gst_caps_from_string ("application/x-rtp, "
"extmap-10=" TWCC_EXTMAP_STR "");
fail_unless (gst_caps_is_subset (caps, expected_caps));
gst_caps_unref (caps);
gst_caps_unref (expected_caps);
push_buffer (state, "pts", 0 * GST_SECOND, NULL);
g_object_unref (pay);
gst_harness_teardown (h);
set_state (state, GST_STATE_NULL);
validate_buffers_received (1);
validate_buffer (0, "pts", 0 * GST_SECOND, NULL);
validate_events_received (3);
validate_normal_start_events (0);
fail_unless_equals_int (GST_RTP_DUMMY_HDR_EXT (ext)->write_count, 1);
gst_object_unref (ext);
destroy_payloader (state);
}
GST_END_TEST;
GST_START_TEST (rtp_base_payload_two_byte_hdr_ext)
{
GstRTPHeaderExtension *ext;
State *state;
state = create_payloader ("application/x-rtp", &sinktmpl, NULL);
ext = rtp_dummy_hdr_ext_new ();
GST_RTP_DUMMY_HDR_EXT (ext)->supported_flags =
GST_RTP_HEADER_EXTENSION_TWO_BYTE;
gst_rtp_header_extension_set_id (ext, 1);
g_signal_emit_by_name (state->element, "add-extension", ext);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 0 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
validate_buffers_received (1);
validate_buffer (0, "pts", 0 * GST_SECOND, NULL);
validate_events_received (3);
validate_normal_start_events (0);
fail_unless_equals_int (GST_RTP_DUMMY_HDR_EXT (ext)->write_count, 1);
gst_object_unref (ext);
destroy_payloader (state);
}
GST_END_TEST;
GST_START_TEST (rtp_base_payload_clear_extensions)
{
GstRTPHeaderExtension *ext;
State *state;
state = create_payloader ("application/x-rtp", &sinktmpl, NULL);
ext = rtp_dummy_hdr_ext_new ();
gst_rtp_header_extension_set_id (ext, 1);
g_signal_emit_by_name (state->element, "add-extension", ext);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 0 * GST_SECOND, NULL);
g_signal_emit_by_name (state->element, "clear-extensions", ext);
push_buffer (state, "pts", 1 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
validate_buffers_received (2);
validate_buffer (0, "pts", 0 * GST_SECOND, NULL);
validate_buffer (1, "pts", 1 * GST_SECOND, NULL);
validate_events_received (3);
validate_normal_start_events (0);
fail_unless_equals_int (GST_RTP_DUMMY_HDR_EXT (ext)->write_count, 1);
gst_object_unref (ext);
destroy_payloader (state);
}
GST_END_TEST;
GST_START_TEST (rtp_base_payload_multiple_exts)
{
GstRTPHeaderExtension *ext1, *ext2;
State *state;
state = create_payloader ("application/x-rtp", &sinktmpl, NULL);
ext1 = rtp_dummy_hdr_ext_new ();
gst_rtp_header_extension_set_id (ext1, 1);
ext2 = rtp_dummy_hdr_ext_new ();
gst_rtp_header_extension_set_id (ext2, 2);
g_signal_emit_by_name (state->element, "add-extension", ext1);
g_signal_emit_by_name (state->element, "add-extension", ext2);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 0 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
validate_buffers_received (1);
validate_buffer (0, "pts", 0 * GST_SECOND, NULL);
validate_events_received (3);
validate_normal_start_events (0);
fail_unless_equals_int (GST_RTP_DUMMY_HDR_EXT (ext1)->write_count, 1);
fail_unless_equals_int (GST_RTP_DUMMY_HDR_EXT (ext2)->write_count, 1);
gst_object_unref (ext1);
gst_object_unref (ext2);
destroy_payloader (state);
}
GST_END_TEST;
static GstStaticPadTemplate sinktmpl_with_extmap_str =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("application/x-rtp, payload=(int)98, ssrc=(uint)24, "
"timestamp-offset=(uint)212, seqnum-offset=(uint)2424, extmap-4=(string)"
DUMMY_HDR_EXT_URI));
static GstRTPHeaderExtension *
request_extension (GstRTPBasePayload * depayload, guint ext_id,
const gchar * ext_uri, gpointer user_data)
{
GstRTPHeaderExtension *ext = user_data;
if (ext && gst_rtp_header_extension_get_id (ext) == ext_id
&& g_strcmp0 (ext_uri, gst_rtp_header_extension_get_uri (ext)) == 0)
return gst_object_ref (ext);
return NULL;
}
GST_START_TEST (rtp_base_payload_caps_request)
{
GstRTPHeaderExtension *ext;
State *state;
state =
create_payloader ("application/x-rtp", &sinktmpl_with_extmap_str, NULL);
ext = rtp_dummy_hdr_ext_new ();
gst_rtp_header_extension_set_id (ext, 4);
g_signal_connect (state->element, "request-extension",
G_CALLBACK (request_extension), ext);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 0 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
validate_buffers_received (1);
validate_buffer (0, "pts", 0 * GST_SECOND, NULL);
validate_events_received (3);
validate_normal_start_events (0);
fail_unless_equals_int (GST_RTP_DUMMY_HDR_EXT (ext)->write_count, 1);
gst_object_unref (ext);
destroy_payloader (state);
}
GST_END_TEST;
static GstRTPHeaderExtension *
request_extension_ignored (GstRTPBasePayload * depayload, guint ext_id,
const gchar * ext_uri, gpointer user_data)
{
guint *request_counter = user_data;
*request_counter += 1;
return NULL;
}
GST_START_TEST (rtp_base_payload_caps_request_ignored)
{
State *state;
guint request_counter = 0;
state =
create_payloader ("application/x-rtp", &sinktmpl_with_extmap_str, NULL);
g_signal_connect (state->element, "request-extension",
G_CALLBACK (request_extension_ignored), &request_counter);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 0 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
fail_unless_equals_int (request_counter, 1);
validate_buffers_received (1);
destroy_payloader (state);
}
GST_END_TEST;
GST_START_TEST (rtp_base_payload_extensions_in_output_caps)
{
GstRTPHeaderExtension *ext;
State *state;
state = create_payloader ("application/x-rtp", &sinktmpl, NULL);
ext = rtp_dummy_hdr_ext_new ();
GST_RTP_DUMMY_HDR_EXT (ext)->supported_flags =
GST_RTP_HEADER_EXTENSION_TWO_BYTE;
gst_rtp_header_extension_set_id (ext, 1);
g_signal_emit_by_name (state->element, "add-extension", ext);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 0 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
validate_buffers_received (1);
validate_buffer (0, "pts", 0 * GST_SECOND, NULL);
validate_events_received (3);
validate_normal_start_events (0);
validate_event (1, "caps", "extmap-str", 1, DUMMY_HDR_EXT_URI, NULL);
fail_unless_equals_int (GST_RTP_DUMMY_HDR_EXT (ext)->write_count, 1);
gst_object_unref (ext);
ext = NULL;
destroy_payloader (state);
}
GST_END_TEST;
static Suite *
rtp_basepayloading_suite (void)
{
@ -2052,13 +2292,20 @@ rtp_basepayloading_suite (void)
tcase_add_test (tc_chain, rtp_base_payload_property_ptime_multiple_test);
tcase_add_test (tc_chain, rtp_base_payload_property_stats_test);
tcase_add_test (tc_chain, rtp_base_payload_property_source_info_test);
tcase_add_test (tc_chain, rtp_base_payload_property_twcc_ext_id_test);
tcase_add_test (tc_chain, rtp_base_payload_framerate_attribute);
tcase_add_test (tc_chain, rtp_base_payload_max_framerate_attribute);
tcase_add_test (tc_chain, rtp_base_payload_segment_time);
tcase_add_test (tc_chain, rtp_base_payload_one_byte_hdr_ext);
tcase_add_test (tc_chain, rtp_base_payload_two_byte_hdr_ext);
tcase_add_test (tc_chain, rtp_base_payload_clear_extensions);
tcase_add_test (tc_chain, rtp_base_payload_multiple_exts);
tcase_add_test (tc_chain, rtp_base_payload_caps_request);
tcase_add_test (tc_chain, rtp_base_payload_caps_request_ignored);
tcase_add_test (tc_chain, rtp_base_payload_extensions_in_output_caps);
return s;
}