Files
simple-instant-stream/crates/server/tests/whip_sdp_probe.rs
T
2026-08-07 11:48:31 +01:00

119 lines
4.4 KiB
Rust

//! Temporary probe: replicate handle_whip_injest's Rtc setup, feed it a
//! realistic OBS/libdatachannel WHIP offer, print the answer SDP and check
//! whether the string-fixups in webrtc_ingest.rs actually match.
use std::{net::SocketAddr, time::Instant};
use str0m::{change::SdpOffer, net::Protocol, Candidate, Rtc};
fn obs_like_offer() -> String {
let mut s = String::new();
s.push_str("v=0\r\n");
s.push_str("o=- 4527835755568137757 2 IN IP4 127.0.0.1\r\n");
s.push_str("s=-\r\n");
s.push_str("t=0 0\r\n");
s.push_str("a=group:BUNDLE 0 1\r\n");
s.push_str("a=msid-semantic: WMS *\r\n");
s.push_str("m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n");
s.push_str("c=IN IP4 0.0.0.0\r\n");
s.push_str("a=rtcp:9 IN IP4 0.0.0.0\r\n");
s.push_str("a=ice-ufrag:obs_ufrag_audio\r\n");
s.push_str("a=ice-pwd:obs_pwd_audio\r\n");
s.push_str("a=ice-options:trickle\r\n");
s.push_str("a=fingerprint:sha-256 5A:6B:7C:8D:9E:AF:B0:C1:D2:E3:F4:05:16:27:38:49:5A:6B:7C:8D:9E:AF:B0:C1:D2:E3:F4:05:16:27:38:49:5A\r\n");
s.push_str("a=setup:actpass\r\n");
s.push_str("a=mid:0\r\n");
s.push_str("a=sendrecv\r\n");
s.push_str("a=rtcp-mux\r\n");
s.push_str("a=rtpmap:111 opus/48000/2\r\n");
s.push_str("a=rtcp-fb:111 transport-cc\r\n");
s.push_str("a=fmtp:111 minptime=10;useinbandfec=1\r\n");
s.push_str("m=video 9 UDP/TLS/RTP/SAVPF 96\r\n");
s.push_str("c=IN IP4 0.0.0.0\r\n");
s.push_str("a=rtcp:9 IN IP4 0.0.0.0\r\n");
s.push_str("a=ice-ufrag:obs_ufrag_video\r\n");
s.push_str("a=ice-pwd:obs_pwd_video\r\n");
s.push_str("a=ice-options:trickle\r\n");
s.push_str("a=fingerprint:sha-256 5A:6B:7C:8D:9E:AF:B0:C1:D2:E3:F4:05:16:27:38:49:5A:6B:7C:8D:9E:AF:B0:C1:D2:E3:F4:05:16:27:38:49:5A\r\n");
s.push_str("a=setup:actpass\r\n");
s.push_str("a=mid:1\r\n");
s.push_str("a=sendrecv\r\n");
s.push_str("a=rtcp-mux\r\n");
s.push_str("a=rtpmap:96 H264/90000\r\n");
s.push_str("a=rtcp-fb:96 nack\r\n");
s.push_str("a=rtcp-fb:96 nack pli\r\n");
s.push_str("a=rtcp-fb:96 transport-cc\r\n");
s.push_str(
"a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r\n",
);
s
}
#[test]
fn probe_whip_answer() {
let offer_sdp = obs_like_offer();
let sdp_offer = SdpOffer::from_sdp_string(&offer_sdp).expect("parse offer");
// Mirror handle_whip_injest's builder setup.
let mut builder = Rtc::builder();
{
let cc = builder.codec_config();
cc.clear();
cc.enable_opus(true);
cc.enable_h264(true);
}
let mut rtc = builder.build(Instant::now());
let public_addr: SocketAddr = "203.0.113.7:6969".parse().unwrap();
let candidate = Candidate::host(public_addr, Protocol::Udp).unwrap();
rtc.add_local_candidate(candidate);
let answer = rtc.sdp_api().accept_offer(sdp_offer).expect("accept offer");
let answer_sdp = answer.to_sdp_string();
println!("=== RAW ANSWER ===");
println!("{answer_sdp}");
println!("=== END RAW ANSWER ===");
// Now replicate the fixups from webrtc_ingest.rs verbatim.
let answer_sdp = answer_sdp
.replace("a=ice-options:trickle\r\n", "")
.replace("a=ice-options:trickle\n", "");
let answer_sdp = answer_sdp
.replace("a=group:BUNDLE 0 1\r\n", "")
.replace("a=group:BUNDLE 0 1\n", "");
let answer_sdp =
if let Some(cand_line) = answer_sdp.lines().find(|l| l.starts_with("a=candidate:")) {
let cand_replacement = format!(
"m=video 9 UDP/TLS/RTP/SAVPF 96\r\nc=IN IP4 127.0.0.1\r\n{}\r\n",
cand_line
);
answer_sdp.replace(
"m=video 9 UDP/TLS/RTP/SAVPF 96\r\nc=IN IP4 0.0.0.0\r\n",
&cand_replacement,
)
} else {
answer_sdp
};
println!("=== FIXED ANSWER ===");
println!("{answer_sdp}");
println!("=== END FIXED ANSWER ===");
// Diagnostics
let video_mline = answer_sdp
.lines()
.find(|l| l.starts_with("m=video"))
.unwrap();
println!("video m-line after fixup: {video_mline}");
println!(
"has a=candidate after fixup: {}",
answer_sdp.lines().any(|l| l.starts_with("a=candidate:"))
);
println!(
"a=candidate lines: {:?}",
answer_sdp
.lines()
.filter(|l| l.starts_with("a=candidate:"))
.collect::<Vec<_>>()
);
}