mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-09-09 05:38:58 +00:00
SDP's are generated and consumed according to the W3C PeerConnection API available from https://www.w3.org/TR/webrtc/ The SDP is either created initially from the connected sink pads/attached transceivers as in the case of generating an offer or intersected with the connected sink pads/attached transceivers as in the case for creating an answer. In both cases, the rtp payloaded streams sent by the peer are exposed as separate src pads. The implementation supports trickle ICE, RTCP muxing, reduced size RTCP. With contributions from: Nirbheek Chauhan <nirbheek@centricular.com> Mathieu Duponchelle <mathieu@centricular.com> Edward Hervey <edward@centricular.com> https://bugzilla.gnome.org/show_bug.cgi?id=792523
215 lines
6.7 KiB
C
215 lines
6.7 KiB
C
#include <gst/gst.h>
|
|
#include <gst/sdp/sdp.h>
|
|
#include <gst/webrtc/webrtc.h>
|
|
|
|
#include <string.h>
|
|
|
|
static GMainLoop *loop;
|
|
static GstElement *pipe1, *webrtc1, *webrtc2;
|
|
static GstBus *bus1;
|
|
|
|
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;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
_webrtc_pad_added (GstElement * webrtc, GstPad * new_pad, GstElement * pipe)
|
|
{
|
|
GstElement *out = NULL;
|
|
GstPad *sink = NULL;
|
|
GstCaps *caps;
|
|
GstStructure *s;
|
|
const gchar *encoding_name;
|
|
|
|
if (GST_PAD_DIRECTION (new_pad) != GST_PAD_SRC)
|
|
return;
|
|
|
|
caps = gst_pad_get_current_caps (new_pad);
|
|
if (!caps)
|
|
caps = gst_pad_query_caps (new_pad, NULL);
|
|
GST_ERROR_OBJECT (new_pad, "caps %" GST_PTR_FORMAT, caps);
|
|
g_assert (gst_caps_is_fixed (caps));
|
|
s = gst_caps_get_structure (caps, 0);
|
|
encoding_name = gst_structure_get_string (s, "encoding-name");
|
|
if (g_strcmp0 (encoding_name, "VP8") == 0) {
|
|
out = gst_parse_bin_from_description ("rtpvp8depay ! vp8dec ! "
|
|
"videoconvert ! queue ! xvimagesink sync=false", TRUE, NULL);
|
|
} else if (g_strcmp0 (encoding_name, "OPUS") == 0) {
|
|
out = gst_parse_bin_from_description ("rtpopusdepay ! opusdec ! "
|
|
"audioconvert ! audioresample ! audiorate ! queue ! autoaudiosink",
|
|
TRUE, NULL);
|
|
} else {
|
|
g_critical ("Unknown encoding name %s", encoding_name);
|
|
g_assert_not_reached ();
|
|
}
|
|
gst_bin_add (GST_BIN (pipe), out);
|
|
gst_element_sync_state_with_parent (out);
|
|
sink = out->sinkpads->data;
|
|
|
|
gst_pad_link (new_pad, sink);
|
|
|
|
gst_caps_unref (caps);
|
|
}
|
|
|
|
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);
|
|
|
|
g_signal_emit_by_name (webrtc1, "set-remote-description", answer, NULL);
|
|
g_signal_emit_by_name (webrtc2, "set-local-description", answer, NULL);
|
|
|
|
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);
|
|
}
|
|
|
|
int
|
|
main (int argc, char *argv[])
|
|
{
|
|
gst_init (&argc, &argv);
|
|
|
|
loop = g_main_loop_new (NULL, FALSE);
|
|
pipe1 =
|
|
gst_parse_launch ("webrtcbin name=smpte webrtcbin name=ball "
|
|
"videotestsrc pattern=smpte ! queue ! vp8enc ! rtpvp8pay ! queue ! "
|
|
"application/x-rtp,media=video,payload=96,encoding-name=VP8 ! smpte.sink_0 "
|
|
"audiotestsrc ! opusenc ! rtpopuspay ! queue ! "
|
|
"application/x-rtp,media=audio,payload=97,encoding-name=OPUS ! smpte.sink_1 "
|
|
"videotestsrc pattern=ball ! queue ! vp8enc ! rtpvp8pay ! queue ! "
|
|
"application/x-rtp,media=video,payload=96,encoding-name=VP8 ! ball.sink_1 "
|
|
"audiotestsrc wave=saw ! opusenc ! rtpopuspay ! queue ! "
|
|
"application/x-rtp,media=audio,payload=97,encoding-name=OPUS ! ball.sink_0 ",
|
|
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_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;
|
|
}
|