webrtcbin: initial support for sending and receiving simulcast streams

Input (sink pads) is the already-ssrc-muxed stream with the relevant rtp
sdes header extensions already applied:
  - mid
  - stream-id
  - repaired-stream-id

Output (src pads) have the pads separated into individual ssrc's as
that's what rtpbin gives us.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/1664>
This commit is contained in:
Matthew Waters 2021-11-16 19:27:11 +11:00 committed by GStreamer Marge Bot
parent 75b23d646a
commit 2377f8b3f2
7 changed files with 912 additions and 108 deletions

View file

@ -66,6 +66,10 @@
#define DEFAULT_JB_LATENCY 200
#define RTPHDREXT_MID GST_RTP_HDREXT_BASE "sdes:mid"
#define RTPHDREXT_STREAM_ID GST_RTP_HDREXT_BASE "sdes:rtp-stream-id"
#define RTPHDREXT_REPAIRED_STREAM_ID GST_RTP_HDREXT_BASE "sdes:repaired-rtp-stream-id"
/**
* SECTION: element-webrtcbin
* title: webrtcbin
@ -263,18 +267,21 @@ gst_webrtc_bin_pad_class_init (GstWebRTCBinPadClass * klass)
}
static void
gst_webrtc_bin_pad_update_ssrc_event (GstWebRTCBinPad * wpad)
gst_webrtc_bin_pad_update_tos_event (GstWebRTCBinPad * wpad)
{
if (wpad->received_caps) {
WebRTCTransceiver *trans = (WebRTCTransceiver *) wpad->trans;
WebRTCTransceiver *trans = (WebRTCTransceiver *) wpad->trans;
if (wpad->received_caps && trans->parent.mid) {
GstPad *pad = GST_PAD (wpad);
gst_event_take (&trans->ssrc_event,
gst_event_take (&trans->tos_event,
gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_STICKY,
gst_structure_new ("GstWebRtcBinUpdateTos", "ssrc", G_TYPE_UINT,
trans->current_ssrc, NULL)));
gst_structure_new ("GstWebRtcBinUpdateTos", "mid", G_TYPE_STRING,
trans->parent.mid, NULL)));
gst_pad_send_event (pad, gst_event_ref (trans->ssrc_event));
GST_DEBUG_OBJECT (pad, "sending new tos event %" GST_PTR_FORMAT,
trans->tos_event);
gst_pad_send_event (pad, gst_event_ref (trans->tos_event));
}
}
@ -311,12 +318,7 @@ gst_webrtcbin_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
GST_PTR_FORMAT, pad, check_negotiation, caps);
if (check_negotiation) {
WebRTCTransceiver *trans = WEBRTC_TRANSCEIVER (wpad->trans);
const GstStructure *s;
s = gst_caps_get_structure (caps, 0);
gst_structure_get_uint (s, "ssrc", &trans->current_ssrc);
gst_webrtc_bin_pad_update_ssrc_event (wpad);
gst_webrtc_bin_pad_update_tos_event (wpad);
}
/* A remote description might have been set while the pad hadn't
@ -857,12 +859,6 @@ _find_pad_for_transceiver (GstWebRTCBin * webrtc, GstPadDirection direction,
}
#if 0
static gboolean
match_for_ssrc (GstWebRTCBinPad * pad, guint * ssrc)
{
return pad->ssrc == *ssrc;
}
static gboolean
match_for_pad (GstWebRTCBinPad * pad, GstWebRTCBinPad * other)
{
@ -870,6 +866,77 @@ match_for_pad (GstWebRTCBinPad * pad, GstWebRTCBinPad * other)
}
#endif
struct SsrcMatch
{
GstWebRTCRTPTransceiverDirection direction;
guint32 ssrc;
};
static gboolean
mid_ssrc_match_for_ssrc (SsrcMapItem * entry, const struct SsrcMatch *match)
{
return entry->direction == match->direction && entry->ssrc == match->ssrc;
}
static gboolean
mid_ssrc_remove_ssrc (SsrcMapItem * item, const struct SsrcMatch *match)
{
return !mid_ssrc_match_for_ssrc (item, match);
}
static SsrcMapItem *
find_mid_ssrc_for_ssrc (GstWebRTCBin * webrtc,
GstWebRTCRTPTransceiverDirection direction, guint rtp_session, guint ssrc)
{
TransportStream *stream = _find_transport_for_session (webrtc, rtp_session);
struct SsrcMatch m = { direction, ssrc };
if (!stream)
return NULL;
return transport_stream_find_ssrc_map_item (stream, &m,
(FindSsrcMapFunc) mid_ssrc_match_for_ssrc);
}
static SsrcMapItem *
find_or_add_ssrc_map_item (GstWebRTCBin * webrtc,
GstWebRTCRTPTransceiverDirection direction, guint rtp_session, guint ssrc,
guint media_idx)
{
TransportStream *stream = _find_transport_for_session (webrtc, rtp_session);
struct SsrcMatch m = { direction, ssrc };
SsrcMapItem *item;
if (!stream)
return NULL;
if ((item = transport_stream_find_ssrc_map_item (stream, &m,
(FindSsrcMapFunc) mid_ssrc_match_for_ssrc)))
return item;
return transport_stream_add_ssrc_map_item (stream, direction, ssrc,
media_idx);
}
static void
remove_ssrc_entry_by_ssrc (GstWebRTCBin * webrtc, guint rtp_session, guint ssrc)
{
TransportStream *stream;
stream = _find_transport_for_session (webrtc, rtp_session);
if (stream) {
struct SsrcMatch m =
{ GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY, ssrc };
transport_stream_filter_ssrc_map_item (stream, &m,
(FindSsrcMapFunc) mid_ssrc_remove_ssrc);
m.direction = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY;
transport_stream_filter_ssrc_map_item (stream, &m,
(FindSsrcMapFunc) mid_ssrc_remove_ssrc);
}
}
static gboolean
_unlock_pc_thread (GMutex * lock)
{
@ -1538,7 +1605,7 @@ _check_if_negotiation_is_needed (GstWebRTCBin * webrtc)
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 */
* as we will need the formats 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");
@ -1892,7 +1959,7 @@ _find_codec_preferences (GstWebRTCBin * webrtc,
if (caps) {
if (trans)
gst_caps_replace (&trans->last_configured_caps, caps);
gst_caps_replace (&trans->last_retrieved_caps, caps);
ret = caps;
}
@ -1901,8 +1968,8 @@ _find_codec_preferences (GstWebRTCBin * webrtc,
if (!ret) {
if (codec_preferences)
ret = gst_caps_ref (codec_preferences);
else if (trans->last_configured_caps)
ret = gst_caps_ref (trans->last_configured_caps);
else if (trans->last_retrieved_caps)
ret = gst_caps_ref (trans->last_retrieved_caps);
}
out:
@ -1971,14 +2038,6 @@ _on_dtls_transport_notify_state (GstWebRTCDTLSTransport * transport,
_update_peer_connection_state (webrtc);
}
static gboolean
match_ssrc (GstWebRTCRTPTransceiver * rtp_trans, gconstpointer data)
{
WebRTCTransceiver *trans = (WebRTCTransceiver *) rtp_trans;
return (trans->current_ssrc == GPOINTER_TO_UINT (data));
}
static gboolean
_on_sending_rtcp (GObject * internal_session, GstBuffer * buffer,
gboolean early, gpointer user_data)
@ -1993,17 +2052,28 @@ _on_sending_rtcp (GObject * internal_session, GstBuffer * buffer,
if (gst_rtcp_buffer_get_first_packet (&rtcp, &packet)) {
if (gst_rtcp_packet_get_type (&packet) == GST_RTCP_TYPE_SR) {
guint32 ssrc;
GstWebRTCRTPTransceiver *rtp_trans;
GstWebRTCRTPTransceiver *rtp_trans = NULL;
WebRTCTransceiver *trans;
guint rtp_session;
SsrcMapItem *mid;
gst_rtcp_packet_sr_get_sender_info (&packet, &ssrc, NULL, NULL, NULL,
NULL);
rtp_session =
GPOINTER_TO_UINT (g_object_get_data (internal_session,
"GstWebRTCBinRTPSessionID"));
rtp_trans = _find_transceiver (webrtc, GUINT_TO_POINTER (ssrc),
match_ssrc);
mid = find_mid_ssrc_for_ssrc (webrtc,
GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY, rtp_session, ssrc);
if (mid && mid->mid) {
rtp_trans = _find_transceiver_for_mid (webrtc, mid->mid);
GST_LOG_OBJECT (webrtc, "found %" GST_PTR_FORMAT " from mid entry "
"using rtp session %u ssrc %u -> mid \'%s\'", rtp_trans,
rtp_session, ssrc, mid->mid);
}
trans = (WebRTCTransceiver *) rtp_trans;
if (rtp_trans && rtp_trans->sender && trans->ssrc_event) {
if (rtp_trans && rtp_trans->sender && trans->tos_event) {
GstPad *pad;
gchar *pad_name = NULL;
@ -2013,7 +2083,7 @@ _on_sending_rtcp (GObject * internal_session, GstBuffer * buffer,
pad = gst_element_get_static_pad (webrtc->rtpbin, pad_name);
g_free (pad_name);
if (pad) {
gst_pad_push_event (pad, gst_event_ref (trans->ssrc_event));
gst_pad_push_event (pad, gst_event_ref (trans->tos_event));
gst_object_unref (pad);
}
}
@ -2036,6 +2106,8 @@ gst_webrtc_bin_attach_tos_to_session (GstWebRTCBin * webrtc, guint session_id)
session_id, &internal_session);
if (internal_session) {
g_object_set_data (internal_session, "GstWebRTCBinRTPSessionID",
GUINT_TO_POINTER (session_id));
g_signal_connect (internal_session, "on-sending-rtcp",
G_CALLBACK (_on_sending_rtcp), webrtc);
g_object_unref (internal_session);
@ -2063,14 +2135,13 @@ _nicesink_pad_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
gst_event_get_structure (GST_PAD_PROBE_INFO_EVENT (info));
if (gst_structure_has_name (s, "GstWebRtcBinUpdateTos")) {
guint ssrc;
const char *mid;
gint priority;
if (gst_structure_get_uint (s, "ssrc", &ssrc)) {
if ((mid = gst_structure_get_string (s, "mid"))) {
GstWebRTCRTPTransceiver *rtp_trans;
rtp_trans = _find_transceiver (webrtc, GUINT_TO_POINTER (ssrc),
match_ssrc);
rtp_trans = _find_transceiver_for_mid (webrtc, mid);
if (rtp_trans) {
WebRTCTransceiver *trans = WEBRTC_TRANSCEIVER (rtp_trans);
GstWebRTCICEStream *stream = _find_ice_stream_for_session (webrtc,
@ -2710,10 +2781,12 @@ _pick_rtx_payload_types (GstWebRTCBin * webrtc, WebRTCTransceiver * trans,
/* 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);
if (target_ssrc != -1) {
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);
@ -2979,6 +3052,49 @@ _gather_extmap (GstCaps * caps, GError ** error)
return edata.extmap;
}
struct has_hdrext
{
const char *rtphdrext;
gboolean result;
};
static gboolean
structure_value_has_rtphdrext (GQuark field_id, const GValue * value,
gpointer user_data)
{
struct has_hdrext *rtphdrext = user_data;
if (g_str_has_prefix (g_quark_to_string (field_id), "extmap-")) {
const char *val = NULL;
if (GST_VALUE_HOLDS_ARRAY (value) && gst_value_array_get_size (value) >= 2) {
value = gst_value_array_get_value (value, 1);
}
if (G_VALUE_HOLDS_STRING (value)) {
val = g_value_get_string (value);
}
if (g_strcmp0 (val, rtphdrext->rtphdrext) == 0) {
rtphdrext->result = TRUE;
return FALSE;
}
}
return TRUE;
}
static gboolean
caps_contain_rtp_header_extension (const GstCaps * caps,
const char *rtphdrextname)
{
const GstStructure *s = gst_caps_get_structure (caps, 0);
struct has_hdrext data = { rtphdrextname, FALSE };
gst_structure_foreach (s, structure_value_has_rtphdrext, &data);
return data.result;
}
static gboolean
_copy_field (GQuark field_id, const GValue * value, GstStructure * s)
{
@ -3178,9 +3294,12 @@ sdp_media_from_transceiver (GstWebRTCBin * webrtc, GstSDPMedia * media,
if (!gst_structure_get_int (s, "clock-rate", &clockrate))
GST_WARNING_OBJECT (webrtc,
"Caps %" GST_PTR_FORMAT " are missing clock-rate", caps);
if (!gst_structure_get_uint (s, "ssrc", &rtx_target_ssrc))
GST_WARNING_OBJECT (webrtc, "Caps %" GST_PTR_FORMAT " are missing ssrc",
caps);
if (!gst_structure_get_uint (s, "ssrc", &rtx_target_ssrc)) {
if (!caps_contain_rtp_header_extension (caps, RTPHDREXT_MID)) {
GST_WARNING_OBJECT (webrtc, "Caps %" GST_PTR_FORMAT " are missing ssrc",
caps);
}
}
_pick_fec_payload_types (webrtc, WEBRTC_TRANSCEIVER (trans), reserved_pts,
clockrate, &rtx_target_pt, media);
@ -3196,6 +3315,7 @@ sdp_media_from_transceiver (GstWebRTCBin * webrtc, GstSDPMedia * media,
/* Some identifier; we also add the media name to it so it's identifiable */
if (trans->mid) {
const char *media_mid = gst_sdp_media_get_attribute_val (media, "mid");
if (!media_mid) {
gst_sdp_media_add_attribute (media, "mid", trans->mid);
} else if (g_strcmp0 (media_mid, trans->mid) != 0) {
@ -6001,7 +6121,11 @@ _set_description_task (GstWebRTCBin * webrtc, struct set_description *sd)
if (split[0] && sscanf (split[0], "%u", &ssrc) && split[1]
&& g_str_has_prefix (split[1], "cname:")) {
g_ptr_array_add (item->remote_ssrcmap, ssrcmap_item_new (ssrc, i));
if (!find_mid_ssrc_for_ssrc (webrtc,
GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY,
rtp_session_id, ssrc))
transport_stream_add_ssrc_map_item (item,
GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY, ssrc, i);
}
g_strfreev (split);
}
@ -6624,13 +6748,12 @@ on_rtpbin_pad_added (GstElement * rtpbin, GstPad * new_pad,
GST_TRACE_OBJECT (webrtc, "new rtpbin pad %s", new_pad_name);
if (g_str_has_prefix (new_pad_name, "recv_rtp_src_")) {
guint32 session_id = 0, ssrc = 0, pt = 0;
GstWebRTCRTPTransceiver *rtp_trans;
SsrcMapItem *mid_entry;
GstWebRTCRTPTransceiver *rtp_trans = NULL;
WebRTCTransceiver *trans;
TransportStream *stream;
GstWebRTCBinPad *pad;
guint media_idx = 0;
gboolean found_ssrc = FALSE;
guint i;
guint media_idx;
if (sscanf (new_pad_name, "recv_rtp_src_%u_%u_%u", &session_id, &ssrc,
&pt) != 3) {
@ -6638,33 +6761,43 @@ on_rtpbin_pad_added (GstElement * rtpbin, GstPad * new_pad,
return;
}
media_idx = session_id;
PC_LOCK (webrtc);
stream = _find_transport_for_session (webrtc, session_id);
if (!stream)
g_warn_if_reached ();
media_idx = session_id;
mid_entry =
find_mid_ssrc_for_ssrc (webrtc,
GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY, session_id, ssrc);
for (i = 0; i < stream->remote_ssrcmap->len; i++) {
SsrcMapItem *item = g_ptr_array_index (stream->remote_ssrcmap, i);
if (item->ssrc == ssrc) {
media_idx = item->media_idx;
found_ssrc = TRUE;
break;
if (mid_entry) {
if (mid_entry->mid) {
/* Can't use the mid_entry if the mid doesn't exist */
rtp_trans = _find_transceiver_for_mid (webrtc, mid_entry->mid);
if (rtp_trans) {
g_assert_cmpint (rtp_trans->mline, ==, mid_entry->media_idx);
}
}
}
if (!found_ssrc) {
if (mid_entry->media_idx != -1)
media_idx = mid_entry->media_idx;
} else {
GST_WARNING_OBJECT (webrtc, "Could not find ssrc %u", ssrc);
/* TODO: connect up to fakesink and reconnect later when this information
* is known from RTCP SDES or RTP Header extension
*/
}
rtp_trans = _find_transceiver_for_mline (webrtc, media_idx);
if (!rtp_trans)
rtp_trans = _find_transceiver_for_mline (webrtc, media_idx);
if (!rtp_trans)
g_warn_if_reached ();
trans = WEBRTC_TRANSCEIVER (rtp_trans);
g_assert (trans->stream == stream);
pad = _find_pad_for_transceiver (webrtc, GST_PAD_SRC, rtp_trans);
GST_TRACE_OBJECT (webrtc, "found pad %" GST_PTR_FORMAT
" for rtpbin pad name %s", pad, new_pad_name);
if (!_remove_pending_pad (webrtc, pad)) {
@ -6708,6 +6841,7 @@ on_rtpbin_request_pt_map (GstElement * rtpbin, guint session_id, guint pt,
GST_DEBUG_OBJECT (webrtc, "getting pt map for pt %d in session %d", pt,
session_id);
PC_LOCK (webrtc);
stream = _find_transport_for_session (webrtc, session_id);
if (!stream)
goto unknown_session;
@ -6718,10 +6852,12 @@ on_rtpbin_request_pt_map (GstElement * rtpbin, guint session_id, guint pt,
GST_DEBUG_OBJECT (webrtc, "Found caps %" GST_PTR_FORMAT " for pt %d in "
"session %d", ret, pt, session_id);
PC_UNLOCK (webrtc);
return ret;
unknown_session:
{
PC_UNLOCK (webrtc);
GST_DEBUG_OBJECT (webrtc, "unknown session %d", session_id);
return NULL;
}
@ -6897,6 +7033,10 @@ on_rtpbin_bye_ssrc (GstElement * rtpbin, guint session_id, guint ssrc,
GstWebRTCBin * webrtc)
{
GST_INFO_OBJECT (webrtc, "session %u ssrc %u received bye", session_id, ssrc);
PC_LOCK (webrtc);
remove_ssrc_entry_by_ssrc (webrtc, session_id, ssrc);
PC_UNLOCK (webrtc);
}
static void
@ -6904,6 +7044,10 @@ on_rtpbin_bye_timeout (GstElement * rtpbin, guint session_id, guint ssrc,
GstWebRTCBin * webrtc)
{
GST_INFO_OBJECT (webrtc, "session %u ssrc %u bye timeout", session_id, ssrc);
PC_LOCK (webrtc);
remove_ssrc_entry_by_ssrc (webrtc, session_id, ssrc);
PC_UNLOCK (webrtc);
}
static void
@ -6912,6 +7056,10 @@ on_rtpbin_sender_timeout (GstElement * rtpbin, guint session_id, guint ssrc,
{
GST_INFO_OBJECT (webrtc, "session %u ssrc %u sender timeout", session_id,
ssrc);
PC_LOCK (webrtc);
remove_ssrc_entry_by_ssrc (webrtc, session_id, ssrc);
PC_UNLOCK (webrtc);
}
static void
@ -6919,6 +7067,14 @@ on_rtpbin_new_ssrc (GstElement * rtpbin, guint session_id, guint ssrc,
GstWebRTCBin * webrtc)
{
GST_INFO_OBJECT (webrtc, "session %u ssrc %u new ssrc", session_id, ssrc);
if (ssrc == 0)
return;
PC_LOCK (webrtc);
find_or_add_ssrc_map_item (webrtc,
GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY, session_id, ssrc, -1);
PC_UNLOCK (webrtc);
}
static void
@ -6939,7 +7095,30 @@ static void
on_rtpbin_ssrc_sdes (GstElement * rtpbin, guint session_id, guint ssrc,
GstWebRTCBin * webrtc)
{
GObject *session;
GST_INFO_OBJECT (webrtc, "session %u ssrc %u sdes", session_id, ssrc);
g_signal_emit_by_name (rtpbin, "get-internal-session", session_id, &session);
if (session) {
GObject *source;
g_signal_emit_by_name (session, "get-source-by-ssrc", ssrc, &source);
if (source) {
GstStructure *sdes;
g_object_get (source, "sdes", &sdes, NULL);
/* TODO: when the sdes contains the mid, use that to correlate streams
* as necessary */
GST_DEBUG_OBJECT (webrtc, "session %u ssrc %u sdes %" GST_PTR_FORMAT,
session_id, ssrc, sdes);
gst_clear_structure (&sdes);
gst_clear_object (&source);
}
g_clear_object (&session);
}
}
static void
@ -6954,14 +7133,72 @@ on_rtpbin_timeout (GstElement * rtpbin, guint session_id, guint ssrc,
GstWebRTCBin * webrtc)
{
GST_INFO_OBJECT (webrtc, "session %u ssrc %u timeout", session_id, ssrc);
PC_LOCK (webrtc);
remove_ssrc_entry_by_ssrc (webrtc, session_id, ssrc);
PC_UNLOCK (webrtc);
}
static void
on_rtpbin_new_sender_ssrc (GstElement * rtpbin, guint session_id, guint ssrc,
GstWebRTCBin * webrtc)
{
SsrcMapItem *mid;
GST_INFO_OBJECT (webrtc, "session %u ssrc %u new sender ssrc", session_id,
ssrc);
PC_LOCK (webrtc);
mid = find_mid_ssrc_for_ssrc (webrtc,
GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY, session_id, ssrc);
if (!mid) {
TransportStream *stream = _find_transport_for_session (webrtc, session_id);
transport_stream_add_ssrc_map_item (stream,
GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY, ssrc, -1);
} else if (mid->mid) {
/* XXX: when peers support the sdes rtcp item, use this to send the mid rtcp
* sdes item. Requires being able to set the sdes on the rtpsource. */
#if 0
GObject *session;
g_signal_emit_by_name (rtpbin, "get-internal-session", session_id,
&session, NULL);
if (session) {
GObject *source;
g_signal_emit_by_name (session, "get-source-by-ssrc", ssrc, &source);
if (source) {
GstStructure *sdes;
const char *sdes_field_name;
g_object_get (source, "sdes", &sdes, NULL);
GST_WARNING_OBJECT (webrtc, "session %u ssrc %u retrieve sdes %"
GST_PTR_FORMAT, session_id, ssrc, sdes);
sdes_field_name = gst_rtcp_sdes_type_to_name (GST_RTCP_SDES_MID);
g_assert (sdes_field_name);
gst_structure_set (sdes, sdes_field_name, G_TYPE_STRING, mid->mid,
NULL);
if (mid->rid) {
sdes_field_name =
gst_rtcp_sdes_type_to_name (GST_RTCP_SDES_RTP_STREAM_ID);
g_assert (sdes_field_name);
gst_structure_set (sdes, sdes_field_name, mid->rid, NULL);
// TODO: repaired-rtp-stream-id
}
// TODO: writable sdes?
g_object_set (source, "sdes", sdes, NULL);
GST_INFO_OBJECT (webrtc,
"session %u ssrc %u set sdes %" GST_PTR_FORMAT, session_id, ssrc,
sdes);
gst_clear_structure (&sdes);
gst_clear_object (&source);
}
g_clear_object (&session);
}
#endif
}
PC_UNLOCK (webrtc);
}
static void
@ -6972,12 +7209,49 @@ on_rtpbin_sender_ssrc_active (GstElement * rtpbin, guint session_id, guint ssrc,
ssrc);
}
struct new_jb_args
{
GstWebRTCBin *webrtc;
GstElement *jitterbuffer;
TransportStream *stream;
guint ssrc;
};
static gboolean
jitter_buffer_set_retransmission (SsrcMapItem * item,
const struct new_jb_args *data)
{
GstWebRTCRTPTransceiver *trans;
gboolean do_nack;
if (item->media_idx == -1)
return TRUE;
trans = _find_transceiver_for_mline (data->webrtc, item->media_idx);
if (!trans) {
g_warn_if_reached ();
return TRUE;
}
do_nack = WEBRTC_TRANSCEIVER (trans)->do_nack;
/* We don't set do-retransmission on rtpbin as we want per-session control */
GST_LOG_OBJECT (data->webrtc, "setting do-nack=%s for transceiver %"
GST_PTR_FORMAT " with transport %" GST_PTR_FORMAT
" rtp session %u ssrc %u", do_nack ? "true" : "false", trans,
data->stream, data->stream->session_id, data->ssrc);
g_object_set (data->jitterbuffer, "do-retransmission", do_nack, NULL);
g_weak_ref_set (&item->rtpjitterbuffer, data->jitterbuffer);
return TRUE;
}
static void
on_rtpbin_new_jitterbuffer (GstElement * rtpbin, GstElement * jitterbuffer,
guint session_id, guint ssrc, GstWebRTCBin * webrtc)
{
TransportStream *stream;
guint i;
struct new_jb_args d = { 0, };
PC_LOCK (webrtc);
GST_INFO_OBJECT (webrtc, "new jitterbuffer %" GST_PTR_FORMAT " for "
@ -6988,33 +7262,13 @@ on_rtpbin_new_jitterbuffer (GstElement * rtpbin, GstElement * jitterbuffer,
goto out;
}
/* XXX: this will fail with no ssrc in the remote sdp as used with e.g. simulcast
* newer SDP versions from chrome/firefox */
for (i = 0; i < stream->remote_ssrcmap->len; i++) {
SsrcMapItem *item = g_ptr_array_index (stream->remote_ssrcmap, i);
d.webrtc = webrtc;
d.jitterbuffer = jitterbuffer;
d.stream = stream;
d.ssrc = ssrc;
transport_stream_filter_ssrc_map_item (stream, &d,
(FindSsrcMapFunc) jitter_buffer_set_retransmission);
if (item->ssrc == ssrc) {
GstWebRTCRTPTransceiver *trans;
gboolean do_nack;
trans = _find_transceiver_for_mline (webrtc, item->media_idx);
if (!trans) {
g_warn_if_reached ();
break;
}
do_nack = WEBRTC_TRANSCEIVER (trans)->do_nack;
/* We don't set do-retransmission on rtpbin as we want per-session control */
GST_LOG_OBJECT (webrtc, "setting do-nack=%s for transceiver %"
GST_PTR_FORMAT " with transport %" GST_PTR_FORMAT
" rtp session %u ssrc %u", do_nack ? "true" : "false", trans, stream,
session_id, ssrc);
g_object_set (jitterbuffer, "do-retransmission", do_nack, NULL);
g_weak_ref_set (&item->rtpjitterbuffer, jitterbuffer);
break;
}
}
out:
PC_UNLOCK (webrtc);
}
@ -7145,6 +7399,91 @@ sink_pad_block (GstPad * pad, GstPadProbeInfo * info, gpointer unused)
return GST_PAD_PROBE_OK;
}
static void
peek_sink_buffer (GstWebRTCBin * webrtc, guint rtp_session_id,
guint media_idx, WebRTCTransceiver * trans, GstBuffer * buffer)
{
GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
SsrcMapItem *item;
guint ssrc;
if (!gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp))
return;
ssrc = gst_rtp_buffer_get_ssrc (&rtp);
gst_rtp_buffer_unmap (&rtp);
if (!ssrc) {
GST_WARNING_OBJECT (webrtc,
"incoming buffer does not contain a valid ssrc");
return;
}
PC_LOCK (webrtc);
item =
find_or_add_ssrc_map_item (webrtc,
GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY, rtp_session_id, ssrc,
media_idx);
if (item->media_idx == -1) {
char *str;
GST_DEBUG_OBJECT (webrtc, "updating media idx of ssrc item %p to %u", item,
media_idx);
item->media_idx = media_idx;
/* ensure that the rtx mapping contains a valid ssrc to use for rtx when
* used even when there are no ssrc's in the input/codec preferences caps */
str = g_strdup_printf ("%u", ssrc);
if (!gst_structure_has_field_typed (trans->local_rtx_ssrc_map, str,
G_TYPE_UINT)) {
/* TODO: ssrc-collision? */
gst_structure_set (trans->local_rtx_ssrc_map, str, G_TYPE_UINT,
g_random_int (), NULL);
_set_internal_rtpbin_element_props_from_stream (webrtc, trans->stream);
}
g_free (str);
}
PC_UNLOCK (webrtc);
}
static GstPadProbeReturn
sink_pad_buffer_peek (GstPad * pad, GstPadProbeInfo * info,
GstWebRTCBin * webrtc)
{
GstWebRTCBinPad *webrtc_pad = GST_WEBRTC_BIN_PAD (pad);
WebRTCTransceiver *trans;
guint rtp_session_id, media_idx;
if (!webrtc_pad->trans)
return GST_PAD_PROBE_OK;
trans = (WebRTCTransceiver *) webrtc_pad->trans;
if (!trans->stream)
return GST_PAD_PROBE_OK;
rtp_session_id = trans->stream->session_id;
media_idx = webrtc_pad->trans->mline;
if (media_idx != G_MAXUINT)
return GST_PAD_PROBE_OK;
if (info->type & GST_PAD_PROBE_TYPE_BUFFER) {
GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER (info);
peek_sink_buffer (webrtc, rtp_session_id, media_idx, trans, buffer);
} else if (info->type & GST_PAD_PROBE_TYPE_BUFFER_LIST) {
GstBufferList *list = GST_PAD_PROBE_INFO_BUFFER_LIST (info);
guint i, n;
n = gst_buffer_list_length (list);
for (i = 0; i < n; i++) {
GstBuffer *buffer = gst_buffer_list_get (list, i);
peek_sink_buffer (webrtc, rtp_session_id, media_idx, trans, buffer);
}
} else {
g_assert_not_reached ();
}
return GST_PAD_PROBE_OK;
}
static GstPad *
gst_webrtc_bin_request_new_pad (GstElement * element, GstPadTemplate * templ,
@ -7310,6 +7649,10 @@ gst_webrtc_bin_request_new_pad (GstElement * element, GstPadTemplate * templ,
g_list_append (webrtc->priv->pending_sink_transceivers,
gst_object_ref (pad));
gst_pad_add_probe (GST_PAD (pad),
GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BUFFER_LIST,
(GstPadProbeCallback) sink_pad_buffer_peek, webrtc, NULL);
if (lock_mline) {
WebRTCTransceiver *wtrans = WEBRTC_TRANSCEIVER (trans);
wtrans->mline_locked = TRUE;

View file

@ -321,10 +321,11 @@ _get_stats_from_rtp_source_stats (GstWebRTCBin * webrtc,
gst_structure_get (source_stats, "have-sr", G_TYPE_BOOLEAN, &have_sr, NULL);
for (i = 0; i < stream->remote_ssrcmap->len; i++) {
SsrcMapItem *item = g_ptr_array_index (stream->remote_ssrcmap, i);
for (i = 0; i < stream->ssrcmap->len; i++) {
SsrcMapItem *item = g_ptr_array_index (stream->ssrcmap, i);
if (item->ssrc == ssrc) {
if (item->direction == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY
&& item->ssrc == ssrc) {
GObject *jb = g_weak_ref_get (&item->rtpjitterbuffer);
if (jb) {

View file

@ -190,7 +190,7 @@ transport_stream_finalize (GObject * object)
TransportStream *stream = TRANSPORT_STREAM (object);
g_array_free (stream->ptmap, TRUE);
g_ptr_array_free (stream->remote_ssrcmap, TRUE);
g_ptr_array_free (stream->ssrcmap, TRUE);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
@ -277,11 +277,13 @@ clear_ptmap_item (PtMapItem * item)
gst_caps_unref (item->caps);
}
SsrcMapItem *
ssrcmap_item_new (guint32 ssrc, guint media_idx)
static SsrcMapItem *
ssrcmap_item_new (GstWebRTCRTPTransceiverDirection direction, guint32 ssrc,
guint media_idx)
{
SsrcMapItem *ssrc_item = g_slice_new (SsrcMapItem);
SsrcMapItem *ssrc_item = g_new0 (SsrcMapItem, 1);
ssrc_item->direction = direction;
ssrc_item->media_idx = media_idx;
ssrc_item->ssrc = ssrc;
g_weak_ref_init (&ssrc_item->rtpjitterbuffer, NULL);
@ -293,7 +295,67 @@ static void
ssrcmap_item_free (SsrcMapItem * item)
{
g_weak_ref_clear (&item->rtpjitterbuffer);
g_slice_free (SsrcMapItem, item);
g_clear_pointer (&item->mid, g_free);
g_clear_pointer (&item->rid, g_free);
g_free (item);
}
SsrcMapItem *
transport_stream_find_ssrc_map_item (TransportStream * stream,
gconstpointer data, FindSsrcMapFunc func)
{
int i;
for (i = 0; i < stream->ssrcmap->len; i++) {
SsrcMapItem *item = g_ptr_array_index (stream->ssrcmap, i);
if (func (item, data))
return item;
}
return NULL;
}
void
transport_stream_filter_ssrc_map_item (TransportStream * stream,
gconstpointer data, FindSsrcMapFunc func)
{
int i;
for (i = 0; i < stream->ssrcmap->len;) {
SsrcMapItem *item = g_ptr_array_index (stream->ssrcmap, i);
if (!func (item, data)) {
GST_TRACE_OBJECT (stream, "removing ssrc %u", item->ssrc);
g_ptr_array_remove_index_fast (stream->ssrcmap, i);
} else {
i++;
}
}
}
SsrcMapItem *
transport_stream_add_ssrc_map_item (TransportStream * stream,
GstWebRTCRTPTransceiverDirection direction, guint32 ssrc, guint media_idx)
{
SsrcMapItem *ret = NULL;
char *dir_str = gst_webrtc_rtp_transceiver_direction_to_string (direction);
g_return_val_if_fail (direction ==
GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY
|| direction == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY, NULL);
g_return_val_if_fail (ssrc != 0, NULL);
GST_INFO_OBJECT (stream, "Adding mapping for rtp session %u media_idx %u "
"direction %s ssrc %u", stream->session_id, media_idx, dir_str, ssrc);
/* XXX: duplicates? */
ret = ssrcmap_item_new (direction, ssrc, media_idx);
g_ptr_array_add (stream->ssrcmap, ret);
g_free (dir_str);
return ret;
}
static void
@ -301,7 +363,7 @@ transport_stream_init (TransportStream * stream)
{
stream->ptmap = g_array_new (FALSE, TRUE, sizeof (PtMapItem));
g_array_set_clear_func (stream->ptmap, (GDestroyNotify) clear_ptmap_item);
stream->remote_ssrcmap = g_ptr_array_new_with_free_func (
stream->ssrcmap = g_ptr_array_new_with_free_func (
(GDestroyNotify) ssrcmap_item_free);
}

View file

@ -40,14 +40,14 @@ typedef struct
typedef struct
{
GstWebRTCRTPTransceiverDirection direction;
guint32 ssrc;
guint media_idx;
char *mid;
char *rid;
GWeakRef rtpjitterbuffer; /* for stats */
} SsrcMapItem;
SsrcMapItem * ssrcmap_item_new (guint32 ssrc,
guint media_idx);
struct _TransportStream
{
GstObject parent;
@ -62,7 +62,7 @@ struct _TransportStream
GstWebRTCDTLSTransport *transport;
GArray *ptmap; /* array of PtMapItem's */
GPtrArray *remote_ssrcmap; /* array of SsrcMapItem's */
GPtrArray *ssrcmap; /* array of SsrcMapItem's */
gboolean output_connected; /* whether receive bin is connected to rtpbin */
GstElement *rtxsend;
@ -88,6 +88,21 @@ int * transport_stream_get_all_pt (TransportStream * stream,
GstCaps * transport_stream_get_caps_for_pt (TransportStream * stream,
guint pt);
typedef gboolean (*FindSsrcMapFunc) (SsrcMapItem * e1, gconstpointer data);
SsrcMapItem * transport_stream_find_ssrc_map_item (TransportStream * stream,
gconstpointer data,
FindSsrcMapFunc func);
void transport_stream_filter_ssrc_map_item (TransportStream * stream,
gconstpointer data,
FindSsrcMapFunc func);
SsrcMapItem * transport_stream_add_ssrc_map_item (TransportStream * stream,
GstWebRTCRTPTransceiverDirection direction,
guint32 ssrc,
guint media_idx);
G_END_DECLS
#endif /* __TRANSPORT_STREAM_H__ */

View file

@ -157,9 +157,10 @@ webrtc_transceiver_finalize (GObject * object)
gst_structure_free (trans->local_rtx_ssrc_map);
trans->local_rtx_ssrc_map = NULL;
gst_caps_replace (&trans->last_configured_caps, NULL);
gst_caps_replace (&trans->last_retrieved_caps, NULL);
gst_caps_replace (&trans->last_send_configured_caps, NULL);
gst_event_replace (&trans->ssrc_event, NULL);
gst_event_replace (&trans->tos_event, NULL);
G_OBJECT_CLASS (parent_class)->finalize (object);
}

View file

@ -40,15 +40,19 @@ struct _WebRTCTransceiver
TransportStream *stream;
GstStructure *local_rtx_ssrc_map;
guint current_ssrc;
GstEvent *ssrc_event;
GstEvent *tos_event;
/* Properties */
GstWebRTCFECType fec_type;
guint fec_percentage;
gboolean do_nack;
GstCaps *last_configured_caps;
/* The last caps that we put into to a SDP media section */
GstCaps *last_retrieved_caps;
/* The last caps that we successfully configured from a valid
* set_local/remote description call.
*/
GstCaps *last_send_configured_caps;
gboolean mline_locked;

View file

@ -4727,6 +4727,383 @@ GST_START_TEST (test_max_bundle_fec)
GST_END_TEST;
#define RTPHDREXT_MID GST_RTP_HDREXT_BASE "sdes:mid"
#define RTPHDREXT_STREAM_ID GST_RTP_HDREXT_BASE "sdes:rtp-stream-id"
#define RTPHDREXT_REPAIRED_STREAM_ID GST_RTP_HDREXT_BASE "sdes:repaired-rtp-stream-id"
#define L16_CAPS "application/x-rtp, payload=11, media=audio," \
" encoding-name=L16, clock-rate=44100"
static GstCaps *
create_simulcast_audio_caps (GstWebRTCRTPTransceiverDirection direction,
guint n_rid, guint ssrc[], const char *mid, guint mid_ext_id,
const char *const *rids, guint stream_ext_id, guint repaired_ext_id)
{
GstStructure *s;
GstCaps *caps;
const char *dir_str;
if (direction == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY)
dir_str = "recv";
else if (direction == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY)
dir_str = "send";
else
g_assert_not_reached ();
caps = gst_caps_from_string (L16_CAPS);
s = gst_caps_get_structure (caps, 0);
if (mid && mid_ext_id != G_MAXUINT) {
char *extmap_key = g_strdup_printf ("extmap-%u", mid_ext_id);
gst_structure_set (s, "a-mid", G_TYPE_STRING, mid, extmap_key,
G_TYPE_STRING, RTPHDREXT_MID, NULL);
g_free (extmap_key);
}
if (rids && n_rid > 0 && stream_ext_id != G_MAXUINT) {
GString *simulcast_value = g_string_new (dir_str);
char *extmap_key, *value;
int i;
g_string_append_c (simulcast_value, ' ');
for (i = 0; i < n_rid; i++) {
char *rid_key = g_strdup_printf ("rid-%s", rids[i]);
gst_structure_set (s, rid_key, G_TYPE_STRING, dir_str, NULL);
if (i > 0)
g_string_append_c (simulcast_value, ';');
g_string_append (simulcast_value, rids[i]);
g_free (rid_key);
}
value = g_string_free (simulcast_value, FALSE);
simulcast_value = NULL;
extmap_key = g_strdup_printf ("extmap-%u", stream_ext_id);
gst_structure_set (s, extmap_key, G_TYPE_STRING, RTPHDREXT_STREAM_ID,
"a-simulcast", G_TYPE_STRING, value, NULL);
g_clear_pointer (&extmap_key, g_free);
g_clear_pointer (&value, g_free);
if (repaired_ext_id != G_MAXUINT) {
extmap_key = g_strdup_printf ("extmap-%u", repaired_ext_id);
gst_structure_set (s, extmap_key, G_TYPE_STRING,
RTPHDREXT_REPAIRED_STREAM_ID, NULL);
g_clear_pointer (&extmap_key, g_free);
}
}
return caps;
}
static void
add_simulcast_audio_test_src_harness (GstHarness * h, guint n_rid,
guint ssrc[], const char *mid, guint mid_ext_id,
const char *const *rids, guint stream_ext_id, guint repaired_ext_id)
{
GstRTPHeaderExtension *ext;
GstElement *capsfilter;
char *launch_str;
GstCaps *caps;
int i;
caps =
create_simulcast_audio_caps
(GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY, n_rid, ssrc, mid,
mid_ext_id, rids, stream_ext_id, repaired_ext_id);
gst_harness_set_src_caps (h, gst_caps_ref (caps));
if (n_rid == 0) {
launch_str =
g_strdup ("audiotestsrc is-live=true ! " L16_CAPS
",ssrc=(uint)3384078954 ! rtpL16pay name=payloader0");
} else {
GString *launch = g_string_new (NULL);
for (i = 0; i < n_rid; i++) {
const char *rtpfunnel = "funnel.";
if (i == 0)
rtpfunnel = "rtpfunnel name=funnel ! capsfilter name=capsfilter";
g_string_append_printf (launch, "audiotestsrc is-live=true ! "
"rtpL16pay name=payloader%u ! " L16_CAPS ", ssrc=(uint)%u ! %s ", i,
ssrc[i], rtpfunnel);
}
launch_str = g_string_free (launch, FALSE);
}
GST_INFO ("generated launch string %s", launch_str);
gst_harness_add_src_parse (h, launch_str, TRUE);
g_clear_pointer (&launch_str, g_free);
capsfilter =
gst_bin_get_by_name (GST_BIN (h->src_harness->element), "capsfilter");
g_object_set (capsfilter, "caps", caps, NULL);
gst_clear_object (&capsfilter);
gst_clear_caps (&caps);
for (i = 0; i == 0 || i < n_rid; i++) {
const char *rid = n_rid > 0 ? rids[i] : NULL;
char *pay_name = g_strdup_printf ("payloader%u", i);
GstElement *payloader =
gst_bin_get_by_name (GST_BIN (h->src_harness->element), pay_name);
fail_unless (payloader);
g_clear_pointer (&pay_name, g_free);
if (mid_ext_id != G_MAXUINT) {
ext = gst_rtp_header_extension_create_from_uri (RTPHDREXT_MID);
fail_unless (ext);
gst_rtp_header_extension_set_id (ext, mid_ext_id);
g_object_set (ext, "mid", mid, NULL);
g_signal_emit_by_name (payloader, "add-extension", ext);
gst_clear_object (&ext);
}
if (n_rid > 0 && stream_ext_id != G_MAXUINT) {
ext = gst_rtp_header_extension_create_from_uri (RTPHDREXT_STREAM_ID);
fail_unless (ext);
gst_rtp_header_extension_set_id (ext, stream_ext_id);
g_object_set (ext, "rid", rid, NULL);
g_signal_emit_by_name (payloader, "add-extension", ext);
gst_clear_object (&ext);
}
if (n_rid > 0 && stream_ext_id != G_MAXUINT) {
ext = gst_rtp_header_extension_create_from_uri (RTPHDREXT_STREAM_ID);
fail_unless (ext);
gst_rtp_header_extension_set_id (ext, stream_ext_id);
g_object_set (ext, "rid", rid, NULL);
g_signal_emit_by_name (payloader, "add-extension", ext);
gst_clear_object (&ext);
}
gst_clear_object (&payloader);
}
}
#undef L16_CAPS
static gboolean
gst_g_ptr_array_find_str (GPtrArray * ptr, const char *needle, guint * index)
{
guint i;
for (i = 0; i < ptr->len; i++) {
const char *test = g_ptr_array_index (ptr, i);
if (g_strcmp0 (test, needle) == 0) {
if (index)
*index = i;
return TRUE;
}
}
return FALSE;
}
struct ExpectedRid
{
guint n_rid;
const char *const *rid;
};
static void
on_sdp_media_rid (struct test_webrtc *t, GstElement * element,
GstWebRTCSessionDescription * desc, gpointer user_data)
{
struct ExpectedRid *expected_rids = user_data;
int i;
for (i = 0; i < gst_sdp_message_medias_len (desc->sdp); i++) {
const GstSDPMedia *media = gst_sdp_message_get_media (desc->sdp, i);
struct ExpectedRid *expected_rid = &expected_rids[i];
GPtrArray *seen_rid = g_ptr_array_new_with_free_func (g_free);
int j;
for (j = 0; j < gst_sdp_media_attributes_len (media); j++) {
const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, j);
if (g_strcmp0 (attr->key, "rid") == 0) {
const char *p;
char *v;
guint k;
p = attr->value;
/* take up to either space or nul-terminator */
while (p && *p && *p == ' ')
p++;
v = (char *) p;
/* take up to either space or nul-terminator */
while (p && *p && *p != ' ')
p++;
g_assert (v != p);
v = g_strndup (v, p - v);
GST_INFO ("rid = %s", v);
fail_unless (FALSE == gst_g_ptr_array_find_str (seen_rid, v, NULL),
"duplicate/multiple rid for media %u", i);
for (k = 0; k < expected_rid->n_rid; k++) {
GST_LOG ("expected %u = %s", k, expected_rid->rid[k]);
if (g_strcmp0 (v, expected_rid->rid[k]) == 0)
break;
}
fail_unless (k < expected_rid->n_rid, "rid %s not found in media %u",
v, i);
g_ptr_array_add (seen_rid, v);
}
}
fail_unless (seen_rid->len == expected_rid->n_rid,
"mismatch in number of rid's in media %u, seen %u, expected %u", i,
seen_rid->len, expected_rid->n_rid);
g_ptr_array_unref (seen_rid);
}
}
GST_START_TEST (test_simulcast)
{
struct test_webrtc *t = test_webrtc_new ();
guint media_format_count[] = { 1, };
VAL_SDP_INIT (media_formats, on_sdp_media_count_formats,
media_format_count, NULL);
VAL_SDP_INIT (payloads, on_sdp_media_no_duplicate_payloads, NULL,
&media_formats);
const char *expected_rids0[] = { "a", "z" };
struct ExpectedRid expected_rids = { G_N_ELEMENTS (expected_rids0),
expected_rids0
};
VAL_SDP_INIT (rids, on_sdp_media_rid, &expected_rids, &payloads);
VAL_SDP_INIT (non_reject, _count_non_rejected_media,
GUINT_TO_POINTER (1), &rids);
VAL_SDP_INIT (count, _count_num_sdp_media, GUINT_TO_POINTER (1), &non_reject);
const gchar *expected_offer_setup[] = { "actpass", };
VAL_SDP_INIT (offer_setup, on_sdp_media_setup, expected_offer_setup, &count);
const gchar *expected_answer_setup[] = { "active", };
VAL_SDP_INIT (answer_setup, on_sdp_media_setup, expected_answer_setup,
&count);
const gchar *expected_offer_direction[] = { "sendrecv", };
VAL_SDP_INIT (offer, on_sdp_media_direction, expected_offer_direction,
&offer_setup);
const gchar *expected_answer_direction[] = { "recvonly", };
VAL_SDP_INIT (answer, on_sdp_media_direction, expected_answer_direction,
&answer_setup);
GstHarness *h;
GList *sink_harnesses = NULL;
GObject *trans;
guint i;
GstElement *rtpbin2;
GstBuffer *buf;
guint mid_ext_id = 1;
guint stream_ext_id = 2;
guint repaired_ext_id = 3;
const char *mid = "5";
guint ssrcs[] = { 123456789, 987654321 };
GArray *ssrcs_received;
GstCaps *caps;
t->on_negotiation_needed = NULL;
t->on_ice_candidate = NULL;
t->on_pad_added = _pad_added_harness;
t->pad_added_data = &sink_harnesses;
gst_util_set_object_arg (G_OBJECT (t->webrtc1), "bundle-policy",
"max-bundle");
gst_util_set_object_arg (G_OBJECT (t->webrtc2), "bundle-policy",
"max-bundle");
rtpbin2 = gst_bin_get_by_name (GST_BIN (t->webrtc2), "rtpbin");
fail_unless (rtpbin2 != NULL);
g_signal_connect (rtpbin2, "new-jitterbuffer",
G_CALLBACK (new_jitterbuffer_set_fast_start), NULL);
g_object_unref (rtpbin2);
h = gst_harness_new_with_element (t->webrtc1, "sink_0", NULL);
add_simulcast_audio_test_src_harness (h, expected_rids.n_rid, ssrcs, mid,
mid_ext_id, expected_rids.rid, stream_ext_id, repaired_ext_id);
t->harnesses = g_list_prepend (t->harnesses, h);
/* setup recvonly transceiver as answer */
caps =
create_simulcast_audio_caps
(GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY, expected_rids.n_rid,
ssrcs, mid, mid_ext_id, expected_rids.rid, stream_ext_id,
repaired_ext_id);
g_signal_emit_by_name (t->webrtc2, "add-transceiver",
GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY, caps, &trans);
gst_clear_caps (&caps);
fail_unless (trans != NULL);
g_clear_object (&trans);
test_validate_sdp (t, &offer, &answer);
fail_if (gst_element_set_state (t->webrtc1,
GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE);
fail_if (gst_element_set_state (t->webrtc2,
GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE);
for (i = 0; i < 10; i++) {
gst_harness_push_from_src (h);
}
ssrcs_received = g_array_new (FALSE, TRUE, sizeof (guint32));
/* Get one buffer out for each ssrc sent.
*/
g_mutex_lock (&t->lock);
while (ssrcs_received->len < G_N_ELEMENTS (ssrcs)) {
GList *l;
guint i;
gst_harness_push_from_src (h);
if (g_list_length (sink_harnesses) < 2) {
g_cond_wait_until (&t->cond, &t->lock, g_get_monotonic_time () + 5000);
if (g_list_length (sink_harnesses) < 2)
continue;
}
for (l = sink_harnesses; l; l = l->next) {
GstHarness *sink_harness = (GstHarness *) l->data;
GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
GstWebRTCRTPTransceiver *rtp_trans;
char *trans_mid;
GstPad *srcpad;
guint ssrc;
fail_unless (sink_harness->element == t->webrtc2);
buf = gst_harness_try_pull (sink_harness);
if (!buf)
continue;
/* ensure that the resulting pad has the correct mid set */
srcpad = gst_pad_get_peer (sink_harness->sinkpad);
fail_unless (srcpad != NULL);
g_object_get (srcpad, "transceiver", &rtp_trans, NULL);
gst_clear_object (&srcpad);
fail_unless (rtp_trans);
g_object_get (rtp_trans, "mid", &trans_mid, NULL);
gst_clear_object (&rtp_trans);
fail_unless (trans_mid != NULL);
fail_unless_equals_string (trans_mid, mid);
g_clear_pointer (&trans_mid, g_free);
fail_unless (gst_rtp_buffer_map (buf, GST_MAP_READ, &rtp));
ssrc = gst_rtp_buffer_get_ssrc (&rtp);
for (i = 0; i < ssrcs_received->len; i++) {
if (g_array_index (ssrcs_received, guint, i) == ssrc)
break;
}
if (i == ssrcs_received->len) {
g_array_append_val (ssrcs_received, ssrc);
}
gst_rtp_buffer_unmap (&rtp);
gst_buffer_unref (buf);
}
}
g_mutex_unlock (&t->lock);
test_webrtc_free (t);
g_list_free (sink_harnesses);
g_array_unref (ssrcs_received);
}
GST_END_TEST;
static Suite *
webrtcbin_suite (void)
{
@ -4784,6 +5161,7 @@ webrtcbin_suite (void)
tcase_add_test (tc, test_renego_rtx);
tcase_add_test (tc, test_bundle_mid_header_extension);
tcase_add_test (tc, test_max_bundle_fec);
tcase_add_test (tc, test_simulcast);
if (sctpenc && sctpdec) {
tcase_add_test (tc, test_data_channel_create);
tcase_add_test (tc, test_data_channel_remote_notify);