Skip to content

Commit

Permalink
rtp_sender: Create outgoing rtx streams
Browse files Browse the repository at this point in the history
  • Loading branch information
anders-avos committed Aug 26, 2024
1 parent 0f28085 commit d75c624
Show file tree
Hide file tree
Showing 9 changed files with 492 additions and 8 deletions.
1 change: 1 addition & 0 deletions webrtc/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ impl API {
Arc::clone(&self.media_engine),
interceptor,
false,
self.setting_engine.enable_sender_rtx,
)
.await
}
Expand Down
8 changes: 8 additions & 0 deletions webrtc/src/api/setting_engine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ pub struct SettingEngine {
pub(crate) srtp_protection_profiles: Vec<SrtpProtectionProfile>,
pub(crate) receive_mtu: usize,
pub(crate) mid_generator: Option<Arc<dyn Fn(isize) -> String + Send + Sync>>,
pub(crate) enable_sender_rtx: bool,
}

impl SettingEngine {
Expand Down Expand Up @@ -324,4 +325,11 @@ impl SettingEngine {
pub fn set_mid_generator(&mut self, f: impl Fn(isize) -> String + Send + Sync + 'static) {
self.mid_generator = Some(Arc::new(f));
}

/// enable_sender_rtx allows outgoing rtx streams to be created where applicable.
/// RTPSender will create an RTP retransmission stream for each source stream where a retransmission
/// codec is configured.
pub fn enable_sender_rtx(&mut self, is_enabled: bool) {
self.enable_sender_rtx = is_enabled;
}
}
2 changes: 2 additions & 0 deletions webrtc/src/peer_connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1404,6 +1404,7 @@ impl RTCPeerConnection {
};

let receive_mtu = self.internal.setting_engine.get_receive_mtu();
let enable_sender_rtx = self.internal.setting_engine.enable_sender_rtx;

let receiver = Arc::new(RTCRtpReceiver::new(
receive_mtu,
Expand All @@ -1422,6 +1423,7 @@ impl RTCPeerConnection {
Arc::clone(&self.internal.media_engine),
Arc::clone(&self.interceptor),
false,
enable_sender_rtx,
)
.await,
);
Expand Down
16 changes: 14 additions & 2 deletions webrtc/src/peer_connection/peer_connection_internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,7 @@ impl PeerConnectionInternal {
Arc::clone(&self.media_engine),
interceptor,
false,
self.setting_engine.enable_sender_rtx,
)
.await,
);
Expand Down Expand Up @@ -589,6 +590,7 @@ impl PeerConnectionInternal {
Arc::clone(&self.media_engine),
Arc::clone(&interceptor),
false,
self.setting_engine.enable_sender_rtx,
)
.await,
);
Expand Down Expand Up @@ -1378,20 +1380,30 @@ impl PeerConnectionInternal {
let sender = transceiver.sender().await;
let track_encodings = sender.track_encodings.lock().await;
for encoding in track_encodings.iter() {
let track_id = encoding.track.id().to_string();
let track_id = encoding.track.id();
let kind = match encoding.track.kind() {
RTPCodecType::Unspecified => continue,
RTPCodecType::Audio => "audio",
RTPCodecType::Video => "video",
};

track_infos.push(TrackInfo {
track_id,
track_id: track_id.to_owned(),
ssrc: encoding.ssrc,
mid: mid.to_owned(),
rid: encoding.track.rid().map(Into::into),
kind,
});

if let Some(rtx) = &encoding.rtx {
track_infos.push(TrackInfo {
track_id: track_id.to_owned(),
ssrc: rtx.ssrc,
mid: mid.to_owned(),
rid: encoding.track.rid().map(Into::into),
kind,
});
}
}
}

Expand Down
17 changes: 17 additions & 0 deletions webrtc/src/peer_connection/sdp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,23 @@ pub(crate) async fn add_transceiver_sdp(
track.stream_id().to_owned(), /* streamLabel */
track.id().to_owned(),
);

if encoding.rtx.ssrc != 0 {
media = media.with_media_source(
encoding.rtx.ssrc,
track.stream_id().to_owned(),
track.stream_id().to_owned(),
track.id().to_owned(),
);

media = media.with_value_attribute(
ATTR_KEY_SSRCGROUP.to_owned(),
format!(
"{} {} {}",
SEMANTIC_TOKEN_FLOW_IDENTIFICATION, encoding.ssrc, encoding.rtx.ssrc
),
);
}
}

if send_parameters.encodings.len() > 1 {
Expand Down
155 changes: 155 additions & 0 deletions webrtc/src/peer_connection/sdp/sdp_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,7 @@ async fn test_media_description_fingerprints() -> Result<()> {
Arc::clone(&api.media_engine),
Arc::clone(&interceptor),
false,
false,
)
.await,
))
Expand Down Expand Up @@ -1140,6 +1141,160 @@ async fn test_populate_sdp() -> Result<()> {
assert_eq!(offer_sdp.attribute(ATTR_KEY_GROUP), None);
}

