use rtc::peer_connection::RTCPeerConnectionBuilder;
use rtc::peer_connection::configuration::media_engine::{
MIME_TYPE_FLEX_FEC03, MIME_TYPE_RTX, MIME_TYPE_VP8, MIME_TYPE_VP9, MediaEngine,
};
use rtc::rtp_transceiver::rtp_sender::{RTCRtpCodec, RTCRtpCodecParameters, RtpCodecKind};
use std::time::Instant;
const VP9_FMTP: &str = "profile-id=0";
const FEC_FMTP: &str = "repair-window=10000000";
fn codec(mime: &str, payload_type: u8, fmtp: &str) -> RTCRtpCodecParameters {
RTCRtpCodecParameters {
rtp_codec: RTCRtpCodec {
mime_type: mime.to_owned(),
clock_rate: 90000,
channels: 0,
sdp_fmtp_line: fmtp.to_owned(),
rtcp_feedback: vec![],
},
payload_type,
}
}
fn video_engine(codecs: &[RTCRtpCodecParameters]) -> MediaEngine {
let mut media_engine = MediaEngine::default();
for c in codecs {
media_engine
.register_codec(c.clone(), RtpCodecKind::Video)
.expect("register codec");
}
media_engine
}
fn codec_set(vp8: u8, vp8_rtx: u8, vp9: u8, vp9_rtx: u8, fec: u8) -> Vec<RTCRtpCodecParameters> {
vec![
codec(MIME_TYPE_VP8, vp8, ""),
codec(MIME_TYPE_RTX, vp8_rtx, &format!("apt={vp8}")),
codec(MIME_TYPE_VP9, vp9, VP9_FMTP),
codec(MIME_TYPE_RTX, vp9_rtx, &format!("apt={vp9}")),
codec(MIME_TYPE_FLEX_FEC03, fec, FEC_FMTP),
]
}
fn answer_formats() -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut offerer = RTCPeerConnectionBuilder::new()
.with_media_engine(video_engine(&codec_set(100, 101, 98, 99, 35)))
.build(Instant::now())?;
let mut answerer = RTCPeerConnectionBuilder::new()
.with_media_engine(video_engine(&codec_set(96, 97, 94, 95, 45)))
.build(Instant::now())?;
offerer.add_transceiver_from_kind(RtpCodecKind::Video, None)?;
let offer = offerer.create_offer(None)?;
offerer.set_local_description(Instant::now(), offer.clone())?;
answerer.set_remote_description(Instant::now(), offer)?;
let answer = answerer.create_answer(None)?;
Ok(video_formats(&answer.sdp))
}
fn video_formats(sdp: &str) -> Vec<u8> {
sdp.lines()
.find(|l| l.starts_with("m=video "))
.expect("answer should carry a video media section")
.split_whitespace()
.skip(3) .map(|f| f.parse().expect("payload type should be numeric"))
.collect()
}
fn index_of(formats: &[u8], payload_type: u8) -> usize {
formats
.iter()
.position(|pt| *pt == payload_type)
.unwrap_or_else(|| panic!("payload type {payload_type} missing from {formats:?}"))
}
#[test]
fn test_answer_rtx_order_follows_primaries() -> Result<(), Box<dyn std::error::Error>> {
let formats = answer_formats()?;
assert!(
index_of(&formats, 100) < index_of(&formats, 98),
"answer should keep the offer's primary order, got {formats:?}",
);
assert!(
index_of(&formats, 101) < index_of(&formats, 99),
"rtx-for-VP8 (101) should precede rtx-for-VP9 (99), matching their primaries; \
got {formats:?}",
);
assert_eq!(
formats.iter().filter(|pt| **pt == 35).count(),
1,
"FlexFEC (35) should appear exactly once, got {formats:?}",
);
Ok(())
}
#[test]
fn test_answer_codec_order_is_stable_across_answers() -> Result<(), Box<dyn std::error::Error>> {
let first = answer_formats()?;
for round in 2..=16 {
assert_eq!(
answer_formats()?,
first,
"answer codec order changed on round {round}; the same offer must always produce \
the same answer",
);
}
Ok(())
}