gstreamer/tests/check/elements/webrtcbin.c
Matthew Waters 62cc5e51d1 tests/webrtc: wait until the SDP has been set before continuing
If we renegotiate, then it is currently possible for an added stream to
be added to webrtcbin before the SDP is complete.  This causes an
internal inconsistency as there is a 'pending sink transceiver' without
a corresponding media section in the sdp.  It also does not have an
associated transport stream and will fail in _connect_input_stream().
2019-05-30 21:33:09 +10:00

2818 lines
90 KiB
C

/* GStreamer
*
* Unit tests for webrtcbin
*
* Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/gst.h>
#include <gst/check/gstcheck.h>
#include <gst/check/gstharness.h>
#include <gst/webrtc/webrtc.h>
#include "../../../ext/webrtc/webrtcsdp.h"
#include "../../../ext/webrtc/webrtcsdp.c"
#include "../../../ext/webrtc/utils.h"
#include "../../../ext/webrtc/utils.c"
#define OPUS_RTP_CAPS(pt) "application/x-rtp,payload=" G_STRINGIFY(pt) ",encoding-name=OPUS,media=audio,clock-rate=48000,ssrc=(uint)3384078950"
#define VP8_RTP_CAPS(pt) "application/x-rtp,payload=" G_STRINGIFY(pt) ",encoding-name=VP8,media=video,clock-rate=90000,ssrc=(uint)3484078950"
#define TEST_IS_OFFER_ELEMENT(t, e) ((t)->offerror == 1 && (e) == (t)->webrtc1 ? TRUE : FALSE)
#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,
STATE_NEGOTIATION_NEEDED,
STATE_OFFER_CREATED,
STATE_OFFER_SET,
STATE_ANSWER_CREATED,
STATE_ANSWER_SET,
STATE_EOS,
STATE_ERROR,
STATE_CUSTOM,
} TestState;
/* basic premise of this is that webrtc1 and webrtc2 are attempting to connect
* to each other in various configurations */
struct test_webrtc;
struct test_webrtc
{
GList *harnesses;
GThread *thread;
GMainLoop *loop;
GstBus *bus1;
GstBus *bus2;
GstElement *webrtc1;
GstElement *webrtc2;
GMutex lock;
GCond cond;
TestState state;
guint offerror;
gpointer user_data;
GDestroyNotify data_notify;
/* *INDENT-OFF* */
void (*on_negotiation_needed) (struct test_webrtc * t,
GstElement * element,
gpointer user_data);
gpointer negotiation_data;
GDestroyNotify negotiation_notify;
void (*on_ice_candidate) (struct test_webrtc * t,
GstElement * element,
guint mlineindex,
gchar * candidate,
GstElement * other,
gpointer user_data);
gpointer ice_candidate_data;
GDestroyNotify ice_candidate_notify;
void (*on_offer_created) (struct test_webrtc * t,
GstElement * element,
GstPromise * promise,
gpointer user_data);
GstWebRTCSessionDescription *offer_desc;
guint offer_set_count;
gpointer offer_data;
GDestroyNotify offer_notify;
void (*on_offer_set) (struct test_webrtc * t,
GstElement * element,
GstPromise * promise,
gpointer user_data);
gpointer offer_set_data;
GDestroyNotify offer_set_notify;
void (*on_answer_created) (struct test_webrtc * t,
GstElement * element,
GstPromise * promise,
gpointer user_data);
GstWebRTCSessionDescription *answer_desc;
guint answer_set_count;
gpointer answer_data;
GDestroyNotify answer_notify;
void (*on_answer_set) (struct test_webrtc * t,
GstElement * element,
GstPromise * promise,
gpointer user_data);
gpointer answer_set_data;
GDestroyNotify answer_set_notify;
void (*on_data_channel) (struct test_webrtc * t,
GstElement * element,
GObject *data_channel,
gpointer user_data);
gpointer data_channel_data;
GDestroyNotify data_channel_notify;
void (*on_pad_added) (struct test_webrtc * t,
GstElement * element,
GstPad * pad,
gpointer user_data);
gpointer pad_added_data;
GDestroyNotify pad_added_notify;
void (*bus_message) (struct test_webrtc * t,
GstBus * bus,
GstMessage * msg,
gpointer user_data);
gpointer bus_data;
GDestroyNotify bus_notify;
/* *INDENT-ON* */
};
static void
test_webrtc_signal_state_unlocked (struct test_webrtc *t, TestState state)
{
t->state = state;
g_cond_broadcast (&t->cond);
}
static void
test_webrtc_signal_state (struct test_webrtc *t, TestState state)
{
g_mutex_lock (&t->lock);
test_webrtc_signal_state_unlocked (t, state);
g_mutex_unlock (&t->lock);
}
static void
_on_answer_set (GstPromise * promise, gpointer user_data)
{
struct test_webrtc *t = user_data;
GstElement *answerer = TEST_GET_ANSWERER (t);
g_mutex_lock (&t->lock);
if (++t->answer_set_count >= 2 && t->on_answer_set) {
t->on_answer_set (t, answerer, promise, t->answer_set_data);
}
if (t->state == STATE_ANSWER_CREATED)
t->state = STATE_ANSWER_SET;
g_cond_broadcast (&t->cond);
gst_promise_unref (promise);
g_mutex_unlock (&t->lock);
}
static void
_on_answer_received (GstPromise * promise, gpointer user_data)
{
struct test_webrtc *t = user_data;
GstElement *offeror = TEST_GET_OFFEROR (t);
GstElement *answerer = TEST_GET_ANSWERER (t);
const GstStructure *reply;
GstWebRTCSessionDescription *answer = NULL;
gchar *desc;
reply = gst_promise_get_reply (promise);
gst_structure_get (reply, "answer",
GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &answer, NULL);
desc = gst_sdp_message_as_text (answer->sdp);
GST_INFO ("Created Answer: %s", desc);
g_free (desc);
g_mutex_lock (&t->lock);
g_assert (t->answer_desc == NULL);
t->answer_desc = answer;
if (t->on_answer_created) {
t->on_answer_created (t, answerer, promise, t->answer_data);
}
gst_promise_unref (promise);
promise = gst_promise_new_with_change_func (_on_answer_set, t, NULL);
g_signal_emit_by_name (answerer, "set-local-description", t->answer_desc,
promise);
promise = gst_promise_new_with_change_func (_on_answer_set, t, NULL);
g_signal_emit_by_name (offeror, "set-remote-description", t->answer_desc,
promise);
test_webrtc_signal_state_unlocked (t, STATE_ANSWER_CREATED);
g_mutex_unlock (&t->lock);
}
static void
_on_offer_set (GstPromise * promise, gpointer user_data)
{
struct test_webrtc *t = user_data;
GstElement *offeror = TEST_GET_OFFEROR (t);
g_mutex_lock (&t->lock);
if (++t->offer_set_count >= 2 && t->on_offer_set) {
t->on_offer_set (t, offeror, promise, t->offer_set_data);
}
if (t->state == STATE_OFFER_CREATED)
t->state = STATE_OFFER_SET;
g_cond_broadcast (&t->cond);
gst_promise_unref (promise);
g_mutex_unlock (&t->lock);
}
static void
_on_offer_received (GstPromise * promise, gpointer user_data)
{
struct test_webrtc *t = user_data;
GstElement *offeror = TEST_GET_OFFEROR (t);
GstElement *answerer = TEST_GET_ANSWERER (t);
const GstStructure *reply;
GstWebRTCSessionDescription *offer = NULL;
gchar *desc;
reply = gst_promise_get_reply (promise);
gst_structure_get (reply, "offer",
GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL);
desc = gst_sdp_message_as_text (offer->sdp);
GST_INFO ("Created offer: %s", desc);
g_free (desc);
g_mutex_lock (&t->lock);
g_assert (t->offer_desc == NULL);
t->offer_desc = offer;
if (t->on_offer_created) {
t->on_offer_created (t, offeror, promise, t->offer_data);
}
gst_promise_unref (promise);
promise = gst_promise_new_with_change_func (_on_offer_set, t, NULL);
g_signal_emit_by_name (offeror, "set-local-description", t->offer_desc,
promise);
promise = gst_promise_new_with_change_func (_on_offer_set, t, NULL);
g_signal_emit_by_name (answerer, "set-remote-description", t->offer_desc,
promise);
promise = gst_promise_new_with_change_func (_on_answer_received, t, NULL);
g_signal_emit_by_name (answerer, "create-answer", NULL, promise);
test_webrtc_signal_state_unlocked (t, STATE_OFFER_CREATED);
g_mutex_unlock (&t->lock);
}
static gboolean
_bus_watch (GstBus * bus, GstMessage * msg, struct test_webrtc *t)
{
g_mutex_lock (&t->lock);
switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_STATE_CHANGED:
if (GST_ELEMENT (msg->src) == t->webrtc1
|| GST_ELEMENT (msg->src) == t->webrtc2) {
GstState old, new, pending;
gst_message_parse_state_changed (msg, &old, &new, &pending);
{
gchar *dump_name = g_strconcat ("%s-state_changed-",
GST_OBJECT_NAME (msg->src), 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;
{
gchar *dump_name;
dump_name =
g_strconcat ("%s-error", GST_OBJECT_NAME (t->webrtc1), NULL);
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (t->webrtc1),
GST_DEBUG_GRAPH_SHOW_ALL, dump_name);
g_free (dump_name);
dump_name =
g_strconcat ("%s-error", GST_OBJECT_NAME (t->webrtc2), NULL);
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (t->webrtc2),
GST_DEBUG_GRAPH_SHOW_ALL, dump_name);
g_free (dump_name);
}
gst_message_parse_error (msg, &err, &dbg_info);
GST_WARNING ("ERROR from element %s: %s\n",
GST_OBJECT_NAME (msg->src), err->message);
GST_WARNING ("Debugging info: %s\n", (dbg_info) ? dbg_info : "none");
g_error_free (err);
g_free (dbg_info);
test_webrtc_signal_state_unlocked (t, STATE_ERROR);
break;
}
case GST_MESSAGE_EOS:{
{
gchar *dump_name;
dump_name = g_strconcat ("%s-eos", GST_OBJECT_NAME (t->webrtc1), NULL);
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (t->webrtc1),
GST_DEBUG_GRAPH_SHOW_ALL, dump_name);
g_free (dump_name);
dump_name = g_strconcat ("%s-eos", GST_OBJECT_NAME (t->webrtc2), NULL);
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (t->webrtc2),
GST_DEBUG_GRAPH_SHOW_ALL, dump_name);
g_free (dump_name);
}
GST_INFO ("EOS received\n");
test_webrtc_signal_state_unlocked (t, STATE_EOS);
break;
}
default:
break;
}
if (t->bus_message)
t->bus_message (t, bus, msg, t->bus_data);
g_mutex_unlock (&t->lock);
return TRUE;
}
static void
_on_negotiation_needed (GstElement * webrtc, struct test_webrtc *t)
{
g_mutex_lock (&t->lock);
if (t->on_negotiation_needed)
t->on_negotiation_needed (t, webrtc, t->negotiation_data);
if (t->state == STATE_NEW)
t->state = STATE_NEGOTIATION_NEEDED;
g_cond_broadcast (&t->cond);
g_mutex_unlock (&t->lock);
}
static void
_on_ice_candidate (GstElement * webrtc, guint mlineindex, gchar * candidate,
struct test_webrtc *t)
{
GstElement *other;
g_mutex_lock (&t->lock);
other = webrtc == t->webrtc1 ? t->webrtc2 : t->webrtc1;
if (t->on_ice_candidate)
t->on_ice_candidate (t, webrtc, mlineindex, candidate, other,
t->ice_candidate_data);
g_signal_emit_by_name (other, "add-ice-candidate", mlineindex, candidate);
g_mutex_unlock (&t->lock);
}
static void
_on_pad_added (GstElement * webrtc, GstPad * new_pad, struct test_webrtc *t)
{
g_mutex_lock (&t->lock);
if (t->on_pad_added)
t->on_pad_added (t, webrtc, new_pad, t->pad_added_data);
g_mutex_unlock (&t->lock);
}
static void
_on_data_channel (GstElement * webrtc, GObject * data_channel,
struct test_webrtc *t)
{
g_mutex_lock (&t->lock);
if (t->on_data_channel)
t->on_data_channel (t, webrtc, data_channel, t->data_channel_data);
g_mutex_unlock (&t->lock);
}
static void
_pad_added_not_reached (struct test_webrtc *t, GstElement * element,
GstPad * pad, gpointer user_data)
{
g_assert_not_reached ();
}
static void
_ice_candidate_not_reached (struct test_webrtc *t, GstElement * element,
guint mlineindex, gchar * candidate, GstElement * other, gpointer user_data)
{
g_assert_not_reached ();
}
static void
_negotiation_not_reached (struct test_webrtc *t, GstElement * element,
gpointer user_data)
{
g_assert_not_reached ();
}
static void
_bus_no_errors (struct test_webrtc *t, GstBus * bus, GstMessage * msg,
gpointer user_data)
{
switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_ERROR:{
g_assert_not_reached ();
break;
}
default:
break;
}
}
static void
_offer_answer_not_reached (struct test_webrtc *t, GstElement * element,
GstPromise * promise, gpointer user_data)
{
g_assert_not_reached ();
}
static void
_on_data_channel_not_reached (struct test_webrtc *t, GstElement * element,
GObject * data_channel, gpointer user_data)
{
g_assert_not_reached ();
}
static void
_broadcast (struct test_webrtc *t)
{
g_mutex_lock (&t->lock);
g_cond_broadcast (&t->cond);
g_mutex_unlock (&t->lock);
}
static gboolean
_unlock_create_thread (GMutex * lock)
{
g_mutex_unlock (lock);
return G_SOURCE_REMOVE;
}
static gpointer
_bus_thread (struct test_webrtc *t)
{
g_mutex_lock (&t->lock);
t->loop = g_main_loop_new (NULL, FALSE);
g_idle_add ((GSourceFunc) _unlock_create_thread, &t->lock);
g_cond_broadcast (&t->cond);
g_main_loop_run (t->loop);
g_mutex_lock (&t->lock);
g_main_loop_unref (t->loop);
t->loop = NULL;
g_cond_broadcast (&t->cond);
g_mutex_unlock (&t->lock);
return NULL;
}
static void
element_added_disable_sync (GstBin * bin, GstBin * sub_bin,
GstElement * element, gpointer user_data)
{
GObjectClass *class = G_OBJECT_GET_CLASS (element);
if (g_object_class_find_property (class, "async"))
g_object_set (element, "async", FALSE, NULL);
if (g_object_class_find_property (class, "sync"))
g_object_set (element, "sync", FALSE, NULL);
}
static struct test_webrtc *
test_webrtc_new (void)
{
struct test_webrtc *ret = g_new0 (struct test_webrtc, 1);
ret->on_negotiation_needed = _negotiation_not_reached;
ret->on_ice_candidate = _ice_candidate_not_reached;
ret->on_pad_added = _pad_added_not_reached;
ret->on_offer_created = _offer_answer_not_reached;
ret->on_answer_created = _offer_answer_not_reached;
ret->on_data_channel = _on_data_channel_not_reached;
ret->bus_message = _bus_no_errors;
g_mutex_init (&ret->lock);
g_cond_init (&ret->cond);
ret->bus1 = gst_bus_new ();
ret->bus2 = gst_bus_new ();
gst_bus_add_watch (ret->bus1, (GstBusFunc) _bus_watch, ret);
gst_bus_add_watch (ret->bus2, (GstBusFunc) _bus_watch, ret);
ret->webrtc1 = gst_element_factory_make ("webrtcbin", NULL);
ret->webrtc2 = gst_element_factory_make ("webrtcbin", NULL);
fail_unless (ret->webrtc1 != NULL && ret->webrtc2 != NULL);
gst_element_set_bus (ret->webrtc1, ret->bus1);
gst_element_set_bus (ret->webrtc2, ret->bus2);
g_signal_connect (ret->webrtc1, "deep-element-added",
G_CALLBACK (element_added_disable_sync), NULL);
g_signal_connect (ret->webrtc2, "deep-element-added",
G_CALLBACK (element_added_disable_sync), NULL);
g_signal_connect (ret->webrtc1, "on-negotiation-needed",
G_CALLBACK (_on_negotiation_needed), ret);
g_signal_connect (ret->webrtc2, "on-negotiation-needed",
G_CALLBACK (_on_negotiation_needed), ret);
g_signal_connect (ret->webrtc1, "on-ice-candidate",
G_CALLBACK (_on_ice_candidate), ret);
g_signal_connect (ret->webrtc2, "on-ice-candidate",
G_CALLBACK (_on_ice_candidate), ret);
g_signal_connect (ret->webrtc1, "on-data-channel",
G_CALLBACK (_on_data_channel), ret);
g_signal_connect (ret->webrtc2, "on-data-channel",
G_CALLBACK (_on_data_channel), ret);
g_signal_connect (ret->webrtc1, "pad-added", G_CALLBACK (_on_pad_added), ret);
g_signal_connect (ret->webrtc2, "pad-added", G_CALLBACK (_on_pad_added), ret);
g_signal_connect_swapped (ret->webrtc1, "notify::ice-gathering-state",
G_CALLBACK (_broadcast), ret);
g_signal_connect_swapped (ret->webrtc2, "notify::ice-gathering-state",
G_CALLBACK (_broadcast), ret);
g_signal_connect_swapped (ret->webrtc1, "notify::ice-connection-state",
G_CALLBACK (_broadcast), ret);
g_signal_connect_swapped (ret->webrtc2, "notify::ice-connection-state",
G_CALLBACK (_broadcast), ret);
ret->thread = g_thread_new ("test-webrtc", (GThreadFunc) _bus_thread, ret);
g_mutex_lock (&ret->lock);
while (!ret->loop)
g_cond_wait (&ret->cond, &ret->lock);
g_mutex_unlock (&ret->lock);
return ret;
}
static void
test_webrtc_reset_negotiation (struct test_webrtc *t)
{
if (t->offer_desc)
gst_webrtc_session_description_free (t->offer_desc);
t->offer_desc = NULL;
t->offer_set_count = 0;
if (t->answer_desc)
gst_webrtc_session_description_free (t->answer_desc);
t->answer_desc = NULL;
t->answer_set_count = 0;
test_webrtc_signal_state (t, STATE_NEGOTIATION_NEEDED);
}
static void
test_webrtc_free (struct test_webrtc *t)
{
/* Otherwise while one webrtcbin is being destroyed, the other could
* generate a signal that calls into the destroyed webrtcbin */
g_signal_handlers_disconnect_by_data (t->webrtc1, t);
g_signal_handlers_disconnect_by_data (t->webrtc2, t);
g_main_loop_quit (t->loop);
g_mutex_lock (&t->lock);
while (t->loop)
g_cond_wait (&t->cond, &t->lock);
g_mutex_unlock (&t->lock);
g_thread_join (t->thread);
gst_bus_remove_watch (t->bus1);
gst_bus_remove_watch (t->bus2);
gst_bus_set_flushing (t->bus1, TRUE);
gst_bus_set_flushing (t->bus2, TRUE);
gst_object_unref (t->bus1);
gst_object_unref (t->bus2);
g_list_free_full (t->harnesses, (GDestroyNotify) gst_harness_teardown);
if (t->data_notify)
t->data_notify (t->user_data);
if (t->negotiation_notify)
t->negotiation_notify (t->negotiation_data);
if (t->ice_candidate_notify)
t->ice_candidate_notify (t->ice_candidate_data);
if (t->offer_notify)
t->offer_notify (t->offer_data);
if (t->offer_set_notify)
t->offer_set_notify (t->offer_set_data);
if (t->answer_notify)
t->answer_notify (t->answer_data);
if (t->answer_set_notify)
t->answer_set_notify (t->answer_set_data);
if (t->pad_added_notify)
t->pad_added_notify (t->pad_added_data);
if (t->data_channel_notify)
t->data_channel_notify (t->data_channel_data);
fail_unless_equals_int (GST_STATE_CHANGE_SUCCESS,
gst_element_set_state (t->webrtc1, GST_STATE_NULL));
fail_unless_equals_int (GST_STATE_CHANGE_SUCCESS,
gst_element_set_state (t->webrtc2, GST_STATE_NULL));
test_webrtc_reset_negotiation (t);
gst_object_unref (t->webrtc1);
gst_object_unref (t->webrtc2);
g_mutex_clear (&t->lock);
g_cond_clear (&t->cond);
g_free (t);
}
static void
test_webrtc_create_offer (struct test_webrtc *t, GstElement * webrtc)
{
GstPromise *promise;
t->offerror = webrtc == t->webrtc1 ? 1 : 2;
promise = gst_promise_new_with_change_func (_on_offer_received, t, NULL);
g_signal_emit_by_name (webrtc, "create-offer", NULL, promise);
}
static void
test_webrtc_wait_for_state_mask (struct test_webrtc *t, TestState state)
{
g_mutex_lock (&t->lock);
while (((1 << t->state) & state) == 0) {
GST_INFO ("test state 0x%x, current 0x%x", state, (1 << t->state));
g_cond_wait (&t->cond, &t->lock);
}
GST_INFO ("have test state 0x%x, current 0x%x", state, 1 << t->state);
g_mutex_unlock (&t->lock);
}
static void
test_webrtc_wait_for_answer_error_eos (struct test_webrtc *t)
{
TestState states = 0;
states |= (1 << STATE_ANSWER_SET);
states |= (1 << STATE_EOS);
states |= (1 << STATE_ERROR);
test_webrtc_wait_for_state_mask (t, states);
}
#if 0
static void
test_webrtc_wait_for_ice_gathering_complete (struct test_webrtc *t)
{
GstWebRTCICEGatheringState ice_state1, ice_state2;
g_mutex_lock (&t->lock);
g_object_get (t->webrtc1, "ice-gathering-state", &ice_state1, NULL);
g_object_get (t->webrtc2, "ice-gathering-state", &ice_state2, NULL);
while (ice_state1 != GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE &&
ice_state2 != GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE) {
g_cond_wait (&t->cond, &t->lock);
g_object_get (t->webrtc1, "ice-gathering-state", &ice_state1, NULL);
g_object_get (t->webrtc2, "ice-gathering-state", &ice_state2, NULL);
}
g_mutex_unlock (&t->lock);
}
static void
test_webrtc_wait_for_ice_connection (struct test_webrtc *t,
GstWebRTCICEConnectionState states)
{
GstWebRTCICEConnectionState ice_state1, ice_state2, current;
g_mutex_lock (&t->lock);
g_object_get (t->webrtc1, "ice-connection-state", &ice_state1, NULL);
g_object_get (t->webrtc2, "ice-connection-state", &ice_state2, NULL);
current = (1 << ice_state1) | (1 << ice_state2);
while ((current & states) == 0 || (current & ~states)) {
g_cond_wait (&t->cond, &t->lock);
g_object_get (t->webrtc1, "ice-connection-state", &ice_state1, NULL);
g_object_get (t->webrtc2, "ice-connection-state", &ice_state2, NULL);
current = (1 << ice_state1) | (1 << ice_state2);
}
g_mutex_unlock (&t->lock);
}
#endif
static void
_pad_added_fakesink (struct test_webrtc *t, GstElement * element,
GstPad * pad, gpointer user_data)
{
GstHarness *h;
if (GST_PAD_DIRECTION (pad) != GST_PAD_SRC)
return;
h = gst_harness_new_with_element (element, NULL, "src_%u");
gst_harness_add_sink_parse (h, "fakesink async=false sync=false");
t->harnesses = g_list_prepend (t->harnesses, h);
}
static void
on_negotiation_needed_hit (struct test_webrtc *t, GstElement * element,
gpointer user_data)
{
guint *flag = (guint *) user_data;
*flag = 1;
}
typedef void (*ValidateSDPFunc) (struct test_webrtc * t, GstElement * element,
GstWebRTCSessionDescription * desc, gpointer user_data);
struct validate_sdp;
struct validate_sdp
{
ValidateSDPFunc validate;
gpointer user_data;
struct validate_sdp *next;
};
#define VAL_SDP_INIT(name,func,data,next) \
struct validate_sdp name = { func, data, next }
static void
_check_validate_sdp (struct test_webrtc *t, GstElement * element,
GstPromise * promise, gpointer user_data)
{
struct validate_sdp *validate = user_data;
GstWebRTCSessionDescription *desc = NULL;
if (t->offerror == 1 && t->webrtc1 == element)
desc = t->offer_desc;
else
desc = t->answer_desc;
while (validate) {
validate->validate (t, element, desc, validate->user_data);
validate = validate->next;
}
}
static void
test_validate_sdp_full (struct test_webrtc *t, struct validate_sdp *offer,
struct validate_sdp *answer, TestState wait_mask,
gboolean perform_state_change)
{
if (offer) {
t->offer_data = offer;
t->on_offer_created = _check_validate_sdp;
} else {
t->offer_data = NULL;
t->on_offer_created = NULL;
}
if (answer) {
t->answer_data = answer;
t->on_answer_created = _check_validate_sdp;
} else {
t->answer_data = NULL;
t->on_answer_created = NULL;
}
if (perform_state_change) {
fail_if (gst_element_set_state (t->webrtc1,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
fail_if (gst_element_set_state (t->webrtc2,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
}
test_webrtc_create_offer (t, t->webrtc1);
if (wait_mask == 0) {
test_webrtc_wait_for_answer_error_eos (t);
fail_unless (t->state == STATE_ANSWER_SET);
} else {
test_webrtc_wait_for_state_mask (t, wait_mask);
}
}
static void
test_validate_sdp (struct test_webrtc *t, struct validate_sdp *offer,
struct validate_sdp *answer)
{
test_validate_sdp_full (t, offer, answer, 0, TRUE);
}
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 ();
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 */
t->on_negotiation_needed = NULL;
test_validate_sdp (t, &offer, &answer);
test_webrtc_free (t);
}
GST_END_TEST;
static void
add_fake_audio_src_harness (GstHarness * h, gint pt)
{
GstCaps *caps = gst_caps_from_string (OPUS_RTP_CAPS (pt));
GstStructure *s = gst_caps_get_structure (caps, 0);
gst_structure_set (s, "payload", G_TYPE_INT, pt, NULL);
gst_harness_set_src_caps (h, caps);
gst_harness_add_src_parse (h, "fakesrc is-live=true", TRUE);
}
static void
add_fake_video_src_harness (GstHarness * h, gint pt)
{
GstCaps *caps = gst_caps_from_string (VP8_RTP_CAPS (pt));
GstStructure *s = gst_caps_get_structure (caps, 0);
gst_structure_set (s, "payload", G_TYPE_INT, pt, NULL);
gst_harness_set_src_caps (h, caps);
gst_harness_add_src_parse (h, "fakesrc is-live=true", TRUE);
}
static struct test_webrtc *
create_audio_test (void)
{
struct test_webrtc *t = test_webrtc_new ();
GstHarness *h;
t->on_negotiation_needed = NULL;
t->on_ice_candidate = NULL;
t->on_pad_added = _pad_added_fakesink;
h = gst_harness_new_with_element (t->webrtc1, "sink_0", NULL);
add_fake_audio_src_harness (h, 96);
t->harnesses = g_list_prepend (t->harnesses, h);
return t;
}
GST_START_TEST (test_audio)
{
struct test_webrtc *t = create_audio_test ();
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 */
test_validate_sdp (t, &offer, &answer);
test_webrtc_free (t);
}
GST_END_TEST;
static struct test_webrtc *
create_audio_video_test (void)
{
struct test_webrtc *t = create_audio_test ();
GstHarness *h;
h = gst_harness_new_with_element (t->webrtc1, "sink_1", NULL);
add_fake_video_src_harness (h, 97);
t->harnesses = g_list_prepend (t->harnesses, h);
return t;
}
GST_START_TEST (test_audio_video)
{
struct test_webrtc *t = create_audio_video_test ();
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 */
test_validate_sdp (t, &offer, &answer);
test_webrtc_free (t);
}
GST_END_TEST;
static void
on_sdp_media_direction (struct test_webrtc *t, GstElement * element,
GstWebRTCSessionDescription * desc, gpointer user_data)
{
gchar **expected_directions = user_data;
int i;
for (i = 0; i < gst_sdp_message_medias_len (desc->sdp); i++) {
const GstSDPMedia *media = gst_sdp_message_get_media (desc->sdp, i);
if (g_strcmp0 (gst_sdp_media_get_media (media), "audio") == 0
|| g_strcmp0 (gst_sdp_media_get_media (media), "video") == 0) {
gboolean have_direction = FALSE;
int j;
for (j = 0; j < gst_sdp_media_attributes_len (media); j++) {
const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, j);
if (g_strcmp0 (attr->key, "inactive") == 0) {
fail_unless (have_direction == FALSE,
"duplicate/multiple directions for media %u", j);
have_direction = TRUE;
fail_unless (g_strcmp0 (attr->key, expected_directions[i]) == 0);
} else if (g_strcmp0 (attr->key, "sendonly") == 0) {
fail_unless (have_direction == FALSE,
"duplicate/multiple directions for media %u", j);
have_direction = TRUE;
fail_unless (g_strcmp0 (attr->key, expected_directions[i]) == 0);
} else if (g_strcmp0 (attr->key, "recvonly") == 0) {
fail_unless (have_direction == FALSE,
"duplicate/multiple directions for media %u", j);
have_direction = TRUE;
fail_unless (g_strcmp0 (attr->key, expected_directions[i]) == 0);
} else if (g_strcmp0 (attr->key, "sendrecv") == 0) {
fail_unless (have_direction == FALSE,
"duplicate/multiple directions for media %u", j);
have_direction = TRUE;
fail_unless (g_strcmp0 (attr->key, expected_directions[i]) == 0);
}
}
fail_unless (have_direction, "no direction attribute in media %u", i);
}
}
}
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" };
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 */
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);
test_webrtc_free (t);
}
GST_END_TEST;
static void
on_sdp_media_payload_types (struct test_webrtc *t, GstElement * element,
GstWebRTCSessionDescription * desc, gpointer user_data)
{
const GstSDPMedia *vmedia;
guint j;
vmedia = gst_sdp_message_get_media (desc->sdp, 1);
for (j = 0; j < gst_sdp_media_attributes_len (vmedia); j++) {
const GstSDPAttribute *attr = gst_sdp_media_get_attribute (vmedia, j);
if (!g_strcmp0 (attr->key, "rtpmap")) {
if (g_str_has_prefix (attr->value, "97")) {
fail_unless_equals_string (attr->value, "97 VP8/90000");
} else if (g_str_has_prefix (attr->value, "96")) {
fail_unless_equals_string (attr->value, "96 red/90000");
} else if (g_str_has_prefix (attr->value, "98")) {
fail_unless_equals_string (attr->value, "98 ulpfec/90000");
} else if (g_str_has_prefix (attr->value, "99")) {
fail_unless_equals_string (attr->value, "99 rtx/90000");
} else if (g_str_has_prefix (attr->value, "100")) {
fail_unless_equals_string (attr->value, "100 rtx/90000");
}
}
}
}
/* In this test we verify that webrtcbin will pick available payload
* types when it needs to, in that example for RTX and FEC */
GST_START_TEST (test_payload_types)
{
struct test_webrtc *t = create_audio_video_test ();
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;
g_signal_emit_by_name (t->webrtc1, "get-transceivers", &transceivers);
fail_unless_equals_int (transceivers->len, 2);
trans = g_array_index (transceivers, GstWebRTCRTPTransceiver *, 1);
g_object_set (trans, "fec-type", GST_WEBRTC_FEC_TYPE_ULP_RED, "do-nack", TRUE,
NULL);
g_array_unref (transceivers);
/* We don't really care about the answer here */
test_validate_sdp (t, &offer, NULL);
test_webrtc_free (t);
}
GST_END_TEST;
static void
on_sdp_media_setup (struct test_webrtc *t, GstElement * element,
GstWebRTCSessionDescription * desc, gpointer user_data)
{
gchar **expected_setup = user_data;
int i;
for (i = 0; i < gst_sdp_message_medias_len (desc->sdp); i++) {
const GstSDPMedia *media = gst_sdp_message_get_media (desc->sdp, i);
gboolean have_setup = FALSE;
int j;
for (j = 0; j < gst_sdp_media_attributes_len (media); j++) {
const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, j);
if (g_strcmp0 (attr->key, "setup") == 0) {
fail_unless (have_setup == FALSE,
"duplicate/multiple setup for media %u", j);
have_setup = TRUE;
fail_unless (g_strcmp0 (attr->value, expected_setup[i]) == 0);
}
}
fail_unless (have_setup, "no setup attribute in media %u", i);
}
}
GST_START_TEST (test_media_setup)
{
struct test_webrtc *t = create_audio_test ();
const gchar *expected_offer[] = { "actpass" };
const gchar *expected_answer[] = { "active" };
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);
test_webrtc_free (t);
}
GST_END_TEST;
GST_START_TEST (test_no_nice_elements_request_pad)
{
struct test_webrtc *t = test_webrtc_new ();
GstPluginFeature *nicesrc, *nicesink;
GstRegistry *registry;
GstPad *pad;
/* check that the absence of libnice elements posts an error on the bus
* when requesting a pad */
registry = gst_registry_get ();
nicesrc = gst_registry_lookup_feature (registry, "nicesrc");
nicesink = gst_registry_lookup_feature (registry, "nicesink");
if (nicesrc)
gst_registry_remove_feature (registry, nicesrc);
if (nicesink)
gst_registry_remove_feature (registry, nicesink);
t->bus_message = NULL;
pad = gst_element_get_request_pad (t->webrtc1, "sink_0");
fail_unless (pad == NULL);
test_webrtc_wait_for_answer_error_eos (t);
fail_unless_equals_int (STATE_ERROR, t->state);
test_webrtc_free (t);
if (nicesrc)
gst_registry_add_feature (registry, nicesrc);
if (nicesink)
gst_registry_add_feature (registry, nicesink);
}
GST_END_TEST;
GST_START_TEST (test_no_nice_elements_state_change)
{
struct test_webrtc *t = test_webrtc_new ();
GstPluginFeature *nicesrc, *nicesink;
GstRegistry *registry;
/* check that the absence of libnice elements posts an error on the bus */
registry = gst_registry_get ();
nicesrc = gst_registry_lookup_feature (registry, "nicesrc");
nicesink = gst_registry_lookup_feature (registry, "nicesink");
if (nicesrc)
gst_registry_remove_feature (registry, nicesrc);
if (nicesink)
gst_registry_remove_feature (registry, nicesink);
t->bus_message = NULL;
gst_element_set_state (t->webrtc1, GST_STATE_READY);
test_webrtc_wait_for_answer_error_eos (t);
fail_unless_equals_int (STATE_ERROR, t->state);
test_webrtc_free (t);
if (nicesrc)
gst_registry_add_feature (registry, nicesrc);
if (nicesink)
gst_registry_add_feature (registry, nicesink);
}
GST_END_TEST;
static void
validate_rtc_stats (const GstStructure * s)
{
GstWebRTCStatsType type = 0;
double ts = 0.;
gchar *id = NULL;
fail_unless (gst_structure_get (s, "type", GST_TYPE_WEBRTC_STATS_TYPE, &type,
NULL));
fail_unless (gst_structure_get (s, "id", G_TYPE_STRING, &id, NULL));
fail_unless (gst_structure_get (s, "timestamp", G_TYPE_DOUBLE, &ts, NULL));
fail_unless (type != 0);
fail_unless (ts != 0.);
fail_unless (id != NULL);
g_free (id);
}
static void
validate_codec_stats (const GstStructure * s)
{
guint pt = 0, clock_rate = 0;
fail_unless (gst_structure_get (s, "payload-type", G_TYPE_UINT, &pt, NULL));
fail_unless (gst_structure_get (s, "clock-rate", G_TYPE_UINT, &clock_rate,
NULL));
fail_unless (pt >= 0 && pt <= 127);
fail_unless (clock_rate >= 0);
}
static void
validate_rtc_stream_stats (const GstStructure * s, const GstStructure * stats)
{
gchar *codec_id, *transport_id;
GstStructure *codec, *transport;
fail_unless (gst_structure_get (s, "codec-id", G_TYPE_STRING, &codec_id,
NULL));
fail_unless (gst_structure_get (s, "transport-id", G_TYPE_STRING,
&transport_id, NULL));
fail_unless (gst_structure_get (stats, codec_id, GST_TYPE_STRUCTURE, &codec,
NULL));
fail_unless (gst_structure_get (stats, transport_id, GST_TYPE_STRUCTURE,
&transport, NULL));
fail_unless (codec != NULL);
fail_unless (transport != NULL);
gst_structure_free (transport);
gst_structure_free (codec);
g_free (codec_id);
g_free (transport_id);
}
static void
validate_inbound_rtp_stats (const GstStructure * s, const GstStructure * stats)
{
guint ssrc, fir, pli, nack;
gint packets_lost;
guint64 packets_received, bytes_received;
double jitter;
gchar *remote_id;
GstStructure *remote;
validate_rtc_stream_stats (s, stats);
fail_unless (gst_structure_get (s, "ssrc", G_TYPE_UINT, &ssrc, NULL));
fail_unless (gst_structure_get (s, "fir-count", G_TYPE_UINT, &fir, NULL));
fail_unless (gst_structure_get (s, "pli-count", G_TYPE_UINT, &pli, NULL));
fail_unless (gst_structure_get (s, "nack-count", G_TYPE_UINT, &nack, NULL));
fail_unless (gst_structure_get (s, "packets-received", G_TYPE_UINT64,
&packets_received, NULL));
fail_unless (gst_structure_get (s, "bytes-received", G_TYPE_UINT64,
&bytes_received, NULL));
fail_unless (gst_structure_get (s, "jitter", G_TYPE_DOUBLE, &jitter, NULL));
fail_unless (gst_structure_get (s, "packets-lost", G_TYPE_INT, &packets_lost,
NULL));
fail_unless (gst_structure_get (s, "remote-id", G_TYPE_STRING, &remote_id,
NULL));
fail_unless (gst_structure_get (stats, remote_id, GST_TYPE_STRUCTURE, &remote,
NULL));
fail_unless (remote != NULL);
gst_structure_free (remote);
g_free (remote_id);
}
static void
validate_remote_inbound_rtp_stats (const GstStructure * s,
const GstStructure * stats)
{
guint ssrc;
gint packets_lost;
double jitter, rtt;
gchar *local_id;
GstStructure *local;
validate_rtc_stream_stats (s, stats);
fail_unless (gst_structure_get (s, "ssrc", G_TYPE_UINT, &ssrc, NULL));
fail_unless (gst_structure_get (s, "jitter", G_TYPE_DOUBLE, &jitter, NULL));
fail_unless (gst_structure_get (s, "packets-lost", G_TYPE_INT, &packets_lost,
NULL));
fail_unless (gst_structure_get (s, "round-trip-time", G_TYPE_DOUBLE, &rtt,
NULL));
fail_unless (gst_structure_get (s, "local-id", G_TYPE_STRING, &local_id,
NULL));
fail_unless (gst_structure_get (stats, local_id, GST_TYPE_STRUCTURE, &local,
NULL));
fail_unless (local != NULL);
gst_structure_free (local);
g_free (local_id);
}
static void
validate_outbound_rtp_stats (const GstStructure * s, const GstStructure * stats)
{
guint ssrc, fir, pli, nack;
guint64 packets_sent, bytes_sent;
gchar *remote_id;
GstStructure *remote;
validate_rtc_stream_stats (s, stats);
fail_unless (gst_structure_get (s, "ssrc", G_TYPE_UINT, &ssrc, NULL));
fail_unless (gst_structure_get (s, "fir-count", G_TYPE_UINT, &fir, NULL));
fail_unless (gst_structure_get (s, "pli-count", G_TYPE_UINT, &pli, NULL));
fail_unless (gst_structure_get (s, "nack-count", G_TYPE_UINT, &nack, NULL));
fail_unless (gst_structure_get (s, "packets-sent", G_TYPE_UINT64,
&packets_sent, NULL));
fail_unless (gst_structure_get (s, "bytes-sent", G_TYPE_UINT64, &bytes_sent,
NULL));
fail_unless (gst_structure_get (s, "remote-id", G_TYPE_STRING, &remote_id,
NULL));
fail_unless (gst_structure_get (stats, remote_id, GST_TYPE_STRUCTURE, &remote,
NULL));
fail_unless (remote != NULL);
gst_structure_free (remote);
g_free (remote_id);
}
static void
validate_remote_outbound_rtp_stats (const GstStructure * s,
const GstStructure * stats)
{
guint ssrc;
gchar *local_id;
GstStructure *local;
validate_rtc_stream_stats (s, stats);
fail_unless (gst_structure_get (s, "ssrc", G_TYPE_UINT, &ssrc, NULL));
fail_unless (gst_structure_get (s, "local-id", G_TYPE_STRING, &local_id,
NULL));
fail_unless (gst_structure_get (stats, local_id, GST_TYPE_STRUCTURE, &local,
NULL));
fail_unless (local != NULL);
gst_structure_free (local);
g_free (local_id);
}
static gboolean
validate_stats_foreach (GQuark field_id, const GValue * value,
const GstStructure * stats)
{
const gchar *field = g_quark_to_string (field_id);
GstWebRTCStatsType type;
const GstStructure *s;
fail_unless (GST_VALUE_HOLDS_STRUCTURE (value));
s = gst_value_get_structure (value);
GST_INFO ("validating field %s %" GST_PTR_FORMAT, field, s);
validate_rtc_stats (s);
gst_structure_get (s, "type", GST_TYPE_WEBRTC_STATS_TYPE, &type, NULL);
if (type == GST_WEBRTC_STATS_CODEC) {
validate_codec_stats (s);
} else if (type == GST_WEBRTC_STATS_INBOUND_RTP) {
validate_inbound_rtp_stats (s, stats);
} else if (type == GST_WEBRTC_STATS_OUTBOUND_RTP) {
validate_outbound_rtp_stats (s, stats);
} else if (type == GST_WEBRTC_STATS_REMOTE_INBOUND_RTP) {
validate_remote_inbound_rtp_stats (s, stats);
} else if (type == GST_WEBRTC_STATS_REMOTE_OUTBOUND_RTP) {
validate_remote_outbound_rtp_stats (s, stats);
} else if (type == GST_WEBRTC_STATS_CSRC) {
} else if (type == GST_WEBRTC_STATS_PEER_CONNECTION) {
} else if (type == GST_WEBRTC_STATS_DATA_CHANNEL) {
} else if (type == GST_WEBRTC_STATS_STREAM) {
} else if (type == GST_WEBRTC_STATS_TRANSPORT) {
} else if (type == GST_WEBRTC_STATS_CANDIDATE_PAIR) {
} else if (type == GST_WEBRTC_STATS_LOCAL_CANDIDATE) {
} else if (type == GST_WEBRTC_STATS_REMOTE_CANDIDATE) {
} else if (type == GST_WEBRTC_STATS_CERTIFICATE) {
} else {
g_assert_not_reached ();
}
return TRUE;
}
static void
validate_stats (const GstStructure * stats)
{
gst_structure_foreach (stats,
(GstStructureForeachFunc) validate_stats_foreach, (gpointer) stats);
}
static void
_on_stats (GstPromise * promise, gpointer user_data)
{
struct test_webrtc *t = user_data;
const GstStructure *reply = gst_promise_get_reply (promise);
int i;
validate_stats (reply);
i = GPOINTER_TO_INT (t->user_data);
i++;
t->user_data = GINT_TO_POINTER (i);
if (i >= 2)
test_webrtc_signal_state (t, STATE_CUSTOM);
gst_promise_unref (promise);
}
GST_START_TEST (test_session_stats)
{
struct test_webrtc *t = test_webrtc_new ();
GstPromise *p;
/* test that the stats generated without any streams are sane */
t->on_negotiation_needed = NULL;
test_validate_sdp (t, NULL, NULL);
p = gst_promise_new_with_change_func (_on_stats, t, NULL);
g_signal_emit_by_name (t->webrtc1, "get-stats", NULL, p);
p = gst_promise_new_with_change_func (_on_stats, t, NULL);
g_signal_emit_by_name (t->webrtc2, "get-stats", NULL, p);
test_webrtc_wait_for_state_mask (t, 1 << STATE_CUSTOM);
test_webrtc_free (t);
}
GST_END_TEST;
GST_START_TEST (test_add_transceiver)
{
struct test_webrtc *t = test_webrtc_new ();
GstWebRTCRTPTransceiverDirection direction;
GstWebRTCRTPTransceiver *trans;
direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV;
g_signal_emit_by_name (t->webrtc1, "add-transceiver", direction, NULL,
&trans);
fail_unless (trans != NULL);
fail_unless_equals_int (direction, trans->direction);
gst_object_unref (trans);
test_webrtc_free (t);
}
GST_END_TEST;
GST_START_TEST (test_get_transceivers)
{
struct test_webrtc *t = create_audio_test ();
GstWebRTCRTPTransceiver *trans;
GArray *transceivers;
g_signal_emit_by_name (t->webrtc1, "get-transceivers", &transceivers);
fail_unless (transceivers != NULL);
fail_unless_equals_int (1, transceivers->len);
trans = g_array_index (transceivers, GstWebRTCRTPTransceiver *, 0);
fail_unless (trans != NULL);
g_array_unref (transceivers);
test_webrtc_free (t);
}
GST_END_TEST;
GST_START_TEST (test_add_recvonly_transceiver)
{
struct test_webrtc *t = test_webrtc_new ();
GstWebRTCRTPTransceiverDirection direction;
GstWebRTCRTPTransceiver *trans;
const gchar *expected_offer[] = { "recvonly" };
const gchar *expected_answer[] = { "sendonly" };
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;
/* add a transceiver that will only receive an opus stream and check that
* the created offer is marked as recvonly */
t->on_negotiation_needed = NULL;
t->on_ice_candidate = NULL;
t->on_pad_added = _pad_added_fakesink;
/* setup recvonly transceiver */
caps = gst_caps_from_string (OPUS_RTP_CAPS (96));
direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY;
g_signal_emit_by_name (t->webrtc1, "add-transceiver", direction, caps,
&trans);
gst_caps_unref (caps);
fail_unless (trans != NULL);
gst_object_unref (trans);
/* setup sendonly peer */
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);
test_webrtc_free (t);
}
GST_END_TEST;
GST_START_TEST (test_recvonly_sendonly)
{
struct test_webrtc *t = test_webrtc_new ();
GstWebRTCRTPTransceiverDirection direction;
GstWebRTCRTPTransceiver *trans;
const gchar *expected_offer[] = { "recvonly", "sendonly" };
const gchar *expected_answer[] = { "sendonly", "recvonly" };
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;
/* add a transceiver that will only receive an opus stream and check that
* the created offer is marked as recvonly */
t->on_negotiation_needed = NULL;
t->on_ice_candidate = NULL;
t->on_pad_added = _pad_added_fakesink;
/* setup recvonly transceiver */
caps = gst_caps_from_string (OPUS_RTP_CAPS (96));
direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY;
g_signal_emit_by_name (t->webrtc1, "add-transceiver", direction, caps,
&trans);
gst_caps_unref (caps);
fail_unless (trans != NULL);
gst_object_unref (trans);
/* setup sendonly stream */
h = gst_harness_new_with_element (t->webrtc1, "sink_1", NULL);
add_fake_audio_src_harness (h, 96);
t->harnesses = g_list_prepend (t->harnesses, h);
g_signal_emit_by_name (t->webrtc1, "get-transceivers", &transceivers);
fail_unless (transceivers != NULL);
trans = g_array_index (transceivers, GstWebRTCRTPTransceiver *, 1);
trans->direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY;
g_array_unref (transceivers);
/* setup sendonly peer */
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);
test_webrtc_free (t);
}
GST_END_TEST;
static void
on_sdp_has_datachannel (struct test_webrtc *t, GstElement * element,
GstWebRTCSessionDescription * desc, gpointer user_data)
{
gboolean have_data_channel = FALSE;
int i;
for (i = 0; i < gst_sdp_message_medias_len (desc->sdp); i++) {
if (_message_media_is_datachannel (desc->sdp, i)) {
/* there should only be one data channel m= section */
fail_unless_equals_int (FALSE, have_data_channel);
have_data_channel = TRUE;
}
}
fail_unless_equals_int (TRUE, have_data_channel);
}
static void
on_channel_error_not_reached (GObject * channel, GError * error,
gpointer user_data)
{
g_assert_not_reached ();
}
GST_START_TEST (test_data_channel_create)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel = 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;
t->on_ice_candidate = NULL;
fail_if (gst_element_set_state (t->webrtc1,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
fail_if (gst_element_set_state (t->webrtc2,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
g_signal_emit_by_name (t->webrtc1, "create-data-channel", "label", NULL,
&channel);
g_assert_nonnull (channel);
g_object_get (channel, "label", &label, NULL);
g_assert_cmpstr (label, ==, "label");
g_signal_connect (channel, "on-error",
G_CALLBACK (on_channel_error_not_reached), NULL);
test_validate_sdp (t, &offer, &answer);
g_object_unref (channel);
g_free (label);
test_webrtc_free (t);
}
GST_END_TEST;
static void
have_data_channel (struct test_webrtc *t, GstElement * element,
GObject * our, gpointer user_data)
{
GObject *other = user_data;
gchar *our_label, *other_label;
g_signal_connect (our, "on-error", G_CALLBACK (on_channel_error_not_reached),
NULL);
g_object_get (our, "label", &our_label, NULL);
g_object_get (other, "label", &other_label, NULL);
g_assert_cmpstr (our_label, ==, other_label);
g_free (our_label);
g_free (other_label);
test_webrtc_signal_state_unlocked (t, STATE_CUSTOM);
}
GST_START_TEST (test_data_channel_remote_notify)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel = 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->on_ice_candidate = NULL;
t->on_data_channel = have_data_channel;
fail_if (gst_element_set_state (t->webrtc1,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
fail_if (gst_element_set_state (t->webrtc2,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
g_signal_emit_by_name (t->webrtc1, "create-data-channel", "label", NULL,
&channel);
g_assert_nonnull (channel);
t->data_channel_data = channel;
g_signal_connect (channel, "on-error",
G_CALLBACK (on_channel_error_not_reached), NULL);
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);
test_validate_sdp_full (t, &offer, &answer, 1 << STATE_CUSTOM, FALSE);
g_object_unref (channel);
test_webrtc_free (t);
}
GST_END_TEST;
static const gchar *test_string = "GStreamer WebRTC is awesome!";
static void
on_message_string (GObject * channel, const gchar * str, struct test_webrtc *t)
{
gchar *expected = g_object_steal_data (channel, "expected");
g_assert_cmpstr (expected, ==, str);
g_free (expected);
test_webrtc_signal_state (t, STATE_CUSTOM);
}
static void
have_data_channel_transfer_string (struct test_webrtc *t, GstElement * element,
GObject * our, gpointer user_data)
{
GObject *other = user_data;
GstWebRTCDataChannelState state;
g_object_get (our, "ready-state", &state, NULL);
fail_unless_equals_int (GST_WEBRTC_DATA_CHANNEL_STATE_OPEN, state);
g_object_get (other, "ready-state", &state, NULL);
fail_unless_equals_int (GST_WEBRTC_DATA_CHANNEL_STATE_OPEN, state);
g_object_set_data_full (our, "expected", g_strdup (test_string), g_free);
g_signal_connect (our, "on-message-string", G_CALLBACK (on_message_string),
t);
g_signal_connect (other, "on-error",
G_CALLBACK (on_channel_error_not_reached), NULL);
g_signal_emit_by_name (other, "send-string", test_string);
}
GST_START_TEST (test_data_channel_transfer_string)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel = 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->on_ice_candidate = NULL;
t->on_data_channel = have_data_channel_transfer_string;
fail_if (gst_element_set_state (t->webrtc1,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
fail_if (gst_element_set_state (t->webrtc2,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
g_signal_emit_by_name (t->webrtc1, "create-data-channel", "label", NULL,
&channel);
g_assert_nonnull (channel);
t->data_channel_data = channel;
g_signal_connect (channel, "on-error",
G_CALLBACK (on_channel_error_not_reached), NULL);
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);
test_validate_sdp_full (t, &offer, &answer, 1 << STATE_CUSTOM, FALSE);
g_object_unref (channel);
test_webrtc_free (t);
}
GST_END_TEST;
#define g_assert_cmpbytes(b1, b2) \
G_STMT_START { \
gsize l1, l2; \
const guint8 *d1 = g_bytes_get_data (b1, &l1); \
const guint8 *d2 = g_bytes_get_data (b2, &l2); \
g_assert_cmpmem (d1, l1, d2, l2); \
} G_STMT_END;
static void
on_message_data (GObject * channel, GBytes * data, struct test_webrtc *t)
{
GBytes *expected = g_object_steal_data (channel, "expected");
g_assert_cmpbytes (data, expected);
g_bytes_unref (expected);
test_webrtc_signal_state (t, STATE_CUSTOM);
}
static void
have_data_channel_transfer_data (struct test_webrtc *t, GstElement * element,
GObject * our, gpointer user_data)
{
GObject *other = user_data;
GBytes *data = g_bytes_new_static (test_string, strlen (test_string));
GstWebRTCDataChannelState state;
g_object_get (our, "ready-state", &state, NULL);
fail_unless_equals_int (GST_WEBRTC_DATA_CHANNEL_STATE_OPEN, state);
g_object_get (other, "ready-state", &state, NULL);
fail_unless_equals_int (GST_WEBRTC_DATA_CHANNEL_STATE_OPEN, state);
g_object_set_data_full (our, "expected", g_bytes_ref (data),
(GDestroyNotify) g_bytes_unref);
g_signal_connect (our, "on-message-data", G_CALLBACK (on_message_data), t);
g_signal_connect (other, "on-error",
G_CALLBACK (on_channel_error_not_reached), NULL);
g_signal_emit_by_name (other, "send-data", data);
}
GST_START_TEST (test_data_channel_transfer_data)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel = 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->on_ice_candidate = NULL;
t->on_data_channel = have_data_channel_transfer_data;
fail_if (gst_element_set_state (t->webrtc1,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
fail_if (gst_element_set_state (t->webrtc2,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
g_signal_emit_by_name (t->webrtc1, "create-data-channel", "label", NULL,
&channel);
g_assert_nonnull (channel);
t->data_channel_data = channel;
g_signal_connect (channel, "on-error",
G_CALLBACK (on_channel_error_not_reached), NULL);
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);
test_validate_sdp_full (t, &offer, &answer, 1 << STATE_CUSTOM, FALSE);
g_object_unref (channel);
test_webrtc_free (t);
}
GST_END_TEST;
static void
have_data_channel_create_data_channel (struct test_webrtc *t,
GstElement * element, GObject * our, gpointer user_data)
{
GObject *another;
t->on_data_channel = have_data_channel_transfer_string;
g_signal_emit_by_name (t->webrtc1, "create-data-channel", "label", NULL,
&another);
g_assert_nonnull (another);
t->data_channel_data = another;
g_signal_connect (another, "on-error",
G_CALLBACK (on_channel_error_not_reached), NULL);
}
GST_START_TEST (test_data_channel_create_after_negotiate)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel = 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->on_ice_candidate = NULL;
t->on_data_channel = have_data_channel_create_data_channel;
fail_if (gst_element_set_state (t->webrtc1,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
fail_if (gst_element_set_state (t->webrtc2,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
g_signal_emit_by_name (t->webrtc1, "create-data-channel", "prev-label", NULL,
&channel);
g_assert_nonnull (channel);
t->data_channel_data = channel;
g_signal_connect (channel, "on-error",
G_CALLBACK (on_channel_error_not_reached), NULL);
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);
test_validate_sdp_full (t, &offer, &answer, 1 << STATE_CUSTOM, FALSE);
g_object_unref (channel);
test_webrtc_free (t);
}
GST_END_TEST;
static void
on_buffered_amount_low_emitted (GObject * channel, struct test_webrtc *t)
{
test_webrtc_signal_state (t, STATE_CUSTOM);
}
static void
have_data_channel_check_low_threshold_emitted (struct test_webrtc *t,
GstElement * element, GObject * our, gpointer user_data)
{
g_signal_connect (our, "on-buffered-amount-low",
G_CALLBACK (on_buffered_amount_low_emitted), t);
g_object_set (our, "buffered-amount-low-threshold", 1, NULL);
g_signal_connect (our, "on-error", G_CALLBACK (on_channel_error_not_reached),
NULL);
g_signal_emit_by_name (our, "send-string", "DATA");
}
GST_START_TEST (test_data_channel_low_threshold)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel = 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->on_ice_candidate = NULL;
t->on_data_channel = have_data_channel_check_low_threshold_emitted;
fail_if (gst_element_set_state (t->webrtc1,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
fail_if (gst_element_set_state (t->webrtc2,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
g_signal_emit_by_name (t->webrtc1, "create-data-channel", "label", NULL,
&channel);
g_assert_nonnull (channel);
t->data_channel_data = channel;
g_signal_connect (channel, "on-error",
G_CALLBACK (on_channel_error_not_reached), NULL);
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);
test_validate_sdp_full (t, &offer, &answer, 1 << STATE_CUSTOM, FALSE);
g_object_unref (channel);
test_webrtc_free (t);
}
GST_END_TEST;
static void
on_channel_error (GObject * channel, GError * error, struct test_webrtc *t)
{
g_assert_nonnull (error);
test_webrtc_signal_state (t, STATE_CUSTOM);
}
static void
have_data_channel_transfer_large_data (struct test_webrtc *t,
GstElement * element, GObject * our, gpointer user_data)
{
GObject *other = user_data;
const gsize size = 1024 * 1024;
guint8 *random_data = g_new (guint8, size);
GBytes *data;
gsize i;
for (i = 0; i < size; i++)
random_data[i] = (guint8) (i & 0xff);
data = g_bytes_new_static (random_data, size);
g_object_set_data_full (our, "expected", g_bytes_ref (data),
(GDestroyNotify) g_bytes_unref);
g_signal_connect (our, "on-message-data", G_CALLBACK (on_message_data), t);
g_signal_connect (other, "on-error", G_CALLBACK (on_channel_error), t);
g_signal_emit_by_name (other, "send-data", data);
}
GST_START_TEST (test_data_channel_max_message_size)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel = 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->on_ice_candidate = NULL;
t->on_data_channel = have_data_channel_transfer_large_data;
fail_if (gst_element_set_state (t->webrtc1,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
fail_if (gst_element_set_state (t->webrtc2,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
g_signal_emit_by_name (t->webrtc1, "create-data-channel", "label", NULL,
&channel);
g_assert_nonnull (channel);
t->data_channel_data = channel;
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);
test_validate_sdp_full (t, &offer, &answer, 1 << STATE_CUSTOM, FALSE);
g_object_unref (channel);
test_webrtc_free (t);
}
GST_END_TEST;
static void
_on_ready_state_notify (GObject * channel, GParamSpec * pspec,
struct test_webrtc *t)
{
gint *n_ready = t->data_channel_data;
GstWebRTCDataChannelState ready_state;
g_object_get (channel, "ready-state", &ready_state, NULL);
if (ready_state == GST_WEBRTC_DATA_CHANNEL_STATE_OPEN) {
if (g_atomic_int_add (n_ready, 1) >= 1) {
test_webrtc_signal_state (t, STATE_CUSTOM);
}
}
}
GST_START_TEST (test_data_channel_pre_negotiated)
{
struct test_webrtc *t = test_webrtc_new ();
GObject *channel1 = NULL, *channel2 = 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;
t->on_negotiation_needed = NULL;
t->on_ice_candidate = NULL;
fail_if (gst_element_set_state (t->webrtc1,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
fail_if (gst_element_set_state (t->webrtc2,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
s = gst_structure_new ("application/data-channel", "negotiated",
G_TYPE_BOOLEAN, TRUE, "id", G_TYPE_INT, 1, NULL);
g_signal_emit_by_name (t->webrtc1, "create-data-channel", "label", s,
&channel1);
g_assert_nonnull (channel1);
g_signal_emit_by_name (t->webrtc2, "create-data-channel", "label", s,
&channel2);
g_assert_nonnull (channel2);
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);
test_validate_sdp_full (t, &offer, &answer, 0, FALSE);
t->data_channel_data = &n_ready;
g_signal_connect (channel1, "notify::ready-state",
G_CALLBACK (_on_ready_state_notify), t);
g_signal_connect (channel2, "notify::ready-state",
G_CALLBACK (_on_ready_state_notify), t);
test_webrtc_wait_for_state_mask (t, 1 << STATE_CUSTOM);
test_webrtc_signal_state (t, STATE_NEW);
have_data_channel_transfer_string (t, t->webrtc1, channel1, channel2);
test_webrtc_wait_for_state_mask (t, 1 << STATE_CUSTOM);
g_object_unref (channel1);
g_object_unref (channel2);
gst_structure_free (s);
test_webrtc_free (t);
}
GST_END_TEST;
static void
_count_non_rejected_media (struct test_webrtc *t, GstElement * element,
GstWebRTCSessionDescription * sd, gpointer user_data)
{
guint expected = GPOINTER_TO_UINT (user_data);
guint non_rejected_media;
guint i;
non_rejected_media = 0;
for (i = 0; i < gst_sdp_message_medias_len (sd->sdp); i++) {
const GstSDPMedia *media = gst_sdp_message_get_media (sd->sdp, i);
if (gst_sdp_media_get_port (media) != 0)
non_rejected_media += 1;
}
fail_unless_equals_int (non_rejected_media, expected);
}
static void
_check_bundle_tag (struct test_webrtc *t, GstElement * element,
GstWebRTCSessionDescription * sd, gpointer user_data)
{
gchar **bundled = NULL;
GStrv expected = user_data;
guint i;
fail_unless (_parse_bundle (sd->sdp, &bundled));
if (!bundled) {
fail_unless_equals_int (g_strv_length (expected), 0);
} else {
fail_unless_equals_int (g_strv_length (bundled), g_strv_length (expected));
}
for (i = 0; i < g_strv_length (expected); i++) {
fail_unless (g_strv_contains ((const gchar **) bundled, expected[i]));
}
g_strfreev (bundled);
}
static void
_check_bundle_only_media (struct test_webrtc *t, GstElement * element,
GstWebRTCSessionDescription * sd, gpointer user_data)
{
gchar **expected_bundle_only = user_data;
guint i;
for (i = 0; i < gst_sdp_message_medias_len (sd->sdp); i++) {
const GstSDPMedia *media = gst_sdp_message_get_media (sd->sdp, i);
const gchar *mid = gst_sdp_media_get_attribute_val (media, "mid");
if (g_strv_contains ((const gchar **) expected_bundle_only, mid))
fail_unless (_media_has_attribute_key (media, "bundle-only"));
}
}
GST_START_TEST (test_bundle_audio_video_max_bundle_max_bundle)
{
struct test_webrtc *t = create_audio_video_test ();
const gchar *bundle[] = { "audio0", "video1", NULL };
const gchar *offer_bundle_only[] = { "video1", NULL };
const gchar *answer_bundle_only[] = { NULL };
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
* 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");
test_validate_sdp (t, &offer, &answer);
test_webrtc_free (t);
}
GST_END_TEST;
GST_START_TEST (test_bundle_audio_video_max_compat_max_bundle)
{
struct test_webrtc *t = create_audio_video_test ();
const gchar *bundle[] = { "audio0", "video1", NULL };
const gchar *bundle_only[] = { NULL };
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
* 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");
test_validate_sdp (t, &bundle_sdp, &bundle_sdp);
test_webrtc_free (t);
}
GST_END_TEST;
GST_START_TEST (test_bundle_audio_video_max_bundle_none)
{
struct test_webrtc *t = create_audio_video_test ();
const gchar *offer_bundle[] = { "audio0", "video1", NULL };
const gchar *offer_bundle_only[] = { "video1", NULL };
const gchar *answer_bundle[] = { NULL };
const gchar *answer_bundle_only[] = { NULL };
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
* 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 set a none policy on the answering webrtcbin,
* this means that the answer should contain no bundled
* medias, and as the bundle-policy of the offering webrtcbin
* is set to max-bundle, only one media should be active.
*/
gst_util_set_object_arg (G_OBJECT (t->webrtc2), "bundle-policy", "none");
test_validate_sdp (t, &offer, &answer);
test_webrtc_free (t);
}
GST_END_TEST;
GST_START_TEST (test_bundle_audio_video_data)
{
struct test_webrtc *t = create_audio_video_test ();
const gchar *bundle[] = { "audio0", "video1", "application2", NULL };
const gchar *offer_bundle_only[] = { "video1", "application2", NULL };
const gchar *answer_bundle_only[] = { NULL };
GObject *channel = NULL;
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
* 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");
fail_if (gst_element_set_state (t->webrtc1,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
fail_if (gst_element_set_state (t->webrtc2,
GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
g_signal_emit_by_name (t->webrtc1, "create-data-channel", "label", NULL,
&channel);
test_validate_sdp (t, &offer, &answer);
g_object_unref (channel);
test_webrtc_free (t);
}
GST_END_TEST;
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" };
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);
test_validate_sdp (t, &offer, &answer);
fail_unless_equals_int (negotiation_flag, 1);
test_webrtc_reset_negotiation (t);
test_validate_sdp (t, &offer, &answer);
test_webrtc_free (t);
}
GST_END_TEST;
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" };
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;
/* test that each mline gets a unique transceiver even with the same caps */
h = gst_harness_new_with_element (t->webrtc1, "sink_1", NULL);
add_fake_audio_src_harness (h, 96);
t->harnesses = g_list_prepend (t->harnesses, h);
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);
g_signal_emit_by_name (t->webrtc1, "get-transceivers", &transceivers);
fail_unless (transceivers != NULL);
fail_unless_equals_int (2, transceivers->len);
trans = g_array_index (transceivers, GstWebRTCRTPTransceiver *, 0);
fail_unless (trans != NULL);
fail_unless_equals_int (trans->mline, 0);
trans = g_array_index (transceivers, GstWebRTCRTPTransceiver *, 1);
fail_unless (trans != NULL);
fail_unless_equals_int (trans->mline, 1);
g_array_unref (transceivers);
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_reset_negotiation (t);
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_reset_negotiation (t);
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_full (t, &offer, &answer, 0, FALSE);
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_reset_negotiation (t);
test_validate_sdp_full (t, &offer, &answer, 0, FALSE);
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_reset_negotiation (t);
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_reset_negotiation (t);
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_reset_negotiation (t);
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)
{
Suite *s = suite_create ("webrtcbin");
TCase *tc = tcase_create ("general");
GstPluginFeature *nicesrc, *nicesink, *dtlssrtpdec, *dtlssrtpenc;
GstPluginFeature *sctpenc, *sctpdec;
GstRegistry *registry;
registry = gst_registry_get ();
nicesrc = gst_registry_lookup_feature (registry, "nicesrc");
nicesink = gst_registry_lookup_feature (registry, "nicesink");
dtlssrtpenc = gst_registry_lookup_feature (registry, "dtlssrtpenc");
dtlssrtpdec = gst_registry_lookup_feature (registry, "dtlssrtpdec");
sctpenc = gst_registry_lookup_feature (registry, "sctpenc");
sctpdec = gst_registry_lookup_feature (registry, "sctpdec");
tcase_add_test (tc, test_no_nice_elements_request_pad);
tcase_add_test (tc, test_no_nice_elements_state_change);
if (nicesrc && nicesink && dtlssrtpenc && dtlssrtpdec) {
tcase_add_test (tc, test_sdp_no_media);
tcase_add_test (tc, test_session_stats);
tcase_add_test (tc, test_audio);
tcase_add_test (tc, test_audio_video);
tcase_add_test (tc, test_media_direction);
tcase_add_test (tc, test_media_setup);
tcase_add_test (tc, test_add_transceiver);
tcase_add_test (tc, test_get_transceivers);
tcase_add_test (tc, test_add_recvonly_transceiver);
tcase_add_test (tc, test_recvonly_sendonly);
tcase_add_test (tc, test_payload_types);
tcase_add_test (tc, test_bundle_audio_video_max_bundle_max_bundle);
tcase_add_test (tc, test_bundle_audio_video_max_bundle_none);
tcase_add_test (tc, test_bundle_audio_video_max_compat_max_bundle);
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);
tcase_add_test (tc, test_data_channel_transfer_string);
tcase_add_test (tc, test_data_channel_transfer_data);
tcase_add_test (tc, test_data_channel_create_after_negotiate);
tcase_add_test (tc, test_data_channel_low_threshold);
tcase_add_test (tc, test_data_channel_max_message_size);
tcase_add_test (tc, test_data_channel_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,
sctpdec);
}
} else {
GST_WARNING ("Some required elements were not found. "
"All media tests are disabled. nicesrc %p, nicesink %p, "
"dtlssrtpenc %p, dtlssrtpdec %p", nicesrc, nicesink, dtlssrtpenc,
dtlssrtpdec);
}
if (nicesrc)
gst_object_unref (nicesrc);
if (nicesink)
gst_object_unref (nicesink);
if (dtlssrtpdec)
gst_object_unref (dtlssrtpdec);
if (dtlssrtpenc)
gst_object_unref (dtlssrtpenc);
if (sctpenc)
gst_object_unref (sctpenc);
if (sctpdec)
gst_object_unref (sctpdec);
suite_add_tcase (s, tc);
return s;
}
GST_CHECK_MAIN (webrtcbin);