diff options
Diffstat (limited to 'ext/webrtc/gstwebrtcbin.c')
-rw-r--r-- | ext/webrtc/gstwebrtcbin.c | 3530 |
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); +} |