mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-26 19:51:11 +00:00
webrtcbin: deduplicate extmaps
When an extmap is defined twice for the same ID, firefox complains and errors out (chrome is smart enough to accept strict duplicates). To work around this, we deduplicate extmap attributes, and also error out when a different extmap is defined for the same ID. Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/1383>
This commit is contained in:
parent
d867180b4e
commit
e90859f4d8
2 changed files with 309 additions and 2 deletions
|
@ -2766,6 +2766,146 @@ _add_fingerprint_to_media (GstWebRTCDTLSTransport * transport,
|
|||
g_free (val);
|
||||
}
|
||||
|
||||
static gchar *
|
||||
_parse_extmap (GQuark field_id, const GValue * value, GError ** error)
|
||||
{
|
||||
gchar *ret = NULL;
|
||||
|
||||
if (G_VALUE_HOLDS_STRING (value)) {
|
||||
ret = g_value_dup_string (value);
|
||||
} else if (G_VALUE_HOLDS (value, GST_TYPE_ARRAY)
|
||||
&& gst_value_array_get_size (value) == 3) {
|
||||
const GValue *val;
|
||||
const gchar *direction, *extensionname, *extensionattributes;
|
||||
|
||||
val = gst_value_array_get_value (value, 0);
|
||||
direction = g_value_get_string (val);
|
||||
|
||||
val = gst_value_array_get_value (value, 1);
|
||||
extensionname = g_value_get_string (val);
|
||||
|
||||
val = gst_value_array_get_value (value, 2);
|
||||
extensionattributes = g_value_get_string (val);
|
||||
|
||||
if (!extensionname || *extensionname == '\0')
|
||||
goto done;
|
||||
|
||||
if (direction && *direction != '\0' && extensionattributes
|
||||
&& *extensionattributes != '\0') {
|
||||
ret =
|
||||
g_strdup_printf ("/%s %s %s", direction, extensionname,
|
||||
extensionattributes);
|
||||
} else if (direction && *direction != '\0') {
|
||||
ret = g_strdup_printf ("/%s %s", direction, extensionname);
|
||||
} else if (extensionattributes && *extensionattributes != '\0') {
|
||||
ret = g_strdup_printf ("%s %s", extensionname, extensionattributes);
|
||||
} else {
|
||||
ret = g_strdup (extensionname);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ret && error) {
|
||||
gchar *val_str = gst_value_serialize (value);
|
||||
|
||||
g_set_error (error, GST_WEBRTC_BIN_ERROR,
|
||||
GST_WEBRTC_BIN_ERROR_CAPS_NEGOTIATION_FAILED,
|
||||
"Invalid value for %s: %s", g_quark_to_string (field_id), val_str);
|
||||
g_free (val_str);
|
||||
}
|
||||
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gboolean ret;
|
||||
GstStructure *extmap;
|
||||
GError **error;
|
||||
} ExtmapData;
|
||||
|
||||
static gboolean
|
||||
_dedup_extmap_field (GQuark field_id, const GValue * value, ExtmapData * data)
|
||||
{
|
||||
gboolean is_extmap =
|
||||
g_str_has_prefix (g_quark_to_string (field_id), "extmap-");
|
||||
|
||||
if (!data->ret)
|
||||
goto done;
|
||||
|
||||
if (is_extmap) {
|
||||
gchar *new_value = _parse_extmap (field_id, value, data->error);
|
||||
|
||||
if (!new_value) {
|
||||
data->ret = FALSE;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (gst_structure_id_has_field (data->extmap, field_id)) {
|
||||
gchar *old_value =
|
||||
_parse_extmap (field_id, gst_structure_id_get_value (data->extmap,
|
||||
field_id), NULL);
|
||||
|
||||
g_assert (old_value);
|
||||
|
||||
if (g_strcmp0 (new_value, old_value)) {
|
||||
GST_ERROR
|
||||
("extmap contains different values for id %s (%s != %s)",
|
||||
g_quark_to_string (field_id), old_value, new_value);
|
||||
g_set_error (data->error, GST_WEBRTC_BIN_ERROR,
|
||||
GST_WEBRTC_BIN_ERROR_CAPS_NEGOTIATION_FAILED,
|
||||
"extmap contains different values for id %s (%s != %s)",
|
||||
g_quark_to_string (field_id), old_value, new_value);
|
||||
data->ret = FALSE;
|
||||
}
|
||||
|
||||
g_free (old_value);
|
||||
|
||||
}
|
||||
|
||||
if (data->ret) {
|
||||
gst_structure_id_set_value (data->extmap, field_id, value);
|
||||
}
|
||||
|
||||
g_free (new_value);
|
||||
}
|
||||
|
||||
done:
|
||||
return !is_extmap;
|
||||
}
|
||||
|
||||
static GstStructure *
|
||||
_gather_extmap (GstCaps * caps, GError ** error)
|
||||
{
|
||||
ExtmapData edata =
|
||||
{ TRUE, gst_structure_new_empty ("application/x-extmap"), error };
|
||||
guint i, n;
|
||||
|
||||
n = gst_caps_get_size (caps);
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
GstStructure *s = gst_caps_get_structure (caps, i);
|
||||
|
||||
gst_structure_filter_and_map_in_place (s,
|
||||
(GstStructureFilterMapFunc) _dedup_extmap_field, &edata);
|
||||
|
||||
if (!edata.ret) {
|
||||
gst_clear_structure (&edata.extmap);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return edata.extmap;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_copy_field (GQuark field_id, const GValue * value, GstStructure * s)
|
||||
{
|
||||
gst_structure_id_set_value (s, field_id, value);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* based off https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-18#section-5.2.1 */
|
||||
static gboolean
|
||||
sdp_media_from_transceiver (GstWebRTCBin * webrtc, GstSDPMedia * media,
|
||||
|
@ -2788,6 +2928,7 @@ sdp_media_from_transceiver (GstWebRTCBin * webrtc, GstSDPMedia * media,
|
|||
gchar *direction, *sdp_mid, *ufrag, *pwd;
|
||||
gboolean bundle_only;
|
||||
GstCaps *caps;
|
||||
GstStructure *extmap;
|
||||
int i;
|
||||
|
||||
if (trans->direction == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE
|
||||
|
@ -2856,14 +2997,38 @@ sdp_media_from_transceiver (GstWebRTCBin * webrtc, GstSDPMedia * media,
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
caps = gst_caps_make_writable (caps);
|
||||
|
||||
/* When an extmap is defined twice for the same ID, firefox complains and
|
||||
* errors out (chrome is smart enough to accept strict duplicates).
|
||||
*
|
||||
* To work around this, we deduplicate extmap attributes, and also error
|
||||
* out when a different extmap is defined for the same ID.
|
||||
*
|
||||
* _gather_extmap will strip out all extmap- fields, which will then be
|
||||
* added upon adding the first format for the media.
|
||||
*/
|
||||
extmap = _gather_extmap (caps, error);
|
||||
|
||||
if (!extmap) {
|
||||
GST_ERROR_OBJECT (webrtc,
|
||||
"Failed to build extmap for transceiver %" GST_PTR_FORMAT, trans);
|
||||
gst_caps_unref (caps);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
caps = _add_supported_attributes_to_caps (webrtc, WEBRTC_TRANSCEIVER (trans),
|
||||
caps);
|
||||
|
||||
for (i = 0; i < gst_caps_get_size (caps); i++) {
|
||||
GstCaps *format = gst_caps_new_empty ();
|
||||
const GstStructure *s = gst_caps_get_structure (caps, i);
|
||||
GstStructure *s = gst_structure_copy (gst_caps_get_structure (caps, i));
|
||||
|
||||
gst_caps_append_structure (format, gst_structure_copy (s));
|
||||
if (i == 0) {
|
||||
gst_structure_foreach (extmap, (GstStructureForeachFunc) _copy_field, s);
|
||||
}
|
||||
|
||||
gst_caps_append_structure (format, s);
|
||||
|
||||
GST_DEBUG_OBJECT (webrtc, "Adding %u-th caps %" GST_PTR_FORMAT
|
||||
" to %u-th media", i, format, media_idx);
|
||||
|
@ -2875,6 +3040,8 @@ sdp_media_from_transceiver (GstWebRTCBin * webrtc, GstSDPMedia * media,
|
|||
gst_caps_unref (format);
|
||||
}
|
||||
|
||||
gst_clear_structure (&extmap);
|
||||
|
||||
{
|
||||
const GstStructure *s = gst_caps_get_structure (caps, 0);
|
||||
gint clockrate = -1;
|
||||
|
|
|
@ -3490,6 +3490,19 @@ offer_set_produced_error (struct test_webrtc *t, GstElement * element,
|
|||
test_webrtc_signal_state_unlocked (t, STATE_CUSTOM);
|
||||
}
|
||||
|
||||
static void
|
||||
offer_created_produced_error (struct test_webrtc *t, GstElement * element,
|
||||
GstPromise * promise, gpointer user_data)
|
||||
{
|
||||
const GstStructure *reply;
|
||||
GError *error = NULL;
|
||||
|
||||
reply = gst_promise_get_reply (promise);
|
||||
fail_unless (gst_structure_get (reply, "error", G_TYPE_ERROR, &error, NULL));
|
||||
GST_INFO ("error produced: %s", error->message);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
GST_START_TEST (test_renego_lose_media_fails)
|
||||
{
|
||||
struct test_webrtc *t = create_audio_video_test ();
|
||||
|
@ -3573,6 +3586,130 @@ GST_START_TEST (test_bundle_codec_preferences_rtx_no_duplicate_payloads)
|
|||
|
||||
GST_END_TEST;
|
||||
|
||||
static void
|
||||
on_sdp_media_no_duplicate_extmaps (struct test_webrtc *t, GstElement * element,
|
||||
GstWebRTCSessionDescription * desc, gpointer user_data)
|
||||
{
|
||||
const GstSDPMedia *media = gst_sdp_message_get_media (desc->sdp, 0);
|
||||
|
||||
fail_unless (media != NULL);
|
||||
|
||||
fail_unless_equals_string (gst_sdp_media_get_attribute_val_n (media, "extmap",
|
||||
0), "1 foobar");
|
||||
|
||||
fail_unless (gst_sdp_media_get_attribute_val_n (media, "extmap", 1) == NULL);
|
||||
}
|
||||
|
||||
/* In this test, we validate that identical extmaps for multiple formats
|
||||
* in the caps of a single transceiver are deduplicated. This is necessary
|
||||
* because Firefox will complain about duplicate extmap ids and fail negotiation
|
||||
* otherwise. */
|
||||
GST_START_TEST (test_codec_preferences_no_duplicate_extmaps)
|
||||
{
|
||||
struct test_webrtc *t = test_webrtc_new ();
|
||||
GstWebRTCRTPTransceiver *trans;
|
||||
GstWebRTCRTPTransceiverDirection direction;
|
||||
VAL_SDP_INIT (extmaps, on_sdp_media_no_duplicate_extmaps, NULL, NULL);
|
||||
GstCaps *caps;
|
||||
GstStructure *s;
|
||||
|
||||
caps = gst_caps_new_empty ();
|
||||
|
||||
s = gst_structure_from_string (VP8_RTP_CAPS (96), NULL);
|
||||
gst_structure_set (s, "extmap-1", G_TYPE_STRING, "foobar", NULL);
|
||||
gst_caps_append_structure (caps, s);
|
||||
s = gst_structure_from_string (H264_RTP_CAPS (97), NULL);
|
||||
gst_structure_set (s, "extmap-1", G_TYPE_STRING, "foobar", NULL);
|
||||
gst_caps_append_structure (caps, s);
|
||||
|
||||
direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY;
|
||||
g_signal_emit_by_name (t->webrtc1, "add-transceiver", direction, caps,
|
||||
&trans);
|
||||
gst_caps_unref (caps);
|
||||
fail_unless (trans != NULL);
|
||||
|
||||
t->on_negotiation_needed = NULL;
|
||||
t->on_pad_added = NULL;
|
||||
t->on_ice_candidate = NULL;
|
||||
|
||||
test_validate_sdp (t, &extmaps, NULL);
|
||||
|
||||
test_webrtc_free (t);
|
||||
}
|
||||
|
||||
GST_END_TEST;
|
||||
|
||||
/* In this test, we validate that trying to use different values
|
||||
* for the same extmap id in multiple formats in the caps of a
|
||||
* single transceiver errors out when creating the offer. */
|
||||
GST_START_TEST (test_codec_preferences_incompatible_extmaps)
|
||||
{
|
||||
struct test_webrtc *t = test_webrtc_new ();
|
||||
GstWebRTCRTPTransceiver *trans;
|
||||
GstWebRTCRTPTransceiverDirection direction;
|
||||
GstCaps *caps;
|
||||
GstStructure *s;
|
||||
|
||||
caps = gst_caps_new_empty ();
|
||||
|
||||
s = gst_structure_from_string (VP8_RTP_CAPS (96), NULL);
|
||||
gst_structure_set (s, "extmap-1", G_TYPE_STRING, "foobar", NULL);
|
||||
gst_caps_append_structure (caps, s);
|
||||
s = gst_structure_from_string (H264_RTP_CAPS (97), NULL);
|
||||
gst_structure_set (s, "extmap-1", G_TYPE_STRING, "foobaz", NULL);
|
||||
gst_caps_append_structure (caps, s);
|
||||
|
||||
direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY;
|
||||
g_signal_emit_by_name (t->webrtc1, "add-transceiver", direction, caps,
|
||||
&trans);
|
||||
gst_caps_unref (caps);
|
||||
fail_unless (trans != NULL);
|
||||
|
||||
t->on_negotiation_needed = NULL;
|
||||
t->on_pad_added = NULL;
|
||||
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_webrtc_free (t);
|
||||
}
|
||||
|
||||
GST_END_TEST;
|
||||
|
||||
/* In this test, we validate that extmap values must be of the correct type */
|
||||
GST_START_TEST (test_codec_preferences_invalid_extmap)
|
||||
{
|
||||
struct test_webrtc *t = test_webrtc_new ();
|
||||
GstWebRTCRTPTransceiver *trans;
|
||||
GstWebRTCRTPTransceiverDirection direction;
|
||||
GstCaps *caps;
|
||||
GstStructure *s;
|
||||
|
||||
caps = gst_caps_new_empty ();
|
||||
|
||||
s = gst_structure_from_string (VP8_RTP_CAPS (96), NULL);
|
||||
gst_structure_set (s, "extmap-1", G_TYPE_INT, 42, NULL);
|
||||
gst_caps_append_structure (caps, s);
|
||||
|
||||
direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY;
|
||||
g_signal_emit_by_name (t->webrtc1, "add-transceiver", direction, caps,
|
||||
&trans);
|
||||
gst_caps_unref (caps);
|
||||
fail_unless (trans != NULL);
|
||||
|
||||
t->on_negotiation_needed = NULL;
|
||||
t->on_pad_added = NULL;
|
||||
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_webrtc_free (t);
|
||||
}
|
||||
|
||||
GST_END_TEST;
|
||||
|
||||
GST_START_TEST (test_reject_request_pad)
|
||||
{
|
||||
struct test_webrtc *t = test_webrtc_new ();
|
||||
|
@ -4275,6 +4412,9 @@ webrtcbin_suite (void)
|
|||
tcase_add_test (tc, test_codec_preferences_negotiation_sinkpad);
|
||||
tcase_add_test (tc, test_codec_preferences_negotiation_srcpad);
|
||||
tcase_add_test (tc, test_codec_preferences_in_on_new_transceiver);
|
||||
tcase_add_test (tc, test_codec_preferences_no_duplicate_extmaps);
|
||||
tcase_add_test (tc, test_codec_preferences_incompatible_extmaps);
|
||||
tcase_add_test (tc, test_codec_preferences_invalid_extmap);
|
||||
if (sctpenc && sctpdec) {
|
||||
tcase_add_test (tc, test_data_channel_create);
|
||||
tcase_add_test (tc, test_data_channel_remote_notify);
|
||||
|
|
Loading…
Reference in a new issue