File
Blob: firmware/vendor/str0m/tests/comfort-noise.rs
| 1 | use std::net::Ipv4Addr; |
| 2 | use std::time::{Duration, Instant}; |
| 3 | |
| 4 | mod common; |
| 5 | use common::{TestRtc, init_crypto_default, init_log, negotiate, progress}; |
| 6 | use str0m::Rtc; |
| 7 | use str0m::format::{Codec, CodecSpec, FormatParams, PayloadParams}; |
| 8 | use str0m::media::{Direction, Frequency, MediaKind, MediaTime, Mid}; |
| 9 | use str0m::rtp::RtpWrite; |
| 10 | use str0m::{Event, RtcError}; |
| 11 | use tracing::{Span, info_span}; |
| 12 | |
| 13 | #[test] |
| 14 | fn 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"), ¶ms, info_span!("R"), ¶ms); |
| 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] |
| 42 | fn 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(¶ms, 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] |
| 81 | fn 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(¶ms); |
| 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] |
| 125 | fn 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(¶ms, 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] |
| 158 | fn 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(¶ms, 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 | |
| 201 | fn 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 | |
| 217 | fn connected_with_params(params: &[PayloadParams], rtp_mode: bool) -> (TestRtc, TestRtc, Mid) { |
| 218 | connected_with_reordering(params, rtp_mode, Some(0)) |
| 219 | } |
| 220 | |
| 221 | fn connected_with_default_audio_reordering(params: &[PayloadParams]) -> (TestRtc, TestRtc, Mid) { |
| 222 | connected_with_reordering(params, false, None) |
| 223 | } |
| 224 | |
| 225 | fn 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 | |
| 251 | fn build_params_with_mode(span: Span, params: &[PayloadParams], rtp_mode: bool) -> TestRtc { |
| 252 | build_params(span, params, rtp_mode, Some(0)) |
| 253 | } |
| 254 | |
| 255 | fn 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 | |
| 279 | fn 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 | |
| 310 | fn 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 | |
| 323 | fn 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 | |
| 336 | fn 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 | |
| 346 | fn 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 | } |