// "Sender RTX"
{
let mut se = SettingEngine::default();
se.enable_sender_rtx(true);

let mut me = MediaEngine::default();
me.register_default_codecs()?;

me.register_codec(
RTCRtpCodecParameters {
capability: RTCRtpCodecCapability {
mime_type: "video/rtx".to_owned(),
clock_rate: 90000,
channels: 0,
sdp_fmtp_line: "apt=96".to_string(),
rtcp_feedback: vec![],
},
payload_type: 97,
..Default::default()
},
RTPCodecType::Video,
)?;

me.push_codecs(me.video_codecs.clone(), RTPCodecType::Video)
.await;

let api = APIBuilder::new()
.with_media_engine(me)
.with_setting_engine(se.clone())
.build();
let interceptor = api.interceptor_registry.build("")?;
let transport = Arc::new(RTCDtlsTransport::default());
let receiver = Arc::new(api.new_rtp_receiver(
RTPCodecType::Video,
Arc::clone(&transport),
Arc::clone(&interceptor),
));

let codec = RTCRtpCodecCapability {
mime_type: "video/vp8".to_owned(),
..Default::default()
};

let track = Arc::new(TrackLocalStaticSample::new_with_rid(
codec.clone(),
"video".to_owned(),
"f".to_owned(),
"webrtc-rs".to_owned(),
));

let sender = Arc::new(
api.new_rtp_sender(
Some(track),
Arc::clone(&transport),
Arc::clone(&interceptor),
)
.await,
);

sender
.add_encoding(Arc::new(TrackLocalStaticSample::new_with_rid(
codec.clone(),
"video".to_owned(),
"h".to_owned(),
"webrtc-rs".to_owned(),
)))
.await?;

let tr = RTCRtpTransceiver::new(
receiver,
sender,
RTCRtpTransceiverDirection::Sendonly,
RTPCodecType::Video,
api.media_engine.video_codecs.clone(),
Arc::clone(&api.media_engine),
None,
)
.await;

let media_sections = vec![MediaSection {
id: "video".to_owned(),
transceivers: vec![tr],
data: false,
..Default::default()
}];

let d = SessionDescription::default();

let params = PopulateSdpParams {
media_description_fingerprint: se.sdp_media_level_fingerprints,
is_icelite: se.candidates.ice_lite,
connection_role: DEFAULT_DTLS_ROLE_OFFER.to_connection_role(),
ice_gathering_state: RTCIceGatheringState::Complete,
match_bundle_group: None,
};
let offer_sdp = populate_sdp(
d,
&[],
&api.media_engine,
&[],
&RTCIceParameters::default(),
&media_sections,
params,
)
.await?;

// Test codecs and FID groups
let mut found_vp8 = false;
let mut found_rtx = false;
let mut found_ssrcs: Vec<&str> = Vec::new();
let mut found_fids = Vec::new();
for desc in &offer_sdp.media_descriptions {
if desc.media_name.media != "video" {
continue;
}
for a in &desc.attributes {
if a.key.contains("rtpmap") {
if let Some(value) = &a.value {
if value == "96 VP8/90000" {
found_vp8 = true;
} else if value == "97 rtx/90000" {
found_rtx = true;
}
}
} else if a.key == "ssrc" {
if let Some((ssrc, _)) = a.value.as_ref().and_then(|v| v.split_once(' ')) {
found_ssrcs.push(ssrc);
}
} else if a.key == "ssrc-group" {
if let Some(group) = a.value.as_ref().and_then(|v| v.strip_prefix("FID ")) {
let Some((a, b)) = group.split_once(" ") else {
panic!("invalid FID format in sdp")
};

found_fids.extend([a, b]);
}
}
}
}

found_fids.sort();

found_ssrcs.sort();
// the sdp may have multiple attributes for each ssrc
found_ssrcs.dedup();

assert!(found_vp8, "vp8 should be present in sdp");
assert!(found_rtx, "rtx should be present in sdp");
assert_eq!(found_ssrcs.len(), 4, "all ssrcs should be present in sdp");
assert_eq!(found_fids.len(), 4, "all fids should be present in sdp");

assert_eq!(found_ssrcs, found_fids);
}

Ok(())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::peer_connection::peer_connection_test::{
close_pair_now, create_vnet_pair, signal_pair, until_connection_state,
};
use crate::rtp_transceiver::rtp_codec::RTCRtpHeaderExtensionParameters;
use crate::rtp_transceiver::RTCPFeedback;
use crate::rtp_transceiver::{RTCPFeedback, RTCRtpCodecCapability};
use crate::track::track_local::track_local_static_sample::TrackLocalStaticSample;
use crate::track::track_local::TrackLocal;

Expand Down
Loading

0 comments on commit d75c624

Please sign in to comment.