webrtcbin: implement support for group: BUNDLE

This commit is contained in:
Mathieu Duponchelle 2018-09-14 00:08:34 +02:00
parent 51d5db3f47
commit 9f684a2f81
7 changed files with 912 additions and 221 deletions

File diff suppressed because it is too large Load diff

View file

@ -83,6 +83,7 @@ struct _GstWebRTCBin
GstBin parent; GstBin parent;
GstElement *rtpbin; GstElement *rtpbin;
GstElement *rtpfunnel;
GstWebRTCSignalingState signaling_state; GstWebRTCSignalingState signaling_state;
GstWebRTCICEGatheringState ice_gathering_state; GstWebRTCICEGatheringState ice_gathering_state;
@ -94,6 +95,8 @@ struct _GstWebRTCBin
GstWebRTCSessionDescription *current_remote_description; GstWebRTCSessionDescription *current_remote_description;
GstWebRTCSessionDescription *pending_remote_description; GstWebRTCSessionDescription *pending_remote_description;
GstWebRTCBundlePolicy bundle_policy;
GstWebRTCBinPrivate *priv; GstWebRTCBinPrivate *priv;
}; };

View file

@ -128,6 +128,7 @@ transport_stream_finalize (GObject * object)
TransportStream *stream = TRANSPORT_STREAM (object); TransportStream *stream = TRANSPORT_STREAM (object);
g_array_free (stream->ptmap, TRUE); g_array_free (stream->ptmap, TRUE);
g_array_free (stream->remote_ssrcmap, TRUE);
G_OBJECT_CLASS (parent_class)->finalize (object); G_OBJECT_CLASS (parent_class)->finalize (object);
} }
@ -238,6 +239,7 @@ transport_stream_init (TransportStream * stream)
{ {
stream->ptmap = g_array_new (FALSE, TRUE, sizeof (PtMapItem)); stream->ptmap = g_array_new (FALSE, TRUE, sizeof (PtMapItem));
g_array_set_clear_func (stream->ptmap, (GDestroyNotify) clear_ptmap_item); g_array_set_clear_func (stream->ptmap, (GDestroyNotify) clear_ptmap_item);
stream->remote_ssrcmap = g_array_new (FALSE, TRUE, sizeof (SsrcMapItem));
} }
TransportStream * TransportStream *

View file

@ -37,6 +37,12 @@ typedef struct
GstCaps *caps; GstCaps *caps;
} PtMapItem; } PtMapItem;
typedef struct
{
guint32 ssrc;
guint media_idx;
} SsrcMapItem;
struct _TransportStream struct _TransportStream
{ {
GstObject parent; GstObject parent;
@ -54,6 +60,7 @@ struct _TransportStream
GstWebRTCDTLSTransport *rtcp_transport; GstWebRTCDTLSTransport *rtcp_transport;
GArray *ptmap; /* array of PtMapItem's */ GArray *ptmap; /* array of PtMapItem's */
GArray *remote_ssrcmap; /* array of SsrcMapItem's */
}; };
struct _TransportStreamClass struct _TransportStreamClass

View file

