webrtc: Initial support for stream addition/removal

Limitations:
- No transport changes at all (ICE, DTLS)
- Codec changes are untested and probably don't work
- Stream removal doesn't remove transports (i.e. non-bundled transports
  will stay around until webrtcbin is shutdown)
- Unified Plan SDP only. No Plan-B support.
This commit is contained in:
Matthew Waters 2018-11-28 17:23:31 +11:00
parent 015cb75f66
commit 177aa22bcd
16 changed files with 1623 additions and 381 deletions

1
.gitignore vendored
View file

@ -75,6 +75,7 @@ gst*orc.h
/tests/examples/webrtc/webrtc
/tests/examples/webrtc/webrtcbidirectional
/tests/examples/webrtc/webrtcswap
/tests/examples/webrtc/webrtcrenego
/tests/examples/webrtc/webrtctransceiver
Build

File diff suppressed because it is too large Load diff

View file

@ -131,6 +131,8 @@ struct _GstWebRTCBinPrivate
/* count of the number of media streams we've offered for uniqueness */
/* FIXME: overflow? */
guint media_counter;
/* the number of times create_offer has been called for the version field */
guint offer_count;
GstStructure *stats;
};

View file

@ -75,6 +75,36 @@ transport_stream_get_pt (TransportStream * stream, const gchar * encoding_name)
return ret;
}
int *
transport_stream_get_all_pt (TransportStream * stream,
const gchar * encoding_name, gsize * pt_len)
{
guint i;
gsize ret_i = 0;
gsize ret_size = 8;
int *ret = NULL;
for (i = 0; i < stream->ptmap->len; i++) {
PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i);
if (!gst_caps_is_empty (item->caps)) {
GstStructure *s = gst_caps_get_structure (item->caps, 0);
if (!g_strcmp0 (gst_structure_get_string (s, "encoding-name"),
encoding_name)) {
if (!ret)
ret = g_new0 (int, ret_size);
if (ret_i >= ret_size) {
ret_size *= 2;
ret = g_realloc_n (ret, ret_size, sizeof (int));
}
ret[ret_i++] = item->pt;
}
}
}
*pt_len = ret_i;
return ret;
}
static void
transport_stream_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
@ -152,6 +182,14 @@ transport_stream_dispose (GObject * object)
gst_object_unref (stream->rtcp_transport);
stream->rtcp_transport = NULL;
if (stream->rtxsend)
gst_object_unref (stream->rtxsend);
stream->rtxsend = NULL;
if (stream->rtxreceive)
gst_object_unref (stream->rtxreceive);
stream->rtxreceive = NULL;
GST_OBJECT_PARENT (object) = NULL;
G_OBJECT_CLASS (parent_class)->dispose (object);

View file

@ -61,6 +61,10 @@ struct _TransportStream
GArray *ptmap; /* array of PtMapItem's */
GArray *remote_ssrcmap; /* array of SsrcMapItem's */
gboolean output_connected; /* whether receive bin is connected to rtpbin */
GstElement *rtxsend;
GstElement *rtxreceive;
};
struct _TransportStreamClass
@ -72,6 +76,9 @@ TransportStream * transport_stream_new (GstWebRTCBin * webrtc,
guint session_id);
int transport_stream_get_pt (TransportStream * stream,
const gchar * encoding_name);
int * transport_stream_get_all_pt (TransportStream * stream,
const gchar * encoding_name,
gsize * pt_len);
GstCaps * transport_stream_get_caps_for_pt (TransportStream * stream,
guint pt);

View file

@ -21,6 +21,8 @@
# include "config.h"
#endif
#include <stdlib.h>
#include "utils.h"
#include "gstwebrtcbin.h"
@ -53,7 +55,22 @@ _find_pad_template (GstElement * element, GstPadDirection direction,
}
GstSDPMessage *
_get_latest_sdp (GstWebRTCBin * webrtc)
_get_latest_offer (GstWebRTCBin * webrtc)
{
if (webrtc->current_local_description &&
webrtc->current_local_description->type == GST_WEBRTC_SDP_TYPE_OFFER) {
return webrtc->current_local_description->sdp;
}
if (webrtc->current_remote_description &&
webrtc->current_remote_description->type == GST_WEBRTC_SDP_TYPE_OFFER) {
return webrtc->current_remote_description->sdp;
}
return NULL;
}
GstSDPMessage *
_get_latest_answer (GstWebRTCBin * webrtc)
{
if (webrtc->current_local_description &&
webrtc->current_local_description->type == GST_WEBRTC_SDP_TYPE_ANSWER) {
@ -63,14 +80,19 @@ _get_latest_sdp (GstWebRTCBin * webrtc)
webrtc->current_remote_description->type == GST_WEBRTC_SDP_TYPE_ANSWER) {
return webrtc->current_remote_description->sdp;
}
if (webrtc->current_local_description &&
webrtc->current_local_description->type == GST_WEBRTC_SDP_TYPE_OFFER) {
return webrtc->current_local_description->sdp;
}
if (webrtc->current_remote_description &&
webrtc->current_remote_description->type == GST_WEBRTC_SDP_TYPE_OFFER) {
return webrtc->current_remote_description->sdp;
}
return NULL;
}
GstSDPMessage *
_get_latest_sdp (GstWebRTCBin * webrtc)
{
GstSDPMessage *ret = NULL;
if ((ret = _get_latest_answer (webrtc)))
return ret;
if ((ret = _get_latest_offer (webrtc)))
return ret;
return NULL;
}
@ -142,3 +164,31 @@ _g_checksum_to_webrtc_string (GChecksumType type)
return NULL;
}
}
GstCaps *
_rtp_caps_from_media (const GstSDPMedia * media)
{
GstCaps *ret;
int i, j;
ret = gst_caps_new_empty ();
for (i = 0; i < gst_sdp_media_formats_len (media); i++) {
guint pt = atoi (gst_sdp_media_get_format (media, i));
GstCaps *caps;
caps = gst_sdp_media_get_caps_from_media (media, pt);
/* gst_sdp_media_get_caps_from_media() produces caps with name
* "application/x-unknown" which will fail intersection with
* "application/x-rtp" caps so mangle the returns caps to have the
* correct name here */
for (j = 0; j < gst_caps_get_size (caps); j++) {
GstStructure *s = gst_caps_get_structure (caps, j);
gst_structure_set_name (s, "application/x-rtp");
}
gst_caps_append (ret, caps);
}
return ret;
}

View file

@ -47,6 +47,8 @@ GstPadTemplate * _find_pad_template (GstElement * element,
const gchar * name);
GstSDPMessage * _get_latest_sdp (GstWebRTCBin * webrtc);
GstSDPMessage * _get_latest_offer (GstWebRTCBin * webrtc);
GstSDPMessage * _get_latest_answer (GstWebRTCBin * webrtc);
GstWebRTCICEStream * _find_ice_stream_for_session (GstWebRTCBin * webrtc,
guint session_id);
@ -74,6 +76,8 @@ G_GNUC_INTERNAL
gchar * _enum_value_to_string (GType type, guint value);
G_GNUC_INTERNAL
const gchar * _g_checksum_to_webrtc_string (GChecksumType type);
G_GNUC_INTERNAL
GstCaps * _rtp_caps_from_media (const GstSDPMedia * media);
G_END_DECLS

View file

