webrtc: support renegotiating adding/removing RTX

We need to always add the RTX/RED/ULPFEC elements as rtpbin will only
call us once to request aux/fec senders/receivers.

We also need to regenerate the media section of the SDP instead of
blindly copying from the previous offer.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/1762>
This commit is contained in:
Matthew Waters 2021-08-03 12:14:49 +10:00
parent 8a598deef2
commit b7d0ddd1a4
6 changed files with 484 additions and 350 deletions

View file

@ -2943,10 +2943,10 @@ _copy_field (GQuark field_id, const GValue * value, GstStructure * s)
/* 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, guint media_idx,
GString * bundled_mids, guint bundle_idx, gchar * bundle_ufrag,
gchar * bundle_pwd, GArray * reserved_pts, GHashTable * all_mids,
GError ** error)
const GstSDPMedia * last_media, GstWebRTCRTPTransceiver * trans,
guint media_idx, GString * bundled_mids, guint bundle_idx,
gchar * bundle_ufrag, gchar * bundle_pwd, GArray * reserved_pts,
GHashTable * all_mids, GError ** error)
{
/* TODO:
* rtp header extensions
@ -2965,8 +2965,7 @@ sdp_media_from_transceiver (GstWebRTCBin * webrtc, GstSDPMedia * media,
GstStructure *extmap;
int i;
if (trans->direction == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE
|| trans->direction == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE)
if (trans->direction == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE)
return FALSE;
g_assert (trans->mline == -1 || trans->mline == media_idx);
@ -2974,8 +2973,49 @@ sdp_media_from_transceiver (GstWebRTCBin * webrtc, GstSDPMedia * media,
bundle_only = bundled_mids && bundle_idx != media_idx
&& webrtc->bundle_policy == GST_WEBRTC_BUNDLE_POLICY_MAX_BUNDLE;
/* mandated by JSEP */
gst_sdp_media_add_attribute (media, "setup", "actpass");
caps = _find_codec_preferences (webrtc, trans, media_idx, error);
caps = _add_supported_attributes_to_caps (webrtc, WEBRTC_TRANSCEIVER (trans),
caps);
if (!caps || gst_caps_is_empty (caps) || gst_caps_is_any (caps)) {
gst_clear_caps (&caps);
if (last_media) {
guint i, n;
n = gst_sdp_media_formats_len (last_media);
if (n > 0) {
caps = gst_caps_new_empty ();
for (i = 0; i < n; i++) {
guint fmt = atoi (gst_sdp_media_get_format (last_media, i));
GstCaps *tmp = gst_sdp_media_get_caps_from_media (last_media, fmt);
GstStructure *s = gst_caps_get_structure (tmp, 0);
gst_structure_set_name (s, "application/x-rtp");
gst_caps_append_structure (caps, gst_structure_copy (s));
gst_clear_caps (&tmp);
}
GST_DEBUG_OBJECT (webrtc, "using previously negotiated caps for "
"transceiver %" GST_PTR_FORMAT " %" GST_PTR_FORMAT, trans, caps);
}
}
if (!caps) {
GST_WARNING_OBJECT (webrtc, "no caps available for transceiver %"
GST_PTR_FORMAT ", skipping", trans);
return FALSE;
}
}
if (last_media) {
const char *setup = gst_sdp_media_get_attribute_val (last_media, "setup");
if (setup)
gst_sdp_media_add_attribute (media, "setup", setup);
else
return FALSE;
} else {
/* mandated by JSEP */
gst_sdp_media_add_attribute (media, "setup", "actpass");
}
/* FIXME: deal with ICE restarts */
if (last_offer && trans->mline != -1 && trans->mid) {
@ -3022,15 +3062,6 @@ sdp_media_from_transceiver (GstWebRTCBin * webrtc, GstSDPMedia * media,
gst_sdp_media_add_attribute (media, direction, "");
g_free (direction);
caps = _find_codec_preferences (webrtc, trans, media_idx, error);
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;
}
caps = gst_caps_make_writable (caps);
/* When an extmap is defined twice for the same ID, firefox complains and
@ -3385,9 +3416,11 @@ _create_offer_task (GstWebRTCBin * webrtc, const GstStructure * options,
trans = g_ptr_array_index (webrtc->priv->transceivers, j);
if (trans->mid && g_strcmp0 (trans->mid, last_mid) == 0) {
GstSDPMedia *media;
const gchar *mid;
WebRTCTransceiver *wtrans = WEBRTC_TRANSCEIVER (trans);
const char *mid;
GstSDPMedia media;
memset (&media, 0, sizeof (media));
g_assert (!g_list_find (seen_transceivers, trans));
@ -3405,30 +3438,35 @@ _create_offer_task (GstWebRTCBin * webrtc, const GstStructure * options,
GST_PTR_FORMAT " with mid %s into media index %u", trans,
trans->mid, media_idx);
/* FIXME: deal with format changes */
gst_sdp_media_copy (last_media, &media);
_media_replace_direction (media, trans->direction);
mid = gst_sdp_media_get_attribute_val (media, "mid");
g_assert (mid);
if (g_hash_table_contains (all_mids, mid)) {
gst_sdp_media_free (media);
g_set_error (error, GST_WEBRTC_ERROR,
GST_WEBRTC_ERROR_INTERNAL_FAILURE,
"Duplicate mid %s when creating offer", mid);
goto cancel_offer;
if (webrtc->bundle_policy == GST_WEBRTC_BUNDLE_POLICY_NONE) {
reserved_pts = g_array_new (FALSE, FALSE, sizeof (guint));
}
g_hash_table_insert (all_mids, g_strdup (mid), NULL);
gst_sdp_media_init (&media);
if (!sdp_media_from_transceiver (webrtc, &media, last_media, trans,
media_idx, bundled_mids, 0, bundle_ufrag, bundle_pwd,
reserved_pts, all_mids, error)) {
gst_sdp_media_uninit (&media);
if (!*error)
g_set_error_literal (error, GST_WEBRTC_ERROR,
GST_WEBRTC_ERROR_INTERNAL_FAILURE,
"Could not reuse transceiver");
}
if (bundled_mids)
g_string_append_printf (bundled_mids, " %s", mid);
if (webrtc->bundle_policy == GST_WEBRTC_BUNDLE_POLICY_NONE) {
g_array_free (reserved_pts, TRUE);
reserved_pts = NULL;
}
if (*error)
goto cancel_offer;
gst_sdp_message_add_media (ret, media);
mid = gst_sdp_media_get_attribute_val (&media, "mid");
g_assert (mid && g_strcmp0 (last_mid, mid) == 0);
gst_sdp_message_add_media (ret, &media);
media_idx++;
gst_sdp_media_free (media);
gst_sdp_media_uninit (&media);
seen_transceivers = g_list_prepend (seen_transceivers, trans);
break;
}
@ -3559,7 +3597,7 @@ _create_offer_task (GstWebRTCBin * webrtc, const GstStructure * options,
GST_LOG_OBJECT (webrtc, "adding transceiver %" GST_PTR_FORMAT " at media "
"index %u", trans, media_idx);
if (sdp_media_from_transceiver (webrtc, &media, trans, media_idx,
if (sdp_media_from_transceiver (webrtc, &media, NULL, trans, media_idx,
bundled_mids, 0, bundle_ufrag, bundle_pwd, reserved_pts, all_mids,
error)) {
/* as per JSEP, a=rtcp-mux-only is only added for new streams */
@ -3728,6 +3766,7 @@ _media_add_rtx (GstSDPMedia * media, WebRTCTransceiver * trans,
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);
}
}
}
@ -4379,12 +4418,10 @@ out:
static GstElement *
_build_fec_encoder (GstWebRTCBin * webrtc, WebRTCTransceiver * trans)
{
GstElement *ret = NULL;
GstElement *prev = NULL;
guint ulpfec_pt = 0;
guint red_pt = 0;
GstPad *sinkpad = NULL;
GstWebRTCRTPTransceiver *rtp_trans = GST_WEBRTC_RTP_TRANSCEIVER (trans);
guint ulpfec_pt = 0, red_pt = 0;
GstPad *sinkpad, *srcpad, *ghost;
GstElement *ret;
if (trans->stream) {
ulpfec_pt =
@ -4392,69 +4429,215 @@ _build_fec_encoder (GstWebRTCBin * webrtc, WebRTCTransceiver * trans)
red_pt = transport_stream_get_pt (trans->stream, "RED", rtp_trans->mline);
}
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 (trans->stream, ulpfec_pt);
GST_DEBUG_OBJECT (webrtc,
"Creating ULPFEC encoder for mline %u with pt %d", rtp_trans->mline,
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",
trans->fec_percentage, NULL);
g_object_bind_property (rtp_trans, "fec-percentage", fecenc, "percentage",
G_BINDING_BIDIRECTIONAL);
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 (trans->ulpfecenc || trans->redenc) {
g_critical ("webrtcbin: duplicate call to create a fec encoder or "
"red encoder!");
return NULL;
}
if (red_pt) {
GstElement *redenc = gst_element_factory_make ("rtpredenc", NULL);
GST_DEBUG_OBJECT (webrtc,
"Creating ULPFEC encoder for mline %u with pt %d", rtp_trans->mline,
ulpfec_pt);
GST_DEBUG_OBJECT (webrtc, "Creating RED encoder for mline %u with pt %d",
rtp_trans->mline, red_pt);
ret = gst_bin_new (NULL);
gst_bin_add (GST_BIN (ret), redenc);
if (prev)
gst_element_link (prev, redenc);
else
sinkpad = gst_element_get_static_pad (redenc, "sink");
trans->ulpfecenc = gst_element_factory_make ("rtpulpfecenc", NULL);
gst_object_ref_sink (trans->ulpfecenc);
if (!gst_bin_add (GST_BIN (ret), trans->ulpfecenc))
g_warn_if_reached ();
sinkpad = gst_element_get_static_pad (trans->ulpfecenc, "sink");
g_object_set (redenc, "pt", red_pt, "allow-no-red-blocks", TRUE, NULL);
g_object_bind_property (rtp_trans, "fec-percentage", trans->ulpfecenc,
"percentage", G_BINDING_BIDIRECTIONAL);
prev = redenc;
}
trans->redenc = gst_element_factory_make ("rtpredenc", NULL);
gst_object_ref_sink (trans->redenc);
if (sinkpad) {
GstPad *ghost = gst_ghost_pad_new ("sink", sinkpad);
gst_object_unref (sinkpad);
gst_element_add_pad (ret, ghost);
}
GST_DEBUG_OBJECT (webrtc, "Creating RED encoder for mline %u with pt %d",
rtp_trans->mline, red_pt);
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);
}
gst_bin_add (GST_BIN (ret), trans->redenc);
gst_element_link (trans->ulpfecenc, trans->redenc);
ghost = gst_ghost_pad_new ("sink", sinkpad);
gst_clear_object (&sinkpad);
gst_element_add_pad (ret, ghost);
ghost = NULL;
srcpad = gst_element_get_static_pad (trans->redenc, "src");
ghost = gst_ghost_pad_new ("src", srcpad);
gst_clear_object (&srcpad);
gst_element_add_pad (ret, ghost);
ghost = NULL;
return ret;
}
static gboolean
_merge_structure (GQuark field_id, const GValue * value, gpointer user_data)
{
GstStructure *s = user_data;
gst_structure_id_set_value (s, field_id, value);
return TRUE;
}
#define GST_WEBRTC_PAYLOAD_TYPE "gst.webrtcbin.payload.type"
static void
try_match_transceiver_with_fec_decoder (GstWebRTCBin * webrtc,
WebRTCTransceiver * trans)
{
GList *l;
for (l = trans->stream->fecdecs; l; l = l->next) {
GstElement *fecdec = GST_ELEMENT (l->data);
gboolean found_transceiver = FALSE;
int original_pt;
guint i;
original_pt =
GPOINTER_TO_INT (g_object_get_data (G_OBJECT (fecdec),
GST_WEBRTC_PAYLOAD_TYPE));
if (original_pt <= 0) {
GST_WARNING_OBJECT (trans, "failed to match fec decoder with "
"transceiver, fec decoder %" GST_PTR_FORMAT " does not contain a "
"valid payload type", fecdec);
continue;
}
for (i = 0; i < trans->stream->ptmap->len; i++) {
PtMapItem *item = &g_array_index (trans->stream->ptmap, PtMapItem, i);
/* FIXME: this only works for a 1-1 original_pt->fec_pt mapping */
if (original_pt == item->pt && item->media_idx != -1
&& item->media_idx == trans->parent.mline) {
if (trans->ulpfecdec) {
GST_FIXME_OBJECT (trans, "cannot");
gst_clear_object (&trans->ulpfecdec);
}
trans->ulpfecdec = gst_object_ref (fecdec);
found_transceiver = TRUE;
break;
}
}
if (!found_transceiver) {
GST_WARNING_OBJECT (trans, "failed to match fec decoder with "
"transceiver");
}
}
}
static void
_set_internal_rtpbin_element_props_from_stream (GstWebRTCBin * webrtc,
TransportStream * stream)
{
GstStructure *merged_local_rtx_ssrc_map;
GstStructure *pt_map = gst_structure_new_empty ("application/x-rtp-pt-map");
GValue red_pt_array = { 0, };
gint *rtx_pt;
gsize rtx_count;
gsize i;
gst_value_array_init (&red_pt_array, 0);
rtx_pt = transport_stream_get_all_pt (stream, "RTX", &rtx_count);
GST_DEBUG_OBJECT (stream, "have %" G_GSIZE_FORMAT " rtx payloads", rtx_count);
for (i = 0; i < rtx_count; i++) {
GstCaps *rtx_caps = transport_stream_get_caps_for_pt (stream, rtx_pt[i]);
const GstStructure *s = gst_caps_get_structure (rtx_caps, 0);
const gchar *apt = gst_structure_get_string (s, "apt");
GST_LOG_OBJECT (stream, "setting rtx mapping: %s -> %u", apt, rtx_pt[i]);
gst_structure_set (pt_map, apt, G_TYPE_UINT, rtx_pt[i], NULL);
}
GST_DEBUG_OBJECT (stream, "setting payload map on %" GST_PTR_FORMAT " : %"
GST_PTR_FORMAT " and %" GST_PTR_FORMAT, stream->rtxreceive,
stream->rtxsend, pt_map);
if (stream->rtxreceive)
g_object_set (stream->rtxreceive, "payload-type-map", pt_map, NULL);
if (stream->rtxsend)
g_object_set (stream->rtxsend, "payload-type-map", pt_map, NULL);
gst_structure_free (pt_map);
g_clear_pointer (&rtx_pt, g_free);
merged_local_rtx_ssrc_map =
gst_structure_new_empty ("application/x-rtp-ssrc-map");
for (i = 0; i < webrtc->priv->transceivers->len; i++) {
GstWebRTCRTPTransceiver *rtp_trans =
g_ptr_array_index (webrtc->priv->transceivers, i);
WebRTCTransceiver *trans = WEBRTC_TRANSCEIVER (rtp_trans);
if (trans->stream == stream) {
gint ulpfec_pt, red_pt = 0;
ulpfec_pt = transport_stream_get_pt (stream, "ULPFEC", rtp_trans->mline);
if (ulpfec_pt <= 0)
ulpfec_pt = 0;
red_pt = transport_stream_get_pt (stream, "RED", rtp_trans->mline);
if (red_pt <= 0) {
red_pt = -1;
} else {
GValue ptval = { 0, };
g_value_init (&ptval, G_TYPE_INT);
g_value_set_int (&ptval, red_pt);
gst_value_array_append_value (&red_pt_array, &ptval);
g_value_unset (&ptval);
}
GST_DEBUG_OBJECT (webrtc, "stream %" GST_PTR_FORMAT " transceiever %"
GST_PTR_FORMAT " has FEC payload %d and RED payload %d", stream,
trans, ulpfec_pt, red_pt);
if (trans->ulpfecenc) {
g_object_set (trans->ulpfecenc, "pt", ulpfec_pt, "multipacket",
rtp_trans->kind == GST_WEBRTC_KIND_VIDEO, "percentage",
trans->fec_percentage, NULL);
}
try_match_transceiver_with_fec_decoder (webrtc, trans);
if (trans->ulpfecdec) {
g_object_set (trans->ulpfecdec, "pt", ulpfec_pt, NULL);
}
if (trans->redenc) {
gboolean always_produce = TRUE;
if (red_pt == -1) {
/* passthrough settings */
red_pt = 0;
always_produce = FALSE;
}
g_object_set (trans->redenc, "pt", red_pt, "allow-no-red-blocks",
always_produce, NULL);
}
if (trans->local_rtx_ssrc_map) {
gst_structure_foreach (trans->local_rtx_ssrc_map,
_merge_structure, merged_local_rtx_ssrc_map);
}
}
}
if (stream->rtxsend)
g_object_set (stream->rtxsend, "ssrc-map", merged_local_rtx_ssrc_map, NULL);
gst_clear_structure (&merged_local_rtx_ssrc_map);
if (stream->reddec) {
g_object_set_property (G_OBJECT (stream->reddec), "payloads",
&red_pt_array);
}
g_value_unset (&red_pt_array);
}
static GstPad *
_connect_input_stream (GstWebRTCBin * webrtc, GstWebRTCBinPad * pad)
@ -4511,21 +4694,26 @@ _connect_input_stream (GstWebRTCBin * webrtc, GstWebRTCBinPad * pad)
gst_element_sync_state_with_parent (clocksync);
srcpad = gst_element_get_static_pad (clocksync, "src");
sinkpad = gst_element_get_static_pad (clocksync, "sink");
if ((fec_encoder = _build_fec_encoder (webrtc, trans))) {
GstPad *fec_sink;
gst_bin_add (GST_BIN (webrtc), fec_encoder);
gst_element_sync_state_with_parent (fec_encoder);
fec_sink = gst_element_get_static_pad (fec_encoder, "sink");
gst_pad_link (srcpad, fec_sink);
gst_object_unref (srcpad);
gst_object_unref (fec_sink);
srcpad = gst_element_get_static_pad (fec_encoder, "src");
fec_encoder = _build_fec_encoder (webrtc, trans);
if (!fec_encoder) {
g_warn_if_reached ();
return NULL;
}
_set_internal_rtpbin_element_props_from_stream (webrtc, trans->stream);
gst_bin_add (GST_BIN (webrtc), fec_encoder);
gst_element_sync_state_with_parent (fec_encoder);
sinkpad = gst_element_get_static_pad (fec_encoder, "sink");
if (gst_pad_link (srcpad, sinkpad) != GST_PAD_LINK_OK)
g_warn_if_reached ();
gst_clear_object (&srcpad);
gst_clear_object (&sinkpad);
sinkpad = gst_element_get_static_pad (clocksync, "sink");
srcpad = gst_element_get_static_pad (fec_encoder, "src");
if (!webrtc->rtpfunnel) {
rtp_templ =
_find_pad_template (webrtc->rtpbin, GST_PAD_SINK, GST_PAD_REQUEST,
@ -4539,8 +4727,6 @@ _connect_input_stream (GstWebRTCBin * webrtc, GstWebRTCBinPad * pad)
gst_pad_link (srcpad, rtp_sink);
gst_object_unref (rtp_sink);
gst_ghost_pad_set_target (GST_GHOST_PAD (pad), sinkpad);
pad_name = g_strdup_printf ("send_rtp_src_%u", pad->trans->mline);
if (!gst_element_link_pads (GST_ELEMENT (webrtc->rtpbin), pad_name,
GST_ELEMENT (trans->stream->send_bin), "rtp_sink"))
@ -4552,14 +4738,15 @@ _connect_input_stream (GstWebRTCBin * webrtc, GstWebRTCBinPad * pad)
gst_element_request_pad_simple (webrtc->rtpfunnel, pad_name);
gst_pad_link (srcpad, funnel_sinkpad);
gst_ghost_pad_set_target (GST_GHOST_PAD (pad), sinkpad);
g_free (pad_name);
gst_object_unref (funnel_sinkpad);
}
gst_object_unref (srcpad);
gst_object_unref (sinkpad);
gst_ghost_pad_set_target (GST_GHOST_PAD (pad), sinkpad);
gst_clear_object (&srcpad);
gst_clear_object (&sinkpad);
gst_element_sync_state_with_parent (GST_ELEMENT (trans->stream->send_bin));
@ -4715,41 +4902,6 @@ _filter_sdp_fields (GQuark field_id, const GValue * value,
return TRUE;
}
static void
_set_rtx_ptmap_from_stream (GstWebRTCBin * webrtc, TransportStream * stream)
{
gint *rtx_pt;
gsize rtx_count;
rtx_pt = transport_stream_get_all_pt (stream, "RTX", &rtx_count);
GST_LOG_OBJECT (stream, "have %" G_GSIZE_FORMAT " rtx payloads", rtx_count);
if (rtx_pt) {
GstStructure *pt_map = gst_structure_new_empty ("application/x-rtp-pt-map");
gsize i;
for (i = 0; i < rtx_count; i++) {
GstCaps *rtx_caps = transport_stream_get_caps_for_pt (stream, rtx_pt[i]);
const GstStructure *s = gst_caps_get_structure (rtx_caps, 0);
const gchar *apt = gst_structure_get_string (s, "apt");
GST_LOG_OBJECT (stream, "setting rtx mapping: %s -> %u", apt, rtx_pt[i]);
gst_structure_set (pt_map, apt, G_TYPE_UINT, rtx_pt[i], NULL);
}
GST_DEBUG_OBJECT (stream, "setting payload map on %" GST_PTR_FORMAT " : %"
GST_PTR_FORMAT " and %" GST_PTR_FORMAT, stream->rtxreceive,
stream->rtxsend, pt_map);
if (stream->rtxreceive)
g_object_set (stream->rtxreceive, "payload-type-map", pt_map, NULL);
if (stream->rtxsend)
g_object_set (stream->rtxsend, "payload-type-map", pt_map, NULL);
gst_structure_free (pt_map);
g_free (rtx_pt);
}
}
static void
_update_transport_ptmap_from_media (GstWebRTCBin * webrtc,
TransportStream * stream, const GstSDPMessage * sdp, guint media_idx)
@ -5026,7 +5178,7 @@ _update_transceiver_from_sdp_media (GstWebRTCBin * webrtc,
if (!bundled || bundle_idx == media_idx) {
if (stream->rtxsend || stream->rtxreceive) {
_set_rtx_ptmap_from_stream (webrtc, stream);
_set_internal_rtpbin_element_props_from_stream (webrtc, stream);
}
g_object_set (stream, "dtls-client",
@ -6467,82 +6619,56 @@ unknown_session:
}
}
static gboolean
_merge_structure (GQuark field_id, const GValue * value, gpointer user_data)
{
GstStructure *s = user_data;
gst_structure_id_set_value (s, field_id, value);
return TRUE;
}
static GstElement *
on_rtpbin_request_aux_sender (GstElement * rtpbin, guint session_id,
GstWebRTCBin * webrtc)
{
TransportStream *stream;
gboolean have_rtx = FALSE;
GstElement *ret = NULL;
GstElement *ret, *rtx;
GstPad *pad;
char *name;
stream = _find_transport_for_session (webrtc, session_id);
if (stream)
have_rtx = transport_stream_get_pt (stream, "RTX", -1) != 0;
GST_LOG_OBJECT (webrtc, "requesting aux sender for stream %" GST_PTR_FORMAT,
stream);
if (have_rtx) {
GstElement *rtx;
GstPad *pad;
gchar *name;
GstStructure *merged_local_rtx_ssrc_map =
gst_structure_new_empty ("application/x-rtp-ssrc-map");
guint i;
if (stream->rtxsend) {
GST_WARNING_OBJECT (webrtc, "rtprtxsend already created! rtpbin bug?!");
goto out;
}
GST_INFO ("creating AUX sender");
ret = gst_bin_new (NULL);
rtx = gst_element_factory_make ("rtprtxsend", NULL);
g_object_set (rtx, "max-size-packets", 500, NULL);
_set_rtx_ptmap_from_stream (webrtc, stream);
for (i = 0; i < webrtc->priv->transceivers->len; i++) {
WebRTCTransceiver *trans =
WEBRTC_TRANSCEIVER (g_ptr_array_index (webrtc->priv->transceivers,
i));
if (trans->stream == stream && trans->local_rtx_ssrc_map)
gst_structure_foreach (trans->local_rtx_ssrc_map,
_merge_structure, merged_local_rtx_ssrc_map);
}
g_object_set (rtx, "ssrc-map", merged_local_rtx_ssrc_map, NULL);
gst_structure_free (merged_local_rtx_ssrc_map);
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);
stream->rtxsend = gst_object_ref (rtx);
if (!stream) {
/* a rtp session without a stream is a webrtcbin bug */
g_warn_if_reached ();
return NULL;
}
out:
if (stream->rtxsend) {
GST_WARNING_OBJECT (webrtc, "rtprtxsend already created! rtpbin bug?!");
g_warn_if_reached ();
return NULL;
}
GST_DEBUG_OBJECT (webrtc, "requesting aux sender for session %u "
"stream %" GST_PTR_FORMAT, session_id, stream);
ret = gst_bin_new (NULL);
rtx = gst_element_factory_make ("rtprtxsend", NULL);
/* XXX: allow control from outside? */
g_object_set (rtx, "max-size-packets", 500, NULL);
if (!gst_bin_add (GST_BIN (ret), rtx))
g_warn_if_reached ();
stream->rtxsend = gst_object_ref (rtx);
_set_internal_rtpbin_element_props_from_stream (webrtc, stream);
name = g_strdup_printf ("src_%u", session_id);
pad = gst_element_get_static_pad (rtx, "src");
if (!gst_element_add_pad (ret, gst_ghost_pad_new (name, pad)))
g_warn_if_reached ();
gst_clear_object (&pad);
g_clear_pointer (&name, g_free);
name = g_strdup_printf ("sink_%u", session_id);
pad = gst_element_get_static_pad (rtx, "sink");
if (!gst_element_add_pad (ret, gst_ghost_pad_new (name, pad)))
g_warn_if_reached ();
gst_clear_object (&pad);
g_clear_pointer (&name, g_free);
return ret;
}
@ -6550,108 +6676,68 @@ static GstElement *
on_rtpbin_request_aux_receiver (GstElement * rtpbin, guint session_id,
GstWebRTCBin * webrtc)
{
GstElement *ret = NULL;
GstElement *prev = NULL;
GstPad *sinkpad = NULL;
TransportStream *stream;
gint rtx_pt = 0;
GValue red_pt_array = { 0, };
gboolean have_red_pt = FALSE;
g_value_init (&red_pt_array, GST_TYPE_ARRAY);
GstPad *pad, *ghost;
GstElement *ret;
char *name;
stream = _find_transport_for_session (webrtc, session_id);
if (stream) {
guint i = 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"), "RED")) {
GValue ptval = { 0, };
g_value_init (&ptval, G_TYPE_INT);
g_value_set_int (&ptval, item->pt);
gst_value_array_append_value (&red_pt_array, &ptval);
g_value_unset (&ptval);
have_red_pt = TRUE;
}
}
}
rtx_pt = transport_stream_get_pt (stream, "RTX", -1);
if (!stream) {
/* no transport stream before the session has been created is a webrtcbin
* programming error! */
g_warn_if_reached ();
return NULL;
}
GST_LOG_OBJECT (webrtc, "requesting aux receiver for stream %" GST_PTR_FORMAT,
stream);
if (have_red_pt || rtx_pt)
ret = gst_bin_new (NULL);
if (rtx_pt) {
if (stream->rtxreceive) {
GST_WARNING_OBJECT (webrtc,
"rtprtxreceive already created! rtpbin bug?!");
goto error;
}
stream->rtxreceive = gst_element_factory_make ("rtprtxreceive", NULL);
_set_rtx_ptmap_from_stream (webrtc, stream);
gst_bin_add (GST_BIN (ret), stream->rtxreceive);
sinkpad = gst_element_get_static_pad (stream->rtxreceive, "sink");
prev = gst_object_ref (stream->rtxreceive);
if (stream->rtxreceive) {
GST_WARNING_OBJECT (webrtc, "rtprtxreceive already created! rtpbin bug?!");
g_warn_if_reached ();
return NULL;
}
if (have_red_pt) {
GstElement *rtpreddec = gst_element_factory_make ("rtpreddec", NULL);
GST_DEBUG_OBJECT (webrtc, "Creating RED decoder in session %u", session_id);
gst_bin_add (GST_BIN (ret), rtpreddec);
g_object_set_property (G_OBJECT (rtpreddec), "payloads", &red_pt_array);
if (prev)
gst_element_link (prev, rtpreddec);
else
sinkpad = gst_element_get_static_pad (rtpreddec, "sink");
prev = rtpreddec;
if (stream->reddec) {
GST_WARNING_OBJECT (webrtc, "rtpreddec already created! rtpbin bug?!");
g_warn_if_reached ();
return NULL;
}
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);
}
GST_DEBUG_OBJECT (webrtc, "requesting aux receiver for session %u "
"stream %" GST_PTR_FORMAT, session_id, stream);
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);
}
ret = gst_bin_new (NULL);
stream->rtxreceive = gst_element_factory_make ("rtprtxreceive", NULL);
gst_object_ref (stream->rtxreceive);
if (!gst_bin_add (GST_BIN (ret), stream->rtxreceive))
g_warn_if_reached ();
stream->reddec = gst_element_factory_make ("rtpreddec", NULL);
gst_object_ref (stream->reddec);
if (!gst_bin_add (GST_BIN (ret), stream->reddec))
g_warn_if_reached ();
_set_internal_rtpbin_element_props_from_stream (webrtc, stream);
if (!gst_element_link (stream->rtxreceive, stream->reddec))
g_warn_if_reached ();
name = g_strdup_printf ("sink_%u", session_id);
pad = gst_element_get_static_pad (stream->rtxreceive, "sink");
ghost = gst_ghost_pad_new (name, pad);
g_clear_pointer (&name, g_free);
gst_clear_object (&pad);
if (!gst_element_add_pad (ret, ghost))
g_warn_if_reached ();
name = g_strdup_printf ("src_%u", session_id);
pad = gst_element_get_static_pad (stream->reddec, "src");
ghost = gst_ghost_pad_new (name, pad);
g_clear_pointer (&name, g_free);
gst_clear_object (&pad);
if (!gst_element_add_pad (ret, ghost))
g_warn_if_reached ();
out:
g_value_unset (&red_pt_array);
return ret;
error:
if (ret)
gst_object_unref (ret);
goto out;
}
static GstElement *
@ -6660,10 +6746,14 @@ on_rtpbin_request_fec_decoder_full (GstElement * rtpbin, guint session_id,
{
TransportStream *stream;
GstElement *ret = NULL;
gint fec_pt = 0;
GObject *internal_storage;
stream = _find_transport_for_session (webrtc, session_id);
if (!stream) {
/* a rtp session without a stream is a webrtcbin bug */
g_warn_if_reached ();
return NULL;
}
/* TODO: for now, we only support ulpfec, but once we support
* more algorithms, if the remote may use more than one algorithm,
@ -6671,33 +6761,25 @@ on_rtpbin_request_fec_decoder_full (GstElement * rtpbin, guint session_id,
*
* + 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) {
guint i;
GST_DEBUG_OBJECT (webrtc, "Creating ULPFEC decoder for pt %d in session %u "
"stream %" GST_PTR_FORMAT, pt, session_id, stream);
for (i = 0; i < stream->ptmap->len; i++) {
PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i);
ret = gst_element_factory_make ("rtpulpfecdec", NULL);
if (item->pt == pt) {
fec_pt = transport_stream_get_pt (stream, "ULPFEC", item->media_idx);
break;
}
}
}
g_signal_emit_by_name (webrtc->rtpbin, "get-internal-storage", session_id,
&internal_storage);
if (fec_pt) {
GST_DEBUG_OBJECT (webrtc, "Creating ULPFEC decoder for pt %d in session %u",
fec_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, "storage", internal_storage, NULL);
g_clear_object (&internal_storage);
g_object_set (ret, "pt", fec_pt, "storage", internal_storage, NULL);
g_object_unref (internal_storage);
}
g_object_set_data (G_OBJECT (ret), GST_WEBRTC_PAYLOAD_TYPE,
GINT_TO_POINTER (pt));
PC_LOCK (webrtc);
stream->fecdecs = g_list_prepend (stream->fecdecs, gst_object_ref (ret));
_set_internal_rtpbin_element_props_from_stream (webrtc, stream);
PC_UNLOCK (webrtc);
return ret;
}

View file

@ -59,7 +59,7 @@ transport_stream_get_pt (TransportStream * stream, const gchar * encoding_name,
guint media_idx)
{
guint i;
gint ret = 0;
gint ret = -1;
for (i = 0; i < stream->ptmap->len; i++) {
PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i);
@ -165,25 +165,14 @@ transport_stream_dispose (GObject * object)
{
TransportStream *stream = TRANSPORT_STREAM (object);
if (stream->send_bin)
gst_object_unref (stream->send_bin);
stream->send_bin = NULL;
if (stream->receive_bin)
gst_object_unref (stream->receive_bin);
stream->receive_bin = NULL;
if (stream->transport)
gst_object_unref (stream->transport);
stream->transport = NULL;
if (stream->rtxsend)
gst_object_unref (stream->rtxsend);
stream->rtxsend = NULL;
if (stream->rtxreceive)
gst_object_unref (stream->rtxreceive);
stream->rtxreceive = NULL;
gst_clear_object (&stream->send_bin);
gst_clear_object (&stream->receive_bin);
gst_clear_object (&stream->transport);
gst_clear_object (&stream->rtxsend);
gst_clear_object (&stream->rtxreceive);
gst_clear_object (&stream->reddec);
g_list_free_full (stream->fecdecs, (GDestroyNotify) gst_object_unref);
stream->fecdecs = NULL;
GST_OBJECT_PARENT (object) = NULL;

View file

@ -67,6 +67,9 @@ struct _TransportStream
GstElement *rtxsend;
GstElement *rtxreceive;
GstElement *reddec;
GList *fecdecs;
};
struct _TransportStreamClass

View file

@ -148,9 +148,10 @@ webrtc_transceiver_finalize (GObject * object)
{
WebRTCTransceiver *trans = WEBRTC_TRANSCEIVER (object);
if (trans->stream)
gst_object_unref (trans->stream);
trans->stream = NULL;
gst_clear_object (&trans->stream);
gst_clear_object (&trans->ulpfecdec);
gst_clear_object (&trans->ulpfecenc);
gst_clear_object (&trans->redenc);
if (trans->local_rtx_ssrc_map)
gst_structure_free (trans->local_rtx_ssrc_map);

View file

@ -51,6 +51,10 @@ struct _WebRTCTransceiver
GstCaps *last_configured_caps;
gboolean mline_locked;
GstElement *ulpfecdec;
GstElement *ulpfecenc;
GstElement *redenc;
};
struct _WebRTCTransceiverClass

View file

@ -4371,6 +4371,60 @@ GST_START_TEST (test_codec_preferences_in_on_new_transceiver)
GST_END_TEST;
GST_START_TEST (test_renego_rtx)
{
struct test_webrtc *t = create_audio_video_test ();
VAL_SDP_INIT (no_duplicate_payloads, on_sdp_media_no_duplicate_payloads,
NULL, NULL);
guint media_format_count[] = { 1, 1 };
VAL_SDP_INIT (media_formats, on_sdp_media_count_formats,
media_format_count, &no_duplicate_payloads);
VAL_SDP_INIT (count_media, _count_num_sdp_media, GUINT_TO_POINTER (2),
&media_formats);
VAL_SDP_INIT (payloads, on_sdp_media_payload_types,
GUINT_TO_POINTER (1), &count_media);
const gchar *expected_offer_direction[] = { "sendrecv", "sendrecv", };
VAL_SDP_INIT (offer_direction, on_sdp_media_direction,
expected_offer_direction, &payloads);
const gchar *expected_answer_direction[] = { "recvonly", "recvonly", };
VAL_SDP_INIT (answer_direction, on_sdp_media_direction,
expected_answer_direction, &payloads);
const gchar *expected_offer_setup[] = { "actpass", "actpass", };
VAL_SDP_INIT (offer, on_sdp_media_setup, expected_offer_setup,
&offer_direction);
const gchar *expected_answer_setup[] = { "active", "active", };
VAL_SDP_INIT (answer, on_sdp_media_setup, expected_answer_setup,
&answer_direction);
GstWebRTCRTPTransceiver *trans;
t->on_negotiation_needed = NULL;
t->on_ice_candidate = NULL;
t->on_pad_added = _pad_added_fakesink;
test_validate_sdp (t, &offer, &answer);
test_webrtc_reset_negotiation (t);
g_signal_emit_by_name (t->webrtc1, "get-transceiver", 1, &trans);
g_object_set (trans, "do-nack", TRUE, "fec-type",
GST_WEBRTC_FEC_TYPE_ULP_RED, NULL);
g_clear_object (&trans);
g_signal_emit_by_name (t->webrtc2, "get-transceiver", 1, &trans);
g_object_set (trans, "do-nack", TRUE, "fec-type",
GST_WEBRTC_FEC_TYPE_ULP_RED, NULL);
g_clear_object (&trans);
/* adding RTX/RED/FEC increases the number of media formats */
media_format_count[1] = 5;
test_validate_sdp (t, &offer, &answer);
test_webrtc_free (t);
}
GST_END_TEST;
static Suite *
webrtcbin_suite (void)
{
@ -4425,6 +4479,7 @@ webrtcbin_suite (void)
tcase_add_test (tc, test_codec_preferences_no_duplicate_extmaps);
tcase_add_test (tc, test_codec_preferences_incompatible_extmaps);
tcase_add_test (tc, test_codec_preferences_invalid_extmap);
tcase_add_test (tc, test_renego_rtx);
if (sctpenc && sctpdec) {
tcase_add_test (tc, test_data_channel_create);
tcase_add_test (tc, test_data_channel_remote_notify);