#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
}
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));
}
}
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
#if 0
static gboolean
-match_for_ssrc (GstWebRTCBinPad * pad, guint * ssrc)
+match_for_pad (GstWebRTCBinPad * pad, GstWebRTCBinPad * other)
{
- return pad->ssrc == *ssrc;
+ return pad == other;
}
+#endif
+
+struct SsrcMatch
+{
+ GstWebRTCRTPTransceiverDirection direction;
+ guint32 ssrc;
+};
static gboolean
-match_for_pad (GstWebRTCBinPad * pad, GstWebRTCBinPad * other)
+mid_ssrc_match_for_ssrc (SsrcMapItem * entry, const struct SsrcMatch *match)
{
- return pad == other;
+ 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);
+ }
}
-#endif
static gboolean
_unlock_pc_thread (GMutex * lock)
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");
if (caps) {
if (trans)
- gst_caps_replace (&trans->last_configured_caps, caps);
+ gst_caps_replace (&trans->last_retrieved_caps, caps);
ret = caps;
}
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:
}
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)
{
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_trans = _find_transceiver (webrtc, GUINT_TO_POINTER (ssrc),
- match_ssrc);
+ rtp_session =
+ GPOINTER_TO_UINT (g_object_get_data (internal_session,
+ "GstWebRTCBinRTPSessionID"));
+
+ 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;
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);
}
}
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);
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,
/* 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);
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)
{
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);
/* 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) {
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);
}
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) {
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)) {
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;
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;
}
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
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
{
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
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
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
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
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 "
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);
}
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,
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;
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)
{
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);