@ -212,7 +212,7 @@ _media_has_mid (const GstSDPMedia * media, guint media_idx, GError ** error)
return TRUE;
}
static const gchar *
const gchar *
_media_get_ice_ufrag (const GstSDPMessage * msg, guint media_idx)
{
const gchar *ice_ufrag;
@ -227,7 +227,7 @@ _media_get_ice_ufrag (const GstSDPMessage * msg, guint media_idx)
return ice_ufrag;
}
static const gchar *
const gchar *
_media_get_ice_pwd (const GstSDPMessage * msg, guint media_idx)
{
const gchar *ice_pwd;
@ -437,11 +437,13 @@ _media_replace_direction (GstSDPMedia * media,
if (g_strcmp0 (attr->key, "sendonly") == 0
|| g_strcmp0 (attr->key, "sendrecv") == 0
|| g_strcmp0 (attr->key, "recvonly") == 0) {
|| g_strcmp0 (attr->key, "recvonly") == 0
|| g_strcmp0 (attr->key, "inactive") == 0) {
GstSDPAttribute new_attr = { 0, };
GST_TRACE ("replace %s with %s", attr->key, dir_str);
gst_sdp_attribute_set (&new_attr, dir_str, "");
gst_sdp_media_replace_attribute (media, i, &new_attr);
g_free (dir_str);
return;
}
}
@ -768,6 +770,21 @@ _message_media_is_datachannel (const GstSDPMessage * msg, guint media_id)
return TRUE;
}
guint
_message_get_datachannel_index (const GstSDPMessage * msg)
{
guint i;
for (i = 0; i < gst_sdp_message_medias_len (msg); i++) {
if (_message_media_is_datachannel (msg, i)) {
g_assert (i < G_MAXUINT);
return i;
}
}
return G_MAXUINT;
}
void
_get_ice_credentials_from_sdp_media (const GstSDPMessage * sdp, guint media_idx,
gchar ** ufrag, gchar ** pwd)
@ -833,6 +850,8 @@ _parse_bundle (GstSDPMessage * sdp, GStrv * bundled)
if (!(*bundled)[0]) {
GST_ERROR ("Invalid format for BUNDLE group, expected at least "
"one mid (%s)", group);
g_strfreev (*bundled);
*bundled = NULL;
goto done;
}
} else {

View file

@ -89,6 +89,8 @@ void _get_ice_credentials_from_sdp_media (con
G_GNUC_INTERNAL
gboolean _message_media_is_datachannel (const GstSDPMessage * msg,
guint media_id);
G_GNUC_INTERNAL
guint _message_get_datachannel_index (const GstSDPMessage * msg);
G_GNUC_INTERNAL
gboolean _get_bundle_index (GstSDPMessage * sdp,
@ -98,4 +100,11 @@ G_GNUC_INTERNAL
gboolean _parse_bundle (GstSDPMessage * sdp,
GStrv * bundled);
G_GNUC_INTERNAL
const gchar * _media_get_ice_pwd (const GstSDPMessage * msg,
guint media_idx);
G_GNUC_INTERNAL
const gchar * _media_get_ice_ufrag (const GstSDPMessage * msg,
guint media_idx);
#endif /* __WEBRTC_UTILS_H__ */

View file

@ -25,9 +25,14 @@
#include "utils.h"
#include "webrtctransceiver.h"
#define GST_CAT_DEFAULT webrtc_transceiver_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
#define webrtc_transceiver_parent_class parent_class
G_DEFINE_TYPE (WebRTCTransceiver, webrtc_transceiver,
GST_TYPE_WEBRTC_RTP_TRANSCEIVER);
G_DEFINE_TYPE_WITH_CODE (WebRTCTransceiver, webrtc_transceiver,
GST_TYPE_WEBRTC_RTP_TRANSCEIVER,
GST_DEBUG_CATEGORY_INIT (webrtc_transceiver_debug,
"webrtctransceiver", 0, "webrtctransceiver"););
#define DEFAULT_FEC_TYPE GST_WEBRTC_FEC_TYPE_NONE
#define DEFAULT_DO_NACK FALSE
@ -172,6 +177,8 @@ webrtc_transceiver_finalize (GObject * object)
gst_structure_free (trans->local_rtx_ssrc_map);
trans->local_rtx_ssrc_map = NULL;
gst_caps_replace (&trans->last_configured_caps, NULL);
G_OBJECT_CLASS (parent_class)->finalize (object);
}

View file

@ -44,6 +44,8 @@ struct _WebRTCTransceiver
GstWebRTCFECType fec_type;
guint fec_percentage;
gboolean do_nack;
GstCaps *last_configured_caps;
};
struct _WebRTCTransceiverClass

View file

@ -39,7 +39,7 @@ GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstWebRTCRTPTransceiver,
gst_webrtc_rtp_transceiver, GST_TYPE_OBJECT,
GST_DEBUG_CATEGORY_INIT (gst_webrtc_rtp_transceiver_debug,
"webrtctransceiver", 0, "webrtctransceiver");
"webrtcrtptransceiver", 0, "webrtcrtptransceiver");
);
enum

View file

