mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-26 17:18:15 +00:00
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:
parent
015cb75f66
commit
177aa22bcd
16 changed files with 1623 additions and 381 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -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
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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__ */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,8 @@ struct _WebRTCTransceiver
|
|||
GstWebRTCFECType fec_type;
|
||||
guint fec_percentage;
|
||||
gboolean do_nack;
|
||||
|
||||
GstCaps *last_configured_caps;
|
||||
};
|
||||
|
||||
struct _WebRTCTransceiverClass
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
examples = ['webrtc', 'webrtcbidirectional', 'webrtcswap', 'webrtctransceiver']
|
||||
examples = ['webrtc', 'webrtcbidirectional', 'webrtcswap', 'webrtctransceiver', 'webrtcrenego']
|
||||
|
||||
foreach example : examples
|
||||
exe_name = example
|
||||
|
|
289
tests/examples/webrtc/webrtcrenego.c
Normal file
289
tests/examples/webrtc/webrtcrenego.c
Normal 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;
|
||||
}
|
Loading…
Reference in a new issue