@ -281,11 +281,9 @@ gboolean
validate_sdp (GstWebRTCBin * webrtc, SDPSource source, validate_sdp (GstWebRTCBin * webrtc, SDPSource source,
GstWebRTCSessionDescription * sdp, GError ** error) GstWebRTCSessionDescription * sdp, GError ** error)
{ {
#if 0
const gchar *group, *bundle_ice_ufrag = NULL, *bundle_ice_pwd = NULL; const gchar *group, *bundle_ice_ufrag = NULL, *bundle_ice_pwd = NULL;
gchar **group_members = NULL; gchar **group_members = NULL;
gboolean is_bundle = FALSE; gboolean is_bundle = FALSE;
#endif
int i; int i;
if (!_check_valid_state_for_sdp_change (webrtc, source, sdp->type, error)) if (!_check_valid_state_for_sdp_change (webrtc, source, sdp->type, error))
@ -294,30 +292,21 @@ validate_sdp (GstWebRTCBin * webrtc, SDPSource source,
return FALSE; return FALSE;
/* not explicitly required /* not explicitly required
if (ICE && !_check_trickle_ice (sdp->sdp)) if (ICE && !_check_trickle_ice (sdp->sdp))
return FALSE; return FALSE;*/
group = gst_sdp_message_get_attribute_val (sdp->sdp, "group"); group = gst_sdp_message_get_attribute_val (sdp->sdp, "group");
is_bundle = g_str_has_prefix (group, "BUNDLE"); is_bundle = group && g_str_has_prefix (group, "BUNDLE");
if (is_bundle) if (is_bundle)
group_members = g_strsplit (&group[6], " ", -1);*/ group_members = g_strsplit (&group[6], " ", -1);
for (i = 0; i < gst_sdp_message_medias_len (sdp->sdp); i++) { for (i = 0; i < gst_sdp_message_medias_len (sdp->sdp); i++) {
const GstSDPMedia *media = gst_sdp_message_get_media (sdp->sdp, i); const GstSDPMedia *media = gst_sdp_message_get_media (sdp->sdp, i);
#if 0
const gchar *mid; const gchar *mid;
gboolean media_in_bundle = FALSE, first_media_in_bundle = FALSE; gboolean media_in_bundle = FALSE;
gboolean bundle_only = FALSE;
#endif
if (!_media_has_mid (media, i, error)) if (!_media_has_mid (media, i, error))
goto fail; goto fail;
#if 0
mid = gst_sdp_media_get_attribute_val (media, "mid"); mid = gst_sdp_media_get_attribute_val (media, "mid");
media_in_bundle = is_bundle && g_strv_contains (group_members, mid); media_in_bundle = is_bundle
if (media_in_bundle) && g_strv_contains ((const gchar **) group_members, mid);
bundle_only =
gst_sdp_media_get_attribute_val (media, "bundle-only") != NULL;
first_media_in_bundle = media_in_bundle
&& g_strcmp0 (mid, group_members[0]) == 0;
#endif
if (!_media_get_ice_ufrag (sdp->sdp, i)) { if (!_media_get_ice_ufrag (sdp->sdp, i)) {
g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP, g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
"media %u is missing or contains an empty \'ice-ufrag\' attribute", "media %u is missing or contains an empty \'ice-ufrag\' attribute",
@ -331,7 +320,6 @@ validate_sdp (GstWebRTCBin * webrtc, SDPSource source,
} }
if (!_media_has_setup (media, i, error)) if (!_media_has_setup (media, i, error))
goto fail; goto fail;
#if 0
/* check paramaters in bundle are the same */ /* check paramaters in bundle are the same */
if (media_in_bundle) { if (media_in_bundle) {
const gchar *ice_ufrag = const gchar *ice_ufrag =
@ -339,7 +327,7 @@ validate_sdp (GstWebRTCBin * webrtc, SDPSource source,
const gchar *ice_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd"); const gchar *ice_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd");
if (!bundle_ice_ufrag) if (!bundle_ice_ufrag)
bundle_ice_ufrag = ice_ufrag; bundle_ice_ufrag = ice_ufrag;
else if (!g_strcmp0 (bundle_ice_ufrag, ice_ufrag) != 0) { else if (g_strcmp0 (bundle_ice_ufrag, ice_ufrag) != 0) {
g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP, g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
"media %u has different ice-ufrag values in bundle. " "media %u has different ice-ufrag values in bundle. "
"%s != %s", i, bundle_ice_ufrag, ice_ufrag); "%s != %s", i, bundle_ice_ufrag, ice_ufrag);
@ -347,22 +335,21 @@ validate_sdp (GstWebRTCBin * webrtc, SDPSource source,
} }
if (!bundle_ice_pwd) { if (!bundle_ice_pwd) {
bundle_ice_pwd = ice_pwd; bundle_ice_pwd = ice_pwd;
} else if (g_strcmp0 (bundle_ice_pwd, ice_pwd) == 0) { } else if (g_strcmp0 (bundle_ice_pwd, ice_pwd) != 0) {
g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP, g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
"media %u has different ice-ufrag values in bundle. " "media %u has different ice-pwd values in bundle. "
"%s != %s", i, bundle_ice_ufrag, ice_ufrag); "%s != %s", i, bundle_ice_pwd, ice_pwd);
goto fail; goto fail;
} }
} }
#endif
} }
// g_strv_free (group_members); g_strfreev (group_members);
return TRUE; return TRUE;
fail: fail:
// g_strv_free (group_members); g_strfreev (group_members);
return FALSE; return FALSE;
} }

View file