@ -41,6 +41,8 @@
#define TEST_GET_OFFEROR(t) (TEST_IS_OFFER_ELEMENT(t, t->webrtc1) ? (t)->webrtc1 : t->webrtc2)
#define TEST_GET_ANSWERER(t) (TEST_IS_OFFER_ELEMENT(t, t->webrtc1) ? (t)->webrtc2 : t->webrtc1)
#define TEST_SDP_IS_LOCAL(t, e, d) ((TEST_IS_OFFER_ELEMENT (t, e) ^ ((d)->type == GST_WEBRTC_SDP_TYPE_OFFER)) == 0)
typedef enum
{
STATE_NEW,
@ -626,12 +628,12 @@ _pad_added_fakesink (struct test_webrtc *t, GstElement * element,
}
static void
_count_num_sdp_media (struct test_webrtc *t, GstElement * element,
GstWebRTCSessionDescription * desc, gpointer user_data)
on_negotiation_needed_hit (struct test_webrtc *t, GstElement * element,
gpointer user_data)
{
guint expected = GPOINTER_TO_UINT (user_data);
guint *flag = (guint *) user_data;
fail_unless_equals_int (gst_sdp_message_medias_len (desc->sdp), expected);
*flag = 1;
}
typedef void (*ValidateSDPFunc) (struct test_webrtc * t, GstElement * element,
@ -645,6 +647,9 @@ struct validate_sdp
struct validate_sdp *next;
};
#define VAL_SDP_INIT(name,func,data,next) \
struct validate_sdp name = { func, data, next }
static GstWebRTCSessionDescription *
_check_validate_sdp (struct test_webrtc *t, GstElement * element,
GstPromise * promise, gpointer user_data)
@ -698,13 +703,20 @@ test_validate_sdp (struct test_webrtc *t, struct validate_sdp *offer,
fail_unless (t->state == STATE_ANSWER_CREATED);
}
static void
_count_num_sdp_media (struct test_webrtc *t, GstElement * element,
GstWebRTCSessionDescription * desc, gpointer user_data)
{
guint expected = GPOINTER_TO_UINT (user_data);
fail_unless_equals_int (gst_sdp_message_medias_len (desc->sdp), expected);
}
GST_START_TEST (test_sdp_no_media)
{
struct test_webrtc *t = test_webrtc_new ();
struct validate_sdp offer =
{ _count_num_sdp_media, GUINT_TO_POINTER (0), NULL };
struct validate_sdp answer =
{ _count_num_sdp_media, GUINT_TO_POINTER (0), NULL };
VAL_SDP_INIT (offer, _count_num_sdp_media, GUINT_TO_POINTER (0), NULL);
VAL_SDP_INIT (answer, _count_num_sdp_media, GUINT_TO_POINTER (0), NULL);
/* check that a no stream connection creates 0 media sections */
@ -756,10 +768,8 @@ create_audio_test (void)
GST_START_TEST (test_audio)
{
struct test_webrtc *t = create_audio_test ();
struct validate_sdp offer =
{ _count_num_sdp_media, GUINT_TO_POINTER (1), NULL };
struct validate_sdp answer =
{ _count_num_sdp_media, GUINT_TO_POINTER (1), NULL };
VAL_SDP_INIT (offer, _count_num_sdp_media, GUINT_TO_POINTER (1), NULL);
VAL_SDP_INIT (answer, _count_num_sdp_media, GUINT_TO_POINTER (1), NULL);
/* check that a single stream connection creates the associated number
* of media sections */
@ -786,10 +796,8 @@ create_audio_video_test (void)
GST_START_TEST (test_audio_video)
{
struct test_webrtc *t = create_audio_video_test ();
struct validate_sdp offer =
{ _count_num_sdp_media, GUINT_TO_POINTER (2), NULL };
struct validate_sdp answer =
{ _count_num_sdp_media, GUINT_TO_POINTER (2), NULL };
VAL_SDP_INIT (offer, _count_num_sdp_media, GUINT_TO_POINTER (2), NULL);
VAL_SDP_INIT (answer, _count_num_sdp_media, GUINT_TO_POINTER (2), NULL);
/* check that a dual stream connection creates the associated number
* of media sections */
@ -850,15 +858,14 @@ GST_START_TEST (test_media_direction)
struct test_webrtc *t = create_audio_video_test ();
const gchar *expected_offer[] = { "sendrecv", "sendrecv" };
const gchar *expected_answer[] = { "sendrecv", "recvonly" };
struct validate_sdp offer_direction =
{ on_sdp_media_direction, expected_offer, NULL };
struct validate_sdp offer =
{ _count_num_sdp_media, GUINT_TO_POINTER (2), &offer_direction };
struct validate_sdp answer_direction =
{ on_sdp_media_direction, expected_answer, NULL };
struct validate_sdp answer =
{ _count_num_sdp_media, GUINT_TO_POINTER (2), &answer_direction };
GstHarness *h;
VAL_SDP_INIT (offer_direction, on_sdp_media_direction, expected_offer, NULL);
VAL_SDP_INIT (offer, _count_num_sdp_media, GUINT_TO_POINTER (2),
&offer_direction);
VAL_SDP_INIT (answer_direction, on_sdp_media_direction, expected_answer,
NULL);
VAL_SDP_INIT (answer, _count_num_sdp_media, GUINT_TO_POINTER (2),
&answer_direction);
/* check the default media directions for transceivers */
@ -905,9 +912,8 @@ on_sdp_media_payload_types (struct test_webrtc *t, GstElement * element,
GST_START_TEST (test_payload_types)
{
struct test_webrtc *t = create_audio_video_test ();
struct validate_sdp payloads = { on_sdp_media_payload_types, NULL, NULL };
struct validate_sdp offer =
{ _count_num_sdp_media, GUINT_TO_POINTER (2), &payloads };
VAL_SDP_INIT (payloads, on_sdp_media_payload_types, NULL, NULL);
VAL_SDP_INIT (offer, _count_num_sdp_media, GUINT_TO_POINTER (2), &payloads);
GstWebRTCRTPTransceiver *trans;
GArray *transceivers;
@ -956,8 +962,8 @@ GST_START_TEST (test_media_setup)
struct test_webrtc *t = create_audio_test ();
const gchar *expected_offer[] = { "actpass" };
const gchar *expected_answer[] = { "active" };
struct validate_sdp offer = { on_sdp_media_setup, expected_offer, NULL };
struct validate_sdp answer = { on_sdp_media_setup, expected_answer, NULL };
VAL_SDP_INIT (offer, on_sdp_media_setup, expected_offer, NULL);
VAL_SDP_INIT (answer, on_sdp_media_setup, expected_answer, NULL);
/* check the default dtls setup negotiation values */
test_validate_sdp (t, &offer, &answer);
@ -1333,9 +1339,8 @@ GST_START_TEST (test_add_recvonly_transceiver)
GstWebRTCRTPTransceiver *trans;
const gchar *expected_offer[] = { "recvonly" };
const gchar *expected_answer[] = { "sendonly" };
struct validate_sdp offer = { on_sdp_media_direction, expected_offer, NULL };
struct validate_sdp answer =
{ on_sdp_media_direction, expected_answer, NULL };
VAL_SDP_INIT (offer, on_sdp_media_direction, expected_offer, NULL);
VAL_SDP_INIT (answer, on_sdp_media_direction, expected_answer, NULL);
GstCaps *caps;
GstHarness *h;
@ -1372,9 +1377,8 @@ GST_START_TEST (test_recvonly_sendonly)
GstWebRTCRTPTransceiver *trans;
const gchar *expected_offer[] = { "recvonly", "sendonly" };
const gchar *expected_answer[] = { "sendonly", "recvonly" };
struct validate_sdp offer = { on_sdp_media_direction, expected_offer, NULL };
struct validate_sdp answer =
{ on_sdp_media_direction, expected_answer, NULL };
VAL_SDP_INIT (offer, on_sdp_media_direction, expected_offer, NULL);
VAL_SDP_INIT (answer, on_sdp_media_direction, expected_answer, NULL);
GstCaps *caps;
GstHarness *h;
GArray *transceivers;
@ -1446,8 +1450,8 @@ GST_START_TEST (test_data_channel_create)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel = NULL;
struct validate_sdp offer = { on_sdp_has_datachannel, NULL, NULL };
struct validate_sdp answer = { on_sdp_has_datachannel, NULL, NULL };
VAL_SDP_INIT (offer, on_sdp_has_datachannel, NULL, NULL);
VAL_SDP_INIT (answer, on_sdp_has_datachannel, NULL, NULL);
gchar *label;
t->on_negotiation_needed = NULL;
@ -1500,8 +1504,8 @@ GST_START_TEST (test_data_channel_remote_notify)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel = NULL;
struct validate_sdp offer = { on_sdp_has_datachannel, NULL, NULL };
struct validate_sdp answer = { on_sdp_has_datachannel, NULL, NULL };
VAL_SDP_INIT (offer, on_sdp_has_datachannel, NULL, NULL);
VAL_SDP_INIT (answer, on_sdp_has_datachannel, NULL, NULL);
t->on_negotiation_needed = NULL;
t->offer_data = &offer;
@ -1575,8 +1579,8 @@ GST_START_TEST (test_data_channel_transfer_string)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel = NULL;
struct validate_sdp offer = { on_sdp_has_datachannel, NULL, NULL };
struct validate_sdp answer = { on_sdp_has_datachannel, NULL, NULL };
VAL_SDP_INIT (offer, on_sdp_has_datachannel, NULL, NULL);
VAL_SDP_INIT (answer, on_sdp_has_datachannel, NULL, NULL);
t->on_negotiation_needed = NULL;
t->offer_data = &offer;
@ -1657,8 +1661,8 @@ GST_START_TEST (test_data_channel_transfer_data)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel = NULL;
struct validate_sdp offer = { on_sdp_has_datachannel, NULL, NULL };
struct validate_sdp answer = { on_sdp_has_datachannel, NULL, NULL };
VAL_SDP_INIT (offer, on_sdp_has_datachannel, NULL, NULL);
VAL_SDP_INIT (answer, on_sdp_has_datachannel, NULL, NULL);
t->on_negotiation_needed = NULL;
t->offer_data = &offer;
@ -1715,8 +1719,8 @@ GST_START_TEST (test_data_channel_create_after_negotiate)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel = NULL;
struct validate_sdp offer = { on_sdp_has_datachannel, NULL, NULL };
struct validate_sdp answer = { on_sdp_has_datachannel, NULL, NULL };
VAL_SDP_INIT (offer, on_sdp_has_datachannel, NULL, NULL);
VAL_SDP_INIT (answer, on_sdp_has_datachannel, NULL, NULL);
t->on_negotiation_needed = NULL;
t->offer_data = &offer;
@ -1776,8 +1780,8 @@ GST_START_TEST (test_data_channel_low_threshold)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel = NULL;
struct validate_sdp offer = { on_sdp_has_datachannel, NULL, NULL };
struct validate_sdp answer = { on_sdp_has_datachannel, NULL, NULL };
VAL_SDP_INIT (offer, on_sdp_has_datachannel, NULL, NULL);
VAL_SDP_INIT (answer, on_sdp_has_datachannel, NULL, NULL);
t->on_negotiation_needed = NULL;
t->offer_data = &offer;
@ -1849,8 +1853,8 @@ GST_START_TEST (test_data_channel_max_message_size)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel = NULL;
struct validate_sdp offer = { on_sdp_has_datachannel, NULL, NULL };
struct validate_sdp answer = { on_sdp_has_datachannel, NULL, NULL };
VAL_SDP_INIT (offer, on_sdp_has_datachannel, NULL, NULL);
VAL_SDP_INIT (answer, on_sdp_has_datachannel, NULL, NULL);
t->on_negotiation_needed = NULL;
t->offer_data = &offer;
@ -1904,8 +1908,8 @@ GST_START_TEST (test_data_channel_pre_negotiated)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel1 = NULL, *channel2 = NULL;
struct validate_sdp offer = { on_sdp_has_datachannel, NULL, NULL };
struct validate_sdp answer = { on_sdp_has_datachannel, NULL, NULL };
VAL_SDP_INIT (offer, on_sdp_has_datachannel, NULL, NULL);
VAL_SDP_INIT (answer, on_sdp_has_datachannel, NULL, NULL);
GstStructure *s;
gint n_ready = 0;
@ -2028,17 +2032,16 @@ GST_START_TEST (test_bundle_audio_video_max_bundle_max_bundle)
const gchar *offer_bundle_only[] = { "video1", NULL };
const gchar *answer_bundle_only[] = { NULL };
struct validate_sdp count =
{ _count_num_sdp_media, GUINT_TO_POINTER (2), NULL };
struct validate_sdp bundle_tag = { _check_bundle_tag, bundle, &count };
struct validate_sdp offer_non_reject =
{ _count_non_rejected_media, GUINT_TO_POINTER (1), &bundle_tag };
struct validate_sdp answer_non_reject =
{ _count_non_rejected_media, GUINT_TO_POINTER (2), &bundle_tag };
struct validate_sdp offer =
{ _check_bundle_only_media, &offer_bundle_only, &offer_non_reject };
struct validate_sdp answer =
{ _check_bundle_only_media, &answer_bundle_only, &answer_non_reject };
VAL_SDP_INIT (count, _count_num_sdp_media, GUINT_TO_POINTER (2), NULL);
VAL_SDP_INIT (bundle_tag, _check_bundle_tag, bundle, &count);
VAL_SDP_INIT (offer_non_reject, _count_non_rejected_media,
GUINT_TO_POINTER (1), &bundle_tag);
VAL_SDP_INIT (answer_non_reject, _count_non_rejected_media,
GUINT_TO_POINTER (2), &bundle_tag);
VAL_SDP_INIT (offer, _check_bundle_only_media, &offer_bundle_only,
&offer_non_reject);
VAL_SDP_INIT (answer, _check_bundle_only_media, &answer_bundle_only,
&answer_non_reject);
/* We set a max-bundle policy on the offering webrtcbin,
* this means that all the offered medias should be part
@ -2068,13 +2071,12 @@ GST_START_TEST (test_bundle_audio_video_max_compat_max_bundle)
const gchar *bundle[] = { "audio0", "video1", NULL };
const gchar *bundle_only[] = { NULL };
struct validate_sdp count =
{ _count_num_sdp_media, GUINT_TO_POINTER (2), NULL };
struct validate_sdp bundle_tag = { _check_bundle_tag, bundle, &count };
struct validate_sdp count_non_reject =
{ _count_non_rejected_media, GUINT_TO_POINTER (2), &bundle_tag };
struct validate_sdp bundle_sdp =
{ _check_bundle_only_media, &bundle_only, &count_non_reject };
VAL_SDP_INIT (count, _count_num_sdp_media, GUINT_TO_POINTER (2), NULL);
VAL_SDP_INIT (bundle_tag, _check_bundle_tag, bundle, &count);
VAL_SDP_INIT (count_non_reject, _count_non_rejected_media,
GUINT_TO_POINTER (2), &bundle_tag);
VAL_SDP_INIT (bundle_sdp, _check_bundle_only_media, &bundle_only,
&count_non_reject);
/* We set a max-compat policy on the offering webrtcbin,
* this means that all the offered medias should be part
@ -2106,18 +2108,17 @@ GST_START_TEST (test_bundle_audio_video_max_bundle_none)
const gchar *answer_bundle[] = { NULL };
const gchar *answer_bundle_only[] = { NULL };
struct validate_sdp count =
{ _count_num_sdp_media, GUINT_TO_POINTER (2), NULL };
struct validate_sdp count_non_reject =
{ _count_non_rejected_media, GUINT_TO_POINTER (1), &count };
struct validate_sdp offer_bundle_tag =
{ _check_bundle_tag, offer_bundle, &count_non_reject };
struct validate_sdp answer_bundle_tag =
{ _check_bundle_tag, answer_bundle, &count_non_reject };
struct validate_sdp offer =
{ _check_bundle_only_media, &offer_bundle_only, &offer_bundle_tag };
struct validate_sdp answer =
{ _check_bundle_only_media, &answer_bundle_only, &answer_bundle_tag };
VAL_SDP_INIT (count, _count_num_sdp_media, GUINT_TO_POINTER (2), NULL);
VAL_SDP_INIT (count_non_reject, _count_non_rejected_media,
GUINT_TO_POINTER (1), &count);
VAL_SDP_INIT (offer_bundle_tag, _check_bundle_tag, offer_bundle,
&count_non_reject);
VAL_SDP_INIT (answer_bundle_tag, _check_bundle_tag, answer_bundle,
&count_non_reject);
VAL_SDP_INIT (offer, _check_bundle_only_media, &offer_bundle_only,
&offer_bundle_tag);
VAL_SDP_INIT (answer, _check_bundle_only_media, &answer_bundle_only,
&answer_bundle_tag);
/* We set a max-bundle policy on the offering webrtcbin,
* this means that all the offered medias should be part
@ -2148,17 +2149,16 @@ GST_START_TEST (test_bundle_audio_video_data)
const gchar *answer_bundle_only[] = { NULL };
GObject *channel = NULL;
struct validate_sdp count =
{ _count_num_sdp_media, GUINT_TO_POINTER (3), NULL };
struct validate_sdp bundle_tag = { _check_bundle_tag, bundle, &count };
struct validate_sdp offer_non_reject =
{ _count_non_rejected_media, GUINT_TO_POINTER (1), &bundle_tag };
struct validate_sdp answer_non_reject =
{ _count_non_rejected_media, GUINT_TO_POINTER (3), &bundle_tag };
struct validate_sdp offer =
{ _check_bundle_only_media, &offer_bundle_only, &offer_non_reject };
struct validate_sdp answer =
{ _check_bundle_only_media, &answer_bundle_only, &answer_non_reject };
VAL_SDP_INIT (count, _count_num_sdp_media, GUINT_TO_POINTER (3), NULL);
VAL_SDP_INIT (bundle_tag, _check_bundle_tag, bundle, &count);
VAL_SDP_INIT (offer_non_reject, _count_non_rejected_media,
GUINT_TO_POINTER (1), &bundle_tag);
VAL_SDP_INIT (answer_non_reject, _count_non_rejected_media,
GUINT_TO_POINTER (3), &bundle_tag);
VAL_SDP_INIT (offer, _check_bundle_only_media, &offer_bundle_only,
&offer_non_reject);
VAL_SDP_INIT (answer, _check_bundle_only_media, &answer_bundle_only,
&answer_non_reject);
/* We set a max-bundle policy on the offering webrtcbin,
* this means that all the offered medias should be part
@ -2196,19 +2196,23 @@ GST_START_TEST (test_duplicate_nego)
struct test_webrtc *t = create_audio_video_test ();
const gchar *expected_offer[] = { "sendrecv", "sendrecv" };
const gchar *expected_answer[] = { "sendrecv", "recvonly" };
struct validate_sdp offer = { on_sdp_media_direction, expected_offer, NULL };
struct validate_sdp answer =
{ on_sdp_media_direction, expected_answer, NULL };
VAL_SDP_INIT (offer, on_sdp_media_direction, expected_offer, NULL);
VAL_SDP_INIT (answer, on_sdp_media_direction, expected_answer, NULL);
GstHarness *h;
guint negotiation_flag = 0;
/* check that negotiating twice succeeds */
t->on_negotiation_needed = on_negotiation_needed_hit;
t->negotiation_data = &negotiation_flag;
h = gst_harness_new_with_element (t->webrtc2, "sink_0", NULL);
add_fake_audio_src_harness (h, 96);
t->harnesses = g_list_prepend (t->harnesses, h);
t->on_negotiation_needed = NULL;
test_validate_sdp (t, &offer, &answer);
fail_unless_equals_int (negotiation_flag, 1);
test_webrtc_signal_state (t, STATE_NEGOTIATION_NEEDED);
test_validate_sdp (t, &offer, &answer);
@ -2222,9 +2226,8 @@ GST_START_TEST (test_dual_audio)
struct test_webrtc *t = create_audio_test ();
const gchar *expected_offer[] = { "sendrecv", "sendrecv", };
const gchar *expected_answer[] = { "sendrecv", "recvonly" };
struct validate_sdp offer = { on_sdp_media_direction, expected_offer, NULL };
struct validate_sdp answer =
{ on_sdp_media_direction, expected_answer, NULL };
VAL_SDP_INIT (offer, on_sdp_media_direction, expected_offer, NULL);
VAL_SDP_INIT (answer, on_sdp_media_direction, expected_answer, NULL);
GstHarness *h;
GstWebRTCRTPTransceiver *trans;
GArray *transceivers;
@ -2258,6 +2261,431 @@ GST_START_TEST (test_dual_audio)
test_webrtc_free (t);
}
GST_END_TEST;
static void
sdp_increasing_session_version (struct test_webrtc *t, GstElement * element,
GstWebRTCSessionDescription * desc, gpointer user_data)
{
GstWebRTCSessionDescription *previous;
const GstSDPOrigin *our_origin, *previous_origin;
const gchar *prop;
guint64 our_v, previous_v;
prop =
TEST_SDP_IS_LOCAL (t, element,
desc) ? "current-local-description" : "current-remote-description";
g_object_get (element, prop, &previous, NULL);
our_origin = gst_sdp_message_get_origin (desc->sdp);
previous_origin = gst_sdp_message_get_origin (previous->sdp);
our_v = g_ascii_strtoull (our_origin->sess_version, NULL, 10);
previous_v = g_ascii_strtoull (previous_origin->sess_version, NULL, 10);
ck_assert_int_lt (previous_v, our_v);
gst_webrtc_session_description_free (previous);
}
static void
sdp_equal_session_id (struct test_webrtc *t, GstElement * element,
GstWebRTCSessionDescription * desc, gpointer user_data)
{
GstWebRTCSessionDescription *previous;
const GstSDPOrigin *our_origin, *previous_origin;
const gchar *prop;
prop =
TEST_SDP_IS_LOCAL (t, element,
desc) ? "current-local-description" : "current-remote-description";
g_object_get (element, prop, &previous, NULL);
our_origin = gst_sdp_message_get_origin (desc->sdp);
previous_origin = gst_sdp_message_get_origin (previous->sdp);
fail_unless_equals_string (previous_origin->sess_id, our_origin->sess_id);
gst_webrtc_session_description_free (previous);
}
static void
sdp_media_equal_attribute (struct test_webrtc *t, GstElement * element,
GstWebRTCSessionDescription * desc, GstWebRTCSessionDescription * previous,
const gchar * attr)
{
guint i, n;
n = MIN (gst_sdp_message_medias_len (previous->sdp),
gst_sdp_message_medias_len (desc->sdp));
for (i = 0; i < n; i++) {
const GstSDPMedia *our_media, *other_media;
const gchar *our_mid, *other_mid;
our_media = gst_sdp_message_get_media (desc->sdp, i);
other_media = gst_sdp_message_get_media (previous->sdp, i);
our_mid = gst_sdp_media_get_attribute_val (our_media, attr);
other_mid = gst_sdp_media_get_attribute_val (other_media, attr);
fail_unless_equals_string (our_mid, other_mid);
}
}
static void
sdp_media_equal_mid (struct test_webrtc *t, GstElement * element,
GstWebRTCSessionDescription * desc, gpointer user_data)
{
GstWebRTCSessionDescription *previous;
const gchar *prop;
prop =
TEST_SDP_IS_LOCAL (t, element,
desc) ? "current-local-description" : "current-remote-description";
g_object_get (element, prop, &previous, NULL);
sdp_media_equal_attribute (t, element, desc, previous, "mid");
gst_webrtc_session_description_free (previous);
}
static void
sdp_media_equal_ice_params (struct test_webrtc *t, GstElement * element,
GstWebRTCSessionDescription * desc, gpointer user_data)
{
GstWebRTCSessionDescription *previous;
const gchar *prop;
prop =
TEST_SDP_IS_LOCAL (t, element,
desc) ? "current-local-description" : "current-remote-description";
g_object_get (element, prop, &previous, NULL);
sdp_media_equal_attribute (t, element, desc, previous, "ice-ufrag");
sdp_media_equal_attribute (t, element, desc, previous, "ice-pwd");
gst_webrtc_session_description_free (previous);
}
static void
sdp_media_equal_fingerprint (struct test_webrtc *t, GstElement * element,
GstWebRTCSessionDescription * desc, gpointer user_data)
{
GstWebRTCSessionDescription *previous;
const gchar *prop;
prop =
TEST_SDP_IS_LOCAL (t, element,
desc) ? "current-local-description" : "current-remote-description";
g_object_get (element, prop, &previous, NULL);
sdp_media_equal_attribute (t, element, desc, previous, "fingerprint");
gst_webrtc_session_description_free (previous);
}
GST_START_TEST (test_renego_add_stream)
{
struct test_webrtc *t = create_audio_video_test ();
const gchar *expected_offer[] = { "sendrecv", "sendrecv", "sendrecv" };
const gchar *expected_answer[] = { "sendrecv", "recvonly", "recvonly" };
VAL_SDP_INIT (offer, on_sdp_media_direction, expected_offer, NULL);
VAL_SDP_INIT (answer, on_sdp_media_direction, expected_answer, NULL);
VAL_SDP_INIT (renego_mid, sdp_media_equal_mid, NULL, NULL);
VAL_SDP_INIT (renego_ice_params, sdp_media_equal_ice_params, NULL,
&renego_mid);
VAL_SDP_INIT (renego_sess_id, sdp_equal_session_id, NULL, &renego_ice_params);
VAL_SDP_INIT (renego_sess_ver, sdp_increasing_session_version, NULL,
&renego_sess_id);
VAL_SDP_INIT (renego_fingerprint, sdp_media_equal_fingerprint, NULL,
&renego_sess_ver);
GstHarness *h;
/* negotiate an AV stream and then renegotiate an extra stream */
h = gst_harness_new_with_element (t->webrtc2, "sink_0", NULL);
add_fake_audio_src_harness (h, 96);
t->harnesses = g_list_prepend (t->harnesses, h);
test_validate_sdp (t, &offer, &answer);
h = gst_harness_new_with_element (t->webrtc1, "sink_2", NULL);
add_fake_audio_src_harness (h, 98);
t->harnesses = g_list_prepend (t->harnesses, h);
offer.next = &renego_fingerprint;
answer.next = &renego_fingerprint;
/* renegotiate! */
test_webrtc_signal_state (t, STATE_NEGOTIATION_NEEDED);
test_validate_sdp (t, &offer, &answer);
test_webrtc_free (t);
}
GST_END_TEST;
GST_START_TEST (test_renego_stream_add_data_channel)
{
struct test_webrtc *t = create_audio_video_test ();
const gchar *expected_offer[] = { "sendrecv", "sendrecv" };
const gchar *expected_answer[] = { "sendrecv", "recvonly" };
VAL_SDP_INIT (offer, on_sdp_media_direction, expected_offer, NULL);
VAL_SDP_INIT (answer, on_sdp_media_direction, expected_answer, NULL);
VAL_SDP_INIT (renego_mid, sdp_media_equal_mid, NULL, NULL);
VAL_SDP_INIT (renego_ice_params, sdp_media_equal_ice_params, NULL,
&renego_mid);
VAL_SDP_INIT (renego_sess_id, sdp_equal_session_id, NULL, &renego_ice_params);
VAL_SDP_INIT (renego_sess_ver, sdp_increasing_session_version, NULL,
&renego_sess_id);
VAL_SDP_INIT (renego_fingerprint, sdp_media_equal_fingerprint, NULL,
&renego_sess_ver);
GObject *channel;
GstHarness *h;
/* negotiate an AV stream and then renegotiate a data channel */
h = gst_harness_new_with_element (t->webrtc2, "sink_0", NULL);
add_fake_audio_src_harness (h, 96);
t->harnesses = g_list_prepend (t->harnesses, h);
test_validate_sdp (t, &offer, &answer);
g_signal_emit_by_name (t->webrtc1, "create-data-channel", "label", NULL,
&channel);
offer.next = &renego_fingerprint;
answer.next = &renego_fingerprint;
/* renegotiate! */
test_webrtc_signal_state (t, STATE_NEGOTIATION_NEEDED);
test_validate_sdp (t, &offer, &answer);
g_object_unref (channel);
test_webrtc_free (t);
}
GST_END_TEST;
GST_START_TEST (test_renego_data_channel_add_stream)
{
struct test_webrtc *t = test_webrtc_new ();
const gchar *expected_offer[] = { NULL, "sendrecv" };
const gchar *expected_answer[] = { NULL, "recvonly" };
VAL_SDP_INIT (offer, on_sdp_media_direction, expected_offer, NULL);
VAL_SDP_INIT (answer, on_sdp_media_direction, expected_answer, NULL);
VAL_SDP_INIT (renego_mid, sdp_media_equal_mid, NULL, NULL);
VAL_SDP_INIT (renego_ice_params, sdp_media_equal_ice_params, NULL,
&renego_mid);
VAL_SDP_INIT (renego_sess_id, sdp_equal_session_id, NULL, &renego_ice_params);
VAL_SDP_INIT (renego_sess_ver, sdp_increasing_session_version, NULL,
&renego_sess_id);
VAL_SDP_INIT (renego_fingerprint, sdp_media_equal_fingerprint, NULL,
&renego_sess_ver);
GObject *channel;
GstHarness *h;
/* negotiate an AV stream and then renegotiate a data channel */
t->on_negotiation_needed = NULL;
t->on_ice_candidate = NULL;
t->on_pad_added = _pad_added_fakesink;
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);
g_signal_emit_by_name (t->webrtc1, "create-data-channel", "label", NULL,
&channel);
test_validate_sdp (t, &offer, &answer);
h = gst_harness_new_with_element (t->webrtc1, "sink_1", NULL);
add_fake_audio_src_harness (h, 97);
t->harnesses = g_list_prepend (t->harnesses, h);
offer.next = &renego_fingerprint;
answer.next = &renego_fingerprint;
/* renegotiate! */
test_webrtc_signal_state (t, STATE_NEGOTIATION_NEEDED);
test_validate_sdp (t, &offer, &answer);
g_object_unref (channel);
test_webrtc_free (t);
}
GST_END_TEST;
GST_START_TEST (test_bundle_renego_add_stream)
{
struct test_webrtc *t = create_audio_video_test ();
const gchar *expected_offer[] = { "sendrecv", "sendrecv", "sendrecv" };
const gchar *expected_answer[] = { "sendrecv", "recvonly", "recvonly" };
const gchar *bundle[] = { "audio0", "video1", "audio2", NULL };
const gchar *offer_bundle_only[] = { "video1", "audio2", NULL };
const gchar *answer_bundle_only[] = { NULL };
VAL_SDP_INIT (offer, on_sdp_media_direction, expected_offer, NULL);
VAL_SDP_INIT (answer, on_sdp_media_direction, expected_answer, NULL);
VAL_SDP_INIT (renego_mid, sdp_media_equal_mid, NULL, NULL);
VAL_SDP_INIT (renego_ice_params, sdp_media_equal_ice_params, NULL,
&renego_mid);
VAL_SDP_INIT (renego_sess_id, sdp_equal_session_id, NULL, &renego_ice_params);
VAL_SDP_INIT (renego_sess_ver, sdp_increasing_session_version, NULL,
&renego_sess_id);
VAL_SDP_INIT (renego_fingerprint, sdp_media_equal_fingerprint, NULL,
&renego_sess_ver);
VAL_SDP_INIT (bundle_tag, _check_bundle_tag, bundle, &renego_fingerprint);
VAL_SDP_INIT (offer_non_reject, _count_non_rejected_media,
GUINT_TO_POINTER (1), &bundle_tag);
VAL_SDP_INIT (answer_non_reject, _count_non_rejected_media,
GUINT_TO_POINTER (3), &bundle_tag);
VAL_SDP_INIT (offer_bundle_only_sdp, _check_bundle_only_media,
&offer_bundle_only, &offer_non_reject);
VAL_SDP_INIT (answer_bundle_only_sdp, _check_bundle_only_media,
&answer_bundle_only, &answer_non_reject);
GstHarness *h;
/* 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
*/
gst_util_set_object_arg (G_OBJECT (t->webrtc1), "bundle-policy",
"max-bundle");
/* 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.
*/
gst_util_set_object_arg (G_OBJECT (t->webrtc2), "bundle-policy",
"max-bundle");
/* negotiate an AV stream and then renegotiate an extra stream */
h = gst_harness_new_with_element (t->webrtc2, "sink_0", NULL);
add_fake_audio_src_harness (h, 96);
t->harnesses = g_list_prepend (t->harnesses, h);
test_validate_sdp (t, &offer, &answer);
h = gst_harness_new_with_element (t->webrtc1, "sink_2", NULL);
add_fake_audio_src_harness (h, 98);
t->harnesses = g_list_prepend (t->harnesses, h);
offer.next = &offer_bundle_only_sdp;
answer.next = &answer_bundle_only_sdp;
/* renegotiate! */
test_webrtc_signal_state (t, STATE_NEGOTIATION_NEEDED);
test_validate_sdp (t, &offer, &answer);
test_webrtc_free (t);
}
GST_END_TEST;
GST_START_TEST (test_bundle_max_compat_max_bundle_renego_add_stream)
{
struct test_webrtc *t = create_audio_video_test ();
const gchar *expected_offer[] = { "sendrecv", "sendrecv", "sendrecv" };
const gchar *expected_answer[] = { "sendrecv", "recvonly", "recvonly" };
const gchar *bundle[] = { "audio0", "video1", "audio2", NULL };
const gchar *bundle_only[] = { NULL };
VAL_SDP_INIT (offer, on_sdp_media_direction, expected_offer, NULL);
VAL_SDP_INIT (answer, on_sdp_media_direction, expected_answer, NULL);
VAL_SDP_INIT (renego_mid, sdp_media_equal_mid, NULL, NULL);
VAL_SDP_INIT (renego_ice_params, sdp_media_equal_ice_params, NULL,
&renego_mid);
VAL_SDP_INIT (renego_sess_id, sdp_equal_session_id, NULL, &renego_ice_params);
VAL_SDP_INIT (renego_sess_ver, sdp_increasing_session_version, NULL,
&renego_sess_id);
VAL_SDP_INIT (renego_fingerprint, sdp_media_equal_fingerprint, NULL,
&renego_sess_ver);
VAL_SDP_INIT (bundle_tag, _check_bundle_tag, bundle, &renego_fingerprint);
VAL_SDP_INIT (count_non_reject, _count_non_rejected_media,
GUINT_TO_POINTER (3), &bundle_tag);
VAL_SDP_INIT (bundle_sdp, _check_bundle_only_media, &bundle_only,
&count_non_reject);
GstHarness *h;
/* 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
*/
gst_util_set_object_arg (G_OBJECT (t->webrtc1), "bundle-policy",
"max-compat");
/* 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.
*/
gst_util_set_object_arg (G_OBJECT (t->webrtc2), "bundle-policy",
"max-bundle");
/* negotiate an AV stream and then renegotiate an extra stream */
h = gst_harness_new_with_element (t->webrtc2, "sink_0", NULL);
add_fake_audio_src_harness (h, 96);
t->harnesses = g_list_prepend (t->harnesses, h);
test_validate_sdp (t, &offer, &answer);
h = gst_harness_new_with_element (t->webrtc1, "sink_2", NULL);
add_fake_audio_src_harness (h, 98);
t->harnesses = g_list_prepend (t->harnesses, h);
offer.next = &bundle_sdp;
answer.next = &bundle_sdp;
/* renegotiate! */
test_webrtc_signal_state (t, STATE_NEGOTIATION_NEEDED);
test_validate_sdp (t, &offer, &answer);
test_webrtc_free (t);
}
GST_END_TEST;
GST_START_TEST (test_renego_transceiver_set_direction)
{
struct test_webrtc *t = create_audio_test ();
const gchar *expected_offer[] = { "sendrecv" };
const gchar *expected_answer[] = { "sendrecv" };
VAL_SDP_INIT (offer, on_sdp_media_direction, expected_offer, NULL);
VAL_SDP_INIT (answer, on_sdp_media_direction, expected_answer, NULL);
GstWebRTCRTPTransceiver *transceiver;
GstHarness *h;
GstPad *pad;
/* negotiate an AV stream and then change the transceiver direction */
h = gst_harness_new_with_element (t->webrtc2, "sink_0", NULL);
add_fake_audio_src_harness (h, 96);
t->harnesses = g_list_prepend (t->harnesses, h);
test_validate_sdp (t, &offer, &answer);
/* renegotiate an inactive transceiver! */
pad = gst_element_get_static_pad (t->webrtc1, "sink_0");
g_object_get (pad, "transceiver", &transceiver, NULL);
fail_unless (transceiver != NULL);
gst_webrtc_rtp_transceiver_set_direction (transceiver,
GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE);
expected_offer[0] = "inactive";
expected_answer[0] = "inactive";
/* TODO: also validate EOS events from the inactive change */
test_webrtc_signal_state (t, STATE_NEGOTIATION_NEEDED);
test_validate_sdp (t, &offer, &answer);
gst_object_unref (pad);
gst_object_unref (transceiver);
test_webrtc_free (t);
}
GST_END_TEST;
static Suite *
webrtcbin_suite (void)
{
@ -2294,6 +2722,10 @@ webrtcbin_suite (void)
tcase_add_test (tc, test_bundle_audio_video_max_compat_max_bundle);
tcase_add_test (tc, test_dual_audio);
tcase_add_test (tc, test_duplicate_nego);
tcase_add_test (tc, test_renego_add_stream);
tcase_add_test (tc, test_bundle_renego_add_stream);
tcase_add_test (tc, test_bundle_max_compat_max_bundle_renego_add_stream);
tcase_add_test (tc, test_renego_transceiver_set_direction);
if (sctpenc && sctpdec) {
tcase_add_test (tc, test_data_channel_create);
tcase_add_test (tc, test_data_channel_remote_notify);
@ -2304,6 +2736,8 @@ webrtcbin_suite (void)
tcase_add_test (tc, test_data_channel_max_message_size);
tcase_add_test (tc, test_data_channel_pre_negotiated);
tcase_add_test (tc, test_bundle_audio_video_data);
tcase_add_test (tc, test_renego_stream_add_data_channel);
tcase_add_test (tc, test_renego_data_channel_add_stream);
} else {
GST_WARNING ("Some required elements were not found. "
"All datachannel tests are disabled. sctpenc %p, sctpdec %p", sctpenc,

View file

@ -1,5 +1,5 @@
noinst_PROGRAMS = webrtc webrtcbidirectional webrtcswap webrtctransceiver
noinst_PROGRAMS = webrtc webrtcbidirectional webrtcswap webrtctransceiver webrtcrenego
webrtc_SOURCES = webrtc.c
webrtc_CFLAGS=\
@ -52,3 +52,16 @@ webrtctransceiver_LDADD=\
$(GST_LIBS) \
$(GST_SDP_LIBS) \
$(top_builddir)/gst-libs/gst/webrtc/libgstwebrtc-@GST_API_VERSION@.la
webrtcrenego_SOURCES = webrtcrenego.c
webrtcrenego_CFLAGS=\
-I$(top_srcdir)/gst-libs \
-I$(top_builddir)/gst-libs \
$(GST_PLUGINS_BASE_CFLAGS) \
$(GST_CFLAGS) \
$(GST_SDP_CFLAGS)
webrtcrenego_LDADD=\
$(GST_PLUGINS_BASE_LIBS) \
$(GST_LIBS) \
$(GST_SDP_LIBS) \
$(top_builddir)/gst-libs/gst/webrtc/libgstwebrtc-@GST_API_VERSION@.la

View file

@ -1,4 +1,4 @@
examples = ['webrtc', 'webrtcbidirectional', 'webrtcswap', 'webrtctransceiver']
examples = ['webrtc', 'webrtcbidirectional', 'webrtcswap', 'webrtctransceiver', 'webrtcrenego']
foreach example : examples
exe_name = example

View file

@ -0,0 +1,289 @@
#include <gst/gst.h>
#include <gst/sdp/sdp.h>
#include <gst/webrtc/webrtc.h>
#include <string.h>
static GMainLoop *loop;
static GstElement *pipe1, *webrtc1, *webrtc2, *extra_src;
static GstBus *bus1;
#define SEND_SRC(pattern) "videotestsrc is-live=true pattern=" pattern " ! timeoverlay ! queue ! vp8enc ! rtpvp8pay ! queue ! " \
"capsfilter caps=application/x-rtp,media=video,payload=96,encoding-name=VP8"
static void
_element_message (GstElement * parent, GstMessage * msg)
{
switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_EOS:{
GstElement *receive, *webrtc;
GstPad *pad, *peer;
g_print ("Got element EOS message from %s parent %s\n",
GST_OBJECT_NAME (msg->src), GST_OBJECT_NAME (parent));
receive = GST_ELEMENT (msg->src);
pad = gst_element_get_static_pad (receive, "sink");
peer = gst_pad_get_peer (pad);
webrtc = GST_ELEMENT (gst_pad_get_parent (peer));
gst_bin_remove (GST_BIN (pipe1), receive);
gst_pad_unlink (peer, pad);
gst_element_release_request_pad (webrtc, peer);
gst_object_unref (pad);
gst_object_unref (peer);
gst_element_set_state (receive, GST_STATE_NULL);
break;
}
default:
break;
}
}
static gboolean
_bus_watch (GstBus * bus, GstMessage * msg, GstElement * pipe)
{
switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_STATE_CHANGED:
if (GST_ELEMENT (msg->src) == pipe) {
GstState old, new, pending;
gst_message_parse_state_changed (msg, &old, &new, &pending);
{
gchar *dump_name = g_strconcat ("state_changed-",
gst_element_state_get_name (old), "_",
gst_element_state_get_name (new), NULL);
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (msg->src),
GST_DEBUG_GRAPH_SHOW_ALL, dump_name);
g_free (dump_name);
}
}
break;
case GST_MESSAGE_ERROR:{
GError *err = NULL;
gchar *dbg_info = NULL;
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipe),
GST_DEBUG_GRAPH_SHOW_ALL, "error");
gst_message_parse_error (msg, &err, &dbg_info);
g_printerr ("ERROR from element %s: %s\n",
GST_OBJECT_NAME (msg->src), err->message);
g_printerr ("Debugging info: %s\n", (dbg_info) ? dbg_info : "none");
g_error_free (err);
g_free (dbg_info);
g_main_loop_quit (loop);
break;
}
case GST_MESSAGE_EOS:{
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipe),
GST_DEBUG_GRAPH_SHOW_ALL, "eos");
g_print ("EOS received\n");
g_main_loop_quit (loop);
break;
}
case GST_MESSAGE_ELEMENT:{
const GstStructure *s = gst_message_get_structure (msg);
if (g_strcmp0 (gst_structure_get_name (s), "GstBinForwarded") == 0) {
GstMessage *sub_msg;
gst_structure_get (s, "message", GST_TYPE_MESSAGE, &sub_msg, NULL);
_element_message (GST_ELEMENT (msg->src), sub_msg);
gst_message_unref (sub_msg);
}
break;
}
default:
break;
}
return TRUE;
}
static void
_webrtc_pad_added (GstElement * webrtc, GstPad * new_pad, GstElement * pipe)
{
GstElement *out;
GstPad *sink;
if (GST_PAD_DIRECTION (new_pad) != GST_PAD_SRC)
return;
out = gst_parse_bin_from_description ("queue ! rtpvp8depay ! vp8dec ! "
"videoconvert ! queue ! xvimagesink", TRUE, NULL);
gst_bin_add (GST_BIN (pipe), out);
gst_element_sync_state_with_parent (out);
sink = out->sinkpads->data;
gst_pad_link (new_pad, sink);
}
static void
_on_answer_received (GstPromise * promise, gpointer user_data)
{
GstWebRTCSessionDescription *answer = NULL;
const GstStructure *reply;
gchar *desc;
g_assert (gst_promise_wait (promise) == GST_PROMISE_RESULT_REPLIED);
reply = gst_promise_get_reply (promise);
gst_structure_get (reply, "answer",
GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &answer, NULL);
gst_promise_unref (promise);
desc = gst_sdp_message_as_text (answer->sdp);
g_print ("Created answer:\n%s\n", desc);
g_free (desc);
/* this is one way to tell webrtcbin that we don't want to be notified when
* this task is complete: set a NULL promise */
g_signal_emit_by_name (webrtc1, "set-remote-description", answer, NULL);
/* this is another way to tell webrtcbin that we don't want to be notified
* when this task is complete: interrupt the promise */
promise = gst_promise_new ();
g_signal_emit_by_name (webrtc2, "set-local-description", answer, promise);
gst_promise_interrupt (promise);
gst_promise_unref (promise);
gst_webrtc_session_description_free (answer);
}
static void
_on_offer_received (GstPromise * promise, gpointer user_data)
{
GstWebRTCSessionDescription *offer = NULL;
const GstStructure *reply;
gchar *desc;
g_assert (gst_promise_wait (promise) == GST_PROMISE_RESULT_REPLIED);
reply = gst_promise_get_reply (promise);
gst_structure_get (reply, "offer",
GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL);
gst_promise_unref (promise);
desc = gst_sdp_message_as_text (offer->sdp);
g_print ("Created offer:\n%s\n", desc);
g_free (desc);
g_signal_emit_by_name (webrtc1, "set-local-description", offer, NULL);
g_signal_emit_by_name (webrtc2, "set-remote-description", offer, NULL);
promise = gst_promise_new_with_change_func (_on_answer_received, user_data,
NULL);
g_signal_emit_by_name (webrtc2, "create-answer", NULL, promise);
gst_webrtc_session_description_free (offer);
}
static void
_on_negotiation_needed (GstElement * element, gpointer user_data)
{
GstPromise *promise;
promise = gst_promise_new_with_change_func (_on_offer_received, user_data,
NULL);
g_signal_emit_by_name (webrtc1, "create-offer", NULL, promise);
}
static void
_on_ice_candidate (GstElement * webrtc, guint mlineindex, gchar * candidate,
GstElement * other)
{
g_signal_emit_by_name (other, "add-ice-candidate", mlineindex, candidate);
}
static gboolean
stream_change (gpointer data)
{
if (!extra_src) {
g_print ("Adding extra stream\n");
extra_src =
gst_parse_bin_from_description (SEND_SRC ("circular"), TRUE, NULL);
gst_element_set_locked_state (extra_src, TRUE);
gst_bin_add (GST_BIN (pipe1), extra_src);
gst_element_link (extra_src, webrtc1);
gst_element_set_locked_state (extra_src, FALSE);
gst_element_sync_state_with_parent (extra_src);
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipe1),
GST_DEBUG_GRAPH_SHOW_ALL, "add");
} else {
GstPad *pad, *peer;
GstWebRTCRTPTransceiver *transceiver;
g_print ("Removing extra stream\n");
pad = gst_element_get_static_pad (extra_src, "src");
peer = gst_pad_get_peer (pad);
gst_element_send_event (extra_src, gst_event_new_eos ());
g_object_get (peer, "transceiver", &transceiver, NULL);
gst_webrtc_rtp_transceiver_set_direction (transceiver,
GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE);
gst_element_set_locked_state (extra_src, TRUE);
gst_element_set_state (extra_src, GST_STATE_NULL);
gst_pad_unlink (pad, peer);
gst_element_release_request_pad (webrtc1, peer);
gst_object_unref (peer);
gst_object_unref (pad);
gst_bin_remove (GST_BIN (pipe1), extra_src);
extra_src = NULL;
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipe1),
GST_DEBUG_GRAPH_SHOW_ALL, "remove");
}
return G_SOURCE_CONTINUE;
}
int
main (int argc, char *argv[])
{
gst_init (&argc, &argv);
loop = g_main_loop_new (NULL, FALSE);
pipe1 = gst_parse_launch (SEND_SRC ("smpte")
" ! webrtcbin name=smpte bundle-policy=max-bundle " SEND_SRC ("ball")
" ! webrtcbin name=ball bundle-policy=max-bundle", NULL);
g_object_set (pipe1, "message-forward", TRUE, NULL);
bus1 = gst_pipeline_get_bus (GST_PIPELINE (pipe1));
gst_bus_add_watch (bus1, (GstBusFunc) _bus_watch, pipe1);
webrtc1 = gst_bin_get_by_name (GST_BIN (pipe1), "smpte");
g_signal_connect (webrtc1, "on-negotiation-needed",
G_CALLBACK (_on_negotiation_needed), NULL);
g_signal_connect (webrtc1, "pad-added", G_CALLBACK (_webrtc_pad_added),
pipe1);
webrtc2 = gst_bin_get_by_name (GST_BIN (pipe1), "ball");
g_signal_connect (webrtc2, "pad-added", G_CALLBACK (_webrtc_pad_added),
pipe1);
g_signal_connect (webrtc1, "on-ice-candidate",
G_CALLBACK (_on_ice_candidate), webrtc2);
g_signal_connect (webrtc2, "on-ice-candidate",
G_CALLBACK (_on_ice_candidate), webrtc1);
g_print ("Starting pipeline\n");
gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_PLAYING);
g_timeout_add_seconds (5, stream_change, NULL);
g_main_loop_run (loop);
gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_NULL);
g_print ("Pipeline stopped\n");
gst_object_unref (webrtc1);
gst_object_unref (webrtc2);
gst_bus_remove_watch (bus1);
gst_object_unref (bus1);
gst_object_unref (pipe1);
gst_deinit ();
return 0;
}