From 76a5bf4421d8dbb7643c1beaed114d26ffffa768 Mon Sep 17 00:00:00 2001 From: Sangchul Lee Date: Fri, 13 Aug 2021 17:06:03 +0900 Subject: [PATCH] webrtc: Update tests/ based on 1.18.4 Files below are updated. - tests/check/elements/webrtcbin.c - tests/examples/webrtc/* Change-Id: Ife33500c886357ec60fbaf28f5fdd16a84d4d575 Signed-off-by: Sangchul Lee --- tests/check/elements/webrtcbin.c | 1465 +++++++++++++++++---------- tests/examples/webrtc/meson.build | 4 +- tests/examples/webrtc/webrtc-sendrecv.c | 1028 ------------------- tests/examples/webrtc/webrtcbidirectional.c | 2 +- tests/examples/webrtc/webrtcrenego.c | 289 ++++++ 5 files changed, 1233 insertions(+), 1555 deletions(-) delete mode 100644 tests/examples/webrtc/webrtc-sendrecv.c create mode 100644 tests/examples/webrtc/webrtcrenego.c diff --git a/tests/check/elements/webrtcbin.c b/tests/check/elements/webrtcbin.c index c7b7447..3b79bed 100644 --- a/tests/check/elements/webrtcbin.c +++ b/tests/check/elements/webrtcbin.c @@ -37,12 +37,20 @@ #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_NEGOTATION_NEEDED, + STATE_NEGOTIATION_NEEDED, STATE_OFFER_CREATED, + STATE_OFFER_SET, STATE_ANSWER_CREATED, + STATE_ANSWER_SET, STATE_EOS, STATE_ERROR, STATE_CUSTOM, @@ -80,24 +88,40 @@ struct test_webrtc gpointer user_data); gpointer ice_candidate_data; GDestroyNotify ice_candidate_notify; - GstWebRTCSessionDescription * (*on_offer_created) (struct test_webrtc * t, - GstElement * element, - GstPromise * promise, - gpointer user_data); + 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; - GstWebRTCSessionDescription * (*on_answer_created) (struct test_webrtc * t, - GstElement * element, - GstPromise * promise, - gpointer user_data); - gpointer data_channel_data; - GDestroyNotify data_channel_notify; - void (*on_data_channel) (struct test_webrtc * t, - GstElement * element, - GObject *data_channel, - gpointer user_data); + 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, @@ -114,11 +138,43 @@ struct test_webrtc }; 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 = t->offerror == 1 ? t->webrtc1 : t->webrtc2; - GstElement *answerer = t->offerror == 2 ? t->webrtc1 : t->webrtc2; + GstElement *offeror = TEST_GET_OFFEROR (t); + GstElement *answerer = TEST_GET_ANSWERER (t); const GstStructure *reply; GstWebRTCSessionDescription *answer = NULL; gchar *desc; @@ -131,28 +187,49 @@ _on_answer_received (GstPromise * promise, gpointer user_data) g_free (desc); g_mutex_lock (&t->lock); + + g_assert (t->answer_desc == NULL); + t->answer_desc = answer; + if (t->on_answer_created) { - gst_webrtc_session_description_free (answer); - answer = t->on_answer_created (t, answerer, promise, t->answer_data); + t->on_answer_created (t, answerer, promise, t->answer_data); } gst_promise_unref (promise); - g_signal_emit_by_name (answerer, "set-local-description", answer, NULL); - g_signal_emit_by_name (offeror, "set-remote-description", answer, NULL); + 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); - t->state = STATE_ANSWER_CREATED; - g_cond_broadcast (&t->cond); + 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); - gst_webrtc_session_description_free (answer); + 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 = t->offerror == 1 ? t->webrtc1 : t->webrtc2; - GstElement *answerer = t->offerror == 2 ? t->webrtc1 : t->webrtc2; + GstElement *offeror = TEST_GET_OFFEROR (t); + GstElement *answerer = TEST_GET_ANSWERER (t); const GstStructure *reply; GstWebRTCSessionDescription *offer = NULL; gchar *desc; @@ -165,23 +242,27 @@ _on_offer_received (GstPromise * promise, gpointer user_data) g_free (desc); g_mutex_lock (&t->lock); + + g_assert (t->offer_desc == NULL); + t->offer_desc = offer; + if (t->on_offer_created) { - gst_webrtc_session_description_free (offer); - offer = t->on_offer_created (t, offeror, promise, t->offer_data); + t->on_offer_created (t, offeror, promise, t->offer_data); } gst_promise_unref (promise); - g_signal_emit_by_name (offeror, "set-local-description", offer, NULL); - g_signal_emit_by_name (answerer, "set-remote-description", offer, NULL); + 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); - t->state = STATE_OFFER_CREATED; - g_cond_broadcast (&t->cond); + test_webrtc_signal_state_unlocked (t, STATE_OFFER_CREATED); g_mutex_unlock (&t->lock); - - gst_webrtc_session_description_free (offer); } static gboolean @@ -225,13 +306,12 @@ _bus_watch (GstBus * bus, GstMessage * msg, struct test_webrtc *t) } gst_message_parse_error (msg, &err, &dbg_info); - GST_WARNING ("ERROR from element %s: %s\n", + GST_WARNING ("ERROR from element %s: %s", GST_OBJECT_NAME (msg->src), err->message); - GST_WARNING ("Debugging info: %s\n", (dbg_info) ? dbg_info : "none"); + GST_WARNING ("Debugging info: %s", (dbg_info) ? dbg_info : "none"); g_error_free (err); g_free (dbg_info); - t->state = STATE_ERROR; - g_cond_broadcast (&t->cond); + test_webrtc_signal_state_unlocked (t, STATE_ERROR); break; } case GST_MESSAGE_EOS:{ @@ -246,9 +326,8 @@ _bus_watch (GstBus * bus, GstMessage * msg, struct test_webrtc *t) GST_DEBUG_GRAPH_SHOW_ALL, dump_name); g_free (dump_name); } - GST_INFO ("EOS received\n"); - t->state = STATE_EOS; - g_cond_broadcast (&t->cond); + GST_INFO ("EOS received"); + test_webrtc_signal_state_unlocked (t, STATE_EOS); break; } default: @@ -269,7 +348,7 @@ _on_negotiation_needed (GstElement * webrtc, struct test_webrtc *t) if (t->on_negotiation_needed) t->on_negotiation_needed (t, webrtc, t->negotiation_data); if (t->state == STATE_NEW) - t->state = STATE_NEGOTATION_NEEDED; + t->state = STATE_NEGOTIATION_NEEDED; g_cond_broadcast (&t->cond); g_mutex_unlock (&t->lock); } @@ -345,7 +424,7 @@ _bus_no_errors (struct test_webrtc *t, GstBus * bus, GstMessage * msg, } } -static GstWebRTCSessionDescription * +static void _offer_answer_not_reached (struct test_webrtc *t, GstElement * element, GstPromise * promise, gpointer user_data) { @@ -420,6 +499,13 @@ test_webrtc_new (void) g_mutex_init (&ret->lock); g_cond_init (&ret->cond); + 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); + ret->bus1 = gst_bus_new (); ret->bus2 = gst_bus_new (); gst_bus_add_watch (ret->bus1, (GstBusFunc) _bus_watch, ret); @@ -458,14 +544,22 @@ test_webrtc_new (void) 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); + return ret; +} - g_mutex_lock (&ret->lock); - while (!ret->loop) - g_cond_wait (&ret->cond, &ret->lock); - g_mutex_unlock (&ret->lock); +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; - return ret; + test_webrtc_signal_state (t, STATE_NEGOTIATION_NEEDED); } static void @@ -503,8 +597,12 @@ test_webrtc_free (struct test_webrtc *t) 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) @@ -515,6 +613,8 @@ test_webrtc_free (struct test_webrtc *t) 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); @@ -550,27 +650,12 @@ static void test_webrtc_wait_for_answer_error_eos (struct test_webrtc *t) { TestState states = 0; - states |= (1 << STATE_ANSWER_CREATED); + states |= (1 << STATE_ANSWER_SET); states |= (1 << STATE_EOS); states |= (1 << STATE_ERROR); test_webrtc_wait_for_state_mask (t, states); } -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); -} - #if 0 static void test_webrtc_wait_for_ice_gathering_complete (struct test_webrtc *t) @@ -621,47 +706,111 @@ _pad_added_fakesink (struct test_webrtc *t, GstElement * element, t->harnesses = g_list_prepend (t->harnesses, h); } -static GstWebRTCSessionDescription * -_count_num_sdp_media (struct test_webrtc *t, GstElement * element, +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) { - GstWebRTCSessionDescription *offer = NULL; - guint expected = GPOINTER_TO_UINT (user_data); - const GstStructure *reply; - const gchar *field; + struct validate_sdp *validate = user_data; + GstWebRTCSessionDescription *desc = NULL; - field = t->offerror == 1 && t->webrtc1 == element ? "offer" : "answer"; + if (t->offerror == 1 && t->webrtc1 == element) + desc = t->offer_desc; + else + desc = t->answer_desc; - reply = gst_promise_get_reply (promise); - gst_structure_get (reply, field, - GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL); + 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); +} - fail_unless_equals_int (gst_sdp_message_medias_len (offer->sdp), expected); +static void +_count_num_sdp_media (struct test_webrtc *t, GstElement * element, + GstWebRTCSessionDescription * desc, gpointer user_data) +{ + guint expected = GPOINTER_TO_UINT (user_data); - return offer; + 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; - t->offer_data = GUINT_TO_POINTER (0); - t->on_offer_created = _count_num_sdp_media; - t->answer_data = GUINT_TO_POINTER (0); - t->on_answer_created = _count_num_sdp_media; + test_validate_sdp (t, &offer, &answer); - fail_if (gst_element_set_state (t->webrtc1, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - fail_if (gst_element_set_state (t->webrtc2, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - - test_webrtc_create_offer (t, t->webrtc1); - - test_webrtc_wait_for_answer_error_eos (t); - fail_unless (t->state == STATE_ANSWER_CREATED); test_webrtc_free (t); } @@ -694,6 +843,7 @@ create_audio_test (void) 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); @@ -706,26 +856,13 @@ create_audio_test (void) 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 */ - t->on_negotiation_needed = NULL; - t->offer_data = GUINT_TO_POINTER (1); - t->on_offer_created = _count_num_sdp_media; - t->answer_data = GUINT_TO_POINTER (1); - t->on_answer_created = _count_num_sdp_media; - t->on_ice_candidate = NULL; - - fail_if (gst_element_set_state (t->webrtc1, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - fail_if (gst_element_set_state (t->webrtc2, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - - test_webrtc_create_offer (t, t->webrtc1); - - test_webrtc_wait_for_answer_error_eos (t); - fail_unless_equals_int (STATE_ANSWER_CREATED, t->state); + test_validate_sdp (t, &offer, &answer); test_webrtc_free (t); } @@ -734,16 +871,9 @@ GST_END_TEST; static struct test_webrtc * create_audio_video_test (void) { - struct test_webrtc *t = test_webrtc_new (); + struct test_webrtc *t = create_audio_test (); GstHarness *h; - t->on_negotiation_needed = 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); - 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); @@ -754,60 +884,18 @@ create_audio_video_test (void) 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 */ - t->on_negotiation_needed = NULL; - t->offer_data = GUINT_TO_POINTER (2); - t->on_offer_created = _count_num_sdp_media; - t->answer_data = GUINT_TO_POINTER (2); - t->on_answer_created = _count_num_sdp_media; - t->on_ice_candidate = NULL; - - fail_if (gst_element_set_state (t->webrtc1, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - fail_if (gst_element_set_state (t->webrtc2, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - - test_webrtc_create_offer (t, t->webrtc1); - - test_webrtc_wait_for_answer_error_eos (t); - fail_unless_equals_int (STATE_ANSWER_CREATED, t->state); + test_validate_sdp (t, &offer, &answer); test_webrtc_free (t); } GST_END_TEST; -typedef void (*ValidateSDPFunc) (struct test_webrtc * t, GstElement * element, - GstWebRTCSessionDescription * desc, gpointer user_data); - -struct validate_sdp -{ - ValidateSDPFunc validate; - gpointer user_data; -}; - -static GstWebRTCSessionDescription * -_check_validate_sdp (struct test_webrtc *t, GstElement * element, - GstPromise * promise, gpointer user_data) -{ - struct validate_sdp *validate = user_data; - GstWebRTCSessionDescription *offer = NULL; - const GstStructure *reply; - const gchar *field; - - field = t->offerror == 1 && t->webrtc1 == element ? "offer" : "answer"; - - reply = gst_promise_get_reply (promise); - gst_structure_get (reply, field, - GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL); - - validate->validate (t, element, offer, validate->user_data); - - return offer; -} - static void on_sdp_media_direction (struct test_webrtc *t, GstElement * element, GstWebRTCSessionDescription * desc, gpointer user_data) @@ -817,35 +905,39 @@ on_sdp_media_direction (struct test_webrtc *t, GstElement * element, 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_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); + 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); } - fail_unless (have_direction, "no direction attribute in media %u", j); } } @@ -854,9 +946,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 = { on_sdp_media_direction, expected_offer }; - struct validate_sdp answer = { on_sdp_media_direction, expected_answer }; 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 */ @@ -864,22 +961,7 @@ GST_START_TEST (test_media_direction) add_fake_audio_src_harness (h, 96); t->harnesses = g_list_prepend (t->harnesses, h); - t->on_negotiation_needed = NULL; - t->offer_data = &offer; - t->on_offer_created = _check_validate_sdp; - t->answer_data = &answer; - t->on_answer_created = _check_validate_sdp; - t->on_ice_candidate = NULL; - - fail_if (gst_element_set_state (t->webrtc1, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - fail_if (gst_element_set_state (t->webrtc2, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - - test_webrtc_create_offer (t, t->webrtc1); - - test_webrtc_wait_for_answer_error_eos (t); - fail_unless_equals_int (STATE_ANSWER_CREATED, t->state); + test_validate_sdp (t, &offer, &answer); test_webrtc_free (t); } @@ -892,8 +974,6 @@ on_sdp_media_payload_types (struct test_webrtc *t, GstElement * element, const GstSDPMedia *vmedia; guint j; - fail_unless_equals_int (gst_sdp_message_medias_len (desc->sdp), 2); - vmedia = gst_sdp_message_get_media (desc->sdp, 1); for (j = 0; j < gst_sdp_media_attributes_len (vmedia); j++) { @@ -920,17 +1000,11 @@ 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 offer = { on_sdp_media_payload_types, NULL }; + 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; - t->on_negotiation_needed = NULL; - t->offer_data = &offer; - t->on_offer_created = _check_validate_sdp; - t->on_ice_candidate = NULL; - /* We don't really care about the answer here */ - t->on_answer_created = NULL; - g_signal_emit_by_name (t->webrtc1, "get-transceivers", &transceivers); fail_unless_equals_int (transceivers->len, 2); trans = g_array_index (transceivers, GstWebRTCRTPTransceiver *, 1); @@ -938,15 +1012,8 @@ GST_START_TEST (test_payload_types) NULL); g_array_unref (transceivers); - fail_if (gst_element_set_state (t->webrtc1, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - fail_if (gst_element_set_state (t->webrtc2, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - - test_webrtc_create_offer (t, t->webrtc1); - - test_webrtc_wait_for_answer_error_eos (t); - fail_unless_equals_int (STATE_ANSWER_CREATED, t->state); + /* We don't really care about the answer here */ + test_validate_sdp (t, &offer, NULL); test_webrtc_free (t); } @@ -974,7 +1041,7 @@ on_sdp_media_setup (struct test_webrtc *t, GstElement * element, fail_unless (g_strcmp0 (attr->value, expected_setup[i]) == 0); } } - fail_unless (have_setup, "no setup attribute in media %u", j); + fail_unless (have_setup, "no setup attribute in media %u", i); } } @@ -983,27 +1050,11 @@ 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 }; - struct validate_sdp answer = { on_sdp_media_setup, expected_answer }; + 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 */ - - t->on_negotiation_needed = NULL; - t->offer_data = &offer; - t->on_offer_created = _check_validate_sdp; - t->answer_data = &answer; - t->on_answer_created = _check_validate_sdp; - t->on_ice_candidate = NULL; - - fail_if (gst_element_set_state (t->webrtc1, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - fail_if (gst_element_set_state (t->webrtc2, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - - test_webrtc_create_offer (t, t->webrtc1); - - test_webrtc_wait_for_answer_error_eos (t); - fail_unless_equals_int (STATE_ANSWER_CREATED, t->state); + test_validate_sdp (t, &offer, &answer); test_webrtc_free (t); } @@ -1315,20 +1366,8 @@ GST_START_TEST (test_session_stats) GstPromise *p; /* test that the stats generated without any streams are sane */ - t->on_negotiation_needed = NULL; - t->on_offer_created = NULL; - t->on_answer_created = NULL; - - fail_if (gst_element_set_state (t->webrtc1, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - fail_if (gst_element_set_state (t->webrtc2, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - - test_webrtc_create_offer (t, t->webrtc1); - - test_webrtc_wait_for_answer_error_eos (t); - fail_unless_equals_int (STATE_ANSWER_CREATED, t->state); + test_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); @@ -1388,22 +1427,16 @@ 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 }; - struct validate_sdp answer = { on_sdp_media_direction, expected_answer }; + 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_pad_added = _pad_added_fakesink; t->on_negotiation_needed = NULL; - t->offer_data = &offer; - t->on_offer_created = _check_validate_sdp; - t->answer_data = &answer; - t->on_answer_created = _check_validate_sdp; t->on_ice_candidate = NULL; + t->on_pad_added = _pad_added_fakesink; /* setup recvonly transceiver */ caps = gst_caps_from_string (OPUS_RTP_CAPS (96)); @@ -1418,16 +1451,8 @@ GST_START_TEST (test_add_recvonly_transceiver) 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_if (gst_element_set_state (t->webrtc1, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - fail_if (gst_element_set_state (t->webrtc2, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - - test_webrtc_create_offer (t, t->webrtc1); - - test_webrtc_wait_for_answer_error_eos (t); - fail_unless_equals_int (STATE_ANSWER_CREATED, t->state); test_webrtc_free (t); } @@ -1440,23 +1465,17 @@ 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 }; - struct validate_sdp answer = { on_sdp_media_direction, expected_answer }; + 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_pad_added = _pad_added_fakesink; - t->on_negotiation_needed = NULL; - t->offer_data = &offer; - t->on_offer_created = _check_validate_sdp; - t->answer_data = &answer; - t->on_answer_created = _check_validate_sdp; t->on_ice_candidate = NULL; + t->on_pad_added = _pad_added_fakesink; /* setup recvonly transceiver */ caps = gst_caps_from_string (OPUS_RTP_CAPS (96)); @@ -1483,15 +1502,8 @@ GST_START_TEST (test_recvonly_sendonly) add_fake_audio_src_harness (h, 96); t->harnesses = g_list_prepend (t->harnesses, h); - fail_if (gst_element_set_state (t->webrtc1, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - fail_if (gst_element_set_state (t->webrtc2, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - - test_webrtc_create_offer (t, t->webrtc1); + test_validate_sdp (t, &offer, &answer); - test_webrtc_wait_for_answer_error_eos (t); - fail_unless_equals_int (STATE_ANSWER_CREATED, t->state); test_webrtc_free (t); } @@ -1526,15 +1538,11 @@ 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 }; - struct validate_sdp answer = { on_sdp_has_datachannel, 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->offer_data = &offer; - t->on_offer_created = _check_validate_sdp; - t->answer_data = &answer; - t->on_answer_created = _check_validate_sdp; t->on_ice_candidate = NULL; fail_if (gst_element_set_state (t->webrtc1, @@ -1550,10 +1558,8 @@ GST_START_TEST (test_data_channel_create) g_signal_connect (channel, "on-error", G_CALLBACK (on_channel_error_not_reached), NULL); - test_webrtc_create_offer (t, t->webrtc1); + test_validate_sdp (t, &offer, &answer); - test_webrtc_wait_for_answer_error_eos (t); - fail_unless_equals_int (STATE_ANSWER_CREATED, t->state); g_object_unref (channel); g_free (label); test_webrtc_free (t); @@ -1586,14 +1592,10 @@ 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 }; - struct validate_sdp answer = { on_sdp_has_datachannel, 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; - t->on_offer_created = _check_validate_sdp; - t->answer_data = &answer; - t->on_answer_created = _check_validate_sdp; t->on_ice_candidate = NULL; t->on_data_channel = have_data_channel; @@ -1614,9 +1616,7 @@ GST_START_TEST (test_data_channel_remote_notify) fail_if (gst_element_set_state (t->webrtc2, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); - test_webrtc_create_offer (t, t->webrtc1); - - test_webrtc_wait_for_state_mask (t, 1 << STATE_CUSTOM); + test_validate_sdp_full (t, &offer, &answer, 1 << STATE_CUSTOM, FALSE); g_object_unref (channel); test_webrtc_free (t); @@ -1629,7 +1629,13 @@ 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"); + GstWebRTCDataChannelState state; + gchar *expected; + + g_object_get (channel, "ready-state", &state, NULL); + fail_unless_equals_int (GST_WEBRTC_DATA_CHANNEL_STATE_OPEN, state); + + expected = g_object_steal_data (channel, "expected"); g_assert_cmpstr (expected, ==, str); g_free (expected); @@ -1645,8 +1651,6 @@ have_data_channel_transfer_string (struct test_webrtc *t, GstElement * element, 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), @@ -1661,14 +1665,10 @@ 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 }; - struct validate_sdp answer = { on_sdp_has_datachannel, 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; - t->on_offer_created = _check_validate_sdp; - t->answer_data = &answer; - t->on_answer_created = _check_validate_sdp; t->on_ice_candidate = NULL; t->on_data_channel = have_data_channel_transfer_string; @@ -1689,9 +1689,7 @@ GST_START_TEST (test_data_channel_transfer_string) fail_if (gst_element_set_state (t->webrtc2, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); - test_webrtc_create_offer (t, t->webrtc1); - - test_webrtc_wait_for_state_mask (t, 1 << STATE_CUSTOM); + test_validate_sdp_full (t, &offer, &answer, 1 << STATE_CUSTOM, FALSE); g_object_unref (channel); test_webrtc_free (t); @@ -1710,7 +1708,13 @@ GST_END_TEST; static void on_message_data (GObject * channel, GBytes * data, struct test_webrtc *t) { - GBytes *expected = g_object_steal_data (channel, "expected"); + GstWebRTCDataChannelState state; + GBytes *expected; + + g_object_get (channel, "ready-state", &state, NULL); + fail_unless_equals_int (GST_WEBRTC_DATA_CHANNEL_STATE_OPEN, state); + + expected = g_object_steal_data (channel, "expected"); g_assert_cmpbytes (data, expected); g_bytes_unref (expected); @@ -1727,8 +1731,6 @@ have_data_channel_transfer_data (struct test_webrtc *t, GstElement * element, 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); @@ -1737,20 +1739,17 @@ have_data_channel_transfer_data (struct test_webrtc *t, GstElement * element, g_signal_connect (other, "on-error", G_CALLBACK (on_channel_error_not_reached), NULL); g_signal_emit_by_name (other, "send-data", data); + g_bytes_unref (data); } 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 }; - struct validate_sdp answer = { on_sdp_has_datachannel, 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; - t->on_offer_created = _check_validate_sdp; - t->answer_data = &answer; - t->on_answer_created = _check_validate_sdp; t->on_ice_candidate = NULL; t->on_data_channel = have_data_channel_transfer_data; @@ -1771,9 +1770,7 @@ GST_START_TEST (test_data_channel_transfer_data) fail_if (gst_element_set_state (t->webrtc2, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); - test_webrtc_create_offer (t, t->webrtc1); - - test_webrtc_wait_for_state_mask (t, 1 << STATE_CUSTOM); + test_validate_sdp_full (t, &offer, &answer, 1 << STATE_CUSTOM, FALSE); g_object_unref (channel); test_webrtc_free (t); @@ -1793,6 +1790,7 @@ have_data_channel_create_data_channel (struct test_webrtc *t, &another); g_assert_nonnull (another); t->data_channel_data = another; + t->data_channel_notify = (GDestroyNotify) g_object_unref; g_signal_connect (another, "on-error", G_CALLBACK (on_channel_error_not_reached), NULL); } @@ -1801,14 +1799,10 @@ 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 }; - struct validate_sdp answer = { on_sdp_has_datachannel, 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; - t->on_offer_created = _check_validate_sdp; - t->answer_data = &answer; - t->on_answer_created = _check_validate_sdp; t->on_ice_candidate = NULL; t->on_data_channel = have_data_channel_create_data_channel; @@ -1829,9 +1823,7 @@ GST_START_TEST (test_data_channel_create_after_negotiate) fail_if (gst_element_set_state (t->webrtc2, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); - test_webrtc_create_offer (t, t->webrtc1); - - test_webrtc_wait_for_state_mask (t, 1 << STATE_CUSTOM); + test_validate_sdp_full (t, &offer, &answer, 1 << STATE_CUSTOM, FALSE); g_object_unref (channel); test_webrtc_free (t); @@ -1855,21 +1847,17 @@ have_data_channel_check_low_threshold_emitted (struct test_webrtc *t, g_signal_connect (our, "on-error", G_CALLBACK (on_channel_error_not_reached), NULL); - g_signal_emit_by_name (our, "send-string", "DATA"); + g_signal_emit_by_name (our, "send-string", "A"); } 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 }; - struct validate_sdp answer = { on_sdp_has_datachannel, 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; - t->on_offer_created = _check_validate_sdp; - t->answer_data = &answer; - t->on_answer_created = _check_validate_sdp; t->on_ice_candidate = NULL; t->on_data_channel = have_data_channel_check_low_threshold_emitted; @@ -1890,9 +1878,7 @@ GST_START_TEST (test_data_channel_low_threshold) fail_if (gst_element_set_state (t->webrtc2, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); - test_webrtc_create_offer (t, t->webrtc1); - - test_webrtc_wait_for_state_mask (t, 1 << STATE_CUSTOM); + test_validate_sdp_full (t, &offer, &answer, 1 << STATE_CUSTOM, FALSE); g_object_unref (channel); test_webrtc_free (t); @@ -1921,7 +1907,8 @@ have_data_channel_transfer_large_data (struct test_webrtc *t, for (i = 0; i < size; i++) random_data[i] = (guint8) (i & 0xff); - data = g_bytes_new_static (random_data, size); + data = g_bytes_new_with_free_func (random_data, size, + (GDestroyNotify) g_free, random_data); g_object_set_data_full (our, "expected", g_bytes_ref (data), (GDestroyNotify) g_bytes_unref); @@ -1929,20 +1916,17 @@ have_data_channel_transfer_large_data (struct test_webrtc *t, g_signal_connect (other, "on-error", G_CALLBACK (on_channel_error), t); g_signal_emit_by_name (other, "send-data", data); + g_bytes_unref (data); } 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 }; - struct validate_sdp answer = { on_sdp_has_datachannel, 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; - t->on_offer_created = _check_validate_sdp; - t->answer_data = &answer; - t->on_answer_created = _check_validate_sdp; t->on_ice_candidate = NULL; t->on_data_channel = have_data_channel_transfer_large_data; @@ -1961,9 +1945,7 @@ GST_START_TEST (test_data_channel_max_message_size) fail_if (gst_element_set_state (t->webrtc2, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); - test_webrtc_create_offer (t, t->webrtc1); - - test_webrtc_wait_for_state_mask (t, 1 << STATE_CUSTOM); + test_validate_sdp_full (t, &offer, &answer, 1 << STATE_CUSTOM, FALSE); g_object_unref (channel); test_webrtc_free (t); @@ -1981,8 +1963,9 @@ _on_ready_state_notify (GObject * channel, GParamSpec * pspec, g_object_get (channel, "ready-state", &ready_state, NULL); if (ready_state == GST_WEBRTC_DATA_CHANNEL_STATE_OPEN) { - if (++(*n_ready) >= 2) + if (g_atomic_int_add (n_ready, 1) >= 1) { test_webrtc_signal_state (t, STATE_CUSTOM); + } } } @@ -1990,16 +1973,12 @@ 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 }; - struct validate_sdp answer = { on_sdp_has_datachannel, 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->offer_data = &offer; - t->on_offer_created = _check_validate_sdp; - t->answer_data = &answer; - t->on_answer_created = _check_validate_sdp; t->on_ice_candidate = NULL; fail_if (gst_element_set_state (t->webrtc1, @@ -2022,9 +2001,7 @@ GST_START_TEST (test_data_channel_pre_negotiated) fail_if (gst_element_set_state (t->webrtc2, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); - test_webrtc_create_offer (t, t->webrtc1); - test_webrtc_wait_for_answer_error_eos (t); - fail_unless (t->state == STATE_ANSWER_CREATED); + test_validate_sdp_full (t, &offer, &answer, 0, FALSE); t->data_channel_data = &n_ready; @@ -2048,56 +2025,63 @@ GST_START_TEST (test_data_channel_pre_negotiated) GST_END_TEST; -typedef struct +static void +_count_non_rejected_media (struct test_webrtc *t, GstElement * element, + GstWebRTCSessionDescription * sd, gpointer user_data) { - guint num_media; - guint num_active_media; - const gchar **bundled; - const gchar **bundled_only; -} BundleCheckData; + 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_bundled_sdp_media (struct test_webrtc *t, GstElement * element, +_check_bundle_tag (struct test_webrtc *t, GstElement * element, GstWebRTCSessionDescription * sd, gpointer user_data) { gchar **bundled = NULL; - BundleCheckData *data = (BundleCheckData *) user_data; + GStrv expected = user_data; guint i; - guint active_media; - - fail_unless_equals_int (gst_sdp_message_medias_len (sd->sdp), - data->num_media); fail_unless (_parse_bundle (sd->sdp, &bundled)); if (!bundled) { - fail_unless_equals_int (g_strv_length ((GStrv) data->bundled), 0); + fail_unless_equals_int (g_strv_length (expected), 0); } else { - fail_unless_equals_int (g_strv_length (bundled), - g_strv_length ((GStrv) data->bundled)); + fail_unless_equals_int (g_strv_length (bundled), g_strv_length (expected)); } - for (i = 0; data->bundled[i]; i++) { - fail_unless (g_strv_contains ((const gchar **) bundled, data->bundled[i])); + for (i = 0; i < g_strv_length (expected); i++) { + fail_unless (g_strv_contains ((const gchar **) bundled, expected[i])); } - active_media = 0; + 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 **) data->bundled_only, mid)) + if (g_strv_contains ((const gchar **) expected_bundle_only, mid)) fail_unless (_media_has_attribute_key (media, "bundle-only")); - - if (gst_sdp_media_get_port (media) != 0) - active_media += 1; } - - fail_unless_equals_int (active_media, data->num_active_media); - - if (bundled) - g_strfreev (bundled); } GST_START_TEST (test_bundle_audio_video_max_bundle_max_bundle) @@ -2106,52 +2090,34 @@ GST_START_TEST (test_bundle_audio_video_max_bundle_max_bundle) 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 */ - BundleCheckData offer_data = { - 2, - 1, - bundle, - offer_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. */ - BundleCheckData answer_data = { - 2, - 2, - bundle, - answer_bundle_only, - }; - struct validate_sdp offer = { _check_bundled_sdp_media, &offer_data }; - struct validate_sdp answer = { _check_bundled_sdp_media, &answer_data }; - - gst_util_set_object_arg (G_OBJECT (t->webrtc1), "bundle-policy", - "max-bundle"); gst_util_set_object_arg (G_OBJECT (t->webrtc2), "bundle-policy", "max-bundle"); - t->on_negotiation_needed = NULL; - t->offer_data = &offer; - t->on_offer_created = _check_validate_sdp; - t->answer_data = &answer; - t->on_answer_created = _check_validate_sdp; - t->on_ice_candidate = NULL; - - fail_if (gst_element_set_state (t->webrtc1, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - fail_if (gst_element_set_state (t->webrtc2, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - - test_webrtc_create_offer (t, t->webrtc1); - - test_webrtc_wait_for_answer_error_eos (t); - fail_unless_equals_int (STATE_ANSWER_CREATED, t->state); + test_validate_sdp (t, &offer, &answer); test_webrtc_free (t); } @@ -2163,52 +2129,30 @@ 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 */ - BundleCheckData offer_data = { - 2, - 2, - bundle, - 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. */ - BundleCheckData answer_data = { - 2, - 2, - bundle, - bundle_only, - }; - struct validate_sdp offer = { _check_bundled_sdp_media, &offer_data }; - struct validate_sdp answer = { _check_bundled_sdp_media, &answer_data }; - - gst_util_set_object_arg (G_OBJECT (t->webrtc1), "bundle-policy", - "max-compat"); gst_util_set_object_arg (G_OBJECT (t->webrtc2), "bundle-policy", "max-bundle"); - t->on_negotiation_needed = NULL; - t->offer_data = &offer; - t->on_offer_created = _check_validate_sdp; - t->answer_data = &answer; - t->on_answer_created = _check_validate_sdp; - t->on_ice_candidate = NULL; - - fail_if (gst_element_set_state (t->webrtc1, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - fail_if (gst_element_set_state (t->webrtc2, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - - test_webrtc_create_offer (t, t->webrtc1); - - test_webrtc_wait_for_answer_error_eos (t); - fail_unless_equals_int (STATE_ANSWER_CREATED, t->state); + test_validate_sdp (t, &bundle_sdp, &bundle_sdp); test_webrtc_free (t); } @@ -2222,51 +2166,34 @@ GST_START_TEST (test_bundle_audio_video_max_bundle_none) 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 */ - BundleCheckData offer_data = { - 2, - 1, - offer_bundle, - offer_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. */ - BundleCheckData answer_data = { - 2, - 1, - answer_bundle, - answer_bundle_only, - }; - struct validate_sdp offer = { _check_bundled_sdp_media, &offer_data }; - struct validate_sdp answer = { _check_bundled_sdp_media, &answer_data }; - - gst_util_set_object_arg (G_OBJECT (t->webrtc1), "bundle-policy", - "max-bundle"); gst_util_set_object_arg (G_OBJECT (t->webrtc2), "bundle-policy", "none"); - t->on_negotiation_needed = NULL; - t->offer_data = &offer; - t->on_offer_created = _check_validate_sdp; - t->answer_data = &answer; - t->on_answer_created = _check_validate_sdp; - t->on_ice_candidate = NULL; - - fail_if (gst_element_set_state (t->webrtc1, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - fail_if (gst_element_set_state (t->webrtc2, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); - - test_webrtc_create_offer (t, t->webrtc1); - - test_webrtc_wait_for_answer_error_eos (t); - fail_unless_equals_int (STATE_ANSWER_CREATED, t->state); + test_validate_sdp (t, &offer, &answer); test_webrtc_free (t); } @@ -2280,55 +2207,367 @@ GST_START_TEST (test_bundle_audio_video_data) 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 */ - BundleCheckData offer_data = { - 3, - 1, - bundle, - offer_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. */ - BundleCheckData answer_data = { - 3, - 3, - bundle, - answer_bundle_only, - }; - struct validate_sdp offer = { _check_bundled_sdp_media, &offer_data }; - struct validate_sdp answer = { _check_bundled_sdp_media, &answer_data }; - - gst_util_set_object_arg (G_OBJECT (t->webrtc1), "bundle-policy", - "max-bundle"); gst_util_set_object_arg (G_OBJECT (t->webrtc2), "bundle-policy", "max-bundle"); + 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->offer_data = &offer; - t->on_offer_created = _check_validate_sdp; - t->answer_data = &answer; - t->on_answer_created = _check_validate_sdp; t->on_ice_candidate = NULL; + t->on_pad_added = _pad_added_fakesink; fail_if (gst_element_set_state (t->webrtc1, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); fail_if (gst_element_set_state (t->webrtc2, - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); g_signal_emit_by_name (t->webrtc1, "create-data-channel", "label", NULL, &channel); - test_webrtc_create_offer (t, t->webrtc1); + test_validate_sdp_full (t, &offer, &answer, 0, FALSE); - test_webrtc_wait_for_answer_error_eos (t); - fail_unless_equals_int (STATE_ANSWER_CREATED, t->state); + 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); @@ -2336,6 +2575,176 @@ GST_START_TEST (test_bundle_audio_video_data) 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); + g_object_set (transceiver, "direction", + GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE, NULL); + 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) { @@ -2370,6 +2779,12 @@ webrtcbin_suite (void) 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); @@ -2380,9 +2795,11 @@ 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 are disabled. sctpenc %p, sctpdec %p", sctpenc, + "All datachannel tests are disabled. sctpenc %p, sctpdec %p", sctpenc, sctpdec); } } else { diff --git a/tests/examples/webrtc/meson.build b/tests/examples/webrtc/meson.build index 716bcf3..3ed7e6e 100644 --- a/tests/examples/webrtc/meson.build +++ b/tests/examples/webrtc/meson.build @@ -1,4 +1,4 @@ -examples = ['webrtc', 'webrtcbidirectional', 'webrtcswap', 'webrtctransceiver'] +examples = ['webrtc', 'webrtcbidirectional', 'webrtcswap', 'webrtctransceiver', 'webrtcrenego'] foreach example : examples exe_name = example @@ -9,7 +9,7 @@ foreach example : examples install: false, include_directories : [configinc], dependencies : [glib_dep, gst_dep, gstwebrtc_dep], - c_args : ['-DHAVE_CONFIG_H=1', '-DGST_USE_UNSTABLE_API'], + c_args : gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API'], ) endforeach diff --git a/tests/examples/webrtc/webrtc-sendrecv.c b/tests/examples/webrtc/webrtc-sendrecv.c deleted file mode 100644 index 7553691..0000000 --- a/tests/examples/webrtc/webrtc-sendrecv.c +++ /dev/null @@ -1,1028 +0,0 @@ -/* - * Demo gstreamer app for negotiating and streaming a sendrecv webrtc stream - * with a browser JS app. - * - * gcc webrtc-sendrecv.c $(pkg-config --cflags --libs gstreamer-webrtc-1.0 gstreamer-sdp-1.0 libsoup-2.4 json-glib-1.0) -o webrtc-sendrecv - * - * Author: Nirbheek Chauhan - */ -#include -#include - -#ifndef GST_USE_UNSTABLE_API -#define GST_USE_UNSTABLE_API -#endif -#include - -/* For signalling */ -#include -#include - -#include -#define HTTP_PROXY "http://10.112.1.184:8080" -#define ENTER g_print ("%s:%d>%s\n",__FILE__, __LINE__, __FUNCTION__); -enum AppState { - APP_STATE_UNKNOWN = 0, - APP_STATE_ERROR = 1, /* generic error */ - SERVER_CONNECTING = 1000, - SERVER_CONNECTION_ERROR, - SERVER_CONNECTED, /* Ready to register */ - SERVER_REGISTERING = 2000, - SERVER_REGISTRATION_ERROR, - SERVER_REGISTERED, /* Ready to call a peer */ - SERVER_CLOSED, /* server connection closed by us or the server */ - PEER_CONNECTING = 3000, - PEER_CONNECTION_ERROR, - PEER_CONNECTED, - PEER_CALL_NEGOTIATING = 4000, - PEER_CALL_WAITING, - PEER_CALL_STARTED, - PEER_CALL_STOPPING, - PEER_CALL_STOPPED, - PEER_CALL_ERROR, -}; - -static GMainLoop *loop; -static GstElement *pipe1, *webrtc1; -static GObject *send_channel, *receive_channel; - -static SoupWebsocketConnection *ws_conn = NULL; -static enum AppState app_state = 0; -static const gchar *peer_id = NULL; -static const gchar *server_url = "wss://webrtc.nirbheek.in:8443"; -static gboolean disable_ssl = FALSE; -static gboolean remote_is_offerer = FALSE; -static gboolean use_camera_mic = FALSE; -static gboolean use_proxy = FALSE; -static gint32 our_id = 0; - -static GOptionEntry entries[] = -{ - { "peer-id", 0, 0, G_OPTION_ARG_STRING, &peer_id, "String ID of the peer to connect to", "ID" }, - { "server", 0, 0, G_OPTION_ARG_STRING, &server_url, "Signalling server to connect to", "URL" }, - { "disable-ssl", 0, 0, G_OPTION_ARG_NONE, &disable_ssl, "Disable ssl", NULL }, - { "remote-offerer", 0, 0, G_OPTION_ARG_NONE, &remote_is_offerer, "Request that the peer generate the offer and we'll answer", NULL }, - { "use-camera-mic", 0, 0, G_OPTION_ARG_NONE, &use_camera_mic, "Use camera and mic", NULL }, - { "use-proxy", 0, 0, G_OPTION_ARG_NONE, &use_proxy, "Use proxy", NULL }, - { NULL }, -}; - -static gboolean -cleanup_and_quit_loop (const gchar * msg, enum AppState state) -{ - ENTER; - - if (msg) - g_printerr ("%s\n", msg); - if (state > 0) - app_state = state; - - if (ws_conn) { - if (soup_websocket_connection_get_state (ws_conn) == - SOUP_WEBSOCKET_STATE_OPEN) - /* This will call us again */ - soup_websocket_connection_close (ws_conn, 1000, ""); - else - g_object_unref (ws_conn); - } - - if (loop) { - g_main_loop_quit (loop); - loop = NULL; - } - - /* To allow usage as a GSourceFunc */ - return G_SOURCE_REMOVE; -} - -static gchar* -get_string_from_json_object (JsonObject * object) -{ - JsonNode *root; - JsonGenerator *generator; - gchar *text; - ENTER; - - /* Make it the root node */ - root = json_node_init_object (json_node_alloc (), object); - generator = json_generator_new (); - json_generator_set_root (generator, root); - text = json_generator_to_data (generator, NULL); - - /* Release everything */ - g_object_unref (generator); - json_node_free (root); - return text; -} - -static void -handle_media_stream (GstPad * pad, GstElement * pipe, const char * convert_name, - const char * sink_name) -{ - GstPad *qpad; - GstElement *q, *conv, *resample, *sink; - GstPadLinkReturn ret; - ENTER; - - g_print ("Trying to handle stream with %s ! %s", convert_name, sink_name); - - q = gst_element_factory_make ("queue", NULL); - g_assert_nonnull (q); - conv = gst_element_factory_make (convert_name, NULL); - g_assert_nonnull (conv); - sink = gst_element_factory_make (sink_name, NULL); - g_assert_nonnull (sink); - - if (g_strcmp0 (convert_name, "audioconvert") == 0) { - /* Might also need to resample, so add it just in case. - * Will be a no-op if it's not required. */ - resample = gst_element_factory_make ("audioresample", NULL); - g_assert_nonnull (resample); - gst_bin_add_many (GST_BIN (pipe), q, conv, resample, sink, NULL); - gst_element_sync_state_with_parent (q); - gst_element_sync_state_with_parent (conv); - gst_element_sync_state_with_parent (resample); - gst_element_sync_state_with_parent (sink); - gst_element_link_many (q, conv, resample, sink, NULL); - } else { - gst_bin_add_many (GST_BIN (pipe), q, conv, sink, NULL); - gst_element_sync_state_with_parent (q); - gst_element_sync_state_with_parent (conv); - gst_element_sync_state_with_parent (sink); - gst_element_link_many (q, conv, sink, NULL); - } - - qpad = gst_element_get_static_pad (q, "sink"); - - ret = gst_pad_link (pad, qpad); - g_assert_cmphex (ret, ==, GST_PAD_LINK_OK); -} - -static void -on_incoming_decodebin_stream (GstElement * decodebin, GstPad * pad, - GstElement * pipe) -{ - GstCaps *caps; - const gchar *name; - ENTER; - - if (!gst_pad_has_current_caps (pad)) { - g_printerr ("Pad '%s' has no caps, can't do anything, ignoring\n", - GST_PAD_NAME (pad)); - return; - } - - caps = gst_pad_get_current_caps (pad); - name = gst_structure_get_name (gst_caps_get_structure (caps, 0)); - - if (g_str_has_prefix (name, "video")) { - handle_media_stream (pad, pipe, "videoconvert", "autovideosink"); - } else if (g_str_has_prefix (name, "audio")) { - handle_media_stream (pad, pipe, "audioconvert", "autoaudiosink"); - } else { - g_printerr ("Unknown pad %s, ignoring", GST_PAD_NAME (pad)); - } -} - -static void -on_incoming_stream (GstElement * webrtc, GstPad * pad, GstElement * pipe) -{ - GstElement *decodebin; - GstPad *sinkpad; - ENTER; - - if (GST_PAD_DIRECTION (pad) != GST_PAD_SRC) - return; - - decodebin = gst_element_factory_make ("decodebin", NULL); - g_signal_connect (decodebin, "pad-added", - G_CALLBACK (on_incoming_decodebin_stream), pipe); - gst_bin_add (GST_BIN (pipe), decodebin); - gst_element_sync_state_with_parent (decodebin); - - sinkpad = gst_element_get_static_pad (decodebin, "sink"); - gst_pad_link (pad, sinkpad); - gst_object_unref (sinkpad); -} - -static void -send_ice_candidate_message (GstElement * webrtc G_GNUC_UNUSED, guint mlineindex, - gchar * candidate, gpointer user_data G_GNUC_UNUSED) -{ - gchar *text; - JsonObject *ice, *msg; - ENTER; - - if (app_state < PEER_CALL_NEGOTIATING) { - cleanup_and_quit_loop ("Can't send ICE, not in call", APP_STATE_ERROR); - return; - } - - ice = json_object_new (); - json_object_set_string_member (ice, "candidate", candidate); - json_object_set_int_member (ice, "sdpMLineIndex", mlineindex); - msg = json_object_new (); - json_object_set_object_member (msg, "ice", ice); - text = get_string_from_json_object (msg); - json_object_unref (msg); - - soup_websocket_connection_send_text (ws_conn, text); - g_free (text); -} - -static void -send_sdp_to_peer (GstWebRTCSessionDescription *desc) -{ - gchar *text; - JsonObject *msg, *sdp; - ENTER; - - if (app_state < PEER_CALL_NEGOTIATING) { - cleanup_and_quit_loop ("Can't send SDP to peer, not in call", APP_STATE_ERROR); - return; - } - - text = gst_sdp_message_as_text (desc->sdp); - sdp = json_object_new (); - - if (desc->type == GST_WEBRTC_SDP_TYPE_OFFER) { - g_print ("Sending offer:\n%s\n", text); - json_object_set_string_member (sdp, "type", "offer"); - } - else if (desc->type == GST_WEBRTC_SDP_TYPE_ANSWER) { - g_print ("Sending answer:\n%s\n", text); - json_object_set_string_member (sdp, "type", "answer"); - } - else { - g_assert_not_reached (); - } - - json_object_set_string_member (sdp, "sdp", text); - g_free (text); - - msg = json_object_new (); - json_object_set_object_member (msg, "sdp", sdp); - text = get_string_from_json_object (msg); - json_object_unref (msg); - - soup_websocket_connection_send_text (ws_conn, text); - g_free (text); -} - -/* Offer created by our pipeline, to be sent to the peer */ -static void -on_offer_created (GstPromise * promise, gpointer user_data) -{ - GstWebRTCSessionDescription *offer = NULL; - const GstStructure *reply; - ENTER; - - g_assert_cmphex (app_state, ==, PEER_CALL_NEGOTIATING); - - g_assert_cmphex (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); - - promise = gst_promise_new (); - g_signal_emit_by_name (webrtc1, "set-local-description", offer, promise); - gst_promise_interrupt (promise); - gst_promise_unref (promise); - - /* Send offer to peer */ - send_sdp_to_peer (offer); - gst_webrtc_session_description_free (offer); -} - -static void -on_negotiation_needed (GstElement * element, gpointer user_data) -{ - app_state = PEER_CALL_NEGOTIATING; - ENTER; - - if (remote_is_offerer) { - gchar *msg = g_strdup_printf ("OFFER_REQUEST"); - soup_websocket_connection_send_text (ws_conn, msg); - g_free (msg); - } else { - GstPromise *promise; - promise = gst_promise_new_with_change_func (on_offer_created, user_data, NULL);; - g_signal_emit_by_name (webrtc1, "create-offer", NULL, promise); - } -} - -#define STUN_SERVER " stun-server=stun://stun.l.google.com:19302 " -#define RTP_CAPS_OPUS "application/x-rtp,media=audio,encoding-name=OPUS,payload=" -#define RTP_CAPS_VP8 "application/x-rtp,media=video,encoding-name=VP8,payload=" - -static void -data_channel_on_error (GObject * dc, gpointer user_data) -{ - ENTER; - - cleanup_and_quit_loop ("Data channel error", 0); -} - -static void -data_channel_on_open (GObject * dc, gpointer user_data) -{ - GBytes *bytes = g_bytes_new ("data", strlen("data")); - ENTER; - - g_print ("data channel opened\n"); - g_signal_emit_by_name (dc, "send-string", "Hi! from GStreamer"); - g_signal_emit_by_name (dc, "send-data", bytes); - g_bytes_unref (bytes); -} - -static void -data_channel_on_close (GObject * dc, gpointer user_data) -{ - ENTER; - - cleanup_and_quit_loop ("Data channel closed", 0); -} - -static void -data_channel_on_message_string (GObject * dc, gchar *str, gpointer user_data) -{ - ENTER; - - g_print ("Received data channel message: %s\n", str); -} - -static void -connect_data_channel_signals (GObject * data_channel) -{ - ENTER; - - g_signal_connect (data_channel, "on-error", G_CALLBACK (data_channel_on_error), - NULL); - g_signal_connect (data_channel, "on-open", G_CALLBACK (data_channel_on_open), - NULL); - g_signal_connect (data_channel, "on-close", G_CALLBACK (data_channel_on_close), - NULL); - g_signal_connect (data_channel, "on-message-string", G_CALLBACK (data_channel_on_message_string), - NULL); -} - -static void -on_data_channel (GstElement * webrtc, GObject * data_channel, gpointer user_data) -{ - ENTER; - - connect_data_channel_signals (data_channel); - receive_channel = data_channel; -} - -static void -on_ice_gathering_state_notify (GstElement * webrtcbin, GParamSpec * pspec, - gpointer user_data) -{ - GstWebRTCICEGatheringState ice_gather_state; - const gchar *new_state = "unknown"; - ENTER; - - g_object_get (webrtcbin, "ice-gathering-state", &ice_gather_state, - NULL); - switch (ice_gather_state) { - case GST_WEBRTC_ICE_GATHERING_STATE_NEW: - new_state = "new"; - break; - case GST_WEBRTC_ICE_GATHERING_STATE_GATHERING: - new_state = "gathering"; - break; - case GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE: - new_state = "complete"; - break; - } - g_print ("ICE gathering state changed to %s\n", new_state); -} - -static gboolean -start_pipeline (void) -{ - GstStateChangeReturn ret; - GError *error = NULL; - ENTER; - - if (!use_camera_mic) - pipe1 = - gst_parse_launch ("webrtcbin bundle-policy=max-bundle name=sendrecv " STUN_SERVER - "videotestsrc is-live=true pattern=ball ! videoconvert ! queue ! vp8enc deadline=1 ! rtpvp8pay ! " - "queue ! " RTP_CAPS_VP8 "96 ! sendrecv. " - "audiotestsrc is-live=true wave=red-noise ! audioconvert ! audioresample ! queue ! opusenc ! rtpopuspay ! " - "queue ! " RTP_CAPS_OPUS "97 ! sendrecv. ", - &error); - else - pipe1 = - gst_parse_launch ("webrtcbin bundle-policy=max-bundle name=sendrecv " STUN_SERVER - "camerasrc camera-id=1 ! ""video/x-raw,format=I420,width=352,height=288"" ! queue ! vp8enc deadline=1 ! rtpvp8pay ! " //avenc_h263 ! rtph263pay ! " - "queue ! " RTP_CAPS_VP8 "96 ! sendrecv. " - "pulsesrc ! audioconvert ! audioresample ! queue ! opusenc ! rtpopuspay ! " - "queue ! " RTP_CAPS_OPUS "97 ! sendrecv. ", - &error); - - if (error) { - g_printerr ("Failed to parse launch: %s\n", error->message); - g_error_free (error); - goto err; - } - - webrtc1 = gst_bin_get_by_name (GST_BIN (pipe1), "sendrecv"); - g_assert_nonnull (webrtc1); - - /* This is the gstwebrtc entry point where we create the offer and so on. It - * will be called when the pipeline goes to PLAYING. */ - g_signal_connect (webrtc1, "on-negotiation-needed", - G_CALLBACK (on_negotiation_needed), NULL); - /* We need to transmit this ICE candidate to the browser via the websockets - * signalling server. Incoming ice candidates from the browser need to be - * added by us too, see on_server_message() */ - g_signal_connect (webrtc1, "on-ice-candidate", - G_CALLBACK (send_ice_candidate_message), NULL); - g_signal_connect (webrtc1, "notify::ice-gathering-state", - G_CALLBACK (on_ice_gathering_state_notify), NULL); - - gst_element_set_state (pipe1, GST_STATE_READY); - - g_signal_emit_by_name (webrtc1, "create-data-channel", "channel", NULL, - &send_channel); - if (send_channel) { - g_print ("Created data channel\n"); - connect_data_channel_signals (send_channel); - } else { - g_print ("Could not create data channel, is usrsctp available?\n"); - } - - g_signal_connect (webrtc1, "on-data-channel", G_CALLBACK (on_data_channel), - NULL); - /* Incoming streams will be exposed via this signal */ - g_signal_connect (webrtc1, "pad-added", G_CALLBACK (on_incoming_stream), - pipe1); - /* Lifetime is the same as the pipeline itself */ - gst_object_unref (webrtc1); - - g_print ("Starting pipeline\n"); - ret = gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_PLAYING); - if (ret == GST_STATE_CHANGE_FAILURE) - goto err; - - return TRUE; - -err: - if (pipe1) - g_clear_object (&pipe1); - if (webrtc1) - webrtc1 = NULL; - return FALSE; -} - -static gboolean -start_pipeline_answer (void) -{ - GstStateChangeReturn ret; - GError *error = NULL; - ENTER; - - if (!use_camera_mic) - pipe1 = - gst_parse_launch ("webrtcbin bundle-policy=max-bundle name=sendrecv " STUN_SERVER - "videotestsrc is-live=true pattern=ball ! videoconvert ! queue ! vp8enc deadline=1 ! rtpvp8pay ! " - "queue ! " RTP_CAPS_VP8 "96 ! sendrecv. " - "audiotestsrc is-live=true wave=red-noise ! audioconvert ! audioresample ! queue ! opusenc ! rtpopuspay ! " - "queue ! " RTP_CAPS_OPUS "97 ! sendrecv. ", - &error); - else - pipe1 = - gst_parse_launch ("webrtcbin bundle-policy=max-bundle name=sendrecv " STUN_SERVER - "camerasrc camera-id=1 ! ""video/x-raw,format=I420,width=352,height=288"" ! queue ! vp8enc deadline=1 ! rtpvp8pay ! " //avenc_h263 ! rtph263pay ! " - "queue ! " RTP_CAPS_VP8 "96 ! sendrecv. " - "pulsesrc ! audioconvert ! audioresample ! queue ! opusenc ! rtpopuspay ! " - "queue ! " RTP_CAPS_OPUS "97 ! sendrecv. ", - &error); - - if (error) { - g_printerr ("Failed to parse launch: %s\n", error->message); - g_error_free (error); - goto err; - } - - webrtc1 = gst_bin_get_by_name (GST_BIN (pipe1), "sendrecv"); - g_assert_nonnull (webrtc1); - - /* We need to transmit this ICE candidate to the browser via the websockets - * signalling server. Incoming ice candidates from the browser need to be - * added by us too, see on_server_message() */ - g_signal_connect (webrtc1, "on-ice-candidate", - G_CALLBACK (send_ice_candidate_message), NULL); - g_signal_connect (webrtc1, "notify::ice-gathering-state", - G_CALLBACK (on_ice_gathering_state_notify), NULL); - - gst_element_set_state (pipe1, GST_STATE_READY); - - g_signal_emit_by_name (webrtc1, "create-data-channel", "channel", NULL, - &send_channel); - if (send_channel) { - g_print ("Created data channel\n"); - connect_data_channel_signals (send_channel); - } else { - g_print ("Could not create data channel, is usrsctp available?\n"); - } - - g_signal_connect (webrtc1, "on-data-channel", G_CALLBACK (on_data_channel), - NULL); - /* Incoming streams will be exposed via this signal */ - g_signal_connect (webrtc1, "pad-added", G_CALLBACK (on_incoming_stream), - pipe1); - /* Lifetime is the same as the pipeline itself */ - gst_object_unref (webrtc1); - - g_print ("Starting pipeline, our id(%d)\n", our_id); - ret = gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_PLAYING); - if (ret == GST_STATE_CHANGE_FAILURE) - goto err; - - return TRUE; - -err: - if (pipe1) - g_clear_object (&pipe1); - if (webrtc1) - webrtc1 = NULL; - return FALSE; -} - - -static gboolean -setup_call (void) -{ - gchar *msg; - ENTER; - - if (soup_websocket_connection_get_state (ws_conn) != - SOUP_WEBSOCKET_STATE_OPEN) - return FALSE; - - if (!peer_id) - return FALSE; - - g_print ("Setting up signalling server call with %s\n", peer_id); - app_state = PEER_CONNECTING; - msg = g_strdup_printf ("SESSION %s", peer_id); - soup_websocket_connection_send_text (ws_conn, msg); - g_free (msg); - return TRUE; -} - -static gint32 -register_with_server (void) -{ - gchar *hello; - gint32 our_id; - ENTER; - - if (soup_websocket_connection_get_state (ws_conn) != - SOUP_WEBSOCKET_STATE_OPEN) - return -1; - - our_id = g_random_int_range (10, 10000); - g_print ("Registering id %i with server\n", our_id); - app_state = SERVER_REGISTERING; - - /* Register with the server with a random integer id. Reply will be received - * by on_server_message() */ - hello = g_strdup_printf ("HELLO %i", our_id); - soup_websocket_connection_send_text (ws_conn, hello); - g_free (hello); - - return our_id; -} - -static void -on_server_closed (SoupWebsocketConnection * conn G_GNUC_UNUSED, - gpointer user_data G_GNUC_UNUSED) -{ - app_state = SERVER_CLOSED; - ENTER; - - cleanup_and_quit_loop ("Server connection closed", 0); -} - -/* Answer created by our pipeline, to be sent to the peer */ -static void -on_answer_created (GstPromise * promise, gpointer user_data) -{ - GstWebRTCSessionDescription *answer = NULL; - const GstStructure *reply; - ENTER; - - if (peer_id) - g_assert_cmphex (app_state, ==, PEER_CALL_NEGOTIATING); - else - g_assert_cmphex (app_state, ==, PEER_CALL_WAITING); - - g_assert_cmphex (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); - - promise = gst_promise_new (); - g_signal_emit_by_name (webrtc1, "set-local-description", answer, promise); - gst_promise_interrupt (promise); - gst_promise_unref (promise); - - /* Send answer to peer */ - send_sdp_to_peer (answer); - gst_webrtc_session_description_free (answer); -} - -static void -on_offer_received (GstSDPMessage *sdp) -{ - GstWebRTCSessionDescription *offer = NULL; - GstPromise *promise; - ENTER; - - offer = gst_webrtc_session_description_new (GST_WEBRTC_SDP_TYPE_OFFER, sdp); - g_assert_nonnull (offer); - - /* Set remote description on our pipeline */ - { - promise = gst_promise_new (); - g_signal_emit_by_name (webrtc1, "set-remote-description", offer, - promise); - gst_promise_interrupt (promise); - gst_promise_unref (promise); - } - gst_webrtc_session_description_free (offer); - - promise = gst_promise_new_with_change_func (on_answer_created, NULL, - NULL); - g_signal_emit_by_name (webrtc1, "create-answer", NULL, promise); -} - -/* One mega message handler for our asynchronous calling mechanism */ -static void -on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, - GBytes * message, gpointer user_data) -{ - gchar *text; - ENTER; - - switch (type) { - case SOUP_WEBSOCKET_DATA_BINARY: - g_printerr ("Received unknown binary message, ignoring\n"); - return; - case SOUP_WEBSOCKET_DATA_TEXT: { - gsize size; - const gchar *data = g_bytes_get_data (message, &size); - /* Convert to NULL-terminated string */ - text = g_strndup (data, size); - g_print ("Received text message, [%s]\n", text); - break; - } - default: - g_assert_not_reached (); - } - - /* Server has accepted our registration, we are ready to send commands */ - if (g_strcmp0 (text, "HELLO") == 0) { - if (app_state != SERVER_REGISTERING) { - cleanup_and_quit_loop ("ERROR: Received HELLO when not registering", - APP_STATE_ERROR); - goto out; - } - app_state = SERVER_REGISTERED; - g_print ("Registered with server\n"); - /* Ask signalling server to connect us with a specific peer */ - if (peer_id) { - if (!setup_call ()) { - cleanup_and_quit_loop ("ERROR: Failed to setup call", PEER_CALL_ERROR); - goto out; - } - } else { - /* should WAIT for another peer */ - g_print ("need to wait for another peer...(our id:%d)\n", our_id); - app_state = PEER_CALL_WAITING; - /* Start negotiation (exchange SDP and ICE candidates) */ - if (!start_pipeline_answer ()) - cleanup_and_quit_loop ("ERROR: failed to start pipeline", - PEER_CALL_ERROR); - } - /* Call has been setup by the server, now we can start negotiation */ - } else if (g_strcmp0 (text, "SESSION_OK") == 0) { - if (app_state != PEER_CONNECTING) { - cleanup_and_quit_loop ("ERROR: Received SESSION_OK when not calling", - PEER_CONNECTION_ERROR); - goto out; - } - - app_state = PEER_CONNECTED; - /* Start negotiation (exchange SDP and ICE candidates) */ - if (!start_pipeline ()) - cleanup_and_quit_loop ("ERROR: failed to start pipeline", - PEER_CALL_ERROR); - /* Handle errors */ - } else if (g_str_has_prefix (text, "ERROR")) { - switch (app_state) { - case SERVER_CONNECTING: - app_state = SERVER_CONNECTION_ERROR; - break; - case SERVER_REGISTERING: - app_state = SERVER_REGISTRATION_ERROR; - break; - case PEER_CONNECTING: - app_state = PEER_CONNECTION_ERROR; - break; - case PEER_CALL_WAITING: - case PEER_CONNECTED: - case PEER_CALL_NEGOTIATING: - app_state = PEER_CALL_ERROR; - break; - default: - app_state = APP_STATE_ERROR; - } - cleanup_and_quit_loop (text, 0); - /* Look for JSON messages containing SDP and ICE candidates */ - } else { - JsonNode *root; - JsonObject *object, *child; - JsonParser *parser = json_parser_new (); - if (!json_parser_load_from_data (parser, text, -1, NULL)) { - g_printerr ("Unknown message '%s', ignoring", text); - g_object_unref (parser); - goto out; - } - - root = json_parser_get_root (parser); - if (!JSON_NODE_HOLDS_OBJECT (root)) { - g_printerr ("Unknown json message '%s', ignoring", text); - g_object_unref (parser); - goto out; - } - - object = json_node_get_object (root); - /* Check type of JSON message */ - if (json_object_has_member (object, "sdp")) { - int ret; - GstSDPMessage *sdp; - const gchar *text, *sdptype; - GstWebRTCSessionDescription *answer; - - if (peer_id) - g_assert_cmphex (app_state, ==, PEER_CALL_NEGOTIATING); - else - g_assert_cmphex (app_state, ==, PEER_CALL_WAITING); - - child = json_object_get_object_member (object, "sdp"); - - if (!json_object_has_member (child, "type")) { - cleanup_and_quit_loop ("ERROR: received SDP without 'type'", - PEER_CALL_ERROR); - goto out; - } - - sdptype = json_object_get_string_member (child, "type"); - /* In this example, we create the offer and receive one answer by default, - * but it's possible to comment out the offer creation and wait for an offer - * instead, so we handle either here. - * - * See tests/examples/webrtcbidirectional.c in gst-plugins-bad for another - * example how to handle offers from peers and reply with answers using webrtcbin. */ - text = json_object_get_string_member (child, "sdp"); - ret = gst_sdp_message_new (&sdp); - g_assert_cmphex (ret, ==, GST_SDP_OK); - ret = gst_sdp_message_parse_buffer ((guint8 *) text, strlen (text), sdp); - g_assert_cmphex (ret, ==, GST_SDP_OK); - - if (g_str_equal (sdptype, "answer")) { - g_print ("Received answer:\n%s\n", text); - answer = gst_webrtc_session_description_new (GST_WEBRTC_SDP_TYPE_ANSWER, - sdp); - g_assert_nonnull (answer); - - /* Set remote description on our pipeline */ - { - GstPromise *promise = gst_promise_new (); - g_signal_emit_by_name (webrtc1, "set-remote-description", answer, - promise); - gst_promise_interrupt (promise); - gst_promise_unref (promise); - } - app_state = PEER_CALL_STARTED; - } - else { - g_print ("Received offer:\n%s\n", text); - on_offer_received (sdp); - } - - } else if (json_object_has_member (object, "ice")) { - const gchar *candidate; - gint sdpmlineindex; - - child = json_object_get_object_member (object, "ice"); - candidate = json_object_get_string_member (child, "candidate"); - sdpmlineindex = json_object_get_int_member (child, "sdpMLineIndex"); - - /* Add ice candidate sent by remote peer */ - g_signal_emit_by_name (webrtc1, "add-ice-candidate", sdpmlineindex, - candidate); - } else { - g_printerr ("Ignoring unknown JSON message:\n%s\n", text); - } - g_object_unref (parser); - } - -out: - g_free (text); -} - -static void -on_server_connected (SoupSession * session, GAsyncResult * res, - SoupMessage *msg) -{ - GError *error = NULL; - ENTER; - - g_print("on_server_connected\n"); - ws_conn = soup_session_websocket_connect_finish (session, res, &error); - if (error) { - cleanup_and_quit_loop (error->message, SERVER_CONNECTION_ERROR); - g_error_free (error); - return; - } - - g_assert_nonnull (ws_conn); - - app_state = SERVER_CONNECTED; - g_print ("Connected to signalling server\n"); - - g_signal_connect (ws_conn, "closed", G_CALLBACK (on_server_closed), NULL); - g_signal_connect (ws_conn, "message", G_CALLBACK (on_server_message), NULL); - - /* Register with the server so it knows about us and can accept commands */ - our_id = register_with_server (); -} - -/* - * Connect to the signalling server. This is the entrypoint for everything else. - */ - -/* TIZEN: add for log */ -static inline gchar -gst_soup_util_log_make_level_tag (SoupLoggerLogLevel level) -{ - gchar c; - - if (G_UNLIKELY ((gint) level > 9)) - return '?'; - - switch (level) { - case SOUP_LOGGER_LOG_MINIMAL: - c = 'M'; - break; - case SOUP_LOGGER_LOG_HEADERS: - c = 'H'; - break; - case SOUP_LOGGER_LOG_BODY: - c = 'B'; - break; - default: - /* Unknown level. If this is hit libsoup likely added a new - * log level to SoupLoggerLogLevel and it should be added - * as a case */ - c = level + '0'; - break; - } - return c; -} - -static void -_log_printer_cb (SoupLogger G_GNUC_UNUSED * logger, - SoupLoggerLogLevel level, char direction, const char *data, - gpointer user_data) -{ - gchar c; - - c = gst_soup_util_log_make_level_tag (level); - g_print("HTTP_SESSION(%c): %c %s\n", c, direction, data); -} - -static void -connect_to_websocket_server_async (void) -{ - SoupLogger *logger; - SoupMessage *message; - SoupSession *session; - SoupURI *proxy_uri; - const char *https_aliases[] = {"wss", NULL}; - ENTER; - - if (!use_proxy){ - session = soup_session_new_with_options (SOUP_SESSION_SSL_STRICT, !disable_ssl, - SOUP_SESSION_HTTPS_ALIASES, https_aliases, NULL); - } else { - proxy_uri = soup_uri_new (HTTP_PROXY); - session = soup_session_new_with_options (SOUP_SESSION_SSL_STRICT, !disable_ssl, - SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, - SOUP_SESSION_PROXY_URI, proxy_uri, - SOUP_SESSION_SSL_CA_FILE, "/opt/var/lib/ca-certificates/ca-bundle.pem", - SOUP_SESSION_HTTPS_ALIASES, https_aliases, NULL); - soup_uri_free (proxy_uri); - } - - logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1); - - /* TIZEN: add for log */ - soup_logger_set_printer (logger, _log_printer_cb, NULL, NULL); - - soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger)); - g_object_unref (logger); - - message = soup_message_new (SOUP_METHOD_GET, server_url); - - g_print ("Connecting to server[%s]...\n", server_url); - - /* Once connected, we will register */ - soup_session_websocket_connect_async (session, message, NULL, NULL, NULL, - (GAsyncReadyCallback) on_server_connected, message); - app_state = SERVER_CONNECTING; -} - -static gboolean -check_plugins (void) -{ - int i; - gboolean ret; - GstPlugin *plugin; - GstRegistry *registry; - const gchar *needed[] = { "opus", "vpx", "nice", "webrtc", "dtls", "srtp", - "rtpmanager", "videotestsrc", "audiotestsrc", NULL}; - ENTER; - - registry = gst_registry_get (); - ret = TRUE; - for (i = 0; i < g_strv_length ((gchar **) needed); i++) { - plugin = gst_registry_find_plugin (registry, needed[i]); - if (!plugin) { - g_print ("Required gstreamer plugin '%s' not found\n", needed[i]); - ret = FALSE; - continue; - } - gst_object_unref (plugin); - } - return ret; -} - -int -main (int argc, char *argv[]) -{ - GOptionContext *context; - GError *error = NULL; - ENTER; - - context = g_option_context_new ("- gstreamer webrtc sendrecv demo"); - g_option_context_add_main_entries (context, entries, NULL); - g_option_context_add_group (context, gst_init_get_option_group ()); - if (!g_option_context_parse (context, &argc, &argv, &error)) { - g_printerr ("Error initializing: %s\n", error->message); - return -1; - } - - if (!check_plugins ()) - return -1; -#if 0 - if (!peer_id) { - g_printerr ("--peer-id is a required argument\n"); - return -1; - } -#endif - - /* Disable ssl when running a localhost server, because - * it's probably a test server with a self-signed certificate */ - { - GstUri *uri = gst_uri_from_string (server_url); - if (g_strcmp0 ("localhost", gst_uri_get_host (uri)) == 0 || - g_strcmp0 ("127.0.0.1", gst_uri_get_host (uri)) == 0) - disable_ssl = TRUE; - gst_uri_unref (uri); - } - - loop = g_main_loop_new (NULL, FALSE); - - connect_to_websocket_server_async (); - - g_main_loop_run (loop); - g_main_loop_unref (loop); - - if (pipe1) { - gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_NULL); - g_print ("Pipeline stopped\n"); - gst_object_unref (pipe1); - } - - return 0; -} diff --git a/tests/examples/webrtc/webrtcbidirectional.c b/tests/examples/webrtc/webrtcbidirectional.c index 2b8bf11..13acc86 100644 --- a/tests/examples/webrtc/webrtcbidirectional.c +++ b/tests/examples/webrtc/webrtcbidirectional.c @@ -99,7 +99,7 @@ _on_answer_received (GstPromise * promise, gpointer user_data) /* 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, NULL); + g_signal_emit_by_name (webrtc2, "set-local-description", answer, promise); gst_promise_interrupt (promise); gst_promise_unref (promise); diff --git a/tests/examples/webrtc/webrtcrenego.c b/tests/examples/webrtc/webrtcrenego.c new file mode 100644 index 0000000..e80a3b3 --- /dev/null +++ b/tests/examples/webrtc/webrtcrenego.c @@ -0,0 +1,289 @@ +#include +#include +#include + +#include + +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); + g_object_set (transceiver, "direction", + GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE, NULL); + + 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; +} -- 2.7.4