mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-06-04 22:48:54 +00:00
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:
parent
75b23d646a
commit
2377f8b3f2
7 changed files with 912 additions and 108 deletions
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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__ */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue