feat: WHIP support
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
//! 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<_>>()
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user