From 993bc8fc01a780314f17f69c310e98b152a06d7c Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Tue, 18 Oct 2022 12:17:04 +1100 Subject: [PATCH] webrtc: implement support for msid values Local msid values are taken from sink pad property, or fallback to the previously used cname. The remote msid values are exposed on the relevant src pads. Part-of: --- .../docs/plugins/gst_plugins_cache.json | 59 +++- .../gst-plugins-bad/ext/webrtc/gstwebrtcbin.c | 274 +++++++++++++++--- .../gst-plugins-bad/ext/webrtc/gstwebrtcbin.h | 11 + .../gst-plugins-bad/ext/webrtc/utils.c | 25 ++ .../gst-plugins-bad/ext/webrtc/utils.h | 2 + .../tests/check/elements/webrtcbin.c | 193 +++++++++++- 6 files changed, 511 insertions(+), 53 deletions(-) diff --git a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json index 519ead4fbd..00d4861048 100644 --- a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json +++ b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json @@ -236665,12 +236665,13 @@ "caps": "application/x-rtp:\n", "direction": "sink", "presence": "request", - "type": "GstWebRTCBinPad" + "type": "GstWebRTCBinSinkPad" }, "src_%%u": { "caps": "application/x-rtp:\n", "direction": "src", - "presence": "sometimes" + "presence": "sometimes", + "type": "GstWebRTCBinSrcPad" } }, "properties": { @@ -237128,6 +237129,60 @@ "writable": false } } + }, + "GstWebRTCBinSinkPad": { + "hierarchy": [ + "GstWebRTCBinSinkPad", + "GstWebRTCBinPad", + "GstGhostPad", + "GstProxyPad", + "GstPad", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "kind": "object", + "properties": { + "msid": { + "blurb": "Local MediaStream ID to use for this pad (NULL = unset)", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "NULL", + "mutable": "null", + "readable": true, + "type": "gchararray", + "writable": true + } + } + }, + "GstWebRTCBinSrcPad": { + "hierarchy": [ + "GstWebRTCBinSrcPad", + "GstWebRTCBinPad", + "GstGhostPad", + "GstProxyPad", + "GstPad", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "kind": "object", + "properties": { + "msid": { + "blurb": "Remote MediaStream ID in use for this pad (NULL = not advertised)", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "NULL", + "mutable": "null", + "readable": true, + "type": "gchararray", + "writable": false + } + } } }, "package": "GStreamer Bad Plug-ins", diff --git a/subprojects/gst-plugins-bad/ext/webrtc/gstwebrtcbin.c b/subprojects/gst-plugins-bad/ext/webrtc/gstwebrtcbin.c index 2401fd4762..5377aa3c6c 100644 --- a/subprojects/gst-plugins-bad/ext/webrtc/gstwebrtcbin.c +++ b/subprojects/gst-plugins-bad/ext/webrtc/gstwebrtcbin.c @@ -254,13 +254,9 @@ gst_webrtc_bin_pad_finalize (GObject * object) { GstWebRTCBinPad *pad = GST_WEBRTC_BIN_PAD (object); - if (pad->trans) - gst_object_unref (pad->trans); - pad->trans = NULL; - - if (pad->received_caps) - gst_caps_unref (pad->received_caps); - pad->received_caps = NULL; + gst_clear_object (&pad->trans); + gst_clear_caps (&pad->received_caps); + g_clear_pointer (&pad->msid, g_free); G_OBJECT_CLASS (gst_webrtc_bin_pad_parent_class)->finalize (object); } @@ -457,33 +453,177 @@ gst_webrtc_bin_pad_init (GstWebRTCBinPad * pad) } static GstWebRTCBinPad * -gst_webrtc_bin_pad_new (const gchar * name, GstPadDirection direction) +gst_webrtc_bin_pad_new (const gchar * name, GstPadDirection direction, + char *msid) { GstWebRTCBinPad *pad; GstPadTemplate *template; + GType pad_type; - if (direction == GST_PAD_SINK) + if (direction == GST_PAD_SINK) { template = gst_static_pad_template_get (&sink_template); - else if (direction == GST_PAD_SRC) + pad_type = GST_TYPE_WEBRTC_BIN_SINK_PAD; + } else if (direction == GST_PAD_SRC) { template = gst_static_pad_template_get (&src_template); - else + pad_type = GST_TYPE_WEBRTC_BIN_SRC_PAD; + } else { g_assert_not_reached (); + } pad = - g_object_new (gst_webrtc_bin_pad_get_type (), "name", name, "direction", + g_object_new (pad_type, "name", name, "direction", direction, "template", template, NULL); gst_object_unref (template); - if (direction == GST_PAD_SINK) { - gst_pad_set_event_function (GST_PAD (pad), gst_webrtcbin_sink_event); - gst_pad_set_query_function (GST_PAD (pad), gst_webrtcbin_sink_query); - } + pad->msid = msid; GST_DEBUG_OBJECT (pad, "new visible pad with direction %s", direction == GST_PAD_SRC ? "src" : "sink"); return pad; } +enum +{ + PROP_SINK_PAD_MSID = 1, +}; + +/** + * GstWebRTCBinSinkPad: + * + * Since: 1.22 + */ +struct _GstWebRTCBinSinkPad +{ + GstWebRTCBinPad pad; +}; + +G_DEFINE_TYPE (GstWebRTCBinSinkPad, gst_webrtc_bin_sink_pad, + GST_TYPE_WEBRTC_BIN_PAD); + +static void +gst_webrtc_bin_sink_pad_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstWebRTCBinPad *pad = GST_WEBRTC_BIN_PAD (object); + + switch (prop_id) { + case PROP_SINK_PAD_MSID: + g_value_set_string (value, pad->msid); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_webrtc_bin_sink_pad_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstWebRTCBinPad *pad = GST_WEBRTC_BIN_PAD (object); + + switch (prop_id) { + case PROP_SINK_PAD_MSID: + g_free (pad->msid); + pad->msid = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_webrtc_bin_sink_pad_class_init (GstWebRTCBinSinkPadClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->get_property = gst_webrtc_bin_sink_pad_get_property; + gobject_class->set_property = gst_webrtc_bin_sink_pad_set_property; + + /** + * GstWebRTCBinSinkPad:msid: + * + * The MediaStream Identifier to use for this pad (MediaStreamTrack). + * Fallback is the RTP SDES cname value if not provided. + * + * Since: 1.22 + */ + g_object_class_install_property (gobject_class, + PROP_SINK_PAD_MSID, + g_param_spec_string ("msid", "MSID", + "Local MediaStream ID to use for this pad (NULL = unset)", NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_webrtc_bin_sink_pad_init (GstWebRTCBinSinkPad * pad) +{ + gst_pad_set_event_function (GST_PAD (pad), gst_webrtcbin_sink_event); + gst_pad_set_query_function (GST_PAD (pad), gst_webrtcbin_sink_query); +} + +enum +{ + PROP_SRC_PAD_MSID = 1, +}; + +/** + * GstWebRTCBinSrcPad: + * + * Since: 1.22 + */ +struct _GstWebRTCBinSrcPad +{ + GstWebRTCBinPad pad; +}; + +G_DEFINE_TYPE (GstWebRTCBinSrcPad, gst_webrtc_bin_src_pad, + GST_TYPE_WEBRTC_BIN_PAD); + +static void +gst_webrtc_bin_src_pad_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstWebRTCBinPad *pad = GST_WEBRTC_BIN_PAD (object); + + switch (prop_id) { + case PROP_SRC_PAD_MSID: + g_value_set_string (value, pad->msid); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_webrtc_bin_src_pad_class_init (GstWebRTCBinSrcPadClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->get_property = gst_webrtc_bin_src_pad_get_property; + + /** + * GstWebRTCBinSrcPad:msid: + * + * The MediaStream Identifier the remote peer used for this pad (MediaStreamTrack). + * Will be NULL if not advertised in the remote SDP. + * + * Since: 1.22 + */ + g_object_class_install_property (gobject_class, + PROP_SRC_PAD_MSID, + g_param_spec_string ("msid", "MSID", + "Remote MediaStream ID in use for this pad (NULL = not advertised)", + NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_webrtc_bin_src_pad_init (GstWebRTCBinSrcPad * pad) +{ +} + #define gst_webrtc_bin_parent_class parent_class G_DEFINE_TYPE_WITH_CODE (GstWebRTCBin, gst_webrtc_bin, GST_TYPE_BIN, G_ADD_PRIVATE (GstWebRTCBin) @@ -2867,15 +3007,27 @@ _media_add_rtx_ssrc (GQuark field_id, const GValue * value, RtxSsrcData * data) gchar *str; GstStructure *sdes; const gchar *cname; + GstWebRTCBinPad *sink_pad; + const char *msid = NULL; g_object_get (data->webrtc->rtpbin, "sdes", &sdes, NULL); /* http://www.freesoft.org/CIE/RFC/1889/24.htm */ cname = gst_structure_get_string (sdes, "cname"); + sink_pad = + _find_pad_for_transceiver (data->webrtc, GST_PAD_SINK, + GST_WEBRTC_RTP_TRANSCEIVER (data->trans)); + if (sink_pad) + msid = sink_pad->msid; + /* fallback to cname if no msid provided */ + if (!msid) + msid = cname; + /* https://tools.ietf.org/html/draft-ietf-mmusic-msid-16 */ + /* FIXME: the ssrc is not present in RFC8830, do we still need that? */ str = g_strdup_printf ("%u msid:%s %s", g_value_get_uint (value), - cname, GST_OBJECT_NAME (data->trans)); + msid, GST_OBJECT_NAME (data->trans)); gst_sdp_media_add_attribute (data->media, "ssrc", str); g_free (str); @@ -2883,6 +3035,7 @@ _media_add_rtx_ssrc (GQuark field_id, const GValue * value, RtxSsrcData * data) gst_sdp_media_add_attribute (data->media, "ssrc", str); g_free (str); + gst_clear_object (&sink_pad); gst_structure_free (sdes); return TRUE; @@ -2911,10 +3064,22 @@ _media_add_ssrcs (GstSDPMedia * media, GstCaps * caps, GstWebRTCBin * webrtc, if (gst_structure_get_uint (s, "ssrc", &ssrc)) { gchar *str; + GstWebRTCBinPad *sink_pad; + const char *msid = NULL; + + sink_pad = + _find_pad_for_transceiver (webrtc, GST_PAD_SINK, + GST_WEBRTC_RTP_TRANSCEIVER (trans)); + if (sink_pad) + msid = sink_pad->msid; + /* fallback to cname if no msid provided */ + if (!msid) + msid = cname; /* https://tools.ietf.org/html/draft-ietf-mmusic-msid-16 */ + /* FIXME: the ssrc is not present in RFC8830, do we still need that? */ str = - g_strdup_printf ("%u msid:%s %s", ssrc, cname, + g_strdup_printf ("%u msid:%s %s", ssrc, msid, GST_OBJECT_NAME (trans)); gst_sdp_media_add_attribute (media, "ssrc", str); g_free (str); @@ -2922,6 +3087,8 @@ _media_add_ssrcs (GstSDPMedia * media, GstCaps * caps, GstWebRTCBin * webrtc, str = g_strdup_printf ("%u cname:%s", ssrc, cname); gst_sdp_media_add_attribute (media, "ssrc", str); g_free (str); + + gst_clear_object (&sink_pad); } } @@ -4697,7 +4864,7 @@ gst_webrtc_bin_create_answer (GstWebRTCBin * webrtc, static GstWebRTCBinPad * _create_pad_for_sdp_media (GstWebRTCBin * webrtc, GstPadDirection direction, - GstWebRTCRTPTransceiver * trans, guint serial) + GstWebRTCRTPTransceiver * trans, guint serial, char *msid) { GstWebRTCBinPad *pad; gchar *pad_name; @@ -4712,7 +4879,7 @@ _create_pad_for_sdp_media (GstWebRTCBin * webrtc, GstPadDirection direction, pad_name = g_strdup_printf ("%s_%u", direction == GST_PAD_SRC ? "src" : "sink", serial); - pad = gst_webrtc_bin_pad_new (pad_name, direction); + pad = gst_webrtc_bin_pad_new (pad_name, direction, msid); g_free (pad_name); pad->trans = gst_object_ref (trans); @@ -5420,12 +5587,21 @@ _update_transceiver_from_sdp_media (GstWebRTCBin * webrtc, WebRTCTransceiver *trans = WEBRTC_TRANSCEIVER (rtp_trans); GstWebRTCRTPTransceiverDirection prev_dir = rtp_trans->current_direction; GstWebRTCRTPTransceiverDirection new_dir; + const GstSDPMedia *local_media, *remote_media; const GstSDPMedia *media = gst_sdp_message_get_media (sdp, media_idx); GstWebRTCDTLSSetup new_setup; + char *local_msid = NULL; gboolean new_rtcp_rsize; ReceiveState receive_state = RECEIVE_STATE_UNSET; int i; + local_media = + gst_sdp_message_get_media (webrtc->current_local_description->sdp, + media_idx); + remote_media = + gst_sdp_message_get_media (webrtc->current_remote_description->sdp, + media_idx); + rtp_trans->mline = media_idx; if (!g_strcmp0 (gst_sdp_media_get_media (media), "audio")) { @@ -5452,17 +5628,9 @@ _update_transceiver_from_sdp_media (GstWebRTCBin * webrtc, } { - const GstSDPMedia *local_media, *remote_media; GstWebRTCRTPTransceiverDirection local_dir, remote_dir; GstWebRTCDTLSSetup local_setup, remote_setup; - local_media = - gst_sdp_message_get_media (webrtc->current_local_description->sdp, - media_idx); - remote_media = - gst_sdp_message_get_media (webrtc->current_remote_description->sdp, - media_idx); - local_setup = _get_dtls_setup_from_media (local_media); remote_setup = _get_dtls_setup_from_media (remote_media); new_setup = _get_final_setup (local_setup, remote_setup); @@ -5555,16 +5723,30 @@ _update_transceiver_from_sdp_media (GstWebRTCBin * webrtc, new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV) { GstWebRTCBinPad *pad = _find_pad_for_transceiver (webrtc, GST_PAD_SINK, rtp_trans); + local_msid = _get_msid_from_media (local_media); if (pad) { GST_DEBUG_OBJECT (webrtc, "found existing send pad %" GST_PTR_FORMAT - " for transceiver %" GST_PTR_FORMAT, pad, trans); + " for transceiver %" GST_PTR_FORMAT " with msid \'%s\'", pad, trans, + pad->msid); + if (g_strcmp0 (pad->msid, local_msid) != 0) { + GST_DEBUG_OBJECT (webrtc, "send pad %" GST_PTR_FORMAT + " transceiver %" GST_PTR_FORMAT " changing msid from \'%s\'" + " to \'%s\'", pad, trans, pad->msid, local_msid); + g_clear_pointer (&pad->msid, g_free); + pad->msid = local_msid; + g_object_notify (G_OBJECT (pad), "msid"); + local_msid = NULL; + } else { + g_clear_pointer (&local_msid, g_free); + } gst_object_unref (pad); } else { GST_DEBUG_OBJECT (webrtc, "creating new send pad for transceiver %" GST_PTR_FORMAT, trans); pad = _create_pad_for_sdp_media (webrtc, GST_PAD_SINK, rtp_trans, - G_MAXUINT); + G_MAXUINT, local_msid); + local_msid = NULL; _connect_input_stream (webrtc, pad); _add_pad (webrtc, pad); } @@ -5573,15 +5755,30 @@ _update_transceiver_from_sdp_media (GstWebRTCBin * webrtc, new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV) { GstWebRTCBinPad *pad = _find_pad_for_transceiver (webrtc, GST_PAD_SRC, rtp_trans); + char *remote_msid = _get_msid_from_media (remote_media); + if (pad) { GST_DEBUG_OBJECT (webrtc, "found existing receive pad %" GST_PTR_FORMAT - " for transceiver %" GST_PTR_FORMAT, pad, trans); + " for transceiver %" GST_PTR_FORMAT " with msid \'%s\'", pad, trans, + pad->msid); + if (g_strcmp0 (pad->msid, remote_msid) != 0) { + GST_DEBUG_OBJECT (webrtc, "receive pad %" GST_PTR_FORMAT + " transceiver %" GST_PTR_FORMAT " changing msid from \'%s\'" + " to \'%s\'", pad, trans, pad->msid, remote_msid); + g_clear_pointer (&pad->msid, g_free); + pad->msid = remote_msid; + remote_msid = NULL; + g_object_notify (G_OBJECT (pad), "msid"); + } else { + g_clear_pointer (&remote_msid, g_free); + } gst_object_unref (pad); } else { GST_DEBUG_OBJECT (webrtc, "creating new receive pad for transceiver %" GST_PTR_FORMAT, trans); pad = _create_pad_for_sdp_media (webrtc, GST_PAD_SRC, rtp_trans, - G_MAXUINT); + G_MAXUINT, remote_msid); + remote_msid = NULL; if (!trans->stream) { TransportStream *item; @@ -5597,7 +5794,6 @@ _update_transceiver_from_sdp_media (GstWebRTCBin * webrtc, * as soon as the pad is added */ _add_pad_to_list (webrtc, pad); } - } rtp_trans->mline = media_idx; @@ -7058,7 +7254,8 @@ on_rtpbin_pad_added (GstElement * rtpbin, GstPad * new_pad, * or somesuch */ gst_clear_object (&pad); pad = - _create_pad_for_sdp_media (webrtc, GST_PAD_SRC, rtp_trans, G_MAXUINT); + _create_pad_for_sdp_media (webrtc, GST_PAD_SRC, rtp_trans, G_MAXUINT, + NULL); GST_TRACE_OBJECT (webrtc, "duplicate output ssrc? created new pad %" GST_PTR_FORMAT " for %" GST_PTR_FORMAT " for rtp pad %s", pad, rtp_trans, new_pad_name); @@ -7940,7 +8137,7 @@ gst_webrtc_bin_request_new_pad (GstElement * element, GstPadTemplate * templ, } } } - pad = _create_pad_for_sdp_media (webrtc, GST_PAD_SINK, trans, serial); + pad = _create_pad_for_sdp_media (webrtc, GST_PAD_SINK, trans, serial, NULL); pad->block_id = gst_pad_add_probe (GST_PAD (pad), GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BUFFER_LIST, @@ -8265,8 +8462,9 @@ gst_webrtc_bin_class_init (GstWebRTCBinClass * klass) element_class->change_state = gst_webrtc_bin_change_state; gst_element_class_add_static_pad_template_with_gtype (element_class, - &sink_template, GST_TYPE_WEBRTC_BIN_PAD); - gst_element_class_add_static_pad_template (element_class, &src_template); + &sink_template, GST_TYPE_WEBRTC_BIN_SINK_PAD); + gst_element_class_add_static_pad_template_with_gtype (element_class, + &src_template, GST_TYPE_WEBRTC_BIN_SRC_PAD); gst_element_class_set_metadata (element_class, "WebRTC Bin", "Filter/Network/WebRTC", "A bin for webrtc connections", @@ -8764,6 +8962,8 @@ gst_webrtc_bin_class_init (GstWebRTCBinClass * klass) NULL, GST_TYPE_WEBRTC_DATA_CHANNEL, 2, G_TYPE_STRING, GST_TYPE_STRUCTURE); gst_type_mark_as_plugin_api (GST_TYPE_WEBRTC_BIN_PAD, 0); + gst_type_mark_as_plugin_api (GST_TYPE_WEBRTC_BIN_SINK_PAD, 0); + gst_type_mark_as_plugin_api (GST_TYPE_WEBRTC_BIN_SRC_PAD, 0); } static void diff --git a/subprojects/gst-plugins-bad/ext/webrtc/gstwebrtcbin.h b/subprojects/gst-plugins-bad/ext/webrtc/gstwebrtcbin.h index 9663224440..9445d9e5a4 100644 --- a/subprojects/gst-plugins-bad/ext/webrtc/gstwebrtcbin.h +++ b/subprojects/gst-plugins-bad/ext/webrtc/gstwebrtcbin.h @@ -38,6 +38,8 @@ GType gst_webrtc_bin_pad_get_type(void); typedef struct _GstWebRTCBinPad GstWebRTCBinPad; typedef struct _GstWebRTCBinPadClass GstWebRTCBinPadClass; +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GstWebRTCBinPad, gst_object_unref); + struct _GstWebRTCBinPad { GstGhostPad parent; @@ -46,6 +48,7 @@ struct _GstWebRTCBinPad gulong block_id; GstCaps *received_caps; + char *msid; }; struct _GstWebRTCBinPadClass @@ -53,6 +56,14 @@ struct _GstWebRTCBinPadClass GstGhostPadClass parent_class; }; +G_DECLARE_FINAL_TYPE (GstWebRTCBinSinkPad, gst_webrtc_bin_sink_pad, GST, + WEBRTC_BIN_SINK_PAD, GstWebRTCBinPad); +#define GST_TYPE_WEBRTC_BIN_SINK_PAD (gst_webrtc_bin_sink_pad_get_type()) + +G_DECLARE_FINAL_TYPE (GstWebRTCBinSrcPad, gst_webrtc_bin_src_pad, GST, + WEBRTC_BIN_SRC_PAD, GstWebRTCBinPad); +#define GST_TYPE_WEBRTC_BIN_SRC_PAD (gst_webrtc_bin_src_pad_get_type()) + GType gst_webrtc_bin_get_type(void); #define GST_TYPE_WEBRTC_BIN (gst_webrtc_bin_get_type()) #define GST_WEBRTC_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_WEBRTC_BIN,GstWebRTCBin)) diff --git a/subprojects/gst-plugins-bad/ext/webrtc/utils.c b/subprojects/gst-plugins-bad/ext/webrtc/utils.c index 5b6dd08fa4..f0741d1e55 100644 --- a/subprojects/gst-plugins-bad/ext/webrtc/utils.c +++ b/subprojects/gst-plugins-bad/ext/webrtc/utils.c @@ -223,3 +223,28 @@ webrtc_kind_from_caps (const GstCaps * caps) return GST_WEBRTC_KIND_UNKNOWN; } + +char * +_get_msid_from_media (const GstSDPMedia * media) +{ + int i; + + for (i = 0; i < gst_sdp_media_attributes_len (media); i++) { + const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i); + const char *start, *end; + + if (!attr->value) + continue; + + start = strstr (attr->value, "msid:"); + if (!start) + continue; + + start += strlen ("msid:"); + end = strstr (start, " "); + if (end) + return g_strndup (start, end - start); + } + + return NULL; +} diff --git a/subprojects/gst-plugins-bad/ext/webrtc/utils.h b/subprojects/gst-plugins-bad/ext/webrtc/utils.h index 9d4e1ee3dd..e5d3d124ab 100644 --- a/subprojects/gst-plugins-bad/ext/webrtc/utils.h +++ b/subprojects/gst-plugins-bad/ext/webrtc/utils.h @@ -66,6 +66,8 @@ G_GNUC_INTERNAL GstCaps * _rtp_caps_from_media (const GstSDPMedia * media); G_GNUC_INTERNAL GstWebRTCKind webrtc_kind_from_caps (const GstCaps * caps); +G_GNUC_INTERNAL +char * _get_msid_from_media (const GstSDPMedia * media); #define gst_webrtc_kind_to_string(kind) _enum_value_to_string(GST_TYPE_WEBRTC_KIND, kind) #define gst_webrtc_rtp_transceiver_direction_to_string(dir) _enum_value_to_string(GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION, dir) diff --git a/subprojects/gst-plugins-bad/tests/check/elements/webrtcbin.c b/subprojects/gst-plugins-bad/tests/check/elements/webrtcbin.c index 42c759c958..b9d1b83d5a 100644 --- a/subprojects/gst-plugins-bad/tests/check/elements/webrtcbin.c +++ b/subprojects/gst-plugins-bad/tests/check/elements/webrtcbin.c @@ -47,7 +47,7 @@ typedef enum { - STATE_NEW, + STATE_NEW = 1, STATE_NEGOTIATION_NEEDED, STATE_OFFER_CREATED, STATE_OFFER_SET, @@ -776,27 +776,56 @@ test_webrtc_create_offer (struct test_webrtc *t) } static TestState -test_webrtc_wait_for_state_mask (struct test_webrtc *t, TestState state) +test_webrtc_check_for_state_mask_unlocked (struct test_webrtc *t, + TestState state) { guint i; + GST_LOG ("attempting to check for state mask 0x%x", state); + for (i = 0; i < t->states->len; i++) { + TestState val = g_array_index (t->states, TestState, i); + + if (((1 << val) & state) != 0) { + GST_DEBUG ("found state 0x%x in wait mask 0x%x at idx %u", val, state, i); + g_array_remove_range (t->states, 0, i + 1); + return val; + } + } + + return 0; +} + +static TestState +test_webrtc_check_for_state_mask (struct test_webrtc *t, TestState state) +{ + TestState ret; + + g_mutex_lock (&t->lock); + ret = test_webrtc_check_for_state_mask_unlocked (t, state); + g_mutex_unlock (&t->lock); + + return ret; +} + +static TestState +test_webrtc_wait_for_state_mask (struct test_webrtc *t, TestState state) +{ + TestState ret = 0; + g_mutex_lock (&t->lock); GST_LOG ("attempting to wait for state mask 0x%x", state); while (TRUE) { - for (i = 0; i < t->states->len; i++) { - TestState val = g_array_index (t->states, TestState, i); + ret = test_webrtc_check_for_state_mask_unlocked (t, state); + + if (ret) + break; - if (((1 << val) & state) != 0) { - GST_DEBUG ("found state 0x%x in wait mask 0x%x at idx %u", val, state, - i); - g_array_remove_range (t->states, 0, i + 1); - g_mutex_unlock (&t->lock); - return val; - } - } g_cond_wait (&t->cond, &t->lock); } + g_mutex_unlock (&t->lock); + + return ret; } static TestState @@ -3862,7 +3891,7 @@ GST_START_TEST (test_codec_preferences_incompatible_extmaps) t->on_ice_candidate = NULL; t->on_offer_created = offer_created_produced_error; - test_validate_sdp_full (t, NULL, NULL, STATE_OFFER_CREATED, TRUE); + test_validate_sdp_full (t, NULL, NULL, 1 << STATE_ERROR, TRUE); test_webrtc_free (t); } @@ -3895,7 +3924,7 @@ GST_START_TEST (test_codec_preferences_invalid_extmap) t->on_ice_candidate = NULL; t->on_offer_created = offer_created_produced_error; - test_validate_sdp_full (t, NULL, NULL, STATE_OFFER_CREATED, TRUE); + test_validate_sdp_full (t, NULL, NULL, 1 << STATE_ERROR, TRUE); test_webrtc_free (t); } @@ -5540,6 +5569,141 @@ GST_START_TEST (test_data_channel_recreate_offer) GST_END_TEST; +static void +validate_msid (struct test_webrtc *t, GstElement * element, + GstWebRTCSessionDescription * desc, gpointer user_data) +{ + char **expected_msid = user_data; + int i; + + for (i = 0; i < gst_sdp_message_medias_len (desc->sdp); i++) { + const GstSDPMedia *media = gst_sdp_message_get_media (desc->sdp, i); + gboolean have_msid = FALSE; + char *prev_msid = NULL; + int j; + + for (j = 0; j < gst_sdp_media_attributes_len (media); j++) { + const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, j); + const char *start; + + if (!attr->value) + continue; + + start = strstr (attr->value, "msid:"); + if (start) { + const char *end; + char *msid; + + start += strlen ("msid:"); + end = strstr (start, " "); + msid = g_strndup (start, end - start); + fail_unless (end, "Invalid msid attribute"); + fail_if (have_msid && g_strcmp0 (prev_msid, msid) != 0, + "different values for multiple msid values at mline %u, " + "prev msid %s, msid %s", i, prev_msid, msid); + have_msid = TRUE; + fail_unless_equals_string (msid, expected_msid[i]); + g_clear_pointer (&prev_msid, g_free); + prev_msid = msid; + } + } + g_clear_pointer (&prev_msid, g_free); + fail_unless (have_msid, "no msid attribute in media %u", i); + } +} + +static void +_pad_added_src_check_msid (struct test_webrtc *t, GstElement * element, + GstPad * pad, gpointer user_data) +{ + const char *expected_msid = user_data; + char *msid; + + if (GST_PAD_DIRECTION (pad) != GST_PAD_SRC) + return; + + g_object_get (pad, "msid", &msid, NULL); + fail_unless_equals_string (msid, expected_msid); + g_clear_pointer (&msid, g_free); + + test_webrtc_signal_state_unlocked (t, STATE_CUSTOM); +} + +GST_START_TEST (test_msid) +{ + struct test_webrtc *t = create_audio_test (); + VAL_SDP_INIT (no_duplicate_payloads, on_sdp_media_no_duplicate_payloads, + NULL, NULL); + guint media_format_count[] = { 1, 5 }; + VAL_SDP_INIT (media_formats, on_sdp_media_count_formats, + media_format_count, &no_duplicate_payloads); + VAL_SDP_INIT (count, _count_num_sdp_media, GUINT_TO_POINTER (2), + &media_formats); + const gchar *expected_offer_msid[] = { "a1", "a1", }; + VAL_SDP_INIT (offer_msid, validate_msid, expected_offer_msid, &count); + const gchar *expected_offer_setup[] = { "actpass", "actpass", }; + VAL_SDP_INIT (offer_setup, on_sdp_media_setup, expected_offer_setup, + &offer_msid); + const gchar *expected_offer_direction[] = { "sendrecv", "sendrecv", }; + VAL_SDP_INIT (offer, on_sdp_media_direction, expected_offer_direction, + &offer_setup); + const gchar *expected_answer_setup[] = { "active", "active", }; + VAL_SDP_INIT (answer_setup, on_sdp_media_setup, expected_answer_setup, + &count); + const gchar *expected_answer_direction[] = { "recvonly", "recvonly", }; + VAL_SDP_INIT (answer, on_sdp_media_direction, expected_answer_direction, + &answer_setup); + GstPad *pad; + GstHarness *src; + GstElement *rtpbin2; + + t->on_pad_added = _pad_added_src_check_msid; + t->pad_added_data = (gpointer) "a1"; + + rtpbin2 = gst_bin_get_by_name (GST_BIN (t->webrtc2), "rtpbin"); + fail_unless (rtpbin2 != NULL); + g_signal_connect (rtpbin2, "new-jitterbuffer", + G_CALLBACK (new_jitterbuffer_set_fast_start), NULL); + g_object_unref (rtpbin2); + + g_signal_connect (t->webrtc1, "on-new-transceiver", + G_CALLBACK (on_new_transceiver_set_rtx_fec), NULL); + g_signal_connect (t->webrtc2, "on-new-transceiver", + G_CALLBACK (on_new_transceiver_set_rtx_fec), NULL); + + src = gst_harness_new_with_element (t->webrtc1, "sink_1", NULL); + add_audio_test_src_harness (src, 0x12345678); + t->harnesses = g_list_prepend (t->harnesses, src); + + pad = gst_element_get_static_pad (t->webrtc1, "sink_0"); + g_object_set (pad, "msid", "a1", NULL); + gst_clear_object (&pad); + + pad = gst_element_get_static_pad (t->webrtc1, "sink_1"); + g_object_set (pad, "msid", "a1", NULL); + gst_clear_object (&pad); + + test_validate_sdp (t, &offer, &answer); + + fail_if (gst_element_set_state (t->webrtc1, + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + fail_if (gst_element_set_state (t->webrtc2, + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + while (TRUE) { + gst_harness_push_from_src (src); + + if (test_webrtc_check_for_state_mask (t, 1 << STATE_CUSTOM)) + break; + + g_usleep (10 * 1000); + } + + test_webrtc_free (t); +} + +GST_END_TEST; + static Suite * webrtcbin_suite (void) { @@ -5604,6 +5768,7 @@ webrtcbin_suite (void) tcase_add_test (tc, test_bundle_multiple_media_rtx_payload_mapping); tcase_add_test (tc, test_invalid_add_media_in_answer); tcase_add_test (tc, test_add_turn_server); + tcase_add_test (tc, test_msid); if (sctpenc && sctpdec) { tcase_add_test (tc, test_data_channel_create); tcase_add_test (tc, test_data_channel_remote_notify);