webrtcbin: implement support for FEC and RTX
https://bugzilla.gnome.org/show_bug.cgi?id=795044
diff --git a/ext/webrtc/gstwebrtcbin.c b/ext/webrtc/gstwebrtcbin.c
index 08e6b6a..c553a93 100644
--- a/ext/webrtc/gstwebrtcbin.c
+++ b/ext/webrtc/gstwebrtcbin.c
@@ -76,6 +76,8 @@
* 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);
@@ -118,6 +120,10 @@
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);
}
@@ -145,6 +151,48 @@
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)
{
@@ -157,6 +205,8 @@
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;
@@ -172,6 +222,9 @@
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,
@@ -192,6 +245,7 @@
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,
@@ -1005,6 +1059,34 @@
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)
@@ -1013,6 +1095,14 @@
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 */
@@ -1161,7 +1251,7 @@
GST_LOG_OBJECT (webrtc, "retreiving codec preferences from %" GST_PTR_FORMAT,
trans);
- if (trans->codec_preferences) {
+ 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);
@@ -1187,18 +1277,21 @@
}
static GstCaps *
-_add_supported_attributes_to_caps (const GstCaps * caps)
+_add_supported_attributes_to_caps (GstWebRTCBin * webrtc,
+ WebRTCTransceiver * trans, const GstCaps * caps)
{
GstCaps *ret;
- int i;
+ 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 (!gst_structure_has_field (s, "rtcp-fb-nack"))
- gst_structure_set (s, "rtcp-fb-nack", G_TYPE_BOOLEAN, TRUE, NULL);
+ 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? */
@@ -1234,7 +1327,8 @@
}
static WebRTCTransceiver *
-_create_webrtc_transceiver (GstWebRTCBin * webrtc)
+_create_webrtc_transceiver (GstWebRTCBin * webrtc,
+ GstWebRTCRTPTransceiverDirection direction, guint mline)
{
WebRTCTransceiver *trans;
GstWebRTCRTPTransceiver *rtp_trans;
@@ -1245,14 +1339,17 @@
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;
+ 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;
}
@@ -1311,6 +1408,216 @@
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);
+
+ 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,
@@ -1353,7 +1660,9 @@
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);
+ 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 */
@@ -1384,6 +1693,34 @@
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++);
@@ -1426,6 +1763,7 @@
{
GstSDPMessage *ret;
int i;
+ gchar *str;
gst_sdp_message_new (&ret);
@@ -1440,6 +1778,11 @@
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;
@@ -1476,13 +1819,122 @@
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;
- int i;
+ guint i;
if (!webrtc->pending_remote_description) {
GST_ERROR_OBJECT (webrtc,
@@ -1523,7 +1975,11 @@
GstWebRTCDTLSSetup offer_setup, answer_setup;
GstCaps *offer_caps, *answer_caps = NULL;
gchar *cert;
- int j;
+ 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);
@@ -1557,7 +2013,6 @@
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);
@@ -1579,7 +2034,7 @@
rtp_trans =
g_array_index (webrtc->priv->transceivers, GstWebRTCRTPTransceiver *,
j);
- trans_caps = _find_codec_preferences (webrtc, rtp_trans, GST_PAD_SINK, i);
+ 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);
@@ -1621,21 +2076,44 @@
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);
+ trans = _create_webrtc_transceiver (webrtc, answer_dir, i);
rtp_trans = GST_WEBRTC_RTP_TRANSCEIVER (trans);
- rtp_trans->direction = answer_dir;
- rtp_trans->mline = i;
} 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);
@@ -1864,6 +2342,7 @@
gst_object_unref (rtp_sink);
trans = WEBRTC_TRANSCEIVER (pad->trans);
+
if (!trans->stream) {
TransportStream *item;
/* FIXME: bundle */
@@ -1958,6 +2437,16 @@
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,
@@ -2037,6 +2526,7 @@
GstStructure *s;
PtMapItem item;
gint pt;
+ guint j;
pt = atoi (gst_sdp_media_get_format (media, i));
@@ -2056,9 +2546,24 @@
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;
- item.caps = outcaps;
+ gst_caps_unref (outcaps);
g_array_append_val (stream->ptmap, item);
}
@@ -2195,14 +2700,14 @@
} 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);
+ 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);
}
}
@@ -2433,11 +2938,24 @@
}
if (webrtc->signaling_state == GST_WEBRTC_SIGNALING_STATE_STABLE) {
+ GList *tmp;
gboolean prev_need_negotiation = webrtc->priv->need_negotiation;
/* media modifications */
_update_transceivers_from_sdp (webrtc, sd->source, sd->sdp);
+ for (tmp = webrtc->priv->pending_sink_transceivers; tmp; tmp = tmp->next) {
+ GstWebRTCBinPad *pad = GST_WEBRTC_BIN_PAD (tmp->data);
+
+ _connect_input_stream (webrtc, pad);
+ gst_pad_remove_probe (GST_PAD (pad), pad->block_id);
+ pad->block_id = 0;
+ }
+
+ g_list_free_full (webrtc->priv->pending_sink_transceivers,
+ (GDestroyNotify) gst_object_unref);
+ webrtc->priv->pending_sink_transceivers = NULL;
+
/* 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
@@ -2737,9 +3255,8 @@
g_return_val_if_fail (direction != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE,
NULL);
- trans = _create_webrtc_transceiver (webrtc);
+ trans = _create_webrtc_transceiver (webrtc, direction, -1);
rtp_trans = GST_WEBRTC_RTP_TRANSCEIVER (trans);
- rtp_trans->direction = direction;
if (caps)
rtp_trans->codec_preferences = gst_caps_ref (caps);
@@ -2858,14 +3375,266 @@
on_rtpbin_request_aux_sender (GstElement * rtpbin, guint session_id,
GstWebRTCBin * webrtc)
{
- return NULL;
+ TransportStream *stream;
+ GstStructure *pt_map = gst_structure_new_empty ("application/x-rtp-pt-map");
+ GstElement *ret = NULL;
+ GstWebRTCRTPTransceiver *trans;
+
+ stream = _find_transport_for_session (webrtc, session_id);
+ trans = _find_transceiver (webrtc, &session_id,
+ (FindTransceiverFunc) transceiver_match_for_mline);
+
+ if (stream) {
+ guint i;
+
+ 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);
+ gint pt;
+ const gchar *apt_str = gst_structure_get_string (s, "apt");
+
+ if (!apt_str)
+ continue;
+
+ if (!g_strcmp0 (gst_structure_get_string (s, "encoding-name"), "RTX") &&
+ gst_structure_get_int (s, "payload", &pt)) {
+ gst_structure_set (pt_map, apt_str, G_TYPE_UINT, pt, NULL);
+ }
+ }
+ }
+ }
+
+ if (gst_structure_n_fields (pt_map)) {
+ GstElement *rtx;
+ GstPad *pad;
+ gchar *name;
+
+ GST_INFO ("creating AUX sender");
+ ret = gst_bin_new (NULL);
+ rtx = gst_element_factory_make ("rtprtxsend", NULL);
+ g_object_set (rtx, "payload-type-map", pt_map, "max-size-packets", 500,
+ NULL);
+
+ if (WEBRTC_TRANSCEIVER (trans)->local_rtx_ssrc_map)
+ g_object_set (rtx, "ssrc-map",
+ WEBRTC_TRANSCEIVER (trans)->local_rtx_ssrc_map, NULL);
+
+ gst_bin_add (GST_BIN (ret), rtx);
+
+ pad = gst_element_get_static_pad (rtx, "src");
+ name = g_strdup_printf ("src_%u", session_id);
+ gst_element_add_pad (ret, gst_ghost_pad_new (name, pad));
+ g_free (name);
+ gst_object_unref (pad);
+
+ pad = gst_element_get_static_pad (rtx, "sink");
+ name = g_strdup_printf ("sink_%u", session_id);
+ gst_element_add_pad (ret, gst_ghost_pad_new (name, pad));
+ g_free (name);
+ gst_object_unref (pad);
+ }
+
+ gst_structure_free (pt_map);
+
+ return ret;
}
static GstElement *
on_rtpbin_request_aux_receiver (GstElement * rtpbin, guint session_id,
GstWebRTCBin * webrtc)
{
- return NULL;
+ GstElement *ret = NULL;
+ GstElement *prev = NULL;
+ GstPad *sinkpad = NULL;
+ TransportStream *stream;
+ gint red_pt = 0;
+ gint rtx_pt = 0;
+
+ stream = _find_transport_for_session (webrtc, session_id);
+
+ if (stream) {
+ red_pt = _transport_stream_get_pt (stream, "RED");
+ rtx_pt = _transport_stream_get_pt (stream, "RTX");
+ }
+
+ if (red_pt || rtx_pt)
+ ret = gst_bin_new (NULL);
+
+ if (rtx_pt) {
+ GstCaps *rtx_caps = _transport_stream_get_caps_for_pt (stream, rtx_pt);
+ GstElement *rtx = gst_element_factory_make ("rtprtxreceive", NULL);
+ GstStructure *pt_map;
+ const GstStructure *s = gst_caps_get_structure (rtx_caps, 0);
+
+ gst_bin_add (GST_BIN (ret), rtx);
+
+ pt_map = gst_structure_new_empty ("application/x-rtp-pt-map");
+ gst_structure_set (pt_map, gst_structure_get_string (s, "apt"), G_TYPE_UINT,
+ rtx_pt, NULL);
+ g_object_set (rtx, "payload-type-map", pt_map, NULL);
+
+ sinkpad = gst_element_get_static_pad (rtx, "sink");
+
+ prev = rtx;
+ }
+
+ if (red_pt) {
+ GstElement *rtpreddec = gst_element_factory_make ("rtpreddec", NULL);
+
+ GST_DEBUG_OBJECT (webrtc, "Creating RED decoder for pt %d in session %u",
+ red_pt, session_id);
+
+ gst_bin_add (GST_BIN (ret), rtpreddec);
+
+ g_object_set (rtpreddec, "pt", red_pt, NULL);
+
+ if (prev)
+ gst_element_link (prev, rtpreddec);
+ else
+ sinkpad = gst_element_get_static_pad (rtpreddec, "sink");
+
+ prev = rtpreddec;
+ }
+
+ if (sinkpad) {
+ gchar *name = g_strdup_printf ("sink_%u", session_id);
+ GstPad *ghost = gst_ghost_pad_new (name, sinkpad);
+ g_free (name);
+ gst_object_unref (sinkpad);
+ gst_element_add_pad (ret, ghost);
+ }
+
+ if (prev) {
+ gchar *name = g_strdup_printf ("src_%u", session_id);
+ GstPad *srcpad = gst_element_get_static_pad (prev, "src");
+ GstPad *ghost = gst_ghost_pad_new (name, srcpad);
+ g_free (name);
+ gst_object_unref (srcpad);
+ gst_element_add_pad (ret, ghost);
+ }
+
+ return ret;
+}
+
+static GstElement *
+on_rtpbin_request_fec_decoder (GstElement * rtpbin, guint session_id,
+ GstWebRTCBin * webrtc)
+{
+ TransportStream *stream;
+ GstElement *ret = NULL;
+ gint pt = 0;
+ GObject *internal_storage;
+
+ stream = _find_transport_for_session (webrtc, session_id);
+
+ /* TODO: for now, we only support ulpfec, but once we support
+ * more algorithms, if the remote may use more than one algorithm,
+ * we will want to do the following:
+ *
+ * + Return a bin here, with the relevant FEC decoders plugged in
+ * and their payload type set to 0
+ * + Enable the decoders by setting the payload type only when
+ * we detect it (by connecting to ptdemux:new-payload-type for
+ * example)
+ */
+ if (stream)
+ pt = _transport_stream_get_pt (stream, "ULPFEC");
+
+ if (pt) {
+ GST_DEBUG_OBJECT (webrtc, "Creating ULPFEC decoder for pt %d in session %u",
+ pt, session_id);
+ ret = gst_element_factory_make ("rtpulpfecdec", NULL);
+ g_signal_emit_by_name (webrtc->rtpbin, "get-internal-storage", session_id,
+ &internal_storage);
+
+ g_object_set (ret, "pt", pt, "storage", internal_storage, NULL);
+ g_object_unref (internal_storage);
+ }
+
+ return ret;
+}
+
+static GstElement *
+on_rtpbin_request_fec_encoder (GstElement * rtpbin, guint session_id,
+ GstWebRTCBin * webrtc)
+{
+ GstElement *ret = NULL;
+ GstElement *prev = NULL;
+ TransportStream *stream;
+ guint ulpfec_pt = 0;
+ guint red_pt = 0;
+ GstPad *sinkpad = NULL;
+ GstWebRTCRTPTransceiver *trans;
+
+ stream = _find_transport_for_session (webrtc, session_id);
+ trans = _find_transceiver (webrtc, &session_id,
+ (FindTransceiverFunc) transceiver_match_for_mline);
+
+ if (stream) {
+ ulpfec_pt = _transport_stream_get_pt (stream, "ULPFEC");
+ red_pt = _transport_stream_get_pt (stream, "RED");
+ }
+
+ if (ulpfec_pt || red_pt)
+ ret = gst_bin_new (NULL);
+
+ if (ulpfec_pt) {
+ GstElement *fecenc = gst_element_factory_make ("rtpulpfecenc", NULL);
+ GstCaps *caps = _transport_stream_get_caps_for_pt (stream, ulpfec_pt);
+
+ GST_DEBUG_OBJECT (webrtc,
+ "Creating ULPFEC encoder for session %d with pt %d", session_id,
+ ulpfec_pt);
+
+ gst_bin_add (GST_BIN (ret), fecenc);
+ sinkpad = gst_element_get_static_pad (fecenc, "sink");
+ g_object_set (fecenc, "pt", ulpfec_pt, "percentage",
+ WEBRTC_TRANSCEIVER (trans)->fec_percentage, NULL);
+
+
+ if (caps && !gst_caps_is_empty (caps)) {
+ const GstStructure *s = gst_caps_get_structure (caps, 0);
+ const gchar *media = gst_structure_get_string (s, "media");
+
+ if (!g_strcmp0 (media, "video"))
+ g_object_set (fecenc, "multipacket", TRUE, NULL);
+ }
+
+ prev = fecenc;
+ }
+
+ if (red_pt) {
+ GstElement *redenc = gst_element_factory_make ("rtpredenc", NULL);
+
+ GST_DEBUG_OBJECT (webrtc, "Creating RED encoder for session %d with pt %d",
+ session_id, red_pt);
+
+ gst_bin_add (GST_BIN (ret), redenc);
+ if (prev)
+ gst_element_link (prev, redenc);
+ else
+ sinkpad = gst_element_get_static_pad (redenc, "sink");
+
+ g_object_set (redenc, "pt", red_pt, "allow-no-red-blocks", TRUE, NULL);
+
+ prev = redenc;
+ }
+
+ if (sinkpad) {
+ GstPad *ghost = gst_ghost_pad_new ("sink", sinkpad);
+ gst_object_unref (sinkpad);
+ gst_element_add_pad (ret, ghost);
+ }
+
+ if (prev) {
+ GstPad *srcpad = gst_element_get_static_pad (prev, "src");
+ GstPad *ghost = gst_ghost_pad_new ("src", srcpad);
+ gst_object_unref (srcpad);
+ gst_element_add_pad (ret, ghost);
+ }
+
+ return ret;
}
static void
@@ -2878,6 +3647,26 @@
on_rtpbin_new_jitterbuffer (GstElement * rtpbin, GstElement * jitterbuffer,
guint session_id, guint ssrc, GstWebRTCBin * webrtc)
{
+ GstWebRTCRTPTransceiver *trans;
+
+ trans = _find_transceiver (webrtc, &session_id,
+ (FindTransceiverFunc) transceiver_match_for_mline);
+
+ if (trans) {
+ /* We don't set do-retransmission on rtpbin as we want per-session control */
+ g_object_set (jitterbuffer, "do-retransmission",
+ WEBRTC_TRANSCEIVER (trans)->do_nack, NULL);
+ } else {
+ g_assert_not_reached ();
+ }
+}
+
+static void
+on_rtpbin_new_storage (GstElement * rtpbin, GstElement * storage,
+ guint session_id, GstWebRTCBin * webrtc)
+{
+ /* TODO: when exposing latency, set size-time based on that */
+ g_object_set (storage, "size-time", 250 * GST_MSECOND, NULL);
}
static GstElement *
@@ -2891,6 +3680,8 @@
/* mandated by WebRTC */
gst_util_set_object_arg (G_OBJECT (rtpbin), "rtp-profile", "savpf");
+ g_object_set (rtpbin, "do-lost", TRUE, NULL);
+
g_signal_connect (rtpbin, "pad-added", G_CALLBACK (on_rtpbin_pad_added),
webrtc);
g_signal_connect (rtpbin, "request-pt-map",
@@ -2899,6 +3690,12 @@
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, "new-storage",
+ G_CALLBACK (on_rtpbin_new_storage), webrtc);
+ g_signal_connect (rtpbin, "request-fec-decoder",
+ G_CALLBACK (on_rtpbin_request_fec_decoder), webrtc);
+ g_signal_connect (rtpbin, "request-fec-encoder",
+ G_CALLBACK (on_rtpbin_request_fec_encoder), webrtc);
g_signal_connect (rtpbin, "on-ssrc-active",
G_CALLBACK (on_rtpbin_ssrc_active), webrtc);
g_signal_connect (rtpbin, "new-jitterbuffer",
@@ -2974,6 +3771,14 @@
return ret;
}
+static GstPadProbeReturn
+pad_block (GstPad * pad, GstPadProbeInfo * info, gpointer unused)
+{
+ GST_LOG_OBJECT (pad, "blocking pad with data %" GST_PTR_FORMAT, info->data);
+
+ return GST_PAD_PROBE_OK;
+}
+
static GstPad *
gst_webrtc_bin_request_new_pad (GstElement * element, GstPadTemplate * templ,
const gchar * name, const GstCaps * caps)
@@ -3019,15 +3824,18 @@
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;
- }
+ if (!trans)
+ trans =
+ GST_WEBRTC_RTP_TRANSCEIVER (_create_webrtc_transceiver (webrtc,
+ GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV, serial));
pad->trans = gst_object_ref (trans);
- _connect_input_stream (webrtc, pad);
- /* TODO: update negotiation-needed */
+ pad->block_id = gst_pad_add_probe (GST_PAD (pad), GST_PAD_PROBE_TYPE_BLOCK |
+ GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BUFFER_LIST,
+ (GstPadProbeCallback) pad_block, NULL, NULL);
+ webrtc->priv->pending_sink_transceivers =
+ g_list_append (webrtc->priv->pending_sink_transceivers,
+ gst_object_ref (pad));
_add_pad (webrtc, pad);
}
@@ -3173,6 +3981,11 @@
(GDestroyNotify) _free_pending_pad);
webrtc->priv->pending_pads = NULL;
+ if (webrtc->priv->pending_sink_transceivers)
+ g_list_free_full (webrtc->priv->pending_sink_transceivers,
+ (GDestroyNotify) gst_object_unref);
+ webrtc->priv->pending_sink_transceivers = NULL;
+
if (webrtc->current_local_description)
gst_webrtc_session_description_free (webrtc->current_local_description);
webrtc->current_local_description = NULL;
@@ -3206,7 +4019,8 @@
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_with_gtype (element_class,
+ &sink_template, GST_TYPE_WEBRTC_BIN_PAD);
gst_element_class_add_static_pad_template (element_class, &src_template);
gst_element_class_set_metadata (element_class, "WebRTC Bin",
@@ -3437,6 +4251,16 @@
G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING);
/**
+ * GstWebRTCBin::on-new-transceiver:
+ * @object: the #GstWebRtcBin
+ * @candidate: the new #GstWebRTCRTPTransceiver
+ */
+ gst_webrtc_bin_signals[ON_NEW_TRANSCEIVER_SIGNAL] =
+ g_signal_new ("on-new-transceiver", G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
+ G_TYPE_NONE, 1, GST_TYPE_WEBRTC_RTP_TRANSCEIVER);
+
+ /**
* GstWebRTCBin::add-transceiver:
* @object: the #GstWebRtcBin
* @direction: the direction of the new transceiver
diff --git a/ext/webrtc/gstwebrtcbin.h b/ext/webrtc/gstwebrtcbin.h
index bbcc5f5..49603ec 100644
--- a/ext/webrtc/gstwebrtcbin.h
+++ b/ext/webrtc/gstwebrtcbin.h
@@ -57,6 +57,9 @@
guint mlineindex;
GstWebRTCRTPTransceiver *trans;
+ gulong block_id;
+
+ GstCaps *received_caps;
};
struct _GstWebRTCBinPadClass
@@ -125,6 +128,7 @@
gboolean async_pending;
GList *pending_pads;
+ GList *pending_sink_transceivers;
/* count of the number of media streams we've offered for uniqueness */
/* FIXME: overflow? */
diff --git a/ext/webrtc/webrtctransceiver.c b/ext/webrtc/webrtctransceiver.c
index 310956f..1735b1a 100644
--- a/ext/webrtc/webrtctransceiver.c
+++ b/ext/webrtc/webrtctransceiver.c
@@ -29,10 +29,17 @@
G_DEFINE_TYPE (WebRTCTransceiver, webrtc_transceiver,
GST_TYPE_WEBRTC_RTP_TRANSCEIVER);
+#define DEFAULT_FEC_TYPE GST_WEBRTC_FEC_TYPE_NONE
+#define DEFAULT_DO_NACK FALSE
+#define DEFAULT_FEC_PERCENTAGE 100
+
enum
{
PROP_0,
PROP_WEBRTC,
+ PROP_FEC_TYPE,
+ PROP_FEC_PERCENTAGE,
+ PROP_DO_NACK,
};
void
@@ -78,6 +85,15 @@
switch (prop_id) {
case PROP_WEBRTC:
break;
+ case PROP_FEC_TYPE:
+ trans->fec_type = g_value_get_enum (value);
+ break;
+ case PROP_DO_NACK:
+ trans->do_nack = g_value_get_boolean (value);
+ break;
+ case PROP_FEC_PERCENTAGE:
+ trans->fec_percentage = g_value_get_uint (value);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -93,6 +109,15 @@
GST_OBJECT_LOCK (trans);
switch (prop_id) {
+ case PROP_FEC_TYPE:
+ g_value_set_enum (value, trans->fec_type);
+ break;
+ case PROP_DO_NACK:
+ g_value_set_boolean (value, trans->do_nack);
+ break;
+ case PROP_FEC_PERCENTAGE:
+ g_value_set_uint (value, trans->fec_percentage);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -109,6 +134,10 @@
gst_object_unref (trans->stream);
trans->stream = NULL;
+ if (trans->local_rtx_ssrc_map)
+ gst_structure_free (trans->local_rtx_ssrc_map);
+ trans->local_rtx_ssrc_map = NULL;
+
G_OBJECT_CLASS (parent_class)->finalize (object);
}
@@ -129,6 +158,28 @@
"Parent webrtcbin",
GST_TYPE_WEBRTC_BIN,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_FEC_TYPE,
+ g_param_spec_enum ("fec-type", "FEC type",
+ "The type of Forward Error Correction to use",
+ GST_TYPE_WEBRTC_FEC_TYPE,
+ DEFAULT_FEC_TYPE,
+ G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_DO_NACK,
+ g_param_spec_boolean ("do-nack", "Do nack",
+ "Whether to send negative acknowledgements for feedback",
+ DEFAULT_DO_NACK,
+ G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_FEC_PERCENTAGE,
+ g_param_spec_uint ("fec-percentage", "FEC percentage",
+ "The amount of Forward Error Correction to apply",
+ 0, 100, DEFAULT_FEC_PERCENTAGE,
+ G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
static void
diff --git a/ext/webrtc/webrtctransceiver.h b/ext/webrtc/webrtctransceiver.h
index b90fea0..25bb24e 100644
--- a/ext/webrtc/webrtctransceiver.h
+++ b/ext/webrtc/webrtctransceiver.h
@@ -38,6 +38,12 @@
GstWebRTCRTPTransceiver parent;
TransportStream *stream;
+ GstStructure *local_rtx_ssrc_map;
+
+ /* Properties */
+ GstWebRTCFECType fec_type;
+ guint fec_percentage;
+ gboolean do_nack;
};
struct _WebRTCTransceiverClass
diff --git a/gst-libs/gst/webrtc/webrtc_fwd.h b/gst-libs/gst/webrtc/webrtc_fwd.h
index 6b8364d..be0a3c3 100644
--- a/gst-libs/gst/webrtc/webrtc_fwd.h
+++ b/gst-libs/gst/webrtc/webrtc_fwd.h
@@ -253,4 +253,15 @@
GST_WEBRTC_STATS_CERTIFICATE,
} GstWebRTCStatsType;
+/**
+ * GstWebRTCFECType:
+ * GST_WEBRTC_FEC_TYPE_NONE: none
+ * GST_WEBRTC_FEC_TYPE_ULP_RED: ulpfec + red
+ */
+typedef enum /*< underscore_name=gst_webrtc_fec_type >*/
+{
+ GST_WEBRTC_FEC_TYPE_NONE,
+ GST_WEBRTC_FEC_TYPE_ULP_RED,
+} GstWebRTCFECType;
+
#endif /* __GST_WEBRTC_FWD_H__ */
diff --git a/tests/check/elements/webrtcbin.c b/tests/check/elements/webrtcbin.c
index b7f42e0..756f380 100644
--- a/tests/check/elements/webrtcbin.c
+++ b/tests/check/elements/webrtcbin.c
@@ -822,6 +822,67 @@
GST_END_TEST;
static void
+on_sdp_media_payload_types (struct test_webrtc *t, GstElement * element,
+ GstWebRTCSessionDescription * desc, gpointer user_data)
+{
+ const GstSDPMedia *vmedia;
+ guint j;
+
+ fail_unless_equals_int (gst_sdp_message_medias_len (desc->sdp), 2);
+
+ vmedia = gst_sdp_message_get_media (desc->sdp, 1);
+
+ for (j = 0; j < gst_sdp_media_attributes_len (vmedia); j++) {
+ const GstSDPAttribute *attr = gst_sdp_media_get_attribute (vmedia, j);
+
+ if (!g_strcmp0 (attr->key, "rtpmap")) {
+ if (g_str_has_prefix (attr->value, "97")) {
+ fail_unless_equals_string (attr->value, "97 VP8/90000");
+ } else if (g_str_has_prefix (attr->value, "96")) {
+ fail_unless_equals_string (attr->value, "96 red/90000");
+ } else if (g_str_has_prefix (attr->value, "98")) {
+ fail_unless_equals_string (attr->value, "98 ulpfec/90000");
+ } else if (g_str_has_prefix (attr->value, "99")) {
+ fail_unless_equals_string (attr->value, "99 rtx/90000");
+ } else if (g_str_has_prefix (attr->value, "100")) {
+ fail_unless_equals_string (attr->value, "100 rtx/90000");
+ }
+ }
+ }
+}
+
+/* In this test we verify that webrtcbin will pick available payload
+ * types when it needs to, in that example for RTX and FEC */
+GST_START_TEST (test_payload_types)
+{
+ struct test_webrtc *t = create_audio_video_test ();
+ struct validate_sdp offer = { on_sdp_media_payload_types, NULL };
+ GstWebRTCRTPTransceiver *trans;
+ GArray *transceivers;
+
+ t->offer_data = &offer;
+ t->on_offer_created = validate_sdp;
+ t->on_ice_candidate = NULL;
+ /* We don't really care about the answer here */
+ t->on_answer_created = NULL;
+
+ g_signal_emit_by_name (t->webrtc1, "get-transceivers", &transceivers);
+ fail_unless_equals_int (transceivers->len, 2);
+ trans = g_array_index (transceivers, GstWebRTCRTPTransceiver *, 1);
+ g_object_set (trans, "fec-type", GST_WEBRTC_FEC_TYPE_ULP_RED, "do-nack", TRUE,
+ NULL);
+ g_array_unref (transceivers);
+
+ test_webrtc_create_offer (t, t->webrtc1);
+
+ test_webrtc_wait_for_answer_error_eos (t);
+ fail_unless_equals_int (STATE_ANSWER_CREATED, t->state);
+ test_webrtc_free (t);
+}
+
+GST_END_TEST;
+
+static void
on_sdp_media_setup (struct test_webrtc *t, GstElement * element,
GstWebRTCSessionDescription * desc, gpointer user_data)
{
@@ -1367,6 +1428,7 @@
tcase_add_test (tc, test_get_transceivers);
tcase_add_test (tc, test_add_recvonly_transceiver);
tcase_add_test (tc, test_recvonly_sendonly);
+ tcase_add_test (tc, test_payload_types);
}
if (nicesrc)
diff --git a/tests/examples/webrtc/Makefile.am b/tests/examples/webrtc/Makefile.am
index 520942d..6323263 100644
--- a/tests/examples/webrtc/Makefile.am
+++ b/tests/examples/webrtc/Makefile.am
@@ -1,5 +1,5 @@
-noinst_PROGRAMS = webrtc webrtcbidirectional webrtcswap
+noinst_PROGRAMS = webrtc webrtcbidirectional webrtcswap webrtctransceiver
webrtc_SOURCES = webrtc.c
webrtc_CFLAGS=\
@@ -39,3 +39,16 @@
$(GST_LIBS) \
$(GST_SDP_LIBS) \
$(top_builddir)/gst-libs/gst/webrtc/libgstwebrtc-@GST_API_VERSION@.la
+
+webrtctransceiver_SOURCES = webrtctransceiver.c
+webrtctransceiver_CFLAGS=\
+ -I$(top_srcdir)/gst-libs \
+ -I$(top_builddir)/gst-libs \
+ $(GST_PLUGINS_BASE_CFLAGS) \
+ $(GST_CFLAGS) \
+ $(GST_SDP_CFLAGS)
+webrtctransceiver_LDADD=\
+ $(GST_PLUGINS_BASE_LIBS) \
+ $(GST_LIBS) \
+ $(GST_SDP_LIBS) \
+ $(top_builddir)/gst-libs/gst/webrtc/libgstwebrtc-@GST_API_VERSION@.la
diff --git a/tests/examples/webrtc/meson.build b/tests/examples/webrtc/meson.build
index 7c2aab7..90c15e5 100644
--- a/tests/examples/webrtc/meson.build
+++ b/tests/examples/webrtc/meson.build
@@ -1,4 +1,4 @@
-examples = ['webrtc', 'webrtcbidirectional', 'webrtcswap']
+examples = ['webrtc', 'webrtcbidirectional', 'webrtcswap', 'webrtctransceiver']
foreach example : examples
exe_name = example
diff --git a/tests/examples/webrtc/webrtctransceiver.c b/tests/examples/webrtc/webrtctransceiver.c
new file mode 100644
index 0000000..df1a18e
--- /dev/null
+++ b/tests/examples/webrtc/webrtctransceiver.c
@@ -0,0 +1,218 @@
+#include <gst/gst.h>
+#include <gst/sdp/sdp.h>
+#include <gst/webrtc/webrtc.h>
+
+#include <string.h>
+
+static GMainLoop *loop;
+static GstElement *pipe1, *webrtc1, *webrtc2;
+static GstBus *bus1;
+
+static gboolean
+_bus_watch (GstBus * bus, GstMessage * msg, GstElement * pipe)
+{
+ switch (GST_MESSAGE_TYPE (msg)) {
+ case GST_MESSAGE_STATE_CHANGED:
+ if (GST_ELEMENT (msg->src) == pipe) {
+ GstState old, new, pending;
+
+ gst_message_parse_state_changed (msg, &old, &new, &pending);
+
+ {
+ gchar *dump_name = g_strconcat ("state_changed-",
+ gst_element_state_get_name (old), "_",
+ gst_element_state_get_name (new), NULL);
+ GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (msg->src),
+ GST_DEBUG_GRAPH_SHOW_ALL, dump_name);
+ g_free (dump_name);
+ }
+ }
+ break;
+ case GST_MESSAGE_ERROR:{
+ GError *err = NULL;
+ gchar *dbg_info = NULL;
+
+ GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipe),
+ GST_DEBUG_GRAPH_SHOW_ALL, "error");
+
+ gst_message_parse_error (msg, &err, &dbg_info);
+ g_printerr ("ERROR from element %s: %s\n",
+ GST_OBJECT_NAME (msg->src), err->message);
+ g_printerr ("Debugging info: %s\n", (dbg_info) ? dbg_info : "none");
+ g_error_free (err);
+ g_free (dbg_info);
+ g_main_loop_quit (loop);
+ break;
+ }
+ case GST_MESSAGE_EOS:{
+ GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipe),
+ GST_DEBUG_GRAPH_SHOW_ALL, "eos");
+ g_print ("EOS received\n");
+ g_main_loop_quit (loop);
+ break;
+ }
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+static void
+_webrtc_pad_added (GstElement * webrtc, GstPad * new_pad, GstElement * pipe)
+{
+ GstElement *out;
+ GstPad *sink;
+
+ if (GST_PAD_DIRECTION (new_pad) != GST_PAD_SRC)
+ return;
+
+ out = gst_parse_bin_from_description ("rtpvp8depay ! vp8dec ! "
+ "videoconvert ! queue ! xvimagesink", TRUE, NULL);
+ gst_bin_add (GST_BIN (pipe), out);
+ gst_element_sync_state_with_parent (out);
+
+ sink = out->sinkpads->data;
+
+ gst_pad_link (new_pad, sink);
+}
+
+static void
+_on_answer_received (GstPromise * promise, gpointer user_data)
+{
+ GstWebRTCSessionDescription *answer = NULL;
+ const GstStructure *reply;
+ gchar *desc;
+
+ g_assert (gst_promise_wait (promise) == GST_PROMISE_RESULT_REPLIED);
+ reply = gst_promise_get_reply (promise);
+ gst_structure_get (reply, "answer",
+ GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &answer, NULL);
+ gst_promise_unref (promise);
+ desc = gst_sdp_message_as_text (answer->sdp);
+ g_print ("Created answer:\n%s\n", desc);
+ g_free (desc);
+
+ g_signal_emit_by_name (webrtc1, "set-remote-description", answer, NULL);
+ g_signal_emit_by_name (webrtc2, "set-local-description", answer, NULL);
+
+ gst_webrtc_session_description_free (answer);
+}
+
+static void
+_on_offer_received (GstPromise * promise, gpointer user_data)
+{
+ GstWebRTCSessionDescription *offer = NULL;
+ const GstStructure *reply;
+ gchar *desc;
+
+ g_assert (gst_promise_wait (promise) == GST_PROMISE_RESULT_REPLIED);
+ reply = gst_promise_get_reply (promise);
+ gst_structure_get (reply, "offer",
+ GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL);
+ gst_promise_unref (promise);
+ desc = gst_sdp_message_as_text (offer->sdp);
+ g_print ("Created offer:\n%s\n", desc);
+ g_free (desc);
+
+ g_signal_emit_by_name (webrtc1, "set-local-description", offer, NULL);
+ g_signal_emit_by_name (webrtc2, "set-remote-description", offer, NULL);
+
+ promise = gst_promise_new_with_change_func (_on_answer_received, user_data,
+ NULL);
+ g_signal_emit_by_name (webrtc2, "create-answer", NULL, promise);
+
+ gst_webrtc_session_description_free (offer);
+}
+
+static void
+_on_negotiation_needed (GstElement * element, gpointer user_data)
+{
+ GstPromise *promise;
+
+ promise = gst_promise_new_with_change_func (_on_offer_received, user_data,
+ NULL);
+ g_signal_emit_by_name (webrtc1, "create-offer", NULL, promise);
+}
+
+static void
+_on_ice_candidate (GstElement * webrtc, guint mlineindex, gchar * candidate,
+ GstElement * other)
+{
+ g_signal_emit_by_name (other, "add-ice-candidate", mlineindex, candidate);
+}
+
+static void
+_on_new_transceiver (GstElement * webrtc, GstWebRTCRTPTransceiver * trans)
+{
+ /* If we expected more than one transceiver, we would take a look at
+ * trans->mline, and compare it with webrtcbin's local description */
+ g_object_set (trans, "fec-type", GST_WEBRTC_FEC_TYPE_ULP_RED, NULL);
+}
+
+static void
+add_fec_to_offer (GstElement * webrtc)
+{
+ GstWebRTCRTPTransceiver *trans;
+ GArray *transceivers;
+
+ /* A transceiver has already been created when a sink pad was
+ * requested on the sending webrtcbin */
+
+ g_signal_emit_by_name (webrtc, "get-transceivers", &transceivers);
+
+ trans = g_array_index (transceivers, GstWebRTCRTPTransceiver *, 0);
+
+ g_object_set (trans, "fec-type", GST_WEBRTC_FEC_TYPE_ULP_RED,
+ "fec-percentage", 100, NULL);
+
+ g_array_unref (transceivers);
+}
+
+int
+main (int argc, char *argv[])
+{
+ gst_init (&argc, &argv);
+
+ loop = g_main_loop_new (NULL, FALSE);
+ pipe1 =
+ gst_parse_launch
+ ("videotestsrc pattern=ball ! video/x-raw ! queue ! vp8enc ! rtpvp8pay ! queue ! "
+ "application/x-rtp,media=video,payload=96,encoding-name=VP8 ! "
+ "webrtcbin name=send webrtcbin name=recv", NULL);
+ bus1 = gst_pipeline_get_bus (GST_PIPELINE (pipe1));
+ gst_bus_add_watch (bus1, (GstBusFunc) _bus_watch, pipe1);
+
+ webrtc1 = gst_bin_get_by_name (GST_BIN (pipe1), "send");
+ g_signal_connect (webrtc1, "on-negotiation-needed",
+ G_CALLBACK (_on_negotiation_needed), NULL);
+ add_fec_to_offer (webrtc1);
+
+ webrtc2 = gst_bin_get_by_name (GST_BIN (pipe1), "recv");
+ g_signal_connect (webrtc2, "pad-added", G_CALLBACK (_webrtc_pad_added),
+ pipe1);
+ g_signal_connect (webrtc1, "on-ice-candidate",
+ G_CALLBACK (_on_ice_candidate), webrtc2);
+ g_signal_connect (webrtc2, "on-ice-candidate",
+ G_CALLBACK (_on_ice_candidate), webrtc1);
+ g_signal_connect (webrtc2, "on-new-transceiver",
+ G_CALLBACK (_on_new_transceiver), NULL);
+
+ g_print ("Starting pipeline\n");
+ gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_PLAYING);
+
+ g_main_loop_run (loop);
+
+ gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_NULL);
+ g_print ("Pipeline stopped\n");
+
+ gst_object_unref (webrtc1);
+ gst_object_unref (webrtc2);
+ gst_bus_remove_watch (bus1);
+ gst_object_unref (bus1);
+ gst_object_unref (pipe1);
+
+ gst_deinit ();
+
+ return 0;
+}