Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Match group bundle from offer #568

Merged
merged 1 commit into from
May 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions webrtc/src/peer_connection/peer_connection_internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,7 @@ impl PeerConnectionInternal {
is_icelite: self.setting_engine.candidates.ice_lite,
connection_role: DEFAULT_DTLS_ROLE_OFFER.to_connection_role(),
ice_gathering_state: self.ice_gathering_state(),
match_bundle_group: None,
};
populate_sdp(
d,
Expand Down Expand Up @@ -780,7 +781,7 @@ impl PeerConnectionInternal {
}

// If we are offering also include unmatched local transceivers
if include_unmatched {
let match_bundle_group = if include_unmatched {
for t in &local_transceivers {
t.sender().await.set_negotiated();
media_sections.push(MediaSection {
Expand All @@ -803,7 +804,14 @@ impl PeerConnectionInternal {
..Default::default()
});
}
}
None
} else {
remote_description
.as_ref()
.and_then(|d| d.parsed.as_ref())
.and_then(|d| d.attribute(ATTR_KEY_GROUP))
.map(ToOwned::to_owned)
};

let dtls_fingerprints = if let Some(cert) = self.dtls_transport.certificates.first() {
cert.get_fingerprints()
Expand All @@ -816,6 +824,7 @@ impl PeerConnectionInternal {
is_icelite: self.setting_engine.candidates.ice_lite,
connection_role,
ice_gathering_state: self.ice_gathering_state(),
match_bundle_group,
};
populate_sdp(
d,
Expand Down
23 changes: 21 additions & 2 deletions webrtc/src/peer_connection/sdp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,13 @@ impl TryFrom<&String> for SimulcastRid {
}
}

fn bundle_match(bundle: Option<&String>, id: &str) -> bool {
match bundle {
None => true,
Some(b) => b.split_whitespace().any(|s| s == id),
}
}

#[derive(Default)]
pub(crate) struct MediaSection {
pub(crate) id: String,
Expand All @@ -752,6 +759,7 @@ pub(crate) struct PopulateSdpParams {
pub(crate) is_icelite: bool,
pub(crate) connection_role: ConnectionRole,
pub(crate) ice_gathering_state: RTCIceGatheringState,
pub(crate) match_bundle_group: Option<String>,
}

/// populate_sdp serializes a PeerConnections state into an SDP
Expand Down Expand Up @@ -819,7 +827,14 @@ pub(crate) async fn populate_sdp(
};

if should_add_id {
append_bundle(&m.id, &mut bundle_value, &mut bundle_count);
if bundle_match(params.match_bundle_group.as_ref(), &m.id) {
append_bundle(&m.id, &mut bundle_value, &mut bundle_count);
} else if let Some(desc) = d.media_descriptions.last_mut() {
desc.media_name.port = RangedPort {
value: 0,
range: None,
}
}
}
}

Expand All @@ -837,7 +852,11 @@ pub(crate) async fn populate_sdp(
d = d.with_value_attribute(ATTR_KEY_ICELITE.to_owned(), ATTR_KEY_ICELITE.to_owned());
}

Ok(d.with_value_attribute(ATTR_KEY_GROUP.to_owned(), bundle_value))
if bundle_count > 0 {
d = d.with_value_attribute(ATTR_KEY_GROUP.to_owned(), bundle_value);
}

Ok(d)
}

pub(crate) fn get_mid_value(media: &MediaDescription) -> Option<&String> {
Expand Down
226 changes: 226 additions & 0 deletions webrtc/src/peer_connection/sdp/sdp_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ async fn fingerprint_test(
is_icelite: false,
connection_role: ConnectionRole::Active,
ice_gathering_state: RTCIceGatheringState::New,
match_bundle_group: None,
};

let s = populate_sdp(
Expand Down Expand Up @@ -732,6 +733,7 @@ async fn test_populate_sdp() -> Result<()> {
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,
Expand Down Expand Up @@ -836,6 +838,7 @@ async fn test_populate_sdp() -> Result<()> {
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,
Expand Down Expand Up @@ -869,6 +872,228 @@ async fn test_populate_sdp() -> Result<()> {
assert!(found_vp8, "vp8 should be present in sdp");
}

//"Bundle all"
{
let se = SettingEngine::default();
let mut me = MediaEngine::default();
me.register_default_codecs()?;

let api = APIBuilder::new().with_media_engine(me).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 sender = Arc::new(
api.new_rtp_sender(None, Arc::clone(&transport), Arc::clone(&interceptor))
.await,
);

let tr = RTCRtpTransceiver::new(
receiver,
sender,
RTCRtpTransceiverDirection::Recvonly,
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,
rid_map: vec![],
..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?;

assert_eq!(
offer_sdp.attribute(ATTR_KEY_GROUP),
Some(&"BUNDLE video".to_owned())
);
}

//"Bundle matched"
{
let se = SettingEngine::default();
let mut me = MediaEngine::default();
me.register_default_codecs()?;

let api = APIBuilder::new().with_media_engine(me).build();
let interceptor = api.interceptor_registry.build("")?;
let transport = Arc::new(RTCDtlsTransport::default());

let video_receiver = Arc::new(api.new_rtp_receiver(
RTPCodecType::Video,
Arc::clone(&transport),
Arc::clone(&interceptor),
));
let audio_receiver = Arc::new(api.new_rtp_receiver(
RTPCodecType::Audio,
Arc::clone(&transport),
Arc::clone(&interceptor),
));

let video_sender = Arc::new(
api.new_rtp_sender(None, Arc::clone(&transport), Arc::clone(&interceptor))
.await,
);
let audio_sender = Arc::new(
api.new_rtp_sender(None, Arc::clone(&transport), Arc::clone(&interceptor))
.await,
);

let trv = RTCRtpTransceiver::new(
video_receiver,
video_sender,
RTCRtpTransceiverDirection::Recvonly,
RTPCodecType::Video,
api.media_engine.video_codecs.clone(),
Arc::clone(&api.media_engine),
None,
)
.await;

let tra = RTCRtpTransceiver::new(
audio_receiver,
audio_sender,
RTCRtpTransceiverDirection::Recvonly,
RTPCodecType::Audio,
api.media_engine.audio_codecs.clone(),
Arc::clone(&api.media_engine),
None,
)
.await;

let media_sections = vec![
MediaSection {
id: "video".to_owned(),
transceivers: vec![trv],
data: false,
rid_map: vec![],
..Default::default()
},
MediaSection {
id: "audio".to_owned(),
transceivers: vec![tra],
data: false,
rid_map: vec![],
..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: Some("audio".to_owned()),
};
let offer_sdp = populate_sdp(
d,
&[],
&api.media_engine,
&[],
&RTCIceParameters::default(),
&media_sections,
params,
)
.await?;

assert_eq!(
offer_sdp.attribute(ATTR_KEY_GROUP),
Some(&"BUNDLE audio".to_owned())
);
}

//"empty bundle group"
{
let se = SettingEngine::default();
let mut me = MediaEngine::default();
me.register_default_codecs()?;

let api = APIBuilder::new().with_media_engine(me).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 sender = Arc::new(
api.new_rtp_sender(None, Arc::clone(&transport), Arc::clone(&interceptor))
.await,
);

let tr = RTCRtpTransceiver::new(
receiver,
sender,
RTCRtpTransceiverDirection::Recvonly,
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,
rid_map: vec![],
..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: Some("".to_owned()),
};
let offer_sdp = populate_sdp(
d,
&[],
&api.media_engine,
&[],
&RTCIceParameters::default(),
&media_sections,
params,
)
.await?;

assert_eq!(offer_sdp.attribute(ATTR_KEY_GROUP), None);
}

Ok(())
}

Expand Down Expand Up @@ -962,6 +1187,7 @@ async fn test_populate_sdp_reject() -> Result<()> {
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,
Expand Down
Loading