Commit 177aa22b authored by Matthew Waters's avatar Matthew Waters 🐨

webrtc: Initial support for stream addition/removal

Limitations:
- No transport changes at all (ICE, DTLS)
- Codec changes are untested and probably don't work
- Stream removal doesn't remove transports (i.e. non-bundled transports
  will stay around until webrtcbin is shutdown)
- Unified Plan SDP only. No Plan-B support.
parent 015cb75f
......@@ -75,6 +75,7 @@ gst*orc.h
/tests/examples/webrtc/webrtc
/tests/examples/webrtc/webrtcbidirectional
/tests/examples/webrtc/webrtcswap
/tests/examples/webrtc/webrtcrenego
/tests/examples/webrtc/webrtctransceiver
Build
......
This diff is collapsed.
......@@ -131,6 +131,8 @@ struct _GstWebRTCBinPrivate
/* count of the number of media streams we've offered for uniqueness */
/* FIXME: overflow? */
guint media_counter;
/* the number of times create_offer has been called for the version field */
guint offer_count;
GstStructure *stats;
};
......
......@@ -75,6 +75,36 @@ transport_stream_get_pt (TransportStream * stream, const gchar * encoding_name)
return ret;
}
int *
transport_stream_get_all_pt (TransportStream * stream,
const gchar * encoding_name, gsize * pt_len)
{
guint i;
gsize ret_i = 0;
gsize ret_size = 8;
int *ret = NULL;
for (i = 0; i < stream->ptmap->len; i++) {
PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i);
if (!gst_caps_is_empty (item->caps)) {
GstStructure *s = gst_caps_get_structure (item->caps, 0);
if (!g_strcmp0 (gst_structure_get_string (s, "encoding-name"),
encoding_name)) {
if (!ret)
ret = g_new0 (int, ret_size);
if (ret_i >= ret_size) {
ret_size *= 2;
ret = g_realloc_n (ret, ret_size, sizeof (int));
}
ret[ret_i++] = item->pt;
}
}
}
*pt_len = ret_i;
return ret;
}
static void
transport_stream_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
......@@ -152,6 +182,14 @@ transport_stream_dispose (GObject * object)
gst_object_unref (stream->rtcp_transport);
stream->rtcp_transport = NULL;
if (stream->rtxsend)
gst_object_unref (stream->rtxsend);
stream->rtxsend = NULL;
if (stream->rtxreceive)
gst_object_unref (stream->rtxreceive);
stream->rtxreceive = NULL;
GST_OBJECT_PARENT (object) = NULL;
G_OBJECT_CLASS (parent_class)->dispose (object);
......
......@@ -61,6 +61,10 @@ struct _TransportStream
GArray *ptmap; /* array of PtMapItem's */
GArray *remote_ssrcmap; /* array of SsrcMapItem's */
gboolean output_connected; /* whether receive bin is connected to rtpbin */
GstElement *rtxsend;
GstElement *rtxreceive;
};
struct _TransportStreamClass
......@@ -72,6 +76,9 @@ TransportStream * transport_stream_new (GstWebRTCBin * webrtc,
guint session_id);
int transport_stream_get_pt (TransportStream * stream,
const gchar * encoding_name);
int * transport_stream_get_all_pt (TransportStream * stream,
const gchar * encoding_name,
gsize * pt_len);
GstCaps * transport_stream_get_caps_for_pt (TransportStream * stream,
guint pt);
......
......@@ -21,6 +21,8 @@
# include "config.h"
#endif
#include <stdlib.h>
#include "utils.h"
#include "gstwebrtcbin.h"
......@@ -53,28 +55,48 @@ _find_pad_template (GstElement * element, GstPadDirection direction,
}
GstSDPMessage *
_get_latest_sdp (GstWebRTCBin * webrtc)
_get_latest_offer (GstWebRTCBin * webrtc)
{
if (webrtc->current_local_description &&
webrtc->current_local_description->type == GST_WEBRTC_SDP_TYPE_ANSWER) {
webrtc->current_local_description->type == GST_WEBRTC_SDP_TYPE_OFFER) {
return webrtc->current_local_description->sdp;
}
if (webrtc->current_remote_description &&
webrtc->current_remote_description->type == GST_WEBRTC_SDP_TYPE_ANSWER) {
webrtc->current_remote_description->type == GST_WEBRTC_SDP_TYPE_OFFER) {
return webrtc->current_remote_description->sdp;
}
return NULL;
}
GstSDPMessage *
_get_latest_answer (GstWebRTCBin * webrtc)
{
if (webrtc->current_local_description &&
webrtc->current_local_description->type == GST_WEBRTC_SDP_TYPE_OFFER) {
webrtc->current_local_description->type == GST_WEBRTC_SDP_TYPE_ANSWER) {
return webrtc->current_local_description->sdp;
}
if (webrtc->current_remote_description &&
webrtc->current_remote_description->type == GST_WEBRTC_SDP_TYPE_OFFER) {
webrtc->current_remote_description->type == GST_WEBRTC_SDP_TYPE_ANSWER) {
return webrtc->current_remote_description->sdp;
}
return NULL;
}
GstSDPMessage *
_get_latest_sdp (GstWebRTCBin * webrtc)
{
GstSDPMessage *ret = NULL;
if ((ret = _get_latest_answer (webrtc)))
return ret;
if ((ret = _get_latest_offer (webrtc)))
return ret;
return NULL;
}
struct pad_block *
_create_pad_block (GstElement * element, GstPad * pad, gulong block_id,
gpointer user_data, GDestroyNotify notify)
......@@ -142,3 +164,31 @@ _g_checksum_to_webrtc_string (GChecksumType type)
return NULL;
}
}
GstCaps *
_rtp_caps_from_media (const GstSDPMedia * media)
{
GstCaps *ret;
int i, j;
ret = gst_caps_new_empty ();
for (i = 0; i < gst_sdp_media_formats_len (media); i++) {
guint pt = atoi (gst_sdp_media_get_format (media, i));
GstCaps *caps;
caps = gst_sdp_media_get_caps_from_media (media, pt);
/* gst_sdp_media_get_caps_from_media() produces caps with name
* "application/x-unknown" which will fail intersection with
* "application/x-rtp" caps so mangle the returns caps to have the
* correct name here */
for (j = 0; j < gst_caps_get_size (caps); j++) {
GstStructure *s = gst_caps_get_structure (caps, j);
gst_structure_set_name (s, "application/x-rtp");
}
gst_caps_append (ret, caps);
}
return ret;
}
......@@ -47,6 +47,8 @@ GstPadTemplate * _find_pad_template (GstElement * element,
const gchar * name);
GstSDPMessage * _get_latest_sdp (GstWebRTCBin * webrtc);
GstSDPMessage * _get_latest_offer (GstWebRTCBin * webrtc);
GstSDPMessage * _get_latest_answer (GstWebRTCBin * webrtc);
GstWebRTCICEStream * _find_ice_stream_for_session (GstWebRTCBin * webrtc,
guint session_id);
......@@ -74,6 +76,8 @@ G_GNUC_INTERNAL
gchar * _enum_value_to_string (GType type, guint value);
G_GNUC_INTERNAL
const gchar * _g_checksum_to_webrtc_string (GChecksumType type);
G_GNUC_INTERNAL
GstCaps * _rtp_caps_from_media (const GstSDPMedia * media);
G_END_DECLS
......
......@@ -212,7 +212,7 @@ _media_has_mid (const GstSDPMedia * media, guint media_idx, GError ** error)
return TRUE;
}
static const gchar *
const gchar *
_media_get_ice_ufrag (const GstSDPMessage * msg, guint media_idx)
{
const gchar *ice_ufrag;
......@@ -227,7 +227,7 @@ _media_get_ice_ufrag (const GstSDPMessage * msg, guint media_idx)
return ice_ufrag;
}
static const gchar *
const gchar *
_media_get_ice_pwd (const GstSDPMessage * msg, guint media_idx)
{
const gchar *ice_pwd;
......@@ -437,11 +437,13 @@ _media_replace_direction (GstSDPMedia * media,
if (g_strcmp0 (attr->key, "sendonly") == 0
|| g_strcmp0 (attr->key, "sendrecv") == 0
|| g_strcmp0 (attr->key, "recvonly") == 0) {
|| g_strcmp0 (attr->key, "recvonly") == 0
|| g_strcmp0 (attr->key, "inactive") == 0) {
GstSDPAttribute new_attr = { 0, };
GST_TRACE ("replace %s with %s", attr->key, dir_str);
gst_sdp_attribute_set (&new_attr, dir_str, "");
gst_sdp_media_replace_attribute (media, i, &new_attr);
g_free (dir_str);
return;
}
}
......@@ -768,6 +770,21 @@ _message_media_is_datachannel (const GstSDPMessage * msg, guint media_id)
return TRUE;
}
guint
_message_get_datachannel_index (const GstSDPMessage * msg)
{
guint i;
for (i = 0; i < gst_sdp_message_medias_len (msg); i++) {
if (_message_media_is_datachannel (msg, i)) {
g_assert (i < G_MAXUINT);
return i;
}
}
return G_MAXUINT;
}
void
_get_ice_credentials_from_sdp_media (const GstSDPMessage * sdp, guint media_idx,
gchar ** ufrag, gchar ** pwd)
......@@ -833,6 +850,8 @@ _parse_bundle (GstSDPMessage * sdp, GStrv * bundled)
if (!(*bundled)[0]) {
GST_ERROR ("Invalid format for BUNDLE group, expected at least "
"one mid (%s)", group);
g_strfreev (*bundled);
*bundled = NULL;
goto done;
}
} else {
......
......@@ -89,6 +89,8 @@ void _get_ice_credentials_from_sdp_media (con
G_GNUC_INTERNAL
gboolean _message_media_is_datachannel (const GstSDPMessage * msg,
guint media_id);
G_GNUC_INTERNAL
guint _message_get_datachannel_index (const GstSDPMessage * msg);
G_GNUC_INTERNAL
gboolean _get_bundle_index (GstSDPMessage * sdp,
......@@ -98,4 +100,11 @@ G_GNUC_INTERNAL
gboolean _parse_bundle (GstSDPMessage * sdp,
GStrv * bundled);
G_GNUC_INTERNAL
const gchar * _media_get_ice_pwd (const GstSDPMessage * msg,
guint media_idx);
G_GNUC_INTERNAL
const gchar * _media_get_ice_ufrag (const GstSDPMessage * msg,
guint media_idx);
#endif /* __WEBRTC_UTILS_H__ */
......@@ -25,9 +25,14 @@
#include "utils.h"
#include "webrtctransceiver.h"
#define GST_CAT_DEFAULT webrtc_transceiver_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
#define webrtc_transceiver_parent_class parent_class
G_DEFINE_TYPE (WebRTCTransceiver, webrtc_transceiver,
GST_TYPE_WEBRTC_RTP_TRANSCEIVER);
G_DEFINE_TYPE_WITH_CODE (WebRTCTransceiver, webrtc_transceiver,
GST_TYPE_WEBRTC_RTP_TRANSCEIVER,
GST_DEBUG_CATEGORY_INIT (webrtc_transceiver_debug,
"webrtctransceiver", 0, "webrtctransceiver"););
#define DEFAULT_FEC_TYPE GST_WEBRTC_FEC_TYPE_NONE
#define DEFAULT_DO_NACK FALSE
......@@ -172,6 +177,8 @@ webrtc_transceiver_finalize (GObject * object)
gst_structure_free (trans->local_rtx_ssrc_map);
trans->local_rtx_ssrc_map = NULL;
gst_caps_replace (&trans->last_configured_caps, NULL);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
......
......@@ -44,6 +44,8 @@ struct _WebRTCTransceiver
GstWebRTCFECType fec_type;
guint fec_percentage;
gboolean do_nack;
GstCaps *last_configured_caps;
};
struct _WebRTCTransceiverClass
......
......@@ -39,7 +39,7 @@ GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstWebRTCRTPTransceiver,
gst_webrtc_rtp_transceiver, GST_TYPE_OBJECT,
GST_DEBUG_CATEGORY_INIT (gst_webrtc_rtp_transceiver_debug,
"webrtctransceiver", 0, "webrtctransceiver");
"webrtcrtptransceiver", 0, "webrtcrtptransceiver");
);
enum
......
This diff is collapsed.
noinst_PROGRAMS = webrtc webrtcbidirectional webrtcswap webrtctransceiver
noinst_PROGRAMS = webrtc webrtcbidirectional webrtcswap webrtctransceiver webrtcrenego
webrtc_SOURCES = webrtc.c
webrtc_CFLAGS=\
......@@ -52,3 +52,16 @@ webrtctransceiver_LDADD=\
$(GST_LIBS) \
$(GST_SDP_LIBS) \
$(top_builddir)/gst-libs/gst/webrtc/libgstwebrtc-@GST_API_VERSION@.la
webrtcrenego_SOURCES = webrtcrenego.c
webrtcrenego_CFLAGS=\
-I$(top_srcdir)/gst-libs \
-I$(top_builddir)/gst-libs \
$(GST_PLUGINS_BASE_CFLAGS) \
$(GST_CFLAGS) \
$(GST_SDP_CFLAGS)
webrtcrenego_LDADD=\
$(GST_PLUGINS_BASE_LIBS) \
$(GST_LIBS) \
$(GST_SDP_LIBS) \
$(top_builddir)/gst-libs/gst/webrtc/libgstwebrtc-@GST_API_VERSION@.la
examples = ['webrtc', 'webrtcbidirectional', 'webrtcswap', 'webrtctransceiver']
examples = ['webrtc', 'webrtcbidirectional', 'webrtcswap', 'webrtctransceiver', 'webrtcrenego']
foreach example : examples
exe_name = example
......
#include <gst/gst.h>
#include <gst/sdp/sdp.h>
#include <gst/webrtc/webrtc.h>
#include <string.h>
static GMainLoop *loop;
static GstElement *pipe1, *webrtc1, *webrtc2, *extra_src;
static GstBus *bus1;
#define SEND_SRC(pattern) "videotestsrc is-live=true pattern=" pattern " ! timeoverlay ! queue ! vp8enc ! rtpvp8pay ! queue ! " \
"capsfilter caps=application/x-rtp,media=video,payload=96,encoding-name=VP8"
static void
_element_message (GstElement * parent, GstMessage * msg)
{
switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_EOS:{
GstElement *receive, *webrtc;
GstPad *pad, *peer;
g_print ("Got element EOS message from %s parent %s\n",
GST_OBJECT_NAME (msg->src), GST_OBJECT_NAME (parent));
receive = GST_ELEMENT (msg->src);
pad = gst_element_get_static_pad (receive, "sink");
peer = gst_pad_get_peer (pad);
webrtc = GST_ELEMENT (gst_pad_get_parent (peer));
gst_bin_remove (GST_BIN (pipe1), receive);
gst_pad_unlink (peer, pad);
gst_element_release_request_pad (webrtc, peer);
gst_object_unref (pad);
gst_object_unref (peer);
gst_element_set_state (receive, GST_STATE_NULL);
break;
}
default:
break;
}
}
static gboolean
_bus_watch (GstBus * bus, GstMessage * msg, GstElement * pipe)
{
switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_STATE_CHANGED:
if (GST_ELEMENT (msg->src) == pipe) {
GstState old, new, pending;
gst_message_parse_state_changed (msg, &old, &new, &pending);
{
gchar *dump_name = g_strconcat ("state_changed-",
gst_element_state_get_name (old), "_",
gst_element_state_get_name (new), NULL);
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (msg->src),
GST_DEBUG_GRAPH_SHOW_ALL, dump_name);
g_free (dump_name);
}
}
break;
case GST_MESSAGE_ERROR:{
GError *err = NULL;
gchar *dbg_info = NULL;
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipe),
GST_DEBUG_GRAPH_SHOW_ALL, "error");
gst_message_parse_error (msg, &err, &dbg_info);
g_printerr ("ERROR from element %s: %s\n",
GST_OBJECT_NAME (msg->src), err->message);
g_printerr ("Debugging info: %s\n", (dbg_info) ? dbg_info : "none");
g_error_free (err);
g_free (dbg_info);
g_main_loop_quit (loop);
break;
}
case GST_MESSAGE_EOS:{
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipe),
GST_DEBUG_GRAPH_SHOW_ALL, "eos");
g_print ("EOS received\n");
g_main_loop_quit (loop);
break;
}
case GST_MESSAGE_ELEMENT:{
const GstStructure *s = gst_message_get_structure (msg);
if (g_strcmp0 (gst_structure_get_name (s), "GstBinForwarded") == 0) {
GstMessage *sub_msg;
gst_structure_get (s, "message", GST_TYPE_MESSAGE, &sub_msg, NULL);
_element_message (GST_ELEMENT (msg->src), sub_msg);
gst_message_unref (sub_msg);
}
break;
}
default:
break;
}
return TRUE;
}
static void
_webrtc_pad_added (GstElement * webrtc, GstPad * new_pad, GstElement * pipe)
{
GstElement *out;
GstPad *sink;
if (GST_PAD_DIRECTION (new_pad) != GST_PAD_SRC)
return;
out = gst_parse_bin_from_description ("queue ! rtpvp8depay ! vp8dec ! "
"videoconvert ! queue ! xvimagesink", TRUE, NULL);
gst_bin_add (GST_BIN (pipe), out);
gst_element_sync_state_with_parent (out);
sink = out->sinkpads->data;
gst_pad_link (new_pad, sink);
}
static void
_on_answer_received (GstPromise * promise, gpointer user_data)
{
GstWebRTCSessionDescription *answer = NULL;
const GstStructure *reply;
gchar *desc;
g_assert (gst_promise_wait (promise) == GST_PROMISE_RESULT_REPLIED);
reply = gst_promise_get_reply (promise);
gst_structure_get (reply, "answer",
GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &answer, NULL);
gst_promise_unref (promise);
desc = gst_sdp_message_as_text (answer->sdp);
g_print ("Created answer:\n%s\n", desc);
g_free (desc);
/* this is one way to tell webrtcbin that we don't want to be notified when
* this task is complete: set a NULL promise */
g_signal_emit_by_name (webrtc1, "set-remote-description", answer, NULL);
/* this is another way to tell webrtcbin that we don't want to be notified
* when this task is complete: interrupt the promise */
promise = gst_promise_new ();
g_signal_emit_by_name (webrtc2, "set-local-description", answer, promise);
gst_promise_interrupt (promise);
gst_promise_unref (promise);
gst_webrtc_session_description_free (answer);
}
static void
_on_offer_received (GstPromise * promise, gpointer user_data)
{
GstWebRTCSessionDescription *offer = NULL;
const GstStructure *reply;
gchar *desc;
g_assert (gst_promise_wait (promise) == GST_PROMISE_RESULT_REPLIED);
reply = gst_promise_get_reply (promise);
gst_structure_get (reply, "offer",
GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL);
gst_promise_unref (promise);
desc = gst_sdp_message_as_text (offer->sdp);
g_print ("Created offer:\n%s\n", desc);
g_free (desc);
g_signal_emit_by_name (webrtc1, "set-local-description", offer, NULL);
g_signal_emit_by_name (webrtc2, "set-remote-description", offer, NULL);
promise = gst_promise_new_with_change_func (_on_answer_received, user_data,
NULL);
g_signal_emit_by_name (webrtc2, "create-answer", NULL, promise);
gst_webrtc_session_description_free (offer);
}
static void
_on_negotiation_needed (GstElement * element, gpointer user_data)
{
GstPromise *promise;
promise = gst_promise_new_with_change_func (_on_offer_received, user_data,
NULL);
g_signal_emit_by_name (webrtc1, "create-offer", NULL, promise);
}
static void
_on_ice_candidate (GstElement * webrtc, guint mlineindex, gchar * candidate,
GstElement * other)
{
g_signal_emit_by_name (other, "add-ice-candidate", mlineindex, candidate);
}
static gboolean
stream_change (gpointer data)
{
if (!extra_src) {
g_print ("Adding extra stream\n");
extra_src =
gst_parse_bin_from_description (SEND_SRC ("circular"), TRUE, NULL);
gst_element_set_locked_state (extra_src, TRUE);
gst_bin_add (GST_BIN (pipe1), extra_src);
gst_element_link (extra_src, webrtc1);
gst_element_set_locked_state (extra_src, FALSE);
gst_element_sync_state_with_parent (extra_src);
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipe1),
GST_DEBUG_GRAPH_SHOW_ALL, "add");
} else {
GstPad *pad, *peer;
GstWebRTCRTPTransceiver *transceiver;
g_print ("Removing extra stream\n");
pad = gst_element_get_static_pad (extra_src, "src");
peer = gst_pad_get_peer (pad);
gst_element_send_event (extra_src, gst_event_new_eos ());
g_object_get (peer, "transceiver", &transceiver, NULL);
gst_webrtc_rtp_transceiver_set_direction (transceiver,
GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE);
gst_element_set_locked_state (extra_src, TRUE);
gst_element_set_state (extra_src, GST_STATE_NULL);
gst_pad_unlink (pad, peer);
gst_element_release_request_pad (webrtc1, peer);
gst_object_unref (peer);
gst_object_unref (pad);
gst_bin_remove (GST_BIN (pipe1), extra_src);
extra_src = NULL;
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipe1),
GST_DEBUG_GRAPH_SHOW_ALL, "remove");
}
return G_SOURCE_CONTINUE;
}
int
main (int argc, char *argv[])
{
gst_init (&argc, &argv);
loop = g_main_loop_new (NULL, FALSE);
pipe1 = gst_parse_launch (SEND_SRC ("smpte")
" ! webrtcbin name=smpte bundle-policy=max-bundle " SEND_SRC ("ball")
" ! webrtcbin name=ball bundle-policy=max-bundle", NULL);
g_object_set (pipe1, "message-forward", TRUE, NULL);
bus1 = gst_pipeline_get_bus (GST_PIPELINE (pipe1));
gst_bus_add_watch (bus1, (GstBusFunc) _bus_watch, pipe1);
webrtc1 = gst_bin_get_by_name (GST_BIN (pipe1), "smpte");
g_signal_connect (webrtc1, "on-negotiation-needed",
G_CALLBACK (_on_negotiation_needed), NULL);
g_signal_connect (webrtc1, "pad-added", G_CALLBACK (_webrtc_pad_added),
pipe1);
webrtc2 = gst_bin_get_by_name (GST_BIN (pipe1), "ball");
g_signal_connect (webrtc2, "pad-added", G_CALLBACK (_webrtc_pad_added),
pipe1);
g_signal_connect (webrtc1, "on-ice-candidate",
G_CALLBACK (_on_ice_candidate), webrtc2);
g_signal_connect (webrtc2, "on-ice-candidate",
G_CALLBACK (_on_ice_candidate), webrtc1);
g_print ("Starting pipeline\n");
gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_PLAYING);
g_timeout_add_seconds (5, stream_change, NULL);
g_main_loop_run (loop);
gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_NULL);
g_print ("Pipeline stopped\n");
gst_object_unref (webrtc1);
gst_object_unref (webrtc2);
gst_bus_remove_watch (bus1);
gst_object_unref (bus1);
gst_object_unref (pipe1);
gst_deinit ();
return 0;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment