summaryrefslogtreecommitdiff
path: root/ext/webrtc/gstwebrtcbin.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext/webrtc/gstwebrtcbin.c')
-rw-r--r--ext/webrtc/gstwebrtcbin.c3530
1 files changed, 3530 insertions, 0 deletions
diff --git a/ext/webrtc/gstwebrtcbin.c b/ext/webrtc/gstwebrtcbin.c
new file mode 100644
index 000000000..a834a22c5
--- /dev/null
+++ b/ext/webrtc/gstwebrtcbin.c
@@ -0,0 +1,3530 @@
+/* GStreamer
+ * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "gstwebrtcbin.h"
+#include "gstwebrtcstats.h"
+#include "transportstream.h"
+#include "transportreceivebin.h"
+#include "utils.h"
+#include "webrtcsdp.h"
+#include "webrtctransceiver.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define RANDOM_SESSION_ID \
+ ((((((guint64) g_random_int()) << 32) | \
+ (guint64) g_random_int ())) & \
+ G_GUINT64_CONSTANT (0x7fffffffffffffff))
+
+#define PC_GET_LOCK(w) (&w->priv->pc_lock)
+#define PC_LOCK(w) (g_mutex_lock (PC_GET_LOCK(w)))
+#define PC_UNLOCK(w) (g_mutex_unlock (PC_GET_LOCK(w)))
+
+#define PC_GET_COND(w) (&w->priv->pc_cond)
+#define PC_COND_WAIT(w) (g_cond_wait(PC_GET_COND(w), PC_GET_LOCK(w)))
+#define PC_COND_BROADCAST(w) (g_cond_broadcast(PC_GET_COND(w)))
+#define PC_COND_SIGNAL(w) (g_cond_signal(PC_GET_COND(w)))
+
+/*
+ * This webrtcbin implements the majority of the W3's peerconnection API and
+ * implementation guide where possible. Generating offers, answers and setting
+ * local and remote SDP's are all supported. To start with, only the media
+ * interface has been implemented (no datachannel yet).
+ *
+ * Each input/output pad is equivalent to a Track in W3 parlance which are
+ * added/removed from the bin. The number of requested sink pads is the number
+ * of streams that will be sent to the receiver and will be associated with a
+ * GstWebRTCRTPTransceiver (very similar to W3 RTPTransceiver's).
+ *
+ * On the receiving side, RTPTransceiver's are created in response to setting
+ * a remote description. Output pads for the receiving streams in the set
+ * description are also created.
+ */
+
+/*
+ * TODO:
+ * assert sending payload type matches the stream
+ * reconfiguration (of anything)
+ * LS groups
+ * bundling
+ * setting custom DTLS certificates
+ * data channel
+ *
+ * seperate session id's from mlineindex properly
+ * how to deal with replacing a input/output track/stream
+ */
+
+#define GST_CAT_DEFAULT gst_webrtc_bin_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+GQuark
+gst_webrtc_bin_error_quark (void)
+{
+ return g_quark_from_static_string ("gst-webrtc-bin-error-quark");
+}
+
+G_DEFINE_TYPE (GstWebRTCBinPad, gst_webrtc_bin_pad, GST_TYPE_GHOST_PAD);
+
+static void
+gst_webrtc_bin_pad_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_webrtc_bin_pad_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_webrtc_bin_pad_finalize (GObject * object)
+{
+ GstWebRTCBinPad *pad = GST_WEBRTC_BIN_PAD (object);
+
+ if (pad->trans)
+ gst_object_unref (pad->trans);
+ pad->trans = NULL;
+
+ G_OBJECT_CLASS (gst_webrtc_bin_pad_parent_class)->finalize (object);
+}
+
+static void
+gst_webrtc_bin_pad_class_init (GstWebRTCBinPadClass * klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+
+ gobject_class->get_property = gst_webrtc_bin_pad_get_property;
+ gobject_class->set_property = gst_webrtc_bin_pad_set_property;
+ gobject_class->finalize = gst_webrtc_bin_pad_finalize;
+}
+
+static GstCaps *
+_transport_stream_get_caps_for_pt (TransportStream * stream, guint pt)
+{
+ guint i, len;
+
+ len = stream->ptmap->len;
+ for (i = 0; i < len; i++) {
+ PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i);
+ if (item->pt == pt)
+ return item->caps;
+ }
+ return NULL;
+}
+
+static void
+gst_webrtc_bin_pad_init (GstWebRTCBinPad * pad)
+{
+}
+
+static GstWebRTCBinPad *
+gst_webrtc_bin_pad_new (const gchar * name, GstPadDirection direction)
+{
+ GstWebRTCBinPad *pad =
+ g_object_new (gst_webrtc_bin_pad_get_type (), "name", name, "direction",
+ direction, NULL);
+
+ if (!gst_ghost_pad_construct (GST_GHOST_PAD (pad))) {
+ gst_object_unref (pad);
+ return NULL;
+ }
+
+ GST_DEBUG_OBJECT (pad, "new visible pad with direction %s",
+ direction == GST_PAD_SRC ? "src" : "sink");
+ return pad;
+}
+
+#define gst_webrtc_bin_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstWebRTCBin, gst_webrtc_bin, GST_TYPE_BIN,
+ GST_DEBUG_CATEGORY_INIT (gst_webrtc_bin_debug, "webrtcbin", 0,
+ "webrtcbin element");
+ );
+
+static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink_%u",
+ GST_PAD_SINK,
+ GST_PAD_REQUEST,
+ GST_STATIC_CAPS ("application/x-rtp"));
+
+static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src_%u",
+ GST_PAD_SRC,
+ GST_PAD_SOMETIMES,
+ GST_STATIC_CAPS ("application/x-rtp"));
+
+enum
+{
+ SIGNAL_0,
+ CREATE_OFFER_SIGNAL,
+ CREATE_ANSWER_SIGNAL,
+ SET_LOCAL_DESCRIPTION_SIGNAL,
+ SET_REMOTE_DESCRIPTION_SIGNAL,
+ ADD_ICE_CANDIDATE_SIGNAL,
+ ON_NEGOTIATION_NEEDED_SIGNAL,
+ ON_ICE_CANDIDATE_SIGNAL,
+ GET_STATS_SIGNAL,
+ ADD_TRANSCEIVER_SIGNAL,
+ GET_TRANSCEIVERS_SIGNAL,
+ LAST_SIGNAL,
+};
+
+enum
+{
+ PROP_0,
+ PROP_CONNECTION_STATE,
+ PROP_SIGNALING_STATE,
+ PROP_ICE_GATHERING_STATE,
+ PROP_ICE_CONNECTION_STATE,
+ PROP_LOCAL_DESCRIPTION,
+ PROP_CURRENT_LOCAL_DESCRIPTION,
+ PROP_PENDING_LOCAL_DESCRIPTION,
+ PROP_REMOTE_DESCRIPTION,
+ PROP_CURRENT_REMOTE_DESCRIPTION,
+ PROP_PENDING_REMOTE_DESCRIPTION,
+ PROP_STUN_SERVER,
+ PROP_TURN_SERVER,
+};
+
+static guint gst_webrtc_bin_signals[LAST_SIGNAL] = { 0 };
+
+static GstWebRTCDTLSTransport *
+_transceiver_get_transport (GstWebRTCRTPTransceiver * trans)
+{
+ if (trans->sender) {
+ return trans->sender->transport;
+ } else if (trans->receiver) {
+ return trans->receiver->transport;
+ }
+
+ return NULL;
+}
+
+static GstWebRTCDTLSTransport *
+_transceiver_get_rtcp_transport (GstWebRTCRTPTransceiver * trans)
+{
+ if (trans->sender) {
+ return trans->sender->rtcp_transport;
+ } else if (trans->receiver) {
+ return trans->receiver->rtcp_transport;
+ }
+
+ return NULL;
+}
+
+typedef struct
+{
+ guint session_id;
+ GstWebRTCICEStream *stream;
+} IceStreamItem;
+
+/* FIXME: locking? */
+GstWebRTCICEStream *
+_find_ice_stream_for_session (GstWebRTCBin * webrtc, guint session_id)
+{
+ int i;
+
+ for (i = 0; i < webrtc->priv->ice_stream_map->len; i++) {
+ IceStreamItem *item =
+ &g_array_index (webrtc->priv->ice_stream_map, IceStreamItem, i);
+
+ if (item->session_id == session_id) {
+ GST_TRACE_OBJECT (webrtc, "Found ice stream id %" GST_PTR_FORMAT " for "
+ "session %u", item->stream, session_id);
+ return item->stream;
+ }
+ }
+
+ GST_TRACE_OBJECT (webrtc, "No ice stream available for session %u",
+ session_id);
+ return NULL;
+}
+
+void
+_add_ice_stream_item (GstWebRTCBin * webrtc, guint session_id,
+ GstWebRTCICEStream * stream)
+{
+ IceStreamItem item = { session_id, stream };
+
+ GST_TRACE_OBJECT (webrtc, "adding ice stream %" GST_PTR_FORMAT " for "
+ "session %u", stream, session_id);
+ g_array_append_val (webrtc->priv->ice_stream_map, item);
+}
+
+typedef struct
+{
+ guint session_id;
+ gchar *mid;
+} SessionMidItem;
+
+static void
+clear_session_mid_item (SessionMidItem * item)
+{
+ g_free (item->mid);
+}
+
+typedef gboolean (*FindTransceiverFunc) (GstWebRTCRTPTransceiver * p1,
+ gconstpointer data);
+
+static GstWebRTCRTPTransceiver *
+_find_transceiver (GstWebRTCBin * webrtc, gconstpointer data,
+ FindTransceiverFunc func)
+{
+ int i;
+
+ for (i = 0; i < webrtc->priv->transceivers->len; i++) {
+ GstWebRTCRTPTransceiver *transceiver =
+ g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *,
+ i);
+
+ if (func (transceiver, data))
+ return transceiver;
+ }
+
+ return NULL;
+}
+
+static gboolean
+match_for_mid (GstWebRTCRTPTransceiver * trans, const gchar * mid)
+{
+ return g_strcmp0 (trans->mid, mid) == 0;
+}
+
+static gboolean
+transceiver_match_for_mline (GstWebRTCRTPTransceiver * trans, guint * mline)
+{
+ return trans->mline == *mline;
+}
+
+static GstWebRTCRTPTransceiver *
+_find_transceiver_for_mline (GstWebRTCBin * webrtc, guint mlineindex)
+{
+ GstWebRTCRTPTransceiver *trans;
+
+ trans = _find_transceiver (webrtc, &mlineindex,
+ (FindTransceiverFunc) transceiver_match_for_mline);
+
+ GST_TRACE_OBJECT (webrtc,
+ "Found transceiver %" GST_PTR_FORMAT " for mlineindex %u", trans,
+ mlineindex);
+
+ return trans;
+}
+
+typedef gboolean (*FindTransportFunc) (TransportStream * p1,
+ gconstpointer data);
+
+static TransportStream *
+_find_transport (GstWebRTCBin * webrtc, gconstpointer data,
+ FindTransportFunc func)
+{
+ int i;
+
+ for (i = 0; i < webrtc->priv->transports->len; i++) {
+ TransportStream *stream =
+ g_array_index (webrtc->priv->transports, TransportStream *,
+ i);
+
+ if (func (stream, data))
+ return stream;
+ }
+
+ return NULL;
+}
+
+static gboolean
+match_stream_for_session (TransportStream * trans, guint * session)
+{
+ return trans->session_id == *session;
+}
+
+static TransportStream *
+_find_transport_for_session (GstWebRTCBin * webrtc, guint session_id)
+{
+ TransportStream *stream;
+
+ stream = _find_transport (webrtc, &session_id,
+ (FindTransportFunc) match_stream_for_session);
+
+ GST_TRACE_OBJECT (webrtc,
+ "Found transport %" GST_PTR_FORMAT " for session %u", stream, session_id);
+
+ return stream;
+}
+
+typedef gboolean (*FindPadFunc) (GstWebRTCBinPad * p1, gconstpointer data);
+
+static GstWebRTCBinPad *
+_find_pad (GstWebRTCBin * webrtc, gconstpointer data, FindPadFunc func)
+{
+ GstElement *element = GST_ELEMENT (webrtc);
+ GList *l;
+
+ GST_OBJECT_LOCK (webrtc);
+ l = element->pads;
+ for (; l; l = g_list_next (l)) {
+ if (!GST_IS_WEBRTC_BIN_PAD (l->data))
+ continue;
+ if (func (l->data, data)) {
+ gst_object_ref (l->data);
+ GST_OBJECT_UNLOCK (webrtc);
+ return l->data;
+ }
+ }
+
+ l = webrtc->priv->pending_pads;
+ for (; l; l = g_list_next (l)) {
+ if (!GST_IS_WEBRTC_BIN_PAD (l->data))
+ continue;
+ if (func (l->data, data)) {
+ gst_object_ref (l->data);
+ GST_OBJECT_UNLOCK (webrtc);
+ return l->data;
+ }
+ }
+ GST_OBJECT_UNLOCK (webrtc);
+
+ return NULL;
+}
+
+static void
+_add_pad_to_list (GstWebRTCBin * webrtc, GstWebRTCBinPad * pad)
+{
+ GST_OBJECT_LOCK (webrtc);
+ webrtc->priv->pending_pads = g_list_prepend (webrtc->priv->pending_pads, pad);
+ GST_OBJECT_UNLOCK (webrtc);
+}
+
+static void
+_remove_pending_pad (GstWebRTCBin * webrtc, GstWebRTCBinPad * pad)
+{
+ GST_OBJECT_LOCK (webrtc);
+ webrtc->priv->pending_pads = g_list_remove (webrtc->priv->pending_pads, pad);
+ GST_OBJECT_UNLOCK (webrtc);
+}
+
+static void
+_add_pad (GstWebRTCBin * webrtc, GstWebRTCBinPad * pad)
+{
+ _remove_pending_pad (webrtc, pad);
+
+ if (webrtc->priv->running)
+ gst_pad_set_active (GST_PAD (pad), TRUE);
+ gst_element_add_pad (GST_ELEMENT (webrtc), GST_PAD (pad));
+}
+
+static void
+_remove_pad (GstWebRTCBin * webrtc, GstWebRTCBinPad * pad)
+{
+ _remove_pending_pad (webrtc, pad);
+
+ gst_element_remove_pad (GST_ELEMENT (webrtc), GST_PAD (pad));
+}
+
+typedef struct
+{
+ GstPadDirection direction;
+ guint mlineindex;
+} MLineMatch;
+
+static gboolean
+pad_match_for_mline (GstWebRTCBinPad * pad, const MLineMatch * match)
+{
+ return GST_PAD_DIRECTION (pad) == match->direction
+ && pad->mlineindex == match->mlineindex;
+}
+
+static GstWebRTCBinPad *
+_find_pad_for_mline (GstWebRTCBin * webrtc, GstPadDirection direction,
+ guint mlineindex)
+{
+ MLineMatch m = { direction, mlineindex };
+
+ return _find_pad (webrtc, &m, (FindPadFunc) pad_match_for_mline);
+}
+
+typedef struct
+{
+ GstPadDirection direction;
+ GstWebRTCRTPTransceiver *trans;
+} TransMatch;
+
+static gboolean
+pad_match_for_transceiver (GstWebRTCBinPad * pad, TransMatch * m)
+{
+ return GST_PAD_DIRECTION (pad) == m->direction && pad->trans == m->trans;
+}
+
+static GstWebRTCBinPad *
+_find_pad_for_transceiver (GstWebRTCBin * webrtc, GstPadDirection direction,
+ GstWebRTCRTPTransceiver * trans)
+{
+ TransMatch m = { direction, trans };
+
+ return _find_pad (webrtc, &m, (FindPadFunc) pad_match_for_transceiver);
+}
+
+#if 0
+static gboolean
+match_for_ssrc (GstWebRTCBinPad * pad, guint * ssrc)
+{
+ return pad->ssrc == *ssrc;
+}
+
+static gboolean
+match_for_pad (GstWebRTCBinPad * pad, GstWebRTCBinPad * other)
+{
+ return pad == other;
+}
+#endif
+
+static gboolean
+_unlock_pc_thread (GMutex * lock)
+{
+ g_mutex_unlock (lock);
+ return G_SOURCE_REMOVE;
+}
+
+static gpointer
+_gst_pc_thread (GstWebRTCBin * webrtc)
+{
+ PC_LOCK (webrtc);
+ webrtc->priv->main_context = g_main_context_new ();
+ webrtc->priv->loop = g_main_loop_new (webrtc->priv->main_context, FALSE);
+
+ PC_COND_BROADCAST (webrtc);
+ g_main_context_invoke (webrtc->priv->main_context,
+ (GSourceFunc) _unlock_pc_thread, PC_GET_LOCK (webrtc));
+
+ /* Having the thread be the thread default GMainContext will break the
+ * required queue-like ordering (from W3's peerconnection spec) of re-entrant
+ * tasks */
+ g_main_loop_run (webrtc->priv->loop);
+
+ PC_LOCK (webrtc);
+ g_main_context_unref (webrtc->priv->main_context);
+ webrtc->priv->main_context = NULL;
+ g_main_loop_unref (webrtc->priv->loop);
+ webrtc->priv->loop = NULL;
+ PC_COND_BROADCAST (webrtc);
+ PC_UNLOCK (webrtc);
+
+ return NULL;
+}
+
+static void
+_start_thread (GstWebRTCBin * webrtc)
+{
+ PC_LOCK (webrtc);
+ webrtc->priv->thread = g_thread_new ("gst-pc-ops",
+ (GThreadFunc) _gst_pc_thread, webrtc);
+
+ while (!webrtc->priv->loop)
+ PC_COND_WAIT (webrtc);
+ webrtc->priv->is_closed = FALSE;
+ PC_UNLOCK (webrtc);
+}
+
+static void
+_stop_thread (GstWebRTCBin * webrtc)
+{
+ PC_LOCK (webrtc);
+ webrtc->priv->is_closed = TRUE;
+ g_main_loop_quit (webrtc->priv->loop);
+ while (webrtc->priv->loop)
+ PC_COND_WAIT (webrtc);
+ PC_UNLOCK (webrtc);
+
+ g_thread_unref (webrtc->priv->thread);
+}
+
+static gboolean
+_execute_op (GstWebRTCBinTask * op)
+{
+ PC_LOCK (op->webrtc);
+ if (op->webrtc->priv->is_closed) {
+ GST_DEBUG_OBJECT (op->webrtc,
+ "Peerconnection is closed, aborting execution");
+ goto out;
+ }
+
+ op->op (op->webrtc, op->data);
+
+out:
+ PC_UNLOCK (op->webrtc);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+_free_op (GstWebRTCBinTask * op)
+{
+ if (op->notify)
+ op->notify (op->data);
+ g_free (op);
+}
+
+void
+gst_webrtc_bin_enqueue_task (GstWebRTCBin * webrtc, GstWebRTCBinFunc func,
+ gpointer data, GDestroyNotify notify)
+{
+ GstWebRTCBinTask *op;
+ GSource *source;
+
+ g_return_if_fail (GST_IS_WEBRTC_BIN (webrtc));
+
+ if (webrtc->priv->is_closed) {
+ GST_DEBUG_OBJECT (webrtc, "Peerconnection is closed, aborting execution");
+ if (notify)
+ notify (data);
+ return;
+ }
+ op = g_new0 (GstWebRTCBinTask, 1);
+ op->webrtc = webrtc;
+ op->op = func;
+ op->data = data;
+ op->notify = notify;
+
+ source = g_idle_source_new ();
+ g_source_set_priority (source, G_PRIORITY_DEFAULT);
+ g_source_set_callback (source, (GSourceFunc) _execute_op, op,
+ (GDestroyNotify) _free_op);
+ g_source_attach (source, webrtc->priv->main_context);
+ g_source_unref (source);
+}
+
+/* https://www.w3.org/TR/webrtc/#dom-rtciceconnectionstate */
+static GstWebRTCICEConnectionState
+_collate_ice_connection_states (GstWebRTCBin * webrtc)
+{
+#define STATE(val) GST_WEBRTC_ICE_CONNECTION_STATE_ ## val
+ GstWebRTCICEConnectionState any_state = 0;
+ gboolean all_closed = TRUE;
+ int i;
+
+ for (i = 0; i < webrtc->priv->transceivers->len; i++) {
+ GstWebRTCRTPTransceiver *rtp_trans =
+ g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *,
+ i);
+ WebRTCTransceiver *trans = WEBRTC_TRANSCEIVER (rtp_trans);
+ TransportStream *stream = trans->stream;
+ GstWebRTCICETransport *transport, *rtcp_transport;
+ GstWebRTCICEConnectionState ice_state;
+ gboolean rtcp_mux = FALSE;
+
+ if (rtp_trans->stopped)
+ continue;
+ if (!rtp_trans->mid)
+ continue;
+
+ g_object_get (stream, "rtcp-mux", &rtcp_mux, NULL);
+
+ transport = _transceiver_get_transport (rtp_trans)->transport;
+
+ /* get transport state */
+ g_object_get (transport, "state", &ice_state, NULL);
+ any_state |= (1 << ice_state);
+ if (ice_state != STATE (CLOSED))
+ all_closed = FALSE;
+
+ rtcp_transport = _transceiver_get_rtcp_transport (rtp_trans)->transport;
+
+ if (!rtcp_mux && rtcp_transport && transport != rtcp_transport) {
+ g_object_get (rtcp_transport, "state", &ice_state, NULL);
+ any_state |= (1 << ice_state);
+ if (ice_state != STATE (CLOSED))
+ all_closed = FALSE;
+ }
+ }
+
+ GST_TRACE_OBJECT (webrtc, "ICE connection state: 0x%x", any_state);
+
+ if (webrtc->priv->is_closed) {
+ GST_TRACE_OBJECT (webrtc, "returning closed");
+ return STATE (CLOSED);
+ }
+ /* Any of the RTCIceTransport s are in the failed state. */
+ if (any_state & (1 << STATE (FAILED))) {
+ GST_TRACE_OBJECT (webrtc, "returning failed");
+ return STATE (FAILED);
+ }
+ /* Any of the RTCIceTransport s are in the disconnected state and
+ * none of them are in the failed state. */
+ if (any_state & (1 << STATE (DISCONNECTED))) {
+ GST_TRACE_OBJECT (webrtc, "returning disconnected");
+ return STATE (DISCONNECTED);
+ }
+ /* Any of the RTCIceTransport's are in the checking state and none of them
+ * are in the failed or disconnected state. */
+ if (any_state & (1 << STATE (CHECKING))) {
+ GST_TRACE_OBJECT (webrtc, "returning checking");
+ return STATE (CHECKING);
+ }
+ /* Any of the RTCIceTransport s are in the new state and none of them are
+ * in the checking, failed or disconnected state, or all RTCIceTransport's
+ * are in the closed state. */
+ if ((any_state & (1 << STATE (NEW))) || all_closed) {
+ GST_TRACE_OBJECT (webrtc, "returning new");
+ return STATE (NEW);
+ }
+ /* All RTCIceTransport s are in the connected, completed or closed state
+ * and at least one of them is in the connected state. */
+ if (any_state & (1 << STATE (CONNECTED) | 1 << STATE (COMPLETED) | 1 <<
+ STATE (CLOSED)) && any_state & (1 << STATE (CONNECTED))) {
+ GST_TRACE_OBJECT (webrtc, "returning connected");
+ return STATE (CONNECTED);
+ }
+ /* All RTCIceTransport s are in the completed or closed state and at least
+ * one of them is in the completed state. */
+ if (any_state & (1 << STATE (COMPLETED) | 1 << STATE (CLOSED))
+ && any_state & (1 << STATE (COMPLETED))) {
+ GST_TRACE_OBJECT (webrtc, "returning connected");
+ return STATE (CONNECTED);
+ }
+
+ GST_FIXME ("unspecified situation, returning new");
+ return STATE (NEW);
+#undef STATE
+}
+
+/* https://www.w3.org/TR/webrtc/#dom-rtcicegatheringstate */
+static GstWebRTCICEGatheringState
+_collate_ice_gathering_states (GstWebRTCBin * webrtc)
+{
+#define STATE(val) GST_WEBRTC_ICE_GATHERING_STATE_ ## val
+ GstWebRTCICEGatheringState any_state = 0;
+ gboolean all_completed = webrtc->priv->transceivers->len > 0;
+ int i;
+
+ for (i = 0; i < webrtc->priv->transceivers->len; i++) {
+ GstWebRTCRTPTransceiver *rtp_trans =
+ g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *,
+ i);
+ WebRTCTransceiver *trans = WEBRTC_TRANSCEIVER (rtp_trans);
+ TransportStream *stream = trans->stream;
+ GstWebRTCICETransport *transport, *rtcp_transport;
+ GstWebRTCICEGatheringState ice_state;
+ gboolean rtcp_mux = FALSE;
+
+ if (rtp_trans->stopped)
+ continue;
+ if (!rtp_trans->mid)
+ continue;
+
+ g_object_get (stream, "rtcp-mux", &rtcp_mux, NULL);
+
+ transport = _transceiver_get_transport (rtp_trans)->transport;
+
+ /* get gathering state */
+ g_object_get (transport, "gathering-state", &ice_state, NULL);
+ any_state |= (1 << ice_state);
+ if (ice_state != STATE (COMPLETE))
+ all_completed = FALSE;
+
+ rtcp_transport = _transceiver_get_rtcp_transport (rtp_trans)->transport;
+
+ if (!rtcp_mux && rtcp_transport && rtcp_transport != transport) {
+ g_object_get (rtcp_transport, "gathering-state", &ice_state, NULL);
+ any_state |= (1 << ice_state);
+ if (ice_state != STATE (COMPLETE))
+ all_completed = FALSE;
+ }
+ }
+
+ GST_TRACE_OBJECT (webrtc, "ICE gathering state: 0x%x", any_state);
+
+ /* Any of the RTCIceTransport s are in the gathering state. */
+ if (any_state & (1 << STATE (GATHERING))) {
+ GST_TRACE_OBJECT (webrtc, "returning gathering");
+ return STATE (GATHERING);
+ }
+ /* At least one RTCIceTransport exists, and all RTCIceTransport s are in
+ * the completed gathering state. */
+ if (all_completed) {
+ GST_TRACE_OBJECT (webrtc, "returning complete");
+ return STATE (COMPLETE);
+ }
+
+ /* Any of the RTCIceTransport s are in the new gathering state and none
+ * of the transports are in the gathering state, or there are no transports. */
+ GST_TRACE_OBJECT (webrtc, "returning new");
+ return STATE (NEW);
+#undef STATE
+}
+
+/* https://www.w3.org/TR/webrtc/#rtcpeerconnectionstate-enum */
+static GstWebRTCPeerConnectionState
+_collate_peer_connection_states (GstWebRTCBin * webrtc)
+{
+#define STATE(v) GST_WEBRTC_PEER_CONNECTION_STATE_ ## v
+#define ICE_STATE(v) GST_WEBRTC_ICE_CONNECTION_STATE_ ## v
+#define DTLS_STATE(v) GST_WEBRTC_DTLS_TRANSPORT_STATE_ ## v
+ GstWebRTCICEConnectionState any_ice_state = 0;
+ GstWebRTCDTLSTransportState any_dtls_state = 0;
+ int i;
+
+ for (i = 0; i < webrtc->priv->transceivers->len; i++) {
+ GstWebRTCRTPTransceiver *rtp_trans =
+ g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *,
+ i);
+ WebRTCTransceiver *trans = WEBRTC_TRANSCEIVER (rtp_trans);
+ TransportStream *stream = trans->stream;
+ GstWebRTCDTLSTransport *transport, *rtcp_transport;
+ GstWebRTCICEGatheringState ice_state;
+ GstWebRTCDTLSTransportState dtls_state;
+ gboolean rtcp_mux = FALSE;
+
+ if (rtp_trans->stopped)
+ continue;
+ if (!rtp_trans->mid)
+ continue;
+
+ g_object_get (stream, "rtcp-mux", &rtcp_mux, NULL);
+ transport = _transceiver_get_transport (rtp_trans);
+
+ /* get transport state */
+ g_object_get (transport, "state", &dtls_state, NULL);
+ any_dtls_state |= (1 << dtls_state);
+ g_object_get (transport->transport, "state", &ice_state, NULL);
+ any_ice_state |= (1 << ice_state);
+
+ rtcp_transport = _transceiver_get_rtcp_transport (rtp_trans);
+
+ if (!rtcp_mux && rtcp_transport && rtcp_transport != transport) {
+ g_object_get (rtcp_transport, "state", &dtls_state, NULL);
+ any_dtls_state |= (1 << dtls_state);
+ g_object_get (rtcp_transport->transport, "state", &ice_state, NULL);
+ any_ice_state |= (1 << ice_state);
+ }
+ }
+
+ GST_TRACE_OBJECT (webrtc, "ICE connection state: 0x%x. DTLS connection "
+ "state: 0x%x", any_ice_state, any_dtls_state);
+
+ /* The RTCPeerConnection object's [[ isClosed]] slot is true. */
+ if (webrtc->priv->is_closed) {
+ GST_TRACE_OBJECT (webrtc, "returning closed");
+ return STATE (CLOSED);
+ }
+
+ /* Any of the RTCIceTransport s or RTCDtlsTransport s are in a failed state. */
+ if (any_ice_state & (1 << ICE_STATE (FAILED))) {
+ GST_TRACE_OBJECT (webrtc, "returning failed");
+ return STATE (FAILED);
+ }
+ if (any_dtls_state & (1 << DTLS_STATE (FAILED))) {
+ GST_TRACE_OBJECT (webrtc, "returning failed");
+ return STATE (FAILED);
+ }
+
+ /* Any of the RTCIceTransport's or RTCDtlsTransport's are in the connecting
+ * or checking state and none of them is in the failed state. */
+ if (any_ice_state & (1 << ICE_STATE (CHECKING))) {
+ GST_TRACE_OBJECT (webrtc, "returning connecting");
+ return STATE (CONNECTING);
+ }
+ if (any_dtls_state & (1 << DTLS_STATE (CONNECTING))) {
+ GST_TRACE_OBJECT (webrtc, "returning connecting");
+ return STATE (CONNECTING);
+ }
+
+ /* Any of the RTCIceTransport's or RTCDtlsTransport's are in the disconnected
+ * state and none of them are in the failed or connecting or checking state. */
+ if (any_ice_state & (1 << ICE_STATE (DISCONNECTED))) {
+ GST_TRACE_OBJECT (webrtc, "returning disconnected");
+ return STATE (DISCONNECTED);
+ }
+
+ /* All RTCIceTransport's and RTCDtlsTransport's are in the connected,
+ * completed or closed state and at least of them is in the connected or
+ * completed state. */
+ if (!(any_ice_state & ~(1 << ICE_STATE (CONNECTED) | 1 <<
+ ICE_STATE (COMPLETED) | 1 << ICE_STATE (CLOSED)))
+ && !(any_dtls_state & ~(1 << DTLS_STATE (CONNECTED) | 1 <<
+ DTLS_STATE (CLOSED)))
+ && (any_ice_state & (1 << ICE_STATE (CONNECTED) | 1 <<
+ ICE_STATE (COMPLETED))
+ || any_dtls_state & (1 << DTLS_STATE (CONNECTED)))) {
+ GST_TRACE_OBJECT (webrtc, "returning connected");
+ return STATE (CONNECTED);
+ }
+
+ /* Any of the RTCIceTransport's or RTCDtlsTransport's are in the new state
+ * and none of the transports are in the connecting, checking, failed or
+ * disconnected state, or all transports are in the closed state. */
+ if (!(any_ice_state & ~(1 << ICE_STATE (CLOSED)))) {
+ GST_TRACE_OBJECT (webrtc, "returning new");
+ return STATE (NEW);
+ }
+ if ((any_ice_state & (1 << ICE_STATE (NEW))
+ || any_dtls_state & (1 << DTLS_STATE (NEW)))
+ && !(any_ice_state & (1 << ICE_STATE (CHECKING) | 1 << ICE_STATE (FAILED)
+ | (1 << ICE_STATE (DISCONNECTED))))
+ && !(any_dtls_state & (1 << DTLS_STATE (CONNECTING) | 1 <<
+ DTLS_STATE (FAILED)))) {
+ GST_TRACE_OBJECT (webrtc, "returning new");
+ return STATE (NEW);
+ }
+
+ GST_FIXME_OBJECT (webrtc, "Undefined situation detected, returning new");
+ return STATE (NEW);
+#undef DTLS_STATE
+#undef ICE_STATE
+#undef STATE
+}
+
+static void
+_update_ice_gathering_state_task (GstWebRTCBin * webrtc, gpointer data)
+{
+ GstWebRTCICEGatheringState old_state = webrtc->ice_gathering_state;
+ GstWebRTCICEGatheringState new_state;
+
+ new_state = _collate_ice_gathering_states (webrtc);
+
+ if (new_state != webrtc->ice_gathering_state) {
+ gchar *old_s, *new_s;
+
+ old_s = _enum_value_to_string (GST_TYPE_WEBRTC_ICE_GATHERING_STATE,
+ old_state);
+ new_s = _enum_value_to_string (GST_TYPE_WEBRTC_ICE_GATHERING_STATE,
+ new_state);
+ GST_INFO_OBJECT (webrtc, "ICE gathering state change from %s(%u) to %s(%u)",
+ old_s, old_state, new_s, new_state);
+ g_free (old_s);
+ g_free (new_s);
+
+ webrtc->ice_gathering_state = new_state;
+ PC_UNLOCK (webrtc);
+ g_object_notify (G_OBJECT (webrtc), "ice-gathering-state");
+ PC_LOCK (webrtc);
+ }
+}
+
+static void
+_update_ice_gathering_state (GstWebRTCBin * webrtc)
+{
+ gst_webrtc_bin_enqueue_task (webrtc, _update_ice_gathering_state_task, NULL,
+ NULL);
+}
+
+static void
+_update_ice_connection_state_task (GstWebRTCBin * webrtc, gpointer data)
+{
+ GstWebRTCICEConnectionState old_state = webrtc->ice_connection_state;
+ GstWebRTCICEConnectionState new_state;
+
+ new_state = _collate_ice_connection_states (webrtc);
+
+ if (new_state != old_state) {
+ gchar *old_s, *new_s;
+
+ old_s = _enum_value_to_string (GST_TYPE_WEBRTC_ICE_CONNECTION_STATE,
+ old_state);
+ new_s = _enum_value_to_string (GST_TYPE_WEBRTC_ICE_CONNECTION_STATE,
+ new_state);
+ GST_INFO_OBJECT (webrtc,
+ "ICE connection state change from %s(%u) to %s(%u)", old_s, old_state,
+ new_s, new_state);
+ g_free (old_s);
+ g_free (new_s);
+
+ webrtc->ice_connection_state = new_state;
+ PC_UNLOCK (webrtc);
+ g_object_notify (G_OBJECT (webrtc), "ice-connection-state");
+ PC_LOCK (webrtc);
+ }
+}
+
+static void
+_update_ice_connection_state (GstWebRTCBin * webrtc)
+{
+ gst_webrtc_bin_enqueue_task (webrtc, _update_ice_connection_state_task, NULL,
+ NULL);
+}
+
+static void
+_update_peer_connection_state_task (GstWebRTCBin * webrtc, gpointer data)
+{
+ GstWebRTCPeerConnectionState old_state = webrtc->peer_connection_state;
+ GstWebRTCPeerConnectionState new_state;
+
+ new_state = _collate_peer_connection_states (webrtc);
+
+ if (new_state != old_state) {
+ gchar *old_s, *new_s;
+
+ old_s = _enum_value_to_string (GST_TYPE_WEBRTC_PEER_CONNECTION_STATE,
+ old_state);
+ new_s = _enum_value_to_string (GST_TYPE_WEBRTC_PEER_CONNECTION_STATE,
+ new_state);
+ GST_INFO_OBJECT (webrtc,
+ "Peer connection state change from %s(%u) to %s(%u)", old_s, old_state,
+ new_s, new_state);
+ g_free (old_s);
+ g_free (new_s);
+
+ webrtc->peer_connection_state = new_state;
+ PC_UNLOCK (webrtc);
+ g_object_notify (G_OBJECT (webrtc), "connection-state");
+ PC_LOCK (webrtc);
+ }
+}
+
+static void
+_update_peer_connection_state (GstWebRTCBin * webrtc)
+{
+ gst_webrtc_bin_enqueue_task (webrtc, _update_peer_connection_state_task,
+ NULL, NULL);
+}
+
+/* http://w3c.github.io/webrtc-pc/#dfn-check-if-negotiation-is-needed */
+static gboolean
+_check_if_negotiation_is_needed (GstWebRTCBin * webrtc)
+{
+ int i;
+
+ GST_LOG_OBJECT (webrtc, "checking if negotiation is needed");
+
+ /* If any implementation-specific negotiation is required, as described at
+ * the start of this section, return "true".
+ * FIXME */
+ /* FIXME: emit when input caps/format changes? */
+
+ /* If connection has created any RTCDataChannel's, and no m= section has
+ * been negotiated yet for data, return "true".
+ * FIXME */
+
+ if (!webrtc->current_local_description) {
+ GST_LOG_OBJECT (webrtc, "no local description set");
+ return TRUE;
+ }
+
+ if (!webrtc->current_remote_description) {
+ GST_LOG_OBJECT (webrtc, "no remote description set");
+ return TRUE;
+ }
+
+ for (i = 0; i < webrtc->priv->transceivers->len; i++) {
+ GstWebRTCRTPTransceiver *trans;
+
+ trans =
+ g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *,
+ i);
+
+ if (trans->stopped) {
+ /* FIXME: If t is stopped and is associated with an m= section according to
+ * [JSEP] (section 3.4.1.), but the associated m= section is not yet
+ * rejected in connection's currentLocalDescription or
+ * currentRemoteDescription , return "true". */
+ GST_FIXME_OBJECT (webrtc,
+ "check if the transceiver is rejected in descriptions");
+ } else {
+ const GstSDPMedia *media;
+ GstWebRTCRTPTransceiverDirection local_dir, remote_dir;
+
+ if (trans->mline == -1) {
+ GST_LOG_OBJECT (webrtc, "unassociated transceiver %i %" GST_PTR_FORMAT,
+ i, trans);
+ return TRUE;
+ }
+ /* internal inconsistency */
+ g_assert (trans->mline <
+ gst_sdp_message_medias_len (webrtc->current_local_description->sdp));
+ g_assert (trans->mline <
+ gst_sdp_message_medias_len (webrtc->current_remote_description->sdp));
+
+ /* FIXME: msid handling
+ * If t's direction is "sendrecv" or "sendonly", and the associated m=
+ * section in connection's currentLocalDescription doesn't contain an
+ * "a=msid" line, return "true". */
+
+ media =
+ gst_sdp_message_get_media (webrtc->current_local_description->sdp,
+ trans->mline);
+ local_dir = _get_direction_from_media (media);
+
+ media =
+ gst_sdp_message_get_media (webrtc->current_remote_description->sdp,
+ trans->mline);
+ remote_dir = _get_direction_from_media (media);
+
+ if (webrtc->current_local_description->type == GST_WEBRTC_SDP_TYPE_OFFER) {
+ /* If connection's currentLocalDescription if of type "offer", and
+ * the direction of the associated m= section in neither the offer
+ * nor answer matches t's direction, return "true". */
+
+ if (local_dir != trans->direction && remote_dir != trans->direction) {
+ GST_LOG_OBJECT (webrtc,
+ "transceiver direction doesn't match description");
+ return TRUE;
+ }
+ } else if (webrtc->current_local_description->type ==
+ GST_WEBRTC_SDP_TYPE_ANSWER) {
+ GstWebRTCRTPTransceiverDirection intersect_dir;
+
+ /* If connection's currentLocalDescription if of type "answer", and
+ * the direction of the associated m= section in the answer does not
+ * match t's direction intersected with the offered direction (as
+ * described in [JSEP] (section 5.3.1.)), return "true". */
+
+ /* remote is the offer, local is the answer */
+ intersect_dir = _intersect_answer_directions (remote_dir, local_dir);
+
+ if (intersect_dir != trans->direction) {
+ GST_LOG_OBJECT (webrtc,
+ "transceiver direction doesn't match description");
+ return TRUE;
+ }
+ }
+ }
+ }
+
+ GST_LOG_OBJECT (webrtc, "no negotiation needed");
+ return FALSE;
+}
+
+static void
+_check_need_negotiation_task (GstWebRTCBin * webrtc, gpointer unused)
+{
+ if (webrtc->priv->need_negotiation) {
+ GST_TRACE_OBJECT (webrtc, "emitting on-negotiation-needed");
+ PC_UNLOCK (webrtc);
+ g_signal_emit (webrtc, gst_webrtc_bin_signals[ON_NEGOTIATION_NEEDED_SIGNAL],
+ 0);
+ PC_LOCK (webrtc);
+ }
+}
+
+/* http://w3c.github.io/webrtc-pc/#dfn-update-the-negotiation-needed-flag */
+static void
+_update_need_negotiation (GstWebRTCBin * webrtc)
+{
+ /* If connection's [[isClosed]] slot is true, abort these steps. */
+ if (webrtc->priv->is_closed)
+ return;
+ /* If connection's signaling state is not "stable", abort these steps. */
+ if (webrtc->signaling_state != GST_WEBRTC_SIGNALING_STATE_STABLE)
+ return;
+
+ /* If the result of checking if negotiation is needed is "false", clear the
+ * negotiation-needed flag by setting connection's [[ needNegotiation]] slot
+ * to false, and abort these steps. */
+ if (!_check_if_negotiation_is_needed (webrtc)) {
+ webrtc->priv->need_negotiation = FALSE;
+ return;
+ }
+ /* If connection's [[needNegotiation]] slot is already true, abort these steps. */
+ if (webrtc->priv->need_negotiation)
+ return;
+ /* Set connection's [[needNegotiation]] slot to true. */
+ webrtc->priv->need_negotiation = TRUE;
+ /* Queue a task to check connection's [[ needNegotiation]] slot and, if still
+ * true, fire a simple event named negotiationneeded at connection. */
+ gst_webrtc_bin_enqueue_task (webrtc, _check_need_negotiation_task, NULL,
+ NULL);
+}
+
+static GstCaps *
+_find_codec_preferences (GstWebRTCBin * webrtc, GstWebRTCRTPTransceiver * trans,
+ GstPadDirection direction, guint media_idx)
+{
+ GstCaps *ret = NULL;
+
+ GST_LOG_OBJECT (webrtc, "retreiving codec preferences from %" GST_PTR_FORMAT,
+ trans);
+
+ if (trans->codec_preferences) {
+ GST_LOG_OBJECT (webrtc, "Using codec preferences: %" GST_PTR_FORMAT,
+ trans->codec_preferences);
+ ret = gst_caps_ref (trans->codec_preferences);
+ } else {
+ GstWebRTCBinPad *pad = _find_pad_for_mline (webrtc, direction, media_idx);
+ if (pad) {
+ GstCaps *caps = gst_pad_get_current_caps (GST_PAD (pad));
+ if (caps) {
+ GST_LOG_OBJECT (webrtc, "Using current pad caps: %" GST_PTR_FORMAT,
+ caps);
+ } else {
+ if ((caps = gst_pad_peer_query_caps (GST_PAD (pad), NULL)))
+ GST_LOG_OBJECT (webrtc, "Using peer query caps: %" GST_PTR_FORMAT,
+ caps);
+ }
+ if (caps)
+ ret = caps;
+ gst_object_unref (pad);
+ }
+ }
+
+ return ret;
+}
+
+static GstCaps *
+_add_supported_attributes_to_caps (const GstCaps * caps)
+{
+ GstCaps *ret;
+ int i;
+
+ ret = gst_caps_make_writable (caps);
+
+ for (i = 0; i < gst_caps_get_size (ret); i++) {
+ GstStructure *s = gst_caps_get_structure (ret, i);
+
+ if (!gst_structure_has_field (s, "rtcp-fb-nack"))
+ gst_structure_set (s, "rtcp-fb-nack", G_TYPE_BOOLEAN, TRUE, NULL);
+ if (!gst_structure_has_field (s, "rtcp-fb-nack-pli"))
+ gst_structure_set (s, "rtcp-fb-nack-pli", G_TYPE_BOOLEAN, TRUE, NULL);
+ /* FIXME: is this needed? */
+ /*if (!gst_structure_has_field (s, "rtcp-fb-transport-cc"))
+ gst_structure_set (s, "rtcp-fb-nack-pli", G_TYPE_BOOLEAN, TRUE, NULL); */
+
+ /* FIXME: codec-specific paramters? */
+ }
+
+ return ret;
+}
+
+static void
+_on_ice_transport_notify_state (GstWebRTCICETransport * transport,
+ GParamSpec * pspec, GstWebRTCBin * webrtc)
+{
+ _update_ice_connection_state (webrtc);
+ _update_peer_connection_state (webrtc);
+}
+
+static void
+_on_ice_transport_notify_gathering_state (GstWebRTCICETransport * transport,
+ GParamSpec * pspec, GstWebRTCBin * webrtc)
+{
+ _update_ice_gathering_state (webrtc);
+}
+
+static void
+_on_dtls_transport_notify_state (GstWebRTCDTLSTransport * transport,
+ GParamSpec * pspec, GstWebRTCBin * webrtc)
+{
+ _update_peer_connection_state (webrtc);
+}
+
+static WebRTCTransceiver *
+_create_webrtc_transceiver (GstWebRTCBin * webrtc)
+{
+ WebRTCTransceiver *trans;
+ GstWebRTCRTPTransceiver *rtp_trans;
+ GstWebRTCRTPSender *sender;
+ GstWebRTCRTPReceiver *receiver;
+
+ sender = gst_webrtc_rtp_sender_new (NULL);
+ receiver = gst_webrtc_rtp_receiver_new ();
+ trans = webrtc_transceiver_new (webrtc, sender, receiver);
+ rtp_trans = GST_WEBRTC_RTP_TRANSCEIVER (trans);
+ rtp_trans->direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV;
+ rtp_trans->mline = -1;
+
+ g_array_append_val (webrtc->priv->transceivers, trans);
+
+ gst_object_unref (sender);
+ gst_object_unref (receiver);
+
+ return trans;
+}
+
+static TransportStream *
+_create_transport_channel (GstWebRTCBin * webrtc, guint session_id)
+{
+ GstWebRTCDTLSTransport *transport;
+ TransportStream *ret;
+ gchar *pad_name;
+
+ /* FIXME: how to parametrize the sender and the receiver */
+ ret = transport_stream_new (webrtc, session_id);
+ transport = ret->transport;
+
+ g_signal_connect (G_OBJECT (transport->transport), "notify::state",
+ G_CALLBACK (_on_ice_transport_notify_state), webrtc);
+ g_signal_connect (G_OBJECT (transport->transport),
+ "notify::gathering-state",
+ G_CALLBACK (_on_ice_transport_notify_gathering_state), webrtc);
+ g_signal_connect (G_OBJECT (transport), "notify::state",
+ G_CALLBACK (_on_dtls_transport_notify_state), webrtc);
+
+ if ((transport = ret->rtcp_transport)) {
+ g_signal_connect (G_OBJECT (transport->transport),
+ "notify::state", G_CALLBACK (_on_ice_transport_notify_state), webrtc);
+ g_signal_connect (G_OBJECT (transport->transport),
+ "notify::gathering-state",
+ G_CALLBACK (_on_ice_transport_notify_gathering_state), webrtc);
+ g_signal_connect (G_OBJECT (transport), "notify::state",
+ G_CALLBACK (_on_dtls_transport_notify_state), webrtc);
+ }
+
+ gst_bin_add (GST_BIN (webrtc), GST_ELEMENT (ret->send_bin));
+ gst_bin_add (GST_BIN (webrtc), GST_ELEMENT (ret->receive_bin));
+
+ pad_name = g_strdup_printf ("recv_rtcp_sink_%u", ret->session_id);
+ if (!gst_element_link_pads (GST_ELEMENT (ret->receive_bin), "rtcp_src",
+ GST_ELEMENT (webrtc->rtpbin), pad_name))
+ g_warn_if_reached ();
+ g_free (pad_name);
+
+ pad_name = g_strdup_printf ("send_rtcp_src_%u", ret->session_id);
+ if (!gst_element_link_pads (GST_ELEMENT (webrtc->rtpbin), pad_name,
+ GST_ELEMENT (ret->send_bin), "rtcp_sink"))
+ g_warn_if_reached ();
+ g_free (pad_name);
+
+ g_array_append_val (webrtc->priv->transports, ret);
+
+ GST_TRACE_OBJECT (webrtc,
+ "Create transport %" GST_PTR_FORMAT " for session %u", ret, session_id);
+
+ gst_element_sync_state_with_parent (GST_ELEMENT (ret->send_bin));
+ gst_element_sync_state_with_parent (GST_ELEMENT (ret->receive_bin));
+
+ return ret;
+}
+
+/* based off https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-18#section-5.2.1 */
+static gboolean
+sdp_media_from_transceiver (GstWebRTCBin * webrtc, GstSDPMedia * media,
+ GstWebRTCRTPTransceiver * trans, GstWebRTCSDPType type, guint media_idx)
+{
+ /* TODO:
+ * rtp header extensions
+ * ice attributes
+ * rtx
+ * fec
+ * msid-semantics
+ * msid
+ * dtls fingerprints
+ * multiple dtls fingerprints https://tools.ietf.org/html/draft-ietf-mmusic-4572-update-05
+ */
+ gchar *direction, *sdp_mid;
+ GstCaps *caps;
+ int i;
+
+ /* "An m= section is generated for each RtpTransceiver that has been added
+ * to the Bin, excluding any stopped RtpTransceivers." */
+ if (trans->stopped)
+ return FALSE;
+ if (trans->direction == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE
+ || trans->direction == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE)
+ return FALSE;
+
+ gst_sdp_media_set_port_info (media, 9, 0);
+ gst_sdp_media_set_proto (media, "UDP/TLS/RTP/SAVPF");
+ gst_sdp_media_add_connection (media, "IN", "IP4", "0.0.0.0", 0, 0);
+
+ direction =
+ _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION,
+ trans->direction);
+ gst_sdp_media_add_attribute (media, direction, "");
+ g_free (direction);
+ /* FIXME: negotiate this */
+ gst_sdp_media_add_attribute (media, "rtcp-mux", "");
+ gst_sdp_media_add_attribute (media, "rtcp-rsize", NULL);
+
+ if (type == GST_WEBRTC_SDP_TYPE_OFFER) {
+ caps = _find_codec_preferences (webrtc, trans, GST_PAD_SINK, media_idx);
+ caps = _add_supported_attributes_to_caps (caps);
+ } else if (type == GST_WEBRTC_SDP_TYPE_ANSWER) {
+ caps = _find_codec_preferences (webrtc, trans, GST_PAD_SRC, media_idx);
+ /* FIXME: add rtcp-fb paramaters */
+ } else {
+ g_assert_not_reached ();
+ }
+
+ if (!caps || gst_caps_is_empty (caps) || gst_caps_is_any (caps)) {
+ GST_WARNING_OBJECT (webrtc, "no caps available for transceiver, skipping");
+ if (caps)
+ gst_caps_unref (caps);
+ return FALSE;
+ }
+
+ for (i = 0; i < gst_caps_get_size (caps); i++) {
+ GstCaps *format = gst_caps_new_empty ();
+ const GstStructure *s = gst_caps_get_structure (caps, i);
+
+ gst_caps_append_structure (format, gst_structure_copy (s));
+
+ GST_DEBUG_OBJECT (webrtc, "Adding %u-th caps %" GST_PTR_FORMAT
+ " to %u-th media", i, format, media_idx);
+
+ /* this only looks at the first structure so we loop over the given caps
+ * and add each structure inside it piecemeal */
+ gst_sdp_media_set_media_from_caps (format, media);
+
+ gst_caps_unref (format);
+ }
+
+ /* Some identifier; we also add the media name to it so it's identifiable */
+ sdp_mid = g_strdup_printf ("%s%u", gst_sdp_media_get_media (media),
+ webrtc->priv->media_counter++);
+ gst_sdp_media_add_attribute (media, "mid", sdp_mid);
+ g_free (sdp_mid);
+
+ if (trans->sender) {
+ gchar *cert, *fingerprint, *val;
+
+ if (!trans->sender->transport) {
+ TransportStream *item;
+ /* FIXME: bundle */
+ item = _find_transport_for_session (webrtc, media_idx);
+ if (!item)
+ item = _create_transport_channel (webrtc, media_idx);
+ webrtc_transceiver_set_transport (WEBRTC_TRANSCEIVER (trans), item);
+ }
+
+ g_object_get (trans->sender->transport, "certificate", &cert, NULL);
+
+ fingerprint =
+ _generate_fingerprint_from_certificate (cert, G_CHECKSUM_SHA256);
+ g_free (cert);
+ val =
+ g_strdup_printf ("%s %s",
+ _g_checksum_to_webrtc_string (G_CHECKSUM_SHA256), fingerprint);
+ g_free (fingerprint);
+
+ gst_sdp_media_add_attribute (media, "fingerprint", val);
+ g_free (val);
+ }
+
+ gst_caps_unref (caps);
+
+ return TRUE;
+}
+
+static GstSDPMessage *
+_create_offer_task (GstWebRTCBin * webrtc, const GstStructure * options)
+{
+ GstSDPMessage *ret;
+ int i;
+
+ gst_sdp_message_new (&ret);
+
+ gst_sdp_message_set_version (ret, "0");
+ {
+ /* FIXME: session id and version need special handling depending on the state we're in */
+ gchar *sess_id = g_strdup_printf ("%" G_GUINT64_FORMAT, RANDOM_SESSION_ID);
+ gst_sdp_message_set_origin (ret, "-", sess_id, "0", "IN", "IP4", "0.0.0.0");
+ g_free (sess_id);
+ }
+ gst_sdp_message_set_session_name (ret, "-");
+ gst_sdp_message_add_time (ret, "0", "0", NULL);
+ gst_sdp_message_add_attribute (ret, "ice-options", "trickle");
+
+ /* for each rtp transceiver */
+ for (i = 0; i < webrtc->priv->transceivers->len; i++) {
+ GstWebRTCRTPTransceiver *trans;
+ GstSDPMedia media = { 0, };
+ gchar *ufrag, *pwd;
+
+ trans =
+ g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *,
+ i);
+
+ gst_sdp_media_init (&media);
+ /* mandated by JSEP */
+ gst_sdp_media_add_attribute (&media, "setup", "actpass");
+
+ /* FIXME: only needed when restarting ICE */
+ _generate_ice_credentials (&ufrag, &pwd);
+ gst_sdp_media_add_attribute (&media, "ice-ufrag", ufrag);
+ gst_sdp_media_add_attribute (&media, "ice-pwd", pwd);
+ g_free (ufrag);
+ g_free (pwd);
+
+ if (sdp_media_from_transceiver (webrtc, &media, trans,
+ GST_WEBRTC_SDP_TYPE_OFFER, i))
+ gst_sdp_message_add_media (ret, &media);
+ else
+ gst_sdp_media_uninit (&media);
+ }
+
+ /* FIXME: pre-emptively setup receiving elements when needed */
+
+ /* XXX: only true for the initial offerer */
+ g_object_set (webrtc->priv->ice, "controller", TRUE, NULL);
+
+ return ret;
+}
+
+static GstSDPMessage *
+_create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
+{
+ GstSDPMessage *ret = NULL;
+ const GstWebRTCSessionDescription *pending_remote =
+ webrtc->pending_remote_description;
+ int i;
+
+ if (!webrtc->pending_remote_description) {
+ GST_ERROR_OBJECT (webrtc,
+ "Asked to create an answer without a remote description");
+ return NULL;
+ }
+
+ gst_sdp_message_new (&ret);
+
+ /* FIXME: session id and version need special handling depending on the state we're in */
+ gst_sdp_message_set_version (ret, "0");
+ {
+ const GstSDPOrigin *offer_origin =
+ gst_sdp_message_get_origin (pending_remote->sdp);
+ gst_sdp_message_set_origin (ret, "-", offer_origin->sess_id, "0", "IN",
+ "IP4", "0.0.0.0");
+ }
+ gst_sdp_message_set_session_name (ret, "-");
+
+ for (i = 0; i < gst_sdp_message_attributes_len (pending_remote->sdp); i++) {
+ const GstSDPAttribute *attr =
+ gst_sdp_message_get_attribute (pending_remote->sdp, i);
+
+ if (g_strcmp0 (attr->key, "ice-options") == 0) {
+ gst_sdp_message_add_attribute (ret, attr->key, attr->value);
+ }
+ }
+
+ for (i = 0; i < gst_sdp_message_medias_len (pending_remote->sdp); i++) {
+ /* FIXME:
+ * bundle policy
+ */
+ GstSDPMedia *media = NULL;
+ GstSDPMedia *offer_media;
+ GstWebRTCRTPTransceiver *rtp_trans = NULL;
+ WebRTCTransceiver *trans = NULL;
+ GstWebRTCRTPTransceiverDirection offer_dir, answer_dir;
+ GstWebRTCDTLSSetup offer_setup, answer_setup;
+ GstCaps *offer_caps, *answer_caps = NULL;
+ gchar *cert;
+ int j;
+
+ gst_sdp_media_new (&media);
+ gst_sdp_media_set_port_info (media, 9, 0);
+ gst_sdp_media_set_proto (media, "UDP/TLS/RTP/SAVPF");
+ gst_sdp_media_add_connection (media, "IN", "IP4", "0.0.0.0", 0, 0);
+
+ {
+ /* FIXME: only needed when restarting ICE */
+ gchar *ufrag, *pwd;
+ _generate_ice_credentials (&ufrag, &pwd);
+ gst_sdp_media_add_attribute (media, "ice-ufrag", ufrag);
+ gst_sdp_media_add_attribute (media, "ice-pwd", pwd);
+ g_free (ufrag);
+ g_free (pwd);
+ }
+
+ offer_media =
+ (GstSDPMedia *) gst_sdp_message_get_media (pending_remote->sdp, i);
+ for (j = 0; j < gst_sdp_media_attributes_len (offer_media); j++) {
+ const GstSDPAttribute *attr =
+ gst_sdp_media_get_attribute (offer_media, j);
+
+ if (g_strcmp0 (attr->key, "mid") == 0
+ || g_strcmp0 (attr->key, "rtcp-mux") == 0) {
+ gst_sdp_media_add_attribute (media, attr->key, attr->value);
+ /* FIXME: handle anything we want to keep */
+ }
+ }
+
+ offer_caps = gst_caps_new_empty ();
+ for (j = 0; j < gst_sdp_media_formats_len (offer_media); j++) {
+ guint pt = atoi (gst_sdp_media_get_format (offer_media, j));
+ GstCaps *caps;
+ int k;
+
+ caps = gst_sdp_media_get_caps_from_media (offer_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 (k = 0; k < gst_caps_get_size (caps); k++) {
+ GstStructure *s = gst_caps_get_structure (caps, k);
+ gst_structure_set_name (s, "application/x-rtp");
+ }
+
+ gst_caps_append (offer_caps, caps);
+ }
+
+ for (j = 0; j < webrtc->priv->transceivers->len; j++) {
+ GstCaps *trans_caps;
+
+ rtp_trans =
+ g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *,
+ j);
+ trans_caps = _find_codec_preferences (webrtc, rtp_trans, GST_PAD_SINK, i);
+
+ GST_TRACE_OBJECT (webrtc, "trying to compare %" GST_PTR_FORMAT
+ " and %" GST_PTR_FORMAT, offer_caps, trans_caps);
+
+ /* FIXME: technically this is a little overreaching as some fields we
+ * we can deal with not having and/or we may have unrecognized fields
+ * that we cannot actually support */
+ if (trans_caps) {
+ answer_caps = gst_caps_intersect (offer_caps, trans_caps);
+ if (answer_caps && !gst_caps_is_empty (answer_caps)) {
+ GST_LOG_OBJECT (webrtc,
+ "found compatible transceiver %" GST_PTR_FORMAT
+ " for offer media %u", trans, i);
+ if (trans_caps)
+ gst_caps_unref (trans_caps);
+ break;
+ } else {
+ if (answer_caps) {
+ gst_caps_unref (answer_caps);
+ answer_caps = NULL;
+ }
+ if (trans_caps)
+ gst_caps_unref (trans_caps);
+ rtp_trans = NULL;
+ }
+ } else {
+ rtp_trans = NULL;
+ }
+ }
+
+ if (rtp_trans) {
+ answer_dir = rtp_trans->direction;
+ if (!answer_caps)
+ goto rejected;
+ } else {
+ /* if no transceiver, then we only receive that stream and respond with
+ * the exact same caps */
+ /* FIXME: how to validate that subsequent elements can actually receive
+ * this payload/format */
+ answer_dir = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY;
+ answer_caps = gst_caps_ref (offer_caps);
+ }
+ /* respond with the requested caps */
+ if (answer_caps) {
+ gst_sdp_media_set_media_from_caps (answer_caps, media);
+ gst_caps_unref (answer_caps);
+ answer_caps = NULL;
+ }
+ if (!rtp_trans) {
+ trans = _create_webrtc_transceiver (webrtc);
+ rtp_trans = GST_WEBRTC_RTP_TRANSCEIVER (trans);
+ rtp_trans->direction = answer_dir;
+ rtp_trans->mline = i;
+ } else {
+ trans = WEBRTC_TRANSCEIVER (rtp_trans);
+ }
+
+ /* set the new media direction */
+ offer_dir = _get_direction_from_media (offer_media);
+ answer_dir = _intersect_answer_directions (offer_dir, answer_dir);
+ if (answer_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE) {
+ GST_WARNING_OBJECT (webrtc, "Could not intersect offer direction with "
+ "transceiver direction");
+ goto rejected;
+ }
+ _media_replace_direction (media, answer_dir);
+
+ /* set the a=setup: attribute */
+ offer_setup = _get_dtls_setup_from_media (offer_media);
+ answer_setup = _intersect_dtls_setup (offer_setup);
+ if (answer_setup == GST_WEBRTC_DTLS_SETUP_NONE) {
+ GST_WARNING_OBJECT (webrtc, "Could not intersect offer direction with "
+ "transceiver direction");
+ goto rejected;
+ }
+ _media_replace_setup (media, answer_setup);
+
+ /* FIXME: bundle! */
+ if (!trans->stream) {
+ TransportStream *item = _find_transport_for_session (webrtc, i);
+ if (!item)
+ item = _create_transport_channel (webrtc, i);
+ webrtc_transceiver_set_transport (trans, item);
+ }
+ /* set the a=fingerprint: for this transport */
+ g_object_get (trans->stream->transport, "certificate", &cert, NULL);
+
+ {
+ gchar *fingerprint, *val;
+
+ fingerprint =
+ _generate_fingerprint_from_certificate (cert, G_CHECKSUM_SHA256);
+ g_free (cert);
+ val =
+ g_strdup_printf ("%s %s",
+ _g_checksum_to_webrtc_string (G_CHECKSUM_SHA256), fingerprint);
+ g_free (fingerprint);
+
+ gst_sdp_media_add_attribute (media, "fingerprint", val);
+ g_free (val);
+ }
+
+ if (0) {
+ rejected:
+ GST_INFO_OBJECT (webrtc, "media %u rejected", i);
+ gst_sdp_media_free (media);
+ gst_sdp_media_copy (offer_media, &media);
+ gst_sdp_media_set_port_info (media, 0, 0);
+ }
+ gst_sdp_message_add_media (ret, media);
+ gst_sdp_media_free (media);
+
+ gst_caps_unref (offer_caps);
+ }
+
+ /* FIXME: can we add not matched transceivers? */
+
+ /* XXX: only true for the initial offerer */
+ g_object_set (webrtc->priv->ice, "controller", FALSE, NULL);
+
+ return ret;
+}
+
+struct create_sdp
+{
+ GstStructure *options;
+ GstPromise *promise;
+ GstWebRTCSDPType type;
+};
+
+static void
+_create_sdp_task (GstWebRTCBin * webrtc, struct create_sdp *data)
+{
+ GstWebRTCSessionDescription *desc = NULL;
+ GstSDPMessage *sdp = NULL;
+ GstStructure *s = NULL;
+
+ GST_INFO_OBJECT (webrtc, "creating %s sdp with options %" GST_PTR_FORMAT,
+ gst_webrtc_sdp_type_to_string (data->type), data->options);
+
+ if (data->type == GST_WEBRTC_SDP_TYPE_OFFER)
+ sdp = _create_offer_task (webrtc, data->options);
+ else if (data->type == GST_WEBRTC_SDP_TYPE_ANSWER)
+ sdp = _create_answer_task (webrtc, data->options);
+ else {
+ g_assert_not_reached ();
+ goto out;
+ }
+
+ if (sdp) {
+ desc = gst_webrtc_session_description_new (data->type, sdp);
+ s = gst_structure_new ("application/x-gst-promise",
+ gst_webrtc_sdp_type_to_string (data->type),
+ GST_TYPE_WEBRTC_SESSION_DESCRIPTION, desc, NULL);
+ }
+
+out:
+ PC_UNLOCK (webrtc);
+ gst_promise_reply (data->promise, s);
+ PC_LOCK (webrtc);
+
+ if (desc)
+ gst_webrtc_session_description_free (desc);
+}
+
+static void
+_free_create_sdp_data (struct create_sdp *data)
+{
+ if (data->options)
+ gst_structure_free (data->options);
+ gst_promise_unref (data->promise);
+ g_free (data);
+}
+
+static void
+gst_webrtc_bin_create_offer (GstWebRTCBin * webrtc,
+ const GstStructure * options, GstPromise * promise)
+{
+ struct create_sdp *data = g_new0 (struct create_sdp, 1);
+
+ if (options)
+ data->options = gst_structure_copy (options);
+ data->promise = gst_promise_ref (promise);
+ data->type = GST_WEBRTC_SDP_TYPE_OFFER;
+
+ gst_webrtc_bin_enqueue_task (webrtc, (GstWebRTCBinFunc) _create_sdp_task,
+ data, (GDestroyNotify) _free_create_sdp_data);
+}
+
+static void
+gst_webrtc_bin_create_answer (GstWebRTCBin * webrtc,
+ const GstStructure * options, GstPromise * promise)
+{
+ struct create_sdp *data = g_new0 (struct create_sdp, 1);
+
+ if (options)
+ data->options = gst_structure_copy (options);
+ data->promise = gst_promise_ref (promise);
+ data->type = GST_WEBRTC_SDP_TYPE_ANSWER;
+
+ gst_webrtc_bin_enqueue_task (webrtc, (GstWebRTCBinFunc) _create_sdp_task,
+ data, (GDestroyNotify) _free_create_sdp_data);
+}
+
+static GstWebRTCBinPad *
+_create_pad_for_sdp_media (GstWebRTCBin * webrtc, GstPadDirection direction,
+ guint media_idx)
+{
+ GstWebRTCBinPad *pad;
+ gchar *pad_name;
+
+ pad_name =
+ g_strdup_printf ("%s_%u", direction == GST_PAD_SRC ? "src" : "sink",
+ media_idx);
+ pad = gst_webrtc_bin_pad_new (pad_name, direction);
+ g_free (pad_name);
+ pad->mlineindex = media_idx;
+
+ return pad;
+}
+
+static GstWebRTCRTPTransceiver *
+_find_transceiver_for_sdp_media (GstWebRTCBin * webrtc,
+ const GstSDPMessage * sdp, guint media_idx)
+{
+ const GstSDPMedia *media = gst_sdp_message_get_media (sdp, media_idx);
+ GstWebRTCRTPTransceiver *ret = NULL;
+ int i;
+
+ for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
+ const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
+
+ if (g_strcmp0 (attr->key, "mid") == 0) {
+ if ((ret =
+ _find_transceiver (webrtc, attr->value,
+ (FindTransceiverFunc) match_for_mid)))
+ goto out;
+ }
+ }
+
+ ret = _find_transceiver (webrtc, &media_idx,
+ (FindTransceiverFunc) transceiver_match_for_mline);
+
+out:
+ GST_TRACE_OBJECT (webrtc, "Found transceiver %" GST_PTR_FORMAT, ret);
+ return ret;
+}
+
+static GstPad *
+_connect_input_stream (GstWebRTCBin * webrtc, GstWebRTCBinPad * pad)
+{
+/*
+ * ,-------------------------webrtcbin-------------------------,
+ * ; ;
+ * ; ,-------rtpbin-------, ,--transport_send_%u--, ;
+ * ; ; send_rtp_src_%u o---o rtp_sink ; ;
+ * ; ; ; ; ; ;
+ * ; ; send_rtcp_src_%u o---o rtcp_sink ; ;
+ * ; sink_%u ; ; '---------------------' ;
+ * o----------o send_rtp_sink_%u ; ;
+ * ; '--------------------' ;
+ * '--------------------- -------------------------------------'
+ */
+ GstPadTemplate *rtp_templ;
+ GstPad *rtp_sink;
+ gchar *pad_name;
+ WebRTCTransceiver *trans;
+
+ g_return_val_if_fail (pad->trans != NULL, NULL);
+
+ GST_INFO_OBJECT (pad, "linking input stream %u", pad->mlineindex);
+
+ rtp_templ =
+ _find_pad_template (webrtc->rtpbin, GST_PAD_SINK, GST_PAD_REQUEST,
+ "send_rtp_sink_%u");
+ g_assert (rtp_templ);
+
+ pad_name = g_strdup_printf ("send_rtp_sink_%u", pad->mlineindex);
+ rtp_sink =
+ gst_element_request_pad (webrtc->rtpbin, rtp_templ, pad_name, NULL);
+ g_free (pad_name);
+ gst_ghost_pad_set_target (GST_GHOST_PAD (pad), rtp_sink);
+ gst_object_unref (rtp_sink);
+
+ trans = WEBRTC_TRANSCEIVER (pad->trans);
+ if (!trans->stream) {
+ TransportStream *item;
+ /* FIXME: bundle */
+ item = _find_transport_for_session (webrtc, pad->mlineindex);
+ if (!item)
+ item = _create_transport_channel (webrtc, pad->mlineindex);
+ webrtc_transceiver_set_transport (trans, item);
+ }
+
+ pad_name = g_strdup_printf ("send_rtp_src_%u", pad->mlineindex);
+ if (!gst_element_link_pads (GST_ELEMENT (webrtc->rtpbin), pad_name,
+ GST_ELEMENT (trans->stream->send_bin), "rtp_sink"))
+ g_warn_if_reached ();
+ g_free (pad_name);
+
+ gst_element_sync_state_with_parent (GST_ELEMENT (trans->stream->send_bin));
+
+ return GST_PAD (pad);
+}
+
+/* output pads are receiving elements */
+static GstWebRTCBinPad *
+_connect_output_stream (GstWebRTCBin * webrtc, GstWebRTCBinPad * pad)
+{
+/*
+ * ,------------------------webrtcbin------------------------,
+ * ; ,---------rtpbin---------, ;
+ * ; ,-transport_receive_%u--, ; ; ;
+ * ; ; rtp_src o---o recv_rtp_sink_%u ; ;
+ * ; ; ; ; ; ;
+ * ; ; rtcp_src o---o recv_rtcp_sink_%u ; ;
+ * ; '-----------------------' ; ; ; src_%u
+ * ; ; recv_rtp_src_%u_%u_%u o--o
+ * ; '------------------------' ;
+ * '---------------------------------------------------------'
+ */
+ gchar *pad_name;
+ WebRTCTransceiver *trans;
+
+ g_return_val_if_fail (pad->trans != NULL, NULL);
+
+ GST_INFO_OBJECT (pad, "linking output stream %u", pad->mlineindex);
+
+ trans = WEBRTC_TRANSCEIVER (pad->trans);
+ if (!trans->stream) {
+ TransportStream *item;
+ /* FIXME: bundle */
+ item = _find_transport_for_session (webrtc, pad->mlineindex);
+ if (!item)
+ item = _create_transport_channel (webrtc, pad->mlineindex);
+ webrtc_transceiver_set_transport (trans, item);
+ }
+
+ pad_name = g_strdup_printf ("recv_rtp_sink_%u", pad->mlineindex);
+ if (!gst_element_link_pads (GST_ELEMENT (trans->stream->receive_bin),
+ "rtp_src", GST_ELEMENT (webrtc->rtpbin), pad_name))
+ g_warn_if_reached ();
+ g_free (pad_name);
+
+ gst_element_sync_state_with_parent (GST_ELEMENT (trans->stream->receive_bin));
+
+ return pad;
+}
+
+typedef struct
+{
+ guint mlineindex;
+ gchar *candidate;
+} IceCandidateItem;
+
+static void
+_clear_ice_candidate_item (IceCandidateItem ** item)
+{
+ g_free ((*item)->candidate);
+ g_free (*item);
+}
+
+static void
+_add_ice_candidate (GstWebRTCBin * webrtc, IceCandidateItem * item)
+{
+ GstWebRTCICEStream *stream;
+
+ stream = _find_ice_stream_for_session (webrtc, item->mlineindex);
+ if (stream == NULL) {
+ GST_WARNING_OBJECT (webrtc, "Unknown mline %u, ignoring", item->mlineindex);
+ return;
+ }
+
+ GST_LOG_OBJECT (webrtc, "adding ICE candidate with mline:%u, %s",
+ item->mlineindex, item->candidate);
+
+ gst_webrtc_ice_add_candidate (webrtc->priv->ice, stream, item->candidate);
+}
+
+static void
+_update_transceiver_from_sdp_media (GstWebRTCBin * webrtc,
+ const GstSDPMessage * sdp, guint media_idx,
+ GstWebRTCRTPTransceiver * rtp_trans)
+{
+ WebRTCTransceiver *trans = WEBRTC_TRANSCEIVER (rtp_trans);
+ TransportStream *stream = trans->stream;
+ GstWebRTCRTPTransceiverDirection prev_dir = rtp_trans->current_direction;
+ GstWebRTCRTPTransceiverDirection new_dir;
+ const GstSDPMedia *media = gst_sdp_message_get_media (sdp, media_idx);
+ GstWebRTCDTLSSetup new_setup;
+ gboolean new_rtcp_mux, new_rtcp_rsize;
+ int i;
+
+ rtp_trans->mline = media_idx;
+
+ for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
+ const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
+
+ if (g_strcmp0 (attr->key, "mid") == 0) {
+ g_free (rtp_trans->mid);
+ rtp_trans->mid = g_strdup (attr->value);
+ }
+ }
+
+ if (!stream) {
+ /* FIXME: find an existing transport for e.g. bundle/reconfiguration */
+ stream = _find_transport_for_session (webrtc, media_idx);
+ if (!stream)
+ stream = _create_transport_channel (webrtc, media_idx);
+ webrtc_transceiver_set_transport (trans, stream);
+ }
+
+ {
+ const GstSDPMedia *local_media, *remote_media;
+ GstWebRTCRTPTransceiverDirection local_dir, remote_dir;
+ GstWebRTCDTLSSetup local_setup, remote_setup;
+ guint i, len;
+ const gchar *proto;
+ GstCaps *global_caps;
+
+ local_media =
+ gst_sdp_message_get_media (webrtc->current_local_description->sdp,
+ media_idx);
+ remote_media =
+ gst_sdp_message_get_media (webrtc->current_remote_description->sdp,
+ media_idx);
+
+ local_setup = _get_dtls_setup_from_media (local_media);
+ remote_setup = _get_dtls_setup_from_media (remote_media);
+ new_setup = _get_final_setup (local_setup, remote_setup);
+ if (new_setup == GST_WEBRTC_DTLS_SETUP_NONE)
+ return;
+
+ local_dir = _get_direction_from_media (local_media);
+ remote_dir = _get_direction_from_media (remote_media);
+ new_dir = _get_final_direction (local_dir, remote_dir);
+ if (new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE)
+ return;
+
+ /* get proto */
+ proto = gst_sdp_media_get_proto (media);
+ if (proto != NULL) {
+ /* Parse global SDP attributes once */
+ global_caps = gst_caps_new_empty_simple ("application/x-unknown");
+ GST_DEBUG_OBJECT (webrtc, "mapping sdp session level attributes to caps");
+ gst_sdp_message_attributes_to_caps (sdp, global_caps);
+ GST_DEBUG_OBJECT (webrtc, "mapping sdp media level attributes to caps");
+ gst_sdp_media_attributes_to_caps (media, global_caps);
+
+ /* clear the ptmap */
+ g_array_set_size (stream->ptmap, 0);
+
+ len = gst_sdp_media_formats_len (media);
+ for (i = 0; i < len; i++) {
+ GstCaps *caps, *outcaps;
+ GstStructure *s;
+ PtMapItem item;
+ gint pt;
+
+ pt = atoi (gst_sdp_media_get_format (media, i));
+
+ GST_DEBUG_OBJECT (webrtc, " looking at %d pt: %d", i, pt);
+
+ /* convert caps */
+ caps = gst_sdp_media_get_caps_from_media (media, pt);
+ if (caps == NULL) {
+ GST_WARNING_OBJECT (webrtc, " skipping pt %d without caps", pt);
+ continue;
+ }
+
+ /* Merge in global caps */
+ /* Intersect will merge in missing fields to the current caps */
+ outcaps = gst_caps_intersect (caps, global_caps);
+ gst_caps_unref (caps);
+
+ s = gst_caps_get_structure (outcaps, 0);
+ gst_structure_set_name (s, "application/x-rtp");
+
+ item.pt = pt;
+ item.caps = outcaps;
+
+ g_array_append_val (stream->ptmap, item);
+ }
+
+ gst_caps_unref (global_caps);
+ }
+
+ new_rtcp_mux = _media_has_attribute_key (local_media, "rtcp-mux")
+ && _media_has_attribute_key (remote_media, "rtcp-mux");
+ new_rtcp_rsize = _media_has_attribute_key (local_media, "rtcp-rsize")
+ && _media_has_attribute_key (remote_media, "rtcp-rsize");
+
+ {
+ GObject *session;
+ g_signal_emit_by_name (webrtc->rtpbin, "get-internal-session",
+ media_idx, &session);
+ if (session) {
+ g_object_set (session, "rtcp-reduced-size", new_rtcp_rsize, NULL);
+ g_object_unref (session);
+ }
+ }
+ }
+
+ if (prev_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE
+ && prev_dir != new_dir) {
+ GST_FIXME_OBJECT (webrtc, "implement transceiver direction changes");
+ return;
+ }
+
+ /* FIXME: bundle! */
+ g_object_set (stream, "rtcp-mux", new_rtcp_mux, NULL);
+
+ if (new_dir != prev_dir) {
+ TransportReceiveBin *receive;
+
+ GST_TRACE_OBJECT (webrtc, "transceiver direction change");
+
+ /* FIXME: this may not always be true. e.g. bundle */
+ g_assert (media_idx == stream->session_id);
+
+ if (new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY ||
+ new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV) {
+ GstWebRTCBinPad *pad =
+ _find_pad_for_mline (webrtc, GST_PAD_SINK, media_idx);
+ if (pad) {
+ GST_DEBUG_OBJECT (webrtc, "found existing send pad %" GST_PTR_FORMAT
+ " for transceiver %" GST_PTR_FORMAT, pad, trans);
+ g_assert (pad->trans == rtp_trans);
+ g_assert (pad->mlineindex == media_idx);
+ gst_object_unref (pad);
+ } else {
+ GST_DEBUG_OBJECT (webrtc,
+ "creating new pad send pad for transceiver %" GST_PTR_FORMAT,
+ trans);
+ pad = _create_pad_for_sdp_media (webrtc, GST_PAD_SINK, media_idx);
+ pad->trans = gst_object_ref (rtp_trans);
+ _connect_input_stream (webrtc, pad);
+ _add_pad (webrtc, pad);
+ }
+ g_object_set (stream, "dtls-client",
+ new_setup == GST_WEBRTC_DTLS_SETUP_ACTIVE, NULL);
+ }
+ if (new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY ||
+ new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV) {
+ GstWebRTCBinPad *pad =
+ _find_pad_for_mline (webrtc, GST_PAD_SRC, media_idx);
+ if (pad) {
+ GST_DEBUG_OBJECT (webrtc, "found existing receive pad %" GST_PTR_FORMAT
+ " for transceiver %" GST_PTR_FORMAT, pad, trans);
+ g_assert (pad->trans == rtp_trans);
+ g_assert (pad->mlineindex == media_idx);
+ gst_object_unref (pad);
+ } else {
+ GST_DEBUG_OBJECT (webrtc,
+ "creating new receive pad for transceiver %" GST_PTR_FORMAT, trans);
+ pad = _create_pad_for_sdp_media (webrtc, GST_PAD_SRC, media_idx);
+ pad->trans = gst_object_ref (rtp_trans);
+ _connect_output_stream (webrtc, pad);
+ /* delay adding the pad until rtpbin creates the recv output pad
+ * to ghost to so queries/events travel through the pipeline correctly
+ * as soon as the pad is added */
+ _add_pad_to_list (webrtc, pad);
+ }
+ g_object_set (stream, "dtls-client",
+ new_setup == GST_WEBRTC_DTLS_SETUP_ACTIVE, NULL);
+ }
+
+ receive = TRANSPORT_RECEIVE_BIN (stream->receive_bin);
+ if (new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY ||
+ new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV)
+ transport_receive_bin_set_receive_state (receive, RECEIVE_STATE_PASS);
+ else
+ transport_receive_bin_set_receive_state (receive, RECEIVE_STATE_DROP);
+
+ rtp_trans->mline = media_idx;
+ rtp_trans->current_direction = new_dir;
+ }
+}
+
+static gboolean
+_find_compatible_unassociated_transceiver (GstWebRTCRTPTransceiver * p1,
+ gconstpointer data)
+{
+ if (p1->mid)
+ return FALSE;
+ if (p1->mline != -1)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+_update_transceivers_from_sdp (GstWebRTCBin * webrtc, SDPSource source,
+ GstWebRTCSessionDescription * sdp)
+{
+ int i;
+
+ for (i = 0; i < gst_sdp_message_medias_len (sdp->sdp); i++) {
+ const GstSDPMedia *media = gst_sdp_message_get_media (sdp->sdp, i);
+ GstWebRTCRTPTransceiver *trans;
+
+ /* skip rejected media */
+ if (gst_sdp_media_get_port (media) == 0)
+ continue;
+
+ trans = _find_transceiver_for_sdp_media (webrtc, sdp->sdp, i);
+
+ if (source == SDP_LOCAL && sdp->type == GST_WEBRTC_SDP_TYPE_OFFER && !trans) {
+ GST_ERROR ("State mismatch. Could not find local transceiver by mline.");
+ return FALSE;
+ } else {
+ if (trans) {
+ _update_transceiver_from_sdp_media (webrtc, sdp->sdp, i, trans);
+ } else {
+ trans = _find_transceiver (webrtc, NULL,
+ (FindTransceiverFunc) _find_compatible_unassociated_transceiver);
+ if (!trans)
+ trans =
+ GST_WEBRTC_RTP_TRANSCEIVER (_create_webrtc_transceiver (webrtc));
+ /* XXX: default to the advertised direction in the sdp for new
+ * transceviers. The spec doesn't actually say what happens here, only
+ * that calls to setDirection will change the value. Nothing about
+ * a default value when the transceiver is created internally */
+ trans->direction = _get_direction_from_media (media);
+ _update_transceiver_from_sdp_media (webrtc, sdp->sdp, i, trans);
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+_get_ice_credentials_from_sdp_media (const GstSDPMessage * sdp, guint media_idx,
+ gchar ** ufrag, gchar ** pwd)
+{
+ int i;
+
+ *ufrag = NULL;
+ *pwd = NULL;
+
+ {
+ /* search in the corresponding media section */
+ const GstSDPMedia *media = gst_sdp_message_get_media (sdp, media_idx);
+ const gchar *tmp_ufrag =
+ gst_sdp_media_get_attribute_val (media, "ice-ufrag");
+ const gchar *tmp_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd");
+ if (tmp_ufrag && tmp_pwd) {
+ *ufrag = g_strdup (tmp_ufrag);
+ *pwd = g_strdup (tmp_pwd);
+ return;
+ }
+ }
+
+ /* then in the sdp message itself */
+ for (i = 0; i < gst_sdp_message_attributes_len (sdp); i++) {
+ const GstSDPAttribute *attr = gst_sdp_message_get_attribute (sdp, i);
+
+ if (g_strcmp0 (attr->key, "ice-ufrag") == 0) {
+ g_assert (!*ufrag);
+ *ufrag = g_strdup (attr->value);
+ } else if (g_strcmp0 (attr->key, "ice-pwd") == 0) {
+ g_assert (!*pwd);
+ *pwd = g_strdup (attr->value);
+ }
+ }
+ if (!*ufrag && !*pwd) {
+ /* Check in the medias themselves. According to JSEP, they should be
+ * identical FIXME: only for bundle-d streams */
+ for (i = 0; i < gst_sdp_message_medias_len (sdp); i++) {
+ const GstSDPMedia *media = gst_sdp_message_get_media (sdp, i);
+ const gchar *tmp_ufrag =
+ gst_sdp_media_get_attribute_val (media, "ice-ufrag");
+ const gchar *tmp_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd");
+ if (tmp_ufrag && tmp_pwd) {
+ *ufrag = g_strdup (tmp_ufrag);
+ *pwd = g_strdup (tmp_pwd);
+ break;
+ }
+ }
+ }
+}
+
+struct set_description
+{
+ GstPromise *promise;
+ SDPSource source;
+ GstWebRTCSessionDescription *sdp;
+};
+
+/* http://w3c.github.io/webrtc-pc/#set-description */
+static void
+_set_description_task (GstWebRTCBin * webrtc, struct set_description *sd)
+{
+ GstWebRTCSignalingState new_signaling_state = webrtc->signaling_state;
+ GError *error = NULL;
+
+ {
+ gchar *state = _enum_value_to_string (GST_TYPE_WEBRTC_SIGNALING_STATE,
+ webrtc->signaling_state);
+ gchar *type_str =
+ _enum_value_to_string (GST_TYPE_WEBRTC_SDP_TYPE, sd->sdp->type);
+ gchar *sdp_text = gst_sdp_message_as_text (sd->sdp->sdp);
+ GST_INFO_OBJECT (webrtc, "Attempting to set %s %s in the %s state",
+ _sdp_source_to_string (sd->source), type_str, state);
+ GST_TRACE_OBJECT (webrtc, "SDP contents\n%s", sdp_text);
+ g_free (sdp_text);
+ g_free (state);
+ g_free (type_str);
+ }
+
+ if (!validate_sdp (webrtc, sd->source, sd->sdp, &error)) {
+ GST_ERROR_OBJECT (webrtc, "%s", error->message);
+ goto out;
+ }
+
+ if (webrtc->priv->is_closed) {
+ GST_WARNING_OBJECT (webrtc, "we are closed");
+ goto out;
+ }
+
+ switch (sd->sdp->type) {
+ case GST_WEBRTC_SDP_TYPE_OFFER:{
+ if (sd->source == SDP_LOCAL) {
+ if (webrtc->pending_local_description)
+ gst_webrtc_session_description_free
+ (webrtc->pending_local_description);
+ webrtc->pending_local_description =
+ gst_webrtc_session_description_copy (sd->sdp);
+ new_signaling_state = GST_WEBRTC_SIGNALING_STATE_HAVE_LOCAL_OFFER;
+ } else {
+ if (webrtc->pending_remote_description)
+ gst_webrtc_session_description_free
+ (webrtc->pending_remote_description);
+ webrtc->pending_remote_description =
+ gst_webrtc_session_description_copy (sd->sdp);
+ new_signaling_state = GST_WEBRTC_SIGNALING_STATE_HAVE_REMOTE_OFFER;
+ }
+ break;
+ }
+ case GST_WEBRTC_SDP_TYPE_ANSWER:{
+ if (sd->source == SDP_LOCAL) {
+ if (webrtc->current_local_description)
+ gst_webrtc_session_description_free
+ (webrtc->current_local_description);
+ webrtc->current_local_description =
+ gst_webrtc_session_description_copy (sd->sdp);
+
+ if (webrtc->current_remote_description)
+ gst_webrtc_session_description_free
+ (webrtc->current_remote_description);
+ webrtc->current_remote_description = webrtc->pending_remote_description;
+ webrtc->pending_remote_description = NULL;
+ } else {
+ if (webrtc->current_remote_description)
+ gst_webrtc_session_description_free
+ (webrtc->current_remote_description);
+ webrtc->current_remote_description =
+ gst_webrtc_session_description_copy (sd->sdp);
+
+ if (webrtc->current_local_description)
+ gst_webrtc_session_description_free
+ (webrtc->current_local_description);
+ webrtc->current_local_description = webrtc->pending_local_description;
+ webrtc->pending_local_description = NULL;
+ }
+
+ if (webrtc->pending_local_description)
+ gst_webrtc_session_description_free (webrtc->pending_local_description);
+ webrtc->pending_local_description = NULL;
+
+ if (webrtc->pending_remote_description)
+ gst_webrtc_session_description_free
+ (webrtc->pending_remote_description);
+ webrtc->pending_remote_description = NULL;
+
+ new_signaling_state = GST_WEBRTC_SIGNALING_STATE_STABLE;
+ break;
+ }
+ case GST_WEBRTC_SDP_TYPE_ROLLBACK:{
+ GST_FIXME_OBJECT (webrtc, "rollbacks are completely untested");
+ if (sd->source == SDP_LOCAL) {
+ if (webrtc->pending_local_description)
+ gst_webrtc_session_description_free
+ (webrtc->pending_local_description);
+ webrtc->pending_local_description = NULL;
+ } else {
+ if (webrtc->pending_remote_description)
+ gst_webrtc_session_description_free
+ (webrtc->pending_remote_description);
+ webrtc->pending_remote_description = NULL;
+ }
+
+ new_signaling_state = GST_WEBRTC_SIGNALING_STATE_STABLE;
+ break;
+ }
+ case GST_WEBRTC_SDP_TYPE_PRANSWER:{
+ GST_FIXME_OBJECT (webrtc, "pranswers are completely untested");
+ if (sd->source == SDP_LOCAL) {
+ if (webrtc->pending_local_description)
+ gst_webrtc_session_description_free
+ (webrtc->pending_local_description);
+ webrtc->pending_local_description =
+ gst_webrtc_session_description_copy (sd->sdp);
+
+ new_signaling_state = GST_WEBRTC_SIGNALING_STATE_HAVE_LOCAL_PRANSWER;
+ } else {
+ if (webrtc->pending_remote_description)
+ gst_webrtc_session_description_free
+ (webrtc->pending_remote_description);
+ webrtc->pending_remote_description =
+ gst_webrtc_session_description_copy (sd->sdp);
+
+ new_signaling_state = GST_WEBRTC_SIGNALING_STATE_HAVE_REMOTE_PRANSWER;
+ }
+ break;
+ }
+ }
+
+ if (new_signaling_state != webrtc->signaling_state) {
+ gchar *from = _enum_value_to_string (GST_TYPE_WEBRTC_SIGNALING_STATE,
+ webrtc->signaling_state);
+ gchar *to = _enum_value_to_string (GST_TYPE_WEBRTC_SIGNALING_STATE,
+ new_signaling_state);
+ GST_TRACE_OBJECT (webrtc, "notify signaling-state from %s "
+ "to %s", from, to);
+ webrtc->signaling_state = new_signaling_state;
+ PC_UNLOCK (webrtc);
+ g_object_notify (G_OBJECT (webrtc), "signaling-state");
+ PC_LOCK (webrtc);
+
+ g_free (from);
+ g_free (to);
+ }
+
+ /* TODO: necessary data channel modifications */
+
+ if (sd->sdp->type == GST_WEBRTC_SDP_TYPE_ROLLBACK) {
+ /* FIXME:
+ * If the mid value of an RTCRtpTransceiver was set to a non-null value
+ * by the RTCSessionDescription that is being rolled back, set the mid
+ * value of that transceiver to null, as described by [JSEP]
+ * (section 4.1.7.2.).
+ * If an RTCRtpTransceiver was created by applying the
+ * RTCSessionDescription that is being rolled back, and a track has not
+ * been attached to it via addTrack, remove that transceiver from
+ * connection's set of transceivers, as described by [JSEP]
+ * (section 4.1.7.2.).
+ * Restore the value of connection's [[ sctpTransport]] internal slot
+ * to its value at the last stable signaling state.
+ */
+ }
+
+ if (webrtc->signaling_state == GST_WEBRTC_SIGNALING_STATE_STABLE) {
+ gboolean prev_need_negotiation = webrtc->priv->need_negotiation;
+
+ /* media modifications */
+ _update_transceivers_from_sdp (webrtc, sd->source, sd->sdp);
+
+ /* If connection's signaling state is now stable, update the
+ * negotiation-needed flag. If connection's [[ needNegotiation]] slot
+ * was true both before and after this update, queue a task to check
+ * connection's [[needNegotiation]] slot and, if still true, fire a
+ * simple event named negotiationneeded at connection.*/
+ _update_need_negotiation (webrtc);
+ if (prev_need_negotiation && webrtc->priv->need_negotiation) {
+ _check_need_negotiation_task (webrtc, NULL);
+ }
+ }
+
+ if (sd->source == SDP_LOCAL) {
+ int i;
+
+ for (i = 0; i < gst_sdp_message_medias_len (sd->sdp->sdp); i++) {
+ gchar *ufrag, *pwd;
+ TransportStream *item;
+
+ /* FIXME: bundle */
+ item = _find_transport_for_session (webrtc, i);
+ if (!item)
+ item = _create_transport_channel (webrtc, i);
+
+ _get_ice_credentials_from_sdp_media (sd->sdp->sdp, i, &ufrag, &pwd);
+ gst_webrtc_ice_set_local_credentials (webrtc->priv->ice,
+ item->stream, ufrag, pwd);
+ g_free (ufrag);
+ g_free (pwd);
+ }
+ }
+
+ if (sd->source == SDP_REMOTE) {
+ int i;
+
+ for (i = 0; i < gst_sdp_message_medias_len (sd->sdp->sdp); i++) {
+ gchar *ufrag, *pwd;
+ TransportStream *item;
+
+ /* FIXME: bundle */
+ item = _find_transport_for_session (webrtc, i);
+ if (!item)
+ item = _create_transport_channel (webrtc, i);
+
+ _get_ice_credentials_from_sdp_media (sd->sdp->sdp, i, &ufrag, &pwd);
+ gst_webrtc_ice_set_remote_credentials (webrtc->priv->ice,
+ item->stream, ufrag, pwd);
+ g_free (ufrag);
+ g_free (pwd);
+ }
+ }
+
+ {
+ int i;
+ for (i = 0; i < webrtc->priv->ice_stream_map->len; i++) {
+ IceStreamItem *item =
+ &g_array_index (webrtc->priv->ice_stream_map, IceStreamItem, i);
+
+ gst_webrtc_ice_gather_candidates (webrtc->priv->ice, item->stream);
+ }
+ }
+
+ if (webrtc->current_local_description && webrtc->current_remote_description) {
+ int i;
+
+ for (i = 0; i < webrtc->priv->pending_ice_candidates->len; i++) {
+ IceCandidateItem *item =
+ g_array_index (webrtc->priv->pending_ice_candidates,
+ IceCandidateItem *, i);
+
+ _add_ice_candidate (webrtc, item);
+ }
+ g_array_set_size (webrtc->priv->pending_ice_candidates, 0);
+ }
+
+out:
+ PC_UNLOCK (webrtc);
+ gst_promise_reply (sd->promise, NULL);
+ PC_LOCK (webrtc);
+}
+
+static void
+_free_set_description_data (struct set_description *sd)
+{
+ if (sd->promise)
+ gst_promise_unref (sd->promise);
+ if (sd->sdp)
+ gst_webrtc_session_description_free (sd->sdp);
+ g_free (sd);
+}
+
+static void
+gst_webrtc_bin_set_remote_description (GstWebRTCBin * webrtc,
+ GstWebRTCSessionDescription * remote_sdp, GstPromise * promise)
+{
+ struct set_description *sd;
+
+ if (remote_sdp == NULL)
+ goto bad_input;
+ if (remote_sdp->sdp == NULL)
+ goto bad_input;
+
+ sd = g_new0 (struct set_description, 1);
+ if (promise != NULL)
+ sd->promise = gst_promise_ref (promise);
+ sd->source = SDP_REMOTE;
+ sd->sdp = gst_webrtc_session_description_copy (remote_sdp);
+
+ gst_webrtc_bin_enqueue_task (webrtc, (GstWebRTCBinFunc) _set_description_task,
+ sd, (GDestroyNotify) _free_set_description_data);
+
+ return;
+
+bad_input:
+ {
+ gst_promise_reply (promise, NULL);
+ g_return_if_reached ();
+ }
+}
+
+static void
+gst_webrtc_bin_set_local_description (GstWebRTCBin * webrtc,
+ GstWebRTCSessionDescription * local_sdp, GstPromise * promise)
+{
+ struct set_description *sd;
+
+ if (local_sdp == NULL)
+ goto bad_input;
+ if (local_sdp->sdp == NULL)
+ goto bad_input;
+
+ sd = g_new0 (struct set_description, 1);
+ if (promise != NULL)
+ sd->promise = gst_promise_ref (promise);
+ sd->source = SDP_LOCAL;
+ sd->sdp = gst_webrtc_session_description_copy (local_sdp);
+
+ gst_webrtc_bin_enqueue_task (webrtc, (GstWebRTCBinFunc) _set_description_task,
+ sd, (GDestroyNotify) _free_set_description_data);
+
+ return;
+
+bad_input:
+ {
+ gst_promise_reply (promise, NULL);
+ g_return_if_reached ();
+ }
+}
+
+static void
+_add_ice_candidate_task (GstWebRTCBin * webrtc, IceCandidateItem * item)
+{
+ if (!webrtc->current_local_description || !webrtc->current_remote_description) {
+ IceCandidateItem *new = g_new0 (IceCandidateItem, 1);
+ new->mlineindex = item->mlineindex;
+ new->candidate = g_strdup (item->candidate);
+
+ g_array_append_val (webrtc->priv->pending_ice_candidates, new);
+ } else {
+ _add_ice_candidate (webrtc, item);
+ }
+}
+
+static void
+_free_ice_candidate_item (IceCandidateItem * item)
+{
+ _clear_ice_candidate_item (&item);
+}
+
+static void
+gst_webrtc_bin_add_ice_candidate (GstWebRTCBin * webrtc, guint mline,
+ const gchar * attr)
+{
+ IceCandidateItem *item;
+
+ item = g_new0 (IceCandidateItem, 1);
+ item->mlineindex = mline;
+ if (!g_ascii_strncasecmp (attr, "a=candidate:", 12))
+ item->candidate = g_strdup (attr);
+ else if (!g_ascii_strncasecmp (attr, "candidate:", 10))
+ item->candidate = g_strdup_printf ("a=%s", attr);
+ gst_webrtc_bin_enqueue_task (webrtc,
+ (GstWebRTCBinFunc) _add_ice_candidate_task, item,
+ (GDestroyNotify) _free_ice_candidate_item);
+}
+
+static void
+_on_ice_candidate_task (GstWebRTCBin * webrtc, IceCandidateItem * item)
+{
+ const gchar *cand = item->candidate;
+
+ if (!g_ascii_strncasecmp (cand, "a=candidate:", 12)) {
+ /* stripping away "a=" */
+ cand += 2;
+ }
+
+ GST_TRACE_OBJECT (webrtc, "produced ICE candidate for mline:%u and %s",
+ item->mlineindex, cand);
+
+ PC_UNLOCK (webrtc);
+ g_signal_emit (webrtc, gst_webrtc_bin_signals[ON_ICE_CANDIDATE_SIGNAL],
+ 0, item->mlineindex, cand);
+ PC_LOCK (webrtc);
+}
+
+static void
+_on_ice_candidate (GstWebRTCICE * ice, guint session_id,
+ gchar * candidate, GstWebRTCBin * webrtc)
+{
+ IceCandidateItem *item = g_new0 (IceCandidateItem, 1);
+
+ /* FIXME: bundle support */
+ item->mlineindex = session_id;
+ item->candidate = g_strdup (candidate);
+
+ gst_webrtc_bin_enqueue_task (webrtc,
+ (GstWebRTCBinFunc) _on_ice_candidate_task, item,
+ (GDestroyNotify) _free_ice_candidate_item);
+}
+
+/* https://www.w3.org/TR/webrtc/#dfn-stats-selection-algorithm */
+static GstStructure *
+_get_stats_from_selector (GstWebRTCBin * webrtc, gpointer selector)
+{
+ if (selector)
+ GST_FIXME_OBJECT (webrtc, "Implement stats selection");
+
+ return gst_structure_copy (webrtc->priv->stats);
+}
+
+struct get_stats
+{
+ GstPad *pad;
+ GstPromise *promise;
+};
+
+static void
+_free_get_stats (struct get_stats *stats)
+{
+ if (stats->pad)
+ gst_object_unref (stats->pad);
+ if (stats->promise)
+ gst_promise_unref (stats->promise);
+ g_free (stats);
+}
+
+/* https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-getstats() */
+static void
+_get_stats_task (GstWebRTCBin * webrtc, struct get_stats *stats)
+{
+ GstStructure *s;
+ gpointer selector = NULL;
+
+ gst_webrtc_bin_update_stats (webrtc);
+
+ if (stats->pad) {
+ GstWebRTCBinPad *wpad = GST_WEBRTC_BIN_PAD (stats->pad);
+
+ if (wpad->trans) {
+ if (GST_PAD_DIRECTION (wpad) == GST_PAD_SRC) {
+ selector = wpad->trans->receiver;
+ } else {
+ selector = wpad->trans->sender;
+ }
+ }
+ }
+
+ s = _get_stats_from_selector (webrtc, selector);
+ gst_promise_reply (stats->promise, s);
+}
+
+static void
+gst_webrtc_bin_get_stats (GstWebRTCBin * webrtc, GstPad * pad,
+ GstPromise * promise)
+{
+ struct get_stats *stats;
+
+ g_return_if_fail (promise != NULL);
+ g_return_if_fail (pad == NULL || GST_IS_WEBRTC_BIN_PAD (pad));
+
+ stats = g_new0 (struct get_stats, 1);
+ stats->promise = gst_promise_ref (promise);
+ /* FIXME: check that pad exists in element */
+ if (pad)
+ stats->pad = gst_object_ref (pad);
+
+ gst_webrtc_bin_enqueue_task (webrtc, (GstWebRTCBinFunc) _get_stats_task,
+ stats, (GDestroyNotify) _free_get_stats);
+}
+
+static GstWebRTCRTPTransceiver *
+gst_webrtc_bin_add_transceiver (GstWebRTCBin * webrtc,
+ GstWebRTCRTPTransceiverDirection direction, GstCaps * caps)
+{
+ WebRTCTransceiver *trans;
+ GstWebRTCRTPTransceiver *rtp_trans;
+
+ g_return_val_if_fail (direction != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE,
+ NULL);
+
+ trans = _create_webrtc_transceiver (webrtc);
+ rtp_trans = GST_WEBRTC_RTP_TRANSCEIVER (trans);
+ rtp_trans->direction = direction;
+ if (caps)
+ rtp_trans->codec_preferences = gst_caps_ref (caps);
+
+ return gst_object_ref (trans);
+}
+
+static void
+_deref_and_unref (GstObject ** object)
+{
+ if (object)
+ gst_object_unref (*object);
+}
+
+static GArray *
+gst_webrtc_bin_get_transceivers (GstWebRTCBin * webrtc)
+{
+ GArray *arr = g_array_new (FALSE, TRUE, sizeof (gpointer));
+ int i;
+
+ g_array_set_clear_func (arr, (GDestroyNotify) _deref_and_unref);
+
+ for (i = 0; i < webrtc->priv->transceivers->len; i++) {
+ GstWebRTCRTPTransceiver *trans =
+ g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *,
+ i);
+ gst_object_ref (trans);
+ g_array_append_val (arr, trans);
+ }
+
+ return arr;
+}
+
+/* === rtpbin signal implementations === */
+
+static void
+on_rtpbin_pad_added (GstElement * rtpbin, GstPad * new_pad,
+ GstWebRTCBin * webrtc)
+{
+ gchar *new_pad_name = NULL;
+
+ new_pad_name = gst_pad_get_name (new_pad);
+ GST_TRACE_OBJECT (webrtc, "new rtpbin pad %s", new_pad_name);
+ if (g_str_has_prefix (new_pad_name, "recv_rtp_src_")) {
+ guint32 session_id = 0, ssrc = 0, pt = 0;
+ GstWebRTCRTPTransceiver *rtp_trans;
+ WebRTCTransceiver *trans;
+ TransportStream *stream;
+ GstWebRTCBinPad *pad;
+
+ sscanf (new_pad_name, "recv_rtp_src_%u_%u_%u", &session_id, &ssrc, &pt);
+
+ stream = _find_transport_for_session (webrtc, session_id);
+ if (!stream)
+ g_warn_if_reached ();
+
+ /* FIXME: bundle! */
+ rtp_trans = _find_transceiver_for_mline (webrtc, session_id);
+ if (!rtp_trans)
+ g_warn_if_reached ();
+ trans = WEBRTC_TRANSCEIVER (rtp_trans);
+ g_assert (trans->stream == stream);
+
+ pad = _find_pad_for_transceiver (webrtc, GST_PAD_SRC, rtp_trans);
+
+ GST_TRACE_OBJECT (webrtc, "found pad %" GST_PTR_FORMAT
+ " for rtpbin pad name %s", pad, new_pad_name);
+ if (!pad)
+ g_warn_if_reached ();
+ gst_ghost_pad_set_target (GST_GHOST_PAD (pad), GST_PAD (new_pad));
+
+ if (webrtc->priv->running)
+ gst_pad_set_active (GST_PAD (pad), TRUE);
+ gst_element_add_pad (GST_ELEMENT (webrtc), GST_PAD (pad));
+ _remove_pending_pad (webrtc, pad);
+
+ gst_object_unref (pad);
+ }
+ g_free (new_pad_name);
+}
+
+/* only used for the receiving streams */
+static GstCaps *
+on_rtpbin_request_pt_map (GstElement * rtpbin, guint session_id, guint pt,
+ GstWebRTCBin * webrtc)
+{
+ TransportStream *stream;
+ GstCaps *ret;
+
+ GST_DEBUG_OBJECT (webrtc, "getting pt map for pt %d in session %d", pt,
+ session_id);
+
+ stream = _find_transport_for_session (webrtc, session_id);
+ if (!stream)
+ goto unknown_session;
+
+ if ((ret = _transport_stream_get_caps_for_pt (stream, pt)))
+ gst_caps_ref (ret);
+
+ GST_TRACE_OBJECT (webrtc, "Found caps %" GST_PTR_FORMAT " for pt %d in "
+ "session %d", ret, pt, session_id);
+
+ return ret;
+
+unknown_session:
+ {
+ GST_DEBUG_OBJECT (webrtc, "unknown session %d", session_id);
+ return NULL;
+ }
+}
+
+static GstElement *
+on_rtpbin_request_aux_sender (GstElement * rtpbin, guint session_id,
+ GstWebRTCBin * webrtc)
+{
+ return NULL;
+}
+
+static GstElement *
+on_rtpbin_request_aux_receiver (GstElement * rtpbin, guint session_id,
+ GstWebRTCBin * webrtc)
+{
+ return NULL;
+}
+
+static void
+on_rtpbin_ssrc_active (GstElement * rtpbin, guint session_id, guint ssrc,
+ GstWebRTCBin * webrtc)
+{
+}
+
+static void
+on_rtpbin_new_jitterbuffer (GstElement * rtpbin, GstElement * jitterbuffer,
+ guint session_id, guint ssrc, GstWebRTCBin * webrtc)
+{
+}
+
+static GstElement *
+_create_rtpbin (GstWebRTCBin * webrtc)
+{
+ GstElement *rtpbin;
+
+ if (!(rtpbin = gst_element_factory_make ("rtpbin", "rtpbin")))
+ return NULL;
+
+ /* mandated by WebRTC */
+ gst_util_set_object_arg (G_OBJECT (rtpbin), "rtp-profile", "savpf");
+
+ g_signal_connect (rtpbin, "pad-added", G_CALLBACK (on_rtpbin_pad_added),
+ webrtc);
+ g_signal_connect (rtpbin, "request-pt-map",
+ G_CALLBACK (on_rtpbin_request_pt_map), webrtc);
+ g_signal_connect (rtpbin, "request-aux-sender",
+ G_CALLBACK (on_rtpbin_request_aux_sender), webrtc);
+ g_signal_connect (rtpbin, "request-aux-receiver",
+ G_CALLBACK (on_rtpbin_request_aux_receiver), webrtc);
+ g_signal_connect (rtpbin, "on-ssrc-active",
+ G_CALLBACK (on_rtpbin_ssrc_active), webrtc);
+ g_signal_connect (rtpbin, "new-jitterbuffer",
+ G_CALLBACK (on_rtpbin_new_jitterbuffer), webrtc);
+
+ return rtpbin;
+}
+
+static GstStateChangeReturn
+gst_webrtc_bin_change_state (GstElement * element, GstStateChange transition)
+{
+ GstWebRTCBin *webrtc = GST_WEBRTC_BIN (element);
+ GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+
+ GST_DEBUG ("changing state: %s => %s",
+ gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
+ gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:{
+ GstElement *nice;
+ if (!webrtc->rtpbin) {
+ /* FIXME: is this the right thing for a missing plugin? */
+ GST_ELEMENT_ERROR (webrtc, CORE, MISSING_PLUGIN, (NULL),
+ ("%s", "rtpbin element is not available"));
+ return GST_STATE_CHANGE_FAILURE;
+ }
+ nice = gst_element_factory_make ("nicesrc", NULL);
+ if (!nice) {
+ /* FIXME: is this the right thing for a missing plugin? */
+ GST_ELEMENT_ERROR (webrtc, CORE, MISSING_PLUGIN, (NULL),
+ ("%s", "libnice elements are not available"));
+ return GST_STATE_CHANGE_FAILURE;
+ }
+ gst_object_unref (nice);
+ nice = gst_element_factory_make ("nicesink", NULL);
+ if (!nice) {
+ /* FIXME: is this the right thing for a missing plugin? */
+ GST_ELEMENT_ERROR (webrtc, CORE, MISSING_PLUGIN, (NULL),
+ ("%s", "libnice elements are not available"));
+ return GST_STATE_CHANGE_FAILURE;
+ }
+ gst_object_unref (nice);
+ _update_need_negotiation (webrtc);
+ break;
+ }
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ webrtc->priv->running = TRUE;
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ return ret;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ /* Mangle the return value to NO_PREROLL as that's what really is
+ * occurring here however cannot be propagated correctly due to nicesrc
+ * requiring that it be in PLAYING already in order to send/receive
+ * correctly :/ */
+ ret = GST_STATE_CHANGE_NO_PREROLL;
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ webrtc->priv->running = FALSE;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static GstPad *
+gst_webrtc_bin_request_new_pad (GstElement * element, GstPadTemplate * templ,
+ const gchar * name, const GstCaps * caps)
+{
+ GstWebRTCBin *webrtc = GST_WEBRTC_BIN (element);
+ GstWebRTCBinPad *pad = NULL;
+ GstPluginFeature *feature;
+ guint serial;
+
+ feature = gst_registry_lookup_feature (gst_registry_get (), "nicesrc");
+ if (feature) {
+ gst_object_unref (feature);
+ } else {
+ GST_ELEMENT_ERROR (element, CORE, MISSING_PLUGIN, NULL,
+ ("%s", "libnice elements are not available"));
+ return NULL;
+ }
+
+ feature = gst_registry_lookup_feature (gst_registry_get (), "nicesink");
+ if (feature) {
+ gst_object_unref (feature);
+ } else {
+ GST_ELEMENT_ERROR (element, CORE, MISSING_PLUGIN, NULL,
+ ("%s", "libnice elements are not available"));
+ return NULL;
+ }
+
+ if (templ->direction == GST_PAD_SINK ||
+ g_strcmp0 (templ->name_template, "sink_%u") == 0) {
+ GstWebRTCRTPTransceiver *trans;
+
+ GST_OBJECT_LOCK (webrtc);
+ if (name == NULL || strlen (name) < 6 || !g_str_has_prefix (name, "sink_")) {
+ /* no name given when requesting the pad, use next available int */
+ serial = webrtc->priv->max_sink_pad_serial++;
+ } else {
+ /* parse serial number from requested padname */
+ serial = g_ascii_strtoull (&name[5], NULL, 10);
+ if (serial > webrtc->priv->max_sink_pad_serial)
+ webrtc->priv->max_sink_pad_serial = serial;
+ }
+ GST_OBJECT_UNLOCK (webrtc);
+
+ pad = _create_pad_for_sdp_media (webrtc, GST_PAD_SINK, serial);
+ trans = _find_transceiver_for_mline (webrtc, serial);
+ if (!(trans =
+ GST_WEBRTC_RTP_TRANSCEIVER (_create_webrtc_transceiver (webrtc)))) {
+ trans->direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV;
+ trans->mline = serial;
+ }
+ pad->trans = gst_object_ref (trans);
+ _connect_input_stream (webrtc, pad);
+
+ /* TODO: update negotiation-needed */
+ _add_pad (webrtc, pad);
+ }
+
+ return GST_PAD (pad);
+}
+
+static void
+gst_webrtc_bin_release_pad (GstElement * element, GstPad * pad)
+{
+ GstWebRTCBin *webrtc = GST_WEBRTC_BIN (element);
+ GstWebRTCBinPad *webrtc_pad = GST_WEBRTC_BIN_PAD (pad);
+
+ if (webrtc_pad->trans)
+ gst_object_unref (webrtc_pad->trans);
+ webrtc_pad->trans = NULL;
+
+ _remove_pad (webrtc, webrtc_pad);
+}
+
+static void
+gst_webrtc_bin_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstWebRTCBin *webrtc = GST_WEBRTC_BIN (object);
+
+ switch (prop_id) {
+ case PROP_STUN_SERVER:
+ case PROP_TURN_SERVER:
+ g_object_set_property (G_OBJECT (webrtc->priv->ice), pspec->name, value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_webrtc_bin_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstWebRTCBin *webrtc = GST_WEBRTC_BIN (object);
+
+ PC_LOCK (webrtc);
+ switch (prop_id) {
+ case PROP_CONNECTION_STATE:
+ g_value_set_enum (value, webrtc->peer_connection_state);
+ break;
+ case PROP_SIGNALING_STATE:
+ g_value_set_enum (value, webrtc->signaling_state);
+ break;
+ case PROP_ICE_GATHERING_STATE:
+ g_value_set_enum (value, webrtc->ice_gathering_state);
+ break;
+ case PROP_ICE_CONNECTION_STATE:
+ g_value_set_enum (value, webrtc->ice_connection_state);
+ break;
+ case PROP_LOCAL_DESCRIPTION:
+ if (webrtc->pending_local_description)
+ g_value_set_boxed (value, webrtc->pending_local_description);
+ else if (webrtc->current_local_description)
+ g_value_set_boxed (value, webrtc->current_local_description);
+ else
+ g_value_set_boxed (value, NULL);
+ break;
+ case PROP_CURRENT_LOCAL_DESCRIPTION:
+ g_value_set_boxed (value, webrtc->current_local_description);
+ break;
+ case PROP_PENDING_LOCAL_DESCRIPTION:
+ g_value_set_boxed (value, webrtc->pending_local_description);
+ break;
+ case PROP_REMOTE_DESCRIPTION:
+ if (webrtc->pending_remote_description)
+ g_value_set_boxed (value, webrtc->pending_remote_description);
+ else if (webrtc->current_remote_description)
+ g_value_set_boxed (value, webrtc->current_remote_description);
+ else
+ g_value_set_boxed (value, NULL);
+ break;
+ case PROP_CURRENT_REMOTE_DESCRIPTION:
+ g_value_set_boxed (value, webrtc->current_remote_description);
+ break;
+ case PROP_PENDING_REMOTE_DESCRIPTION:
+ g_value_set_boxed (value, webrtc->pending_remote_description);
+ break;
+ case PROP_STUN_SERVER:
+ case PROP_TURN_SERVER:
+ g_object_get_property (G_OBJECT (webrtc->priv->ice), pspec->name, value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+ PC_UNLOCK (webrtc);
+}
+
+static void
+_free_pending_pad (GstPad * pad)
+{
+ gst_object_unref (pad);
+}
+
+static void
+gst_webrtc_bin_dispose (GObject * object)
+{
+ GstWebRTCBin *webrtc = GST_WEBRTC_BIN (object);
+
+ _stop_thread (webrtc);
+
+ if (webrtc->priv->ice)
+ gst_object_unref (webrtc->priv->ice);
+ webrtc->priv->ice = NULL;
+
+ if (webrtc->priv->ice_stream_map)
+ g_array_free (webrtc->priv->ice_stream_map, TRUE);
+ webrtc->priv->ice_stream_map = NULL;
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gst_webrtc_bin_finalize (GObject * object)
+{
+ GstWebRTCBin *webrtc = GST_WEBRTC_BIN (object);
+
+ if (webrtc->priv->transports)
+ g_array_free (webrtc->priv->transports, TRUE);
+ webrtc->priv->transports = NULL;
+
+ if (webrtc->priv->transceivers)
+ g_array_free (webrtc->priv->transceivers, TRUE);
+ webrtc->priv->transceivers = NULL;
+
+ if (webrtc->priv->pending_ice_candidates)
+ g_array_free (webrtc->priv->pending_ice_candidates, TRUE);
+ webrtc->priv->pending_ice_candidates = NULL;
+
+ if (webrtc->priv->session_mid_map)
+ g_array_free (webrtc->priv->session_mid_map, TRUE);
+ webrtc->priv->session_mid_map = NULL;
+
+ if (webrtc->priv->pending_pads)
+ g_list_free_full (webrtc->priv->pending_pads,
+ (GDestroyNotify) _free_pending_pad);
+ webrtc->priv->pending_pads = NULL;
+
+ if (webrtc->current_local_description)
+ gst_webrtc_session_description_free (webrtc->current_local_description);
+ webrtc->current_local_description = NULL;
+ if (webrtc->pending_local_description)
+ gst_webrtc_session_description_free (webrtc->pending_local_description);
+ webrtc->pending_local_description = NULL;
+
+ if (webrtc->current_remote_description)
+ gst_webrtc_session_description_free (webrtc->current_remote_description);
+ webrtc->current_remote_description = NULL;
+ if (webrtc->pending_remote_description)
+ gst_webrtc_session_description_free (webrtc->pending_remote_description);
+ webrtc->pending_remote_description = NULL;
+
+ if (webrtc->priv->stats)
+ gst_structure_free (webrtc->priv->stats);
+ webrtc->priv->stats = NULL;
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_webrtc_bin_class_init (GstWebRTCBinClass * klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+ GstElementClass *element_class = (GstElementClass *) klass;
+
+ g_type_class_add_private (klass, sizeof (GstWebRTCBinPrivate));
+
+ element_class->request_new_pad = gst_webrtc_bin_request_new_pad;
+ element_class->release_pad = gst_webrtc_bin_release_pad;
+ element_class->change_state = gst_webrtc_bin_change_state;
+
+ gst_element_class_add_static_pad_template (element_class, &sink_template);
+ gst_element_class_add_static_pad_template (element_class, &src_template);
+
+ gst_element_class_set_metadata (element_class, "WebRTC Bin",
+ "Filter/Network/WebRTC", "A bin for webrtc connections",
+ "Matthew Waters <matthew@centricular.com>");
+
+ gobject_class->get_property = gst_webrtc_bin_get_property;
+ gobject_class->set_property = gst_webrtc_bin_set_property;
+ gobject_class->dispose = gst_webrtc_bin_dispose;
+ gobject_class->finalize = gst_webrtc_bin_finalize;
+
+ g_object_class_install_property (gobject_class,
+ PROP_LOCAL_DESCRIPTION,
+ g_param_spec_boxed ("local-description", "Local Description",
+ "The local SDP description to use for this connection",
+ GST_TYPE_WEBRTC_SESSION_DESCRIPTION,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_REMOTE_DESCRIPTION,
+ g_param_spec_boxed ("remote-description", "Remote Description",
+ "The remote SDP description to use for this connection",
+ GST_TYPE_WEBRTC_SESSION_DESCRIPTION,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_STUN_SERVER,
+ g_param_spec_string ("stun-server", "STUN Server",
+ "The STUN server of the form stun://hostname:port",
+ NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_TURN_SERVER,
+ g_param_spec_string ("turn-server", "TURN Server",
+ "The TURN server of the form turn(s)://username:password@host:port",
+ NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_CONNECTION_STATE,
+ g_param_spec_enum ("connection-state", "Connection State",
+ "The overall connection state of this element",
+ GST_TYPE_WEBRTC_PEER_CONNECTION_STATE,
+ GST_WEBRTC_PEER_CONNECTION_STATE_NEW,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_SIGNALING_STATE,
+ g_param_spec_enum ("signaling-state", "Signaling State",
+ "The signaling state of this element",
+ GST_TYPE_WEBRTC_SIGNALING_STATE,
+ GST_WEBRTC_SIGNALING_STATE_STABLE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_ICE_CONNECTION_STATE,
+ g_param_spec_enum ("ice-connection-state", "ICE connection state",
+ "The collective connection state of all ICETransport's",
+ GST_TYPE_WEBRTC_ICE_CONNECTION_STATE,
+ GST_WEBRTC_ICE_CONNECTION_STATE_NEW,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_ICE_GATHERING_STATE,
+ g_param_spec_enum ("ice-gathering-state", "ICE gathering state",
+ "The collective gathering state of all ICETransport's",
+ GST_TYPE_WEBRTC_ICE_GATHERING_STATE,
+ GST_WEBRTC_ICE_GATHERING_STATE_NEW,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GstWebRTCBin::create-offer:
+ * @object: the #GstWebRtcBin
+ * @options: create-offer options
+ * @promise: a #GstPromise which will contain the offer
+ */
+ gst_webrtc_bin_signals[CREATE_OFFER_SIGNAL] =
+ g_signal_new_class_handler ("create-offer", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_CALLBACK (gst_webrtc_bin_create_offer), NULL, NULL,
+ g_cclosure_marshal_generic, G_TYPE_NONE, 2, GST_TYPE_STRUCTURE,
+ GST_TYPE_PROMISE);
+
+ /**
+ * GstWebRTCBin::create-answer:
+ * @object: the #GstWebRtcBin
+ * @options: create-answer options
+ * @promise: a #GstPromise which will contain the answer
+ */
+ gst_webrtc_bin_signals[CREATE_ANSWER_SIGNAL] =
+ g_signal_new_class_handler ("create-answer", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_CALLBACK (gst_webrtc_bin_create_answer), NULL, NULL,
+ g_cclosure_marshal_generic, G_TYPE_NONE, 2, GST_TYPE_STRUCTURE,
+ GST_TYPE_PROMISE);
+
+ /**
+ * GstWebRTCBin::set-local-description:
+ * @object: the #GstWebRtcBin
+ * @type: the type of description being set
+ * @sdp: a #GstSDPMessage description
+ * @promise (allow-none): a #GstPromise to be notified when it's set
+ */
+ gst_webrtc_bin_signals[SET_LOCAL_DESCRIPTION_SIGNAL] =
+ g_signal_new_class_handler ("set-local-description",
+ G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_CALLBACK (gst_webrtc_bin_set_local_description), NULL, NULL,
+ g_cclosure_marshal_generic, G_TYPE_NONE, 2,
+ GST_TYPE_WEBRTC_SESSION_DESCRIPTION, GST_TYPE_PROMISE);
+
+ /**
+ * GstWebRTCBin::set-remote-description:
+ * @object: the #GstWebRtcBin
+ * @type: the type of description being set
+ * @sdp: a #GstSDPMessage description
+ * @promise (allow-none): a #GstPromise to be notified when it's set
+ */
+ gst_webrtc_bin_signals[SET_REMOTE_DESCRIPTION_SIGNAL] =
+ g_signal_new_class_handler ("set-remote-description",
+ G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_CALLBACK (gst_webrtc_bin_set_remote_description), NULL, NULL,
+ g_cclosure_marshal_generic, G_TYPE_NONE, 2,
+ GST_TYPE_WEBRTC_SESSION_DESCRIPTION, GST_TYPE_PROMISE);
+
+ /**
+ * GstWebRTCBin::add-ice-candidate:
+ * @object: the #GstWebRtcBin
+ * @ice-candidate: an ice candidate
+ */
+ gst_webrtc_bin_signals[ADD_ICE_CANDIDATE_SIGNAL] =
+ g_signal_new_class_handler ("add-ice-candidate",
+ G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_CALLBACK (gst_webrtc_bin_add_ice_candidate), NULL, NULL,
+ g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING);
+
+ /**
+ * GstWebRTCBin::get-stats:
+ * @object: the #GstWebRtcBin
+ * @promise: a #GstPromise for the result
+ *
+ * The @promise will contain the result of retrieving the session statistics.
+ * The structure will be named 'application/x-webrtc-stats and contain the
+ * following based on the webrtc-stats spec available from
+ * https://www.w3.org/TR/webrtc-stats/. As the webrtc-stats spec is a draft
+ * and is constantly changing these statistics may be changed to fit with
+ * the latest spec.
+ *
+ * Each field key is a unique identifer for each RTCStats
+ * (https://www.w3.org/TR/webrtc/#rtcstats-dictionary) value (another
+ * GstStructure) in the RTCStatsReport
+ * (https://www.w3.org/TR/webrtc/#rtcstatsreport-object). Each supported
+ * field in the RTCStats subclass is outlined below.
+ *
+ * Each statistics structure contains the following values as defined by
+ * the RTCStats dictionary (https://www.w3.org/TR/webrtc/#rtcstats-dictionary).
+ *
+ * "timestamp" G_TYPE_DOUBLE timestamp the statistics were generated
+ * "type" GST_TYPE_WEBRTC_STATS_TYPE the type of statistics reported
+ * "id" G_TYPE_STRING unique identifier
+ *
+ * RTCCodecStats supported fields (https://w3c.github.io/webrtc-stats/#codec-dict*)
+ *
+ * "payload-type" G_TYPE_UINT the rtp payload number in use
+ * "clock-rate" G_TYPE_UINT the rtp clock-rate
+ *
+ * RTCRTPStreamStats supported fields (https://w3c.github.io/webrtc-stats/#streamstats-dict*)
+ *
+ * "ssrc" G_TYPE_STRING the rtp sequence src in use
+ * "transport-id" G_TYPE_STRING identifier for the associated RTCTransportStats for this stream
+ * "codec-id" G_TYPE_STRING identifier for the associated RTCCodecStats for this stream
+ * "fir-count" G_TYPE_UINT FIR requests received by the sender (only for local statistics)
+ * "pli-count" G_TYPE_UINT PLI requests received by the sender (only for local statistics)
+ * "nack-count" G_TYPE_UINT NACK requests received by the sender (only for local statistics)
+ *
+ * RTCReceivedStreamStats supported fields (https://w3c.github.io/webrtc-stats/#receivedrtpstats-dict*)
+ *
+ * "packets-received" G_TYPE_UINT64 number of packets received (only for local inbound)
+ * "bytes-received" G_TYPE_UINT64 number of bytes received (only for local inbound)
+ * "packets-lost" G_TYPE_UINT number of packets lost
+ * "jitter" G_TYPE_DOUBLE packet jitter measured in secondss
+ *
+ * RTCInboundRTPStreamStats supported fields (https://w3c.github.io/webrtc-stats/#inboundrtpstats-dict*)
+ *
+ * "remote-id" G_TYPE_STRING identifier for the associated RTCRemoteOutboundRTPSTreamStats
+ *
+ * RTCRemoteInboundRTPStreamStats supported fields (https://w3c.github.io/webrtc-stats/#remoteinboundrtpstats-dict*)
+ *
+ * "local-id" G_TYPE_STRING identifier for the associated RTCOutboundRTPSTreamStats
+ * "round-trip-time" G_TYPE_DOUBLE round trip time of packets measured in seconds
+ *
+ * RTCSentRTPStreamStats supported fields (https://w3c.github.io/webrtc-stats/#sentrtpstats-dict*)
+ *
+ * "packets-sent" G_TYPE_UINT64 number of packets sent (only for local outbound)
+ * "bytes-sent" G_TYPE_UINT64 number of packets sent (only for local outbound)
+ *
+ * RTCOutboundRTPStreamStats supported fields (https://w3c.github.io/webrtc-stats/#outboundrtpstats-dict*)
+ *
+ * "remote-id" G_TYPE_STRING identifier for the associated RTCRemoteInboundRTPSTreamStats
+ *
+ * RTCRemoteOutboundRTPStreamStats supported fields (https://w3c.github.io/webrtc-stats/#remoteoutboundrtpstats-dict*)
+ *
+ * "local-id" G_TYPE_STRING identifier for the associated RTCInboundRTPSTreamStats
+ *
+ */
+ gst_webrtc_bin_signals[GET_STATS_SIGNAL] =
+ g_signal_new_class_handler ("get-stats",
+ G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_CALLBACK (gst_webrtc_bin_get_stats), NULL, NULL,
+ g_cclosure_marshal_generic, G_TYPE_NONE, 2, GST_TYPE_PAD,
+ GST_TYPE_PROMISE);
+
+ /**
+ * GstWebRTCBin::on-negotiation-needed:
+ * @object: the #GstWebRtcBin
+ */
+ gst_webrtc_bin_signals[ON_NEGOTIATION_NEEDED_SIGNAL] =
+ g_signal_new ("on-negotiation-needed", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
+ G_TYPE_NONE, 0);
+
+ /**
+ * GstWebRTCBin::on-ice-candidate:
+ * @object: the #GstWebRtcBin
+ * @candidate: the ICE candidate
+ */
+ gst_webrtc_bin_signals[ON_ICE_CANDIDATE_SIGNAL] =
+ g_signal_new ("on-ice-candidate", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
+ G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING);
+
+ /**
+ * GstWebRTCBin::add-transceiver:
+ * @object: the #GstWebRtcBin
+ * @direction: the direction of the new transceiver
+ * @caps: (allow none): the codec preferences for this transceiver
+ *
+ * Returns: the new #GstWebRTCRTPTransceiver
+ */
+ gst_webrtc_bin_signals[ADD_TRANSCEIVER_SIGNAL] =
+ g_signal_new_class_handler ("add-transceiver", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_CALLBACK (gst_webrtc_bin_add_transceiver), NULL, NULL,
+ g_cclosure_marshal_generic, GST_TYPE_WEBRTC_RTP_TRANSCEIVER, 2,
+ GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION, GST_TYPE_CAPS);
+
+ /**
+ * GstWebRTCBin::get-transceivers:
+ * @object: the #GstWebRtcBin
+ *
+ * Returns: a #GArray of #GstWebRTCRTPTransceivers
+ */
+ gst_webrtc_bin_signals[GET_TRANSCEIVERS_SIGNAL] =
+ g_signal_new_class_handler ("get-transceivers", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_CALLBACK (gst_webrtc_bin_get_transceivers), NULL, NULL,
+ g_cclosure_marshal_generic, G_TYPE_ARRAY, 0);
+}
+
+static void
+_deref_unparent_and_unref (GObject ** object)
+{
+ GstObject *obj = GST_OBJECT (*object);
+
+ GST_OBJECT_PARENT (obj) = NULL;
+
+ gst_object_unref (*object);
+}
+
+static void
+_transport_free (GObject ** object)
+{
+ TransportStream *stream = (TransportStream *) * object;
+ GstWebRTCBin *webrtc;
+
+ webrtc = GST_WEBRTC_BIN (GST_OBJECT_PARENT (stream));
+
+ if (stream->transport) {
+ g_signal_handlers_disconnect_by_data (stream->transport->transport, webrtc);
+ g_signal_handlers_disconnect_by_data (stream->transport, webrtc);
+ }
+ if (stream->rtcp_transport) {
+ g_signal_handlers_disconnect_by_data (stream->rtcp_transport->transport,
+ webrtc);
+ g_signal_handlers_disconnect_by_data (stream->rtcp_transport, webrtc);
+ }
+
+ gst_object_unref (*object);
+}
+
+static void
+gst_webrtc_bin_init (GstWebRTCBin * webrtc)
+{
+ webrtc->priv =
+ G_TYPE_INSTANCE_GET_PRIVATE ((webrtc), GST_TYPE_WEBRTC_BIN,
+ GstWebRTCBinPrivate);
+
+ _start_thread (webrtc);
+
+ webrtc->rtpbin = _create_rtpbin (webrtc);
+ gst_bin_add (GST_BIN (webrtc), webrtc->rtpbin);
+
+ webrtc->priv->transceivers = g_array_new (FALSE, TRUE, sizeof (gpointer));
+ g_array_set_clear_func (webrtc->priv->transceivers,
+ (GDestroyNotify) _deref_unparent_and_unref);
+
+ webrtc->priv->transports = g_array_new (FALSE, TRUE, sizeof (gpointer));
+ g_array_set_clear_func (webrtc->priv->transports,
+ (GDestroyNotify) _transport_free);
+
+ webrtc->priv->session_mid_map =
+ g_array_new (FALSE, TRUE, sizeof (SessionMidItem));
+ g_array_set_clear_func (webrtc->priv->session_mid_map,
+ (GDestroyNotify) clear_session_mid_item);
+
+ webrtc->priv->ice = gst_webrtc_ice_new ();
+ g_signal_connect (webrtc->priv->ice, "on-ice-candidate",
+ G_CALLBACK (_on_ice_candidate), webrtc);
+ webrtc->priv->ice_stream_map =
+ g_array_new (FALSE, TRUE, sizeof (IceStreamItem));
+ webrtc->priv->pending_ice_candidates =
+ g_array_new (FALSE, TRUE, sizeof (IceCandidateItem *));
+ g_array_set_clear_func (webrtc->priv->pending_ice_candidates,
+ (GDestroyNotify) _clear_ice_candidate_item);
+}