Skip to content
File

Blob: firmware/vendor/str0m/tests/comfort-noise.rs

rust355 lines
1use std::net::Ipv4Addr;
2use std::time::{Duration, Instant};
3 
4mod common;
5use common::{TestRtc, init_crypto_default, init_log, negotiate, progress};
6use str0m::Rtc;
7use str0m::format::{Codec, CodecSpec, FormatParams, PayloadParams};
8use str0m::media::{Direction, Frequency, MediaKind, MediaTime, Mid};
9use str0m::rtp::RtpWrite;
10use str0m::{Event, RtcError};
11use tracing::{Span, info_span};
12 
13#[test]
14fn negotiates_all_supported_clock_rates() {
15 init_log();
16 init_crypto_default();
17 
18 let params = comfort_noise_params();
19 let (l, r) = with_params(info_span!("L"), &params, info_span!("R"), &params);
20 
21 for rtc in [&l, &r] {
22 let negotiated: Vec<_> = rtc
23 .codec_config()
24 .iter()
25 .filter(|p| p.spec().codec == Codec::CN)
26 .map(|p| (*p.pt(), p.spec().clock_rate.get()))
27 .collect();
28 assert_eq!(
29 negotiated,
30 vec![
31 (13, 8_000),
32 (96, 16_000),
33 (97, 24_000),
34 (98, 32_000),
35 (99, 48_000),
36 ]
37 );
38 }
39}
40 
41#[test]
42fn frame_mode_round_trips_all_supported_clock_rates() -> Result<(), RtcError> {
43 init_log();
44 init_crypto_default();
45 
46 let params = comfort_noise_params();
47 let (mut l, mut r, mid) = connected_with_params(&params, false);
48 
49 for (index, param) in params.iter().enumerate() {
50 let wallclock = l.start + l.duration();
51 let time = MediaTime::new(
52 param.spec().clock_rate.get() as u64,
53 param.spec().clock_rate,
54 );
55 l.writer(mid)
56 .unwrap()
57 .write(param.pt(), wallclock, time, vec![index as u8 + 1])?;
58 progress(&mut l, &mut r)?;
59 }
60 
61 progress_until_cn_events(&mut l, &mut r, false)?;
62 
63 let received: Vec<_> = r
64 .events
65 .iter()
66 .filter_map(|(_, event)| match event {
67 Event::MediaData(data) if data.params.spec().codec == Codec::CN => Some((
68 *data.pt,
69 data.params.spec().clock_rate.get(),
70 data.data.as_ref().to_vec(),
71 )),
72 _ => None,
73 })
74 .collect();
75 
76 assert_eq!(received, expected_cn_events());
77 Ok(())
78}
79 
80#[test]
81fn frame_mode_switching_through_cn_does_not_stall_primary_audio() -> Result<(), RtcError> {
82 init_log();
83 init_crypto_default();
84 
85 let params = [pcmu(), comfort_noise(13, 8_000)];
86 let (mut l, mut r, mid) = connected_with_default_audio_reordering(&params);
87 
88 for (pt, timestamp, payload) in [(0_u8, 0_u64, 1_u8), (13, 160, 42), (0, 320, 2)] {
89 let wallclock = l.start + l.duration();
90 l.writer(mid).unwrap().write(
91 pt.into(),
92 wallclock,
93 MediaTime::new(timestamp, Frequency::EIGHT_KHZ),
94 [payload],
95 )?;
96 progress(&mut l, &mut r)?;
97 }
98 
99 let deadline = l.duration() + Duration::from_secs(2);
100 while l.duration() < deadline {
101 progress(&mut l, &mut r)?;
102 }
103 
104 let received: Vec<_> = r
105 .events
106 .iter()
107 .filter_map(|(_, event)| match event {
108 Event::MediaData(data) => Some((data.params.spec().codec, data.data.as_ref().to_vec())),
109 _ => None,
110 })
111 .collect();
112 assert_eq!(
113 received,
114 vec![
115 (Codec::PCMU, vec![1]),
116 (Codec::CN, vec![42]),
117 (Codec::PCMU, vec![2]),
118 ],
119 "the CN sequence number must not look like a lost PCMU packet"
120 );
121 Ok(())
122}
123 
124#[test]
125fn frame_mode_cn_never_sets_marker_bit() -> Result<(), RtcError> {
126 init_log();
127 init_crypto_default();
128 
129 let params = [comfort_noise(13, 8_000)];
130 let (mut l, mut r, mid) = connected_with_params(&params, false);
131 let wallclock = l.start + l.duration();
132 l.writer(mid).unwrap().start_of_talkspurt(true).write(
133 params[0].pt(),
134 wallclock,
135 MediaTime::new(8_000, Frequency::EIGHT_KHZ),
136 [42],
137 )?;
138 
139 let deadline = l.duration() + Duration::from_secs(2);
140 while l.duration() < deadline {
141 progress(&mut l, &mut r)?;
142 if let Some(data) = r.events.iter().find_map(|(_, event)| match event {
143 Event::MediaData(data) if data.params.spec().codec == Codec::CN => Some(data),
144 _ => None,
145 }) {
146 assert!(
147 !data.audio_start_of_talk_spurt,
148 "an RFC 3389 CN packet must not carry the RTP marker bit"
149 );
150 return Ok(());
151 }
152 }
153 
154 panic!("timed out waiting for Comfort Noise payload");
155}
156 
157#[test]
158fn rtp_mode_round_trips_all_supported_clock_rates() -> Result<(), RtcError> {
159 init_log();
160 init_crypto_default();
161 
162 let params = comfort_noise_params();
163 let (mut l, mut r, mid) = connected_with_params(&params, true);
164 let ssrc = l.direct_api().stream_tx_by_mid(mid, None).unwrap().ssrc();
165 
166 for (index, param) in params.iter().enumerate() {
167 let wallclock = l.start + l.duration();
168 let timestamp = param.spec().clock_rate.get();
169 l.direct_api()
170 .stream_tx(&ssrc)
171 .unwrap()
172 .write_rtp(RtpWrite::new(
173 param.pt(),
174 (index as u64 + 1).into(),
175 timestamp,
176 wallclock,
177 [index as u8 + 1],
178 ));
179 progress(&mut l, &mut r)?;
180 }
181 
182 progress_until_cn_events(&mut l, &mut r, true)?;
183 
184 let received: Vec<_> = r
185 .events
186 .iter()
187 .filter_map(|(_, event)| match event {
188 Event::RtpPacket(packet) => Some((
189 *packet.header.payload_type,
190 packet.time.frequency().get(),
191 packet.payload.as_ref().to_vec(),
192 )),
193 _ => None,
194 })
195 .collect();
196 
197 assert_eq!(received, expected_cn_events());
198 Ok(())
199}
200 
201fn with_params(
202 span_l: Span,
203 params_l: &[PayloadParams],
204 span_r: Span,
205 params_r: &[PayloadParams],
206) -> (TestRtc, TestRtc) {
207 let mut l = build_params_with_mode(span_l, params_l, false);
208 let mut r = build_params_with_mode(span_r, params_r, false);
209 
210 negotiate(&mut l, &mut r, |change| {
211 change.add_media(MediaKind::Audio, Direction::SendRecv, None, None, None);
212 });
213 
214 (l, r)
215}
216 
217fn connected_with_params(params: &[PayloadParams], rtp_mode: bool) -> (TestRtc, TestRtc, Mid) {
218 connected_with_reordering(params, rtp_mode, Some(0))
219}
220 
221fn connected_with_default_audio_reordering(params: &[PayloadParams]) -> (TestRtc, TestRtc, Mid) {
222 connected_with_reordering(params, false, None)
223}
224 
225fn connected_with_reordering(
226 params: &[PayloadParams],
227 rtp_mode: bool,
228 reordering_size_audio: Option<usize>,
229) -> (TestRtc, TestRtc, Mid) {
230 let mut l = build_params(info_span!("L"), params, rtp_mode, reordering_size_audio);
231 let mut r = build_params(info_span!("R"), params, rtp_mode, reordering_size_audio);
232 
233 l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into());
234 r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into());
235 
236 let mid = negotiate(&mut l, &mut r, |change| {
237 change.add_media(MediaKind::Audio, Direction::SendRecv, None, None, None)
238 });
239 
240 while !l.is_connected() || !r.is_connected() {
241 progress(&mut l, &mut r).expect("clean progress");
242 }
243 
244 let max = l.last.max(r.last);
245 l.last = max;
246 r.last = max;
247 
248 (l, r, mid)
249}
250 
251fn build_params_with_mode(span: Span, params: &[PayloadParams], rtp_mode: bool) -> TestRtc {
252 build_params(span, params, rtp_mode, Some(0))
253}
254 
255fn build_params(
256 span: Span,
257 params: &[PayloadParams],
258 rtp_mode: bool,
259 reordering_size_audio: Option<usize>,
260) -> TestRtc {
261 let mut builder = Rtc::builder().clear_codecs().set_rtp_mode(rtp_mode);
262 if let Some(size) = reordering_size_audio {
263 builder = builder.set_reordering_size_audio(size);
264 }
265 let config = builder.codec_config();
266 for param in params {
267 config.add_config(
268 param.pt(),
269 param.resend(),
270 param.spec().codec,
271 param.spec().clock_rate,
272 param.spec().channels,
273 param.spec().format,
274 );
275 }
276 TestRtc::new_with_rtc(span, builder.build(Instant::now()))
277}
278 
279fn progress_until_cn_events(
280 l: &mut TestRtc,
281 r: &mut TestRtc,
282 rtp_mode: bool,
283) -> Result<(), RtcError> {
284 let deadline = l.duration() + Duration::from_secs(2);
285 while l.duration() < deadline {
286 let count = r
287 .events
288 .iter()
289 .filter(|(_, event)| {
290 if rtp_mode {
291 matches!(event, Event::RtpPacket(_))
292 } else {
293 matches!(
294 event,
295 Event::MediaData(data)
296 if data.params.spec().codec == Codec::CN
297 )
298 }
299 })
300 .count();
301 if count == 5 {
302 return Ok(());
303 }
304 progress(l, r)?;
305 }
306 
307 panic!("timed out waiting for all five Comfort Noise payloads");
308}
309 
310fn comfort_noise(pt: u8, clock_rate: u32) -> PayloadParams {
311 PayloadParams::new(
312 pt.into(),
313 None,
314 CodecSpec {
315 codec: Codec::CN,
316 channels: None,
317 clock_rate: Frequency::new(clock_rate).unwrap(),
318 format: FormatParams::default(),
319 },
320 )
321}
322 
323fn pcmu() -> PayloadParams {
324 PayloadParams::new(
325 0.into(),
326 None,
327 CodecSpec {
328 codec: Codec::PCMU,
329 channels: None,
330 clock_rate: Frequency::EIGHT_KHZ,
331 format: FormatParams::default(),
332 },
333 )
334}
335 
336fn comfort_noise_params() -> [PayloadParams; 5] {
337 [
338 comfort_noise(13, 8_000),
339 comfort_noise(96, 16_000),
340 comfort_noise(97, 24_000),
341 comfort_noise(98, 32_000),
342 comfort_noise(99, 48_000),
343 ]
344}
345 
346fn expected_cn_events() -> Vec<(u8, u32, Vec<u8>)> {
347 vec![
348 (13, 8_000, vec![1]),
349 (96, 16_000, vec![2]),
350 (97, 24_000, vec![3]),
351 (98, 32_000, vec![4]),
352 (99, 48_000, vec![5]),
353 ]
354}