/* GStreamer * Copyright (C) 2017 Matthew Waters * * 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 #include #include #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; g_assert (answer_caps != NULL); } 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; if (sscanf (new_pad_name, "recv_rtp_src_%u_%u_%u", &session_id, &ssrc, &pt) != 3) { g_critical ("Invalid rtpbin pad name \'%s\'", new_pad_name); return; } 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 "); 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); }