sendrecv: Add a switch for remote-offerer

Add a switch to the command line utility that makes it request
the initial offer from the peer instead of generating it.

Modify the webrtc.js example to support a new REQUEST_OFFER
message, and generate the offer when receiving it.
This commit is contained in:
Jan Schmidt 2020-03-05 03:03:17 +11:00
parent c8e79c9671
commit 5bf67feae8
2 changed files with 165 additions and 55 deletions

View file

@ -47,12 +47,14 @@ static enum AppState app_state = 0;
static const gchar *peer_id = NULL; static const gchar *peer_id = NULL;
static const gchar *server_url = "wss://webrtc.nirbheek.in:8443"; static const gchar *server_url = "wss://webrtc.nirbheek.in:8443";
static gboolean disable_ssl = FALSE; static gboolean disable_ssl = FALSE;
static gboolean remote_is_offerer = FALSE;
static GOptionEntry entries[] = static GOptionEntry entries[] =
{ {
{ "peer-id", 0, 0, G_OPTION_ARG_STRING, &peer_id, "String ID of the peer to connect to", "ID" }, { "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" }, { "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 }, { "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 },
{ NULL }, { NULL },
}; };
@ -213,21 +215,31 @@ send_ice_candidate_message (GstElement * webrtc G_GNUC_UNUSED, guint mlineindex,
} }
static void static void
send_sdp_offer (GstWebRTCSessionDescription * offer) send_sdp_to_peer (GstWebRTCSessionDescription *desc)
{ {
gchar *text; gchar *text;
JsonObject *msg, *sdp; JsonObject *msg, *sdp;
if (app_state < PEER_CALL_NEGOTIATING) { if (app_state < PEER_CALL_NEGOTIATING) {
cleanup_and_quit_loop ("Can't send offer, not in call", APP_STATE_ERROR); cleanup_and_quit_loop ("Can't send SDP to peer, not in call", APP_STATE_ERROR);
return; return;
} }
text = gst_sdp_message_as_text (offer->sdp); text = gst_sdp_message_as_text (desc->sdp);
g_print ("Sending offer:\n%s\n", text);
sdp = json_object_new (); 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"); 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); json_object_set_string_member (sdp, "sdp", text);
g_free (text); g_free (text);
@ -261,19 +273,25 @@ on_offer_created (GstPromise * promise, gpointer user_data)
gst_promise_unref (promise); gst_promise_unref (promise);
/* Send offer to peer */ /* Send offer to peer */
send_sdp_offer (offer); send_sdp_to_peer (offer);
gst_webrtc_session_description_free (offer); gst_webrtc_session_description_free (offer);
} }
static void static void
on_negotiation_needed (GstElement * element, gpointer user_data) on_negotiation_needed (GstElement * element, gpointer user_data)
{ {
GstPromise *promise;
app_state = PEER_CALL_NEGOTIATING; app_state = PEER_CALL_NEGOTIATING;
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);; promise = gst_promise_new_with_change_func (on_offer_created, user_data, NULL);;
g_signal_emit_by_name (webrtc1, "create-offer", NULL, promise); g_signal_emit_by_name (webrtc1, "create-offer", NULL, promise);
} }
}
#define STUN_SERVER " stun-server=stun://stun.l.google.com:19302 " #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_OPUS "application/x-rtp,media=audio,encoding-name=OPUS,payload="
@ -327,6 +345,29 @@ on_data_channel (GstElement * webrtc, GObject * data_channel, gpointer user_data
receive_channel = 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";
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 static gboolean
start_pipeline (void) start_pipeline (void)
{ {
@ -359,6 +400,8 @@ start_pipeline (void)
* added by us too, see on_server_message() */ * added by us too, see on_server_message() */
g_signal_connect (webrtc1, "on-ice-candidate", g_signal_connect (webrtc1, "on-ice-candidate",
G_CALLBACK (send_ice_candidate_message), NULL); 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); gst_element_set_state (pipe1, GST_STATE_READY);
@ -445,6 +488,55 @@ on_server_closed (SoupWebsocketConnection * conn G_GNUC_UNUSED,
cleanup_and_quit_loop ("Server connection closed", 0); 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;
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, "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;
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 */ /* One mega message handler for our asynchronous calling mechanism */
static void static void
on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type, on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type,
@ -550,21 +642,20 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type,
} }
sdptype = json_object_get_string_member (child, "type"); sdptype = json_object_get_string_member (child, "type");
/* In this example, we always create the offer and receive one answer. /* In this example, we create the offer and receive one answer by default,
* See tests/examples/webrtcbidirectional.c in gst-plugins-bad for how to * but it's possible to comment out the offer creation and wait for an offer
* handle offers from peers and reply with answers using webrtcbin. */ * instead, so we handle either here.
g_assert_cmpstr (sdptype, ==, "answer"); *
* 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"); text = json_object_get_string_member (child, "sdp");
g_print ("Received answer:\n%s\n", text);
ret = gst_sdp_message_new (&sdp); ret = gst_sdp_message_new (&sdp);
g_assert_cmphex (ret, ==, GST_SDP_OK); g_assert_cmphex (ret, ==, GST_SDP_OK);
ret = gst_sdp_message_parse_buffer ((guint8 *) text, strlen (text), sdp); ret = gst_sdp_message_parse_buffer ((guint8 *) text, strlen (text), sdp);
g_assert_cmphex (ret, ==, GST_SDP_OK); 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, answer = gst_webrtc_session_description_new (GST_WEBRTC_SDP_TYPE_ANSWER,
sdp); sdp);
g_assert_nonnull (answer); g_assert_nonnull (answer);
@ -577,8 +668,13 @@ on_server_message (SoupWebsocketConnection * conn, SoupWebsocketDataType type,
gst_promise_interrupt (promise); gst_promise_interrupt (promise);
gst_promise_unref (promise); gst_promise_unref (promise);
} }
app_state = PEER_CALL_STARTED; 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")) { } else if (json_object_has_member (object, "ice")) {
const gchar *candidate; const gchar *candidate;
gint sdpmlineindex; gint sdpmlineindex;

