File
Blob: firmware/vendor/str0m/tests/g722.rs
| 1 | //! G722 timestamp / clock-rate tests. |
| 2 | //! |
| 3 | //! G722 samples audio at 16 kHz but, per RFC 3551 §4.5.2, its RTP clock rate is |
| 4 | //! 8000 Hz (kept at 8000 to stay backwards compatible with RFC 1890, which |
| 5 | //! incorrectly used this value): |
| 6 | //! <https://en.wikipedia.org/wiki/RTP_payload_formats#cite_note-55> |
| 7 | |
| 8 | use std::collections::VecDeque; |
| 9 | use std::net::Ipv4Addr; |
| 10 | use std::time::{Duration, Instant}; |
| 11 | |
| 12 | use str0m::format::Codec; |
| 13 | use str0m::media::{Direction, Frequency, MediaKind, MediaTime}; |
| 14 | use str0m::rtp::{RtpWrite, Ssrc}; |
| 15 | use str0m::{Event, Rtc, RtcError}; |
| 16 | |
| 17 | mod common; |
| 18 | use common::{Peer, TestRtc, connect_l_r_with_rtc, init_crypto_default, init_log, progress}; |
| 19 | |
| 20 | /// G722 is a 16 kHz codec, but per RFC 3551 §4.5.2 its RTP timestamp clock runs at |
| 21 | /// 8000 Hz. In the media (samples/frame) API str0m presents G722 as 16 kHz both |
| 22 | /// when writing and when reading, and maps to/from the 8 kHz RTP clock on the |
| 23 | /// wire internally. See |
| 24 | /// <https://en.wikipedia.org/wiki/RTP_payload_formats#cite_note-55> |
| 25 | #[test] |
| 26 | pub fn g722_media_mode_is_16khz_both_ways() -> Result<(), RtcError> { |
| 27 | init_log(); |
| 28 | init_crypto_default(); |
| 29 | |
| 30 | let mut l = TestRtc::new_with_config(Peer::Left, |c| c.clear_codecs().enable_g722(true)); |
| 31 | let mut r = TestRtc::new_with_config(Peer::Right, |c| c.clear_codecs().enable_g722(true)); |
| 32 | |
| 33 | l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into()); |
| 34 | r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into()); |
| 35 | |
| 36 | let mut change = l.sdp_api(); |
| 37 | let mid = change.add_media(MediaKind::Audio, Direction::SendRecv, None, None, None); |
| 38 | let (offer, pending) = change.apply().unwrap(); |
| 39 | |
| 40 | let answer = r.rtc.sdp_api().accept_offer(offer)?; |
| 41 | l.rtc.sdp_api().accept_answer(pending, answer)?; |
| 42 | |
| 43 | loop { |
| 44 | if l.is_connected() || r.is_connected() { |
| 45 | break; |
| 46 | } |
| 47 | progress(&mut l, &mut r)?; |
| 48 | } |
| 49 | |
| 50 | let max = l.last.max(r.last); |
| 51 | l.last = max; |
| 52 | r.last = max; |
| 53 | |
| 54 | let params = l |
| 55 | .rtc |
| 56 | .codec_config() |
| 57 | .find(|p| p.spec().codec == Codec::G722) |
| 58 | .cloned() |
| 59 | .expect("G722 to be negotiated"); |
| 60 | // The codec is considered 16 kHz in the media API. |
| 61 | assert_eq!(params.spec().clock_rate, Frequency::SIXTEEN_KHZ); |
| 62 | let pt = params.pt(); |
| 63 | |
| 64 | // 20 ms of G722 at 64 kbit/s = 160 octets. Each 20 ms frame advances the |
| 65 | // 16 kHz media time by 320 samples (and the 8 kHz RTP timestamp by 160). |
| 66 | let data = vec![3_u8; 160]; |
| 67 | let mut samples: u64 = 0; |
| 68 | |
| 69 | loop { |
| 70 | { |
| 71 | let wallclock = l.start + l.duration(); |
| 72 | let time = MediaTime::new(samples, Frequency::SIXTEEN_KHZ); |
| 73 | l.writer(mid) |
| 74 | .unwrap() |
| 75 | .write(pt, wallclock, time, data.clone())?; |
| 76 | } |
| 77 | samples += 320; |
| 78 | |
| 79 | progress(&mut l, &mut r)?; |
| 80 | |
| 81 | if l.duration() > Duration::from_secs(2) { |
| 82 | break; |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | let media: Vec<_> = r |
| 87 | .events |
| 88 | .iter() |
| 89 | .filter_map(|(_, e)| match e { |
| 90 | Event::MediaData(d) => Some(d), |
| 91 | _ => None, |
| 92 | }) |
| 93 | .collect(); |
| 94 | |
| 95 | assert!(!media.is_empty(), "R received no G722 MediaData"); |
| 96 | |
| 97 | for d in &media { |
| 98 | // The media (samples/frame) API presents G722 as a 16 kHz codec ... |
| 99 | assert_eq!(d.params.spec().clock_rate, Frequency::SIXTEEN_KHZ); |
| 100 | // ... and the received media time is in that same 16 kHz clock rate. |
| 101 | assert_eq!( |
| 102 | d.time.frequency(), |
| 103 | Frequency::SIXTEEN_KHZ, |
| 104 | "media-mode G722 receive time should be 16 kHz" |
| 105 | ); |
| 106 | // The 16 kHz media time advances in whole 20 ms frames (320 samples). |
| 107 | assert_eq!( |
| 108 | d.time.numer() % 320, |
| 109 | 0, |
| 110 | "expected 16 kHz frame-aligned time" |
| 111 | ); |
| 112 | } |
| 113 | |
| 114 | Ok(()) |
| 115 | } |
| 116 | |
| 117 | fn rtp_mode_g722(peer: Peer, now: Instant) -> Rtc { |
| 118 | let mut b = Rtc::builder() |
| 119 | .set_rtp_mode(true) |
| 120 | .enable_raw_packets(true) |
| 121 | .clear_codecs() |
| 122 | .enable_g722(true); |
| 123 | if let Some(crypto) = peer.crypto_provider() { |
| 124 | b = b.set_crypto_provider(crypto); |
| 125 | } |
| 126 | b.build(now) |
| 127 | } |
| 128 | |
| 129 | /// In RTP mode the user works directly with raw RTP timestamps, which for G722 |
| 130 | /// are the 8 kHz wire values (RFC 3551 §4.5.2). str0m must not apply any 16 kHz |
| 131 | /// scaling in either direction. See |
| 132 | /// <https://en.wikipedia.org/wiki/RTP_payload_formats#cite_note-55> |
| 133 | #[test] |
| 134 | pub fn g722_rtp_mode_is_8khz_both_ways() -> Result<(), RtcError> { |
| 135 | init_log(); |
| 136 | init_crypto_default(); |
| 137 | |
| 138 | let now = Instant::now(); |
| 139 | let (mut l, mut r) = connect_l_r_with_rtc( |
| 140 | rtp_mode_g722(Peer::Left, now), |
| 141 | rtp_mode_g722(Peer::Right, now), |
| 142 | ); |
| 143 | |
| 144 | let mid = "aud".into(); |
| 145 | let ssrc: Ssrc = 1.into(); |
| 146 | |
| 147 | l.direct_api().declare_media(mid, MediaKind::Audio); |
| 148 | l.direct_api().declare_stream_tx(ssrc, None, mid, None); |
| 149 | r.direct_api().declare_media(mid, MediaKind::Audio); |
| 150 | r.direct_api().expect_stream_rx(ssrc, None, mid, None); |
| 151 | |
| 152 | let max = l.last.max(r.last); |
| 153 | l.last = max; |
| 154 | r.last = max; |
| 155 | |
| 156 | let pt = l |
| 157 | .rtc |
| 158 | .codec_config() |
| 159 | .find(|p| p.spec().codec == Codec::G722) |
| 160 | .map(|p| p.pt()) |
| 161 | .expect("G722 PT"); |
| 162 | |
| 163 | // The user supplies the raw 8 kHz RTP timestamp; 20 ms frames advance it by 160. |
| 164 | let base_ts: u32 = 8_000_000; |
| 165 | let data: &[u8] = &[0x1, 0x2, 0x3, 0x4]; |
| 166 | |
| 167 | let mut to_write: VecDeque<(u32, u64)> = VecDeque::from(vec![ |
| 168 | (base_ts, 100), |
| 169 | (base_ts + 160, 101), |
| 170 | (base_ts + 320, 102), |
| 171 | ]); |
| 172 | |
| 173 | let mut write_at = l.last + Duration::from_millis(300); |
| 174 | |
| 175 | loop { |
| 176 | if l.start + l.duration() > write_at { |
| 177 | write_at = l.last + Duration::from_millis(300); |
| 178 | if let Some((time, seq)) = to_write.pop_front() { |
| 179 | let wallclock = l.start + l.duration(); |
| 180 | let mut direct = l.direct_api(); |
| 181 | let stream = direct.stream_tx(&ssrc).unwrap(); |
| 182 | stream.write_rtp(RtpWrite::new(pt, seq.into(), time, wallclock, data)); |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | progress(&mut l, &mut r)?; |
| 187 | |
| 188 | if l.duration() > Duration::from_secs(4) { |
| 189 | break; |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | let media: Vec<_> = r |
| 194 | .events |
| 195 | .iter() |
| 196 | .filter_map(|(_, e)| match e { |
| 197 | Event::RtpPacket(v) => Some(v), |
| 198 | _ => None, |
| 199 | }) |
| 200 | .collect(); |
| 201 | |
| 202 | assert_eq!(media.len(), 3, "expected 3 RTP packets at R"); |
| 203 | |
| 204 | // RTP mode passes the 8 kHz wire timestamp through verbatim (no 16 kHz scaling). |
| 205 | assert_eq!(media[0].header.timestamp, base_ts); |
| 206 | assert_eq!(media[1].header.timestamp, base_ts + 160); |
| 207 | assert_eq!(media[2].header.timestamp, base_ts + 320); |
| 208 | |
| 209 | for v in &media { |
| 210 | assert_eq!( |
| 211 | v.time.frequency(), |
| 212 | Frequency::EIGHT_KHZ, |
| 213 | "RTP-mode G722 time should be at the 8 kHz RTP clock rate" |
| 214 | ); |
| 215 | } |
| 216 | |
| 217 | Ok(()) |
| 218 | } |
| 219 | |
| 220 | /// Cross-mode: a sample (frame) mode sender writes 16 kHz media time, and an |
| 221 | /// RTP mode receiver sees the 8 kHz wire timestamps (RFC 3551 §4.5.2). This |
| 222 | /// verifies the 16 kHz -> 8 kHz mapping happens on the send side. See |
| 223 | /// <https://en.wikipedia.org/wiki/RTP_payload_formats#cite_note-55> |
| 224 | #[test] |
| 225 | pub fn g722_sample_send_to_rtp_receive() -> Result<(), RtcError> { |
| 226 | init_log(); |
| 227 | init_crypto_default(); |
| 228 | |
| 229 | let now = Instant::now(); |
| 230 | // L writes via the sample/frame API (16 kHz media time). |
| 231 | let rtc_l = Rtc::builder().clear_codecs().enable_g722(true).build(now); |
| 232 | // R reads raw RTP packets (8 kHz wire clock). |
| 233 | let rtc_r = Rtc::builder() |
| 234 | .set_rtp_mode(true) |
| 235 | .clear_codecs() |
| 236 | .enable_g722(true) |
| 237 | .build(now); |
| 238 | |
| 239 | let (mut l, mut r) = connect_l_r_with_rtc(rtc_l, rtc_r); |
| 240 | |
| 241 | let mid = "aud".into(); |
| 242 | let ssrc: Ssrc = 1.into(); |
| 243 | |
| 244 | l.direct_api().declare_media(mid, MediaKind::Audio); |
| 245 | l.direct_api().declare_stream_tx(ssrc, None, mid, None); |
| 246 | r.direct_api().declare_media(mid, MediaKind::Audio); |
| 247 | r.direct_api().expect_stream_rx(ssrc, None, mid, None); |
| 248 | |
| 249 | let max = l.last.max(r.last); |
| 250 | l.last = max; |
| 251 | r.last = max; |
| 252 | |
| 253 | let pt = l |
| 254 | .rtc |
| 255 | .codec_config() |
| 256 | .find(|p| p.spec().codec == Codec::G722) |
| 257 | .map(|p| p.pt()) |
| 258 | .expect("G722 PT"); |
| 259 | |
| 260 | let data = vec![7u8; 160]; |
| 261 | // 5 frames of 20 ms; the 16 kHz media time advances 320 samples per frame. |
| 262 | let mut frames: VecDeque<u64> = VecDeque::from(vec![0, 320, 640, 960, 1280]); |
| 263 | let mut write_at = l.last + Duration::from_millis(300); |
| 264 | |
| 265 | loop { |
| 266 | if l.start + l.duration() > write_at { |
| 267 | write_at = l.last + Duration::from_millis(300); |
| 268 | if let Some(samples) = frames.pop_front() { |
| 269 | let wallclock = l.start + l.duration(); |
| 270 | let time = MediaTime::new(samples, Frequency::SIXTEEN_KHZ); |
| 271 | l.writer(mid) |
| 272 | .unwrap() |
| 273 | .write(pt, wallclock, time, data.clone())?; |
| 274 | } |
| 275 | } |
| 276 | |
| 277 | progress(&mut l, &mut r)?; |
| 278 | |
| 279 | if l.duration() > Duration::from_secs(4) { |
| 280 | break; |
| 281 | } |
| 282 | } |
| 283 | |
| 284 | let media: Vec<_> = r |
| 285 | .events |
| 286 | .iter() |
| 287 | .filter_map(|(_, e)| match e { |
| 288 | Event::RtpPacket(v) => Some(v), |
| 289 | _ => None, |
| 290 | }) |
| 291 | .collect(); |
| 292 | |
| 293 | assert_eq!(media.len(), 5, "expected 5 RTP packets at R"); |
| 294 | |
| 295 | // The RTP receiver sees the 8 kHz wire clock; each 20 ms frame advances 160. |
| 296 | let base = media[0].header.timestamp; |
| 297 | for (i, v) in media.iter().enumerate() { |
| 298 | assert_eq!( |
| 299 | v.time.frequency(), |
| 300 | Frequency::EIGHT_KHZ, |
| 301 | "RTP-mode receive time should be 8 kHz" |
| 302 | ); |
| 303 | assert_eq!( |
| 304 | v.header.timestamp, |
| 305 | base + (i as u32) * 160, |
| 306 | "8 kHz wire timestamp mismatch at frame {i}" |
| 307 | ); |
| 308 | } |
| 309 | |
| 310 | Ok(()) |
| 311 | } |
| 312 | |
| 313 | /// Cross-mode: an RTP mode sender writes raw 8 kHz wire timestamps, and a sample |
| 314 | /// (frame) mode receiver sees 16 kHz media time. This verifies the 8 kHz -> 16 kHz |
| 315 | /// mapping happens on the receive side. See |
| 316 | /// <https://en.wikipedia.org/wiki/RTP_payload_formats#cite_note-55> |
| 317 | #[test] |
| 318 | pub fn g722_rtp_send_to_sample_receive() -> Result<(), RtcError> { |
| 319 | init_log(); |
| 320 | init_crypto_default(); |
| 321 | |
| 322 | let now = Instant::now(); |
| 323 | // L writes raw RTP at the 8 kHz wire clock. |
| 324 | let rtc_l = Rtc::builder() |
| 325 | .set_rtp_mode(true) |
| 326 | .clear_codecs() |
| 327 | .enable_g722(true) |
| 328 | .build(now); |
| 329 | // R reads frames via the sample API (16 kHz media time), no reorder hold-back. |
| 330 | let rtc_r = Rtc::builder() |
| 331 | .set_reordering_size_audio(0) |
| 332 | .clear_codecs() |
| 333 | .enable_g722(true) |
| 334 | .build(now); |
| 335 | |
| 336 | let (mut l, mut r) = connect_l_r_with_rtc(rtc_l, rtc_r); |
| 337 | |
| 338 | let mid = "aud".into(); |
| 339 | let ssrc_tx: Ssrc = 1.into(); |
| 340 | |
| 341 | l.direct_api().declare_media(mid, MediaKind::Audio); |
| 342 | l.direct_api().declare_stream_tx(ssrc_tx, None, mid, None); |
| 343 | r.direct_api().declare_media(mid, MediaKind::Audio); |
| 344 | |
| 345 | let max = l.last.max(r.last); |
| 346 | l.last = max; |
| 347 | r.last = max; |
| 348 | |
| 349 | let pt = l |
| 350 | .rtc |
| 351 | .codec_config() |
| 352 | .find(|p| p.spec().codec == Codec::G722) |
| 353 | .map(|p| p.pt()) |
| 354 | .expect("G722 PT"); |
| 355 | let ssrc = l.direct_api().stream_tx_by_mid(mid, None).unwrap().ssrc(); |
| 356 | |
| 357 | let data: &[u8] = &[0x1, 0x2, 0x3, 0x4]; |
| 358 | let base_ts: u32 = 8_000_000; |
| 359 | // 5 frames of 20 ms; the 8 kHz wire timestamp advances 160 per frame. |
| 360 | let mut frames: VecDeque<(u32, u64)> = VecDeque::from(vec![ |
| 361 | (base_ts, 100), |
| 362 | (base_ts + 160, 101), |
| 363 | (base_ts + 320, 102), |
| 364 | (base_ts + 480, 103), |
| 365 | (base_ts + 640, 104), |
| 366 | ]); |
| 367 | let mut write_at = l.last + Duration::from_millis(300); |
| 368 | |
| 369 | loop { |
| 370 | if l.start + l.duration() > write_at { |
| 371 | write_at = l.last + Duration::from_millis(300); |
| 372 | if let Some((ts, seq)) = frames.pop_front() { |
| 373 | let wallclock = l.start + l.duration(); |
| 374 | let mut direct = l.direct_api(); |
| 375 | let stream = direct.stream_tx(&ssrc).unwrap(); |
| 376 | stream.write_rtp(RtpWrite::new(pt, seq.into(), ts, wallclock, data)); |
| 377 | } |
| 378 | } |
| 379 | |
| 380 | progress(&mut l, &mut r)?; |
| 381 | |
| 382 | if l.duration() > Duration::from_secs(4) { |
| 383 | break; |
| 384 | } |
| 385 | } |
| 386 | |
| 387 | let media: Vec<_> = r |
| 388 | .events |
| 389 | .iter() |
| 390 | .filter_map(|(_, e)| match e { |
| 391 | Event::MediaData(v) => Some(v), |
| 392 | _ => None, |
| 393 | }) |
| 394 | .collect(); |
| 395 | |
| 396 | assert_eq!(media.len(), 5, "expected 5 frames at R"); |
| 397 | |
| 398 | // The sample receiver presents G722 as 16 kHz; each 20 ms frame advances 320. |
| 399 | let base = media[0].time.numer(); |
| 400 | for (i, m) in media.iter().enumerate() { |
| 401 | assert_eq!(m.params.spec().clock_rate, Frequency::SIXTEEN_KHZ); |
| 402 | assert_eq!( |
| 403 | m.time.frequency(), |
| 404 | Frequency::SIXTEEN_KHZ, |
| 405 | "sample-mode receive time should be 16 kHz" |
| 406 | ); |
| 407 | assert_eq!( |
| 408 | m.time.numer(), |
| 409 | base + (i as u64) * 320, |
| 410 | "16 kHz media time mismatch at frame {i}" |
| 411 | ); |
| 412 | } |
| 413 | |
| 414 | Ok(()) |
| 415 | } |