blob: b3746bf3e94b1e65f2bbc79f1ce93adc44b5d971 [file] [log] [blame]
/* GStreamer
* Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "gstwebrtcbin.h"
#include "gstwebrtcstats.h"
#include "transportstream.h"
#include "transportreceivebin.h"
#include "utils.h"
#include "webrtcsdp.h"
#include "webrtctransceiver.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define RANDOM_SESSION_ID \
((((((guint64) g_random_int()) << 32) | \
(guint64) g_random_int ())) & \
G_GUINT64_CONSTANT (0x7fffffffffffffff))
#define PC_GET_LOCK(w) (&w->priv->pc_lock)
#define PC_LOCK(w) (g_mutex_lock (PC_GET_LOCK(w)))
#define PC_UNLOCK(w) (g_mutex_unlock (PC_GET_LOCK(w)))
#define PC_GET_COND(w) (&w->priv->pc_cond)
#define PC_COND_WAIT(w) (g_cond_wait(PC_GET_COND(w), PC_GET_LOCK(w)))
#define PC_COND_BROADCAST(w) (g_cond_broadcast(PC_GET_COND(w)))
#define PC_COND_SIGNAL(w) (g_cond_signal(PC_GET_COND(w)))
/*
* This webrtcbin implements the majority of the W3's peerconnection API and
* implementation guide where possible. Generating offers, answers and setting
* local and remote SDP's are all supported. To start with, only the media
* interface has been implemented (no datachannel yet).
*
* Each input/output pad is equivalent to a Track in W3 parlance which are
* added/removed from the bin. The number of requested sink pads is the number
* of streams that will be sent to the receiver and will be associated with a
* GstWebRTCRTPTransceiver (very similar to W3 RTPTransceiver's).
*
* On the receiving side, RTPTransceiver's are created in response to setting
* a remote description. Output pads for the receiving streams in the set
* description are also created.
*/
/*
* TODO:
* assert sending payload type matches the stream
* reconfiguration (of anything)
* LS groups
* bundling
* setting custom DTLS certificates
* data channel
*
* seperate session id's from mlineindex properly
* how to deal with replacing a input/output track/stream
*/
static void _update_need_negotiation (GstWebRTCBin * webrtc);
#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;
if (pad->received_caps)
gst_caps_unref (pad->received_caps);
pad->received_caps = 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 gint
_transport_stream_get_pt (TransportStream * stream, const gchar * encoding_name)
{
guint i;
gint ret = 0;
for (i = 0; i < stream->ptmap->len; i++) {
PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i);
if (!gst_caps_is_empty (item->caps)) {
GstStructure *s = gst_caps_get_structure (item->caps, 0);
if (!g_strcmp0 (gst_structure_get_string (s, "encoding-name"),
encoding_name)) {
ret = item->pt;
break;
}
}
}
return ret;
}
static gboolean
gst_webrtcbin_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
{
GstWebRTCBinPad *wpad = GST_WEBRTC_BIN_PAD (pad);
if (GST_EVENT_TYPE (event) == GST_EVENT_CAPS) {
GstCaps *caps;
gboolean do_update;
gst_event_parse_caps (event, &caps);
do_update = (!wpad->received_caps
|| gst_caps_is_equal (wpad->received_caps, caps));
gst_caps_replace (&wpad->received_caps, caps);
if (do_update)
_update_need_negotiation (GST_WEBRTC_BIN (parent));
}
return gst_pad_event_default (pad, parent, event);
}
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);
gst_pad_set_event_function (GST_PAD (pad), gst_webrtcbin_sink_event);
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 GstPad *_connect_input_stream (GstWebRTCBin * webrtc,
GstWebRTCBinPad * pad);
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,
ON_NEW_TRANSCEIVER_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);
}
static gboolean
_all_sinks_have_caps (GstWebRTCBin * webrtc)
{
GList *l;
gboolean res = FALSE;
GST_OBJECT_LOCK (webrtc);
l = GST_ELEMENT (webrtc)->pads;
for (; l; l = g_list_next (l)) {
if (!GST_IS_WEBRTC_BIN_PAD (l->data))
continue;
if (!GST_WEBRTC_BIN_PAD (l->data)->received_caps)
goto done;
}
l = webrtc->priv->pending_pads;
for (; l; l = g_list_next (l)) {
if (!GST_IS_WEBRTC_BIN_PAD (l->data))
goto done;
}
res = TRUE;
done:
GST_OBJECT_UNLOCK (webrtc);
return res;
}
/* 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");
/* We can't negotiate until we have received caps on all our sink pads,
* as we will need the ssrcs in our offer / answer */
if (!_all_sinks_have_caps (webrtc)) {
GST_LOG_OBJECT (webrtc,
"no negotiation possible until caps have been received on all sink pads");
return FALSE;
}
/* 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 && 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 = NULL;
if (pad->received_caps) {
caps = gst_caps_ref (pad->received_caps);
} else if ((caps = gst_pad_get_current_caps (GST_PAD (pad)))) {
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 (GstWebRTCBin * webrtc,
WebRTCTransceiver * trans, const GstCaps * caps)
{
GstCaps *ret;
guint 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 (trans->do_nack)
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,
GstWebRTCRTPTransceiverDirection direction, guint mline)
{
WebRTCTransceiver *trans;
GstWebRTCRTPTransceiver *rtp_trans;
GstWebRTCRTPSender *sender;
GstWebRTCRTPReceiver *receiver;
sender = gst_webrtc_rtp_sender_new ();
receiver = gst_webrtc_rtp_receiver_new ();
trans = webrtc_transceiver_new (webrtc, sender, receiver);
rtp_trans = GST_WEBRTC_RTP_TRANSCEIVER (trans);
rtp_trans->direction = direction;
rtp_trans->mline = mline;
g_array_append_val (webrtc->priv->transceivers, trans);
gst_object_unref (sender);
gst_object_unref (receiver);
g_signal_emit (webrtc, gst_webrtc_bin_signals[ON_NEW_TRANSCEIVER_SIGNAL],
0, trans);
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;
}
static guint
g_array_find_uint (GArray * array, guint val)
{
guint i;
for (i = 0; i < array->len; i++) {
if (g_array_index (array, guint, i) == val)
return i;
}
return G_MAXUINT;
}
static gboolean
_pick_available_pt (GArray * reserved_pts, guint * i)
{
gboolean ret = FALSE;
for (*i = 96; *i <= 127; (*i)++) {
if (g_array_find_uint (reserved_pts, *i) == G_MAXUINT) {
g_array_append_val (reserved_pts, *i);
ret = TRUE;
break;
}
}
return ret;
}
static gboolean
_pick_fec_payload_types (GstWebRTCBin * webrtc, WebRTCTransceiver * trans,
GArray * reserved_pts, gint clockrate, gint * rtx_target_pt,
GstSDPMedia * media)
{
gboolean ret = TRUE;
if (trans->fec_type == GST_WEBRTC_FEC_TYPE_NONE)
goto done;
if (trans->fec_type == GST_WEBRTC_FEC_TYPE_ULP_RED && clockrate != -1) {
guint pt;
gchar *str;
if (!(ret = _pick_available_pt (reserved_pts, &pt)))
goto done;
/* https://tools.ietf.org/html/rfc5109#section-14.1 */
str = g_strdup_printf ("%u", pt);
gst_sdp_media_add_format (media, str);
g_free (str);
str = g_strdup_printf ("%u red/%d", pt, clockrate);
gst_sdp_media_add_attribute (media, "rtpmap", str);
g_free (str);
*rtx_target_pt = pt;
if (!(ret = _pick_available_pt (reserved_pts, &pt)))
goto done;
str = g_strdup_printf ("%u", pt);
gst_sdp_media_add_format (media, str);
g_free (str);
str = g_strdup_printf ("%u ulpfec/%d", pt, clockrate);
gst_sdp_media_add_attribute (media, "rtpmap", str);
g_free (str);
}
done:
return ret;
}
static gboolean
_pick_rtx_payload_types (GstWebRTCBin * webrtc, WebRTCTransceiver * trans,
GArray * reserved_pts, gint clockrate, gint target_pt, guint target_ssrc,
GstSDPMedia * media)
{
gboolean ret = TRUE;
if (trans->local_rtx_ssrc_map)
gst_structure_free (trans->local_rtx_ssrc_map);
trans->local_rtx_ssrc_map =
gst_structure_new_empty ("application/x-rtp-ssrc-map");
if (trans->do_nack) {
guint pt;
gchar *str;
if (!(ret = _pick_available_pt (reserved_pts, &pt)))
goto done;
/* https://tools.ietf.org/html/rfc4588#section-8.6 */
str = g_strdup_printf ("%u", target_ssrc);
gst_structure_set (trans->local_rtx_ssrc_map, str, G_TYPE_UINT,
g_random_int (), NULL);
g_free (str);
str = g_strdup_printf ("%u", pt);
gst_sdp_media_add_format (media, str);
g_free (str);
str = g_strdup_printf ("%u rtx/%d", pt, clockrate);
gst_sdp_media_add_attribute (media, "rtpmap", str);
g_free (str);
str = g_strdup_printf ("%u apt=%d", pt, target_pt);
gst_sdp_media_add_attribute (media, "fmtp", str);
g_free (str);
}
done:
return ret;
}
/* https://tools.ietf.org/html/rfc5576#section-4.2 */
static gboolean
_media_add_rtx_ssrc_group (GQuark field_id, const GValue * value,
GstSDPMedia * media)
{
gchar *str;
str =
g_strdup_printf ("FID %s %u", g_quark_to_string (field_id),
g_value_get_uint (value));
gst_sdp_media_add_attribute (media, "ssrc-group", str);
g_free (str);
return TRUE;
}
typedef struct
{
GstSDPMedia *media;
GstWebRTCBin *webrtc;
WebRTCTransceiver *trans;
} RtxSsrcData;
static gboolean
_media_add_rtx_ssrc (GQuark field_id, const GValue * value, RtxSsrcData * data)
{
gchar *str;
GstStructure *sdes;
const gchar *cname;
g_object_get (data->webrtc->rtpbin, "sdes", &sdes, NULL);
/* http://www.freesoft.org/CIE/RFC/1889/24.htm */
cname = gst_structure_get_string (sdes, "cname");
/* https://tools.ietf.org/html/draft-ietf-mmusic-msid-16 */
str =
g_strdup_printf ("%u msid:%s %s", g_value_get_uint (value),
cname, GST_OBJECT_NAME (data->trans));
gst_sdp_media_add_attribute (data->media, "ssrc", str);
g_free (str);
str = g_strdup_printf ("%u cname:%s", g_value_get_uint (value), cname);
gst_sdp_media_add_attribute (data->media, "ssrc", str);
g_free (str);
gst_structure_free (sdes);
return TRUE;
}
static void
_media_add_ssrcs (GstSDPMedia * media, GstCaps * caps, GstWebRTCBin * webrtc,
WebRTCTransceiver * trans)
{
guint i;
RtxSsrcData data = { media, webrtc, trans };
const gchar *cname;
GstStructure *sdes;
g_object_get (webrtc->rtpbin, "sdes", &sdes, NULL);
/* http://www.freesoft.org/CIE/RFC/1889/24.htm */
cname = gst_structure_get_string (sdes, "cname");
if (trans->local_rtx_ssrc_map)
gst_structure_foreach (trans->local_rtx_ssrc_map,
(GstStructureForeachFunc) _media_add_rtx_ssrc_group, media);
for (i = 0; i < gst_caps_get_size (caps); i++) {
const GstStructure *s = gst_caps_get_structure (caps, i);
guint ssrc;
if (gst_structure_get_uint (s, "ssrc", &ssrc)) {
gchar *str;
/* https://tools.ietf.org/html/draft-ietf-mmusic-msid-16 */
str =
g_strdup_printf ("%u msid:%s %s", ssrc, cname,
GST_OBJECT_NAME (trans));
gst_sdp_media_add_attribute (media, "ssrc", str);
g_free (str);
str = g_strdup_printf ("%u cname:%s", ssrc, cname);
gst_sdp_media_add_attribute (media, "ssrc", str);
g_free (str);
}
}
gst_structure_free (sdes);
if (trans->local_rtx_ssrc_map)
gst_structure_foreach (trans->local_rtx_ssrc_map,
(GstStructureForeachFunc) _media_add_rtx_ssrc, &data);
}
/* 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 (webrtc, WEBRTC_TRANSCEIVER (trans),
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);
}
if (type == GST_WEBRTC_SDP_TYPE_OFFER) {
GArray *reserved_pts = g_array_new (FALSE, FALSE, sizeof (guint));
const GstStructure *s = gst_caps_get_structure (caps, 0);
gint clockrate = -1;
gint rtx_target_pt;
gint original_rtx_target_pt; /* Workaround chrome bug: https://bugs.chromium.org/p/webrtc/issues/detail?id=6196 */
guint rtx_target_ssrc;
if (gst_structure_get_int (s, "payload", &rtx_target_pt))
g_array_append_val (reserved_pts, rtx_target_pt);
original_rtx_target_pt = rtx_target_pt;
gst_structure_get_int (s, "clock-rate", &clockrate);
gst_structure_get_uint (s, "ssrc", &rtx_target_ssrc);
_pick_fec_payload_types (webrtc, WEBRTC_TRANSCEIVER (trans), reserved_pts,
clockrate, &rtx_target_pt, media);
_pick_rtx_payload_types (webrtc, WEBRTC_TRANSCEIVER (trans), reserved_pts,
clockrate, rtx_target_pt, rtx_target_ssrc, media);
if (original_rtx_target_pt != rtx_target_pt)
_pick_rtx_payload_types (webrtc, WEBRTC_TRANSCEIVER (trans), reserved_pts,
clockrate, original_rtx_target_pt, rtx_target_ssrc, media);
g_array_free (reserved_pts, TRUE);
}
_media_add_ssrcs (media, caps, webrtc, WEBRTC_TRANSCEIVER (trans));
/* 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;
gchar *str;
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");
/* https://tools.ietf.org/html/draft-ietf-mmusic-msid-05#section-3 */
str = g_strdup_printf ("WMS %s", GST_OBJECT (webrtc)->name);
gst_sdp_message_add_attribute (ret, "msid-semantic", str);
g_free (str);
/* 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 void
_media_add_fec (GstSDPMedia * media, WebRTCTransceiver * trans, GstCaps * caps,
gint * rtx_target_pt)
{
guint i;
if (trans->fec_type == GST_WEBRTC_FEC_TYPE_NONE)
return;
for (i = 0; i < gst_caps_get_size (caps); i++) {
const GstStructure *s = gst_caps_get_structure (caps, i);
if (gst_structure_has_name (s, "application/x-rtp")) {
const gchar *encoding_name =
gst_structure_get_string (s, "encoding-name");
gint clock_rate;
gint pt;
if (gst_structure_get_int (s, "clock-rate", &clock_rate) &&
gst_structure_get_int (s, "payload", &pt)) {
if (!g_strcmp0 (encoding_name, "RED")) {
gchar *str;
str = g_strdup_printf ("%u", pt);
gst_sdp_media_add_format (media, str);
g_free (str);
str = g_strdup_printf ("%u red/%d", pt, clock_rate);
*rtx_target_pt = pt;
gst_sdp_media_add_attribute (media, "rtpmap", str);
g_free (str);
} else if (!g_strcmp0 (encoding_name, "ULPFEC")) {
gchar *str;
str = g_strdup_printf ("%u", pt);
gst_sdp_media_add_format (media, str);
g_free (str);
str = g_strdup_printf ("%u ulpfec/%d", pt, clock_rate);
gst_sdp_media_add_attribute (media, "rtpmap", str);
g_free (str);
}
}
}
}
}
static void
_media_add_rtx (GstSDPMedia * media, WebRTCTransceiver * trans,
GstCaps * offer_caps, gint target_pt, guint target_ssrc)
{
guint i;
const GstStructure *s;
if (trans->local_rtx_ssrc_map)
gst_structure_free (trans->local_rtx_ssrc_map);
trans->local_rtx_ssrc_map =
gst_structure_new_empty ("application/x-rtp-ssrc-map");
for (i = 0; i < gst_caps_get_size (offer_caps); i++) {
s = gst_caps_get_structure (offer_caps, i);
if (gst_structure_has_name (s, "application/x-rtp")) {
const gchar *encoding_name =
gst_structure_get_string (s, "encoding-name");
const gchar *apt_str = gst_structure_get_string (s, "apt");
gint apt;
gint clock_rate;
gint pt;
if (!apt_str)
continue;
apt = atoi (apt_str);
if (gst_structure_get_int (s, "clock-rate", &clock_rate) &&
gst_structure_get_int (s, "payload", &pt) && apt == target_pt) {
if (!g_strcmp0 (encoding_name, "RTX")) {
gchar *str;
str = g_strdup_printf ("%u", pt);
gst_sdp_media_add_format (media, str);
g_free (str);
str = g_strdup_printf ("%u rtx/%d", pt, clock_rate);
gst_sdp_media_add_attribute (media, "rtpmap", str);
g_free (str);
str = g_strdup_printf ("%d apt=%d", pt, apt);
gst_sdp_media_add_attribute (media, "fmtp", str);
g_free (str);
str = g_strdup_printf ("%u", target_ssrc);
gst_structure_set (trans->local_rtx_ssrc_map, str, G_TYPE_UINT,
g_random_int (), NULL);
}
}
}
}
}
static void
_get_rtx_target_pt_and_ssrc_from_caps (GstCaps * answer_caps, gint * target_pt,
guint * target_ssrc)
{
const GstStructure *s = gst_caps_get_structure (answer_caps, 0);
gst_structure_get_int (s, "payload", target_pt);
gst_structure_get_uint (s, "ssrc", target_ssrc);
}
static GstSDPMessage *
_create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
{
GstSDPMessage *ret = NULL;
const GstWebRTCSessionDescription *pending_remote =
webrtc->pending_remote_description;
guint 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;
guint j;
guint k;
gint target_pt = -1;
gint original_target_pt = -1;
guint target_ssrc = 0;
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;
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, j);
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);
}
if (!rtp_trans) {
trans = _create_webrtc_transceiver (webrtc, answer_dir, i);
rtp_trans = GST_WEBRTC_RTP_TRANSCEIVER (trans);
} else {
trans = WEBRTC_TRANSCEIVER (rtp_trans);
}
if (!trans->do_nack) {
answer_caps = gst_caps_make_writable (answer_caps);
for (k = 0; k < gst_caps_get_size (answer_caps); k++) {
GstStructure *s = gst_caps_get_structure (answer_caps, k);
gst_structure_remove_fields (s, "rtcp-fb-nack", NULL);
}
}
gst_sdp_media_set_media_from_caps (answer_caps, media);
_get_rtx_target_pt_and_ssrc_from_caps (answer_caps, &target_pt,
&target_ssrc);
original_target_pt = target_pt;
_media_add_fec (media, trans, offer_caps, &target_pt);
if (trans->do_nack) {
_media_add_rtx (media, trans, offer_caps, target_pt, target_ssrc);
if (target_pt != original_target_pt)
_media_add_rtx (media, trans, offer_caps, original_target_pt,
target_ssrc);
}
if (answer_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY)
_media_add_ssrcs (media, answer_caps, webrtc,
WEBRTC_TRANSCEIVER (rtp_trans));
gst_caps_unref (answer_caps);
answer_caps = NULL;
/* 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 gboolean
_filter_sdp_fields (GQuark field_id, const GValue * value,
GstStructure * new_structure)
{
if (!g_str_has_prefix (g_quark_to_string (field_id), "a-")) {
gst_structure_id_set_value (new_structure, field_id, value);
}
return TRUE;
}
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;
guint j;
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");
if (!g_strcmp0 (gst_structure_get_string (s, "encoding-name"),
"ULPFEC"))
gst_structure_set (s, "is-fec", G_TYPE_BOOLEAN, TRUE, NULL);
item.caps = gst_caps_new_empty ();
for (j = 0; j < gst_caps_get_size (outcaps); j++) {
GstStructure *s = gst_caps_get_structure (outcaps, j);
GstStructure *filtered =
gst_structure_new_empty (gst_structure_get_name (s));
gst_structure_foreach (s,
(GstStructureForeachFunc) _filter_sdp_fields, filtered);
gst_caps_append_structure (item.caps, filtered);
}
item.pt = pt;
gst_caps_unref (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);
/* 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 */
if (!trans)
trans =
GST_WEBRTC_RTP_TRANSCEIVER (_create_webrtc_transceiver (webrtc,
_get_direction_from_media (media), i));
_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