@ -321,4 +321,22 @@ typedef enum /*< underscore_name=gst_webrtc_data_channel_state >*/
GST_WEBRTC_DATA_CHANNEL_STATE_CLOSED, GST_WEBRTC_DATA_CHANNEL_STATE_CLOSED,
} GstWebRTCDataChannelState; } GstWebRTCDataChannelState;
/**
* GstWebRTCBundlePolicy:
* GST_WEBRTC_BUNDLE_POLICY_NONE: none
* GST_WEBRTC_BUNDLE_POLICY_BALANCED: balanced
* GST_WEBRTC_BUNDLE_POLICY_MAX_COMPAT: max-compat
* GST_WEBRTC_BUNDLE_POLICY_MAX_BUNDLE: max-bundle
*
* See https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-24#section-4.1.1
* for more information.
*/
typedef enum /*<underscore_name=gst_webrtc_bundle_policy>*/
{
GST_WEBRTC_BUNDLE_POLICY_NONE,
GST_WEBRTC_BUNDLE_POLICY_BALANCED,
GST_WEBRTC_BUNDLE_POLICY_MAX_COMPAT,
GST_WEBRTC_BUNDLE_POLICY_MAX_BUNDLE,
} GstWebRTCBundlePolicy;
#endif /* __GST_WEBRTC_FWD_H__ */ #endif /* __GST_WEBRTC_FWD_H__ */

View file