View file

@ -93,12 +93,16 @@ function onIncomingSDP(sdp) {
function onLocalDescription(desc) { function onLocalDescription(desc) {
console.log("Got local description: " + JSON.stringify(desc)); console.log("Got local description: " + JSON.stringify(desc));
peer_connection.setLocalDescription(desc).then(function() { peer_connection.setLocalDescription(desc).then(function() {
setStatus("Sending SDP answer"); setStatus("Sending SDP " + desc.type);
sdp = {'sdp': peer_connection.localDescription} sdp = {'sdp': peer_connection.localDescription}
ws_conn.send(JSON.stringify(sdp)); ws_conn.send(JSON.stringify(sdp));
}); });
} }
function generateOffer() {
peer_connection.createOffer().then(onLocalDescription).catch(setError);
}
// ICE candidate received from peer, add it to the peer connection // ICE candidate received from peer, add it to the peer connection
function onIncomingICE(ice) { function onIncomingICE(ice) {
var candidate = new RTCIceCandidate(ice); var candidate = new RTCIceCandidate(ice);
@ -116,6 +120,12 @@ function onServerMessage(event) {
handleIncomingError(event.data); handleIncomingError(event.data);
return; return;
} }
if (event.data.startsWith("OFFER_REQUEST")) {
// The peer wants us to set up and then send an offer
if (!peer_connection)
createCall(null).then (generateOffer);
}
else {
// Handle incoming JSON SDP and ICE messages // Handle incoming JSON SDP and ICE messages
try { try {
msg = JSON.parse(event.data); msg = JSON.parse(event.data);
@ -141,6 +151,7 @@ function onServerMessage(event) {
} }
} }
} }
}
function onServerClose(event) { function onServerClose(event) {
setStatus('Disconnected from server'); setStatus('Disconnected from server');
@ -286,7 +297,7 @@ function createCall(msg) {
return stream; return stream;
}).catch(setError); }).catch(setError);
if (!msg.sdp) { if (msg != null && !msg.sdp) {
console.log("WARNING: First message wasn't an SDP message!?"); console.log("WARNING: First message wasn't an SDP message!?");
} }
@ -300,5 +311,8 @@ function createCall(msg) {
ws_conn.send(JSON.stringify({'ice': event.candidate})); ws_conn.send(JSON.stringify({'ice': event.candidate}));
}; };
if (msg != null)
setStatus("Created peer connection for call, waiting for SDP"); setStatus("Created peer connection for call, waiting for SDP");
return local_stream_promise;
} }