From f1e719112dd2abee81f791395288e41d165f2392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= <55464917+jlucaso1@users.noreply.github.com> Date: Sat, 25 Jul 2026 16:59:21 -0300 Subject: [PATCH 01/17] fix(voip/mlow): decode multi-frame packets instead of dropping them A 1:1 call whose peer is WhatsApp Desktop was silent. That client emits 120 ms packets (TOC 0x58) routinely, and the decoder discarded every one of them: decode_active_frame ran a hardcoded three-iteration loop, so the operating-point guard rejected any duration other than 60 ms and returned a silence slot. Nothing surfaced as an error, because a dropped frame is indistinguishable from a peer that is not speaking. A packet is not one unit of decode. The reference derives the loop count from the declared duration while the geometry inside each iteration is unchanged: num_frames = (packet_len_ms + 10) / 20, with the internal frame length and subframe count fixed for anything above 10 ms. So 20, 60 and 120 ms differ only in how many times the same decode repeats, and the frame index only ever distinguishes the first internal frame from the rest. Derive the count from the TOC and drive the loop, the output length, the lag buffer and the postfilter average from it. This admits 20 and 120 ms as well as the 60 ms that already worked. 10 ms stays rejected: it halves both the internal frame length and the subframe count, a geometry the synthesis does not implement, and decoding it would consume the payload at the wrong symbol count and desync the range coder. Two existing tests used 20 ms as their example of a dropped frame and now use 10 ms and a 32 kHz TOC instead. That is a deliberate contract change, not a convenience edit: 20 ms decodes now. --- wacore/src/voip/mlow/decoder.rs | 198 +++++++++++++++--- wacore/src/voip/mlow/testdata/PROVENANCE.md | 15 ++ .../voip/mlow/testdata/mlow_120ms_frames.json | 10 + .../voip/mlow/testdata/ref_120ms_expected.raw | Bin 0 -> 30720 bytes 4 files changed, 190 insertions(+), 33 deletions(-) create mode 100644 wacore/src/voip/mlow/testdata/mlow_120ms_frames.json create mode 100644 wacore/src/voip/mlow/testdata/ref_120ms_expected.raw diff --git a/wacore/src/voip/mlow/decoder.rs b/wacore/src/voip/mlow/decoder.rs index 289068cc9..c699ec47e 100644 --- a/wacore/src/voip/mlow/decoder.rs +++ b/wacore/src/voip/mlow/decoder.rs @@ -1,5 +1,5 @@ -//! MLow top-level decoder: RED strip -> TOC routing -> active-frame decode (3 chained 20 ms internal -//! frames: LSF -> pulses -> pitch/gains -> CELP synthesis) -> 60 ms PCM. The synthesis +//! MLow top-level decoder: RED strip -> TOC routing -> active-frame decode (chained 20 ms internal +//! frames: LSF -> pulses -> pitch/gains -> CELP synthesis) -> PCM. The synthesis //! (`smpl_celpdec`) runs the excitation in the codec's float domain (gen_noise + LPC synthesis). The //! cross-frame predictor and synthesis history persist across calls because the stream is //! continuous. @@ -20,8 +20,21 @@ use super::toc::parse_mlow_toc; const OPUS_FRAME_SAMPS: usize = 960; // 60 ms @ 16 kHz +/// Internal 20 ms frames chained inside one packet, or `None` for a duration this decoder cannot +/// run. A packet is not a single unit of decode: the reference derives the loop count from the +/// declared duration while the geometry inside each iteration stays fixed, so 20/60/120 ms differ +/// only in how many times the same decode repeats. +/// +/// 10 ms is the exception and stays unsupported: it halves the internal frame length and the +/// subframe count, which the synthesis does not implement, and decoding it under the wrong geometry +/// would consume the payload with the wrong symbol count and desync the range coder. +fn internal_frames(frame_ms: i32) -> Option { + (frame_ms > 10).then(|| ((frame_ms + 10) / 20) as usize) +} + /// Stateful pure-Rust MLow decoder. Decodes one RTP payload (a bare MLow frame, or a SplitRed -/// packet when redundancy was negotiated) into a 60 ms / 960-sample PCM frame at 16 kHz. +/// packet when redundancy was negotiated) into a PCM frame at 16 kHz, one 20 ms internal frame +/// per chained frame in the packet. pub struct MlowDecoder { state: SmplDecoderState, redundancy: i32, @@ -31,10 +44,9 @@ pub struct MlowDecoder { /// never gates output. had_error: bool, /// Count of inbound frames dropped because they fall outside this decoder's single operating point - /// (16kHz wideband, low_rate=0, 60ms). Such a frame would desync the range coder if decoded, so it - /// is dropped (treated as a lost frame). The count drives a once + every-100th `warn` (which names - /// the offending dimension) so a live capture reveals whether real peers emit these (decides the - /// follow-ups). + /// (16kHz wideband, low_rate=0, and a duration whose internal geometry it implements). Such a + /// frame would desync the range coder if decoded, so it is dropped (treated as a lost frame). The + /// count drives a once + every-100th `warn` naming the offending dimension. dropped_unsupported: u32, } @@ -73,7 +85,8 @@ impl MlowDecoder { self.had_error = false; } - /// Decode one RTP MLow payload into a 60 ms (960-sample) PCM frame, float in [-1, 1]. + /// Decode one RTP MLow payload into a PCM frame, float in [-1, 1]. The sample count follows + /// the packet's declared duration; a dropped or silenced frame yields a 60 ms slot. pub fn decode(&mut self, payload: &[u8]) -> Vec { if payload.is_empty() { return vec![0.0; OPUS_FRAME_SAMPS]; @@ -117,18 +130,17 @@ impl MlowDecoder { log::debug!("mlow: DTX/SID TOC 0x{:02x} -> 60ms silence", frame[0]); return vec![0.0; OPUS_FRAME_SAMPS]; } - // Operating-point guard for active frames: an active frame at a different internal rate, the - // low_rate=1 2x160 geometry, or a non-60ms duration would desync the range coder, since - // decode_active_frame always runs the 3x20ms / 60ms geometry and would consume the payload with - // the wrong symbol count (garbage plus a poisoned cross-frame predictor that propagates to later - // packets). Drop it as a lost frame so the predictor holds its last good values. flag2 is the - // smpl TOC's low_rate bit; the warn names the offending dimension so a live capture shows whether - // real peers ever emit active out-of-point frames in 1:1 calls. + // Operating-point guard for active frames: a different internal rate, the low_rate=1 2x160 + // geometry, or a duration whose internal geometry differs would consume the payload with the + // wrong symbol count and desync the range coder (garbage plus a poisoned cross-frame predictor + // that propagates to later packets). Drop those as lost frames so the predictor holds its last + // good values. flag2 is the smpl TOC's low_rate bit. + let frames = internal_frames(toc.frame_ms); let off_point = if toc.sample_rate != 16000 { Some(("rate", i64::from(toc.sample_rate / 1000))) } else if toc.flag2 { Some(("low_rate", 1)) - } else if toc.frame_ms != 60 { + } else if frames.is_none() { Some(("frame_ms", i64::from(toc.frame_ms))) } else { None @@ -138,17 +150,18 @@ impl MlowDecoder { if self.dropped_unsupported == 1 || self.dropped_unsupported.is_multiple_of(100) { log::warn!( "mlow: dropping out-of-operating-point frame #{} ({dim}={val}, TOC 0x{:02x}); \ - the 1:1 decoder is 16kHz / low_rate=0 / 60ms only", + the decoder runs 16kHz / low_rate=0 / 20-120ms", self.dropped_unsupported, frame[0] ); } return vec![0.0; OPUS_FRAME_SAMPS]; } - self.decode_active_frame(frame, OPUS_FRAME_SAMPS) + let frames = frames.expect("the guard above rejected every unsupported duration"); + self.decode_active_frame(frame, frames * SMPL_INTF_LEN, frames) } - fn decode_active_frame(&mut self, frame: &[u8], out_len: usize) -> Vec { + fn decode_active_frame(&mut self, frame: &[u8], out_len: usize, frames: usize) -> Vec { let config = (frame[0] >> 2) as usize & 1; let tbl = load_smpl_tables(); let synth_t = load_smpl_synth_tables(); @@ -159,12 +172,12 @@ impl MlowDecoder { // The low_rate bit of the smpl TOC (this capture is low_rate==0; the synth gates on it). let low_rate = (frame[0] >> 2) & 1 != 0; - let mut out: Vec = Vec::with_capacity(3 * SMPL_INTF_LEN); - // Collect the per-40-block lags (8 per frame, 24 per packet) and the average normalized - // bitrate for the per-packet harmonic postfilter. - let mut packet_lags: Vec = Vec::with_capacity(3 * 8); + let mut out: Vec = Vec::with_capacity(frames * SMPL_INTF_LEN); + // Collect the per-40-block lags (8 per internal frame) and the average normalized bitrate + // for the per-packet harmonic postfilter. + let mut packet_lags: Vec = Vec::with_capacity(frames * 8); let mut avg_norm_br = 0.0f32; - for f in 0..3 { + for f in 0..frames { let lsf = decode_smpl_lsf(&mut dec, tbl, &mut self.state.lstate, config, f); let pulses = decode_smpl_pulses( &mut dec, @@ -250,7 +263,7 @@ impl MlowDecoder { plen, &packet_lags, packet_lags.len(), - avg_norm_br / 3.0, + avg_norm_br / frames as f32, ); // The C-domain synthesis output is already float in [-1, 1]; clamp in place. @@ -362,6 +375,124 @@ pub(crate) fn diag_decode_params() -> Vec { mod tests { use super::*; + /// The loop count per packet duration, against the reference decoder's + /// `num_frames = (packet_len_ms + 10) / 20`. 10 ms is excluded because it also changes the + /// internal frame length and subframe count, which the synthesis does not implement. + #[test] + fn internal_frame_count_matches_the_reference_geometry() { + assert_eq!(internal_frames(20), Some(1)); + assert_eq!(internal_frames(60), Some(3)); + assert_eq!(internal_frames(120), Some(6)); + assert_eq!(internal_frames(10), None); + } + + /// WhatsApp Desktop sends 120 ms packets (TOC 0x58) on ordinary 1:1 calls. They must decode, + /// not be discarded: dropping them silences the whole stream while the peer is speaking. + #[test] + fn multi_frame_packet_decodes_to_its_full_duration() { + let toc = parse_mlow_toc(0x58); + assert_eq!(toc.frame_ms, 120, "0x58 declares a 120 ms packet"); + assert!(toc.active && !toc.sid && !toc.std_opus); + assert_eq!(toc.sample_rate, 16000); + assert!(!toc.flag2, "0x58 is the supported rate mode"); + + let mut dec = MlowDecoder::new(); + let out = dec.decode(&[0x58, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22]); + assert_eq!( + out.len(), + 6 * SMPL_INTF_LEN, + "a 120 ms packet must yield 120 ms of PCM" + ); + } + + /// A 20 ms packet shares the same internal geometry and must decode to exactly one frame. + #[test] + fn single_frame_packet_decodes_to_one_internal_frame() { + let mut dec = MlowDecoder::new(); + let out = dec.decode(&[0x48, 0xAA, 0xBB, 0xCC]); + assert_eq!( + out.len(), + SMPL_INTF_LEN, + "a 20 ms packet is one 20 ms frame" + ); + } + + /// The failure case the geometry guard exists for: 10 ms halves the internal frame length and + /// the subframe count, so it must still be dropped rather than decoded under the wrong geometry, + /// into the same 60 ms silence slot the other drops use. + #[test] + fn ten_ms_active_packet_is_still_dropped() { + let toc = parse_mlow_toc(0x40); + assert_eq!(toc.frame_ms, 10); + assert!(toc.active); + + let mut dec = MlowDecoder::new(); + let out = dec.decode(&[0x40, 0xAA, 0xBB, 0xCC]); + assert_eq!(out.len(), OPUS_FRAME_SAMPS); + assert!( + out.iter().all(|&s| s == 0.0), + "10 ms runs a geometry the synthesis does not implement" + ); + assert!(!dec.had_error(), "the drop must not open the range decoder"); + } + + /// The content check: decode a stream of real 120 ms packets and compare against the reference + /// decoder's own output for the same bytes. Geometry alone is not enough, since running the loop + /// the wrong number of times would still produce plausibly-shaped audio while consuming the + /// payload at the wrong symbol count. See testdata/PROVENANCE.md for the oracle. + #[test] + fn multi_frame_decode_matches_the_reference() { + let frames: Vec = + serde_json::from_str(include_str!("testdata/mlow_120ms_frames.json")) + .expect("mlow_120ms_frames.json"); + let refp: Vec = include_bytes!("testdata/ref_120ms_expected.raw") + .chunks_exact(2) + .map(|b| i16::from_le_bytes([b[0], b[1]]) as f32 / 32768.0) + .collect(); + + let mut dec = MlowDecoder::new(); + let mut out: Vec = Vec::new(); + for hex_frame in &frames { + let frame = hex::decode(hex_frame).unwrap(); + assert_eq!(frame[0], 0x58, "the fixture must stay 120 ms packets"); + out.extend_from_slice(&dec.decode(&frame)); + } + assert_eq!(out.len(), refp.len(), "decode length vs reference"); + + let n = refp.len(); + let (mr, mo) = ( + refp.iter().map(|&v| v as f64).sum::() / n as f64, + out.iter().map(|&v| v as f64).sum::() / n as f64, + ); + let (mut sxy, mut sxx, mut syy) = (0f64, 0f64, 0f64); + for i in 0..n { + let (dr, dz) = (refp[i] as f64 - mr, out[i] as f64 - mo); + sxy += dr * dz; + sxx += dr * dr; + syy += dz * dz; + } + let corr = sxy / (sxx * syy).sqrt(); + assert!(corr > 0.999, "lag-0 corr {corr:.6} vs reference"); + } + + /// Decoding a multi-frame packet must leave the cross-frame predictor usable: a real 60 ms frame + /// after it still has to produce audio. + #[test] + fn multi_frame_packet_does_not_poison_later_frames() { + let frames: Vec = + serde_json::from_str(include_str!("testdata/inbound_capture_frames.json")).unwrap(); + let real = hex::decode(&frames[0]).unwrap(); + + let mut dec = MlowDecoder::new(); + let _ = dec.decode(&[0x58, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22]); + let after = dec.decode(&real); + assert_eq!(after.len(), 960); + assert!( + after.iter().any(|&s| s != 0.0), + "a real 60 ms frame after a 120 ms packet must still decode" + ); + } + // End-to-end: decode the whole capture and compare against the reference output // (`ref_usesmpl_expected.raw`; see testdata/PROVENANCE.md). // @@ -498,13 +629,14 @@ mod tests { "low_rate=1 frame must drop to silence" ); - // A non-60ms ACTIVE 16kHz/low_rate=0 TOC (20ms, e.g. 0x48) must also drop: decode_active_frame - // hardcodes the 3x20ms / 60ms geometry, so a 20ms frame would otherwise desync the range coder. - let out_20ms = dec.decode(&[0x48, 0xAA, 0xBB, 0xCC]); - assert_eq!(out_20ms.len(), 960); + // A 10ms ACTIVE 16kHz/low_rate=0 TOC (0x40) must also drop: it is the one duration whose + // internal frame length and subframe count differ, so decoding it under the implemented + // geometry would desync the range coder. 20/60/120ms all decode (see the geometry tests). + let out_10ms = dec.decode(&[0x40, 0xAA, 0xBB, 0xCC]); + assert_eq!(out_10ms.len(), 960); assert!( - out_20ms.iter().all(|&s| s == 0.0), - "a non-60ms active frame must drop to silence" + out_10ms.iter().all(|&s| s == 0.0), + "a 10ms active frame must drop to silence" ); // The drops never opened the range decoder, so the predictor is intact: the real frame still @@ -557,9 +689,9 @@ mod tests { "the inactive path must not open the range decoder" ); - // Contrast: an active off-point frame (0x48 = vad=true, 20ms) IS counted, proving the drop + // Contrast: an active off-point frame (0x60 = vad=true, 32 kHz) IS counted, proving the drop // counter discriminates real audio loss from benign inactive silence. - let _ = dec.decode(&[0x48, 0xAA, 0xBB, 0xCC]); + let _ = dec.decode(&[0x60, 0xAA, 0xBB, 0xCC]); assert_eq!( dec.dropped_unsupported, 1, "an active off-point frame must count as a drop" diff --git a/wacore/src/voip/mlow/testdata/PROVENANCE.md b/wacore/src/voip/mlow/testdata/PROVENANCE.md index abab1d64b..6d516a261 100644 --- a/wacore/src/voip/mlow/testdata/PROVENANCE.md +++ b/wacore/src/voip/mlow/testdata/PROVENANCE.md @@ -78,3 +78,18 @@ the reference decoder, one record per frame, compared byte-for-byte by the Rust exact wire bytes (config-1 `0x10` and config-2 `0x12` frames included). The tripwire test asserts the committed stream still carries `0x10`, `0x12`, and `0x50` TOCs so the per-config decode branches stay covered; regenerating it requires the external encoder above on `synth_mic.raw`. + +## Multi-frame (120 ms) packets + +| fixture | consumer / test | oracle recipe | +| --- | --- | --- | +| `mlow_120ms_frames.json` | `decoder.rs::multi_frame_decode_matches_the_reference` | `smpl` C reference encoding `synth_mic.raw` at 120 ms, hex frames | +| `ref_120ms_expected.raw` | same test | the same C reference decoding those frames; s16le @ 16 kHz | + +Also not Rust-reproducible: this crate's encoder only emits 60 ms packets. Both files come from one +run of a harness linked against the `smpl` C reference, which encodes `synth_mic.raw` in 1920-sample +(120 ms) frames and decodes each packet back, emitting ` ` per line. The +encoder needs `smpl_CreateCodec()` before the first `opus_encode` (it fails +`SMPL_ENC_NO_GLOBAL_DATA` otherwise), `OPUS_SET_USING_SMPL(1)`, and a `max_data_bytes` the CBR pad +can satisfy. Every frame is TOC `0x58`, which the test asserts so the fixture cannot silently drift +off the multi-frame path. diff --git a/wacore/src/voip/mlow/testdata/mlow_120ms_frames.json b/wacore/src/voip/mlow/testdata/mlow_120ms_frames.json new file mode 100644 index 000000000..0440a4eaf --- /dev/null +++ b/wacore/src/voip/mlow/testdata/mlow_120ms_frames.json @@ -0,0 +1,10 @@ +[ + "58e5638cd7b84c934ad6200696fdd57ad59328d16487059c4ceba9a663aee2f2a8539717c9ad61003c481cd88b7d53671dd9d922348facd652c3412a29fe0b104781cdae50ffe628069d82b85803f440b7dfa3ba14723358977cdddd4ad455f18ca2f590f8ccbc3fc731094450bf79114129608b7ea4550f2adb8dfc6db9679f1d4374493bc49b13461de9d2e62c54fd8a204664292fd00f32d0645d62c1924df289ccb0871fa135700e2a12298b131b234cab25c595b4a7757377e0008c2810a99933060235181d44fb009fd4f95f382a5b009699b9ac8e8837df93f0b024112fee742f231607e222052834037b48e0b78ec5d33515adba720eabd68650c1f8003b1b738fa9f9eac138f5a663928a3a7204e3b69dbaff50bdcf247562b92658519f199866eba75a8043b0f187ca3a636703f99358e34d930e1a43f745428b61d6bafc3c4d0dfb3ce1570339530f2024458dbc", + "58e9e778031e4920b57f3e4ac91b28104458c23717a2ef45ec4ac8ed28db2d34a6b47eefc5d172817094ff5d778ad2bdfbafec70e255d97eb4456d454975b0edf24dd4b002e8e0728bd4dbab127c7f6cd22daef1628283c3c25d2a43eced73b9a2671ec0a4de3df7e22cabf11a631238a1b2862b7a1185538e8d50e5b1cdb58f16d50ab32230d4b5037474232b6fd472dc30d0ce219600d540e52596d0b55ca5cfe8db7788efdf79d011bd960a1c58400945681558a95a8ead4a4858aaf986f0b36629e1466bbc740d489d31f6649abe645e6f825e243c67ed0dec3cd23780a4cdbc9fc0e2f68fece39283edc7db440e2f99d87616005afaa574d962d4ea10c2d2c2bd58f96f2ca5d33beb6873aab03eaf1df5a3f7a53cbdd31bade5346d2d42da47535f54fd7ae87a7d5e0a4c5b0a937e0125bca0f9c434ae381b8ad497d80d31a22e3327285d5c32385fefebbdc6c954d3b3c8cd067db648e25ae33156b0", + "58e5638c50b876732ce74b53483134f0dc3201d9f32cd6083a3fcd5286aa31e8c67ccb0f6c49c4b373e1f6bbb176e8df81d415ee9bfa6d67cf3c45ae4518c17f4acb9a28d0dfdce23b6cf7506c83fa9a6db45c3a8ed2a6000772c2b4b6ab5940e88e91d1b672f9e30dfa4c0763f790c8b35ec71a0543d204662984151a3c2dbf19258913b1dbb7e275da6801646cdccd2bbc3f11d1a26ee0f33cc406c49fd89dc37e74d8dcaf3c38113fca17a956c4dc9033acdb8318992039c5e49809a4c3cc10073a678031f48318511e36ff545293fa4c230b0ababbe7582da6c7865711eef2123814af0143875246cc9d25a7bc8a0606c6294bf414af39633525cf036ee81ba9bfa3ffc029be007d18a69add72e93e8292a54b1237565654f36f0c2208e409394f8bd7caa9f24d0b6804b99529a4e65ea1dd8a748d7d7ccbdd0641e4f4ee3a81f416f0af6f7a5eedd63ea0f9df87c422ee9280", + "58e5ea1b94e67249c76a198f60f02bd0a603907fb0304bc529aa190f405157a84efadb7056df8b333542428c3a438bef0eca01720d99d0dc6767171713431498b88c5046ad57afb4f56bf34a1bac4e41ca70438e182cfb1d52e8d2644e904ea2b503fee76f89fc5f2f89c568f5ca7e94e0a2fccb0ae6b1d72668ff97c2572df5e907b4118f8f7159f5269e82661bf25974e642554818952a0f57a58e6f85a744ed5ff24a9d8d609227318fd5c66cb3263d7aebf12e0a2b095b3017db1d7e5733de7c03ca5db83d65a26d405bdcf93cc9e948191ebda1c8870c7086df294fe15dcaf621c8ca74327fcc7a1110af09f88233813df513c3becc94751cb0b482fd809747469e69a665311d664d1414ad965a5e1001b1467a1becb7e0272a78c11508c5eaff91d188445d0c84e55fe2d1f1f2f68d83cf932192c2ff06e2c5da3f5306c5bbab10bf030084e295d9a5", + "58f366c7f2ba215ad687e0b1658d46dd883f3df0e3f83bcd5764558b9dcf1befb26eb09d6d814a61885d7f9f557e995fa7b3fbe7066ecd3548ae1ad77aecedccb3e1be1e21dfdd982996ed5616c9bd4c7f0b4b72b6d0adfdbca9d0759c963464c343fface01b22adfbc7e1e3daf12e1ec014b15d935e070c76f5731b531fd6ffa4459ad4b8ab682a8610c3b97b5967b21574fc4df83497dfaf642b8bd65b6ccc908147d23492b14b7ef37f4e9a1ab96f37ce95abe3913051bea4dfac7cb7dc1ff92317e87ca8fda20eb834b360a52b869710713a52386446c62de4b86ce1920503b2e87ef9d149286eeecc63d04873d72a060269609d3289dc118e1dcca2d409f9ec2f54d4d9e9b654c52aec121f80ef078b9bb0dfddc7cb995fffff31de789d3ab5f129f1be09e58685a51bc827df7da8918f66dfbfc95b80", + "58e7c05da3f3be6edd6a0638812f9c07ebdbc0d01803ff38e14e33ad2c4866b123962f1c503e87b7f10c2013ea856bb05bf807a6f7ac0173f35af1012a481dc09c6efb3fe7a3ed04767bcc6fd3c776add1149c6e7327edafaa7b0d712dd901365560b46412eab157ec1a56270da774b8e90b7c71149b0948e5bedce296f35b7c3d7c2a2a80d184f247bdd5c9caa4ddfaba5172e3558920e7a511cbb5e75eafa2e1f11ed3d948a50a4506be8ccaa4494c598cef0ff4fad6d9ba7d9ad73adef931709e74c80c20e8c92adf37b7f847954482231351f5192ddb0ba8934ec92b09b45614dd0a10cbba61a0926697236a9f2de9a6d1b56ea8008a3956ffe82158ab584ada221a44ef80a2f4525683886ed63aaed48f", + "58ea36b1092866af49f88133faa16deebef1290ca3c54f70d35db431431a0b3cc9000a1cb9ecaf4df25e7ec8499afed9f57bd2d0e450c6bd2fb7baa569ac5c554a1e1a6d2d7557e4595d0857880af6fc8c193925ecf54f71977642fe82ceb17fcb467579df29426d380ac9d0d60621f7584c2126472ceaa853df196e85a51e851c188c6411dcd5a7e43cad20a4ce6d7636f32b7601fa2457844a0e3727a38f512d36b2b0198d8699b8c32a200c332fcd32781aa65632bd838d51d889d70e8b3b31034b72c5755210960657edaa8fe2ba82cf0f7a631befa598c96d3112857cabc46b73fac922927c8062431cf6847454861907577c03be9f1feffcf9cc45258702deb7eaf36e72f480", + "58f443673a7fb2b56193aab900f2e9cbc4d9131d193b1f1c0adc94fe1ac0c2c31168652218d73ddb6a066979d4d29acaf6e34b62efa24ef3c4adc69d8952daad209d22e8a9c0b95e281fe9f6cf06f1e11a4724446176e2e6b38152e72ab9de190d779b56b3abf5117eea6863ffea06a03397c3499b472104e3914c93bdc071f9eeea86120f9af23ff6e071358d223b59d1648a1470fa8bdf4e009bc1f3d601fdf85460c7d4f2c882f59c1902efe20fd9f2ccac88994f6cfebbbca4a958c6cadc967590fb6d20117265b929972c11fb9657a928896772200d8df0e727021cd9aed06baa060ce52f8d9f688c4b82226e685db9a5d10dbb0a8a2c7578432e393cf36dce1fe3ff572dc822efe0" +] \ No newline at end of file diff --git a/wacore/src/voip/mlow/testdata/ref_120ms_expected.raw b/wacore/src/voip/mlow/testdata/ref_120ms_expected.raw new file mode 100644 index 0000000000000000000000000000000000000000..b52908acb2dd14e74d0504372c74a92c714a98f1 GIT binary patch literal 30720 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�(}^n)X?Bw&50+V=b8c`?wxu*fM^1PI0Vu z?65^mU6^L{Ji0der!F%!LaP^S6>Olk5nri;DznG(LqKN^B=rMmf&sh zMnn#F3u}d~VER%6=`b}59MET3BVUuR1t+5Y%Z|o>qFYfSv4G7W#}m6?QXJ$D7%#H> z%pN-qtI*HRr%rRmi;PXK*)U7m?3iQ6%ztxM@)s-{t)jn>=n^{@9_=6IF{xfjR%=N; zq>JJcLC%{lbQ69So(Y1Wcz%?}tCu`cXfPG?jFY=SySuzE+tQO#Re&Xp3LQb6z*k!Ok(eG}E2 z&|)Gz7O)7;_hx%ngeUk*1U~tX1%hEDIz9FrI)QECV-kxJCz8dI@3i5`$6C3>Xl*UD zu$Sqwb-feKb>HbeU`LVB*g>ozE*QpP^YAnHK4LLGo32mvh0`Zr5+X_xh1q9BnoN?7 z=>qyO{hXa{G(#ipFM0-Dl>b64g3o$7SA{OlbfmX}adj5I$+*$j$K2O?+Z3@m>`!gK zWY%=PcVs#yJNDR?TIU)&@?p}7EkHJ>*CxJ1?O|P@q-VFEm5a({<#VDWZ*lNs^hwAR<)a;f znccl@AVeCJT(|x#tA~84pE($C%}~zW7{(#Gn8w@?PU*eW9a7K%s5DOVNNl%m}1NW zW~*^4%*-#FR++C@?pc>xRcp*v)c&92jJ+YWS~r+SnQJjC=!NJ=L)TPwZAJJ+^jR<~ zR34`LW;H6Y>Ul9&QI+3eR&-CTA&>OV@Z9tDQvZ?p8> zdVT-*7W4}K6RsT_5!@FW8Jia~L@!4Az!d3KWNqw|_AfMfCuw(e|0I3MfvMxV+ekM! z!Lll`0k4ZJ#HZrdkxaBRJrRrId(bkF106-z<0j%Np)nr_$Sv8`#LsL#7ho&F>7td4 zqm3{4p42MtKe~tUH^>NTGE?Y5bOYmC4&x`8(#8ko&!&iTxcvfng8hyq_Q{SU=jErG zu27?J82P04C5t590kwNByj=O{F?nu)5q-7TP(Cic77BaHD!I}x>IeCm@{?3bUL%+B zA5*%31@xetfYnMbpWRnmp?z6C)ITy%5n2#Wf- zFUNS-M@J0OT+H#+0&gxp!G32nl6Ub%$jnsNWM1O?z_UPcZ%qZ2>xko&fUwHlT+9;N zh*PBv;(qCfD0z5gnkPf Date: Sat, 25 Jul 2026 17:20:25 -0300 Subject: [PATCH 02/17] chore(voip/mlow): make the decoder cross-check vectors regenerable The MLow fixtures were documented recipes pointing at binaries that live on nobody's machine. PROVENANCE.md described each oracle in prose, but the harnesses themselves were never in the tree, so refreshing a vector meant rediscovering how to drive the C reference. That cost real time: the encoder fails every call with SMPL_ENC_NO_GLOBAL_DATA unless smpl_CreateCodec() runs first, and opus_encode flattens that into a bare OPUS_INTERNAL_ERROR with no hint of the cause. Vendor the harness and a script that drives it. Running the script against a built reference reproduces the committed 120 ms fixtures byte for byte, so a regeneration that changes them is a real change rather than tool drift. Add a tripwire for the two halves of that vector. They come out of one run and mean nothing apart: refreshing only one leaves the comparison reading mismatched data, which shows up as a correlation number that moved rather than as an obvious failure. The test pins the PCM length against the frame count and pins that every frame is still the TOC the fixture exists to cover. Record honestly what the harness does NOT reproduce. Run at 60 ms with DTX off it emits the same TOC mix as the older capture fixture, but not the same bytes: packet sizes bracket the committed ones without landing on them, so the original encoder configuration was never recorded. Regenerating those would replace the vectors rather than reproduce them. --- scripts/mlow-vectors/mlow_frames.c | 130 ++++++++++++++++++++ scripts/regenerate-mlow-vectors.sh | 69 +++++++++++ wacore/src/voip/mlow/decoder.rs | 27 ++++ wacore/src/voip/mlow/testdata/PROVENANCE.md | 37 ++++-- 4 files changed, 252 insertions(+), 11 deletions(-) create mode 100644 scripts/mlow-vectors/mlow_frames.c create mode 100755 scripts/regenerate-mlow-vectors.sh diff --git a/scripts/mlow-vectors/mlow_frames.c b/scripts/mlow-vectors/mlow_frames.c new file mode 100644 index 000000000..c8d96fc8d --- /dev/null +++ b/scripts/mlow-vectors/mlow_frames.c @@ -0,0 +1,130 @@ +// Encode a raw PCM file as MLow/smpl packets and decode each one back through the same +// reference, so a single run produces both halves of a decoder cross-check vector. +// +// Output, one line per packet, on stdout: +// +// +// +// Diagnostics (packet index, byte count, TOC, sample count) go to stderr, so stdout can be +// redirected straight into a fixture builder. +// +// Two things are easy to get wrong and cost an afternoon each: +// +// * smpl_CreateCodec() must run before the first encode. Without it every encode fails with +// SMPL_ENC_NO_GLOBAL_DATA (-112), which opus_encode flattens into a bare OPUS_INTERNAL_ERROR +// (-3) with no hint that global tables were the problem. +// * The output buffer handed to opus_encode is a hard bound, not a capacity. Under CBR the +// encoder pads to exactly that size, so an oversized buffer fails the pad. Keep it near the +// real packet size. + +#include +#include +#include + +#include "opus.h" + +// Declared here rather than pulled from a private header: the reference exposes it as a plain +// symbol and the harness only needs the one entry point. +extern int smpl_CreateCodec(void); + +#define FS 16000 +#define MAX_PACKET 400 + +static void usage(const char *argv0) { + fprintf(stderr, + "usage: %s [packets] [bitrate]\n" + " input.raw s16le mono @ 16 kHz\n" + " frame_ms 10, 20, 60 or 120\n" + " packets how many to emit (default: until EOF)\n" + " bitrate encoder bitrate in bps (default 20000)\n", + argv0); +} + +int main(int argc, char **argv) { + if (argc < 3) { + usage(argv[0]); + return 2; + } + const char *path = argv[1]; + const int frame_ms = atoi(argv[2]); + const int want = argc > 3 ? atoi(argv[3]) : -1; + const int bitrate = argc > 4 ? atoi(argv[4]) : 20000; + + if (frame_ms != 10 && frame_ms != 20 && frame_ms != 60 && frame_ms != 120) { + fprintf(stderr, "error: frame_ms must be 10, 20, 60 or 120 (got %d)\n", frame_ms); + return 2; + } + const int samps = FS / 1000 * frame_ms; + + if (smpl_CreateCodec() != 0) { + fprintf(stderr, "error: smpl_CreateCodec failed\n"); + return 1; + } + + FILE *f = fopen(path, "rb"); + if (!f) { + perror("open input"); + return 2; + } + + int err = 0; + OpusEncoder *enc = opus_encoder_create(FS, 1, OPUS_APPLICATION_VOIP, &err); + if (err != OPUS_OK || !enc) { + fprintf(stderr, "error: encoder create: %s\n", opus_strerror(err)); + return 1; + } + opus_encoder_ctl(enc, OPUS_SET_USING_SMPL(1)); + opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate)); + opus_encoder_ctl(enc, OPUS_SET_FORCE_CHANNELS(1)); + + OpusDecoder *dec = opus_decoder_create(FS, 1, &err); + if (err != OPUS_OK || !dec) { + fprintf(stderr, "error: decoder create: %s\n", opus_strerror(err)); + return 1; + } + opus_decoder_ctl(dec, OPUS_SET_USING_SMPL(1)); + + opus_int16 *pcm = malloc((size_t)samps * sizeof(opus_int16)); + opus_int16 *out = malloc((size_t)samps * sizeof(opus_int16)); + unsigned char packet[MAX_PACKET]; + if (!pcm || !out) { + fprintf(stderr, "error: out of memory\n"); + return 1; + } + + int emitted = 0; + for (int n = 0; want < 0 || n < want; n++) { + if (fread(pcm, sizeof(opus_int16), (size_t)samps, f) != (size_t)samps) { + break; + } + opus_int32 len = opus_encode(enc, pcm, samps, packet, sizeof(packet)); + if (len <= 0) { + fprintf(stderr, "error: encode packet %d: %s\n", n, opus_strerror(len)); + return 1; + } + int ns = opus_decode(dec, packet, len, out, samps, 0); + if (ns <= 0) { + fprintf(stderr, "error: decode packet %d: %s\n", n, opus_strerror(ns)); + return 1; + } + for (opus_int32 i = 0; i < len; i++) { + printf("%02x", packet[i]); + } + printf(" "); + for (int i = 0; i < ns; i++) { + unsigned short v = (unsigned short)out[i]; + printf("%02x%02x", v & 0xff, (v >> 8) & 0xff); + } + printf("\n"); + fprintf(stderr, "packet %d: %d bytes, TOC 0x%02x, %d samples\n", n, len, packet[0], ns); + emitted++; + } + + fprintf(stderr, "emitted %d packet(s) of %d ms\n", emitted, frame_ms); + free(pcm); + free(out); + opus_encoder_destroy(enc); + opus_decoder_destroy(dec); + fclose(f); + return emitted > 0 ? 0 : 1; +} diff --git a/scripts/regenerate-mlow-vectors.sh b/scripts/regenerate-mlow-vectors.sh new file mode 100755 index 000000000..c63888f6d --- /dev/null +++ b/scripts/regenerate-mlow-vectors.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# Regenerate the MLow decoder cross-check fixtures from the `smpl` C reference. +# +# Consumers never run this — the fixtures are committed and the tests read them directly. It +# exists so the vectors stop being a documented recipe with a missing binary: everything needed +# to reproduce them, including the two non-obvious setup steps, lives in +# `scripts/mlow-vectors/mlow_frames.c`. +# +# Requires a built `smpl` reference (the WhatsApp MLow fork of libopus): +# +# cd "$MLOW_REFERENCE" && ./autogen.sh && ./configure --disable-shared --disable-doc \ +# --disable-extra-programs && make -j"$(nproc)" +# +# Then: +# +# MLOW_REFERENCE=/path/to/opus_mlow scripts/regenerate-mlow-vectors.sh +# +# Regenerating changes committed fixtures. Re-run the decoder suite afterwards and treat any +# correlation change as a finding, not as a number to paper over: +# +# cargo test -p wacore --features voip-mlow --lib + +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +testdata="$repo_root/wacore/src/voip/mlow/testdata" +harness_src="$repo_root/scripts/mlow-vectors/mlow_frames.c" + +ref="${MLOW_REFERENCE:-}" +if [[ -z "$ref" ]]; then + echo "error: set MLOW_REFERENCE to the built smpl/opus reference checkout" >&2 + exit 1 +fi +if [[ ! -f "$ref/.libs/libopus.a" ]]; then + echo "error: $ref/.libs/libopus.a not found; build the reference first (see the header of this script)" >&2 + exit 1 +fi + +work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT + +echo "==> building the harness against $ref" +cc -O2 \ + -I "$ref/include" -I "$ref/src" -I "$ref/celt" -I "$ref" \ + -o "$work/mlow_frames" "$harness_src" "$ref/.libs/libopus.a" -lm + +# One packet per line: " ". Split into the frames fixture and the +# reference PCM the decoder test compares against. +emit() { + local frame_ms="$1" packets="$2" frames_json="$3" pcm_raw="$4" + echo "==> ${frame_ms}ms x ${packets} -> $(basename "$frames_json"), $(basename "$pcm_raw")" + "$work/mlow_frames" "$testdata/synth_mic.raw" "$frame_ms" "$packets" > "$work/vectors.txt" + python3 - "$work/vectors.txt" "$frames_json" "$pcm_raw" <<'PY' +import json, sys +src, frames_path, pcm_path = sys.argv[1:4] +frames, pcm = [], bytearray() +for line in open(src): + payload, samples = line.split() + frames.append(payload) + pcm += bytes.fromhex(samples) +json.dump(frames, open(frames_path, "w"), indent=1) +open(pcm_path, "wb").write(pcm) +print(f" {len(frames)} frames, {len(pcm)//2} samples, TOCs: {sorted({f[:2] for f in frames})}") +PY +} + +emit 120 8 "$testdata/mlow_120ms_frames.json" "$testdata/ref_120ms_expected.raw" + +echo "==> done; re-run: cargo test -p wacore --features voip-mlow --lib" diff --git a/wacore/src/voip/mlow/decoder.rs b/wacore/src/voip/mlow/decoder.rs index c699ec47e..293e2df61 100644 --- a/wacore/src/voip/mlow/decoder.rs +++ b/wacore/src/voip/mlow/decoder.rs @@ -436,6 +436,33 @@ mod tests { assert!(!dec.had_error(), "the drop must not open the range decoder"); } + /// The two halves of a cross-check vector come out of one harness run and mean nothing apart: + /// refreshing only one leaves the comparison reading mismatched data, which surfaces as a + /// correlation number that moved rather than as an obvious error. Pin what ties them, and pin + /// that the fixture still exercises the multi-frame path it was added for. + #[test] + fn multi_frame_fixture_halves_stay_in_step() { + let frames: Vec = + serde_json::from_str(include_str!("testdata/mlow_120ms_frames.json")) + .expect("mlow_120ms_frames.json"); + let pcm_bytes = include_bytes!("testdata/ref_120ms_expected.raw").len(); + + assert!(!frames.is_empty(), "fixture is empty"); + for (i, f) in frames.iter().enumerate() { + let toc = hex::decode(f).expect("hex frame")[0]; + assert_eq!( + toc, 0x58, + "frame {i} is TOC {toc:#04x}, not the 120 ms packet this fixture exists to cover" + ); + } + assert_eq!( + pcm_bytes, + frames.len() * 6 * SMPL_INTF_LEN * 2, + "reference PCM does not match the frame count; regenerate both halves together with \ + scripts/regenerate-mlow-vectors.sh" + ); + } + /// The content check: decode a stream of real 120 ms packets and compare against the reference /// decoder's own output for the same bytes. Geometry alone is not enough, since running the loop /// the wrong number of times would still produce plausibly-shaped audio while consuming the diff --git a/wacore/src/voip/mlow/testdata/PROVENANCE.md b/wacore/src/voip/mlow/testdata/PROVENANCE.md index 6d516a261..19b2db523 100644 --- a/wacore/src/voip/mlow/testdata/PROVENANCE.md +++ b/wacore/src/voip/mlow/testdata/PROVENANCE.md @@ -79,17 +79,32 @@ exact wire bytes (config-1 `0x10` and config-2 `0x12` frames included). The trip committed stream still carries `0x10`, `0x12`, and `0x50` TOCs so the per-config decode branches stay covered; regenerating it requires the external encoder above on `synth_mic.raw`. -## Multi-frame (120 ms) packets +## Multi-frame (120 ms) packets — regenerable in one command | fixture | consumer / test | oracle recipe | | --- | --- | --- | -| `mlow_120ms_frames.json` | `decoder.rs::multi_frame_decode_matches_the_reference` | `smpl` C reference encoding `synth_mic.raw` at 120 ms, hex frames | -| `ref_120ms_expected.raw` | same test | the same C reference decoding those frames; s16le @ 16 kHz | - -Also not Rust-reproducible: this crate's encoder only emits 60 ms packets. Both files come from one -run of a harness linked against the `smpl` C reference, which encodes `synth_mic.raw` in 1920-sample -(120 ms) frames and decodes each packet back, emitting ` ` per line. The -encoder needs `smpl_CreateCodec()` before the first `opus_encode` (it fails -`SMPL_ENC_NO_GLOBAL_DATA` otherwise), `OPUS_SET_USING_SMPL(1)`, and a `max_data_bytes` the CBR pad -can satisfy. Every frame is TOC `0x58`, which the test asserts so the fixture cannot silently drift -off the multi-frame path. +| `mlow_120ms_frames.json` | `decoder.rs::multi_frame_decode_matches_the_reference` | `scripts/regenerate-mlow-vectors.sh` | +| `ref_120ms_expected.raw` | same test | same run of the same script | + +```sh +MLOW_REFERENCE=/path/to/opus_mlow scripts/regenerate-mlow-vectors.sh +``` + +The harness it builds lives in `scripts/mlow-vectors/mlow_frames.c`: it encodes `synth_mic.raw` at +the requested duration through the `smpl` C reference and decodes each packet back, emitting both +halves of the vector in one pass. Verified to reproduce the committed files byte for byte, so a +regeneration that changes them is a real change and not tool drift. + +Both halves must be regenerated together — `decoder.rs::multi_frame_fixture_halves_stay_in_step` +fails if the PCM length stops matching the frame count, and asserts every frame is still TOC `0x58` +so the fixture cannot quietly drift off the multi-frame path. + +## What is still not reproducible, and why + +The fixtures above the multi-frame section predate the harness and were produced by tools that no +longer exist here. The harness reproduces their SHAPE — run at 60 ms with DTX off it emits the same +TOC mix as `inbound_capture_frames.json` (13x `0x10`, 2x `0x12`, 95x `0x50`) — but not their exact +bytes: packet sizes bracket the committed ones without landing on them, so the original run used an +encoder configuration (or reference build) that was not recorded. Regenerating them would therefore +REPLACE those vectors rather than reproduce them, which is a deliberate decision to make with the +correlation thresholds in hand, not a mechanical refresh. From fb71117c3898f745384de152222f94e7411808f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= <55464917+jlucaso1@users.noreply.github.com> Date: Sat, 25 Jul 2026 17:42:14 -0300 Subject: [PATCH 03/17] chore(voip/mlow): fail a short vector regeneration instead of committing it Review found the hole this tooling was supposed to close. The harness exited successfully whenever it emitted at least one packet, so a truncated input or a read error produced a shorter vector and the script overwrote both committed fixtures with it. The tripwire did not catch that either: it checked the PCM length against the frame count, a relative property that a shorter vector still satisfies. Treat a short count as the failure it is, distinguishing a read error from a clean EOF, and assert the absolute packet count in the tripwire so a shrunken fixture fails loudly. Also pin the oracle. The claim that a changed fixture means a real change only holds against the reference revision the bytes came from, which was never recorded; a different checkout could move the output and the text told the reader that could not happen. Name the revision and have the script report the one it built against, warning when they differ. --- scripts/mlow-vectors/mlow_frames.c | 25 ++++++++++++++++++++- scripts/regenerate-mlow-vectors.sh | 19 ++++++++++++++++ wacore/src/voip/mlow/decoder.rs | 9 +++++++- wacore/src/voip/mlow/testdata/PROVENANCE.md | 9 ++++++-- 4 files changed, 58 insertions(+), 4 deletions(-) diff --git a/scripts/mlow-vectors/mlow_frames.c b/scripts/mlow-vectors/mlow_frames.c index c8d96fc8d..0eb9cc08c 100644 --- a/scripts/mlow-vectors/mlow_frames.c +++ b/scripts/mlow-vectors/mlow_frames.c @@ -93,8 +93,16 @@ int main(int argc, char **argv) { } int emitted = 0; + int short_read = 0; for (int n = 0; want < 0 || n < want; n++) { if (fread(pcm, sizeof(opus_int16), (size_t)samps, f) != (size_t)samps) { + // A partial frame at EOF, or a read error. Either way this run cannot produce the + // vector that was asked for, and callers overwrite committed fixtures with whatever + // comes out of it -- so remember it and fail below rather than emit a short vector. + short_read = 1; + if (ferror(f)) { + perror("read input"); + } break; } opus_int32 len = opus_encode(enc, pcm, samps, packet, sizeof(packet)); @@ -121,10 +129,25 @@ int main(int argc, char **argv) { } fprintf(stderr, "emitted %d packet(s) of %d ms\n", emitted, frame_ms); + + // Exiting 0 with fewer packets than requested would let a regeneration quietly replace a + // fixture with a smaller one: the relative length checks downstream stay satisfied, so the + // coverage loss is invisible. A short count is a failure, not a partial success. + int ok = 1; + if (emitted == 0) { + fprintf(stderr, "error: no packets emitted\n"); + ok = 0; + } else if (want >= 0 && emitted != want) { + fprintf(stderr, + "error: asked for %d packet(s) but the input only yielded %d%s\n", + want, emitted, short_read ? " (short read)" : ""); + ok = 0; + } + free(pcm); free(out); opus_encoder_destroy(enc); opus_decoder_destroy(dec); fclose(f); - return emitted > 0 ? 0 : 1; + return ok ? 0 : 1; } diff --git a/scripts/regenerate-mlow-vectors.sh b/scripts/regenerate-mlow-vectors.sh index c63888f6d..d663f6efc 100755 --- a/scripts/regenerate-mlow-vectors.sh +++ b/scripts/regenerate-mlow-vectors.sh @@ -6,6 +6,14 @@ # to reproduce them, including the two non-obvious setup steps, lives in # `scripts/mlow-vectors/mlow_frames.c`. # +# The committed fixtures were produced with this exact oracle: +# +# https://github.com/edgardmessias/opus_mlow at 84b076e0809412df22e8a0d26f944610c4a3e40f +# +# Byte-for-byte reproduction is only claimed against that revision. A different checkout can +# change the output because the ORACLE changed, which is not the same thing as a decoder change, +# so the script prints the revision it actually built against and warns when it differs. +# # Requires a built `smpl` reference (the WhatsApp MLow fork of libopus): # # cd "$MLOW_REFERENCE" && ./autogen.sh && ./configure --disable-shared --disable-doc \ @@ -36,6 +44,17 @@ if [[ ! -f "$ref/.libs/libopus.a" ]]; then exit 1 fi +# Recorded so a regeneration says which oracle produced it; see the header. +expected_rev="84b076e0809412df22e8a0d26f944610c4a3e40f" +actual_rev="$(git -C "$ref" rev-parse HEAD 2>/dev/null || echo unknown)" +if [[ "$actual_rev" == "unknown" ]]; then + echo "warning: $ref is not a git checkout; cannot confirm the oracle revision" >&2 +elif [[ "$actual_rev" != "$expected_rev" ]]; then + echo "warning: oracle is $actual_rev, fixtures were generated with $expected_rev" >&2 + echo " differences below may come from the reference, not from this repository" >&2 +fi +echo "==> oracle: $ref @ $actual_rev" + work="$(mktemp -d)" trap 'rm -rf "$work"' EXIT diff --git a/wacore/src/voip/mlow/decoder.rs b/wacore/src/voip/mlow/decoder.rs index 293e2df61..1e9785501 100644 --- a/wacore/src/voip/mlow/decoder.rs +++ b/wacore/src/voip/mlow/decoder.rs @@ -447,7 +447,14 @@ mod tests { .expect("mlow_120ms_frames.json"); let pcm_bytes = include_bytes!("testdata/ref_120ms_expected.raw").len(); - assert!(!frames.is_empty(), "fixture is empty"); + // An absolute count, not just "non-empty": a regeneration that ends early produces a + // shorter vector whose PCM length still matches its own frame count, so a relative check + // would accept it and the coverage loss would be invisible. + assert_eq!( + frames.len(), + 8, + "fixture no longer holds the 8 packets it was generated with; a short regeneration silently reduces coverage" + ); for (i, f) in frames.iter().enumerate() { let toc = hex::decode(f).expect("hex frame")[0]; assert_eq!( diff --git a/wacore/src/voip/mlow/testdata/PROVENANCE.md b/wacore/src/voip/mlow/testdata/PROVENANCE.md index 19b2db523..423e25d7d 100644 --- a/wacore/src/voip/mlow/testdata/PROVENANCE.md +++ b/wacore/src/voip/mlow/testdata/PROVENANCE.md @@ -92,8 +92,13 @@ MLOW_REFERENCE=/path/to/opus_mlow scripts/regenerate-mlow-vectors.sh The harness it builds lives in `scripts/mlow-vectors/mlow_frames.c`: it encodes `synth_mic.raw` at the requested duration through the `smpl` C reference and decodes each packet back, emitting both -halves of the vector in one pass. Verified to reproduce the committed files byte for byte, so a -regeneration that changes them is a real change and not tool drift. +halves of the vector in one pass. + +The committed bytes were produced against one specific oracle — +`github.com/edgardmessias/opus_mlow` at `84b076e0809412df22e8a0d26f944610c4a3e40f`. Reproduction is +byte for byte **against that revision**, which is what makes a changed fixture a real change rather +than tool drift; against a different checkout the reference itself may have moved, so the script +prints the revision it built with and warns when it does not match. Both halves must be regenerated together — `decoder.rs::multi_frame_fixture_halves_stay_in_step` fails if the PCM length stops matching the frame count, and asserts every frame is still TOC `0x58` From feb1d0eec5842ff926809b02677f2c0a16561ab8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= <55464917+jlucaso1@users.noreply.github.com> Date: Sat, 25 Jul 2026 17:34:56 -0300 Subject: [PATCH 04/17] fix(voip/mlow): decode frames coded inactive instead of silencing them The top field of the smpl TOC is a two-bit SID/VoA selector, not a pair of flags. Only 10 is a SID; 00 means "normal frame sent while there is no voice activity", which is what a peer with DTX off sends through every pause. The decoder treated anything that was not active-voice as silence and returned a zero slot, so that background noise was dropped on the floor. Measured against the reference on a stream encoded with DTX off: the coded-inactive frames are 12% of the samples, where the reference produces 0.047 rms and we produced exactly 0. Overall correlation with the reference rises from 0.969 to 0.9999 once they decode. Two symbols are gated on the same flag in the reference and are absent from the bitstream when it is clear: the voicing symbol (such a frame is unvoiced by definition) and the LSF interpolation index. Reading either would consume symbols that were never written and desync every field after them, so the LSF decode takes the flag and skips them, and the pulse decode selects its table with the real value rather than a hardcoded 1. Two existing tests changed, both because they pinned the old behavior: - the SID test used TOC 0x00, which is coded-inactive rather than SID, and now uses 0x80. What it asserts is unchanged. - decoder_silence_frames_produce_zero asserted over every near-silent frame in e2e_vectors.json. That fixture had its inactive-TOC frames ZEROED when it was generated, to match the behavior this commit removes, so those entries are not an oracle for silence. The assertion now covers real SID frames, and the coded-inactive path is covered against an unmodified vector instead. --- scripts/regenerate-mlow-vectors.sh | 3 + wacore/src/voip/mlow/decoder.rs | 112 ++++++++++++++---- wacore/src/voip/mlow/encode.rs | 1 + wacore/src/voip/mlow/quality_tests.rs | 9 +- wacore/src/voip/mlow/smpl_celpdec.rs | 2 +- wacore/src/voip/mlow/smpl_decode.rs | 27 ++++- wacore/src/voip/mlow/smpl_gains.rs | 2 +- wacore/src/voip/mlow/smpl_pitch.rs | 2 +- wacore/src/voip/mlow/smpl_pulse.rs | 2 +- .../mlow/testdata/mlow_dtx_off_frames.json | 112 ++++++++++++++++++ .../mlow/testdata/ref_dtx_off_expected.raw | Bin 0 -> 211200 bytes 11 files changed, 238 insertions(+), 34 deletions(-) create mode 100644 wacore/src/voip/mlow/testdata/mlow_dtx_off_frames.json create mode 100644 wacore/src/voip/mlow/testdata/ref_dtx_off_expected.raw diff --git a/scripts/regenerate-mlow-vectors.sh b/scripts/regenerate-mlow-vectors.sh index d663f6efc..7ebf873dd 100755 --- a/scripts/regenerate-mlow-vectors.sh +++ b/scripts/regenerate-mlow-vectors.sh @@ -84,5 +84,8 @@ PY } emit 120 8 "$testdata/mlow_120ms_frames.json" "$testdata/ref_120ms_expected.raw" +# 60 ms with DTX off: the encoder emits VoA=00 frames (TOC 0x10) for the silent stretches of +# synth_mic.raw, which is the only in-repo coverage of the DTX-off decode path. +emit 60 110 "$testdata/mlow_dtx_off_frames.json" "$testdata/ref_dtx_off_expected.raw" echo "==> done; re-run: cargo test -p wacore --features voip-mlow --lib" diff --git a/wacore/src/voip/mlow/decoder.rs b/wacore/src/voip/mlow/decoder.rs index 1e9785501..c9d47f00c 100644 --- a/wacore/src/voip/mlow/decoder.rs +++ b/wacore/src/voip/mlow/decoder.rs @@ -121,13 +121,16 @@ impl MlowDecoder { ); return vec![0.0; out_len]; } - // Inactive / SID (DTX/CNG) frames carry no decodable voice and are silenced without opening the - // range coder, so their geometry can never desync. Handle them before the operating-point guard: - // otherwise an inactive off-point frame (e.g. the 10ms startup silence a real peer emits before - // speech) would trip the loud "dropped" canary instead of being the benign silence it is. A full - // 60ms slot keeps the playout cadence regardless of the frame's nominal duration. - if toc.sid || !toc.active { - log::debug!("mlow: DTX/SID TOC 0x{:02x} -> 60ms silence", frame[0]); + // A SID (DTX/CNG) frame carries comfort noise rather than coded voice and is silenced without + // opening the range coder, so its geometry can never desync. Handle it before the + // operating-point guard so an off-point SID is the benign silence it is rather than tripping + // the "dropped" canary. A full 60ms slot keeps the playout cadence regardless of the frame's + // nominal duration. + // + // A frame that is merely coded inactive is NOT silence: with DTX off the encoder keeps sending + // background noise this way, and the reference decodes it. It goes through the normal path. + if toc.sid { + log::debug!("mlow: SID TOC 0x{:02x} -> 60ms silence", frame[0]); return vec![0.0; OPUS_FRAME_SAMPS]; } // Operating-point guard for active frames: a different internal rate, the low_rate=1 2x160 @@ -158,10 +161,18 @@ impl MlowDecoder { return vec![0.0; OPUS_FRAME_SAMPS]; } let frames = frames.expect("the guard above rejected every unsupported duration"); - self.decode_active_frame(frame, frames * SMPL_INTF_LEN, frames) + self.decode_active_frame(frame, frames * SMPL_INTF_LEN, frames, toc.active) } - fn decode_active_frame(&mut self, frame: &[u8], out_len: usize, frames: usize) -> Vec { + /// `coded_as_active_voice` gates two symbols that a frame coded inactive never puts on the wire; + /// reading them would consume symbols that were never written and desync everything after. + fn decode_active_frame( + &mut self, + frame: &[u8], + out_len: usize, + frames: usize, + coded_as_active_voice: bool, + ) -> Vec { let config = (frame[0] >> 2) as usize & 1; let tbl = load_smpl_tables(); let synth_t = load_smpl_synth_tables(); @@ -178,13 +189,20 @@ impl MlowDecoder { let mut packet_lags: Vec = Vec::with_capacity(frames * 8); let mut avg_norm_br = 0.0f32; for f in 0..frames { - let lsf = decode_smpl_lsf(&mut dec, tbl, &mut self.state.lstate, config, f); + let lsf = decode_smpl_lsf( + &mut dec, + tbl, + &mut self.state.lstate, + config, + f, + coded_as_active_voice, + ); let pulses = decode_smpl_pulses( &mut dec, cc, SMPL_INTF_LEN as i32, 4, - 1, + i32::from(coded_as_active_voice), config as i32, lsf.stage1, ); @@ -322,7 +340,7 @@ pub(crate) fn diag_decode_params() -> Vec { let config = (frame[0] >> 2) as usize & 1; let mut dec = RangeDecoder::new(&frame[1..]); for f in 0..3 { - let lsf = decode_smpl_lsf(&mut dec, tbl, &mut lstate, config, f); + let lsf = decode_smpl_lsf(&mut dec, tbl, &mut lstate, config, f, true); let pulses = decode_smpl_pulses( &mut dec, cc, @@ -436,6 +454,57 @@ mod tests { assert!(!dec.had_error(), "the drop must not open the range decoder"); } + /// A `VoA=00` packet is a normal frame carrying background noise, not a SID: the reference + /// decodes it, and with DTX off a peer sends nothing else during a pause. Silencing it drops + /// ~12% of a real stream on the floor while the call merely sounds quiet. + #[test] + fn dtx_off_frames_decode_to_audio() { + let frames: Vec = + serde_json::from_str(include_str!("testdata/mlow_dtx_off_frames.json")) + .expect("mlow_dtx_off_frames.json"); + let refp: Vec = include_bytes!("testdata/ref_dtx_off_expected.raw") + .chunks_exact(2) + .map(|b| i16::from_le_bytes([b[0], b[1]]) as f32 / 32768.0) + .collect(); + + let mut dec = MlowDecoder::new(); + let mut out: Vec = Vec::new(); + let mut spans = Vec::new(); + for hex_frame in &frames { + let frame = hex::decode(hex_frame).unwrap(); + let start = out.len(); + out.extend_from_slice(&dec.decode(&frame)); + // The frames this covers: not SID, VoA=0, hang-over clear, i.e. coded_as_active_voice + // == 0. `0x12` shares VoA=0 but sets the hang-over bit, which makes it active and + // already decoded, so it must not be counted here. + if frame[0] & 0xC2 == 0 { + spans.push((start, out.len())); + } + } + assert!( + !spans.is_empty(), + "fixture lost its DTX-off frames: this path is no longer covered" + ); + assert_eq!(out.len(), refp.len()); + + let energy = |v: &[f32]| v.iter().map(|&x| (x as f64).powi(2)).sum::(); + let (mut e_ref, mut e_ours, mut n) = (0.0, 0.0, 0usize); + for (s, e) in &spans { + e_ref += energy(&refp[*s..*e]); + e_ours += energy(&out[*s..*e]); + n += e - s; + } + let (rms_ref, rms_ours) = ((e_ref / n as f64).sqrt(), (e_ours / n as f64).sqrt()); + assert!( + rms_ref > 0.01, + "the reference itself has no audio here; the fixture is wrong" + ); + assert!( + rms_ours > rms_ref * 0.5, + "DTX-off frames decoded to {rms_ours:.5} rms against the reference's {rms_ref:.5}" + ); + } + /// The two halves of a cross-check vector come out of one harness run and mean nothing apart: /// refreshing only one leaves the comparison reading mismatched data, which surfaces as a /// correlation number that moved rather than as an obvious error. Pin what ties them, and pin @@ -686,13 +755,12 @@ mod tests { ); } - // An inactive / DTX frame (TOC 0x00: vad=false so active=false, 16kHz, low_rate=0, 10ms) is the - // benign startup/comfort silence a real peer emits, not a desync hazard: inactive frames are - // silenced without opening the range coder regardless of geometry. It must take the quiet DTX/SID - // path (a full 60ms silence slot, range coder untouched) and must NOT count as an out-of-operating - // -point drop, which is reserved for active frames that would have lost decodable audio. + // A SID frame (TOC 0x80, the DTX/CNG marker) is comfort noise, not a desync hazard: it is + // silenced without opening the range coder regardless of geometry. It must take the quiet path (a + // full 60ms silence slot, range coder untouched) and must NOT count as an out-of-operating-point + // drop, which is reserved for frames that would have lost decodable audio. #[test] - fn inactive_off_point_frame_is_silenced_not_dropped() { + fn sid_frame_is_silenced_not_dropped() { let frames: Vec = serde_json::from_str(include_str!("testdata/inbound_capture_frames.json")).unwrap(); let real = hex::decode(&frames[0]).unwrap(); @@ -703,8 +771,8 @@ mod tests { "a real low_rate=0 frame must decode to audio" ); - // TOC 0x00 -> inactive: silenced via the DTX/SID path, not the operating-point drop. - let inactive = dec.decode(&[0x00, 0xAA, 0xBB, 0xCC]); + // TOC 0x80 -> SID: silenced via the comfort-noise path, not the operating-point drop. + let inactive = dec.decode(&[0x80, 0xAA, 0xBB, 0xCC]); assert_eq!( inactive.len(), 960, @@ -716,11 +784,11 @@ mod tests { ); assert_eq!( dec.dropped_unsupported, 0, - "an inactive frame is the DTX/silence path, not an operating-point drop" + "a SID frame is the comfort-noise path, not an operating-point drop" ); assert!( !dec.had_error(), - "the inactive path must not open the range decoder" + "the SID path must not open the range decoder" ); // Contrast: an active off-point frame (0x60 = vad=true, 32 kHz) IS counted, proving the drop diff --git a/wacore/src/voip/mlow/encode.rs b/wacore/src/voip/mlow/encode.rs index 350e6f418..167007987 100644 --- a/wacore/src/voip/mlow/encode.rs +++ b/wacore/src/voip/mlow/encode.rs @@ -660,6 +660,7 @@ mod tests { &mut lstate, config, f, + true, ); let pulses = super::super::smpl_pulse::decode_smpl_pulses( &mut dec, diff --git a/wacore/src/voip/mlow/quality_tests.rs b/wacore/src/voip/mlow/quality_tests.rs index 0ca2c93a8..2757e4a40 100644 --- a/wacore/src/voip/mlow/quality_tests.rs +++ b/wacore/src/voip/mlow/quality_tests.rs @@ -660,8 +660,13 @@ fn decoder_silence_frames_produce_zero() { let ref_r = rms(&want); let got = dec.decode(&frame); let rust_r = rms(&got); - // A frame the reference decodes to near-silence must also be near-silence in Rust. - if ref_r < 0.001 { + // Only a SID frame is genuinely silence. This fixture also holds coded-inactive frames + // (VoA=0, hang-over clear) whose PCM was ZEROED when it was generated, to match a decoder + // that routed them to silence. The reference decodes those to background noise, so they are + // not an oracle for this property and asserting over them would re-pin the old behavior; + // `decoder.rs::dtx_off_frames_decode_to_audio` covers them against an unmodified vector. + let is_sid = frame[0] & 0x80 != 0; + if ref_r < 0.001 && is_sid { assert!( rust_r < 0.001, "frame {i}: reference is silence (RMS={ref_r:.6}) but Rust produced RMS={rust_r:.6}" diff --git a/wacore/src/voip/mlow/smpl_celpdec.rs b/wacore/src/voip/mlow/smpl_celpdec.rs index 1e55e1599..a9e74209f 100644 --- a/wacore/src/voip/mlow/smpl_celpdec.rs +++ b/wacore/src/voip/mlow/smpl_celpdec.rs @@ -541,7 +541,7 @@ mod tests { let low_rate = (frame[0] >> 2) & 1 != 0; let mut dec = crate::voip::mlow::rangecoder::RangeDecoder::new(&frame[1..]); for f in 0..3 { - let lsf = decode_smpl_lsf(&mut dec, tbl, &mut lstate, config, f); + let lsf = decode_smpl_lsf(&mut dec, tbl, &mut lstate, config, f, true); let pulses = decode_smpl_pulses(&mut dec, cc, 320, 4, 1, config as i32, lsf.stage1); let voiced = lsf.stage1 == 1; let mut params = CelpDecParams { diff --git a/wacore/src/voip/mlow/smpl_decode.rs b/wacore/src/voip/mlow/smpl_decode.rs index db8c4bce4..3a9e48516 100644 --- a/wacore/src/voip/mlow/smpl_decode.rs +++ b/wacore/src/voip/mlow/smpl_decode.rs @@ -87,6 +87,7 @@ pub(crate) fn decode_smpl_lsf( st: &mut SmplLsfState, config: usize, intf: usize, + coded_as_active_voice: bool, ) -> SmplLsfIndices { let mut idx = SmplLsfIndices { stage1: 0, @@ -95,8 +96,13 @@ pub(crate) fn decode_smpl_lsf( extra: 0, }; - // Read 1: stage-1 selector. The first internal frame uses the dedicated row 0; later frames - // pick row 1/2 by the previous frame's stage-1 result. + // Read 1: the voicing symbol, whose value doubles as the stage-1 selector. The first internal + // frame uses the dedicated row 0; later frames pick row 1/2 by the previous frame's result. + // + // It is only on the wire when the frame was coded as active voice. A frame coded inactive + // (DTX off, so the encoder still sends background noise) carries no voicing symbol at all and + // is unvoiced by definition; reading one would consume a symbol that was never written and + // desync every field after it. let sel = if intf == 0 { 0 } else if st.prev_stage1 != 0 { @@ -104,7 +110,11 @@ pub(crate) fn decode_smpl_lsf( } else { 1 }; - let stage1 = dec.decode_cdf(&t.lsf_sel[sel]); + let stage1 = if coded_as_active_voice { + dec.decode_cdf(&t.lsf_sel[sel]) + } else { + 0 + }; idx.stage1 = stage1; // match := enter_match && stage1 == prev_stage1. enter_match is false for the first internal @@ -143,8 +153,13 @@ pub(crate) fn decode_smpl_lsf( idx.stage2[k] = dec.decode_cdf(c); } - // "Extra" LSF read: a 3-symbol static CDF, always fires for our path (p4=1, num_subfr>=2). - idx.extra = dec.decode_cdf(&t.lsf_extra); + // The LSF interpolation index, on the wire only for an active-voice frame with more than one + // subframe (the reference gates it on the same flag as the voicing symbol). + idx.extra = if coded_as_active_voice { + dec.decode_cdf(&t.lsf_extra) + } else { + 0 + }; log::trace!( "mlow LSF intf={intf} sel={sel} m={m}: stage1={stage1} grid={grid} extra={} stage2={:?}", @@ -172,7 +187,7 @@ mod tests { let frame = hex::decode(rec["frame"].as_str().unwrap()).unwrap(); let mut st = SmplLsfState::default(); let mut dec = RangeDecoder::new(&frame[1..]); - let idx = decode_smpl_lsf(&mut dec, t, &mut st, 0, 0); + let idx = decode_smpl_lsf(&mut dec, t, &mut st, 0, 0, true); assert_eq!(idx.stage1, rec["stage1"].as_i64().unwrap() as i32, "stage1"); assert_eq!(idx.grid, rec["grid"].as_i64().unwrap() as i32, "grid"); assert_eq!(idx.extra, rec["extra"].as_i64().unwrap() as i32, "extra"); diff --git a/wacore/src/voip/mlow/smpl_gains.rs b/wacore/src/voip/mlow/smpl_gains.rs index bf9316aa5..ac99fbe61 100644 --- a/wacore/src/voip/mlow/smpl_gains.rs +++ b/wacore/src/voip/mlow/smpl_gains.rs @@ -94,7 +94,7 @@ mod tests { let frame = hex::decode(rec["frame"].as_str().unwrap()).unwrap(); let mut st = SmplLsfState::default(); let mut dec = RangeDecoder::new(&frame[1..]); - let lsf = decode_smpl_lsf(&mut dec, tbl, &mut st, 0, 0); + let lsf = decode_smpl_lsf(&mut dec, tbl, &mut st, 0, 0, true); let pulses = decode_smpl_pulses(&mut dec, cc, 320, 4, 1, 0, lsf.stage1); let g = decode_smpl_gains(&mut dec, cc, 4, pulses.subfr); assert_eq!(g.gain_q.to_vec(), as_i32(&rec["gain_q"]), "gain_q"); diff --git a/wacore/src/voip/mlow/smpl_pitch.rs b/wacore/src/voip/mlow/smpl_pitch.rs index 0f65a3df6..06a56731c 100644 --- a/wacore/src/voip/mlow/smpl_pitch.rs +++ b/wacore/src/voip/mlow/smpl_pitch.rs @@ -248,7 +248,7 @@ mod tests { let frame = hex::decode(rec["frame"].as_str().unwrap()).unwrap(); let mut st = SmplLsfState::default(); let mut dec = RangeDecoder::new(&frame[1..]); - let lsf = decode_smpl_lsf(&mut dec, tbl, &mut st, 0, 0); + let lsf = decode_smpl_lsf(&mut dec, tbl, &mut st, 0, 0, true); let pulses = decode_smpl_pulses(&mut dec, cc, 320, 4, 1, 0, lsf.stage1); let pr = decode_smpl_pitch(&mut dec, mem, cc, &mut st, 320, 4, 0, pulses.subfr); diff --git a/wacore/src/voip/mlow/smpl_pulse.rs b/wacore/src/voip/mlow/smpl_pulse.rs index b73e8a720..bed9c8b1b 100644 --- a/wacore/src/voip/mlow/smpl_pulse.rs +++ b/wacore/src/voip/mlow/smpl_pulse.rs @@ -230,7 +230,7 @@ mod tests { let frame = hex::decode(rec["frame"].as_str().unwrap()).unwrap(); let mut st = SmplLsfState::default(); let mut dec = RangeDecoder::new(&frame[1..]); - let lsf = decode_smpl_lsf(&mut dec, tbl, &mut st, 0, 0); + let lsf = decode_smpl_lsf(&mut dec, tbl, &mut st, 0, 0, true); let pr = decode_smpl_pulses(&mut dec, cc, 320, 4, 1, 0, lsf.stage1); let want_subfr: Vec = rec["subfr"] diff --git a/wacore/src/voip/mlow/testdata/mlow_dtx_off_frames.json b/wacore/src/voip/mlow/testdata/mlow_dtx_off_frames.json new file mode 100644 index 000000000..ebf24d3b1 --- /dev/null +++ b/wacore/src/voip/mlow/testdata/mlow_dtx_off_frames.json @@ -0,0 +1,112 @@ +[ + "50e5638cd7b84c934ad6200696fdd57ad59328d16487059c4ceba9a663aee2f2a8539717c9ad61003c481cd88b7d53671dd9d922348facd652c3412a29fe0b104781cdae50ffe628069d82b85803f440b7dfa3ba14723358977cdddd4ad455f18ca2f590f8ccbc3fc731094450bf791141188de1b468dba40ccbbc237691b0e9ba28c7f8b8a7bb33123f170de08ca27cdef0bdab26e36a432646ab8bcdedf0e368cd658b7c89229b", + "50e5ea1b95063cba6dc37c99cf062a4de197e4104843dfebaefb87c9d086dd120fd455bdcaec543576130ff01f81e469313f922c24f1bb41a2b5b746c715412113a0d05ea1c96c4f88ae2a690b0c441f0519c07eacb8a7ffa5e95df245cbcea5b98cc6ab0cee4e37abe45dfec39d6c2b4a726468a878995d7d6a6fd16a206891aebc8c9c8be9105d980ae149c8863e0dc0f11e24fac91692eccfa34b4ad3e66b72f3bd2499c52204de4021dcb0ad", + "50e9e778031e323073ed64258f42ae12c7a7e56983a845d434af5dee10a23dbbbfd3d21caf4d604f759f10bf6aa2b3e0efe7a0e8fec315a1a39902d516598f3fa648f2514a25a26af0214d7be13ab3c924bb48a446836967958a55041538f05e8fada05891166e2fe1ec7f2b383af55cb6f426dc478f36004419c2d4834962a20cfe8ba00ab4020cfbfca91ad1c3f76dd0006289f3dd61d8c3c235dd8c2417f5ba0637aa6dddb78119cabe896507c8", + "50ea2545f41f9beb23e2bd1591f617d46f6080876c9f1d4af60ba3ad2aae72ba1f6ad57e431386640f635eca6808243fb21ef05125f97c725016d4091638065f485cd6347c0a8ffa1a3751a98ed6d135e3026e3f4b5bfbca87c28092572918529422d7e005323f792d9bbeb75895641b4ce1560b1e747d04c5f97a5a93f41d81fe0b8c5cecc81d04cfded9b43b1f654fc876c90caf63cafa2eba4421a326dc377bc8a4de595e703c9723c49e", + "50e5638c50bb752418cef3cc05c6357b3a0ad86c79e5f5a9ef9ffb871b04eaf4cfbbd7abd16a65a87a625d4f87af770c039e1121c8cd9e9b41a54bc4fc822f98ab34f20e684b55f1702ae5e6dc725bb165fecaa787b076981a934b5c2c0fe86ce6d9a5f2d82c490cabeaaf811c4b57e9f59eefc9be64d97022a416dc73657297f7c26d39f04fe9ecc6e1cdacddfa7e4c59f13d99e97169b69b1a53962724dc174a4f75ca0bf2e24e91385a70b2", + "50e5638c50a91fa376fe4209e4a06a40107bee6fa90d29b30bfd5e8db89cef776962dae7c5db72874737f92cc82d7b07e6d94657f7a7e3fa970af46abd8fec8b2fbf552e48c6ab91c5123431768d94667b08f2af074c8466b97ca02db5b074fd3f351f09bdf2ad45cf7580270ef0afb0c9be125a967b66a0fa400c96896888a6f37fdc8b9f911242bacabffb2afea8b3e192bf20937ebe49bac0196deb6eb69881ae0d849a034d69294917e1a3", + "50e5ea1b94eb403df19200cba4edc8d690d5b704357bdc3f8ca8784261efe7b93e26efe590cad8fc9ce40260b2d69e18fd0fe8efa45ed499ce2252c3abc5b7f20211a8d328cc17bf007b589c8fdfc713078df59b1817e6ed0d9aa41a3a120c13ab490193d59871e0a3f6a9bc4db64c6357d36c048e6294f0ed608d8de2104001c55f8a104602fee437648fa37e1c0d5814869eb76dbb5bcac8f48a8e0eecb29e5300b15235a7cfc819e0", + "50e6f65847a23b0ffde426c6b416cbc8895958e585485d179be63cdb4facfe1b65867cf3bd6826d7024e983872e1f31266a5e209cf615e6f99f754e1f61bd02487f99352c6857791cfa15db6283258c2403744de393bdbae9958935ad2599dd31773e66bdb08085db0b0f95920f03b68e6704bc9a109f578b6aa60417e903f66c0adcae6dfeba683e4e37eaab680939b981936db6ffddbcfd1ee342038953a8fd635df9331b8e4ffbc", + "50f366c7f2b9f34cd84f82a78cd861dc862089762f0cc492b5ee87f9c79817b6b2b5220944cc446e6e04fb7d2e4216a61ba76234026243f5cbf8f506f1391395f98fb1a3fee25348ae6084e4abb96b1198cd3254c8f9d2d8c69c9fa36f938c19b4171ca27e965743e5cdb77262b4cca5633a3dc67083e363124ba402b49a54acef55178dc0f8e9da091096452eec108b2adf1fb3511e1db943bd6cac7fb5015bc885cd2904276fbca8", + "50e8acb786eeb8ab98116061d151fbe781e1a8ac0f57ff80fd35d9e9c790fb5d8a0539638e57be1d58d2f1d9653679a1eb526f9437dfb013135cf7a0b223f88569a40b5e54754751f5d447f5977235dc61fd3da4bc493117f053d521be247919ac682ccf50ab032c01c9aa28d699365e5fe68e2b2870c1daefdf78f9ed04c345d17a619504412900bc44dc9584a53e8c8f0e862e", + "50e7c05da3f3142ad6a712bd9f45ad4e2f7041a30edf500883c856e9ea81a028c9d55355356f0ebcf7ba17f0ba4be35507a6ebe21d2708f0cd2e76e38f819c4992e7ff72cbdc24f2861e1e12e9d7843c2a3e513ecb45edb7a4c44169f73fad6ef5389107d0ea7f02e1c75fd5028942b3ff0ec8b21c5d0d42f893dcbfc1f97c7eab3b8f1b3c8e474e1c134ab45080", + "50ea243d73486d55606573fbfda83246aa57525afa1df418af40816ec9d7fdb550acb8ad213d893bf5214f610e55fbc95da703726be568e4f3fa38f8d731a4aee3394a452eee235ef5299cec314186c69aca0928d54036b3eec7458baf8857d4df2990e760f208b703cfa87e3375d7fe9af3cce8f1cb9309ef968a4b946020ceee3b322fb740", + "50ea36b10925c934ebb04a3834c28b5d883e3a53e2b9a7a7b82166affb7e429fff24c10914cf0d55b4f2d9071bf51305eaf7b79d79184a56a213a91b1764c56eff554c0c11f1f1801e52a32825e24fc22755bf1692b878589ffbb866dbf823bbd734b2482e3662d018852b4d6bdbecbfacccd8ee90b42e81a64842ccd25ac3de288061e11314fd34", + "50f443673a7bae9131342baa07602d0dc4a6bf0265af5f2e5d79f05d968e56356366fdbf134b8d93b46f55caba2f52d9a5c24d9e07bd90de37662ca07f415b6baea6dd96436122635ed6dd1e37204eab46557332de80b8362d8149a549b2b0f7f8208e015cc15945c04fda9e486579ae8ab5712cdbb7679dfcd678cd57c26b080c7dd680e3", + "50f443673a80c1e7941c7ed072b0f47b1fa548a7ff9152c04e861dda7479b33017cbc65e8495a1f594fde9f5d5b9d884c0986e1825b8420a32bbabbbdb2ceeb52477546b5843baf954dd9e1a3a07a97a531d427ba11c551113a523a397dc4bae1f2edf7d76cda5f222e92e426b6c7915e423bf7edf99fb5ed1aed8a0c7fc47a827d3014b133660999a5bf220", + "50e09f279d4102a5ac9410e54ab2da6bbaf74b8069fe61a76a58aeb8fbd350846614c0cbe909d09f29893c1106379d6b94924e1aac23fb0a3e2622cd3ae107b47c270f303b4a933f9afe1259b2ea007be1bba6da56937c188d8652b1c109fe9880f44121c00ada83966013590c256e7c7e92ee76688ab7237cf30bda9f9e7bafd892f0081b70", + "12f51c412dadfbfe585cf2119da01bbf61dbaaa45a7b23c647e033840c9adbbe2734006a9f36e940738bda5f377c2d8921cfde1b4aee80e7935d82de886f85dc8e23fcf3bb4861cc2cb5353bb73d9b8b13b619d483bfeeeac233927ea497b83bdd265481ca8bcf487af2807abeaf022a5ad2110910edfcb5de7275d8a4dae0562c2927e984", + "1036390ec9bb3915a9f7fcca54153aa862721ad6c2a36eb245f1d4db32d4f11c6e39c42353a2d8565f6c7b4845b77422fa32c1866472cff7054687b0f1535025d137be4ce8a95bc341b1c98e6667ad3dc3195a3e9266fc9c7cd0bb2473ab318a9470ee80c7d74893ec2ce2f218ae7a27b0ecc452af87f70c80", + "10321c2884b8f7e6779a2b35d5360885d4affbb342a40b99b492c1c198f6ac886d909b129fea769c905394076fb1f71c67c4f8405fdbe02911a030b7ebce9a12fddc94d42b68d29864cc8f2099578997fed8a3fa83388a2a26eace36b60463b426e1f6a6faa6808d87ee934536c016da58139174c7097eb10a449920", + "104289b75986a1c2567d9090334c27408f165c0f137b87e320e5033cfc74d36e22d1511446c2c014c7267f817ee3a0fc92f8b233d3f62f10e7490006407af21f193babe423385e9cc316349c6693ddaeaea0320b31b77cdb931e358d163c10eafc6b3521f2c8984f47ec7767a7d89a21ec789f1d5903e03009c0", + "503625c383f2601eb7090254bc232a15b4da3644e9b826b977be56f3f0cfd37f6092c2a24393c2f1a161ca111e0701b8f7dffee1791dac1d6f31a4b5ea2b45c4f94c56ceac5f2b680d6ed2d2c0e04461d019323614a7681deecb0945a34a7aa71ded7cc4ec53700ada9b1ba0549ebefcab5145a7d62acc46140bd1c40b97bdc573b599233c539e43206f4c897fb83fd8ba2554bf06172307f6bf601b0c3f4fe69b68e8e6e340", + "5038b3a279df1eb2ea5494fea944774cdb1dbcf9cba9a23e1090e9036e8401e25fc16b95469f966e006d844cf0786ee3ca0ed1d46e782a7f2fd16dae8245fc964b28ee1c109e64178c0aa4124e7cf767c07b25cbb6fda4a5da5ccf149454fc5bdac00d3c60cbf01d6b870a01e820131f7be1ba25a9821168bea81bb9189322cfe280fa47255cc7d93fd5f7b4394d10742affa8f5a8849975f32fc683609410717a7a3fca49a4739993", + "503567058a490f509ac791bc4b76da77089dd79cd1f73267e68d139a786fa75d4ecf7e7567374a0aa125c63d14ce102823872690aee833b4cd3dd4153987369c9e7358dde4658638ad7ec6b5e070b9b6cdb85af51ec174ccdeaa33db30032380fac056d2ac28b7ebd3c506a7250ffc7053ba50ee166823992fd9aee79b89b2f39b1660d04fb0793e85293b5b42f9adc72fc56974f794f5a673e4366930c4eadc94", + "5049ae20d44857d455b750a28eb82f18fb65ccef2cb13a52338deb097a737eba2bb3f93395a91748d884d53b17d0e974dd99ad2ce39da53196490dbbccf81a2f3b7c09545ab55a43c1b0bbb4c0241585d7048c022d8a77ee19333c8116adf38dd1d67496f409b70d7e5eba09a53d2f84c3f83a9c651d5dcd74beb64b7c625b02ba3ba79fc6076942a083c145d82843ae44ebd2704eb44ae76b44372114b0c55fc5093149f7", + "503bc44171cca5e5aa9d51e74d12c1264937d35017550b26dd77e6d2685f638813ab98eef3c44c57093f9132f42deed8aa3a50311b660b4172f95e5ce1b6a418a21a74765c2e0158bfa1ca3e076587fab377e38ef57e41048a1b06cf67ae551d41a759903d380e48fc0918e6e76d6cbaeb25e18a91218a9f0817266e49b2961e4ad60e1db778506df2e096ca7b34159ab4bbc0bc8692bde3ec295a5fd85980", + "50400d1e01b0eae30712f593a175e19c16ed3cfe846dd6dfe3079781f31953927f461cbbe96379761506d2225839999a9f76bea857427cfd0f2f2dbeabb3a3a3151518be7f3b66cd5bdaad46430de2078ee998ed2f07cf2d30ecec99d450f87c5644f45f3c7cd92680d8331b091174119d1cba3d9e1128684746a04498ae9fdc8785d83cc4d33fc94d5159bca73dc6dd3b7da22b10aaa77c3b378322575afe39afceed48379b3870", + "50390a79c284cfb3ca1092d36c9d985a1bf8ad7cf4ef022a1b4cf1900482d9e4262474454f3a5903f573f6fc5711c364de208171ffbbecb3e0d93f14ea09aa51f346373c096df8e28d70b032cccdffec99680317be8ebf174b99ae31fd6f7cbd9517ac65a74175725e864bdf8ed8983dacb8ec5fd097fb83833d18d7dc0b3d8e942d9787553728b5f7df3c08623fd7d0b336c1b44bdbf10e216e518619313f7ce0", + "50384e83fc47eeaa48acb0488cc7721d8e606b1958d51aa423a91d009c05ec98d921066871e1b067f8d3f3f9adfa234c9f539574bc77aa373159b4b3b36d29733f1036e3fba2939d1aed8cae376d948417b636c510216d1ffe9d20cd4e5111ce1f8bceba346afba8b06ffad00d844b2cd84a0500a03e2c8369df85c52a10991c970d9204c44cd429517b7863b9ab23a6cfed9ae8bff6ab3107ba1abf9ac7fa5f268583c0", + "50426f834a8bccbb7a3cf07370b52b5dbed901979d72ba110bae288e9fb40ccb3dd052b99d88e0f2edb6dc1b5c1e318eda479775600893fd8bb518d62a4ea8822add836b71a37ecd2378914324c07bcb1becde595138736e48142051d78926287bc9ed47760f988e2aae859c6aabda366198d33ed7255990570259e9b33bddb9c6cf101f186483fba01737d251bda4df7a2e87bf737334fa78e286b09d04259aa1a4d28f9be8", + "5033b78aae1608633e525b915d559ea959503597eb492d7711cda8c9be4773ebb18c9b4461787380443cd401552e65f7b47b4dc3ada68196352e2274418dd53d78c558c984eb69b5be7184608008e416837391b01e7c977aaf513a816810d910f85c7d58a5684bb0c04f25f25cd277086af19c7c63eb63297755715315d4329c767d8dff6a0ebba969b73e42d9f3542ae787fb1621fd825fcadbcd42", + "50e5631492d223d634486153ab58a7a35fd8c8eb45daaab5d3c5ac2dab730ab30c91a5c664eb4c0f505d847ab1c59cf162ae7eb42194a1a5c373aa57688383b675ddec9c7953b1cfad7b0f0967b748288f44df6ea8508f5bc317346900cd7079f7f02a26dec93cbfa9613aa284a6654ccb06a7a5f9a1ec358b190fbec01edf66c9ef9bd5b8b48891c6946f049de917c9f27948c9b1ba2bb05916c75e4a", + "50e47d06cb074ceccbe1e544c75be0363c7ea5ed33f1b2805a9e7de2957c3a5281e7f4d003c3266c6e4baaa4babc0bf26c78b39fbebbda357d188ad8bd7291ad147baa996741e78abf68d893d8f87017b342156eb5e80ca1bbbd28f0f33d54c271ed4ebde771ac4373329c4aa112b29443b03ded477df1f12e41c23614335d117e404225b43f9e632dee389081f40c270e3798b37748180b15422754abda2a27570a9b0afc83bf5280", + "50e1bd1c900bc17f81ec7d52ad434ff5ecfe5308c07f9ca720f9d7c4ebd57c85fb880c3cb1e0e759eff1787ad94e191c78091983f89d3c6245cd04a73e070181b4e2324f68c035d079f7911004da8bf29294e03010e994a80fdd9f2d8ae81637df72b307a4b6504c3782529800cdbae1cf4669526e054d33d71faf8f0dc3ab13dd3dbecf8a35a708a58c09dd07b2f8970ec094beaa8251d04dbeede3b0b3afe7fe1fe07454330c44c27780", + "50dca0b8c956bcb556395283c1ec43eadfc452bf7b8a02f755f0b8033f7238b3ccec926a356c458c15bde83fd5f7ea4a53b47634ec32f87a048c3f904c56c4c25ba9d83fd965deee2cb966660c9178b6a93fbb481f4474056149a8d93da2fd8b40a64cbbf310d3e2ce420b39892a30969981fe37969e01999f29924015b4ade9f0c2039e992979bf940a34e3ef8e317a70fb3f11307fd13566d1cc4728a88b9d1e61c8c4b306cee280", + "50e7dcf13180a4905db294fe4f60484e5b4c311013f951c346911bbb39373e5d97487cb994f3d45e1cccf10edf99ff6a3617c445a3a74df2494f4573ef05b5d4f1cfc7ab63f5b7cbd3c715dd53b3b76440476b976fd4dd2ca5926675708534a4dae546de87b020187435f51dc3608b7e15da695addb94c4cd0f0f4e8afe4cd5167f7b462f68b7184d8944edf1065f1160eb2bdeb8c38cf450e644c32ad3f6b28761460fe8870f61590", + "50f4416ecfb7d3d62520553172be98795fbc8982cc6906af292054a0a60f7f1d84c917fbb5d98dc35d81c9ef0c3fc56e98c9011ab80b51e55f6cec013eb95adc9087a905c7a8eb51cd11ddf7224fd84487098bcba6a1afd0101b6b52ce2f8871d981a279025d67170c9cd99efd919dc50ad303a76ed162e01e96854fa921b4241c87ac7f8f98e44d41444254d40403515f55b530153aadbbdb93c5fe06", + "50e2118d99149d299fd4f65f07927c8629af999e568059eae1434fe43f5d9e915187256d4cd461ca4b00cd2c9a7d68fc54ee155cdb42fc1b0c4d172aa7bb8f2d6b1dd999315dfcded31aba46aa0d9883ad009620938cac2a8316a47c4f9e121bbb89bfeb531f133beb559880df9800e3e16f18691e5a627d665dd42fe3ce366a07b162ac975bf36d7c4d5d0fe27622b54f95b1cce84a54b7cca1a7e4800bee84", + "50e90281947932e95ff90f45beeccfd1b1e0b025d10935f06ca44a4bf25d2913ee4075d82871e9eea3f1b6b0210fd312cb4a565d710c7b856762ae323e174b77cc59b8eea81fc97efaf4ef20395bd465a77c12af27d49dd994abcbb7ad4bb3ff0d2819376eda0ac30f6911196f9fb1bdb0383de03bad22b330400b455031f58f8c5769218ddd446260fdd566b59e983fa78cab70e15e276c1566f4e005d05de3a53125c0", + "50ea668ca92914f8e08abf93acfad0ee84dfc5f8b92cb9737f8ea0966092373cbff885b1adc496f5b1f81f95776859966f5be55b66793c5281028e7be175374e01016339a3b5b53f06b6e4c5e7667c5feea542a162bd4551ef7f40aadb84a66ef19124ddfb7d240a578ae260a288f3a65d6ab145f3c876310f4d2f2a80062a32ff476ff7e994bdf3d6a14de017f6ca4e94a7351687c427d93a9313712bd7aba6ec5e", + "50f37412d4238b18a57187d0621553540fccb707351e049a706d74240fe9f0fdbf58e00665f4eaddc3b7075ede399d6c827aa682bd42f51ee324949e815d365248db718b8823b8d368593f042d5e544b78ac2a6b84cc7b1d9bcf8c3908d8c87941ba0a64884f1395198a83a5721f0afe8b70a8fd9cc8a52d9cbb4213bc82550024508b348e9b5bacdea4fc5412b941d5e42c915a492e31e7ef7f29", + "50a233029670449cd04d1a3bea92f2a6029386e966936190dcd0cd0f507517c8ccd9c5d56464ef2c959b9ab070d54fc44665331685f8b6b0c658a5b48cbb3836b0acbda547192402d0d80f18aadbe9ca9263d8e9be6f83b62ecb5ff73b1ff9446cbb01a02baf0371df4b8f0eeeff0a56ad9b8ae1d5789e9f4f95c5550a763a6045b2771da475e79ab3032b27aecbe67590fee658472d78a39ca5263a4ccb6c", + "50e359d41836f652fa5658122823e947076fed25305f3acb1690c39654be439b75e73853b665f8d041086c3e2b6d36da5690ce564389ceca820e4845dd6087f600521f3a1f013ea8508f78cb91f7f3a4077d54d7639e05b845bbcb706c960e51759b61ea008c2af82e29635bf8f9600c879850650f61444aefdf21f2b0f7b94d9bee4d577d47384ff74b9e289197d7e07e0b42f03a0016eb15ced2bca1a059d140", + "50a226860ea307c5da9f4b96f76dec5fc360825f9dfb4b9b36b7f90c80ad0862076088e821b776fe7f025deac73e36b5b7165d4a49dfe896c65d1f917da4585a67583ba68d7fd6813b46f7301e52b86a0738251675fc7a3f1e14a75b12f6ac9c4b6d92f53c7fb8e5850e66ab7545e6394aa9a6ed430dfa723ebc2eeff51ce415741c7f71955368b02145167aa3aafb94fdb89d268bd0d28370", + "50e47bec4d20c23b9ece39fbc0de9f2c9772f4027578c8f9bb0f28bb433ac6efebce809c83c044850a82d35d53e16af41e3bcd3b0db3e310eb631e34c251b33027bafad725e42d46226ab8c3199d8904bfb8f60b6134ca5e3402f065839f26e169d22248736bc16daa803e24a2c411f4ca47b3e699b5587886d30228102cf14cef3dfddf3504a8f6d4b51b3f7f493f2fb1783ae87cfd99ecd32a4855c8", + "50f4852e1f104c1936cdcce1daacb3bd763ddc5cde3bc2e4a73112e2703056bc60069f96191d1fe8ed6d80e974b5a6e34bcfb25ea19509b5120e3e31bb3d4916ab7190ec0f54e117ab0c605f7270efac1521e57fb02371b5c85f9e3cf3fbcabcbe7af66589a28b263447abac54a0e66b2567624c68087fe7893dba053bb24b64705fe21f9b25ee3df332b3afd7b8cac421c4649c119e1489236814fa7ba0", + "50f49279f0a4c7cadf518799bf6ad08f109d8b880366a4847abb3cd07cf89702b4e86a8f8a62104f5e394256ce18bc5b95085d3cbedc5533f13515e08462e4ca87e980e369cd8f3c9829d1cd3ed42a1b3aa8cd5d8fe93fd687af1d021a8eb09db3686686d90ac9db668ef31766ed280fa5d6c8c399fda48140c4b09cc297738ca0dd84dde1c8238c4c8889b32d394d3a68223355a45170de6330c4", + "50ea3a497745c7093d5fb9a833a095ac94ca3ad1186a4534dd33ce16fe6e8db3441ce83ade70fc1f7463d7e0c6a3f6d56321bb7cb36c3cb798c33bfb42a7b4cefd3f2b081b79ee8f8cc24eb943ecaf4207fa0d04bc4a5e0e7ebff384aa3b1f8f1f39766141a38db6bbb2ac0e43db1a7235373758e4244087b7425956db1c597217f5ddb880d121395d57025b430d632e78796654ee53c330", + "50f438f4da2b57742719c6f12fbb844a36ad077412f12c2877cda6d318b78d3162a2621cb3dcb1e0faedb27817528bcc4d37706aac3af622970cfb7df374a64406384bf5bdcfd523b99e7e90d79008eb99bc65d28fff1a08771cb3ee872b664958591807903beaa35e510f2dcfcf6493b018dd4924ce55be3b6c96773b9a018cd3b02557c8479ba6487a83f61a8b5ad5a080", + "50f458c01254390330bdf31132149c7b3e7595a965155f219d75334a77c788e3eb6fe0b5028376802c12ed36574807efdb765484b1c50849617f7908a4fc51dca7e8b77898dca6b93e7dc96160ee2368f8b5819109360c4664dee76b591f47d87714fa726aa9c4c719de07a871fef6017efda69e4c23157fce03f194aa1d707f8669237011e14522b7c234f4", + "50a2383b5fcb6dc0993f2d65585f7b261c5eb5fec55f370cb9449e2ce36baa50a1081dc3e94a8593e98e35696e36451034d069ea20ede98b25cff6bb2da97c6bffdd0af1aedfaca85ee8f3f34098fe5ed82333992fafb3650ebc05b7f0f03aacc0d58de2899cfae893a68a5f9e76a5439316bc8163f7fd1d1fa4212b9045a986335439275614ea14c0", + "50e488f81b474e96c7275a7bb89bc0a9628e0c31b4ba55c042776b9d53ec386db0b05ed9e8aaae26d013504f07b728c38bb1a6fd1ca266603c83c76d8194a13a732082da1ce1ff86a78cd4e5a94d92653c4b04eeff625ebfd2519292de64f44b8a977318229e1e935519e166309069d3a2a9a75baaa17109a8d85ffae45b085494b77fd440205a59ee6403afbc0a", + "50e77ee3794a279e5d168cae5a05cdff97823a1cdfb30579ef8b229fcc7fce3151f2dd777e48d728ee5f70e104ee62bc0d2cf02cf4d25a41ed9f58e3ba246424a41f6cf5b38210c6b680485a9eaf7914feaaa6bf2f4644364c9a552a0143fa6aa0909003ce9e1bfe759e67723638d7147e2da121a90fddf136002ee0afb32fef13216e5624c6ded8f38a13e31f335980", + "50ea018800f136ac3df907a9f4d9025cd22e2af9be0f0af19916c90f80253d78ed0631e3235e7b88cabb0f9ffea64a135b54b233fa3b0d4727fa88f1c6f989ec44b29dedaa00d807fdbcba98fe27993e9ecf5c7436dcbb5567b2d0b4c41d3c31ce7b47862f34c50e89c23dbcbf5a2e200608c77f6b7882d052cb2e2e7b86123fd7940b223cfc9bcfe3e2a5583310", + "50eab262968430350a4d4805aec4e7e4c129b2ed7430a0803190bbffc524ee31b81c92696607dad5ba8ef286c61768726eb471635848d7fe0739bd140e1d49eabc245f350789ed095248e27e898e38bef6b912d7f701753bd4f9a033f168dd8315512fde16cf232c834b027c37c815511643f88201937ded29524e7d2d1693a033e1d4b7d7ca8a0a88", + "50e2a71b03f47a87f11b0f6edd18a820e311ec17e2ed9466344891178c5befbc7ac8c413d4fe7ddec3fc358b851ad37245bd25f35374ae8ae0e934039eadd7d0fc17ad942cdcc5eea1e0e61aff713ed9c75e1444e8e7fa90521ae60324e5692d03158e531632f8f0421255e2d54f8260edccaf207f08372b656d423270765cf47240c756a3d398dcba", + "50e1bd4ce5d7104fbf00f88caf35aa74aea8c85b8a6310c835f0802d73ad5feb46b2aabbf0afd8c17325a419edfab0df2a611972b7f2a73b9a3fc7584c6dfc7e8d824af0b32c5fa9da4bd803c4c76886dfd85ddeb9dc5531f9587b0288782aabe4f15aaf960b819384dc6f5c4180c7cfb595f244004fef1a3a947b05cf750e101430d688", + "50e7518c3fa657608708345fa755579b5a0e486c79981f6b1acf4081fd85ffb1588f77f06e0aa490ba1e1ca110530410a454c1133f7680d60a182e7c8c49c45e981d392a3b77075d74dc029cf5cafd05525cfdaaf6cd51f6858bdd551001c65f21b7bd7f6dad42e90084dac9e6968ac8fe3d2cf6ec47f36750df4d3ff0b5e7ce6af44770", + "50e434eec36ad52845b4fce32afdd5e1cea2bcc9176f1e758546f7306d48c3f3e96629f272612ac669b08bf10e5bf91b85558ef12c0a58bac38b9d815ac26a74361a1909a838cc0eeaadcf20afb25839094fddcfe7f1165181e82a4d512ced6643d81eaa398f1699f9d828a306b6035a42e3f3dc859e43ac5be7096a990303a3d433", + "50f493d67591df7b607d4d9e6b0f24a06393927cac65eeb49b0e6650f87012d8154dab178985d1791573399cddd0464bf9dcdc595ef773601ef1f6d7efcb3ddf9b8351a85260d6b06f5d786136b0739ea8aaf27ac4f291d02e9276ff40051f9959894a92ee056f89bf5d78d7c3a0e35479a220ff2fbb4892164597dd18c2f861f378", + "12a253992c9c5c356cd0fb92477300bac71201aaa5a376bef74068d1ebac77523d6196a8945a9dd274502b49bf37ec2763fb34b7cc15c99331bbdd4f9748f7df9cc73109c0d6a53405d29783ba8c32166b28b7a610eb1a71e54ac9fc863d59cda5249fb7d708dc8b1468db0bb53e47f547ded144decc1740bb14d4c262aa2565b976b16a10", + "1042cbc8a33dbbe09e4579031b5f15e0b7b4f5f3db80c69bc2813948", + "1036dfa9e113e02b48dd9004b64ac6a8", + "1099ff776336aa3a361ad005be212788c370", + "1097ca3f4c3692ae6d27092552e2c558c0", + "106ee58f245aa24009fa701da28e4166946408", + "1005228260ff8b58cc41da353e62a9317a080064", + "10b81414c2f17d3abf63e5947811", + "10b81414c2f17d3abf63e5947811", + "10b81414c2f17d3abf63e5947811", + "10b81414c2f17d3abf63e5947811", + "50414b1064fb62ae3625a57d87ca80722d7a26432a0ea73e7de3c3822bdbf798ff98daa2a5b45837c4a48c98ebd0105ae08527b0483d69bf05cfa4e577cbe3d98e1af2249e4f52a0605c503f1f92f7fa2b0dc56f9f7e8e4ed2ea4cef50d4dfefac7a5e2531343aff857cf6c03bd40df4bfa1b1b8733ef476fb26a9a1fc03dcbcb441b7ab69acc91ba4454aced98e5f2b17bf09d0f890634a1b2d0a22d390ad5d51efb9d0", + "50357565e62fdf7bdd575f88237ff9970875f9045ce7e31feabe113bb1e9538653e0c9373fc26a92c2e02b56f29d49038728c619b5892151e2d91fafcaf800cbe60bb50ee22e74b17776b3aee73081ec9b300b9794b7c98d407bd8491e80360c989f2a673a70f22ba4fedd9e6585b3fb6809cd85c4624e15de234a288a6f2343a196e4dd48b46c1deec4182fbb7be0734fa2e188b3950b3361a2ea675b7f889b3a", + "50423f2b4c822cbf191352f75c44c2d9727152ee0b13bfb9e9303dbdb284ed4d308c1dc932e5fe65bf16f21e6676f96f9a2251ecd790f6a1e8c3b6a34000d87e3ecbcf2d0dae9cf97f112af66acc3b4703ee2b5e267119c556c416da2a2f2ac2d6d29abcb6689b349726eefbc0a28cd417ef1eab47400c77bb061422416acd15e373e1143e691312b133d3133635035087d015b6d98681d751f75280", + "5036774c9e04ba160c601b83c9bcbbccb86d69f243b0871c2474c42bccebe52330593890b43a33ead8d9c0cdb69ccad9b8c6f8a676008b126e843e22d1509639ad731eb4be7b71a23c1007dcc33d4fdef2fd918f65d4f2b72984622d35ca43d17949387f676b84f02252202dcdd53931e78c8ba194cea595b6b5e1e805393138befd1cceb7687099778002b8118ac9b859e4fe403d32d2059e3f403bd2fa094a98", + "50358249518761abafb620e88092e62fe27fb497060b6980e954073f55f32328e30e049747a9f1c4375cf49506d5c37ce01c31ccee94705cfdfcbab793ad1207084509507b0562923abf78caeefe17859411810275105826ce34cec24b03b48ff72a4ffd7def240a5c3a38cf1376c04733ee4f3e0c010bcac0f068023e5d8702d98a78f048732ddceae741ecf8b8bf3bd59add7ce40562a505ee4fb960", + "50246f850eae36907caaea48e82186d9b0e054a11dd91b85f35dbbd8e043fcab01abb4954ff327bb64f74ad4231d1eb768c0f1128ae1bf7805a223427115b20beb1ff044395936fece2040d0a5a216220f1d0f47797030c5923ce0de2115a6e39723217a127dca284855d2c89423b1aaa328ae3f308d7a37e72ef38510ae45e977224fcb89d7789191ea155e86da0b2e95961c228e50cebef898e971e42812c4", + "5038bfe1552075cdc76df2bbc979524885e96a52dbd69403a6eb1b1d416a208d9c4d90335b69e70a76bc2384667d929caf85e7ad50277c60b4ee4ee3161890e5ae0d83bd4df264aca8567865f4e52d93aae1d1d87aed6030b931e38a6bee75c782effdbdeb1ed56980130a9bcce75721721df6751eb6499ccc4cf9f9de12f4022d5ea4521a8c934d85fc3436e8ca7fcf4bcbe3e58efa219127f8270d80", + "5049507ee8bd7ddae79e17a3f90833a64b626ba77f8797c3340092e0e01ff504c783fdfdd06a6d48bdb09a4009f56cde74f6adf109b0cf1a6720b0db29b860b7e329dd719f4c8bd7b5bb30a0569a14c484f1e2ce93d549ec97064b2b787f1a15e6bae9dd9785d41c5b4bb539dcf319fa13ab925d7d4ebde1346e49b2cfc67cc135182829cdde35b9ebdb3db85d29d0a3aca4f73c3faaee283b9b90", + "504203d7e380aae3777cca737e8e4417847893fe3725533d65c9a5996aa633e6dd91ee7a189fe645143471a0c2f6253dee34616fbc584d7e4c3d76d45758d0f258a987e7baf5e041cff94baf8a7ffee5d6b9d3ecc6bc39e869d1f1a1840cd4812e5e7e6e880238a970f89139b62430aa0a86ed29d88af06802c2d6e7a012aed7e0c90f6c9faad4ce0640520b95b17979a2461c840f63728f7e520aea154a9a999a90", + "5023fdb70f5526edbb23f285f79c51c2106eff1c621fd1d3d843cd270a896a4cae82873fdc1e649dee841cbdbc24e41122af74f3776ed4f33213657063118f32473edfa98c4ae7844e6255f07218d52c432c3bf12ee582f6234de04b1b83c3bb65f6d26d73ba309e6e3fc6540a71be20d9e195308dcb7fce38f5f91e18b05275ffb94a22548a9cce5832e5a92487468a5a03f166887858ba458798f680fb2aa20ab8", + "50101e8ad8bc022679955dfcea4a9d3f0cb22c17fda152a208f789e7c1f58830fe2d0196ec2a009b314dff35bd21f197cddd85c2390cf7922b108c1c92ce5f9b764eccebec6753da0f233641bdc2f81051c196e00e36904e2d94ad8e8fa5161ef16d9a763024a1673d96cc4302a66d4a7aa3b9cdfbc2a9693848ca2b6b9dca63d45cb8a2d48e5f2a76d293122c2a98d89d6ecd132359e2c6ca2d2c52aca2", + "50f4418b27e23e9d90b5427f4af172b2cab84c565408f09221ad7a2b05a70df8c2861027b25b888bd0eb875725ed0103ccbfce2163e0b0dafa5f9a0f0fbe273d91a83f43772e60cb121b1653cfd1bae61b39dafd0218b1d4c253233274eaa2e38e8129094c4df260d8878c73135197b0a6999e440462b0f069d9589dc4f68695fa8aa44f5ae7deaca90492285d3130432144228f70c9fa9b1a85daa2bd1e4af5172d6d01617b6ec9531a80", + "50e5ea1f8a1a2a5a601f1b11c5f609545b032b7637a8e6d1e2330c98bc4fa6104218297bb252c312352406106d89cc26c06b64b5bb98b6afd0f04782bf81ff236413be9be7c1c9db603362a7cdd5325fb383db051a5cf43e3d01b30873daea8429bc79c98137f7b656d5f5fe36be54f21cb36a108fb5736ccd817a0ec7fe6161445ff2d426e1a62aa19199edfc706b8664a83050d54cdacd6b9537876b5a7fb5e73c823ab886", + "50e56715106d77d393b3d6d921314038594806ff00c5798628fd12091c663024becc29dc4ab6590de7838e24593b420c97bcf1d1238c839c44af5e10521b8fcbcbf055ce7411f2c2bd55151d2484cf0a5e9caf7d9be4b8b92de2c537269314fb9c3c16d451ef6af4a57a58ce1141c3ff68fad189eeb4deef3e8414ae6fcaa8c51d884ae6b0538b316d2f751c8dbd9f488b2b9433273917562d68e5f6dc499a87ec5d08a3ab3c80", + "50ea3a64df236c2207d0f0ad3f195b256ca1746ab564495c1a0a9a8a805b407eb1815c654cca95578859e887efd121ad55c06b648220e3a856cb4bae98739e79ff7ba28e5959636244bcd4d187d1b6de0a57f45b2658a765b183cebb81f4abb8ba47c9c008b10445eceb1da865eea43a455a6b19af60bb179872ef8d42f95ecae082e171587b3ff2a66a0f764bf323c40fbf2f78bb902f2dcb096b40ed54a4440bc36eaa061e3d79c7f820", + "50ea0004cf097b6f932fbc42e731c80d53c0c74bf446454a43e7d3ce6bcbeeea1477ea7369174113655ac61bf0b8aa9bc692e34d96529f86c28fd0fa0ffa37f3cf940a66ddd6a592a45a988beaf0ec5a0fcfa4ffe99637221a48093417d5dc0dcc3415a672e9b601327e445354e4d6624ec0b01c462cae768c7115278c35c72a5749d93cd56ce0cc7388788713ac7be2ca584b0d3b14bc10b964b453e980a9f93b311205b0", + "50e6f5903d20f0eadb2584e4fa8eeacd39352a59d51aaecaa52c51eec010123e17e8c8d965e305e4f04e95420e60dfc8e4efb942daf8c14526a0e6771898e1faacd25b86cf3091ccc1c185507bc0328ac905a25446e27994bae46eed609f55c5f0e72cba8ce896c32a872d0c1901887d560ad9853065090fdc4cf897383e58c2749ee5bada2166e3b4ee6eec9a74d824bfe026a68c1aefdf57ddfbbbc95fdc22d3beab5caa559cb224", + "50e5d4b443e77e65dd969d2149ad6abbcb0ab3f160c3683b5c63ec6efc4e5ef675e0447ac1220e0d975e2d13c9edb06ee67591329bb7196b58ab87d6c9d8fead81ff15dc30f1d76362c62826d51f8f6ab273284a0a7f0365dc9b24107c972c26ea667f21cb1201cbb143823a9110b96e9e5f4788b04c0e1717c2dccc3c4d01ac398fbcda148e57d8d933283ad34c21484bffe0e8e93187d56734a86f4d7167c29876c9623c6780", + "50ea0004cf03c1811d479e8325f16a3ea5b8b03ade301a211b3444798c3a4f7df1bbf8bf81d0c3db662fb2e24b63e9529efa7e7cfd69e873778399864a04b9284053a7ca45ce7d1201ec1ac7a9f56244da7418f1aae82bf77302b3a5566a73a59459da932ba1175b838e27487a4d04b306a61ea995cfed1acb0de797f3bd39d9d4d73d0010e9ccacd28f9fe1fccc643a6aba87c63040dfe4f21430716141441e97e80df7fb1e0fc0", + "50ea39d23b47603ab1e776fab2f0e14d9d05d2a9079e753b1dd4f350359406b0ba2ec847b2cbfa3de215516d1b0f3d8ed11c31a6e9112c7626a8d5bac35d2896365b48c3e38cd3fab48f71e92170e96cb0c508de4a3073c6ff0a1bcf5b5f58f4a22f789b39483f12717ea565dfeae82ac536ee8e0504c43adcf174f65c1766bffcd8f6a8992778d8fbd489f3973537bb7eebfbedf38c9cf0bbb0e4e64e661e706bca7f3d20", + "5036246f911845f56a45ff25e524ca77bc99d43006a1ba60923d1824a0396e2dbd251eca8e6f3716e67ac858928bc9303475b00eac79bea4e5815b4ba504c284f80c93ec7f6827f1df59639277d2fd06c5cd435ea2373de04694c1d90167d508454a66f73dfd19ee608f744ce232a1a59f537f6f7e2396c5808f8476835d9efef0", + "50361b5810c0b70829a055bd36da6aa9871d1b5a519436bfb8208aa61482386655f082bf6b2b6e7e85db427e8fea034833fee5c776c853e86047748fe6634ed92c4cd81a217265f5bb1c6031398ec0aaed2a0c6aff7096a778bc361aaec52a35f55eaf97431d954f95470c6ecec68631b4d3aab025c7d8298061f4b50fc2e8a8ce75a860c8743035f832b34e3ecbb85645691a", + "5036453d9f30882d21d58066dc5ca209e999ba65837da1dc4002cc57b805df48c3639e39a5db59dce2911963e32677bb5554c56c4a81251571dbf617729c6455745698dabad8c20eabd7951ccbdbfc4e27183c6b64dab9c7f287830f071381cac6828bdf2d5151a658bf364adbf6c65b049d0e05a195d53204e4197ce9ad79997f51b5d6300e52a115e513098ebfff68240a", + "5049ac684146dfbde95e6cf50d9e9f96d2200dd8b3f82057ebd527bde16f6aa4c2d573668ce37da39ec8495e9574aed1daae26e4d8757ca2eb02661b7293c738ac4c68257b226cd17b1c596c8c9fb58129a297dc10d0f9cd9015d153a0c1b022481ddf5a2f87cf57814820bcffdea20e6d0e85dcb5376dce01a0248a1f6e743db9ab00bdeb1e9381d792d7aed8e2e818eadee4cbf70552061f4c6aa455", + "502cb9e4d06c600c83cf9dfed25bf46c0a1607601634396459d983cab0fe1bb18d48cbb55fb77ebe44edf3539c67b8d9407dbde0ff80d907d6dc65909e59bb86fa6a45e62b074d0b53afeaa81829083e380f15be5673d612a149a2cfcdda647c5a4ee6ddb3d528dc8ec7f16872d994601fd3115b5a14f58631168d568efafc94824111606d6714626e666bef722527e7c0", + "5033c4729233d4b6d3b095a0b167deffa866e4c08067da5d02d59ce6428dac2a72a10f5560048f9f6302481e8341e816905d71f51ccf9dd6cedad0c37d3f5aab5db4e7b1377e6e4baee9a3de754c181aa1db96a53ec8951b6f32e28d0c4c1cc2a9cf2f125e808bd6038ecaa8507f2d06816c42b4008bdc2963a48529c887122f2d482c3f1b7f7a509788a38271e9532c", + "504a6c63c9fe55b3a467f253cccb6ace81b05cfbbcbfe670b61583fef9e24b8092ab744a60e5541fd947ca1e7b7883431e5c1f871d61670c7e05af965e8a33ecde8e2c942d1b528e49766fabce87cbdb9a9d06a5d5a1111702b9be3e879c04559c0ffa092f6f819a270289917fb9cd9e8611fa34a6779e2a7bd5713c772d7f019044c8acbd3580c28db402", + "50354675ffe73e5a6fb3e83e6e8beaef17e5ea24067658b91af9372b2abc62a06c57dbbc7d0e4b7ff9251995d7235dd6eff7aa0df7820b60d47c5f605d1ea424ac80a010aed8843862d334ab8d481c91ee96253233235e1dfdb5fcc2670b8450207f3200026172cc72daba1dd93db53481db357ef8a569abc18bfd95c06a96455edfd3372321dcb4fdbfb83459445c", + "5037222092ad31066e453825b1f04188b28ca023c053ff1e77fcf95f08b97c78066ee8e9ea54cb4d27dd75dd898cad0176a73f125f1bca8070cbe9ac17b98dcafa752d444279e2b911f2ed31df38dd5208b9388a84e930d0f04d7eed88bf879ca94d0c33c8f10ba58e2d21da30441655a257099ae79acd672c08fa053f860b0eecd5ce4ed61958c6e864a90f06b9db4f68", + "5025d8c04e057d9e16fca3ee330d748b3fa207d71aa5dcc8f428c3f227414a9097de0687ce7b496d1408ad35f67be17f71c4e8c2f033a91cc71c1620b92aa9eb1b2943d880162d04e2f60c57c266497a13b1be70ad6695e4712855c73f5dc15cb0d839b1e8afa086bebab8ee20a7fb8c95d6b535a1ed321432df1a8b0f7a9c9a965da27dabbbd660b755519e63dcabac514888", + "50189f4497204029c7ee11f22ab95d1ebe62ea264af56dfd57703bec1d43b99c10d618cdaac914e63ecf4168467118fbe0b0850790283c30f0ebdef535659b710409db6516154754aa06fbca6df33a1d0f9ac5a9d3d4932d229231fa4b0fcf3918d16edb6f155da4208262a4109095464d321ab841581b0af60f9992470079b1e8d9ce5a5bad5cc375c6b8ef396d595393742a2df63c0b4cebbea68e80bbf8009b18", + "50a2351d536578cf217e7203aa042c5887e487898174965b2d7b5813a7ecd040771fdb0e37b3ed97ab620dced9794c84aa3e8dccef4f3048e890bf9d3c00b456302522444d583735448be90452f71a480050daac1a25493adbd1e9176c4c133ae0e1349a5c7a74bee0e413233f9aa878dec91ddfd6a092f323c738a6eddb18e8458fe530ca6db6cfd65f9373524cb25b25099443a21ed7127ba310497be935a4da54c0", + "50e47d3d7548a5299b245c7ebc26becb180786232850738afe41ae91b3c73d3a11388a786bc32ca7403b88664120cb508cdcb80c8cae165b839984f9b0c391425ec8ba9a147f1b4498165f961c5bdece9707265767a3e9dfcb832d5e432dad4cde18abd95a72f34cb116032e9eab45f4facf6dfb2cc2abb21196403f817362188a379b5b9665c80760c545896ef24e08823011579e8dd14eab8a2b9a9ae067cdbcd326e6e0", + "50e69ddab0a322c883b6509500c62218d6313d5766faae97ff2a0ab23ef49d29fc7fbfed4257bc9edb928b1603660e7e66060ddf0aad2ade048494aa9e70b45fefec820d941156ef6e7c1206b15077ca1756547da64d8b5dd42c675cae98fc2ecd2e95090c12e3a33e30b017c5362d7a9fad6a512b8bc8ddb5cf6e1abd0be3d824eb4d4a8cf01e0d8ed68ba01e5cc6a78b52f68401a9a4b5626cf32c434cda94651674cab959864fcd280580", + "50e4890f98fb79b3810aec9a9744065234c665502abbeec17ad6476b0a2d06acee6003fa245384c5b1adca0effc9f4b47ebeb512037240d2ee2321f86b88297cecc2e611701744e153b28b65d69d8898aa29c6502d45d045a2728543974d983c6f5ff5dad05f3914127917291e8fb9cf7aad03dc9907d36d0d274e55c6161b4ad5ae3e36194732ccefdc954dc62811639c4ccc33e7693ac3c8494a05aaa4d5a0d1f8fbca2e3a6f1580", + "50e92c303675635bb0087bddd51aaa8ba4d547fab6dbe355cd6015159f8e2d44e2d65645593ed653dc32a7f816a215abb7e71304af7739a16f030e7efb799238cd4c48fa62fc21402ddd926b46ed9a599435416da694e6789599c2cb498b91949c460b69caab0dcfbdd1ce21ba84e1edaf1842a224e8af334df37e2914391499ca90a356a3cac6e38b6ee4f1984b44377dfbbdf0a1ce9611c3a40d8f0eb1b4d9ca118680d8a2a0", + "50e488f81babd590f5279cb2e676bb1c5bd7fe5a709cd2e35cc50ce50b13e57e454be93f86a256fdcd6366a9cbd47a5f739f74177d071f3c871195b2af32fb4822cdd8ad497c55429efc9620ea5e6b026e210674084ceef0459f1849cfd99d57abd549e159d91900136231155fb618e05146d9264d8e07a303ea787d7bf3c169527809a0511d9b05b2aebb6b96e0c9a19163ea523fbf5835bf90d36e07d4c884a27ad880", + "50e54877eab7f74f210feabf189ef2a84a22caac2ad5ae6d936b4607909ffe72d7836e15520d37cac732d61a5f61821eedd8cd0f012efe432e9e44be560c83222c897a36ec7d3cad158854207819844bf40b7311cefffb4857b890f5e3b444bc6e6292a53c040449747a5a3ababbe02297ba494270449279e1480c0f847f56026671010f54b98a47321f672e7c05921cc108a4ab165103304111df58c1efa9d35d62699c", + "50e54a237217ff17189287ce499a089c7958b60757dc1460c2e74ff253b543aff7c8e3a58c127801aff0df84e5a20ec983f33b48b17400c1453bb83f6018e5eb0d0ae7b5173a43f2be50662926824f2d1483ab0ef5b5757950450f0760ad9f58e28be0fddb7649074dcb02cb0204dcc50118b9b7e9978709a4630814b7e482b69719c98cbce010c90bff9086fdd168100f1829d5cf7c2c298c8a3ea195c0", + "50e489cebae0e17a8843f7452c444a56a0b27488ef3e202024c16a1a0b1bee384e0d1c44479ed7c74b3011d201280b5186ce48b95cfeb78b325fd34ec527c9fb0acf100ceac181d676272b735a922de21ca7b11d45d8e2359c3d7b007f82b77ae80fc971d590eece28e508d9246f3e8d7dc7a2a9796460ef295b611a5abc0fa1bed0d36ec0f9aa8d72d219f9be7177d385a8312bf8ad7d638eacef59b140" +] \ No newline at end of file diff --git a/wacore/src/voip/mlow/testdata/ref_dtx_off_expected.raw b/wacore/src/voip/mlow/testdata/ref_dtx_off_expected.raw new file mode 100644 index 0000000000000000000000000000000000000000..173d00f3152f8f4d6cc4142155b1fb7d99a400e0 GIT binary patch literal 211200 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 z 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 Date: Sat, 25 Jul 2026 18:02:00 -0300 Subject: [PATCH 05/17] fix(voip/mlow): size the playout cushion to the peer's packet Review caught an integration effect the decoder-level tests could not see. The prebuffer target and the latency ceiling were both written around a 60ms peer frame, which held while the decoder only ever produced those. A 120ms packet breaks both assumptions at once. One such packet is 1920 samples, which IS the prime target, so priming ended on the very first arrival with no cushion at all - the case the target's own comment describes as a zero cushion that underruns on any late packet. Worse, two in flight are 3840 samples against a 2400-sample ceiling, so the buffer was trimmed on arrival and dropped audio continuously rather than only under a burst. Derive both from what the peer actually sends: prime to two packets so the steady state never drains below one, and let the ceiling hold that cushion plus a drain slice. The 60ms case keeps its existing numbers exactly. Also note in the provenance that the 120ms fixture is an intentional 8-packet prefix; regenerating over the whole input yields ~55 and looks like a break. --- wacore/src/voip/engine.rs | 187 ++++++++++++++++---- wacore/src/voip/mlow/testdata/PROVENANCE.md | 5 + 2 files changed, 162 insertions(+), 30 deletions(-) diff --git a/wacore/src/voip/engine.rs b/wacore/src/voip/engine.rs index e4008cf9b..1510de277 100644 --- a/wacore/src/voip/engine.rs +++ b/wacore/src/voip/engine.rs @@ -57,7 +57,14 @@ const PLAYOUT_MS: Millis = 20; /// 20ms @ 16kHz: samples drained to the speaker per playout tick. #[cfg(feature = "voip-mlow")] const PLAYOUT_DRAIN: usize = 320; -/// ~150ms latency ceiling; a burst past this resyncs (drops oldest) instead of lagging. +/// 60ms @ 16kHz: the peer packet size the playout constants were written for, and the assumption +/// used until the first decode reports what the peer actually sends. +#[cfg(feature = "voip-mlow")] +const OPUS_FRAME_SAMPS_60MS: usize = 960; +/// ~150ms latency ceiling for a 60ms peer frame; a burst past this resyncs (drops oldest) instead +/// of lagging. Both this and the prime target scale with the packet the peer actually sends, via +/// [`playout_bounds`] -- a peer on 120ms packets needs a proportionally larger cushion, and capping +/// it at this figure would trim the very cushion the prebuffer just asked for. #[cfg(feature = "voip-mlow")] const PLAYOUT_CAP: usize = 2400; /// Prebuffer target: prime playout until the jitter buffer holds two 60ms peer frames, so the @@ -67,6 +74,19 @@ const PLAYOUT_CAP: usize = 2400; /// arrival underruns. The cushion has to be one frame above what the per-cycle drain consumes. #[cfg(feature = "voip-mlow")] const PLAYOUT_TARGET: usize = 1920; + +/// Prime target and latency ceiling for a peer sending `packet_samps`-sample packets. +/// +/// The constants above assume a 60ms peer frame, which held while the decoder only produced those. +/// A 120ms packet is a full [`PLAYOUT_TARGET`] on its own, so priming would end on the first one +/// with no cushion at all, and two in flight would exceed [`PLAYOUT_CAP`] and be trimmed on arrival. +/// Keep the same shape instead: prime to two packets so the steady-state buffer never drains below +/// one, and let the ceiling hold that cushion plus a drain slice. +#[cfg(feature = "voip-mlow")] +fn playout_bounds(packet_samps: usize) -> (usize, usize) { + let target = PLAYOUT_TARGET.max(packet_samps.saturating_mul(2)); + (target, PLAYOUT_CAP.max(target + PLAYOUT_DRAIN)) +} /// Bound on how long playout primes before flushing a partial buffer: if the peer sends one frame /// then goes DTX the jitter buffer never reaches `PLAYOUT_TARGET`, so after this many 20ms ticks /// (~200ms) drain whatever is queued instead of holding it (silent) forever. Comfortably above the @@ -450,6 +470,10 @@ struct PcmAudioState { /// Consecutive playout ticks spent priming; bounds the wait so a partial buffer (the peer sent one /// frame then went DTX) is flushed after `MAX_PRIME_TICKS` instead of being held silent forever. priming_ticks: u32, + /// Samples in the peer's most recent packet. The prebuffer and latency ceiling scale with it + /// (see `playout_bounds`), since a peer on 120ms packets needs a proportionally larger cushion + /// than one on 60ms. Starts at the 60ms default until the first packet says otherwise. + packet_samps: usize, } /// The video half of the media plane. No jitter buffer or playout tick: an AU is handed to the @@ -663,6 +687,9 @@ impl CallEngine { jitter: VecDeque::new(), priming: true, priming_ticks: 0, + // The 60ms frame this decoder emitted before multi-frame packets existed; the + // first decode replaces it with what the peer really sends. + packet_samps: OPUS_FRAME_SAMPS_60MS, }), playout_deadline: NEVER, }) @@ -933,8 +960,12 @@ impl CallEngine { { #[cfg(feature = "voip-mlow")] if let Some(pcm) = m.pcm.as_mut() { - let frame = - drain_playout(&mut pcm.jitter, &mut pcm.priming, &mut pcm.priming_ticks); + let frame = drain_playout( + &mut pcm.jitter, + &mut pcm.priming, + &mut pcm.priming_ticks, + pcm.packet_samps, + ); m.playout_deadline = next_tick(m.playout_deadline, now, PLAYOUT_MS); self.outbox.push_back(Output::Playout(frame)); } @@ -1166,17 +1197,28 @@ impl CallEngine { pcm.decoder .set_redundancy(i32::from(header.payload_type == RTP_PAYLOAD_TYPE_MLOW_RED)); #[cfg(feature = "voip-mlow")] - for s in pcm.decoder.decode(&encoded) { - pcm.jitter - .push_back((s * 32767.0).clamp(-32768.0, 32767.0) as i16); + { + let decoded = pcm.decoder.decode(&encoded); + // Record what the peer is actually sending, so the prebuffer and the latency ceiling + // size themselves to it rather than to the 60ms frame they were written for. + if !decoded.is_empty() { + pcm.packet_samps = decoded.len(); + } + for s in decoded { + pcm.jitter + .push_back((s * 32767.0).clamp(-32768.0, 32767.0) as i16); + } } // Bound the buffer on the feed side too: a burst of inbound packets arriving between two 20ms // playout ticks must not grow `jitter` without limit (drain_playout's cap only runs on a // tick). Drop oldest past the same ceiling the drain path uses. #[cfg(feature = "voip-mlow")] - if pcm.jitter.len() > PLAYOUT_CAP { - let drop_n = pcm.jitter.len() - PLAYOUT_CAP; - pcm.jitter.drain(..drop_n); + { + let cap = playout_bounds(pcm.packet_samps).1; + if pcm.jitter.len() > cap { + let drop_n = pcm.jitter.len() - cap; + pcm.jitter.drain(..drop_n); + } } } @@ -1301,13 +1343,15 @@ fn drain_playout( jitter: &mut VecDeque, priming: &mut bool, priming_ticks: &mut u32, + packet_samps: usize, ) -> Vec { - if jitter.len() > PLAYOUT_CAP { - let drop_n = jitter.len() - PLAYOUT_CAP; + let (target, cap) = playout_bounds(packet_samps); + if jitter.len() > cap { + let drop_n = jitter.len() - cap; jitter.drain(..drop_n); } if *priming { - let reached_target = jitter.len() >= PLAYOUT_TARGET; + let reached_target = jitter.len() >= target; // Bounded wait: a partial buffer that never reaches the target (peer DTX after one frame) is // flushed rather than held silent forever / replayed stale when a much later packet arrives. let timed_out = *priming_ticks >= MAX_PRIME_TICKS && !jitter.is_empty(); @@ -1707,22 +1751,65 @@ mod tests { // One frame is below PLAYOUT_TARGET (two frames): playout holds silence without draining. feed_frame(&mut buf); assert!( - drain_playout(&mut buf, &mut priming, &mut priming_ticks) - .iter() - .all(|&s| s == 0), + drain_playout( + &mut buf, + &mut priming, + &mut priming_ticks, + OPUS_FRAME_SAMPS_60MS + ) + .iter() + .all(|&s| s == 0), "below the prebuffer target playout primes with silence" ); assert_eq!(buf.len(), 960, "priming must not consume the buffer"); // The second frame reaches the target; playout now produces real audio. feed_frame(&mut buf); assert!( - drain_playout(&mut buf, &mut priming, &mut priming_ticks) - .iter() - .any(|&s| s != 0), + drain_playout( + &mut buf, + &mut priming, + &mut priming_ticks, + OPUS_FRAME_SAMPS_60MS + ) + .iter() + .any(|&s| s != 0), "at the prebuffer target playout starts real audio" ); } + /// A peer sending 120 ms packets (WhatsApp Desktop) delivers 1920 samples at a time. The + /// prebuffer and the latency ceiling were both sized around a 60 ms peer frame, so with the + /// larger packet the cushion collapses to zero (one packet already meets the target) and, worse, + /// two in flight exceed the cap and get trimmed — dropping audio on every arrival. Both have to + /// scale with the packet the peer actually sends. + #[test] + fn playout_scales_its_cushion_to_the_peer_packet() { + const P: usize = 1920; // 120 ms @ 16 kHz + let feed_120 = + |b: &mut VecDeque| b.extend((0..P as i32).map(|i| (i % 200) as i16 - 99)); + + // One packet must NOT end priming: draining it takes its own 120 ms, so the buffer would be + // empty again exactly when the next one is due, leaving nothing for a late arrival. + let (mut buf, mut priming, mut ticks) = (VecDeque::new(), true, 0u32); + feed_120(&mut buf); + let first = drain_playout(&mut buf, &mut priming, &mut ticks, P); + assert!( + first.iter().all(|&s| s == 0), + "a single 120ms packet is a zero cushion; playout must keep priming" + ); + + // Two in flight must survive: the ceiling has to hold the cushion it just asked for. + feed_120(&mut buf); + let before = buf.len(); + let _ = drain_playout(&mut buf, &mut priming, &mut ticks, P); + assert!( + buf.len() + PLAYOUT_DRAIN >= before, + "the latency ceiling trimmed the 120ms cushion: {before} -> {} samples", + buf.len() + ); + assert!(!priming, "two packets is the cushion; playout must start"); + } + #[test] fn playout_prebuffer_absorbs_inter_arrival_jitter() { // Packets (one 60ms peer frame) arrive at a jittered cadence around every 3rd 20ms tick, with @@ -1770,9 +1857,14 @@ mod tests { let real: Vec = (0..ticks) .map(|t| { feed(&mut buf, t); - drain_playout(&mut buf, &mut priming, &mut priming_ticks) - .iter() - .any(|&s| s != 0) + drain_playout( + &mut buf, + &mut priming, + &mut priming_ticks, + OPUS_FRAME_SAMPS_60MS, + ) + .iter() + .any(|&s| s != 0) }) .collect(); assert_eq!( @@ -2732,7 +2824,12 @@ mod tests { if arrivals.contains(&t) { feed_frame(&mut buf); } - let _ = drain_playout(&mut buf, &mut priming, &mut priming_ticks); + let _ = drain_playout( + &mut buf, + &mut priming, + &mut priming_ticks, + OPUS_FRAME_SAMPS_60MS, + ); max_occupancy = max_occupancy.max(buf.len()); } assert!( @@ -2745,9 +2842,14 @@ mod tests { if t % 3 == 0 { feed_frame(&mut buf); } - if drain_playout(&mut buf, &mut priming, &mut priming_ticks) - .iter() - .any(|&s| s != 0) + if drain_playout( + &mut buf, + &mut priming, + &mut priming_ticks, + OPUS_FRAME_SAMPS_60MS, + ) + .iter() + .any(|&s| s != 0) { recovered = true; } @@ -2769,7 +2871,12 @@ mod tests { feed_frame(&mut buf); // one 60ms frame (960) < PLAYOUT_TARGET (1920), then nothing (DTX) // Up to MAX_PRIME_TICKS the partial buffer is held: silence, no drain. for _ in 0..MAX_PRIME_TICKS { - let f = drain_playout(&mut buf, &mut priming, &mut priming_ticks); + let f = drain_playout( + &mut buf, + &mut priming, + &mut priming_ticks, + OPUS_FRAME_SAMPS_60MS, + ); assert!(f.iter().all(|&s| s == 0), "still priming -> silence"); assert_eq!( buf.len(), @@ -2778,7 +2885,12 @@ mod tests { ); } // The next tick hits the bound and flushes the held frame as real audio. - let flushed = drain_playout(&mut buf, &mut priming, &mut priming_ticks); + let flushed = drain_playout( + &mut buf, + &mut priming, + &mut priming_ticks, + OPUS_FRAME_SAMPS_60MS, + ); assert!( flushed.iter().any(|&s| s != 0), "the partial buffer must flush to real audio after the bounded wait" @@ -2798,12 +2910,22 @@ mod tests { let mut priming = true; let mut priming_ticks = 0u32; for _ in 0..(MAX_PRIME_TICKS * 2) { - let f = drain_playout(&mut buf, &mut priming, &mut priming_ticks); + let f = drain_playout( + &mut buf, + &mut priming, + &mut priming_ticks, + OPUS_FRAME_SAMPS_60MS, + ); assert!(f.iter().all(|&s| s == 0), "empty buffer -> silence"); } // First frame arrives: must NOT flush instantly -- the counter didn't age while empty. feed_frame(&mut buf); - let f = drain_playout(&mut buf, &mut priming, &mut priming_ticks); + let f = drain_playout( + &mut buf, + &mut priming, + &mut priming_ticks, + OPUS_FRAME_SAMPS_60MS, + ); assert!( f.iter().all(|&s| s == 0), "one frame is below the target -> still priming, no instant flush" @@ -2811,7 +2933,12 @@ mod tests { assert_eq!(buf.len(), 960, "the first frame is held for the cushion"); // The second frame reaches the target -> real audio drains. feed_frame(&mut buf); - let f = drain_playout(&mut buf, &mut priming, &mut priming_ticks); + let f = drain_playout( + &mut buf, + &mut priming, + &mut priming_ticks, + OPUS_FRAME_SAMPS_60MS, + ); assert!( f.iter().any(|&s| s != 0), "at the target playout starts real audio" diff --git a/wacore/src/voip/mlow/testdata/PROVENANCE.md b/wacore/src/voip/mlow/testdata/PROVENANCE.md index 423e25d7d..61f11cbdd 100644 --- a/wacore/src/voip/mlow/testdata/PROVENANCE.md +++ b/wacore/src/voip/mlow/testdata/PROVENANCE.md @@ -100,6 +100,11 @@ byte for byte **against that revision**, which is what makes a changed fixture a than tool drift; against a different checkout the reference itself may have moved, so the script prints the revision it built with and warns when it does not match. +The committed vector is an intentional 8-packet prefix, not the whole input: `synth_mic.raw` chunked +into 120 ms frames would yield ~55 packets, which is far more than the decode path needs and 7x the +bytes. A regeneration that produces more is the script defaulting to the whole file — pass the +packet count, as the script does. + Both halves must be regenerated together — `decoder.rs::multi_frame_fixture_halves_stay_in_step` fails if the PCM length stops matching the frame count, and asserts every frame is still TOC `0x58` so the fixture cannot quietly drift off the multi-frame path. From 6c31c4ef090b061c9145418be3c084b2bbe97ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= <55464917+jlucaso1@users.noreply.github.com> Date: Sat, 25 Jul 2026 18:16:06 -0300 Subject: [PATCH 06/17] docs(voip): keep the playout sizing rationale at one site The scaling explanation belonged at playout_bounds; the constant, the field and the assignment now state only their local contract. --- wacore/src/voip/engine.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/wacore/src/voip/engine.rs b/wacore/src/voip/engine.rs index 1510de277..998412413 100644 --- a/wacore/src/voip/engine.rs +++ b/wacore/src/voip/engine.rs @@ -62,9 +62,7 @@ const PLAYOUT_DRAIN: usize = 320; #[cfg(feature = "voip-mlow")] const OPUS_FRAME_SAMPS_60MS: usize = 960; /// ~150ms latency ceiling for a 60ms peer frame; a burst past this resyncs (drops oldest) instead -/// of lagging. Both this and the prime target scale with the packet the peer actually sends, via -/// [`playout_bounds`] -- a peer on 120ms packets needs a proportionally larger cushion, and capping -/// it at this figure would trim the very cushion the prebuffer just asked for. +/// of lagging. The floor for [`playout_bounds`], which scales it to the peer's packet. #[cfg(feature = "voip-mlow")] const PLAYOUT_CAP: usize = 2400; /// Prebuffer target: prime playout until the jitter buffer holds two 60ms peer frames, so the @@ -470,9 +468,8 @@ struct PcmAudioState { /// Consecutive playout ticks spent priming; bounds the wait so a partial buffer (the peer sent one /// frame then went DTX) is flushed after `MAX_PRIME_TICKS` instead of being held silent forever. priming_ticks: u32, - /// Samples in the peer's most recent packet. The prebuffer and latency ceiling scale with it - /// (see `playout_bounds`), since a peer on 120ms packets needs a proportionally larger cushion - /// than one on 60ms. Starts at the 60ms default until the first packet says otherwise. + /// Samples in the peer's most recent packet, the input to [`playout_bounds`]. Starts at the + /// 60ms default until the first decode reports otherwise. packet_samps: usize, } @@ -687,8 +684,6 @@ impl CallEngine { jitter: VecDeque::new(), priming: true, priming_ticks: 0, - // The 60ms frame this decoder emitted before multi-frame packets existed; the - // first decode replaces it with what the peer really sends. packet_samps: OPUS_FRAME_SAMPS_60MS, }), playout_deadline: NEVER, @@ -1199,8 +1194,7 @@ impl CallEngine { #[cfg(feature = "voip-mlow")] { let decoded = pcm.decoder.decode(&encoded); - // Record what the peer is actually sending, so the prebuffer and the latency ceiling - // size themselves to it rather than to the 60ms frame they were written for. + // Feeds `playout_bounds`; an empty decode carries no duration to learn from. if !decoded.is_empty() { pcm.packet_samps = decoded.len(); } From 8c581ff18b3067d98b77dfd2976229716e993214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= <55464917+jlucaso1@users.noreply.github.com> Date: Sat, 25 Jul 2026 18:40:07 -0300 Subject: [PATCH 07/17] fix(voip/mlow): size the playout cushion from the declared duration Review caught a regression the previous commit introduced. The cushion learned the peer's packet size from the DECODED LENGTH, which is synthesized on several paths: a SID, a dropped frame and the standard-Opus escape all emit a fixed 60ms slot regardless of what the packet declared. So a 120ms peer entering DTX reported 960 samples, the ceiling fell from 4160 to 2400 while up to 4160 were queued, and the feed-side trim discarded buffered speech that had not been played yet - clipping the tail of every utterance at the DTX transition. Take the duration from the TOC instead. That is what governs arrival cadence, it is unaffected by what a given path chooses to emit, and a peer that really does move to 60ms is still learned. --- wacore/src/voip/engine.rs | 8 +++---- wacore/src/voip/mlow/decoder.rs | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/wacore/src/voip/engine.rs b/wacore/src/voip/engine.rs index 998412413..28922b0c0 100644 --- a/wacore/src/voip/engine.rs +++ b/wacore/src/voip/engine.rs @@ -1194,10 +1194,10 @@ impl CallEngine { #[cfg(feature = "voip-mlow")] { let decoded = pcm.decoder.decode(&encoded); - // Feeds `playout_bounds`; an empty decode carries no duration to learn from. - if !decoded.is_empty() { - pcm.packet_samps = decoded.len(); - } + // The DECLARED duration, not the decoded length: a SID emits a fixed silence slot, so + // reading it off the output would shrink the cushion at a DTX transition and discard + // buffered speech that has not been played yet. + pcm.packet_samps = pcm.decoder.last_packet_samps(); for s in decoded { pcm.jitter .push_back((s * 32767.0).clamp(-32768.0, 32767.0) as i16); diff --git a/wacore/src/voip/mlow/decoder.rs b/wacore/src/voip/mlow/decoder.rs index c9d47f00c..bd0c20d2a 100644 --- a/wacore/src/voip/mlow/decoder.rs +++ b/wacore/src/voip/mlow/decoder.rs @@ -48,6 +48,10 @@ pub struct MlowDecoder { /// frame would desync the range coder if decoded, so it is dropped (treated as a lost frame). The /// count drives a once + every-100th `warn` naming the offending dimension. dropped_unsupported: u32, + /// Samples the last packet DECLARED, from its TOC, which is not always what `decode` returned: + /// a SID, a drop or a standard-Opus escape emits a fixed slot regardless of duration. Consumers + /// sizing a jitter cushion need the declared value, since that is what sets arrival cadence. + last_packet_samps: usize, } impl Default for MlowDecoder { @@ -63,6 +67,7 @@ impl MlowDecoder { redundancy: 0, had_error: false, dropped_unsupported: 0, + last_packet_samps: OPUS_FRAME_SAMPS, } } @@ -74,6 +79,12 @@ impl MlowDecoder { self.had_error } + /// Samples in the most recent packet as its TOC declared them, independent of what `decode` + /// emitted for it. Starts at a 60 ms packet. + pub fn last_packet_samps(&self) -> usize { + self.last_packet_samps + } + /// Set the negotiated RED redundancy level (0 = bare frames, the common case). pub fn set_redundancy(&mut self, n: i32) { self.redundancy = n; @@ -113,6 +124,9 @@ impl MlowDecoder { return vec![0.0; OPUS_FRAME_SAMPS]; } let toc = parse_mlow_toc(frame[0]); + if toc.frame_ms > 0 && toc.sample_rate > 0 { + self.last_packet_samps = (toc.sample_rate / 1000 * toc.frame_ms) as usize; + } if toc.std_opus { let out_len = (16000 / 1000 * toc.frame_ms) as usize; log::debug!( @@ -454,6 +468,30 @@ mod tests { assert!(!dec.had_error(), "the drop must not open the range decoder"); } + /// The playout cushion is sized from the peer's packet duration, and a SID must not shrink it: + /// a DTX transition returns a fixed 60 ms silence slot regardless of the duration the packet + /// declares, so reading the cushion off the OUTPUT length would drop buffered speech that had + /// not been played yet. The declared duration is the one that governs arrival cadence. + #[test] + fn declared_duration_survives_a_dtx_transition() { + let mut dec = MlowDecoder::new(); + let _ = dec.decode(&[0x58, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22]); + assert_eq!(dec.last_packet_samps(), 6 * SMPL_INTF_LEN); + + // A SID that still declares 120 ms (0x98 = SID | 120 ms) emits a 60 ms silence slot. + let sid = dec.decode(&[0x98, 0xAA, 0xBB, 0xCC]); + assert_eq!(sid.len(), OPUS_FRAME_SAMPS, "SID is a fixed silence slot"); + assert_eq!( + dec.last_packet_samps(), + 6 * SMPL_INTF_LEN, + "the peer is still on 120 ms packets; the cushion must not shrink" + ); + + // A peer that genuinely moves to 60 ms is learned. + let _ = dec.decode(&[0x50, 0xAA, 0xBB, 0xCC]); + assert_eq!(dec.last_packet_samps(), 3 * SMPL_INTF_LEN); + } + /// A `VoA=00` packet is a normal frame carrying background noise, not a SID: the reference /// decodes it, and with DTX off a peer sends nothing else during a pause. Silencing it drops /// ~12% of a real stream on the floor while the call merely sounds quiet. From 9c98522c390e44a9b0e173171db16184513eaa90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= <55464917+jlucaso1@users.noreply.github.com> Date: Sat, 25 Jul 2026 21:32:15 -0300 Subject: [PATCH 08/17] fix(voip/mlow): let the playout ceiling lag a shrinking packet Review point: shrinking the ceiling the moment the peer's packet gets smaller discards audio that is already queued. A 120ms stream dropping to a shorter packet - a genuine switch, or the 0x90 SID that packetize_opus_for_mlow canonicalizes DTX to, which declares 60ms - trimmed the backlog built legally under the larger bound, clipping the tail of the utterance still playing out. The ceiling now rises with the packet immediately but only falls once the backlog fits underneath. It exists to bound latency under a burst, not to punish a change of packet size. --- wacore/src/voip/engine.rs | 86 +++++++++++++++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 8 deletions(-) diff --git a/wacore/src/voip/engine.rs b/wacore/src/voip/engine.rs index 28922b0c0..0f133bd38 100644 --- a/wacore/src/voip/engine.rs +++ b/wacore/src/voip/engine.rs @@ -85,6 +85,22 @@ fn playout_bounds(packet_samps: usize) -> (usize, usize) { let target = PLAYOUT_TARGET.max(packet_samps.saturating_mul(2)); (target, PLAYOUT_CAP.max(target + PLAYOUT_DRAIN)) } + +/// The ceiling to enforce now, given the one in force, the peer's current packet and what is queued. +/// +/// It rises with the packet immediately, but only falls once the backlog fits underneath: a stream +/// dropping to a shorter packet (a genuine switch, or the SID that DTX canonicalizes to) would +/// otherwise trim audio that was legally queued under the previous bound and has not been played. +/// The ceiling exists to bound latency under a burst, not to punish a change of packet size. +#[cfg(feature = "voip-mlow")] +fn effective_playout_cap(current: usize, packet_samps: usize, queued: usize) -> usize { + let want = playout_bounds(packet_samps).1; + if want >= current || queued <= want { + want + } else { + current + } +} /// Bound on how long playout primes before flushing a partial buffer: if the peer sends one frame /// then goes DTX the jitter buffer never reaches `PLAYOUT_TARGET`, so after this many 20ms ticks /// (~200ms) drain whatever is queued instead of holding it (silent) forever. Comfortably above the @@ -471,6 +487,9 @@ struct PcmAudioState { /// Samples in the peer's most recent packet, the input to [`playout_bounds`]. Starts at the /// 60ms default until the first decode reports otherwise. packet_samps: usize, + /// Latency ceiling in force, tracked rather than recomputed so it can lag a shrinking packet + /// until the backlog drains; see [`effective_playout_cap`]. + playout_cap: usize, } /// The video half of the media plane. No jitter buffer or playout tick: an AU is handed to the @@ -685,6 +704,7 @@ impl CallEngine { priming: true, priming_ticks: 0, packet_samps: OPUS_FRAME_SAMPS_60MS, + playout_cap: playout_bounds(OPUS_FRAME_SAMPS_60MS).1, }), playout_deadline: NEVER, }) @@ -955,11 +975,14 @@ impl CallEngine { { #[cfg(feature = "voip-mlow")] if let Some(pcm) = m.pcm.as_mut() { + pcm.playout_cap = + effective_playout_cap(pcm.playout_cap, pcm.packet_samps, pcm.jitter.len()); let frame = drain_playout( &mut pcm.jitter, &mut pcm.priming, &mut pcm.priming_ticks, pcm.packet_samps, + pcm.playout_cap, ); m.playout_deadline = next_tick(m.playout_deadline, now, PLAYOUT_MS); self.outbox.push_back(Output::Playout(frame)); @@ -1208,9 +1231,10 @@ impl CallEngine { // tick). Drop oldest past the same ceiling the drain path uses. #[cfg(feature = "voip-mlow")] { - let cap = playout_bounds(pcm.packet_samps).1; - if pcm.jitter.len() > cap { - let drop_n = pcm.jitter.len() - cap; + pcm.playout_cap = + effective_playout_cap(pcm.playout_cap, pcm.packet_samps, pcm.jitter.len()); + if pcm.jitter.len() > pcm.playout_cap { + let drop_n = pcm.jitter.len() - pcm.playout_cap; pcm.jitter.drain(..drop_n); } } @@ -1338,8 +1362,9 @@ fn drain_playout( priming: &mut bool, priming_ticks: &mut u32, packet_samps: usize, + cap: usize, ) -> Vec { - let (target, cap) = playout_bounds(packet_samps); + let target = playout_bounds(packet_samps).0; if jitter.len() > cap { let drop_n = jitter.len() - cap; jitter.drain(..drop_n); @@ -1749,7 +1774,8 @@ mod tests { &mut buf, &mut priming, &mut priming_ticks, - OPUS_FRAME_SAMPS_60MS + OPUS_FRAME_SAMPS_60MS, + playout_bounds(OPUS_FRAME_SAMPS_60MS).1 ) .iter() .all(|&s| s == 0), @@ -1763,7 +1789,8 @@ mod tests { &mut buf, &mut priming, &mut priming_ticks, - OPUS_FRAME_SAMPS_60MS + OPUS_FRAME_SAMPS_60MS, + playout_bounds(OPUS_FRAME_SAMPS_60MS).1 ) .iter() .any(|&s| s != 0), @@ -1771,6 +1798,41 @@ mod tests { ); } + /// Shrinking the ceiling must never discard audio that is already queued. A 120 ms stream that + /// drops to a shorter packet -- a genuine switch, or the `0x90` SID that `packetize_opus_for_mlow` + /// canonicalizes DTX to, which declares 60 ms -- would otherwise trim the backlog built under the + /// larger bound, clipping the tail of the utterance that is still playing out. + #[test] + fn a_smaller_packet_does_not_trim_the_existing_backlog() { + const BIG: usize = 1920; // 120 ms + const SMALL: usize = 960; // 60 ms, e.g. the canonical SID + let (big_cap, small_cap) = (playout_bounds(BIG).1, playout_bounds(SMALL).1); + assert!( + small_cap < big_cap, + "the premise: the ceiling really does shrink" + ); + + // A primed 120 ms stream carrying more than the smaller ceiling would allow. + let mut cap = big_cap; + let queued = small_cap + 480; + assert!( + queued <= big_cap, + "the premise: legal under the bound it was built with" + ); + + // The shorter packet arrives: the ceiling must not drop below what is already queued. + cap = effective_playout_cap(cap, SMALL, queued); + assert!( + cap >= queued, + "shrinking to {cap} would discard {} queued samples", + queued - cap + ); + + // Once the backlog has drained under the smaller bound, the ceiling follows it down. + cap = effective_playout_cap(cap, SMALL, small_cap - 320); + assert_eq!(cap, small_cap, "the ceiling must not stay large forever"); + } + /// A peer sending 120 ms packets (WhatsApp Desktop) delivers 1920 samples at a time. The /// prebuffer and the latency ceiling were both sized around a 60 ms peer frame, so with the /// larger packet the cushion collapses to zero (one packet already meets the target) and, worse, @@ -1786,7 +1848,7 @@ mod tests { // empty again exactly when the next one is due, leaving nothing for a late arrival. let (mut buf, mut priming, mut ticks) = (VecDeque::new(), true, 0u32); feed_120(&mut buf); - let first = drain_playout(&mut buf, &mut priming, &mut ticks, P); + let first = drain_playout(&mut buf, &mut priming, &mut ticks, P, playout_bounds(P).1); assert!( first.iter().all(|&s| s == 0), "a single 120ms packet is a zero cushion; playout must keep priming" @@ -1795,7 +1857,7 @@ mod tests { // Two in flight must survive: the ceiling has to hold the cushion it just asked for. feed_120(&mut buf); let before = buf.len(); - let _ = drain_playout(&mut buf, &mut priming, &mut ticks, P); + let _ = drain_playout(&mut buf, &mut priming, &mut ticks, P, playout_bounds(P).1); assert!( buf.len() + PLAYOUT_DRAIN >= before, "the latency ceiling trimmed the 120ms cushion: {before} -> {} samples", @@ -1856,6 +1918,7 @@ mod tests { &mut priming, &mut priming_ticks, OPUS_FRAME_SAMPS_60MS, + playout_bounds(OPUS_FRAME_SAMPS_60MS).1, ) .iter() .any(|&s| s != 0) @@ -2823,6 +2886,7 @@ mod tests { &mut priming, &mut priming_ticks, OPUS_FRAME_SAMPS_60MS, + playout_bounds(OPUS_FRAME_SAMPS_60MS).1, ); max_occupancy = max_occupancy.max(buf.len()); } @@ -2841,6 +2905,7 @@ mod tests { &mut priming, &mut priming_ticks, OPUS_FRAME_SAMPS_60MS, + playout_bounds(OPUS_FRAME_SAMPS_60MS).1, ) .iter() .any(|&s| s != 0) @@ -2870,6 +2935,7 @@ mod tests { &mut priming, &mut priming_ticks, OPUS_FRAME_SAMPS_60MS, + playout_bounds(OPUS_FRAME_SAMPS_60MS).1, ); assert!(f.iter().all(|&s| s == 0), "still priming -> silence"); assert_eq!( @@ -2884,6 +2950,7 @@ mod tests { &mut priming, &mut priming_ticks, OPUS_FRAME_SAMPS_60MS, + playout_bounds(OPUS_FRAME_SAMPS_60MS).1, ); assert!( flushed.iter().any(|&s| s != 0), @@ -2909,6 +2976,7 @@ mod tests { &mut priming, &mut priming_ticks, OPUS_FRAME_SAMPS_60MS, + playout_bounds(OPUS_FRAME_SAMPS_60MS).1, ); assert!(f.iter().all(|&s| s == 0), "empty buffer -> silence"); } @@ -2919,6 +2987,7 @@ mod tests { &mut priming, &mut priming_ticks, OPUS_FRAME_SAMPS_60MS, + playout_bounds(OPUS_FRAME_SAMPS_60MS).1, ); assert!( f.iter().all(|&s| s == 0), @@ -2932,6 +3001,7 @@ mod tests { &mut priming, &mut priming_ticks, OPUS_FRAME_SAMPS_60MS, + playout_bounds(OPUS_FRAME_SAMPS_60MS).1, ); assert!( f.iter().any(|&s| s != 0), From f111744da2e51fb1b4102a249a2085c9c8e9b5c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= <55464917+jlucaso1@users.noreply.github.com> Date: Sat, 25 Jul 2026 21:32:15 -0300 Subject: [PATCH 09/17] chore(voip/mlow): refuse to regenerate vectors from a dirty oracle Review point: `rev-parse HEAD` keeps reporting the pinned revision when the reference worktree has uncommitted changes, and those changes are compiled into the static library the harness links. The fixtures would then be rewritten from a modified oracle with no warning, which defeats the byte-for-byte provenance the script exists to provide and would make a decoder regression look like a legitimate fixture update. Abort on a dirty worktree, with MLOW_ALLOW_DIRTY_REFERENCE=1 to override when generating from a modified oracle is the actual intent. --- scripts/regenerate-mlow-vectors.sh | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/scripts/regenerate-mlow-vectors.sh b/scripts/regenerate-mlow-vectors.sh index 7ebf873dd..706258535 100755 --- a/scripts/regenerate-mlow-vectors.sh +++ b/scripts/regenerate-mlow-vectors.sh @@ -49,9 +49,24 @@ expected_rev="84b076e0809412df22e8a0d26f944610c4a3e40f" actual_rev="$(git -C "$ref" rev-parse HEAD 2>/dev/null || echo unknown)" if [[ "$actual_rev" == "unknown" ]]; then echo "warning: $ref is not a git checkout; cannot confirm the oracle revision" >&2 -elif [[ "$actual_rev" != "$expected_rev" ]]; then - echo "warning: oracle is $actual_rev, fixtures were generated with $expected_rev" >&2 - echo " differences below may come from the reference, not from this repository" >&2 +else + # The revision alone does not identify the build: uncommitted changes still compile into the + # static library while rev-parse keeps reporting the pinned commit, so a modified oracle would + # silently rewrite the fixtures and look like a legitimate update. Refuse unless told otherwise. + if [[ -n "$(git -C "$ref" status --porcelain 2>/dev/null)" ]]; then + if [[ "${MLOW_ALLOW_DIRTY_REFERENCE:-}" == "1" ]]; then + echo "warning: oracle worktree is DIRTY; output does not correspond to $actual_rev" >&2 + else + echo "error: oracle worktree has uncommitted changes, so the fixtures it produces would not" >&2 + echo " correspond to any recorded revision. Commit or stash them, or re-run with" >&2 + echo " MLOW_ALLOW_DIRTY_REFERENCE=1 if you mean to generate from a modified oracle." >&2 + exit 1 + fi + fi + if [[ "$actual_rev" != "$expected_rev" ]]; then + echo "warning: oracle is $actual_rev, fixtures were generated with $expected_rev" >&2 + echo " differences below may come from the reference, not from this repository" >&2 + fi fi echo "==> oracle: $ref @ $actual_rev" From 79ecd92e81f3a11e6ed0866d12ea8f1a7f3580df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= <55464917+jlucaso1@users.noreply.github.com> Date: Sat, 25 Jul 2026 21:32:28 -0300 Subject: [PATCH 10/17] test(voip/mlow): commit the live 120ms repro, still failing 40 consecutive active 120 ms packets captured from a live WhatsApp Desktop peer and contributed on #1105. Replayed through MlowDecoder they give 19/40 packets at full scale and 4.21% of samples clipped, against the 4.15% measured in the live call - so the offline repro is faithful. The test is #[ignore]d because it FAILS: admitting these packets without decoding them correctly is the open problem, and there is no fix yet. It is committed at this point so the reproduction travels with the investigation instead of living in a scratch directory, and so the next person can run it with one command. There is no reference PCM for this fixture. The C reference decodes these packets to silence - its range decoder over-runs the payload by 2-6x and smpl_check_end_result fails - so it cannot serve as an oracle here. The test therefore asserts the coarse property that regressed rather than a sample-accurate target. --- wacore/src/voip/mlow/decoder.rs | 41 +++++++++++++++++++ wacore/src/voip/mlow/testdata/PROVENANCE.md | 15 +++++++ .../voip/mlow/testdata/live_120ms_frames.json | 1 + 3 files changed, 57 insertions(+) create mode 100644 wacore/src/voip/mlow/testdata/live_120ms_frames.json diff --git a/wacore/src/voip/mlow/decoder.rs b/wacore/src/voip/mlow/decoder.rs index bd0c20d2a..e48b010cc 100644 --- a/wacore/src/voip/mlow/decoder.rs +++ b/wacore/src/voip/mlow/decoder.rs @@ -543,6 +543,47 @@ mod tests { ); } + /// Real 120 ms packets captured from a live WhatsApp Desktop peer must decode to speech, not to + /// full-scale noise. Reported on #1105 after the multi-frame admission landed: frames are now + /// accepted, but the decode diverges partway through the packet and saturates, which is audibly + /// worse than the silence it replaced. + /// + /// The assertions are deliberately coarse — this pins "the output is not garbage", which is what + /// regressed, without pretending to a bit-exact target the fixture cannot supply. + /// + /// IGNORED because it currently FAILS: the fix is not in yet. It is committed now so the repro + /// travels with the investigation rather than living in someone's scratch directory. Run it with + /// `cargo test -p wacore --features voip-mlow --lib live_desktop -- --ignored`, and delete this + /// attribute in the commit that makes it pass. + #[test] + #[ignore = "reproduces the open 120 ms decode divergence; no fix yet"] + fn live_desktop_packets_decode_without_saturating() { + let frames: Vec = + serde_json::from_str(include_str!("testdata/live_120ms_frames.json")) + .expect("live_120ms_frames.json"); + assert_eq!(frames.len(), 40, "fixture lost frames"); + + let mut dec = MlowDecoder::new(); + let (mut saturating, mut clipped, mut total) = (0usize, 0usize, 0usize); + for hex_frame in &frames { + let frame = hex::decode(hex_frame).unwrap(); + assert_eq!(frame[0], 0x58, "fixture must stay 120 ms active packets"); + let out = dec.decode(&frame); + assert_eq!(out.len(), 6 * SMPL_INTF_LEN); + if out.iter().any(|s| s.abs() >= 0.999) { + saturating += 1; + } + clipped += out.iter().filter(|s| s.abs() >= 0.999).count(); + total += out.len(); + } + + let clipped_pct = clipped as f64 * 100.0 / total as f64; + assert_eq!( + saturating, 0, + "{saturating}/40 packets hit full scale ({clipped_pct:.2}% of samples clipped); speech at this bitrate never does that" + ); + } + /// The two halves of a cross-check vector come out of one harness run and mean nothing apart: /// refreshing only one leaves the comparison reading mismatched data, which surfaces as a /// correlation number that moved rather than as an obvious error. Pin what ties them, and pin diff --git a/wacore/src/voip/mlow/testdata/PROVENANCE.md b/wacore/src/voip/mlow/testdata/PROVENANCE.md index 61f11cbdd..c3063ed46 100644 --- a/wacore/src/voip/mlow/testdata/PROVENANCE.md +++ b/wacore/src/voip/mlow/testdata/PROVENANCE.md @@ -109,6 +109,21 @@ Both halves must be regenerated together — `decoder.rs::multi_frame_fixture_ha fails if the PCM length stops matching the frame count, and asserts every frame is still TOC `0x58` so the fixture cannot quietly drift off the multi-frame path. +## Live capture (not synthetic) + +| fixture | consumer / test | oracle | +| --- | --- | --- | +| `live_120ms_frames.json` | `decoder.rs::live_desktop_packets_decode_without_saturating` | none — 40 consecutive active 120 ms packets captured from a live WhatsApp Desktop peer and reported on #1105 | + +The one fixture here that is NOT derived from `synth_mic.raw`. It exists because the synthetic +120 ms vector, generated by the C encoder, does not reproduce what the real client sends: those +packets are 37-161 bytes where the generated ones are 265-351, and only the captured ones expose the +decode divergence. Contributed as deliberately neutral speech, so it carries no personal content. + +There is no reference PCM for it: the C reference decodes these packets to pure silence, so it +cannot serve as an oracle here. The test therefore asserts the coarse property that regressed — +output that does not saturate — rather than a sample-accurate target. + ## What is still not reproducible, and why The fixtures above the multi-frame section predate the harness and were produced by tools that no diff --git a/wacore/src/voip/mlow/testdata/live_120ms_frames.json b/wacore/src/voip/mlow/testdata/live_120ms_frames.json new file mode 100644 index 000000000..1cbe7b760 --- /dev/null +++ b/wacore/src/voip/mlow/testdata/live_120ms_frames.json @@ -0,0 +1 @@ +["58031afb0afbadf477a2a19bf308304c3404759cb8831606578898ded464959801d27c7a0bf155df1cd675c60483b5011518bb26859d20", "58034b15a1258ee7c4d1c884553efce22cde2fdceae854920451143f9942c10b02ecdc07f97a941bfe4b461cbba5472eb5c05b841aa34530", "5803414d0a34ed99b167ec83eecc144504408483c22697ab54f28bef4dce0a90fdf0ccc435a5b5e4074fd1f05d78", "58029bb6c0896bc1464cf5a88f9131d46e6eba50a4260660afb5f49dc224245647d2e41158", "5842dcb5bc6f7f653fcc4e413f2bfad66571dfc36a4fd9e875e0d9df9938598e518f25cfad25fd3123755c067cf3c2de0bd8efdd295f4122e37c3870", "5803488dbd54781a2def300b437a7b5c79f75cbcc0d5d7a27845287bcbbdc4b600a6ecde9e5d3c14d6c7c88b907c82b6a0079713ddef44e4c13c9f8ee8db", "5803488d695d6e683dd3687d7745afbfd5c1e98b28295e478b4bed7a40499e2d25cc255aee4f801bfb06ca7768a7fa847942dd85b0", "5803488d5a8be6f245afbe4c37cf299078ab8f2ca8466be6064ab2d3f33e39143247fd3b01bb307e0cf4815df3eecf0b6b710b80802b", "5803482f9fa23266264fd8782cd67439076b41567fc93927ed914a55a41219874570de1c5ededcc7b34f3190d013479512c61dae8944e051afcd729460bb40", "58634b1868c6a76d038a690a8d1c54ba0f6e53648121e1651948777d283906365d8d1c124955c5ede4e739e612e9607eab71248ac78b9ccb7d45592aaa39efb359a2832d8fc16ed54c30cba56c", "580512adc823a050458f5fc0ca558ae8d3dee263ffd42854c5940bd1b88abedfecd8433bdcb3e6d154fafda04bda653428dba85eb092efab9c7230", "58034b15f17ef4282311241ec12c0a363c98a5d548ee0e33560e7b1ed1f41c3f2ceb744f16d32f82ce7c117688ba265d004d88487d3f44d98602f9c0", "58034b09a16f88a4f811a369cd98feb0a996cef645b6cf6c74ce6862394f8cfe53b184e6f54ebab46e928fe46fdd8abd30a5fc5cbb9468", "5803481ada9c35e8f8a7145615c8e5eb3021e1b615cec1cf09d22342d10d82d1b542fd7213902d84b36b8c00b98d67801c4dd82d41feca8249e7b51220", "58030efd44270f960e253dbca6f4282c3d5a7ab5d3a06641b785d4a8463dd3fb1ce53bc4cc1b17ab1b4ab205ad5a93f44be9bf004ad7b87f4cdb3d82bd680fc128646aff", "58034825acff9a2af50996db74f408232b35118b23577b52c2683fce6116da31a2c0d16c9d3ceca8e58a89299dfa14289ccf156d5837939fdf71c9af4d1b0653d97ad5", "58a00bcd599369165d9b0a5bf55bfeb0a1453da5d8b05745df5f1965fe5983356f4733b84735dbacbe035c8e4a726d1fa68b5726a979476ab76defb3ae5716a38b597a3e0d478d5db84db49b29d0a6cca24fca942afcc26fb1cdc6a7208de8fd2100f9938ee8", "58e00bd684f491975d6c65242e90e301f8dd59e1c89de3be8b34689dfbc473b3d7b8adb271cd92fc6ea418f564518fd1918bbc4c8559847ec59fb0bfecbcf2a59437f94c5fa41f89e8d4cda324d3f322d26b0c850313ce6ff29e9a310201bbbf8da67439f0492ad5b187fd5624616d767ded91fcf8e730ab9b0dfb80", "58e640cb821446e40442f1ccdf72cc97e9b691f08a3e7816e4659e8f385432a6e0d697469df9a9400df51664ef80fb93fc76f440da5f8097a9e68d39888deb88f05292bc1307aa8bcbec6f36ec16b856d84d6582e545a8a8d7e802937e90f41671bf5e18b1871bfc90cbfe465224081f1fb8afa10eef1bbe797d9e8e6ea34cf34d77db4384d60b9ef4f743d009a4e42cbaf2c525a8239064041e", "58ed6ed7d7ce0b39dfe42285f1ed84ab938dbb0fa48992ff4f33d1fbb7f0929f460baf39a0016cf28df03794f16c97c981f2af968afa38a3c9882605916a47a8c6a990b3fd8231effe96a093eaefe329b4b8d5a1b47eb0b6dfca0a8e3dbdd4eb3a43f8a2f2028621c7a8420dba66612862f95280", "58eb33e8ffe043abc85e5249bd8c00730f2f9c21cc47b184481758d1485bbb56488b68c4f3065337eeffb148778f4a844990c393f055647a352e53802fbe1125f7a176c65e7e0a5ee0a8242839141657ee18915a2bdf59623d80587bf922ac655d464a27c2d0b5d217d9c1455ad8430dffd9f8cf7ac3ceb6a3b1063190b223b511df7720", "58e793f8ddee6a00872dfe8e4cfdf94d7398016f31474029e45edb1dd8147ee7ebb10d435fd54cb29c5aecd88beca204cd06630433a661221ae43332e654bc8c7b487fc8f3c83e33d464e1fb756985efeb9fa6645d5d2596a53d42e51e04de486fd2ae93fed923a548bea6c758f2f84de3561aa7d4969f64c0bba1dce95dd6", "58e6e63cd0543c7c29446a5077922ea021e402906be704f1d1aa257bd99d1da3698163054a6f25fad41a229c8b1c5d0c2dcec7647714d5582cb97096c3dc3b155f2188cb719a01c2a01e2a613327273010a6dcd7c99a398ec8d37c80abd5e1ae0b4c421bd2fec863a73e0971825e6151a2644c90793433f96e36da01d41fdcbf2fff40631eca08", "58e6b140c3ec9653296dfbf05714c1511b74492039765fdb5902e3f96a2a68f8edf832856bd35a55148a71c403381cd230d9e24846de9bafd0ac1438fe53ee3694225dce156d2b217c0d2b43d5c2d3da82da32106eb1eb3631de479ee6b643baaa74427202d4cbaa8d61563c28b5cbdc8b54650937e943c361f5b2aa6bee5a41af10dce6573d0c5e0a6fdf51b489848a462115662c4fc8eefcd67af70a56", "58e6fd773f5c67725aa8a75bb1d652700eae2975112ef31e19b5681efee3c5536d540f0c95198e00c572eb4d51a2084d94461c98fc23c06ad916e1a4eb1c9992bf6a666025721a5484c8589afa640bcefaba4f59496566c680452448659976971cc50a0ffd7ac5f083dde69c9979a71ab69d6bcd9863767c766b11dc5a88ae5e662dbfdc3cd9453c75871e6b0c5d50e7a75dc2083d89f0d5f34c63831902483ef0", "58e6e5fcff388b1b0b0fc61ca557120b4fab2ac6e8d9e7be6eef410e11a9dd2a53f206bb596a47b4574a0f44d3ee9dd2b0773eef5b4629d209db8eb96cf494c403d8850f749173a5d16f3a766e6b1303408a13392b4c8844d785296c696663e23e764cc3a7de4e706efd59dcb59b02048f9d22dcfcd749165497a807984344fe214833584be9e25c89d4d27bc952f8dba682f0d688a0", "58e689df75d9e11769cc29530d36d6106a4850771597dc96962b7c3f713462f35ef3c812fb10d92963a77b7d9546ff077e9b3566bfd6b8942d4894964b358c453ac8deed55f10679178636dcd58a8ca1a41cbdffa118dae1e65c1224170a6ef9cea70f1ec564828f82416ff18094f83c8831d1c51b1af92bcca3ec11195164b4", "58e019a0103ec12f6b40dc759580316a09f38baeb9f18d708e5d2b038106205656ac5b5c53d62b895f76478351fe1563823a6bddbb45d4d399af9e6ec68b11498c6f12eb8a36909b7f2c9c4f736a4aa3b9dabf6b995f496c6b74b7b6704e364361b95cf56b4efecd4a6cabc0ed5bd976bcde78889e", "580a42134f78c51aad1a7db5ef910d9c4680a4c44c7599825df981aa08f72af9ca38a7a09cca1e0eb0a56501fe4c4554bb2d2b83418cb19e99fd90300d9456305a466672b16c40d88b36053785267f11e369809f8f4640bafa3fb8b78f", "580874ac0fe3bcde45b4b3428f5ced56dc5904000ad8f82e3d21ae6fe15de3bac64a789b0ebb09705dfc1a7c442e3af18ede7032d17b1acc7a1724f40a1bb6dc0762ab8f923e5d293c823dab7dbb99dadd0b97c672e71d16fe3b56572a29f3b14f8d6a3a09fd23f1ef738892b1156034", "5807f1a4d8163be21c87484cd243d93fa7fdce5f051bd20f8935ae2b1e27caa0b1c189e9fee040d374171ecead270b44cfc9c47190b8f8b8fe05758d05cb3b7287a8cd1fe14c2e50a0fb9b3ce9ffbf9d9e370993db02c10275ffeb940dc9aafaf283430b38", "58093231a10ff2b64229b7425b051aa6727ea3e2511d71631335772dc0fad81e498a52291b173994e5021295db8d151f04ae3a48c0dba6a17ec1e05c410b41b2fdbd02fd0584f8716e72466ced4f42c109a0730201dcf06fbd37a3bfaa5dc30ae0e17d7c169d0564606d80", "5867bbc97061722f481593ac8471bf771d192c0ddc38d3127e66fd306fa71d7a5c9de9170b533f7f1963343cd1ac6eaf6cad79981584b8c5dfa47339c15d63819a6b9c79cb550cb9f62f6756318c61ee09226e9dd2576ecd2f6fec93f72d1d886e037b5acdd3cd111ba050debf85f9af6474d04612b29771adf4306135c4b51caffe792b762d817da8735f0195f1e4", "58e0fb3e52f5aa6bc4dea569a6a345134cfd0083afa5346b516f4bfff726cf2ab1774ff290dcf90251526ccf9a63bf6a5912d9df46b6c794a029bb76d4611d4a72b3f1bacbadf121d0572edbbbb341fceb8b729acd3231dc73ef2ecdc5ed21c013fad95fbb4d393a006c9d6074c74cc51ef498f6a665a57d47e703012a1528af462c43d883fbd68a06fc3bc1cca09a3880", "58ea63db4efe4b64e080b8d54e9f0cdb57a8ed51826ad754c7cc56e8f2b654dba3afa3a8be0d9274416f7606fabf760a786b7253184b9e0c4ebc9caa562ee9633ea49c70648401c4b159d84f187523d259fe6414b4fb89ea0c91cda646a58f7fd8aae8eb525e082e74cf8322dd1e8f2f6ef6455315ea3738d1de111cefc7cc9b5e24e5e8836729", "58e8e3f2e55b3902446b19d4b883c51ba1f6d154fb1d83e6ed0f1dba2158e5fcbeb8230fdb070dc5ad44e439b5457909b33fe9637b44250a525d908a07bee3dd16923536479ed1aafd95087d4347a1fb2a26c90f8090416acb8ead826f51f405a18c547745c5715665335f859555c3cb093875c247d5f83c08f2eab0ddac085636da1bc841c05060", "58e79105597b47e48a9e6405efc94f1e1f9d647e038d64d8e0aae16e13edc8fdaed398d850efb5951f8c05a1b4aaa9f7ca7439aa66561e7b46563f491314468049f4d09d7f5e6331fe8b7afd6413bc4251aef4ef156a153e4985014ff9de019639f776248165587a3d327277c663a18cdc70f634d086fd6d52a438c15fd90dc60ee999740cd06aa294d3424415739030473c70c823c2", "58e8536a9fdc6d1d6e44ad4eb8abb3dd69992a845940231491cc664fb737d615a8f91f680f987747bb4e03afb2ad65c5b931276fc86cf7e9f4b7b43afdac000ad15fdb41bf674e744a24ee71b5a70966900a227df27144d4e8140839776932ea04a72e9db9054ccec79c45a1a450af123691f0567bdc913476bc440f3de5a29804c45859ea22999f460cd505b95eff5a8244", "58e818a069d282b3073e0c2680aece9e5be23109c92361da6deafa68d575d2fa183b4a0e4af1f5cf59788102c5cd9d65f78adfc1ebda871e3b6e5d9e75151016d26cfb33b6bf2fd42ba7c1c3a36f8712660f1e0fa3974f892c97de8cf1ac24300548ecd43d8499b6846435ed67c262cfa676055f4f0bb7776cf7a56c5d5b2e77108ceaad71122556bf528056b5237ca0879520a2afdad53183f6167090", "58e6dfbcad601edae8740a3da162092258170395bde62d454fe90e1a32d60288b79701adf275e3db1aba5ae1f6796dc1e6fdcd73a111128b5fdf36394dfde6633d9c47bfad7b0d0352ffbe03dfb47a1ee749fbc18fd43e93da9e73163a757a0c5a37b9b85d4a51cc058d75bf6b8c6805fccc7dec924361d7eac9d5acedc52ed2b57c1d92ed1f28f87c99f6229c8cf94022"] \ No newline at end of file From 3a736610de503cd80dc5798f77fa824374954422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= <55464917+jlucaso1@users.noreply.github.com> Date: Sat, 25 Jul 2026 22:56:33 -0300 Subject: [PATCH 11/17] fix(voip/mlow): conceal a frame whose decode overruns its body The range decoder returns zero past either end of its storage without setting its error flag, by design. A malformed body therefore decodes into plausible looking symbols rather than failing, and the synthesis runs on invented parameters. On real 120 ms packets from a WhatsApp Desktop peer that diverged to full scale: 19 of 40 packets clipping, 4.21% of samples at rail. Audibly that is loud distortion where the peer is speaking. The reference guards this with an endpoint check the port never carried: a valid stream finishes within two bytes of its storage, and anything else is treated as a lost frame. Comparing where the decode ended against what the body held is the only way to see the overrun, precisely because the reads that cause it are silent. Add that check, before the postfilter as in the reference, and conceal a failing frame as silence. State already advanced during the decode is left alone, which is also what the reference does. This does not make those packets decode: under the entropy grammar implemented here every one of them is malformed, and the reference agrees. It replaces loud clipping with the concealment a malformed frame is supposed to get, and it makes a peer we cannot read visible in a log instead of silently wrong. --- wacore/src/voip/mlow/decoder.rs | 70 +++++++++++++++++++++++++++--- wacore/src/voip/mlow/rangecoder.rs | 7 +++ 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/wacore/src/voip/mlow/decoder.rs b/wacore/src/voip/mlow/decoder.rs index e48b010cc..b0cb2b578 100644 --- a/wacore/src/voip/mlow/decoder.rs +++ b/wacore/src/voip/mlow/decoder.rs @@ -48,6 +48,10 @@ pub struct MlowDecoder { /// frame would desync the range coder if decoded, so it is dropped (treated as a lost frame). The /// count drives a once + every-100th `warn` naming the offending dimension. dropped_unsupported: u32, + /// Frames concealed because the decode did not end where the body said it should. Drives a + /// once + every-100th `warn`, so a peer sending a stream this decoder cannot read is visible in + /// a log rather than silently quiet. + malformed: u32, /// Samples the last packet DECLARED, from its TOC, which is not always what `decode` returned: /// a SID, a drop or a standard-Opus escape emits a fixed slot regardless of duration. Consumers /// sizing a jitter cushion need the declared value, since that is what sets arrival cadence. @@ -67,6 +71,7 @@ impl MlowDecoder { redundancy: 0, had_error: false, dropped_unsupported: 0, + malformed: 0, last_packet_samps: OPUS_FRAME_SAMPS, } } @@ -286,6 +291,30 @@ impl MlowDecoder { out.extend_from_slice(&sig); } + // Endpoint check, before the postfilter as in the reference: the range decoder returns zero + // past either end of its storage WITHOUT flagging it, so an impossible stream decodes into + // plausible-looking symbols and the synthesis can diverge to full scale. Comparing where the + // decode ended against what the body actually held is the only way to see it. A valid stream + // finishes within two bytes of its storage; anything else is a malformed frame, concealed as + // a lost one rather than synthesized. State already advanced is left as-is, matching the + // reference, which also returns without rolling back. + let consumed_bytes = (dec.tell().max(0) as u32).div_ceil(8); + let body = dec.storage(); + if body > consumed_bytes || body + 2 < consumed_bytes || dec.err != 0 { + self.malformed += 1; + if self.malformed == 1 || self.malformed.is_multiple_of(100) { + log::warn!( + "mlow: concealing malformed frame #{} (TOC 0x{:02x}: decode ended at {} bytes \ + of a {}-byte body)", + self.malformed, + frame[0], + consumed_bytes, + body + ); + } + return vec![0.0; out_len]; + } + // Per-packet harmonic postfilter (the codec's final pitch comb + 48-sample group delay), run // once over the whole packet with the 24 per-40-block lags and the average normalized bitrate. let plen = out.len(); @@ -543,6 +572,38 @@ mod tests { ); } + /// A stream that ends where it claims to must be accepted. This is the guard against the + /// endpoint check being too strict: the synthetic 120 ms vector is a well-formed six-frame + /// packet, and rejecting it would silence audio that decodes correctly. + #[test] + fn endpoint_check_accepts_a_well_formed_packet() { + let frames: Vec = + serde_json::from_str(include_str!("testdata/mlow_120ms_frames.json")).unwrap(); + let mut dec = MlowDecoder::new(); + for hex_frame in &frames { + let out = dec.decode(&hex::decode(hex_frame).unwrap()); + assert!( + out.iter().any(|&s| s != 0.0), + "a valid packet must not be rejected as malformed" + ); + } + } + + /// A body whose decode consumes far more bits than it holds is malformed: the range decoder + /// returns zero past the end without flagging it, so the synthesis runs on invented symbols and + /// can diverge to full scale. Conceal it as a lost frame rather than emitting that. + #[test] + fn endpoint_check_rejects_an_overrunning_body() { + // A 120 ms TOC with a body far too short for six internal frames. + let mut dec = MlowDecoder::new(); + let out = dec.decode(&[0x58, 0x03, 0x1a, 0xfb, 0x0a]); + assert_eq!(out.len(), 6 * SMPL_INTF_LEN, "still a full 120 ms slot"); + assert!( + out.iter().all(|&s| s == 0.0), + "an over-running body must be concealed, not synthesized" + ); + } + /// Real 120 ms packets captured from a live WhatsApp Desktop peer must decode to speech, not to /// full-scale noise. Reported on #1105 after the multi-frame admission landed: frames are now /// accepted, but the decode diverges partway through the packet and saturates, which is audibly @@ -551,12 +612,11 @@ mod tests { /// The assertions are deliberately coarse — this pins "the output is not garbage", which is what /// regressed, without pretending to a bit-exact target the fixture cannot supply. /// - /// IGNORED because it currently FAILS: the fix is not in yet. It is committed now so the repro - /// travels with the investigation rather than living in someone's scratch directory. Run it with - /// `cargo test -p wacore --features voip-mlow --lib live_desktop -- --ignored`, and delete this - /// attribute in the commit that makes it pass. + /// Under the entropy grammar this decoder implements, all 40 of these bodies end far from where + /// they claim to, so they are currently concealed rather than decoded — the same verdict the C + /// reference reaches on them. What this pins is that the output is never garbage; if a future + /// change lets them decode to real speech, that is progress and this test still passes. #[test] - #[ignore = "reproduces the open 120 ms decode divergence; no fix yet"] fn live_desktop_packets_decode_without_saturating() { let frames: Vec = serde_json::from_str(include_str!("testdata/live_120ms_frames.json")) diff --git a/wacore/src/voip/mlow/rangecoder.rs b/wacore/src/voip/mlow/rangecoder.rs index c6ab76e10..8e1b7d3a2 100644 --- a/wacore/src/voip/mlow/rangecoder.rs +++ b/wacore/src/voip/mlow/rangecoder.rs @@ -337,6 +337,13 @@ impl<'a> RangeDecoder<'a> { pub(crate) fn tell(&self) -> i32 { self.nbits_total - ilog(self.rng) } + + /// Bytes the payload actually holds. Paired with [`tell`] this is how a caller checks that a + /// stream ended where it claimed to: reads past either end silently return zero here, so an + /// impossible length is only visible by comparing the two. + pub(crate) fn storage(&self) -> u32 { + self.storage + } } /// Opus/CELT range ENCODER (`ec_enc`), the exact inverse of `RangeDecoder`, used by the mlow From 3e9565e0406a0fafc4f9c2e15b3e8c6768953a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= <55464917+jlucaso1@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:58:47 -0300 Subject: [PATCH 12/17] fix(voip/mlow): keep a concealed frame from leaking into the next Two defects in the endpoint check, both from review. The sticky range-error flag became unreachable: the malformed branch returns before the assignment, and `dec.err != 0` is part of its own condition, so `had_error` reported a clean decode for exactly the range-decoder failures it documents and the regression suites read it for. Set it before returning. Concealment also left the damage behind. The overrun is only detectable after the last internal frame, by which point the loop has advanced the LSF predictor, the CELP history and prev_nlsf from parameters invented past the end of the bad body. The next packet was then synthesized partly from those: measured as full-scale samples and RMS 0.290 against 0.114 from a fresh decoder. Snapshot the state before the loop and restore it when concealing. This diverges from the reference, which does not roll back. The reference only ever decodes streams it produced itself; this decoder meets streams it cannot read, so the leak is reachable here and audible in the following frame. The copy costs a few KB once per packet against 20-120 ms of audio. --- wacore/src/voip/mlow/decoder.rs | 58 +++++++++++-------- wacore/src/voip/mlow/smpl_celpdec.rs | 1 + wacore/src/voip/mlow/smpl_synth.rs | 2 +- .../voip/mlow/testdata/live_120ms_frames.json | 1 - 4 files changed, 35 insertions(+), 27 deletions(-) delete mode 100644 wacore/src/voip/mlow/testdata/live_120ms_frames.json diff --git a/wacore/src/voip/mlow/decoder.rs b/wacore/src/voip/mlow/decoder.rs index b0cb2b578..3d4d8eb53 100644 --- a/wacore/src/voip/mlow/decoder.rs +++ b/wacore/src/voip/mlow/decoder.rs @@ -202,6 +202,14 @@ impl MlowDecoder { // The low_rate bit of the smpl TOC (this capture is low_rate==0; the synth gates on it). let low_rate = (frame[0] >> 2) & 1 != 0; + // The overrun that invalidates a body is only detectable after the last internal frame, by + // which point the loop has already advanced the LSF predictor, the CELP history and + // `prev_nlsf`. Keep a copy so concealment can undo them: parameters invented past the end of + // a bad body must not seed the next packet. The reference leaves them advanced, but it never + // meets a stream it cannot read; this decoder does, and the leak is audible in the frame + // after. The copy is a few KB once per packet, against 20-120 ms of audio. + let state_before = self.state.clone(); + let mut out: Vec = Vec::with_capacity(frames * SMPL_INTF_LEN); // Collect the per-40-block lags (8 per internal frame) and the average normalized bitrate // for the per-packet harmonic postfilter. @@ -301,6 +309,12 @@ impl MlowDecoder { let consumed_bytes = (dec.tell().max(0) as u32).div_ceil(8); let body = dec.storage(); if body > consumed_bytes || body + 2 < consumed_bytes || dec.err != 0 { + // Sticky flag first: this branch swallows the range-decoder failure it is reporting, and + // `had_error` is what the suites read to see it. + if dec.err != 0 { + self.had_error = true; + } + self.state = state_before; self.malformed += 1; if self.malformed == 1 || self.malformed.is_multiple_of(100) { log::warn!( @@ -612,35 +626,29 @@ mod tests { /// The assertions are deliberately coarse — this pins "the output is not garbage", which is what /// regressed, without pretending to a bit-exact target the fixture cannot supply. /// - /// Under the entropy grammar this decoder implements, all 40 of these bodies end far from where - /// they claim to, so they are currently concealed rather than decoded — the same verdict the C - /// reference reaches on them. What this pins is that the output is never garbage; if a future - /// change lets them decode to real speech, that is progress and this test still passes. + /// Concealing a malformed frame must leave no trace: the decode loop mutates the LSF predictor, + /// the CELP history and `prev_nlsf` before the overrun is detectable, so without a rollback the + /// NEXT packet is synthesized partly from parameters invented past the end of the bad body. + /// The reference does not roll back, but it also never meets these packets; we do, repeatedly. #[test] - fn live_desktop_packets_decode_without_saturating() { + fn a_concealed_frame_does_not_contaminate_the_next() { let frames: Vec = - serde_json::from_str(include_str!("testdata/live_120ms_frames.json")) - .expect("live_120ms_frames.json"); - assert_eq!(frames.len(), 40, "fixture lost frames"); + serde_json::from_str(include_str!("testdata/inbound_capture_frames.json")).unwrap(); + let real = hex::decode(&frames[0]).unwrap(); - let mut dec = MlowDecoder::new(); - let (mut saturating, mut clipped, mut total) = (0usize, 0usize, 0usize); - for hex_frame in &frames { - let frame = hex::decode(hex_frame).unwrap(); - assert_eq!(frame[0], 0x58, "fixture must stay 120 ms active packets"); - let out = dec.decode(&frame); - assert_eq!(out.len(), 6 * SMPL_INTF_LEN); - if out.iter().any(|s| s.abs() >= 0.999) { - saturating += 1; - } - clipped += out.iter().filter(|s| s.abs() >= 0.999).count(); - total += out.len(); - } + let mut fresh = MlowDecoder::new(); + let want = fresh.decode(&real); - let clipped_pct = clipped as f64 * 100.0 / total as f64; - assert_eq!( - saturating, 0, - "{saturating}/40 packets hit full scale ({clipped_pct:.2}% of samples clipped); speech at this bitrate never does that" + let mut contaminated = MlowDecoder::new(); + let bad = contaminated.decode(&[0x58, 0x03, 0x1a, 0xfb, 0x0a]); + assert!(bad.iter().all(|&s| s == 0.0), "the bad frame is concealed"); + let got = contaminated.decode(&real); + + assert_eq!(got.len(), want.len()); + assert!( + got == want, + "a real frame after a concealed one must decode identically to one decoded on a fresh \ + decoder; state from the malformed body leaked into it" ); } diff --git a/wacore/src/voip/mlow/smpl_celpdec.rs b/wacore/src/voip/mlow/smpl_celpdec.rs index a9e74209f..800855116 100644 --- a/wacore/src/voip/mlow/smpl_celpdec.rs +++ b/wacore/src/voip/mlow/smpl_celpdec.rs @@ -330,6 +330,7 @@ pub(crate) struct CelpDecParams { } /// Persistent decoder synthesis state (float domain). +#[derive(Clone)] pub(crate) struct CelpDecState { noise: NoiseGenerator, acb_state: Vec, diff --git a/wacore/src/voip/mlow/smpl_synth.rs b/wacore/src/voip/mlow/smpl_synth.rs index 68c790d7a..eae682b4a 100644 --- a/wacore/src/voip/mlow/smpl_synth.rs +++ b/wacore/src/voip/mlow/smpl_synth.rs @@ -539,7 +539,7 @@ pub(crate) fn synth_internal_frame( } /// Cross-frame decoder state (the persistent LSF/pitch predictor, prev NLSF, CELP synthesis). -#[derive(Default)] +#[derive(Default, Clone)] pub(crate) struct SmplDecoderState { pub(crate) lstate: super::smpl_decode::SmplLsfState, pub(crate) prev_nlsf: Vec, diff --git a/wacore/src/voip/mlow/testdata/live_120ms_frames.json b/wacore/src/voip/mlow/testdata/live_120ms_frames.json deleted file mode 100644 index 1cbe7b760..000000000 --- a/wacore/src/voip/mlow/testdata/live_120ms_frames.json +++ /dev/null @@ -1 +0,0 @@ -["58031afb0afbadf477a2a19bf308304c3404759cb8831606578898ded464959801d27c7a0bf155df1cd675c60483b5011518bb26859d20", "58034b15a1258ee7c4d1c884553efce22cde2fdceae854920451143f9942c10b02ecdc07f97a941bfe4b461cbba5472eb5c05b841aa34530", "5803414d0a34ed99b167ec83eecc144504408483c22697ab54f28bef4dce0a90fdf0ccc435a5b5e4074fd1f05d78", "58029bb6c0896bc1464cf5a88f9131d46e6eba50a4260660afb5f49dc224245647d2e41158", "5842dcb5bc6f7f653fcc4e413f2bfad66571dfc36a4fd9e875e0d9df9938598e518f25cfad25fd3123755c067cf3c2de0bd8efdd295f4122e37c3870", "5803488dbd54781a2def300b437a7b5c79f75cbcc0d5d7a27845287bcbbdc4b600a6ecde9e5d3c14d6c7c88b907c82b6a0079713ddef44e4c13c9f8ee8db", "5803488d695d6e683dd3687d7745afbfd5c1e98b28295e478b4bed7a40499e2d25cc255aee4f801bfb06ca7768a7fa847942dd85b0", "5803488d5a8be6f245afbe4c37cf299078ab8f2ca8466be6064ab2d3f33e39143247fd3b01bb307e0cf4815df3eecf0b6b710b80802b", "5803482f9fa23266264fd8782cd67439076b41567fc93927ed914a55a41219874570de1c5ededcc7b34f3190d013479512c61dae8944e051afcd729460bb40", "58634b1868c6a76d038a690a8d1c54ba0f6e53648121e1651948777d283906365d8d1c124955c5ede4e739e612e9607eab71248ac78b9ccb7d45592aaa39efb359a2832d8fc16ed54c30cba56c", "580512adc823a050458f5fc0ca558ae8d3dee263ffd42854c5940bd1b88abedfecd8433bdcb3e6d154fafda04bda653428dba85eb092efab9c7230", "58034b15f17ef4282311241ec12c0a363c98a5d548ee0e33560e7b1ed1f41c3f2ceb744f16d32f82ce7c117688ba265d004d88487d3f44d98602f9c0", "58034b09a16f88a4f811a369cd98feb0a996cef645b6cf6c74ce6862394f8cfe53b184e6f54ebab46e928fe46fdd8abd30a5fc5cbb9468", "5803481ada9c35e8f8a7145615c8e5eb3021e1b615cec1cf09d22342d10d82d1b542fd7213902d84b36b8c00b98d67801c4dd82d41feca8249e7b51220", "58030efd44270f960e253dbca6f4282c3d5a7ab5d3a06641b785d4a8463dd3fb1ce53bc4cc1b17ab1b4ab205ad5a93f44be9bf004ad7b87f4cdb3d82bd680fc128646aff", "58034825acff9a2af50996db74f408232b35118b23577b52c2683fce6116da31a2c0d16c9d3ceca8e58a89299dfa14289ccf156d5837939fdf71c9af4d1b0653d97ad5", "58a00bcd599369165d9b0a5bf55bfeb0a1453da5d8b05745df5f1965fe5983356f4733b84735dbacbe035c8e4a726d1fa68b5726a979476ab76defb3ae5716a38b597a3e0d478d5db84db49b29d0a6cca24fca942afcc26fb1cdc6a7208de8fd2100f9938ee8", "58e00bd684f491975d6c65242e90e301f8dd59e1c89de3be8b34689dfbc473b3d7b8adb271cd92fc6ea418f564518fd1918bbc4c8559847ec59fb0bfecbcf2a59437f94c5fa41f89e8d4cda324d3f322d26b0c850313ce6ff29e9a310201bbbf8da67439f0492ad5b187fd5624616d767ded91fcf8e730ab9b0dfb80", "58e640cb821446e40442f1ccdf72cc97e9b691f08a3e7816e4659e8f385432a6e0d697469df9a9400df51664ef80fb93fc76f440da5f8097a9e68d39888deb88f05292bc1307aa8bcbec6f36ec16b856d84d6582e545a8a8d7e802937e90f41671bf5e18b1871bfc90cbfe465224081f1fb8afa10eef1bbe797d9e8e6ea34cf34d77db4384d60b9ef4f743d009a4e42cbaf2c525a8239064041e", "58ed6ed7d7ce0b39dfe42285f1ed84ab938dbb0fa48992ff4f33d1fbb7f0929f460baf39a0016cf28df03794f16c97c981f2af968afa38a3c9882605916a47a8c6a990b3fd8231effe96a093eaefe329b4b8d5a1b47eb0b6dfca0a8e3dbdd4eb3a43f8a2f2028621c7a8420dba66612862f95280", "58eb33e8ffe043abc85e5249bd8c00730f2f9c21cc47b184481758d1485bbb56488b68c4f3065337eeffb148778f4a844990c393f055647a352e53802fbe1125f7a176c65e7e0a5ee0a8242839141657ee18915a2bdf59623d80587bf922ac655d464a27c2d0b5d217d9c1455ad8430dffd9f8cf7ac3ceb6a3b1063190b223b511df7720", "58e793f8ddee6a00872dfe8e4cfdf94d7398016f31474029e45edb1dd8147ee7ebb10d435fd54cb29c5aecd88beca204cd06630433a661221ae43332e654bc8c7b487fc8f3c83e33d464e1fb756985efeb9fa6645d5d2596a53d42e51e04de486fd2ae93fed923a548bea6c758f2f84de3561aa7d4969f64c0bba1dce95dd6", "58e6e63cd0543c7c29446a5077922ea021e402906be704f1d1aa257bd99d1da3698163054a6f25fad41a229c8b1c5d0c2dcec7647714d5582cb97096c3dc3b155f2188cb719a01c2a01e2a613327273010a6dcd7c99a398ec8d37c80abd5e1ae0b4c421bd2fec863a73e0971825e6151a2644c90793433f96e36da01d41fdcbf2fff40631eca08", "58e6b140c3ec9653296dfbf05714c1511b74492039765fdb5902e3f96a2a68f8edf832856bd35a55148a71c403381cd230d9e24846de9bafd0ac1438fe53ee3694225dce156d2b217c0d2b43d5c2d3da82da32106eb1eb3631de479ee6b643baaa74427202d4cbaa8d61563c28b5cbdc8b54650937e943c361f5b2aa6bee5a41af10dce6573d0c5e0a6fdf51b489848a462115662c4fc8eefcd67af70a56", "58e6fd773f5c67725aa8a75bb1d652700eae2975112ef31e19b5681efee3c5536d540f0c95198e00c572eb4d51a2084d94461c98fc23c06ad916e1a4eb1c9992bf6a666025721a5484c8589afa640bcefaba4f59496566c680452448659976971cc50a0ffd7ac5f083dde69c9979a71ab69d6bcd9863767c766b11dc5a88ae5e662dbfdc3cd9453c75871e6b0c5d50e7a75dc2083d89f0d5f34c63831902483ef0", "58e6e5fcff388b1b0b0fc61ca557120b4fab2ac6e8d9e7be6eef410e11a9dd2a53f206bb596a47b4574a0f44d3ee9dd2b0773eef5b4629d209db8eb96cf494c403d8850f749173a5d16f3a766e6b1303408a13392b4c8844d785296c696663e23e764cc3a7de4e706efd59dcb59b02048f9d22dcfcd749165497a807984344fe214833584be9e25c89d4d27bc952f8dba682f0d688a0", "58e689df75d9e11769cc29530d36d6106a4850771597dc96962b7c3f713462f35ef3c812fb10d92963a77b7d9546ff077e9b3566bfd6b8942d4894964b358c453ac8deed55f10679178636dcd58a8ca1a41cbdffa118dae1e65c1224170a6ef9cea70f1ec564828f82416ff18094f83c8831d1c51b1af92bcca3ec11195164b4", "58e019a0103ec12f6b40dc759580316a09f38baeb9f18d708e5d2b038106205656ac5b5c53d62b895f76478351fe1563823a6bddbb45d4d399af9e6ec68b11498c6f12eb8a36909b7f2c9c4f736a4aa3b9dabf6b995f496c6b74b7b6704e364361b95cf56b4efecd4a6cabc0ed5bd976bcde78889e", "580a42134f78c51aad1a7db5ef910d9c4680a4c44c7599825df981aa08f72af9ca38a7a09cca1e0eb0a56501fe4c4554bb2d2b83418cb19e99fd90300d9456305a466672b16c40d88b36053785267f11e369809f8f4640bafa3fb8b78f", "580874ac0fe3bcde45b4b3428f5ced56dc5904000ad8f82e3d21ae6fe15de3bac64a789b0ebb09705dfc1a7c442e3af18ede7032d17b1acc7a1724f40a1bb6dc0762ab8f923e5d293c823dab7dbb99dadd0b97c672e71d16fe3b56572a29f3b14f8d6a3a09fd23f1ef738892b1156034", "5807f1a4d8163be21c87484cd243d93fa7fdce5f051bd20f8935ae2b1e27caa0b1c189e9fee040d374171ecead270b44cfc9c47190b8f8b8fe05758d05cb3b7287a8cd1fe14c2e50a0fb9b3ce9ffbf9d9e370993db02c10275ffeb940dc9aafaf283430b38", "58093231a10ff2b64229b7425b051aa6727ea3e2511d71631335772dc0fad81e498a52291b173994e5021295db8d151f04ae3a48c0dba6a17ec1e05c410b41b2fdbd02fd0584f8716e72466ced4f42c109a0730201dcf06fbd37a3bfaa5dc30ae0e17d7c169d0564606d80", "5867bbc97061722f481593ac8471bf771d192c0ddc38d3127e66fd306fa71d7a5c9de9170b533f7f1963343cd1ac6eaf6cad79981584b8c5dfa47339c15d63819a6b9c79cb550cb9f62f6756318c61ee09226e9dd2576ecd2f6fec93f72d1d886e037b5acdd3cd111ba050debf85f9af6474d04612b29771adf4306135c4b51caffe792b762d817da8735f0195f1e4", "58e0fb3e52f5aa6bc4dea569a6a345134cfd0083afa5346b516f4bfff726cf2ab1774ff290dcf90251526ccf9a63bf6a5912d9df46b6c794a029bb76d4611d4a72b3f1bacbadf121d0572edbbbb341fceb8b729acd3231dc73ef2ecdc5ed21c013fad95fbb4d393a006c9d6074c74cc51ef498f6a665a57d47e703012a1528af462c43d883fbd68a06fc3bc1cca09a3880", "58ea63db4efe4b64e080b8d54e9f0cdb57a8ed51826ad754c7cc56e8f2b654dba3afa3a8be0d9274416f7606fabf760a786b7253184b9e0c4ebc9caa562ee9633ea49c70648401c4b159d84f187523d259fe6414b4fb89ea0c91cda646a58f7fd8aae8eb525e082e74cf8322dd1e8f2f6ef6455315ea3738d1de111cefc7cc9b5e24e5e8836729", "58e8e3f2e55b3902446b19d4b883c51ba1f6d154fb1d83e6ed0f1dba2158e5fcbeb8230fdb070dc5ad44e439b5457909b33fe9637b44250a525d908a07bee3dd16923536479ed1aafd95087d4347a1fb2a26c90f8090416acb8ead826f51f405a18c547745c5715665335f859555c3cb093875c247d5f83c08f2eab0ddac085636da1bc841c05060", "58e79105597b47e48a9e6405efc94f1e1f9d647e038d64d8e0aae16e13edc8fdaed398d850efb5951f8c05a1b4aaa9f7ca7439aa66561e7b46563f491314468049f4d09d7f5e6331fe8b7afd6413bc4251aef4ef156a153e4985014ff9de019639f776248165587a3d327277c663a18cdc70f634d086fd6d52a438c15fd90dc60ee999740cd06aa294d3424415739030473c70c823c2", "58e8536a9fdc6d1d6e44ad4eb8abb3dd69992a845940231491cc664fb737d615a8f91f680f987747bb4e03afb2ad65c5b931276fc86cf7e9f4b7b43afdac000ad15fdb41bf674e744a24ee71b5a70966900a227df27144d4e8140839776932ea04a72e9db9054ccec79c45a1a450af123691f0567bdc913476bc440f3de5a29804c45859ea22999f460cd505b95eff5a8244", "58e818a069d282b3073e0c2680aece9e5be23109c92361da6deafa68d575d2fa183b4a0e4af1f5cf59788102c5cd9d65f78adfc1ebda871e3b6e5d9e75151016d26cfb33b6bf2fd42ba7c1c3a36f8712660f1e0fa3974f892c97de8cf1ac24300548ecd43d8499b6846435ed67c262cfa676055f4f0bb7776cf7a56c5d5b2e77108ceaad71122556bf528056b5237ca0879520a2afdad53183f6167090", "58e6dfbcad601edae8740a3da162092258170395bde62d454fe90e1a32d60288b79701adf275e3db1aba5ae1f6796dc1e6fdcd73a111128b5fdf36394dfde6633d9c47bfad7b0d0352ffbe03dfb47a1ee749fbc18fd43e93da9e73163a757a0c5a37b9b85d4a51cc058d75bf6b8c6805fccc7dec924361d7eac9d5acedc52ed2b57c1d92ed1f28f87c99f6229c8cf94022"] \ No newline at end of file From 537a4b694ae929387ff6b29d791767556ee7d140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= <55464917+jlucaso1@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:58:47 -0300 Subject: [PATCH 13/17] chore(voip/mlow): reject a stale oracle archive, and stop repeating a rationale A clean worktree at the pinned revision still says nothing about the archive the harness links: switching revisions without rebuilding leaves a stale .a that links fine and attributes its fixtures to a commit it was never built from. Compare it against the checkout and refuse when it is older. Also document MLOW_ALLOW_DIRTY_REFERENCE in the script header, since a hard fail should not require reading the body to find its escape hatch, and trim the declared-duration rationale in the engine to a pointer at the field that owns it. --- scripts/regenerate-mlow-vectors.sh | 18 ++++++++++++++++++ wacore/src/voip/engine.rs | 4 +--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/scripts/regenerate-mlow-vectors.sh b/scripts/regenerate-mlow-vectors.sh index 706258535..c0893b58a 100755 --- a/scripts/regenerate-mlow-vectors.sh +++ b/scripts/regenerate-mlow-vectors.sh @@ -23,6 +23,10 @@ # # MLOW_REFERENCE=/path/to/opus_mlow scripts/regenerate-mlow-vectors.sh # +# The script refuses to run against a reference worktree with uncommitted changes, or against a +# library older than the checkout, because neither can be attributed to a recorded revision. Set +# MLOW_ALLOW_DIRTY_REFERENCE=1 when generating from a modified oracle is the actual intent. +# # Regenerating changes committed fixtures. Re-run the decoder suite afterwards and treat any # correlation change as a finding, not as a number to paper over: # @@ -68,6 +72,20 @@ else echo " differences below may come from the reference, not from this repository" >&2 fi fi +# A clean worktree at the right commit still proves nothing about the ARCHIVE: switching revisions +# without rebuilding leaves a stale .a that links fine and attributes its output to a commit it was +# never built from. Compare against the last thing that changed the checkout. +lib="$ref/.libs/libopus.a" +if [[ -e "$ref/.git" ]]; then + head_file="$ref/.git/HEAD" + [[ -f "$ref/.git" ]] && head_file="$(git -C "$ref" rev-parse --git-dir)/HEAD" + if [[ -f "$head_file" && "$head_file" -nt "$lib" ]]; then + echo "error: $lib predates the current checkout, so it was not built from $actual_rev." >&2 + echo " Rebuild it (make -j\"\$(nproc)\" in \$MLOW_REFERENCE) and re-run." >&2 + exit 1 + fi +fi + echo "==> oracle: $ref @ $actual_rev" work="$(mktemp -d)" diff --git a/wacore/src/voip/engine.rs b/wacore/src/voip/engine.rs index 0f133bd38..cba7ea1ef 100644 --- a/wacore/src/voip/engine.rs +++ b/wacore/src/voip/engine.rs @@ -1217,9 +1217,7 @@ impl CallEngine { #[cfg(feature = "voip-mlow")] { let decoded = pcm.decoder.decode(&encoded); - // The DECLARED duration, not the decoded length: a SID emits a fixed silence slot, so - // reading it off the output would shrink the cushion at a DTX transition and discard - // buffered speech that has not been played yet. + // Declared, not decoded; see `MlowDecoder::last_packet_samps`. pcm.packet_samps = pcm.decoder.last_packet_samps(); for s in decoded { pcm.jitter From 9fe07677e8599a5bd8a887d3fa009564bc337da8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= <55464917+jlucaso1@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:59:25 -0300 Subject: [PATCH 14/17] docs(voip/mlow): drop the captured-speech fixture from provenance Companion to the file removal, which landed a commit early by mistake. AGENTS.md forbids real PII in test vectors, including ones derived from production captures, and this document itself states that no captured call audio is present. The 120 ms fixture was compressed speech from a live peer: calling the words neutral removes the conversation, not the person's voice, which is what a working decoder would reconstruct. Committing it contradicted both. The code path keeps its coverage from synthetic bytes in endpoint_check_rejects_an_overrunning_body. The field evidence that real packets take that path belongs in the issue thread as text, not shipped with the repository as someone's voice. --- wacore/src/voip/mlow/testdata/PROVENANCE.md | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/wacore/src/voip/mlow/testdata/PROVENANCE.md b/wacore/src/voip/mlow/testdata/PROVENANCE.md index c3063ed46..61f11cbdd 100644 --- a/wacore/src/voip/mlow/testdata/PROVENANCE.md +++ b/wacore/src/voip/mlow/testdata/PROVENANCE.md @@ -109,21 +109,6 @@ Both halves must be regenerated together — `decoder.rs::multi_frame_fixture_ha fails if the PCM length stops matching the frame count, and asserts every frame is still TOC `0x58` so the fixture cannot quietly drift off the multi-frame path. -## Live capture (not synthetic) - -| fixture | consumer / test | oracle | -| --- | --- | --- | -| `live_120ms_frames.json` | `decoder.rs::live_desktop_packets_decode_without_saturating` | none — 40 consecutive active 120 ms packets captured from a live WhatsApp Desktop peer and reported on #1105 | - -The one fixture here that is NOT derived from `synth_mic.raw`. It exists because the synthetic -120 ms vector, generated by the C encoder, does not reproduce what the real client sends: those -packets are 37-161 bytes where the generated ones are 265-351, and only the captured ones expose the -decode divergence. Contributed as deliberately neutral speech, so it carries no personal content. - -There is no reference PCM for it: the C reference decodes these packets to pure silence, so it -cannot serve as an oracle here. The test therefore asserts the coarse property that regressed — -output that does not saturate — rather than a sample-accurate target. - ## What is still not reproducible, and why The fixtures above the multi-frame section predate the harness and were produced by tools that no From a927ada7e26f3b924ee918c8897c8e314fdc2084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= <55464917+jlucaso1@users.noreply.github.com> Date: Sun, 26 Jul 2026 00:33:56 -0300 Subject: [PATCH 15/17] perf(voip/mlow): keep the test trace out of the rollback snapshot Review caught that the per-packet snapshot added for concealment was copying `dbg_exc_pre`, the test-only excitation capture. That buffer accumulates every synthesized subframe for the whole stream and is never cleared, so cloning it once per packet made decoding quadratic in stream length. The cost was not theoretical: the wacore suite went from 39s to 3332s, and I had already written the 56-minute figure off as the codec being heavy in debug. It was this. Hand-write Clone so the trace starts empty. It is a diagnostic capture, not codec state that concealment needs to restore. --- wacore/src/voip/mlow/smpl_celpdec.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/wacore/src/voip/mlow/smpl_celpdec.rs b/wacore/src/voip/mlow/smpl_celpdec.rs index 800855116..9d438beba 100644 --- a/wacore/src/voip/mlow/smpl_celpdec.rs +++ b/wacore/src/voip/mlow/smpl_celpdec.rs @@ -330,7 +330,6 @@ pub(crate) struct CelpDecParams { } /// Persistent decoder synthesis state (float domain). -#[derive(Clone)] pub(crate) struct CelpDecState { noise: NoiseGenerator, acb_state: Vec, @@ -345,6 +344,26 @@ pub(crate) struct CelpDecState { pub(crate) dbg_exc_pre: Vec, } +// Hand-written so the test-only excitation trace is NOT carried along. It accumulates every +// synthesized subframe for the whole stream and is never cleared, so copying it in the per-packet +// rollback snapshot would make decoding quadratic in stream length. It is a diagnostic capture, not +// codec state that concealment has to restore. +impl Clone for CelpDecState { + fn clone(&self) -> Self { + Self { + noise: self.noise.clone(), + acb_state: self.acb_state.clone(), + acb_state_len: self.acb_state_len, + lpc_synth_mem: self.lpc_synth_mem, + lsf_prev: self.lsf_prev, + prev_nrgres: self.prev_nrgres, + hp: self.hp.clone(), + #[cfg(test)] + dbg_exc_pre: Vec::new(), + } + } +} + impl Default for CelpDecState { fn default() -> Self { let acb_state_len = SMPL_SUBFR_LEN + 2 * SMPL_MAX_PITCH_LAG + SMPL_LTP_INTERPOL_DELAY; From da206787458611f7c90a409a7b4a555f01c11f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= <55464917+jlucaso1@users.noreply.github.com> Date: Sun, 26 Jul 2026 00:33:56 -0300 Subject: [PATCH 16/17] chore(voip/mlow): check the oracle archive against sources, not git metadata `git reset --hard` on the same branch moves the branch ref and leaves .git/HEAD untouched, so the previous mtime check would accept an archive built at another revision and attribute its fixtures to the pinned commit. Compare the archive against the reference sources instead: one of them being newer is the direct statement that the archive does not correspond to the tree whose revision the script is about to record. --- scripts/regenerate-mlow-vectors.sh | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/scripts/regenerate-mlow-vectors.sh b/scripts/regenerate-mlow-vectors.sh index c0893b58a..56d1d1890 100755 --- a/scripts/regenerate-mlow-vectors.sh +++ b/scripts/regenerate-mlow-vectors.sh @@ -76,14 +76,16 @@ fi # without rebuilding leaves a stale .a that links fine and attributes its output to a commit it was # never built from. Compare against the last thing that changed the checkout. lib="$ref/.libs/libopus.a" -if [[ -e "$ref/.git" ]]; then - head_file="$ref/.git/HEAD" - [[ -f "$ref/.git" ]] && head_file="$(git -C "$ref" rev-parse --git-dir)/HEAD" - if [[ -f "$head_file" && "$head_file" -nt "$lib" ]]; then - echo "error: $lib predates the current checkout, so it was not built from $actual_rev." >&2 - echo " Rebuild it (make -j\"\$(nproc)\" in \$MLOW_REFERENCE) and re-run." >&2 - exit 1 - fi +# Compare against the SOURCES rather than any git metadata file: `git reset --hard` on the same +# branch moves the branch ref while leaving .git/HEAD untouched, so a HEAD mtime check would accept +# an archive built from a different revision. A source newer than the archive is the direct +# statement that the archive does not correspond to the tree it would be attributed to. +if newer="$(find "$ref/smpl" "$ref/src" "$ref/celt" \ + \( -name '*.c' -o -name '*.h' \) -newer "$lib" -print -quit 2>/dev/null)" \ + && [[ -n "$newer" ]]; then + echo "error: $lib is older than $newer, so it was not built from the current tree." >&2 + echo " Rebuild it (make -j\"\$(nproc)\" in \$MLOW_REFERENCE) and re-run." >&2 + exit 1 fi echo "==> oracle: $ref @ $actual_rev" From 8845bf3209a2e4443c43ac13ae57a86f2d74a59e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= <55464917+jlucaso1@users.noreply.github.com> Date: Sun, 26 Jul 2026 07:29:55 -0300 Subject: [PATCH 17/17] fix(voip): take the endpoint window from the shipped decoder, not the fork The endpoint check rejected a decode that ended more than two bytes past its storage, following the C fork's smpl_check_end_result. The shipped decoder is looser: WhatsAppNative.dll @ 0x180337e30 does `add r8d, 0x4` before its second compare, accepting up to four. Two is the stricter of the pair, so the port concealed frames the official client plays -- a false report of corruption that silences audio which is fine. The upper slack exists to absorb the range coder's trailing carry bytes, which the encoder does not emit, so the exact bound is not arbitrary and copying it from the fork was the wrong source. The predicate moves into `endpoint_is_valid` so the window is stated once and can be pinned directly. It is tested as arithmetic rather than through a payload because no synthetic body lands on +3 on demand. This does not change the 120 ms Desktop packets tracked on #1105: those over-run by a factor of 3 to 5, nowhere near four bytes, and they are still concealed. --- wacore/src/voip/mlow/decoder.rs | 42 +++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/wacore/src/voip/mlow/decoder.rs b/wacore/src/voip/mlow/decoder.rs index 3d4d8eb53..9924ec245 100644 --- a/wacore/src/voip/mlow/decoder.rs +++ b/wacore/src/voip/mlow/decoder.rs @@ -32,6 +32,18 @@ fn internal_frames(frame_ms: i32) -> Option { (frame_ms > 10).then(|| ((frame_ms + 10) / 20) as usize) } +/// Does a decode that ended after `consumed` bytes of a `storage`-byte body land where a valid +/// stream can end? Under-running is always malformed; the upper slack absorbs the range coder's +/// final carry bytes, which the encoder does not emit. +/// +/// The bound is four, taken from the shipped decoder rather than from the C fork: `WhatsAppNative.dll` +/// @ `0x180337e30` does `add r8d, 0x4` before its second compare, while the fork's +/// `smpl_check_end_result` allows only `+2`. Two is the stricter of the pair, so it would conceal +/// frames the official client plays — a false report of corruption, silencing audio that is fine. +fn endpoint_is_valid(storage: u32, consumed: u32) -> bool { + storage <= consumed && consumed <= storage + 4 +} + /// Stateful pure-Rust MLow decoder. Decodes one RTP payload (a bare MLow frame, or a SplitRed /// packet when redundancy was negotiated) into a PCM frame at 16 kHz, one 20 ms internal frame /// per chained frame in the packet. @@ -302,13 +314,13 @@ impl MlowDecoder { // Endpoint check, before the postfilter as in the reference: the range decoder returns zero // past either end of its storage WITHOUT flagging it, so an impossible stream decodes into // plausible-looking symbols and the synthesis can diverge to full scale. Comparing where the - // decode ended against what the body actually held is the only way to see it. A valid stream - // finishes within two bytes of its storage; anything else is a malformed frame, concealed as - // a lost one rather than synthesized. State already advanced is left as-is, matching the - // reference, which also returns without rolling back. + // decode ended against what the body actually held is the only way to see it. Anything + // outside the accepted window is a malformed frame, concealed as a lost one rather than + // synthesized. State already advanced is left as-is, matching the reference, which also + // returns without rolling back. let consumed_bytes = (dec.tell().max(0) as u32).div_ceil(8); let body = dec.storage(); - if body > consumed_bytes || body + 2 < consumed_bytes || dec.err != 0 { + if !endpoint_is_valid(body, consumed_bytes) || dec.err != 0 { // Sticky flag first: this branch swallows the range-decoder failure it is reporting, and // `had_error` is what the suites read to see it. if dec.err != 0 { @@ -606,6 +618,26 @@ mod tests { /// A body whose decode consumes far more bits than it holds is malformed: the range decoder /// returns zero past the end without flagging it, so the synthesis runs on invented symbols and /// can diverge to full scale. Conceal it as a lost frame rather than emitting that. + #[test] + /// The accepted window is the shipped decoder's, not the C fork's. The fork stops at `+2`, so a + /// stream ending three or four bytes long would be concealed here and played by the official + /// client. Pinned as arithmetic because no synthetic body lands on `+3` on demand. + #[test] + fn endpoint_window_matches_the_shipped_decoder() { + assert!(endpoint_is_valid(40, 40), "exact end is valid"); + assert!(endpoint_is_valid(40, 42), "the fork's +2 stays valid"); + assert!( + endpoint_is_valid(40, 44), + "+4 is valid: WhatsAppNative.dll @ 0x180337e30 adds 4 before its second compare, and \ + rejecting here would silence audio the official client plays" + ); + assert!(!endpoint_is_valid(40, 45), "+5 over-runs"); + assert!( + !endpoint_is_valid(40, 39), + "under-running is always malformed" + ); + } + #[test] fn endpoint_check_rejects_an_overrunning_body() { // A 120 ms TOC with a body far too short for six internal frames.