@ -2070,6 +2070,336 @@ GST_START_TEST (test_data_channel_pre_negotiated)
GST_END_TEST; GST_END_TEST;
typedef struct
{
guint num_media;
guint num_active_media;
const gchar **bundled;
const gchar **bundled_only;
} BundleCheckData;
static gboolean
_parse_bundle (GstSDPMessage * sdp, GStrv * bundled)
{
const gchar *group;
gboolean ret = FALSE;
group = gst_sdp_message_get_attribute_val (sdp, "group");
if (group && g_str_has_prefix (group, "BUNDLE ")) {
*bundled = g_strsplit (group + strlen ("BUNDLE "), " ", 0);
if (!(*bundled)[0]) {
GST_ERROR
("Invalid format for BUNDLE group, expected at least one mid (%s)",
group);
goto done;
}
} else {
ret = TRUE;
goto done;
}
ret = TRUE;
done:
return ret;
}
static gboolean
_media_has_attribute_key (const GstSDPMedia * media, const gchar * key)
{
int i;
for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
if (g_strcmp0 (attr->key, key) == 0)
return TRUE;
}
return FALSE;
}
static void
_check_bundled_sdp_media (struct test_webrtc *t, GstElement * element,
GstWebRTCSessionDescription * sd, gpointer user_data)
{
gchar **bundled = NULL;
BundleCheckData *data = (BundleCheckData *) user_data;
guint i;
guint active_media;
fail_unless_equals_int (gst_sdp_message_medias_len (sd->sdp),
data->num_media);
fail_unless (_parse_bundle (sd->sdp, &bundled));
if (!bundled) {
fail_unless_equals_int (g_strv_length ((GStrv) data->bundled), 0);
} else {
fail_unless_equals_int (g_strv_length (bundled),
g_strv_length ((GStrv) data->bundled));
}
for (i = 0; data->bundled[i]; i++) {
fail_unless (g_strv_contains ((const gchar **) bundled, data->bundled[i]));
}
active_media = 0;
for (i = 0; i < gst_sdp_message_medias_len (sd->sdp); i++) {
const GstSDPMedia *media = gst_sdp_message_get_media (sd->sdp, i);
const gchar *mid = gst_sdp_media_get_attribute_val (media, "mid");
if (g_strv_contains ((const gchar **) data->bundled_only, mid))
fail_unless (_media_has_attribute_key (media, "bundle-only"));
if (gst_sdp_media_get_port (media) != 0)
active_media += 1;
}
fail_unless_equals_int (active_media, data->num_active_media);
if (bundled)
g_strfreev (bundled);
}
GST_START_TEST (test_bundle_audio_video_max_bundle_max_bundle)
{
struct test_webrtc *t = create_audio_video_test ();
const gchar *bundle[] = { "audio0", "video1", NULL };
const gchar *offer_bundle_only[] = { "video1", NULL };
const gchar *answer_bundle_only[] = { NULL };
/* We set a max-bundle policy on the offering webrtcbin,
* this means that all the offered medias should be part
* of the group:BUNDLE attribute, and they should be marked
* as bundle-only
*/
BundleCheckData offer_data = {
2,
1,
bundle,
offer_bundle_only,
};
/* We also set a max-bundle policy on the answering webrtcbin,
* this means that all the offered medias should be part
* of the group:BUNDLE attribute, but need not be marked
* as bundle-only.
*/
BundleCheckData answer_data = {
2,
2,
bundle,
answer_bundle_only,
};
struct validate_sdp offer = { _check_bundled_sdp_media, &offer_data };
struct validate_sdp answer = { _check_bundled_sdp_media, &answer_data };
gst_util_set_object_arg (G_OBJECT (t->webrtc1), "bundle-policy",
"max-bundle");
gst_util_set_object_arg (G_OBJECT (t->webrtc2), "bundle-policy",
"max-bundle");
t->on_negotiation_needed = NULL;
t->offer_data = &offer;
t->on_offer_created = validate_sdp;
t->answer_data = &answer;
t->on_answer_created = validate_sdp;
t->on_ice_candidate = NULL;
fail_if (gst_element_set_state (t->webrtc1,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
fail_if (gst_element_set_state (t->webrtc2,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
test_webrtc_create_offer (t, t->webrtc1);
test_webrtc_wait_for_answer_error_eos (t);
fail_unless_equals_int (STATE_ANSWER_CREATED, t->state);
test_webrtc_free (t);
}
GST_END_TEST;
GST_START_TEST (test_bundle_audio_video_max_compat_max_bundle)
{
struct test_webrtc *t = create_audio_video_test ();
const gchar *bundle[] = { "audio0", "video1", NULL };
const gchar *bundle_only[] = { NULL };
/* We set a max-compat policy on the offering webrtcbin,
* this means that all the offered medias should be part
* of the group:BUNDLE attribute, and they should *not* be marked
* as bundle-only
*/
BundleCheckData offer_data = {
2,
2,
bundle,
bundle_only,
};
/* We set a max-bundle policy on the answering webrtcbin,
* this means that all the offered medias should be part
* of the group:BUNDLE attribute, but need not be marked
* as bundle-only.
*/
BundleCheckData answer_data = {
2,
2,
bundle,
bundle_only,
};
struct validate_sdp offer = { _check_bundled_sdp_media, &offer_data };
struct validate_sdp answer = { _check_bundled_sdp_media, &answer_data };
gst_util_set_object_arg (G_OBJECT (t->webrtc1), "bundle-policy",
"max-compat");
gst_util_set_object_arg (G_OBJECT (t->webrtc2), "bundle-policy",
"max-bundle");
t->on_negotiation_needed = NULL;
t->offer_data = &offer;
t->on_offer_created = validate_sdp;
t->answer_data = &answer;
t->on_answer_created = validate_sdp;
t->on_ice_candidate = NULL;
fail_if (gst_element_set_state (t->webrtc1,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
fail_if (gst_element_set_state (t->webrtc2,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
test_webrtc_create_offer (t, t->webrtc1);
test_webrtc_wait_for_answer_error_eos (t);
fail_unless_equals_int (STATE_ANSWER_CREATED, t->state);
test_webrtc_free (t);
}
GST_END_TEST;
GST_START_TEST (test_bundle_audio_video_max_bundle_none)
{
struct test_webrtc *t = create_audio_video_test ();
const gchar *offer_bundle[] = { "audio0", "video1", NULL };
const gchar *offer_bundle_only[] = { "video1", NULL };
const gchar *answer_bundle[] = { NULL };
const gchar *answer_bundle_only[] = { NULL };
/* We set a max-bundle policy on the offering webrtcbin,
* this means that all the offered medias should be part
* of the group:BUNDLE attribute, and they should be marked
* as bundle-only
*/
BundleCheckData offer_data = {
2,
1,
offer_bundle,
offer_bundle_only,
};
/* We set a none policy on the answering webrtcbin,
* this means that the answer should contain no bundled
* medias, and as the bundle-policy of the offering webrtcbin
* is set to max-bundle, only one media should be active.
*/
BundleCheckData answer_data = {
2,
1,
answer_bundle,
answer_bundle_only,
};
struct validate_sdp offer = { _check_bundled_sdp_media, &offer_data };
struct validate_sdp answer = { _check_bundled_sdp_media, &answer_data };
gst_util_set_object_arg (G_OBJECT (t->webrtc1), "bundle-policy",
"max-bundle");
gst_util_set_object_arg (G_OBJECT (t->webrtc2), "bundle-policy", "none");
t->on_negotiation_needed = NULL;
t->offer_data = &offer;
t->on_offer_created = validate_sdp;
t->answer_data = &answer;
t->on_answer_created = validate_sdp;
t->on_ice_candidate = NULL;
fail_if (gst_element_set_state (t->webrtc1,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
fail_if (gst_element_set_state (t->webrtc2,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
test_webrtc_create_offer (t, t->webrtc1);
test_webrtc_wait_for_answer_error_eos (t);
fail_unless_equals_int (STATE_ANSWER_CREATED, t->state);
test_webrtc_free (t);
}
GST_END_TEST;
GST_START_TEST (test_bundle_audio_video_data)
{
struct test_webrtc *t = create_audio_video_test ();
const gchar *bundle[] = { "audio0", "video1", "application2", NULL };
const gchar *offer_bundle_only[] = { "video1", "application2", NULL };
const gchar *answer_bundle_only[] = { NULL };
GObject *channel = NULL;
/* We set a max-bundle policy on the offering webrtcbin,
* this means that all the offered medias should be part
* of the group:BUNDLE attribute, and they should be marked
* as bundle-only
*/
BundleCheckData offer_data = {
3,
1,
bundle,
offer_bundle_only,
};
/* We also set a max-bundle policy on the answering webrtcbin,
* this means that all the offered medias should be part
* of the group:BUNDLE attribute, but need not be marked
* as bundle-only.
*/
BundleCheckData answer_data = {
3,
3,
bundle,
answer_bundle_only,
};
struct validate_sdp offer = { _check_bundled_sdp_media, &offer_data };
struct validate_sdp answer = { _check_bundled_sdp_media, &answer_data };
gst_util_set_object_arg (G_OBJECT (t->webrtc1), "bundle-policy",
"max-bundle");
gst_util_set_object_arg (G_OBJECT (t->webrtc2), "bundle-policy",
"max-bundle");
t->on_negotiation_needed = NULL;
t->offer_data = &offer;
t->on_offer_created = validate_sdp;
t->answer_data = &answer;
t->on_answer_created = validate_sdp;
t->on_ice_candidate = NULL;
fail_if (gst_element_set_state (t->webrtc1,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
fail_if (gst_element_set_state (t->webrtc2,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
g_signal_emit_by_name (t->webrtc1, "create-data-channel", "label", NULL,
&channel);
test_webrtc_create_offer (t, t->webrtc1);
test_webrtc_wait_for_answer_error_eos (t);
fail_unless_equals_int (STATE_ANSWER_CREATED, t->state);
g_object_unref (channel);
test_webrtc_free (t);
}
GST_END_TEST;
static Suite * static Suite *
webrtcbin_suite (void) webrtcbin_suite (void)
{ {
@ -2101,6 +2431,9 @@ webrtcbin_suite (void)
tcase_add_test (tc, test_add_recvonly_transceiver); tcase_add_test (tc, test_add_recvonly_transceiver);
tcase_add_test (tc, test_recvonly_sendonly); tcase_add_test (tc, test_recvonly_sendonly);
tcase_add_test (tc, test_payload_types); tcase_add_test (tc, test_payload_types);
tcase_add_test (tc, test_bundle_audio_video_max_bundle_max_bundle);
tcase_add_test (tc, test_bundle_audio_video_max_bundle_none);
tcase_add_test (tc, test_bundle_audio_video_max_compat_max_bundle);
if (sctpenc && sctpdec) { if (sctpenc && sctpdec) {
tcase_add_test (tc, test_data_channel_create); tcase_add_test (tc, test_data_channel_create);
tcase_add_test (tc, test_data_channel_remote_notify); tcase_add_test (tc, test_data_channel_remote_notify);
@ -2110,6 +2443,7 @@ webrtcbin_suite (void)
tcase_add_test (tc, test_data_channel_low_threshold); tcase_add_test (tc, test_data_channel_low_threshold);
tcase_add_test (tc, test_data_channel_max_message_size); tcase_add_test (tc, test_data_channel_max_message_size);
tcase_add_test (tc, test_data_channel_pre_negotiated); tcase_add_test (tc, test_data_channel_pre_negotiated);
tcase_add_test (tc, test_bundle_audio_video_data);
} else { } else {
GST_WARNING ("Some required elements were not found. " GST_WARNING ("Some required elements were not found. "
"All datachannel are disabled. sctpenc %p, sctpdec %p", sctpenc, "All datachannel are disabled. sctpenc %p, sctpdec %p", sctpenc,