Skip to main content

wmv_decoder/
decoder.rs

1//! VC-1 / WMV9 Macroblock Decoder
2//!
3//! Full Simple/Main-profile decode path:
4//!   • Proper VLC coefficient decoding (intra + inter TCOEF tables)
5//!   • Uniform / non-uniform inverse quantization
6//!   • VC-1 integer IDCT (8×8, 8×4, 4×8, 4×4)
7//!   • Half-pixel motion compensation with bilinear filter
8//!   • Overlap smoothing filter (Main profile)
9//!   • Reference frame buffer for P/B frames
10
11use crate::bitreader::BitReader;
12use crate::error::{DecoderError, Result};
13use crate::na_msmpeg4_mv_tables::{
14    FF_MSMP4_MV_TABLE0, FF_MSMP4_MV_TABLE0_LENS, FF_MSMP4_MV_TABLE1, FF_MSMP4_MV_TABLE1_LENS,
15};
16use crate::na_msmpeg4_tables::FF_MB_NON_INTRA_TABLES;
17use crate::na_rl_tables::{
18    FF_RL_BASES, FF_WMV1_SCANTABLE, FF_WMV2_SCANTABLE_A, FF_WMV2_SCANTABLE_B,
19};
20use crate::na_simple_idct as ffidct;
21use crate::na_wmv2_tables::{FF_MSMP4_DC_TABLES, FF_MSMP4_MB_I_TABLE};
22use crate::na_wmv2dsp as wmv2dsp;
23use crate::vc1::{FrameType, MvMode, PictureHeader, Profile, SequenceHeader};
24use crate::vc1_tables::{
25    TT_4X4, TT_4X8, TT_4X8_LEFT, TT_4X8_RIGHT, TT_8X4, TT_8X4_BOTTOM, TT_8X4_TOP, TT_8X8,
26    TTBLK_TO_TT, VC1_ZZ_4X4, VC1_ZZ_4X8, VC1_ZZ_8X4, Vc1AcTable, ac_tables, cbpcy_vlcs,
27    mvdata_vlcs, subblkpat_vlcs, ttblk_vlcs, ttmb_vlcs,
28};
29use crate::vlc::{
30    SCAN_INTRA, SCAN_VERT, VLC_ESCAPE, VlcTable, ZIGZAG, unpack_rl, wmv2_cbpc_p_vlc, wmv2_cbpy_vlc,
31    wmv2_tcoef_inter_vlc, wmv2_tcoef_intra_vlc,
32};
33use crate::vlc_tree::VlcTree;
34use crate::wmv2::{Wmv2FrameHeader, Wmv2FrameType, Wmv2Params};
35
36// ─── Frame buffer ────────────────────────────────────────────────────────────
37
38#[derive(Clone, Debug)]
39pub struct YuvFrame {
40    pub width: u32,
41    pub height: u32,
42    pub y: Vec<u8>,
43    pub cb: Vec<u8>,
44    pub cr: Vec<u8>,
45}
46
47impl YuvFrame {
48    pub fn new(width: u32, height: u32) -> Self {
49        let y_sz = (width * height) as usize;
50        let uv_sz = y_sz / 4;
51        YuvFrame {
52            width,
53            height,
54            y: vec![16u8; y_sz],
55            cb: vec![128u8; uv_sz],
56            cr: vec![128u8; uv_sz],
57        }
58    }
59
60    pub fn to_planar_u8(&self) -> Vec<u8> {
61        let mut out = Vec::with_capacity(self.y.len() + self.cb.len() + self.cr.len());
62        out.extend_from_slice(&self.y);
63        out.extend_from_slice(&self.cb);
64        out.extend_from_slice(&self.cr);
65        out
66    }
67
68    pub fn clear(&mut self) {
69        self.y.fill(16);
70        self.cb.fill(128);
71        self.cr.fill(128);
72    }
73}
74
75// ─── VC-1 inverse transforms ─────────────────────────────────────────────────
76// These are integer-for-integer translations of FFmpeg libavcodec/vc1dsp.c.
77// VC-1 uses distinct 8x8, 8x4, 4x8 and 4x4 transforms; they are not scaled
78// variants of a generic IDCT.
79
80pub fn idct8x8(block: &mut [i32; 64]) {
81    let src = *block;
82    let mut temp = [0i32; 64];
83    for i in 0..8 {
84        let t1 = 12 * (src[i] + src[i + 32]) + 4;
85        let t2 = 12 * (src[i] - src[i + 32]) + 4;
86        let t3 = 16 * src[i + 16] + 6 * src[i + 48];
87        let t4 = 6 * src[i + 16] - 16 * src[i + 48];
88        let t5 = t1 + t3;
89        let t6 = t2 + t4;
90        let t7 = t2 - t4;
91        let t8 = t1 - t3;
92        let o1 = 16 * src[i + 8] + 15 * src[i + 24] + 9 * src[i + 40] + 4 * src[i + 56];
93        let o2 = 15 * src[i + 8] - 4 * src[i + 24] - 16 * src[i + 40] - 9 * src[i + 56];
94        let o3 = 9 * src[i + 8] - 16 * src[i + 24] + 4 * src[i + 40] + 15 * src[i + 56];
95        let o4 = 4 * src[i + 8] - 9 * src[i + 24] + 15 * src[i + 40] - 16 * src[i + 56];
96        let d = i * 8;
97        temp[d] = (t5 + o1) >> 3;
98        temp[d + 1] = (t6 + o2) >> 3;
99        temp[d + 2] = (t7 + o3) >> 3;
100        temp[d + 3] = (t8 + o4) >> 3;
101        temp[d + 4] = (t8 - o4) >> 3;
102        temp[d + 5] = (t7 - o3) >> 3;
103        temp[d + 6] = (t6 - o2) >> 3;
104        temp[d + 7] = (t5 - o1) >> 3;
105    }
106    for i in 0..8 {
107        let t1 = 12 * (temp[i] + temp[i + 32]) + 64;
108        let t2 = 12 * (temp[i] - temp[i + 32]) + 64;
109        let t3 = 16 * temp[i + 16] + 6 * temp[i + 48];
110        let t4 = 6 * temp[i + 16] - 16 * temp[i + 48];
111        let t5 = t1 + t3;
112        let t6 = t2 + t4;
113        let t7 = t2 - t4;
114        let t8 = t1 - t3;
115        let o1 = 16 * temp[i + 8] + 15 * temp[i + 24] + 9 * temp[i + 40] + 4 * temp[i + 56];
116        let o2 = 15 * temp[i + 8] - 4 * temp[i + 24] - 16 * temp[i + 40] - 9 * temp[i + 56];
117        let o3 = 9 * temp[i + 8] - 16 * temp[i + 24] + 4 * temp[i + 40] + 15 * temp[i + 56];
118        let o4 = 4 * temp[i + 8] - 9 * temp[i + 24] + 15 * temp[i + 40] - 16 * temp[i + 56];
119        block[i] = (t5 + o1) >> 7;
120        block[i + 8] = (t6 + o2) >> 7;
121        block[i + 16] = (t7 + o3) >> 7;
122        block[i + 24] = (t8 + o4) >> 7;
123        block[i + 32] = (t8 - o4 + 1) >> 7;
124        block[i + 40] = (t7 - o3 + 1) >> 7;
125        block[i + 48] = (t6 - o2 + 1) >> 7;
126        block[i + 56] = (t5 - o1 + 1) >> 7;
127    }
128}
129
130fn inv_trans_8x4_part(block: &mut [i32; 64], row0: usize) {
131    let mut src = [0i32; 32];
132    for r in 0..4 {
133        for c in 0..8 {
134            src[r * 8 + c] = block[(row0 + r) * 8 + c];
135        }
136    }
137    for r in 0..4 {
138        let o = r * 8;
139        let t1 = 12 * (src[o] + src[o + 4]) + 4;
140        let t2 = 12 * (src[o] - src[o + 4]) + 4;
141        let t3 = 16 * src[o + 2] + 6 * src[o + 6];
142        let t4 = 6 * src[o + 2] - 16 * src[o + 6];
143        let t5 = t1 + t3;
144        let t6 = t2 + t4;
145        let t7 = t2 - t4;
146        let t8 = t1 - t3;
147        let a = 16 * src[o + 1] + 15 * src[o + 3] + 9 * src[o + 5] + 4 * src[o + 7];
148        let b = 15 * src[o + 1] - 4 * src[o + 3] - 16 * src[o + 5] - 9 * src[o + 7];
149        let c = 9 * src[o + 1] - 16 * src[o + 3] + 4 * src[o + 5] + 15 * src[o + 7];
150        let d = 4 * src[o + 1] - 9 * src[o + 3] + 15 * src[o + 5] - 16 * src[o + 7];
151        src[o] = (t5 + a) >> 3;
152        src[o + 1] = (t6 + b) >> 3;
153        src[o + 2] = (t7 + c) >> 3;
154        src[o + 3] = (t8 + d) >> 3;
155        src[o + 4] = (t8 - d) >> 3;
156        src[o + 5] = (t7 - c) >> 3;
157        src[o + 6] = (t6 - b) >> 3;
158        src[o + 7] = (t5 - a) >> 3;
159    }
160    for c in 0..8 {
161        let t1 = 17 * (src[c] + src[c + 16]) + 64;
162        let t2 = 17 * (src[c] - src[c + 16]) + 64;
163        let t3 = 22 * src[c + 8] + 10 * src[c + 24];
164        let t4 = 22 * src[c + 24] - 10 * src[c + 8];
165        block[row0 * 8 + c] = (t1 + t3) >> 7;
166        block[(row0 + 1) * 8 + c] = (t2 - t4) >> 7;
167        block[(row0 + 2) * 8 + c] = (t2 + t4) >> 7;
168        block[(row0 + 3) * 8 + c] = (t1 - t3) >> 7;
169    }
170}
171
172fn inv_trans_4x8_part(block: &mut [i32; 64], col0: usize) {
173    let mut src = [0i32; 32];
174    for r in 0..8 {
175        for c in 0..4 {
176            src[r * 4 + c] = block[r * 8 + col0 + c];
177        }
178    }
179    for r in 0..8 {
180        let o = r * 4;
181        let t1 = 17 * (src[o] + src[o + 2]) + 4;
182        let t2 = 17 * (src[o] - src[o + 2]) + 4;
183        let t3 = 22 * src[o + 1] + 10 * src[o + 3];
184        let t4 = 22 * src[o + 3] - 10 * src[o + 1];
185        src[o] = (t1 + t3) >> 3;
186        src[o + 1] = (t2 - t4) >> 3;
187        src[o + 2] = (t2 + t4) >> 3;
188        src[o + 3] = (t1 - t3) >> 3;
189    }
190    for c in 0..4 {
191        let t1 = 12 * (src[c] + src[c + 16]) + 64;
192        let t2 = 12 * (src[c] - src[c + 16]) + 64;
193        let t3 = 16 * src[c + 8] + 6 * src[c + 24];
194        let t4 = 6 * src[c + 8] - 16 * src[c + 24];
195        let t5 = t1 + t3;
196        let t6 = t2 + t4;
197        let t7 = t2 - t4;
198        let t8 = t1 - t3;
199        let a = 16 * src[c + 4] + 15 * src[c + 12] + 9 * src[c + 20] + 4 * src[c + 28];
200        let b = 15 * src[c + 4] - 4 * src[c + 12] - 16 * src[c + 20] - 9 * src[c + 28];
201        let d = 9 * src[c + 4] - 16 * src[c + 12] + 4 * src[c + 20] + 15 * src[c + 28];
202        let e = 4 * src[c + 4] - 9 * src[c + 12] + 15 * src[c + 20] - 16 * src[c + 28];
203        block[col0 + c] = (t5 + a) >> 7;
204        block[8 + col0 + c] = (t6 + b) >> 7;
205        block[16 + col0 + c] = (t7 + d) >> 7;
206        block[24 + col0 + c] = (t8 + e) >> 7;
207        block[32 + col0 + c] = (t8 - e + 1) >> 7;
208        block[40 + col0 + c] = (t7 - d + 1) >> 7;
209        block[48 + col0 + c] = (t6 - b + 1) >> 7;
210        block[56 + col0 + c] = (t5 - a + 1) >> 7;
211    }
212}
213
214fn inv_trans_4x4_part(block: &mut [i32; 64], row0: usize, col0: usize) {
215    let mut src = [0i32; 16];
216    for r in 0..4 {
217        for c in 0..4 {
218            src[r * 4 + c] = block[(row0 + r) * 8 + col0 + c];
219        }
220    }
221    for r in 0..4 {
222        let o = r * 4;
223        let t1 = 17 * (src[o] + src[o + 2]) + 4;
224        let t2 = 17 * (src[o] - src[o + 2]) + 4;
225        let t3 = 22 * src[o + 1] + 10 * src[o + 3];
226        let t4 = 22 * src[o + 3] - 10 * src[o + 1];
227        src[o] = (t1 + t3) >> 3;
228        src[o + 1] = (t2 - t4) >> 3;
229        src[o + 2] = (t2 + t4) >> 3;
230        src[o + 3] = (t1 - t3) >> 3;
231    }
232    for c in 0..4 {
233        let t1 = 17 * (src[c] + src[c + 8]) + 64;
234        let t2 = 17 * (src[c] - src[c + 8]) + 64;
235        let t3 = 22 * src[c + 4] + 10 * src[c + 12];
236        let t4 = 22 * src[c + 12] - 10 * src[c + 4];
237        block[row0 * 8 + col0 + c] = (t1 + t3) >> 7;
238        block[(row0 + 1) * 8 + col0 + c] = (t2 - t4) >> 7;
239        block[(row0 + 2) * 8 + col0 + c] = (t2 + t4) >> 7;
240        block[(row0 + 3) * 8 + col0 + c] = (t1 - t3) >> 7;
241    }
242}
243
244pub fn apply_idct(block: &mut [i32; 64], tt: u8) {
245    use crate::vc1_tables::*;
246    match tt {
247        TT_8X8 => idct8x8(block),
248        TT_8X4 | TT_8X4_TOP | TT_8X4_BOTTOM => {
249            if tt != TT_8X4_BOTTOM {
250                inv_trans_8x4_part(block, 0);
251            }
252            if tt != TT_8X4_TOP {
253                inv_trans_8x4_part(block, 4);
254            }
255        }
256        TT_4X8 | TT_4X8_LEFT | TT_4X8_RIGHT => {
257            if tt != TT_4X8_RIGHT {
258                inv_trans_4x8_part(block, 0);
259            }
260            if tt != TT_4X8_LEFT {
261                inv_trans_4x8_part(block, 4);
262            }
263        }
264        TT_4X4 => {
265            inv_trans_4x4_part(block, 0, 0);
266            inv_trans_4x4_part(block, 0, 4);
267            inv_trans_4x4_part(block, 4, 0);
268            inv_trans_4x4_part(block, 4, 4);
269        }
270        _ => idct8x8(block),
271    }
272}
273
274#[inline(always)]
275fn vc1_transpose_scan_index(index: usize) -> usize {
276    (index >> 3) | ((index & 7) << 3)
277}
278
279#[inline(always)]
280fn vc1_scan_index(scan: &[usize], pos: usize, res_fasttx: bool) -> usize {
281    let index = scan[pos];
282    if res_fasttx {
283        vc1_transpose_scan_index(index)
284    } else {
285        index
286    }
287}
288
289/// FFmpeg switches the inverse-transform family for WMV3 Simple/Main at
290/// decoder initialization time.  When RES_FASTTX=0 it uses the legacy simple
291/// IDCT callbacks; when RES_FASTTX=1 it keeps the VC-1 integer transforms.
292/// Keep that selection explicit instead of treating RES_FASTTX as a bitstream
293/// parsing-only flag.
294fn apply_wmv3_idct(block: &mut [i32; 64], tt: u8, res_fasttx: bool) {
295    if res_fasttx {
296        apply_idct(block, tt);
297        return;
298    }
299
300    let mut b = [0i16; 64];
301    for (dst, src) in b.iter_mut().zip(block.iter()) {
302        *dst = *src as i16;
303    }
304
305    match tt {
306        TT_8X8 => ffidct::ff_simple_idct_int16_8bit(&mut b),
307        TT_8X4 | TT_8X4_TOP | TT_8X4_BOTTOM => {
308            if tt != TT_8X4_BOTTOM {
309                ffidct::ff_simple_idct84_int16_8bit(&mut b, 0);
310            }
311            if tt != TT_8X4_TOP {
312                ffidct::ff_simple_idct84_int16_8bit(&mut b, 4);
313            }
314        }
315        TT_4X8 | TT_4X8_LEFT | TT_4X8_RIGHT => {
316            if tt != TT_4X8_RIGHT {
317                ffidct::ff_simple_idct48_int16_8bit(&mut b, 0);
318            }
319            if tt != TT_4X8_LEFT {
320                ffidct::ff_simple_idct48_int16_8bit(&mut b, 4);
321            }
322        }
323        TT_4X4 => {
324            ffidct::ff_simple_idct44_int16_8bit(&mut b, 0, 0);
325            ffidct::ff_simple_idct44_int16_8bit(&mut b, 0, 4);
326            ffidct::ff_simple_idct44_int16_8bit(&mut b, 4, 0);
327            ffidct::ff_simple_idct44_int16_8bit(&mut b, 4, 4);
328        }
329        _ => ffidct::ff_simple_idct_int16_8bit(&mut b),
330    }
331
332    for (dst, src) in block.iter_mut().zip(b.iter()) {
333        *dst = *src as i32;
334    }
335}
336
337// ─── Inverse quantization ────────────────────────────────────────────────────
338// SMPTE 421M §8.1.4.  Two modes: uniform and non-uniform.
339
340fn iquant_uniform(level: i32, pquant: i32, halfqp: bool) -> i32 {
341    if level == 0 {
342        return 0;
343    }
344    let step = 2 * pquant;
345    let base = step * level.abs() + pquant;
346    let delta = if halfqp { pquant } else { 0 };
347    let result = if level > 0 {
348        base + delta
349    } else {
350        -(base + delta)
351    };
352    result.clamp(-2048, 2047)
353}
354
355fn iquant_nonuniform(level: i32, pquant: i32) -> i32 {
356    if level == 0 {
357        return 0;
358    }
359    // Non-uniform quantizer step table from SMPTE 421M Table 3
360    const STEP: [i32; 32] = [
361        1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
362        27, 29, 31, 33, 35, 37, 63,
363    ];
364    let step = STEP[(pquant as usize).min(31)];
365    let result = step * level.abs() + pquant;
366    if level > 0 {
367        result.clamp(-2048, 2047)
368    } else {
369        (-result).clamp(-2048, 2047)
370    }
371}
372
373// ─── DC step-size tables (SMPTE 421M Table 3) ───────────────────────────────
374// Indexed by pquant (0 unused, 1..31).
375// Luma and chroma have separate tables.
376// The value is multiplied by 128 here to match the IDCT normalization domain
377// (IDCT output = input / 128, so DC_recon must be in the ×128 domain).
378
379const DC_STEP_LUMA: [i32; 32] = [
380    0, // 0: unused
381    128, 256, 384, 512, 640, 768, 896, 1024, // pquant 1-8:  step = pquant
382    1152, 1280, 1408, 1536, 1664, 1792, 1920, 2048, // 9-16
383    2176, 2304, 2432, 2560, 2688, 2816, 2944, 3072, // 17-24
384    3328, 3584, 3840, 4096, 4352, 4608, 8192, // 25-31
385];
386
387const DC_STEP_CHROMA: [i32; 32] = [
388    0, // 0: unused
389    128, 128, 128, 256, 256, 384, 384, 512, // pquant 1-8
390    512, 640, 640, 768, 768, 896, 896, 1024, // 9-16
391    1024, 1152, 1152, 1280, 1280, 1408, 1408, 1536, // 17-24
392    1664, 1792, 1920, 2048, 2176, 2304, 4096, // 25-31
393];
394
395#[inline]
396fn dc_step(pquant: i32, is_luma: bool) -> i32 {
397    let idx = pquant.clamp(1, 31) as usize;
398    if is_luma {
399        DC_STEP_LUMA[idx]
400    } else {
401        DC_STEP_CHROMA[idx]
402    }
403}
404
405// ─── VC-1 loop filter (Simple/Main) ────────────────────────────────────────
406// Ported from FFmpeg libavcodec/vc1dsp.c / vc1_loopfilter.c.
407//
408// VC-1 does not use the WMV1/2 four-tap delta filter here.  The normative
409// filter first evaluates a0/a1/a2 against the picture quantizer, then applies
410// a clipped correction to the two samples straddling the edge.  Edges are
411// processed in groups of four lines: line 2 is the gate for the other three.
412
413#[inline(always)]
414fn vc1_filter_line(plane: &mut [u8], src: usize, stride: isize, pq: i32) -> bool {
415    #[inline(always)]
416    fn at(plane: &[u8], src: usize, stride: isize, n: isize) -> i32 {
417        plane[(src as isize + n * stride) as usize] as i32
418    }
419
420    let mut a0 = (2 * (at(plane, src, stride, -2) - at(plane, src, stride, 1))
421        - 5 * (at(plane, src, stride, -1) - at(plane, src, stride, 0))
422        + 4)
423        >> 3;
424    let a0_sign = a0 >> 31;
425    a0 = (a0 ^ a0_sign) - a0_sign;
426
427    if a0 < pq {
428        let a1 = ((2 * (at(plane, src, stride, -4) - at(plane, src, stride, -1))
429            - 5 * (at(plane, src, stride, -3) - at(plane, src, stride, -2))
430            + 4)
431            >> 3)
432            .abs();
433        let a2 = ((2 * (at(plane, src, stride, 0) - at(plane, src, stride, 3))
434            - 5 * (at(plane, src, stride, 1) - at(plane, src, stride, 2))
435            + 4)
436            >> 3)
437            .abs();
438        if a1 < a0 || a2 < a0 {
439            let mut clip = at(plane, src, stride, -1) - at(plane, src, stride, 0);
440            let clip_sign = clip >> 31;
441            clip = ((clip ^ clip_sign) - clip_sign) >> 1;
442            if clip != 0 {
443                // FFmpeg vc1_filter_line(): d is based on (a0 - a3), and
444                // the correction is applied only when a0 and the edge delta
445                // have opposite signs.  The previous Rust port reversed the
446                // subtraction, converted d to an absolute value, and inverted
447                // the sign test.
448                let a3 = a1.min(a2);
449                let mut d = (5 * (a0 - a3)) >> 3;
450                if (a0_sign ^ clip_sign) != 0 {
451                    d = d.min(clip);
452                    d = (d ^ clip_sign) - clip_sign;
453                    let l = (src as isize - stride) as usize;
454                    let r = src;
455                    plane[l] = (plane[l] as i32 - d).clamp(0, 255) as u8;
456                    plane[r] = (plane[r] as i32 + d).clamp(0, 255) as u8;
457                }
458                return true;
459            }
460        }
461    }
462    false
463}
464
465/// FFmpeg vc1_loop_filter(): `src` points at the first pixel on the positive
466/// side of the edge. `step` walks along the edge and `stride` crosses it.
467fn vc1_loop_filter_edge(
468    plane: &mut [u8],
469    src: usize,
470    step: isize,
471    stride: isize,
472    len: usize,
473    pq: i32,
474) {
475    let mut i = 0usize;
476    while i + 3 < len {
477        let p2 = (src as isize + (i as isize + 2) * step) as usize;
478        if vc1_filter_line(plane, p2, stride, pq) {
479            for off in [0isize, 1, 3] {
480                let p = (src as isize + (i as isize + off) * step) as usize;
481                vc1_filter_line(plane, p, stride, pq);
482            }
483        }
484        i += 4;
485    }
486}
487
488#[inline]
489fn vc1_filter_horizontal_boundary(
490    plane: &mut [u8],
491    width: usize,
492    height: usize,
493    x: usize,
494    y: usize,
495    len: usize,
496    pq: i32,
497) {
498    if y < 4 || y + 3 >= height || x >= width {
499        return;
500    }
501    let len = len.min(width - x) & !3;
502    if len >= 4 {
503        vc1_loop_filter_edge(plane, y * width + x, 1, width as isize, len, pq);
504    }
505}
506
507#[inline]
508fn vc1_filter_vertical_boundary(
509    plane: &mut [u8],
510    width: usize,
511    height: usize,
512    x: usize,
513    y: usize,
514    len: usize,
515    pq: i32,
516) {
517    if x < 4 || x + 3 >= width || y >= height {
518        return;
519    }
520    let len = len.min(height - y) & !3;
521    if len >= 4 {
522        vc1_loop_filter_edge(plane, y * width + x, width as isize, 1, len, pq);
523    }
524}
525
526/// Filter the horizontal borders owned by one progressive I/BI/B macroblock.
527///
528/// This is the operation FFmpeg calls the "V loop" (the filter runs in the
529/// vertical direction across a horizontal edge).  Outer picture edges are not
530/// filtered; the internal 8-pixel luma edge is always filtered.
531fn vc1_i_v_loop_filter_mb(frame: &mut YuvFrame, mb_row: usize, mb_col: usize, pq: i32) {
532    let w = frame.width as usize;
533    let h = frame.height as usize;
534    let x = mb_col * 16;
535    let y = mb_row * 16;
536
537    if y > 0 {
538        vc1_filter_horizontal_boundary(&mut frame.y, w, h, x, y, 16, pq);
539    }
540    if y + 8 < h {
541        vc1_filter_horizontal_boundary(&mut frame.y, w, h, x, y + 8, 16, pq);
542    }
543
544    let cw = w / 2;
545    let ch = h / 2;
546    let cx = mb_col * 8;
547    let cy = mb_row * 8;
548    if cy > 0 {
549        vc1_filter_horizontal_boundary(&mut frame.cb, cw, ch, cx, cy, 8, pq);
550        vc1_filter_horizontal_boundary(&mut frame.cr, cw, ch, cx, cy, 8, pq);
551    }
552}
553
554/// Filter the vertical borders owned by one progressive I/BI/B macroblock.
555///
556/// FFmpeg calls this the "H loop".  It must run after the V loop for the same
557/// macroblock because the integer filter changes samples at edge crossings.
558fn vc1_i_h_loop_filter_mb(frame: &mut YuvFrame, mb_row: usize, mb_col: usize, pq: i32) {
559    let w = frame.width as usize;
560    let h = frame.height as usize;
561    let x = mb_col * 16;
562    let y = mb_row * 16;
563
564    if x > 0 {
565        vc1_filter_vertical_boundary(&mut frame.y, w, h, x, y, 16, pq);
566    }
567    if x + 8 < w {
568        vc1_filter_vertical_boundary(&mut frame.y, w, h, x + 8, y, 16, pq);
569    }
570
571    let cw = w / 2;
572    let ch = h / 2;
573    let cx = mb_col * 8;
574    let cy = mb_row * 8;
575    if cx > 0 {
576        vc1_filter_vertical_boundary(&mut frame.cb, cw, ch, cx, cy, 8, pq);
577        vc1_filter_vertical_boundary(&mut frame.cr, cw, ch, cx, cy, 8, pq);
578    }
579}
580
581/// Reproduce the call order of FFmpeg `ff_vc1_i_loop_filter()` after the full
582/// progressive picture has been reconstructed.
583///
584/// It is tempting to run every horizontal edge and then every vertical edge,
585/// but that is not equivalent: FFmpeg deliberately pipelines the two filters
586/// behind overlap reconstruction.  At edge crossings the first filter changes
587/// pixels consumed by the second, so preserving this macroblock wavefront is
588/// required for bit-exact reconstruction.
589fn vc1_apply_i_loop_filter(frame: &mut YuvFrame, pq: i32, mb_width: usize, mb_height: usize) {
590    if mb_width == 0 || mb_height == 0 {
591        return;
592    }
593
594    for cur_y in 0..mb_height {
595        for cur_x in 0..mb_width {
596            // ff_vc1_i_loop_filter(): V loop trails overlap by one row and
597            // one column, with an explicit last-row/last-column flush.
598            if cur_y >= 1 {
599                if cur_x >= 1 {
600                    vc1_i_v_loop_filter_mb(frame, cur_y - 1, cur_x - 1, pq);
601                }
602                if cur_x + 1 == mb_width {
603                    vc1_i_v_loop_filter_mb(frame, cur_y - 1, cur_x, pq);
604                }
605            }
606            if cur_y + 1 == mb_height {
607                if cur_x >= 1 {
608                    vc1_i_v_loop_filter_mb(frame, cur_y, cur_x - 1, pq);
609                }
610                if cur_x + 1 == mb_width {
611                    vc1_i_v_loop_filter_mb(frame, cur_y, cur_x, pq);
612                }
613            }
614
615            // H loop trails the V loop by another row.  Keep the exact
616            // wavefront/flush order used by FFmpeg instead of globally
617            // regrouping filters by orientation.
618            if cur_y >= 2 {
619                if cur_x >= 1 {
620                    vc1_i_h_loop_filter_mb(frame, cur_y - 2, cur_x - 1, pq);
621                }
622                if cur_x + 1 == mb_width {
623                    vc1_i_h_loop_filter_mb(frame, cur_y - 2, cur_x, pq);
624                }
625            }
626            if cur_y + 1 == mb_height {
627                if cur_y >= 1 {
628                    if cur_x >= 1 {
629                        vc1_i_h_loop_filter_mb(frame, cur_y - 1, cur_x - 1, pq);
630                    }
631                    if cur_x + 1 == mb_width {
632                        vc1_i_h_loop_filter_mb(frame, cur_y - 1, cur_x, pq);
633                    }
634                }
635                if cur_x >= 1 {
636                    vc1_i_h_loop_filter_mb(frame, cur_y, cur_x - 1, pq);
637                }
638                if cur_x + 1 == mb_width {
639                    vc1_i_h_loop_filter_mb(frame, cur_y, cur_x, pq);
640                }
641            }
642        }
643    }
644}
645
646// ─── Coefficient decoder ─────────────────────────────────────────────────────
647
648/// Read raw DC differential (before prediction).
649/// Returns the signed differential value (NOT yet scaled / predicted).
650fn read_dc_diff(br: &mut BitReader<'_>, dc_vlc: &VlcTable) -> i32 {
651    let dc_size = match dc_vlc.decode(br) {
652        Some(s @ 0..) => s as u8,
653        _ => return 0,
654    };
655    if dc_size == 0 {
656        return 0;
657    }
658    let raw = br.read_bits(dc_size).unwrap_or(0) as i32;
659    // MSB=0 → negative (one's complement offset per SMPTE 421M §8.1.4.4)
660    if raw & (1 << (dc_size - 1)) != 0 {
661        raw
662    } else {
663        raw - (1 << dc_size) + 1
664    }
665}
666
667// ─── DC Prediction buffer ────────────────────────────────────────────────────
668// SMPTE 421M §8.1.4.6.
669//
670// For each macroblock position we store the reconstructed (post-IDCT) DC
671// value for each of the 6 blocks (Y0 Y1 Y2 Y3 Cb Cr) in the "DC scale"
672// domain (i.e. the integer value before the final /8 normalisation step).
673//
674// Prediction direction is chosen per-block by comparing the gradient
675// magnitudes of the left and top neighbours.
676
677#[derive(Clone)]
678pub struct DcPredBuffer {
679    mb_w: usize,
680    /// Stored as reconstructed DC * 8 / pquant to stay in coeff domain.
681    /// Layout: [mb_row * mb_w + mb_col][blk 0..6]
682    dc: Vec<[i32; 6]>,
683}
684
685impl DcPredBuffer {
686    pub fn new(mb_w: usize, mb_h: usize) -> Self {
687        DcPredBuffer {
688            mb_w,
689            dc: vec![[1024i32; 6]; mb_w * mb_h],
690        }
691    }
692
693    /// Return the predicted DC for block `blk` at (mb_row, mb_col).
694    /// Also decides prediction direction (horizontal vs vertical).
695    /// Returns (pred_value, use_left: bool).
696    pub fn predict(&self, mb_row: usize, mb_col: usize, blk: usize) -> (i32, bool) {
697        // Neighbour block positions in the DC grid (SMPTE 421M Fig. 8-4)
698        // For luma: blocks are arranged as:
699        //   0 1
700        //   2 3
701        // Left-of-block:  blk 0←left_mb.blk1,  blk 1←same_mb.blk0,
702        //                  blk 2←left_mb.blk3,  blk 3←same_mb.blk2
703        // Top-of-block:   blk 0←top_mb.blk2,   blk 1←top_mb.blk3,
704        //                  blk 2←same_mb.blk0,  blk 3←same_mb.blk1
705        // Chroma (blk 4/5): left = left_mb.same_blk, top = top_mb.same_blk
706        let (dc_left, dc_top, dc_topleft) = self.dc_neighbours(mb_row, mb_col, blk);
707
708        // Gradient: |A - C| (horizontal) vs |B - C| (vertical)
709        // A = left, B = top, C = top-left
710        let grad_h = (dc_left - dc_topleft).unsigned_abs();
711        let grad_v = (dc_top - dc_topleft).unsigned_abs();
712
713        if grad_v <= grad_h {
714            // Predict from top (vertical predictor)
715            (dc_top, false)
716        } else {
717            // Predict from left (horizontal predictor)
718            (dc_left, true)
719        }
720    }
721
722    fn dc_neighbours(&self, mb_row: usize, mb_col: usize, blk: usize) -> (i32, i32, i32) {
723        // Helper: get stored DC for a possibly-out-of-bounds MB/blk
724        let get = |r: isize, c: isize, b: usize| -> i32 {
725            if r < 0 || c < 0 {
726                return 1024;
727            } // mid-gray default
728            let idx = r as usize * self.mb_w + c as usize;
729            if idx >= self.dc.len() {
730                return 1024;
731            }
732            self.dc[idx][b]
733        };
734
735        let r = mb_row as isize;
736        let c = mb_col as isize;
737
738        match blk {
739            // ── luma ───────────────────────────────────────────────────────
740            0 => {
741                let left = get(r, c - 1, 1); // right half of left MB
742                let top = get(r - 1, c, 2); // bottom-left of top MB
743                let topleft = get(r - 1, c - 1, 3);
744                (left, top, topleft)
745            }
746            1 => {
747                // SMPTE 421M §8.1.4.6 Fig 8-4: topleft = blk3 of top-left MB
748                let left = get(r, c, 0);
749                let top = get(r - 1, c, 3);
750                let topleft = get(r - 1, c - 1, 3);
751                (left, top, topleft)
752            }
753            2 => {
754                let left = get(r, c - 1, 3);
755                let top = get(r, c, 0); // blk0 of same MB
756                let topleft = get(r, c - 1, 1);
757                (left, top, topleft)
758            }
759            3 => {
760                let left = get(r, c, 2);
761                let top = get(r, c, 1);
762                let topleft = get(r, c, 0);
763                (left, top, topleft)
764            }
765            // ── chroma ─────────────────────────────────────────────────────
766            _ => {
767                let left = get(r, c - 1, blk);
768                let top = get(r - 1, c, blk);
769                let topleft = get(r - 1, c - 1, blk);
770                (left, top, topleft)
771            }
772        }
773    }
774
775    /// Store the reconstructed DC value (in coeff domain) for later prediction.
776    pub fn store(&mut self, mb_row: usize, mb_col: usize, blk: usize, dc_recon: i32) {
777        let idx = mb_row * self.mb_w + mb_col;
778        if idx < self.dc.len() {
779            self.dc[idx][blk] = dc_recon;
780        }
781    }
782}
783
784// ─── WMV2/MSMPEG4 DC predictor (upstream ff_msmpeg4_pred_dc logic) ─────────────
785
786/// Predictor storage is in "scaled DC coefficient" domain (level * dc_scale).
787/// Default value 1024 corresponds to mid-gray (128) with scale=8.
788pub struct Wmv2DcPredBuffer {
789    mb_w: usize,
790    dc: Vec<[i32; 6]>,
791}
792
793impl Wmv2DcPredBuffer {
794    pub fn new(mb_w: usize, mb_h: usize) -> Self {
795        Wmv2DcPredBuffer {
796            mb_w,
797            dc: vec![[1024i32; 6]; mb_w * mb_h],
798        }
799    }
800
801    fn neighbours(&self, mb_row: usize, mb_col: usize, blk: usize) -> (i32, i32, i32) {
802        // upstream alignment note:
803        //   ff_msmpeg4_pred_dc() predicts in the *8x8 block grid* (block_index[n]) with
804        //   neighbours A(left), B(above-left), C(above):
805        //       B C
806        //       A X
807        // Our storage is per-macroblock ([i32; 6]), so we emulate upstream's block-grid
808        // addressing for luma blocks (0..3) and macroblock-grid for chroma (4..5).
809
810        #[inline(always)]
811        fn get_luma(dc: &[[i32; 6]], mb_w: usize, bx: isize, by: isize) -> i32 {
812            if bx < 0 || by < 0 {
813                return 1024;
814            }
815            let mb_x = (bx >> 1) as usize;
816            let mb_y = (by >> 1) as usize;
817            let idx = mb_y * mb_w + mb_x;
818            if idx >= dc.len() {
819                return 1024;
820            }
821            let sub_x = (bx & 1) as usize;
822            let sub_y = (by & 1) as usize;
823            let b = (sub_y << 1) | sub_x; // 0..3
824            dc[idx][b]
825        }
826
827        #[inline(always)]
828        fn get_chroma(dc: &[[i32; 6]], mb_w: usize, mb_x: isize, mb_y: isize, blk: usize) -> i32 {
829            if mb_x < 0 || mb_y < 0 {
830                return 1024;
831            }
832            let idx = (mb_y as usize) * mb_w + (mb_x as usize);
833            if idx >= dc.len() {
834                return 1024;
835            }
836            dc[idx][blk]
837        }
838
839        if blk < 4 {
840            let bx = (mb_col as isize) * 2 + ((blk & 1) as isize);
841            let by = (mb_row as isize) * 2 + ((blk >> 1) as isize);
842            let a = get_luma(&self.dc, self.mb_w, bx - 1, by);
843            let b = get_luma(&self.dc, self.mb_w, bx - 1, by - 1);
844            let c = get_luma(&self.dc, self.mb_w, bx, by - 1);
845            (a, b, c)
846        } else {
847            let mx = mb_col as isize;
848            let my = mb_row as isize;
849            let a = get_chroma(&self.dc, self.mb_w, mx - 1, my, blk);
850            let b = get_chroma(&self.dc, self.mb_w, mx - 1, my - 1, blk);
851            let c = get_chroma(&self.dc, self.mb_w, mx, my - 1, blk);
852            (a, b, c)
853        }
854    }
855
856    /// Returns (pred_level, dir). dir=0 => left, dir=1 => top.
857    pub fn predict(&self, mb_row: usize, mb_col: usize, blk: usize, scale: i32) -> (i32, i32) {
858        let (a0, b0, c0) = self.neighbours(mb_row, mb_col, blk);
859        // Convert from scaled DC to level domain with rounding: (x + scale/2) / scale.
860        let a = (a0 + (scale >> 1)) / scale;
861        let b = (b0 + (scale >> 1)) / scale;
862        let c = (c0 + (scale >> 1)) / scale;
863
864        // WMV2/MSMPEG4 version > V3 uses STRICT '<' (see upstream ff_msmpeg4_pred_dc).
865        if (a - b).abs() < (b - c).abs() {
866            (c, 1)
867        } else {
868            (a, 0)
869        }
870    }
871
872    pub fn store(&mut self, mb_row: usize, mb_col: usize, blk: usize, dc_coeff_scaled: i32) {
873        let idx = mb_row * self.mb_w + mb_col;
874        if idx < self.dc.len() {
875            self.dc[idx][blk] = dc_coeff_scaled;
876        }
877    }
878}
879
880// ─── AC escape decoder ───────────────────────────────────────────────────────
881// SMPTE 421M §8.1.4.5 — Three escape modes following VLC_ESCAPE sentinel.
882//
883//   After VLC_ESCAPE, read mode bits:
884//     "0"  → Mode 1: level offset
885//     "10" → Mode 2: run offset
886//     "11" → Mode 3: absolute fixed-length
887//
888// Returns (run, signed_level, last).
889#[inline]
890fn decode_escape_coeff(br: &mut BitReader<'_>, ac_vlc: &VlcTable) -> (u8, i32, bool) {
891    let mode = {
892        let b0 = br.read_bit().unwrap_or(false);
893        if !b0 {
894            1u8
895        } else {
896            let b1 = br.read_bit().unwrap_or(false);
897            if b1 { 3 } else { 2 }
898        }
899    };
900    match mode {
901        1 => {
902            // Mode 1: level offset — VLC gives (run, base_level, last)
903            let sym = ac_vlc.decode(br).unwrap_or(0);
904            if sym == VLC_ESCAPE {
905                return (0, 0, true);
906            }
907            let (run, base_level, last) = unpack_rl(sym);
908            let sign = br.read_bit().unwrap_or(false);
909            let offset = ac_vlc.max_level(run as usize, last) as i32 + 1;
910            let level = base_level as i32 + offset;
911            (run, if sign { -level } else { level }, last)
912        }
913        2 => {
914            // Mode 2: run offset — VLC gives (base_run, level, last)
915            let sym = ac_vlc.decode(br).unwrap_or(0);
916            if sym == VLC_ESCAPE {
917                return (0, 0, true);
918            }
919            let (base_run, level, last) = unpack_rl(sym);
920            let sign = br.read_bit().unwrap_or(false);
921            let offset = ac_vlc.max_run(level as usize, last) as i32 + 1;
922            let run = (base_run as i32 + offset).min(63) as u8;
923            let sl = level as i32;
924            (run, if sign { -sl } else { sl }, last)
925        }
926        _ => {
927            // Mode 3: absolute — 1-bit LAST + 6-bit RUN + 8-bit |LEVEL| + 1-bit SIGN
928            let last = br.read_bit().unwrap_or(false);
929            let run = br.read_bits(6).unwrap_or(0) as u8;
930            let level = br.read_bits(8).unwrap_or(1).max(1) as i32;
931            let sign = br.read_bit().unwrap_or(false);
932            (run, if sign { -level } else { level }, last)
933        }
934    }
935}
936
937/// Decode one 8×8 block of AC+DC coefficients.
938/// `is_intra`: use intra table / scan, otherwise inter.
939/// `is_luma`:  use luma DC VLC.
940/// Returns filled `[i32; 64]` in natural order (not zigzag).
941fn decode_block(
942    br: &mut BitReader<'_>,
943    is_intra: bool,
944    is_luma: bool,
945    pquant: i32,
946    halfqp: bool,
947    uniform: bool,
948    tt: u8,
949    dc_luma: &VlcTable,
950    dc_chroma: &VlcTable,
951    ac_intra: &VlcTable,
952    ac_inter: &VlcTable,
953) -> [i32; 64] {
954    let mut blk = [0i32; 64];
955    let scan: &[usize; 64] = if is_intra { &SCAN_INTRA } else { &ZIGZAG };
956
957    // DC coefficient (intra only)
958    if is_intra {
959        let dc_vlc = if is_luma { dc_luma } else { dc_chroma };
960        blk[0] = read_dc_diff(br, dc_vlc);
961    }
962
963    // AC coefficients
964    let ac_vlc = if is_intra { ac_intra } else { ac_inter };
965    let mut idx = if is_intra { 1usize } else { 0 };
966
967    while let Some(sym) = ac_vlc.decode(br) {
968        let (run, signed_level, last) = if sym == VLC_ESCAPE {
969            decode_escape_coeff(br, ac_vlc)
970        } else {
971            let (r, l, last) = unpack_rl(sym);
972            let sign = br.read_bit().unwrap_or(false);
973            (r, if sign { -(l as i32) } else { l as i32 }, last)
974        };
975
976        idx += run as usize;
977        if idx >= 64 {
978            break;
979        }
980
981        let mag = signed_level.abs();
982        let qval = if uniform {
983            iquant_uniform(mag, pquant, halfqp)
984        } else {
985            iquant_nonuniform(mag, pquant)
986        };
987        let signed_val = if signed_level < 0 { -qval } else { qval };
988
989        // Use appropriate scan based on transform type
990        let scan_order: &[usize; 64] = match tt {
991            3 | 4 => &SCAN_VERT,
992            _ => scan,
993        };
994        let pos = scan_order.get(idx).copied().unwrap_or(idx);
995        blk[pos] = signed_val;
996        idx += 1;
997
998        if last || br.is_empty() {
999            break;
1000        }
1001    }
1002
1003    blk
1004}
1005
1006/// Decode only the AC coefficients of one intra block (DC is handled separately).
1007fn decode_block_ac(
1008    br: &mut BitReader<'_>,
1009    _is_luma: bool,
1010    pquant: i32,
1011    halfqp: bool,
1012    uniform: bool,
1013    tt: u8,
1014    ac_vlc: &VlcTable,
1015) -> [i32; 64] {
1016    let mut blk = [0i32; 64];
1017    let mut idx = 1usize; // start at 1, skip DC slot
1018
1019    while let Some(sym) = ac_vlc.decode(br) {
1020        let (run, signed_level, last) = if sym == VLC_ESCAPE {
1021            decode_escape_coeff(br, ac_vlc)
1022        } else {
1023            let (r, l, last) = unpack_rl(sym);
1024            let sign = br.read_bit().unwrap_or(false);
1025            (r, if sign { -(l as i32) } else { l as i32 }, last)
1026        };
1027
1028        idx += run as usize;
1029        if idx >= 64 {
1030            break;
1031        }
1032
1033        let mag = signed_level.abs();
1034        let qval = if uniform {
1035            iquant_uniform(mag, pquant, halfqp)
1036        } else {
1037            iquant_nonuniform(mag, pquant)
1038        };
1039        let sval = if signed_level < 0 { -qval } else { qval };
1040
1041        let scan_order: &[usize; 64] = match tt {
1042            3 | 4 => &SCAN_VERT,
1043            _ => &SCAN_INTRA,
1044        };
1045        let pos = scan_order.get(idx).copied().unwrap_or(idx);
1046        blk[pos] = sval;
1047        idx += 1;
1048
1049        if last || br.is_empty() {
1050            break;
1051        }
1052    }
1053    blk
1054}
1055
1056// ─── AC Prediction buffer ────────────────────────────────────────────────────
1057// SMPTE 421M §8.1.4.7.
1058//
1059// For each macroblock/block we cache the first row (AC[1..7]) and first
1060// column (AC[8,16,24,32,40,48,56]) of reconstructed coefficients (pre-IDCT,
1061// post-IQ) so they can be used as predictors for neighbouring blocks.
1062
1063#[derive(Clone)]
1064pub struct AcPredBuffer {
1065    mb_w: usize,
1066    /// First row of coefficients for each MB×block: [mb_idx][blk][0..7]
1067    row: Vec<[[i32; 7]; 6]>,
1068    /// First col of coefficients for each MB×block: [mb_idx][blk][0..7]
1069    col: Vec<[[i32; 7]; 6]>,
1070}
1071
1072impl AcPredBuffer {
1073    pub fn new(mb_w: usize, mb_h: usize) -> Self {
1074        let n = mb_w * mb_h;
1075        AcPredBuffer {
1076            mb_w,
1077            row: vec![[[0i32; 7]; 6]; n],
1078            col: vec![[[0i32; 7]; 6]; n],
1079        }
1080    }
1081
1082    pub fn clear(&mut self) {
1083        self.row.fill([[0i32; 7]; 6]);
1084        self.col.fill([[0i32; 7]; 6]);
1085    }
1086
1087    /// Get the AC predictor row (indices 1..7 of the reconstructed block).
1088    /// Returns the first row of the left neighbour (for horizontal prediction).
1089    pub fn pred_row(&self, mb_row: usize, mb_col: usize, blk: usize) -> [i32; 7] {
1090        let (src_mb_r, src_mb_c, src_blk) = Self::left_neighbour(mb_row, mb_col, blk);
1091        if src_mb_r as isize >= 0 && src_mb_c as isize >= 0 {
1092            let idx = src_mb_r * self.mb_w + src_mb_c;
1093            if idx < self.row.len() {
1094                return self.row[idx][src_blk];
1095            }
1096        }
1097        [0i32; 7]
1098    }
1099
1100    /// Get the AC predictor column (rows 1..7 of the reconstructed block).
1101    /// Returns the first column of the top neighbour (for vertical prediction).
1102    pub fn pred_col(&self, mb_row: usize, mb_col: usize, blk: usize) -> [i32; 7] {
1103        let (src_mb_r, src_mb_c, src_blk) = Self::top_neighbour(mb_row, mb_col, blk);
1104        if src_mb_r as isize >= 0 && src_mb_c as isize >= 0 {
1105            let idx = src_mb_r * self.mb_w + src_mb_c;
1106            if idx < self.col.len() {
1107                return self.col[idx][src_blk];
1108            }
1109        }
1110        [0i32; 7]
1111    }
1112
1113    /// VC-1 AC prediction from the left uses the neighbour's first coefficient column.
1114    pub fn pred_left_col(&self, mb_row: usize, mb_col: usize, blk: usize) -> [i32; 7] {
1115        let (r, c, b) = Self::left_neighbour(mb_row, mb_col, blk);
1116        let idx = r.wrapping_mul(self.mb_w).wrapping_add(c);
1117        if c < self.mb_w && idx < self.col.len() {
1118            self.col[idx][b]
1119        } else {
1120            [0; 7]
1121        }
1122    }
1123
1124    /// VC-1 AC prediction from the top uses the neighbour's first coefficient row.
1125    pub fn pred_top_row(&self, mb_row: usize, mb_col: usize, blk: usize) -> [i32; 7] {
1126        let (r, c, b) = Self::top_neighbour(mb_row, mb_col, blk);
1127        let idx = r.wrapping_mul(self.mb_w).wrapping_add(c);
1128        if self.mb_w != 0 && c < self.mb_w && idx < self.row.len() {
1129            self.row[idx][b]
1130        } else {
1131            [0; 7]
1132        }
1133    }
1134
1135    pub fn store_row(&mut self, mb_row: usize, mb_col: usize, blk: usize, row: [i32; 7]) {
1136        let idx = mb_row * self.mb_w + mb_col;
1137        if idx < self.row.len() {
1138            self.row[idx][blk] = row;
1139        }
1140    }
1141
1142    pub fn store_col(&mut self, mb_row: usize, mb_col: usize, blk: usize, col: [i32; 7]) {
1143        let idx = mb_row * self.mb_w + mb_col;
1144        if idx < self.col.len() {
1145            self.col[idx][blk] = col;
1146        }
1147    }
1148
1149    /// Left neighbour source: same logic as DC prediction neighbour mapping.
1150    fn left_neighbour(mb_row: usize, mb_col: usize, blk: usize) -> (usize, usize, usize) {
1151        match blk {
1152            0 => (mb_row, mb_col.wrapping_sub(1), 1),
1153            1 => (mb_row, mb_col, 0),
1154            2 => (mb_row, mb_col.wrapping_sub(1), 3),
1155            3 => (mb_row, mb_col, 2),
1156            _ => (mb_row, mb_col.wrapping_sub(1), blk),
1157        }
1158    }
1159
1160    fn top_neighbour(mb_row: usize, mb_col: usize, blk: usize) -> (usize, usize, usize) {
1161        match blk {
1162            0 => (mb_row.wrapping_sub(1), mb_col, 2),
1163            1 => (mb_row.wrapping_sub(1), mb_col, 3),
1164            2 => (mb_row, mb_col, 0),
1165            3 => (mb_row, mb_col, 1),
1166            _ => (mb_row.wrapping_sub(1), mb_col, blk),
1167        }
1168    }
1169}
1170
1171// ─── MV Predictor ────────────────────────────────────────────────────────────
1172// SMPTE 421M §8.3.5.3.
1173//
1174// The MV predictor for 1-MV macroblocks is the median of three neighbouring
1175// MVs: left (A), top (B), and top-right (C).  When a neighbour is out-of-
1176// frame or skipped, its MV is treated as (0,0).
1177
1178#[derive(Clone, Default)]
1179pub struct MvPredictor {
1180    mb_w: usize,
1181    /// Stored MVs per MB: (mvx, mvy) in half-pixel units
1182    mvs: Vec<(i32, i32)>,
1183    /// Whether each MB was skipped (skipped MBs propagate MV=0)
1184    skipped: Vec<bool>,
1185}
1186
1187impl MvPredictor {
1188    pub fn new(mb_w: usize, mb_h: usize) -> Self {
1189        let n = mb_w * mb_h;
1190        MvPredictor {
1191            mb_w,
1192            mvs: vec![(0, 0); n],
1193            skipped: vec![true; n],
1194        }
1195    }
1196
1197    /// Compute the predicted MV for (mb_row, mb_col) from three neighbours.
1198    pub fn predict(&self, mb_row: usize, mb_col: usize) -> (i32, i32) {
1199        let get = |r: isize, c: isize| -> (i32, i32) {
1200            if r < 0 || c < 0 {
1201                return (0, 0);
1202            }
1203            let idx = r as usize * self.mb_w + c as usize;
1204            if idx >= self.mvs.len() || self.skipped[idx] {
1205                return (0, 0);
1206            }
1207            self.mvs[idx]
1208        };
1209
1210        let r = mb_row as isize;
1211        let c = mb_col as isize;
1212
1213        let (ax, ay) = get(r, c - 1); // left
1214        let (bx, by) = get(r - 1, c); // top
1215        let (cx, cy) = get(r - 1, c + 1); // top-right (or top-left if rightmost)
1216        // If top-right is out of bounds, use top-left instead (per spec)
1217        let (cx, cy) = if c + 1 >= self.mb_w as isize {
1218            get(r - 1, c - 1)
1219        } else {
1220            (cx, cy)
1221        };
1222
1223        (median3(ax, bx, cx), median3(ay, by, cy))
1224    }
1225
1226    pub fn store(&mut self, mb_row: usize, mb_col: usize, mv: (i32, i32), skipped: bool) {
1227        let idx = mb_row * self.mb_w + mb_col;
1228        if idx < self.mvs.len() {
1229            self.mvs[idx] = mv;
1230            self.skipped[idx] = skipped;
1231        }
1232    }
1233}
1234
1235#[inline]
1236fn median3(a: i32, b: i32, c: i32) -> i32 {
1237    // Returns the median of three values
1238    if (a <= b && b <= c) || (c <= b && b <= a) {
1239        b
1240    } else if (b <= a && a <= c) || (c <= a && a <= b) {
1241        a
1242    } else {
1243        c
1244    }
1245}
1246
1247#[inline(always)]
1248fn mid_pred(a: i32, b: i32, c: i32) -> i32 {
1249    // upstream mid_pred() helper.
1250    median3(a, b, c)
1251}
1252
1253// ─── VC-1 signed overlap transform (Simple/Main) ───────────────────────────
1254// FFmpeg applies vc1_h_s_overlap()/vc1_v_s_overlap() to inverse-transform
1255// blocks before clipping them to u8.  Keeping the blocks signed is essential:
1256// clipping first and overlapping the final pixels is not equivalent.
1257
1258#[inline]
1259fn vc1_h_s_overlap(left: &mut [i32; 64], right: &mut [i32; 64]) {
1260    let mut rnd1 = 4i32;
1261    let mut rnd2 = 3i32;
1262    for row in 0..8usize {
1263        let li = row * 8;
1264        let ri = row * 8;
1265        let a = left[li + 6];
1266        let b = left[li + 7];
1267        let c = right[ri];
1268        let d = right[ri + 1];
1269        let d1 = a - d;
1270        let d2 = a - d + b - c;
1271
1272        left[li + 6] = ((a * 8) - d1 + rnd1) >> 3;
1273        left[li + 7] = ((b * 8) - d2 + rnd2) >> 3;
1274        right[ri] = ((c * 8) + d2 + rnd1) >> 3;
1275        right[ri + 1] = ((d * 8) + d1 + rnd2) >> 3;
1276
1277        // Progressive Simple/Main uses flags=1 in FFmpeg: alternate rounding
1278        // on every row.
1279        rnd2 = 7 - rnd2;
1280        rnd1 = 7 - rnd1;
1281    }
1282}
1283
1284#[inline]
1285fn vc1_v_s_overlap(top: &mut [i32; 64], bottom: &mut [i32; 64]) {
1286    let mut rnd1 = 4i32;
1287    let mut rnd2 = 3i32;
1288    for col in 0..8usize {
1289        let a = top[48 + col];
1290        let b = top[56 + col];
1291        let c = bottom[col];
1292        let d = bottom[8 + col];
1293        let d1 = a - d;
1294        let d2 = a - d + b - c;
1295
1296        top[48 + col] = ((a * 8) - d1 + rnd1) >> 3;
1297        top[56 + col] = ((b * 8) - d2 + rnd2) >> 3;
1298        bottom[col] = ((c * 8) + d2 + rnd1) >> 3;
1299        bottom[8 + col] = ((d * 8) + d1 + rnd2) >> 3;
1300
1301        rnd2 = 7 - rnd2;
1302        rnd1 = 7 - rnd1;
1303    }
1304}
1305
1306#[inline]
1307fn vc1_two_blocks_mut(
1308    blocks: &mut [[i32; 64]],
1309    a: usize,
1310    b: usize,
1311) -> (&mut [i32; 64], &mut [i32; 64]) {
1312    debug_assert_ne!(a, b);
1313    if a < b {
1314        let (left, right) = blocks.split_at_mut(b);
1315        (&mut left[a], &mut right[0])
1316    } else {
1317        let (left, right) = blocks.split_at_mut(a);
1318        (&mut right[0], &mut left[b])
1319    }
1320}
1321
1322#[inline]
1323fn vc1_recon_index(mb_width: usize, mb_row: usize, mb_col: usize, block: usize) -> usize {
1324    (mb_row * mb_width + mb_col) * 6 + block
1325}
1326
1327/// Apply the progressive Simple/Main overlap transform to buffered signed
1328/// blocks.  Horizontal overlap always precedes vertical overlap, matching
1329/// ff_vc1_i_overlap_filter()/ff_vc1_p_overlap_filter().  `intra` is used for
1330/// P pictures where an edge is overlapped only when both adjacent blocks are
1331/// intra-coded.
1332fn vc1_apply_signed_overlap(
1333    blocks: &mut [[i32; 64]],
1334    intra: &[[bool; 6]],
1335    mb_width: usize,
1336    mb_height: usize,
1337    scale_current_after: bool,
1338) {
1339    #[inline]
1340    fn h_if(blocks: &mut [[i32; 64]], intra: &[[bool; 6]], left: usize, right: usize) {
1341        let lmb = left / 6;
1342        let lblk = left % 6;
1343        let rmb = right / 6;
1344        let rblk = right % 6;
1345        if intra[lmb][lblk] && intra[rmb][rblk] {
1346            let (l, r) = vc1_two_blocks_mut(blocks, left, right);
1347            vc1_h_s_overlap(l, r);
1348        }
1349    }
1350
1351    #[inline]
1352    fn v_if(blocks: &mut [[i32; 64]], intra: &[[bool; 6]], top: usize, bottom: usize) {
1353        let tmb = top / 6;
1354        let tblk = top % 6;
1355        let bmb = bottom / 6;
1356        let bblk = bottom % 6;
1357        if intra[tmb][tblk] && intra[bmb][bblk] {
1358            let (t, b) = vc1_two_blocks_mut(blocks, top, bottom);
1359            vc1_v_s_overlap(t, b);
1360        }
1361    }
1362
1363    #[inline]
1364    fn finish_v_column(
1365        blocks: &mut [[i32; 64]],
1366        intra: &[[bool; 6]],
1367        mb_width: usize,
1368        mb_row: usize,
1369        target_col: usize,
1370    ) {
1371        let bottom = |blk: usize| vc1_recon_index(mb_width, mb_row, target_col, blk);
1372        if mb_row > 0 {
1373            let top = |blk: usize| vc1_recon_index(mb_width, mb_row - 1, target_col, blk);
1374            v_if(blocks, intra, top(2), bottom(0));
1375            v_if(blocks, intra, top(3), bottom(1));
1376        }
1377        v_if(blocks, intra, bottom(0), bottom(2));
1378        v_if(blocks, intra, bottom(1), bottom(3));
1379        if mb_row > 0 {
1380            let top = |blk: usize| vc1_recon_index(mb_width, mb_row - 1, target_col, blk);
1381            v_if(blocks, intra, top(4), bottom(4));
1382            v_if(blocks, intra, top(5), bottom(5));
1383        }
1384    }
1385
1386    // This deliberately mirrors ff_vc1_i_overlap_filter() /
1387    // ff_vc1_p_overlap_filter() in raster order instead of applying all H
1388    // edges and then all V edges globally.  The order is observable because
1389    // the integer overlap transform rounds, and for Simple/Main I pictures
1390    // FFmpeg applies RANGEREDFRM's x2 to the current MB after that MB's
1391    // overlap-filter call.
1392    for mb_row in 0..mb_height {
1393        for mb_col in 0..mb_width {
1394            let cur = |blk: usize| vc1_recon_index(mb_width, mb_row, mb_col, blk);
1395
1396            // ff_vc1_*_overlap_filter(): H overlap on the left and internal
1397            // vertical borders of the current macroblock, in block-number
1398            // order 0..5 (block_map is already normalized by our layout).
1399            if mb_col > 0 {
1400                let left = |blk: usize| vc1_recon_index(mb_width, mb_row, mb_col - 1, blk);
1401                h_if(blocks, intra, left(1), cur(0));
1402                h_if(blocks, intra, cur(0), cur(1));
1403                h_if(blocks, intra, left(3), cur(2));
1404                h_if(blocks, intra, cur(2), cur(3));
1405                h_if(blocks, intra, left(4), cur(4));
1406                h_if(blocks, intra, left(5), cur(5));
1407            } else {
1408                h_if(blocks, intra, cur(0), cur(1));
1409                h_if(blocks, intra, cur(2), cur(3));
1410            }
1411
1412            // V overlap trails H by one MB column.  At each decode position
1413            // FFmpeg finalizes the column on the left, and also the current
1414            // column when this is the last MB in the row.
1415            if mb_col > 0 {
1416                finish_v_column(blocks, intra, mb_width, mb_row, mb_col - 1);
1417            }
1418            if mb_col + 1 == mb_width {
1419                finish_v_column(blocks, intra, mb_width, mb_row, mb_col);
1420            }
1421
1422            // vc1_decode_i_blocks() performs this after
1423            // ff_vc1_i_overlap_filter() for the current macroblock.  P
1424            // pictures already apply x2 immediately after each intra IDCT,
1425            // so callers pass false there.
1426            if scale_current_after {
1427                let base = vc1_recon_index(mb_width, mb_row, mb_col, 0);
1428                for blk in 0..6usize {
1429                    for v in &mut blocks[base + blk] {
1430                        *v *= 2;
1431                    }
1432                }
1433            }
1434        }
1435    }
1436}
1437
1438/// Advanced Profile I-picture overlap for CONDOVER=SELECT.
1439///
1440/// This is the progressive subset of FFmpeg's `ff_vc1_i_overlap_filter()`.
1441/// A horizontal edge is enabled by the current MB's OVERFLAGS bit and, for
1442/// inter-MB edges, the neighbouring MB's bit as well. Vertical edges follow
1443/// the corresponding current/top rule. Internal luma edges only require the
1444/// current MB bit. The traversal intentionally matches the H-then-delayed-V
1445/// order used by the reference decoder because integer overlap rounding makes
1446/// that order observable.
1447fn vc1_apply_advanced_selective_i_overlap(
1448    blocks: &mut [[i32; 64]],
1449    mb_width: usize,
1450    mb_height: usize,
1451    overflags: &[u8],
1452) {
1453    #[inline]
1454    fn h_edge(
1455        blocks: &mut [[i32; 64]],
1456        mb_width: usize,
1457        mb_row: usize,
1458        mb_col: usize,
1459        left_blk: usize,
1460        right_blk: usize,
1461    ) {
1462        let left = vc1_recon_index(mb_width, mb_row, mb_col, left_blk);
1463        let right = vc1_recon_index(mb_width, mb_row, mb_col, right_blk);
1464        let (l, r) = vc1_two_blocks_mut(blocks, left, right);
1465        vc1_h_s_overlap(l, r);
1466    }
1467
1468    #[inline]
1469    fn h_cross(
1470        blocks: &mut [[i32; 64]],
1471        mb_width: usize,
1472        mb_row: usize,
1473        mb_col: usize,
1474        left_blk: usize,
1475        right_blk: usize,
1476    ) {
1477        let left = vc1_recon_index(mb_width, mb_row, mb_col - 1, left_blk);
1478        let right = vc1_recon_index(mb_width, mb_row, mb_col, right_blk);
1479        let (l, r) = vc1_two_blocks_mut(blocks, left, right);
1480        vc1_h_s_overlap(l, r);
1481    }
1482
1483    #[inline]
1484    fn v_edge(
1485        blocks: &mut [[i32; 64]],
1486        mb_width: usize,
1487        mb_row: usize,
1488        mb_col: usize,
1489        top_blk: usize,
1490        bottom_blk: usize,
1491    ) {
1492        let top = vc1_recon_index(mb_width, mb_row, mb_col, top_blk);
1493        let bottom = vc1_recon_index(mb_width, mb_row, mb_col, bottom_blk);
1494        let (t, b) = vc1_two_blocks_mut(blocks, top, bottom);
1495        vc1_v_s_overlap(t, b);
1496    }
1497
1498    #[inline]
1499    fn v_cross(
1500        blocks: &mut [[i32; 64]],
1501        mb_width: usize,
1502        mb_row: usize,
1503        mb_col: usize,
1504        top_blk: usize,
1505        bottom_blk: usize,
1506    ) {
1507        let top = vc1_recon_index(mb_width, mb_row - 1, mb_col, top_blk);
1508        let bottom = vc1_recon_index(mb_width, mb_row, mb_col, bottom_blk);
1509        let (t, b) = vc1_two_blocks_mut(blocks, top, bottom);
1510        vc1_v_s_overlap(t, b);
1511    }
1512
1513    #[inline]
1514    fn finish_v_column(
1515        blocks: &mut [[i32; 64]],
1516        mb_width: usize,
1517        mb_row: usize,
1518        col: usize,
1519        overflags: &[u8],
1520    ) {
1521        let cur = mb_row * mb_width + col;
1522        let cur_on = overflags.get(cur).copied().unwrap_or(0) != 0;
1523        if !cur_on {
1524            return;
1525        }
1526
1527        let top_on = mb_row > 0 && overflags.get(cur - mb_width).copied().unwrap_or(0) != 0;
1528        // Preserve FFmpeg's block-number order: cross-row luma blocks 0/1,
1529        // internal luma blocks 2/3, then chroma 4/5.
1530        if top_on {
1531            v_cross(blocks, mb_width, mb_row, col, 2, 0);
1532            v_cross(blocks, mb_width, mb_row, col, 3, 1);
1533        }
1534        v_edge(blocks, mb_width, mb_row, col, 0, 2);
1535        v_edge(blocks, mb_width, mb_row, col, 1, 3);
1536        if top_on {
1537            v_cross(blocks, mb_width, mb_row, col, 4, 4);
1538            v_cross(blocks, mb_width, mb_row, col, 5, 5);
1539        }
1540    }
1541
1542    for mb_row in 0..mb_height {
1543        for mb_col in 0..mb_width {
1544            let cur = mb_row * mb_width + mb_col;
1545            let cur_on = overflags.get(cur).copied().unwrap_or(0) != 0;
1546
1547            if cur_on {
1548                let left_on = mb_col > 0 && overflags.get(cur - 1).copied().unwrap_or(0) != 0;
1549                // Preserve i=0..5 ordering from ff_vc1_i_overlap_filter().
1550                if left_on {
1551                    h_cross(blocks, mb_width, mb_row, mb_col, 1, 0);
1552                }
1553                h_edge(blocks, mb_width, mb_row, mb_col, 0, 1);
1554                if left_on {
1555                    h_cross(blocks, mb_width, mb_row, mb_col, 3, 2);
1556                }
1557                h_edge(blocks, mb_width, mb_row, mb_col, 2, 3);
1558                if left_on {
1559                    h_cross(blocks, mb_width, mb_row, mb_col, 4, 4);
1560                    h_cross(blocks, mb_width, mb_row, mb_col, 5, 5);
1561                }
1562            }
1563
1564            // Vertical overlap trails by one MB column, exactly like the
1565            // reference implementation.
1566            if mb_col > 0 {
1567                finish_v_column(blocks, mb_width, mb_row, mb_col - 1, overflags);
1568            }
1569            if mb_col + 1 == mb_width {
1570                finish_v_column(blocks, mb_width, mb_row, mb_col, overflags);
1571            }
1572        }
1573    }
1574}
1575
1576#[inline(always)]
1577fn vc1_range_reduce_sample(v: u8) -> u8 {
1578    (((v as i32 - 128) >> 1) + 128) as u8
1579}
1580
1581fn vc1_range_reduce_reference(frame: &mut YuvFrame) {
1582    for v in &mut frame.y {
1583        *v = vc1_range_reduce_sample(*v);
1584    }
1585    for v in &mut frame.cb {
1586        *v = vc1_range_reduce_sample(*v);
1587    }
1588    for v in &mut frame.cr {
1589        *v = vc1_range_reduce_sample(*v);
1590    }
1591}
1592
1593/// FFmpeg vc1_mc.c stores `luma_mv` for chroma loop-filter decisions after
1594/// converting the qpel luma vector to the chroma-vector domain, but before
1595/// FASTUVMC rounding. Keep exactly that representation in vc1_chroma_mvs.
1596#[inline]
1597fn vc1_loop_chroma_mv(mv: (i32, i32)) -> (i32, i32) {
1598    (
1599        (mv.0 + ((mv.0 & 3) == 3) as i32) >> 1,
1600        (mv.1 + ((mv.1 & 3) == 3) as i32) >> 1,
1601    )
1602}
1603
1604// ─── Motion compensation ─────────────────────────────────────────────────────
1605// Half-pixel bilinear interpolation per SMPTE 421M §7.3.
1606
1607fn mc_luma(
1608    dst: &mut [u8],
1609    dst_stride: usize,
1610    src: &[u8],
1611    src_stride: usize,
1612    src_w: usize,
1613    src_h: usize,
1614    x: i32,
1615    y: i32,
1616    w: usize,
1617    h: usize,
1618) {
1619    // x and y in half-pixel units
1620    let xh = x & 1 != 0;
1621    let yh = y & 1 != 0;
1622    let x0 = (x >> 1) as isize;
1623    let y0 = (y >> 1) as isize;
1624
1625    for dy in 0..h {
1626        for dx in 0..w {
1627            let sx = (x0 + dx as isize).clamp(0, src_w as isize - 1) as usize;
1628            let sy = (y0 + dy as isize).clamp(0, src_h as isize - 1) as usize;
1629            let sx1 = (sx + 1).min(src_w - 1);
1630            let sy1 = (sy + 1).min(src_h - 1);
1631
1632            let p00 = src[sy * src_stride + sx] as i32;
1633            let p10 = src[sy * src_stride + sx1] as i32;
1634            let p01 = src[sy1 * src_stride + sx] as i32;
1635            let p11 = src[sy1 * src_stride + sx1] as i32;
1636
1637            let val = match (xh, yh) {
1638                (false, false) => p00,
1639                (true, false) => (p00 + p10 + 1) >> 1,
1640                (false, true) => (p00 + p01 + 1) >> 1,
1641                (true, true) => (p00 + p10 + p01 + p11 + 2) >> 2,
1642            };
1643            dst[dy * dst_stride + dx] = val.clamp(0, 255) as u8;
1644        }
1645    }
1646}
1647
1648// ─── DQUANT: macroblock-level differential quantizer ────────────────────────
1649// SMPTE 421M §8.1.4.10 / §8.3.7.
1650//
1651// This follows FFmpeg's GET_MQUANT() semantics.  A negative mquant is an
1652// internal marker meaning that HALFQP must not be applied to residual scaling;
1653// the absolute value is still the macroblock quantizer.
1654fn read_mquant(
1655    br: &mut BitReader<'_>,
1656    dquant: &crate::vc1::DQuantInfo,
1657    pquant: i32,
1658    mb_x: u32,
1659    mb_y: u32,
1660    mb_width: u32,
1661    mb_height: u32,
1662) -> Result<i32> {
1663    if !dquant.enabled {
1664        return Ok(pquant);
1665    }
1666
1667    let mut mquant = pquant;
1668    let mut edges = 0u8;
1669    match dquant.profile {
1670        3 => {
1671            // DQPROFILE_ALL_MBS
1672            if dquant.bi_level {
1673                let bilevel = br.read_bit().ok_or_else(|| {
1674                    DecoderError::InvalidData("truncated WMV3 DQBILEVEL macroblock bit".into())
1675                })?;
1676                mquant = if bilevel {
1677                    -(dquant.alt_pquant as i32)
1678                } else {
1679                    pquant
1680                };
1681            } else {
1682                let mqdiff = br
1683                    .read_bits(3)
1684                    .ok_or_else(|| DecoderError::InvalidData("truncated WMV3 MQDIFF".into()))?
1685                    as i32;
1686                mquant = if mqdiff != 7 {
1687                    -pquant - mqdiff
1688                } else {
1689                    -(br.read_bits(5)
1690                        .ok_or_else(|| DecoderError::InvalidData("truncated WMV3 MQUANT".into()))?
1691                        as i32)
1692                };
1693            }
1694        }
1695        0 => edges = 1u8 << dquant.edge.min(3),
1696        1 => edges = ((3u16 << dquant.edge.min(3)) % 15) as u8,
1697        2 => edges = 15,
1698        _ => {}
1699    }
1700
1701    if (edges & 1) != 0 && mb_x == 0 {
1702        mquant = -(dquant.alt_pquant as i32);
1703    }
1704    if (edges & 2) != 0 && mb_y == 0 {
1705        mquant = -(dquant.alt_pquant as i32);
1706    }
1707    if (edges & 4) != 0 && mb_x + 1 == mb_width {
1708        mquant = -(dquant.alt_pquant as i32);
1709    }
1710    if (edges & 8) != 0 && mb_y + 1 == mb_height {
1711        mquant = -(dquant.alt_pquant as i32);
1712    }
1713
1714    if mquant == 0 || !(-31..=31).contains(&mquant) {
1715        mquant = 1;
1716    }
1717    Ok(mquant)
1718}
1719
1720// Range reduction for inter motion compensation is applied to the current
1721// picture's sampled reference (see vc1_range_reduce_reference above).  Intra
1722// range expansion stays in the signed transform domain and is performed by
1723// the WMV3 reconstruction paths below.
1724
1725// ─── Write helpers ───────────────────────────────────────────────────────────
1726
1727/// Block (mb_row, mb_col, blk_idx) → (plane ref, x, y, stride, plane_h)
1728fn block_coords(
1729    mb_row: u32,
1730    mb_col: u32,
1731    blk: usize,
1732    width: u32,
1733    height: u32,
1734) -> (bool, usize, usize, usize, usize) {
1735    // Returns (is_luma, px, py, stride, plane_height)
1736    let (is_luma, bx, by) = match blk {
1737        0 => (true, (mb_col * 16) as usize, (mb_row * 16) as usize),
1738        1 => (true, (mb_col * 16 + 8) as usize, (mb_row * 16) as usize),
1739        2 => (true, (mb_col * 16) as usize, (mb_row * 16 + 8) as usize),
1740        3 => (true, (mb_col * 16 + 8) as usize, (mb_row * 16 + 8) as usize),
1741        _ => (false, (mb_col * 8) as usize, (mb_row * 8) as usize),
1742    };
1743    let stride = if is_luma {
1744        width as usize
1745    } else {
1746        (width / 2) as usize
1747    };
1748    let ph = if is_luma {
1749        height as usize
1750    } else {
1751        (height / 2) as usize
1752    };
1753    (is_luma, bx, by, stride, ph)
1754}
1755
1756fn write_intra_block(
1757    frame: &mut YuvFrame,
1758    mb_row: u32,
1759    mb_col: u32,
1760    blk: usize,
1761    coeff: &[i32; 64],
1762) {
1763    let (is_luma, bx, by, stride, ph) =
1764        block_coords(mb_row, mb_col, blk, frame.width, frame.height);
1765    let plane: &mut Vec<u8> = if is_luma {
1766        &mut frame.y
1767    } else if blk == 4 {
1768        &mut frame.cb
1769    } else {
1770        &mut frame.cr
1771    };
1772    for r in 0..8 {
1773        if by + r >= ph {
1774            break;
1775        }
1776        for c in 0..8 {
1777            if bx + c >= stride {
1778                break;
1779            }
1780            let idx = (by + r) * stride + (bx + c);
1781            plane[idx] = (128 + coeff[r * 8 + c]).clamp(0, 255) as u8;
1782        }
1783    }
1784}
1785
1786/// VC-1 Simple/Main I pictures without the high-quant overlap path use
1787/// put_pixels_clamped(): the inverse-transform result is already in the
1788/// unsigned pixel domain and must not receive the +128 signed-block bias.
1789fn write_intra_block_unsigned(
1790    frame: &mut YuvFrame,
1791    mb_row: u32,
1792    mb_col: u32,
1793    blk: usize,
1794    coeff: &[i32; 64],
1795) {
1796    let (is_luma, bx, by, stride, ph) =
1797        block_coords(mb_row, mb_col, blk, frame.width, frame.height);
1798    let plane: &mut Vec<u8> = if is_luma {
1799        &mut frame.y
1800    } else if blk == 4 {
1801        &mut frame.cb
1802    } else {
1803        &mut frame.cr
1804    };
1805    for r in 0..8 {
1806        if by + r >= ph {
1807            break;
1808        }
1809        for c in 0..8 {
1810            if bx + c >= stride {
1811                break;
1812            }
1813            let idx = (by + r) * stride + (bx + c);
1814            plane[idx] = coeff[r * 8 + c].clamp(0, 255) as u8;
1815        }
1816    }
1817}
1818
1819#[inline]
1820fn write_block_to_frame(
1821    frame: &mut YuvFrame,
1822    mb_row: usize,
1823    mb_col: usize,
1824    blk: usize,
1825    coeff: &[i32; 64],
1826) {
1827    write_intra_block(frame, mb_row as u32, mb_col as u32, blk, coeff);
1828}
1829
1830// WMV2 path uses upstream's Simple IDCT (int16). Provide i16 write/add helpers.
1831fn write_intra_block_i16(
1832    frame: &mut YuvFrame,
1833    mb_row: u32,
1834    mb_col: u32,
1835    blk: usize,
1836    coeff: &[i16; 64],
1837) {
1838    let (is_luma, bx, by, stride, ph) =
1839        block_coords(mb_row, mb_col, blk, frame.width, frame.height);
1840    let plane: &mut Vec<u8> = if is_luma {
1841        &mut frame.y
1842    } else if blk == 4 {
1843        &mut frame.cb
1844    } else {
1845        &mut frame.cr
1846    };
1847    for r in 0..8usize {
1848        if by + r >= ph {
1849            break;
1850        }
1851        for c in 0..8usize {
1852            if bx + c >= stride {
1853                break;
1854            }
1855            let idx = (by + r) * stride + (bx + c);
1856            let v = coeff[r * 8 + c] as i32;
1857            plane[idx] = (v + 128).clamp(0, 255) as u8;
1858        }
1859    }
1860}
1861
1862fn add_residual_block_i16(
1863    frame: &mut YuvFrame,
1864    mb_row: u32,
1865    mb_col: u32,
1866    blk: usize,
1867    coeff: &[i16; 64],
1868) {
1869    let (is_luma, bx, by, stride, ph) =
1870        block_coords(mb_row, mb_col, blk, frame.width, frame.height);
1871    let plane: &mut Vec<u8> = if is_luma {
1872        &mut frame.y
1873    } else if blk == 4 {
1874        &mut frame.cb
1875    } else {
1876        &mut frame.cr
1877    };
1878    for r in 0..8usize {
1879        if by + r >= ph {
1880            break;
1881        }
1882        for c in 0..8usize {
1883            if bx + c >= stride {
1884                break;
1885            }
1886            let idx = (by + r) * stride + (bx + c);
1887            let v = plane[idx] as i32 + coeff[r * 8 + c] as i32;
1888            plane[idx] = v.clamp(0, 255) as u8;
1889        }
1890    }
1891}
1892
1893/// Motion compensate one 16×16 macroblock from `reference` into `dst`.
1894/// Motion vectors are in half-pel units (like H.263/MSMPEG4/WMV2).
1895fn motion_compensate_mb(
1896    dst: &mut YuvFrame,
1897    reference: &YuvFrame,
1898    mb_row: usize,
1899    mb_col: usize,
1900    mvx: i32,
1901    mvy: i32,
1902) {
1903    let fw = dst.width as usize;
1904    let fh = dst.height as usize;
1905    if fw == 0 || fh == 0 {
1906        return;
1907    }
1908    let cw = fw / 2;
1909    let ch = fh / 2;
1910
1911    // ── Luma (16×16) ────────────────────────────────────────────────────────
1912    let dst_x = mb_col * 16;
1913    let dst_y = mb_row * 16;
1914    let src_x = dst_x as i32 * 2 + mvx; // half-pel coordinate
1915    let src_y = dst_y as i32 * 2 + mvy;
1916
1917    if reference.y.len() == fw * fh && dst.y.len() == fw * fh {
1918        let mut tmp = [0u8; 256];
1919        mc_luma(&mut tmp, 16, &reference.y, fw, fw, fh, src_x, src_y, 16, 16);
1920        for r in 0..16 {
1921            if dst_y + r >= fh {
1922                break;
1923            }
1924            if dst_x >= fw {
1925                break;
1926            }
1927            let d_off = (dst_y + r) * fw + dst_x;
1928            let s_off = r * 16;
1929            let max = (fw - dst_x).min(16);
1930            dst.y[d_off..d_off + max].copy_from_slice(&tmp[s_off..s_off + max]);
1931        }
1932    }
1933
1934    // ── Chroma (8×8) ───────────────────────────────────────────────────────
1935    // upstream (ff_mspel_motion): motion vectors are in half-luma-pel units.
1936    // For 4:2:0 chroma, 1 chroma pixel = 2 luma pixels, so the same MV value
1937    // corresponds to quarter-chroma-pel units.
1938    // upstream collapses the 2-bit chroma fraction to a boolean (any non-zero
1939    // fractional part triggers half-chroma interpolation):
1940    //   dxy |= (motion_x & 3) != 0
1941    //   mx  = motion_x >> 2
1942    // We reproduce that mapping here by converting to half-chroma-pel coords.
1943    if reference.cb.len() == cw * ch && dst.cb.len() == cw * ch {
1944        let dst_xc = mb_col * 8;
1945        let dst_yc = mb_row * 8;
1946
1947        let mx = mvx >> 2;
1948        let my = mvy >> 2;
1949        let xh = (mvx & 3) != 0;
1950        let yh = (mvy & 3) != 0;
1951
1952        // Half-chroma-pel coordinate for mc_luma().
1953        let src_xc = (dst_xc as i32 + mx) * 2 + if xh { 1 } else { 0 };
1954        let src_yc = (dst_yc as i32 + my) * 2 + if yh { 1 } else { 0 };
1955
1956        let mut tmp_cb = [0u8; 64];
1957        let mut tmp_cr = [0u8; 64];
1958        mc_luma(
1959            &mut tmp_cb,
1960            8,
1961            &reference.cb,
1962            cw,
1963            cw,
1964            ch,
1965            src_xc,
1966            src_yc,
1967            8,
1968            8,
1969        );
1970        mc_luma(
1971            &mut tmp_cr,
1972            8,
1973            &reference.cr,
1974            cw,
1975            cw,
1976            ch,
1977            src_xc,
1978            src_yc,
1979            8,
1980            8,
1981        );
1982
1983        for r in 0..8 {
1984            if dst_yc + r >= ch {
1985                break;
1986            }
1987            if dst_xc >= cw {
1988                break;
1989            }
1990            let d_off = (dst_yc + r) * cw + dst_xc;
1991            let s_off = r * 8;
1992            let max = (cw - dst_xc).min(8);
1993            dst.cb[d_off..d_off + max].copy_from_slice(&tmp_cb[s_off..s_off + max]);
1994            dst.cr[d_off..d_off + max].copy_from_slice(&tmp_cr[s_off..s_off + max]);
1995        }
1996    }
1997}
1998
1999// ── WMV2 MSPEL motion compensation (direct port of upstream ff_mspel_motion + wmv2_mspel_init) ──
2000
2001#[inline]
2002fn clip_u8(v: i32) -> u8 {
2003    if v < 0 {
2004        0
2005    } else if v > 255 {
2006        255
2007    } else {
2008        v as u8
2009    }
2010}
2011
2012#[inline]
2013fn rnd_avg_u8(a: u8, b: u8) -> u8 {
2014    ((a as u16 + b as u16 + 1) >> 1) as u8
2015}
2016
2017#[inline]
2018fn no_rnd_avg_u8(a: u8, b: u8) -> u8 {
2019    ((a as u16 + b as u16) >> 1) as u8
2020}
2021
2022fn wmv2_mspel8_h_lowpass(
2023    dst: &mut [u8],
2024    dst_off: usize,
2025    dst_stride: usize,
2026    src: &[u8],
2027    src_off: usize,
2028    src_stride: usize,
2029    h: usize,
2030) {
2031    for i in 0..h {
2032        let so = src_off + i * src_stride;
2033        let doff = dst_off + i * dst_stride;
2034        // dst[0..8]
2035        dst[doff] = clip_u8(
2036            (9 * (src[so] as i32 + src[so + 1] as i32) - (src[so - 1] as i32 + src[so + 2] as i32)
2037                + 8)
2038                >> 4,
2039        );
2040        dst[doff + 1] = clip_u8(
2041            (9 * (src[so + 1] as i32 + src[so + 2] as i32) - (src[so] as i32 + src[so + 3] as i32)
2042                + 8)
2043                >> 4,
2044        );
2045        dst[doff + 2] = clip_u8(
2046            (9 * (src[so + 2] as i32 + src[so + 3] as i32)
2047                - (src[so + 1] as i32 + src[so + 4] as i32)
2048                + 8)
2049                >> 4,
2050        );
2051        dst[doff + 3] = clip_u8(
2052            (9 * (src[so + 3] as i32 + src[so + 4] as i32)
2053                - (src[so + 2] as i32 + src[so + 5] as i32)
2054                + 8)
2055                >> 4,
2056        );
2057        dst[doff + 4] = clip_u8(
2058            (9 * (src[so + 4] as i32 + src[so + 5] as i32)
2059                - (src[so + 3] as i32 + src[so + 6] as i32)
2060                + 8)
2061                >> 4,
2062        );
2063        dst[doff + 5] = clip_u8(
2064            (9 * (src[so + 5] as i32 + src[so + 6] as i32)
2065                - (src[so + 4] as i32 + src[so + 7] as i32)
2066                + 8)
2067                >> 4,
2068        );
2069        dst[doff + 6] = clip_u8(
2070            (9 * (src[so + 6] as i32 + src[so + 7] as i32)
2071                - (src[so + 5] as i32 + src[so + 8] as i32)
2072                + 8)
2073                >> 4,
2074        );
2075        dst[doff + 7] = clip_u8(
2076            (9 * (src[so + 7] as i32 + src[so + 8] as i32)
2077                - (src[so + 6] as i32 + src[so + 9] as i32)
2078                + 8)
2079                >> 4,
2080        );
2081    }
2082}
2083
2084fn wmv2_mspel8_v_lowpass(
2085    dst: &mut [u8],
2086    dst_off: usize,
2087    dst_stride: usize,
2088    src: &[u8],
2089    src_off: usize,
2090    src_stride: usize,
2091    w: usize,
2092) {
2093    for i in 0..w {
2094        let so = src_off + i;
2095        let s_1 = src[so - src_stride] as i32;
2096        let s0 = src[so] as i32;
2097        let s1 = src[so + src_stride] as i32;
2098        let s2 = src[so + 2 * src_stride] as i32;
2099        let s3 = src[so + 3 * src_stride] as i32;
2100        let s4 = src[so + 4 * src_stride] as i32;
2101        let s5 = src[so + 5 * src_stride] as i32;
2102        let s6 = src[so + 6 * src_stride] as i32;
2103        let s7 = src[so + 7 * src_stride] as i32;
2104        let s8 = src[so + 8 * src_stride] as i32;
2105        let s9 = src[so + 9 * src_stride] as i32;
2106
2107        let do0 = dst_off + i + 0 * dst_stride;
2108        let do1 = dst_off + i + dst_stride;
2109        let do2 = dst_off + i + 2 * dst_stride;
2110        let do3 = dst_off + i + 3 * dst_stride;
2111        let do4 = dst_off + i + 4 * dst_stride;
2112        let do5 = dst_off + i + 5 * dst_stride;
2113        let do6 = dst_off + i + 6 * dst_stride;
2114        let do7 = dst_off + i + 7 * dst_stride;
2115
2116        dst[do0] = clip_u8((9 * (s0 + s1) - (s_1 + s2) + 8) >> 4);
2117        dst[do1] = clip_u8((9 * (s1 + s2) - (s0 + s3) + 8) >> 4);
2118        dst[do2] = clip_u8((9 * (s2 + s3) - (s1 + s4) + 8) >> 4);
2119        dst[do3] = clip_u8((9 * (s3 + s4) - (s2 + s5) + 8) >> 4);
2120        dst[do4] = clip_u8((9 * (s4 + s5) - (s3 + s6) + 8) >> 4);
2121        dst[do5] = clip_u8((9 * (s5 + s6) - (s4 + s7) + 8) >> 4);
2122        dst[do6] = clip_u8((9 * (s6 + s7) - (s5 + s8) + 8) >> 4);
2123        dst[do7] = clip_u8((9 * (s7 + s8) - (s6 + s9) + 8) >> 4);
2124    }
2125}
2126
2127#[inline]
2128fn put_pixels8x8(dst: &mut [u8], dst_off: usize, src: &[u8], src_off: usize, stride: usize) {
2129    for y in 0..8 {
2130        let d = dst_off + y * stride;
2131        let s = src_off + y * stride;
2132        dst[d..d + 8].copy_from_slice(&src[s..s + 8]);
2133    }
2134}
2135
2136#[inline]
2137fn put_pixels8_l2_8_no_rnd(
2138    dst: &mut [u8],
2139    dst_off: usize,
2140    src1: &[u8],
2141    src1_off: usize,
2142    src2: &[u8],
2143    src2_off: usize,
2144    dst_stride: usize,
2145    src1_stride: usize,
2146    src2_stride: usize,
2147    h: usize,
2148) {
2149    for y in 0..h {
2150        let d = dst_off + y * dst_stride;
2151        let s1 = src1_off + y * src1_stride;
2152        let s2 = src2_off + y * src2_stride;
2153        for x in 0..8 {
2154            dst[d + x] = no_rnd_avg_u8(src1[s1 + x], src2[s2 + x]);
2155        }
2156    }
2157}
2158
2159fn put_mspel8_mc10(dst: &mut [u8], dst_off: usize, src: &[u8], src_off: usize, stride: usize) {
2160    let mut half = [0u8; 64];
2161    wmv2_mspel8_h_lowpass(&mut half, 0, 8, src, src_off, stride, 8);
2162    put_pixels8_l2_8_no_rnd(dst, dst_off, src, src_off, &half, 0, stride, stride, 8, 8);
2163}
2164
2165fn put_mspel8_mc20(dst: &mut [u8], dst_off: usize, src: &[u8], src_off: usize, stride: usize) {
2166    wmv2_mspel8_h_lowpass(dst, dst_off, stride, src, src_off, stride, 8);
2167}
2168
2169fn put_mspel8_mc30(dst: &mut [u8], dst_off: usize, src: &[u8], src_off: usize, stride: usize) {
2170    let mut half = [0u8; 64];
2171    wmv2_mspel8_h_lowpass(&mut half, 0, 8, src, src_off, stride, 8);
2172    put_pixels8_l2_8_no_rnd(
2173        dst,
2174        dst_off,
2175        src,
2176        src_off + 1,
2177        &half,
2178        0,
2179        stride,
2180        stride,
2181        8,
2182        8,
2183    );
2184}
2185
2186fn put_mspel8_mc02(dst: &mut [u8], dst_off: usize, src: &[u8], src_off: usize, stride: usize) {
2187    wmv2_mspel8_v_lowpass(dst, dst_off, stride, src, src_off, stride, 8);
2188}
2189
2190fn put_mspel8_mc12(dst: &mut [u8], dst_off: usize, src: &[u8], src_off: usize, stride: usize) {
2191    let mut half_h = [0u8; 88];
2192    let mut half_v = [0u8; 64];
2193    let mut half_hv = [0u8; 64];
2194    // h_lowpass(halfH, src - stride, 8, stride, 11)
2195    wmv2_mspel8_h_lowpass(&mut half_h, 0, 8, src, src_off - stride, stride, 11);
2196    // v_lowpass(halfV, src, 8, stride, 8)
2197    wmv2_mspel8_v_lowpass(&mut half_v, 0, 8, src, src_off, stride, 8);
2198    // v_lowpass(halfHV, halfH + 8, 8, 8, 8)
2199    wmv2_mspel8_v_lowpass(&mut half_hv, 0, 8, &half_h, 8, 8, 8);
2200    put_pixels8_l2_8_no_rnd(dst, dst_off, &half_v, 0, &half_hv, 0, stride, 8, 8, 8);
2201}
2202
2203fn put_mspel8_mc22(dst: &mut [u8], dst_off: usize, src: &[u8], src_off: usize, stride: usize) {
2204    let mut half_h = [0u8; 88];
2205    wmv2_mspel8_h_lowpass(&mut half_h, 0, 8, src, src_off - stride, stride, 11);
2206    wmv2_mspel8_v_lowpass(dst, dst_off, stride, &half_h, 8, 8, 8);
2207}
2208
2209fn put_mspel8_mc32(dst: &mut [u8], dst_off: usize, src: &[u8], src_off: usize, stride: usize) {
2210    let mut half_h = [0u8; 88];
2211    let mut half_v = [0u8; 64];
2212    let mut half_hv = [0u8; 64];
2213    wmv2_mspel8_h_lowpass(&mut half_h, 0, 8, src, src_off - stride, stride, 11);
2214    wmv2_mspel8_v_lowpass(&mut half_v, 0, 8, src, src_off + 1, stride, 8);
2215    wmv2_mspel8_v_lowpass(&mut half_hv, 0, 8, &half_h, 8, 8, 8);
2216    put_pixels8_l2_8_no_rnd(dst, dst_off, &half_v, 0, &half_hv, 0, stride, 8, 8, 8);
2217}
2218
2219#[inline]
2220fn wmv2_put_mspel_pixels(
2221    dxy: usize,
2222    dst: &mut [u8],
2223    dst_off: usize,
2224    src: &[u8],
2225    src_off: usize,
2226    stride: usize,
2227) {
2228    match dxy {
2229        0 => put_pixels8x8(dst, dst_off, src, src_off, stride),
2230        1 => put_mspel8_mc10(dst, dst_off, src, src_off, stride),
2231        2 => put_mspel8_mc20(dst, dst_off, src, src_off, stride),
2232        3 => put_mspel8_mc30(dst, dst_off, src, src_off, stride),
2233        4 => put_mspel8_mc02(dst, dst_off, src, src_off, stride),
2234        5 => put_mspel8_mc12(dst, dst_off, src, src_off, stride),
2235        6 => put_mspel8_mc22(dst, dst_off, src, src_off, stride),
2236        7 => put_mspel8_mc32(dst, dst_off, src, src_off, stride),
2237        _ => put_pixels8x8(dst, dst_off, src, src_off, stride),
2238    }
2239}
2240
2241fn emulated_edge_mc(
2242    buf: &mut [u8],
2243    buf_stride: usize,
2244    src: &[u8],
2245    src_stride: usize,
2246    block_w: usize,
2247    block_h: usize,
2248    src_x: i32,
2249    src_y: i32,
2250    h_edge: usize,
2251    v_edge: usize,
2252) {
2253    let max_x = (h_edge as i32 - 1).max(0);
2254    let max_y = (v_edge as i32 - 1).max(0);
2255    for y in 0..block_h {
2256        let sy = (src_y + y as i32).clamp(0, max_y) as usize;
2257        let drow = y * buf_stride;
2258        let srow = sy * src_stride;
2259        for x in 0..block_w {
2260            let sx = (src_x + x as i32).clamp(0, max_x) as usize;
2261            buf[drow + x] = src[srow + sx];
2262        }
2263    }
2264}
2265
2266#[inline]
2267fn chroma_put_pixels(
2268    dst: &mut [u8],
2269    dst_off: usize,
2270    src: &[u8],
2271    src_off: usize,
2272    stride: usize,
2273    h: usize,
2274) {
2275    for y in 0..h {
2276        let d = dst_off + y * stride;
2277        let s = src_off + y * stride;
2278        dst[d..d + 8].copy_from_slice(&src[s..s + 8]);
2279    }
2280}
2281
2282#[inline]
2283fn chroma_put_x2(
2284    dst: &mut [u8],
2285    dst_off: usize,
2286    src: &[u8],
2287    src_off: usize,
2288    stride: usize,
2289    h: usize,
2290) {
2291    for y in 0..h {
2292        let d = dst_off + y * stride;
2293        let s = src_off + y * stride;
2294        for x in 0..8 {
2295            dst[d + x] = rnd_avg_u8(src[s + x], src[s + x + 1]);
2296        }
2297    }
2298}
2299
2300#[inline]
2301fn chroma_put_y2(
2302    dst: &mut [u8],
2303    dst_off: usize,
2304    src: &[u8],
2305    src_off: usize,
2306    stride: usize,
2307    h: usize,
2308) {
2309    for y in 0..h {
2310        let d = dst_off + y * stride;
2311        let s = src_off + y * stride;
2312        let s2 = s + stride;
2313        for x in 0..8 {
2314            dst[d + x] = rnd_avg_u8(src[s + x], src[s2 + x]);
2315        }
2316    }
2317}
2318
2319#[inline]
2320fn chroma_put_xy2(
2321    dst: &mut [u8],
2322    dst_off: usize,
2323    src: &[u8],
2324    src_off: usize,
2325    stride: usize,
2326    h: usize,
2327) {
2328    for y in 0..h {
2329        let d = dst_off + y * stride;
2330        let s = src_off + y * stride;
2331        let s2 = s + stride;
2332        for x in 0..8 {
2333            let a = src[s + x] as u16;
2334            let b = src[s + x + 1] as u16;
2335            let c = src[s2 + x] as u16;
2336            let e = src[s2 + x + 1] as u16;
2337            dst[d + x] = ((a + b + c + e + 2) >> 2) as u8;
2338        }
2339    }
2340}
2341
2342/// Direct port of upstream `ff_mspel_motion` for WMV2 (MV in half-luma-pel units).
2343fn wmv2_mspel_motion_mb(
2344    dst: &mut YuvFrame,
2345    reference: &YuvFrame,
2346    mb_row: usize,
2347    mb_col: usize,
2348    motion_x: i32,
2349    motion_y: i32,
2350    hshift: u8,
2351) {
2352    let fw = dst.width as usize;
2353    let fh = dst.height as usize;
2354    if fw == 0 || fh == 0 {
2355        return;
2356    }
2357    let cw = fw / 2;
2358    let ch = fh / 2;
2359
2360    // ---- Luma ----
2361    let mut dxy = ((motion_y & 1) << 1) | (motion_x & 1);
2362    dxy = 2 * dxy + hshift as i32;
2363
2364    let mut src_x = mb_col as i32 * 16 + (motion_x >> 1);
2365    let mut src_y = mb_row as i32 * 16 + (motion_y >> 1);
2366
2367    // clip to [-16, width] / [-16, height]
2368    if src_x < -16 {
2369        src_x = -16;
2370    }
2371    if src_x > dst.width as i32 {
2372        src_x = dst.width as i32;
2373    }
2374    if src_y < -16 {
2375        src_y = -16;
2376    }
2377    if src_y > dst.height as i32 {
2378        src_y = dst.height as i32;
2379    }
2380
2381    if src_x <= -16 || src_x >= dst.width as i32 {
2382        dxy &= !3;
2383    }
2384    if src_y <= -16 || src_y >= dst.height as i32 {
2385        dxy &= !4;
2386    }
2387
2388    let linesize = fw;
2389    let mut src_plane: &[u8] = &reference.y;
2390    let src_off: usize;
2391
2392    // edge condition: same as upstream (using h_edge_pos=width, v_edge_pos=height)
2393    if src_x < 1
2394        || src_y < 1
2395        || src_x + 17 >= dst.width as i32
2396        || src_y + 16 + 1 >= dst.height as i32
2397    {
2398        let mut edge = vec![0u8; linesize * 19];
2399        emulated_edge_mc(
2400            &mut edge,
2401            linesize,
2402            &reference.y,
2403            linesize,
2404            19,
2405            19,
2406            src_x - 1,
2407            src_y - 1,
2408            fw,
2409            fh,
2410        );
2411        src_plane = edge.as_slice();
2412        src_off = 1 + linesize;
2413        // keep edge alive via scope capture
2414        // (we rebind below for actual reads)
2415        // NOTE: src_plane points into `edge` which must live for the rest of this function.
2416        // Rust ensures this because `edge` is in this scope.
2417
2418        // Use the edge buffer for the remainder of this luma section.
2419        let dst_x = mb_col * 16;
2420        let dst_y = mb_row * 16;
2421        let dxyu = (dxy as usize).min(7);
2422
2423        // 4x 8x8 blocks
2424        let dst00 = dst_y * linesize + dst_x;
2425        let src00 = src_off;
2426        wmv2_put_mspel_pixels(dxyu, &mut dst.y, dst00, src_plane, src00, linesize);
2427        wmv2_put_mspel_pixels(dxyu, &mut dst.y, dst00 + 8, src_plane, src00 + 8, linesize);
2428        wmv2_put_mspel_pixels(
2429            dxyu,
2430            &mut dst.y,
2431            dst00 + 8 * linesize,
2432            src_plane,
2433            src00 + 8 * linesize,
2434            linesize,
2435        );
2436        wmv2_put_mspel_pixels(
2437            dxyu,
2438            &mut dst.y,
2439            dst00 + 8 + 8 * linesize,
2440            src_plane,
2441            src00 + 8 + 8 * linesize,
2442            linesize,
2443        );
2444
2445        // ---- Chroma (still within edge scope) ----
2446        if dst.cb.is_empty() || reference.cb.is_empty() {
2447            return;
2448        }
2449
2450        let mut cdxy = 0usize;
2451        if (motion_x & 3) != 0 {
2452            cdxy |= 1;
2453        }
2454        if (motion_y & 3) != 0 {
2455            cdxy |= 2;
2456        }
2457        let mx = motion_x >> 2;
2458        let my = motion_y >> 2;
2459
2460        let mut csrc_x = mb_col as i32 * 8 + mx;
2461        let mut csrc_y = mb_row as i32 * 8 + my;
2462
2463        if csrc_x < -8 {
2464            csrc_x = -8;
2465        }
2466        if csrc_x > (dst.width as i32 >> 1) {
2467            csrc_x = dst.width as i32 >> 1;
2468        }
2469        if csrc_y < -8 {
2470            csrc_y = -8;
2471        }
2472        if csrc_y > (dst.height as i32 >> 1) {
2473            csrc_y = dst.height as i32 >> 1;
2474        }
2475
2476        if csrc_x == (dst.width as i32 >> 1) {
2477            cdxy &= !1;
2478        }
2479        if csrc_y == (dst.height as i32 >> 1) {
2480            cdxy &= !2;
2481        }
2482
2483        let uvlinesize = cw;
2484
2485        let mut edge_uv = vec![0u8; uvlinesize * 9];
2486        // cb
2487        emulated_edge_mc(
2488            &mut edge_uv,
2489            uvlinesize,
2490            &reference.cb,
2491            uvlinesize,
2492            9,
2493            9,
2494            csrc_x,
2495            csrc_y,
2496            cw,
2497            ch,
2498        );
2499        let dst_xc = mb_col * 8;
2500        let dst_yc = mb_row * 8;
2501        let dst_cb_off = dst_yc * uvlinesize + dst_xc;
2502        match cdxy {
2503            0 => chroma_put_pixels(&mut dst.cb, dst_cb_off, &edge_uv, 0, uvlinesize, 8),
2504            1 => chroma_put_x2(&mut dst.cb, dst_cb_off, &edge_uv, 0, uvlinesize, 8),
2505            2 => chroma_put_y2(&mut dst.cb, dst_cb_off, &edge_uv, 0, uvlinesize, 8),
2506            _ => chroma_put_xy2(&mut dst.cb, dst_cb_off, &edge_uv, 0, uvlinesize, 8),
2507        }
2508
2509        // cr
2510        emulated_edge_mc(
2511            &mut edge_uv,
2512            uvlinesize,
2513            &reference.cr,
2514            uvlinesize,
2515            9,
2516            9,
2517            csrc_x,
2518            csrc_y,
2519            cw,
2520            ch,
2521        );
2522        let dst_cr_off = dst_yc * uvlinesize + dst_xc;
2523        match cdxy {
2524            0 => chroma_put_pixels(&mut dst.cr, dst_cr_off, &edge_uv, 0, uvlinesize, 8),
2525            1 => chroma_put_x2(&mut dst.cr, dst_cr_off, &edge_uv, 0, uvlinesize, 8),
2526            2 => chroma_put_y2(&mut dst.cr, dst_cr_off, &edge_uv, 0, uvlinesize, 8),
2527            _ => chroma_put_xy2(&mut dst.cr, dst_cr_off, &edge_uv, 0, uvlinesize, 8),
2528        }
2529        return;
2530    }
2531
2532    // non-emu luma
2533    src_off = (src_y as usize) * linesize + (src_x as usize);
2534    let dst_x = mb_col * 16;
2535    let dst_y = mb_row * 16;
2536    let dxyu = (dxy as usize).min(7);
2537
2538    let dst00 = dst_y * linesize + dst_x;
2539    wmv2_put_mspel_pixels(dxyu, &mut dst.y, dst00, src_plane, src_off, linesize);
2540    wmv2_put_mspel_pixels(
2541        dxyu,
2542        &mut dst.y,
2543        dst00 + 8,
2544        src_plane,
2545        src_off + 8,
2546        linesize,
2547    );
2548    wmv2_put_mspel_pixels(
2549        dxyu,
2550        &mut dst.y,
2551        dst00 + 8 * linesize,
2552        src_plane,
2553        src_off + 8 * linesize,
2554        linesize,
2555    );
2556    wmv2_put_mspel_pixels(
2557        dxyu,
2558        &mut dst.y,
2559        dst00 + 8 + 8 * linesize,
2560        src_plane,
2561        src_off + 8 + 8 * linesize,
2562        linesize,
2563    );
2564
2565    // ---- Chroma ----
2566    if dst.cb.is_empty() || reference.cb.is_empty() {
2567        return;
2568    }
2569
2570    let mut cdxy = 0usize;
2571    if (motion_x & 3) != 0 {
2572        cdxy |= 1;
2573    }
2574    if (motion_y & 3) != 0 {
2575        cdxy |= 2;
2576    }
2577    let mx = motion_x >> 2;
2578    let my = motion_y >> 2;
2579
2580    let mut csrc_x = mb_col as i32 * 8 + mx;
2581    let mut csrc_y = mb_row as i32 * 8 + my;
2582
2583    if csrc_x < -8 {
2584        csrc_x = -8;
2585    }
2586    if csrc_x > (dst.width as i32 >> 1) {
2587        csrc_x = dst.width as i32 >> 1;
2588    }
2589    if csrc_y < -8 {
2590        csrc_y = -8;
2591    }
2592    if csrc_y > (dst.height as i32 >> 1) {
2593        csrc_y = dst.height as i32 >> 1;
2594    }
2595
2596    if csrc_x == (dst.width as i32 >> 1) {
2597        cdxy &= !1;
2598    }
2599    if csrc_y == (dst.height as i32 >> 1) {
2600        cdxy &= !2;
2601    }
2602
2603    let uvlinesize = cw;
2604    let need_emu_uv =
2605        csrc_x < 0 || csrc_y < 0 || csrc_x + 9 >= cw as i32 || csrc_y + 9 >= ch as i32;
2606    if need_emu_uv {
2607        let mut edge_uv = vec![0u8; uvlinesize * 9];
2608        let dst_xc = mb_col * 8;
2609        let dst_yc = mb_row * 8;
2610        let dst_cb_off = dst_yc * uvlinesize + dst_xc;
2611        emulated_edge_mc(
2612            &mut edge_uv,
2613            uvlinesize,
2614            &reference.cb,
2615            uvlinesize,
2616            9,
2617            9,
2618            csrc_x,
2619            csrc_y,
2620            cw,
2621            ch,
2622        );
2623        match cdxy {
2624            0 => chroma_put_pixels(&mut dst.cb, dst_cb_off, &edge_uv, 0, uvlinesize, 8),
2625            1 => chroma_put_x2(&mut dst.cb, dst_cb_off, &edge_uv, 0, uvlinesize, 8),
2626            2 => chroma_put_y2(&mut dst.cb, dst_cb_off, &edge_uv, 0, uvlinesize, 8),
2627            _ => chroma_put_xy2(&mut dst.cb, dst_cb_off, &edge_uv, 0, uvlinesize, 8),
2628        }
2629
2630        let dst_cr_off = dst_yc * uvlinesize + dst_xc;
2631        emulated_edge_mc(
2632            &mut edge_uv,
2633            uvlinesize,
2634            &reference.cr,
2635            uvlinesize,
2636            9,
2637            9,
2638            csrc_x,
2639            csrc_y,
2640            cw,
2641            ch,
2642        );
2643        match cdxy {
2644            0 => chroma_put_pixels(&mut dst.cr, dst_cr_off, &edge_uv, 0, uvlinesize, 8),
2645            1 => chroma_put_x2(&mut dst.cr, dst_cr_off, &edge_uv, 0, uvlinesize, 8),
2646            2 => chroma_put_y2(&mut dst.cr, dst_cr_off, &edge_uv, 0, uvlinesize, 8),
2647            _ => chroma_put_xy2(&mut dst.cr, dst_cr_off, &edge_uv, 0, uvlinesize, 8),
2648        }
2649        return;
2650    }
2651    let coff = (csrc_y as usize) * uvlinesize + (csrc_x as usize);
2652    let dst_xc = mb_col * 8;
2653    let dst_yc = mb_row * 8;
2654    let dst_cb_off = dst_yc * uvlinesize + dst_xc;
2655
2656    match cdxy {
2657        0 => chroma_put_pixels(&mut dst.cb, dst_cb_off, &reference.cb, coff, uvlinesize, 8),
2658        1 => chroma_put_x2(&mut dst.cb, dst_cb_off, &reference.cb, coff, uvlinesize, 8),
2659        2 => chroma_put_y2(&mut dst.cb, dst_cb_off, &reference.cb, coff, uvlinesize, 8),
2660        _ => chroma_put_xy2(&mut dst.cb, dst_cb_off, &reference.cb, coff, uvlinesize, 8),
2661    }
2662
2663    let dst_cr_off = dst_yc * uvlinesize + dst_xc;
2664    match cdxy {
2665        0 => chroma_put_pixels(&mut dst.cr, dst_cr_off, &reference.cr, coff, uvlinesize, 8),
2666        1 => chroma_put_x2(&mut dst.cr, dst_cr_off, &reference.cr, coff, uvlinesize, 8),
2667        2 => chroma_put_y2(&mut dst.cr, dst_cr_off, &reference.cr, coff, uvlinesize, 8),
2668        _ => chroma_put_xy2(&mut dst.cr, dst_cr_off, &reference.cr, coff, uvlinesize, 8),
2669    }
2670}
2671
2672fn add_residual_block(
2673    frame: &mut YuvFrame,
2674    mb_row: u32,
2675    mb_col: u32,
2676    blk: usize,
2677    coeff: &[i32; 64],
2678) {
2679    let (is_luma, bx, by, stride, ph) =
2680        block_coords(mb_row, mb_col, blk, frame.width, frame.height);
2681    let plane: &mut Vec<u8> = if is_luma {
2682        &mut frame.y
2683    } else if blk == 4 {
2684        &mut frame.cb
2685    } else {
2686        &mut frame.cr
2687    };
2688    for r in 0..8 {
2689        if by + r >= ph {
2690            break;
2691        }
2692        for c in 0..8 {
2693            if bx + c >= stride {
2694                break;
2695            }
2696            let idx = (by + r) * stride + (bx + c);
2697            plane[idx] = (plane[idx] as i32 + coeff[r * 8 + c]).clamp(0, 255) as u8;
2698        }
2699    }
2700}
2701
2702// ─── Macroblock Decoder ───────────────────────────────────────────────────────
2703
2704// ─── upstream RLTable (WMV1/2/MSMPEG4) ───────────────────────────────────
2705
2706const FF_RL_MAX_RUN: usize = 64;
2707const FF_RL_MAX_LEVEL: usize = 64;
2708
2709#[derive(Clone)]
2710struct Wmv2Rl {
2711    n: usize,
2712    last: usize,
2713    vlc: VlcTree,
2714    run: &'static [u8],
2715    level: &'static [u8],
2716    max_level: [[u8; FF_RL_MAX_RUN + 1]; 2],
2717    max_run: [[u8; FF_RL_MAX_LEVEL + 1]; 2],
2718}
2719
2720impl Wmv2Rl {
2721    fn new(base: &crate::na_rl_tables::RlBase) -> Self {
2722        let mut t = VlcTree::new();
2723        for (idx, (code, len)) in base.vlc.iter().enumerate() {
2724            if *len != 0 {
2725                t.insert(*code, *len, idx as i32);
2726            }
2727        }
2728
2729        let mut max_level = [[0u8; FF_RL_MAX_RUN + 1]; 2];
2730        let mut max_run = [[0u8; FF_RL_MAX_LEVEL + 1]; 2];
2731
2732        for last_flag in 0..2usize {
2733            let (start, end) = if last_flag == 0 {
2734                (0usize, base.last)
2735            } else {
2736                (base.last, base.n)
2737            };
2738            for i in start..end {
2739                let r = base.run[i] as usize;
2740                let l = base.level[i] as usize;
2741                if r <= FF_RL_MAX_RUN && l <= FF_RL_MAX_LEVEL {
2742                    if base.level[i] > max_level[last_flag][r] {
2743                        max_level[last_flag][r] = base.level[i];
2744                    }
2745                    if base.run[i] > max_run[last_flag][l] {
2746                        max_run[last_flag][l] = base.run[i];
2747                    }
2748                }
2749            }
2750        }
2751
2752        Wmv2Rl {
2753            n: base.n,
2754            last: base.last,
2755            vlc: t,
2756            run: base.run,
2757            level: base.level,
2758            max_level,
2759            max_run,
2760        }
2761    }
2762
2763    #[inline(always)]
2764    fn decode_sym(&self, br: &mut BitReader<'_>, qscale: i32) -> Option<(i32, i32)> {
2765        let idx = self.vlc.decode(br)? as usize;
2766        if idx == self.n {
2767            // Match upstream ff_rl_init_vlc(): escape maps to level==0, run==66.
2768            // Using run==0 can underflow i (starts at -1) and trigger OOB.
2769            return Some((0, 66));
2770        }
2771        let (qmul, qadd) = if qscale == 0 {
2772            (1i32, 0i32)
2773        } else {
2774            (qscale * 2, (qscale - 1) | 1)
2775        };
2776        let mut run = (self.run[idx] as i32) + 1;
2777        let level = (self.level[idx] as i32) * qmul + qadd;
2778        if idx >= self.last {
2779            run += 192;
2780        }
2781        Some((level, run))
2782    }
2783
2784    #[inline(always)]
2785    fn max_level_for(&self, last: usize, run: usize) -> i32 {
2786        self.max_level[last.min(1)][run.min(FF_RL_MAX_RUN)] as i32
2787    }
2788
2789    #[inline(always)]
2790    fn max_run_for(&self, last: usize, level: usize) -> i32 {
2791        self.max_run[last.min(1)][level.min(FF_RL_MAX_LEVEL)] as i32
2792    }
2793}
2794
2795#[derive(Clone, Copy, Debug, Default)]
2796struct Vc1MvData {
2797    dx: i32,
2798    dy: i32,
2799    intra: bool,
2800    has_coeffs: bool,
2801}
2802
2803#[inline(always)]
2804fn vc1_dc_scale(q: i32) -> i32 {
2805    const T: [i32; 32] = [
2806        0, 2, 4, 8, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18,
2807        18, 19, 19, 20, 20, 21, 21,
2808    ];
2809    T[q.abs().clamp(1, 31) as usize]
2810}
2811
2812#[inline(always)]
2813fn vc1_decode012_bits(br: &mut BitReader<'_>) -> Result<u8> {
2814    let first = br
2815        .read_bit()
2816        .ok_or_else(|| DecoderError::InvalidData("truncated VC-1 decode012".into()))?;
2817    if !first {
2818        Ok(0)
2819    } else {
2820        Ok(
2821            if br
2822                .read_bit()
2823                .ok_or_else(|| DecoderError::InvalidData("truncated VC-1 decode012".into()))?
2824            {
2825                2
2826            } else {
2827                1
2828            },
2829        )
2830    }
2831}
2832
2833#[inline(always)]
2834fn vc1_decode210_bits(br: &mut BitReader<'_>) -> Result<u8> {
2835    // FFmpeg decode210(): 1 -> 0, 01 -> 1, 00 -> 2.
2836    let first = br
2837        .read_bit()
2838        .ok_or_else(|| DecoderError::InvalidData("truncated VC-1 escape selector".into()))?;
2839    if first {
2840        Ok(0)
2841    } else {
2842        Ok(
2843            if br
2844                .read_bit()
2845                .ok_or_else(|| DecoderError::InvalidData("truncated VC-1 escape selector".into()))?
2846            {
2847                1
2848            } else {
2849                2
2850            },
2851        )
2852    }
2853}
2854
2855#[inline(always)]
2856fn vc1_read_unary_stop_one(br: &mut BitReader<'_>, max: u8) -> Result<u8> {
2857    let mut n = 0u8;
2858    while n < max {
2859        if br
2860            .read_bit()
2861            .ok_or_else(|| DecoderError::InvalidData("truncated VC-1 unary".into()))?
2862        {
2863            break;
2864        }
2865        n += 1;
2866    }
2867    Ok(n)
2868}
2869
2870#[inline(always)]
2871fn vc1_scale_level(level: i32, mquant: i32, halfqp: bool, pquantizer: bool) -> i32 {
2872    if level == 0 {
2873        return 0;
2874    }
2875    let quant = mquant.abs().clamp(1, 31);
2876    let scale = quant * 2
2877        + if mquant < 0 {
2878            0
2879        } else if halfqp {
2880            1
2881        } else {
2882            0
2883        };
2884    let mut out = level * scale;
2885    if !pquantizer {
2886        out += if out < 0 { -quant } else { quant };
2887    }
2888    out
2889}
2890
2891#[inline(always)]
2892fn vc1_rescale_dc_pred(v: i32, current_q: i32, neighbour_q: i32) -> i32 {
2893    const DQSCALE: [i32; 63] = [
2894        0x40000, 0x20000, 0x15555, 0x10000, 0x0CCCD, 0x0AAAB, 0x09249, 0x08000, 0x071C7, 0x06666,
2895        0x05D17, 0x05555, 0x04EC5, 0x04925, 0x04444, 0x04000, 0x03C3C, 0x038E4, 0x035E5, 0x03333,
2896        0x030C3, 0x02E8C, 0x02C86, 0x02AAB, 0x028F6, 0x02762, 0x025ED, 0x02492, 0x0234F, 0x02222,
2897        0x02108, 0x02000, 0x01F08, 0x01E1E, 0x01D42, 0x01C72, 0x01BAD, 0x01AF3, 0x01A42, 0x0199A,
2898        0x018FA, 0x01862, 0x017D0, 0x01746, 0x016C1, 0x01643, 0x015CA, 0x01555, 0x014E6, 0x0147B,
2899        0x01414, 0x013B1, 0x01352, 0x012F7, 0x0129E, 0x01249, 0x011F7, 0x011A8, 0x0115B, 0x01111,
2900        0x010C9, 0x01084, 0x01041,
2901    ];
2902    let q1 = current_q.abs().clamp(1, 31);
2903    let q2 = neighbour_q.abs().clamp(1, 31);
2904    if v == 0 || neighbour_q == 0 || q1 == q2 {
2905        return v;
2906    }
2907    let idx = vc1_dc_scale(q1) - 1;
2908    if !(0..63).contains(&idx) {
2909        return v;
2910    }
2911    (((v as i64) * vc1_dc_scale(q2) as i64 * DQSCALE[idx as usize] as i64 + 0x20000) >> 18) as i32
2912}
2913
2914#[inline(always)]
2915fn vc1_rescale_ac_pred(v: i32, current_q: i32, neighbour_q: i32, halfqp: bool) -> i32 {
2916    const DQSCALE: [i32; 63] = [
2917        0x40000, 0x20000, 0x15555, 0x10000, 0x0CCCD, 0x0AAAB, 0x09249, 0x08000, 0x071C7, 0x06666,
2918        0x05D17, 0x05555, 0x04EC5, 0x04925, 0x04444, 0x04000, 0x03C3C, 0x038E4, 0x035E5, 0x03333,
2919        0x030C3, 0x02E8C, 0x02C86, 0x02AAB, 0x028F6, 0x02762, 0x025ED, 0x02492, 0x0234F, 0x02222,
2920        0x02108, 0x02000, 0x01F08, 0x01E1E, 0x01D42, 0x01C72, 0x01BAD, 0x01AF3, 0x01A42, 0x0199A,
2921        0x018FA, 0x01862, 0x017D0, 0x01746, 0x016C1, 0x01643, 0x015CA, 0x01555, 0x014E6, 0x0147B,
2922        0x01414, 0x013B1, 0x01352, 0x012F7, 0x0129E, 0x01249, 0x011F7, 0x011A8, 0x0115B, 0x01111,
2923        0x010C9, 0x01084, 0x01041,
2924    ];
2925    if v == 0 || neighbour_q == 0 || current_q == neighbour_q {
2926        return v;
2927    }
2928    let q1 = current_q.abs() * 2
2929        + if current_q < 0 {
2930            0
2931        } else if halfqp {
2932            1
2933        } else {
2934            0
2935        }
2936        - 1;
2937    let q2 = neighbour_q.abs() * 2
2938        + if neighbour_q < 0 {
2939            0
2940        } else if halfqp {
2941            1
2942        } else {
2943            0
2944        }
2945        - 1;
2946    if !(1..=63).contains(&q1) || q2 < 1 {
2947        return v;
2948    }
2949    (((v as i64) * (q2 as i64) * (DQSCALE[(q1 - 1) as usize] as i64) + 0x20000) >> 18) as i32
2950}
2951
2952#[inline(always)]
2953fn vc1_bfraction_scale(num: i32, den: i32) -> i32 {
2954    // SMPTE 421M / FFmpeg use a fixed denominator of 256 for B-fraction
2955    // motion-vector scaling.  These are not the exact mathematical fractions:
2956    // e.g. 1/3 is represented by 85 and 2/3 by 170.  Using num/den directly
2957    // changes rounding, especially for negative vectors, and causes visible
2958    // drift/ghosting across B pictures.
2959    match (num, den) {
2960        (1, 2) => 128,
2961        (1, 3) => 85,
2962        (2, 3) => 170,
2963        (1, 4) => 64,
2964        (3, 4) => 192,
2965        (1, 5) => 51,
2966        (2, 5) => 102,
2967        (3, 5) => 153,
2968        (4, 5) => 204,
2969        (1, 6) => 43,
2970        (5, 6) => 215,
2971        (1, 7) => 37,
2972        (2, 7) => 74,
2973        (3, 7) => 111,
2974        (4, 7) => 148,
2975        (5, 7) => 185,
2976        (6, 7) => 222,
2977        (1, 8) => 32,
2978        (3, 8) => 96,
2979        (5, 8) => 160,
2980        (7, 8) => 224,
2981        _ => 0,
2982    }
2983}
2984
2985#[inline(always)]
2986fn vc1_scale_b_mv(value: i32, num: i32, den: i32, inverse: bool, quarter_sample: bool) -> i32 {
2987    let mut n = vc1_bfraction_scale(num, den);
2988    if inverse {
2989        n -= 256;
2990    }
2991    // FFmpeg scale_mv(), B_FRACTION_DEN == 256.  Rust signed right shift is
2992    // arithmetic, matching the C implementation for the values used here.
2993    if quarter_sample {
2994        (value * n + 128) >> 8
2995    } else {
2996        2 * ((value * n + 255) >> 9)
2997    }
2998}
2999
3000#[inline(always)]
3001fn vc1_sample_clamped(src: &[u8], stride: usize, w: usize, h: usize, x: i32, y: i32) -> i32 {
3002    if w == 0 || h == 0 || src.is_empty() {
3003        return 0;
3004    }
3005    let xx = x.clamp(0, w as i32 - 1) as usize;
3006    let yy = y.clamp(0, h as i32 - 1) as usize;
3007    src[yy * stride + xx] as i32
3008}
3009
3010#[inline(always)]
3011fn vc1_mspel_raw4(a: i32, b: i32, c: i32, d: i32, mode: i32) -> i32 {
3012    match mode {
3013        1 => -4 * a + 53 * b + 18 * c - 3 * d,
3014        2 => -a + 9 * b + 9 * c - d,
3015        3 => -3 * a + 18 * b + 53 * c - 4 * d,
3016        _ => b,
3017    }
3018}
3019
3020#[inline(always)]
3021fn vc1_mspel_one(a: i32, b: i32, c: i32, d: i32, mode: i32, r: i32) -> i32 {
3022    match mode {
3023        1 | 3 => (vc1_mspel_raw4(a, b, c, d, mode) + 32 - r) >> 6,
3024        2 => (vc1_mspel_raw4(a, b, c, d, mode) + 8 - r) >> 4,
3025        _ => b,
3026    }
3027}
3028
3029fn vc1_mspel_block(
3030    dst: &mut [u8],
3031    dst_stride: usize,
3032    src: &[u8],
3033    src_stride: usize,
3034    sw: usize,
3035    sh: usize,
3036    src_x: i32,
3037    src_y: i32,
3038    bw: usize,
3039    bh: usize,
3040    hmode: i32,
3041    vmode: i32,
3042    rnd: bool,
3043) {
3044    let rnd_i = rnd as i32;
3045
3046    // FFmpeg runs the normal MC path directly on the reference picture and
3047    // only invokes edge emulation for blocks whose filter footprint crosses a
3048    // picture boundary. The old Rust port called clamp() for every single tap,
3049    // even for interior macroblocks. At 1080p that means hundreds of millions
3050    // of redundant clamps/index calculations per second.
3051    let x0 = src_x as i64;
3052    let y0 = src_y as i64;
3053    let interior_hv =
3054        x0 >= 1 && y0 >= 1 && x0 + bw as i64 + 1 < sw as i64 && y0 + bh as i64 + 1 < sh as i64;
3055    let interior_v =
3056        x0 >= 0 && y0 >= 1 && x0 + bw as i64 <= sw as i64 && y0 + bh as i64 + 1 < sh as i64;
3057    let interior_h =
3058        x0 >= 1 && y0 >= 0 && x0 + bw as i64 + 1 < sw as i64 && y0 + bh as i64 <= sh as i64;
3059    let interior_copy =
3060        x0 >= 0 && y0 >= 0 && x0 + bw as i64 <= sw as i64 && y0 + bh as i64 <= sh as i64;
3061
3062    if hmode != 0 && vmode != 0 {
3063        let shift_value = [0i32, 5, 1, 5];
3064        let shift = (shift_value[hmode as usize] + shift_value[vmode as usize]) >> 1;
3065        let r1 = (1 << (shift - 1)) + rnd_i - 1;
3066        let tw = bw + 3;
3067        // VC-1 luma MC is at most 16x16, so the separable-filter scratch
3068        // buffer is bounded by (16 + 3) * 16. Keep it on the stack: this
3069        // routine is called thousands of times per 1080p frame.
3070        let mut tmp_storage = [0i32; 19 * 16];
3071        let tmp = &mut tmp_storage[..tw * bh];
3072
3073        if interior_hv {
3074            let sx = src_x as usize;
3075            let sy = src_y as usize;
3076            for y in 0..bh {
3077                let yy = sy + y;
3078                for tx in 0..tw {
3079                    let x = sx + tx - 1;
3080                    let raw = vc1_mspel_raw4(
3081                        src[(yy - 1) * src_stride + x] as i32,
3082                        src[yy * src_stride + x] as i32,
3083                        src[(yy + 1) * src_stride + x] as i32,
3084                        src[(yy + 2) * src_stride + x] as i32,
3085                        vmode,
3086                    );
3087                    tmp[y * tw + tx] = (raw + r1) >> shift;
3088                }
3089            }
3090        } else {
3091            for y in 0..bh {
3092                for tx in 0..tw {
3093                    let x = src_x + tx as i32 - 1;
3094                    let yy = src_y + y as i32;
3095                    let raw = vc1_mspel_raw4(
3096                        vc1_sample_clamped(src, src_stride, sw, sh, x, yy - 1),
3097                        vc1_sample_clamped(src, src_stride, sw, sh, x, yy),
3098                        vc1_sample_clamped(src, src_stride, sw, sh, x, yy + 1),
3099                        vc1_sample_clamped(src, src_stride, sw, sh, x, yy + 2),
3100                        vmode,
3101                    );
3102                    tmp[y * tw + tx] = (raw + r1) >> shift;
3103                }
3104            }
3105        }
3106
3107        let r2 = 64 - rnd_i;
3108        for y in 0..bh {
3109            for x in 0..bw {
3110                let i = y * tw + x;
3111                let raw = vc1_mspel_raw4(tmp[i], tmp[i + 1], tmp[i + 2], tmp[i + 3], hmode);
3112                dst[y * dst_stride + x] = ((raw + r2) >> 7).clamp(0, 255) as u8;
3113            }
3114        }
3115    } else if vmode != 0 {
3116        let r = 1 - rnd_i;
3117        if interior_v {
3118            let sx = src_x as usize;
3119            let sy = src_y as usize;
3120            for y in 0..bh {
3121                let yy = sy + y;
3122                for x in 0..bw {
3123                    let xx = sx + x;
3124                    let v = vc1_mspel_one(
3125                        src[(yy - 1) * src_stride + xx] as i32,
3126                        src[yy * src_stride + xx] as i32,
3127                        src[(yy + 1) * src_stride + xx] as i32,
3128                        src[(yy + 2) * src_stride + xx] as i32,
3129                        vmode,
3130                        r,
3131                    );
3132                    dst[y * dst_stride + x] = v.clamp(0, 255) as u8;
3133                }
3134            }
3135        } else {
3136            for y in 0..bh {
3137                for x in 0..bw {
3138                    let xx = src_x + x as i32;
3139                    let yy = src_y + y as i32;
3140                    let v = vc1_mspel_one(
3141                        vc1_sample_clamped(src, src_stride, sw, sh, xx, yy - 1),
3142                        vc1_sample_clamped(src, src_stride, sw, sh, xx, yy),
3143                        vc1_sample_clamped(src, src_stride, sw, sh, xx, yy + 1),
3144                        vc1_sample_clamped(src, src_stride, sw, sh, xx, yy + 2),
3145                        vmode,
3146                        r,
3147                    );
3148                    dst[y * dst_stride + x] = v.clamp(0, 255) as u8;
3149                }
3150            }
3151        }
3152    } else if hmode != 0 {
3153        let r = rnd_i;
3154        if interior_h {
3155            let sx = src_x as usize;
3156            let sy = src_y as usize;
3157            for y in 0..bh {
3158                let yy = sy + y;
3159                let row = yy * src_stride;
3160                for x in 0..bw {
3161                    let xx = sx + x;
3162                    let v = vc1_mspel_one(
3163                        src[row + xx - 1] as i32,
3164                        src[row + xx] as i32,
3165                        src[row + xx + 1] as i32,
3166                        src[row + xx + 2] as i32,
3167                        hmode,
3168                        r,
3169                    );
3170                    dst[y * dst_stride + x] = v.clamp(0, 255) as u8;
3171                }
3172            }
3173        } else {
3174            for y in 0..bh {
3175                for x in 0..bw {
3176                    let xx = src_x + x as i32;
3177                    let yy = src_y + y as i32;
3178                    let v = vc1_mspel_one(
3179                        vc1_sample_clamped(src, src_stride, sw, sh, xx - 1, yy),
3180                        vc1_sample_clamped(src, src_stride, sw, sh, xx, yy),
3181                        vc1_sample_clamped(src, src_stride, sw, sh, xx + 1, yy),
3182                        vc1_sample_clamped(src, src_stride, sw, sh, xx + 2, yy),
3183                        hmode,
3184                        r,
3185                    );
3186                    dst[y * dst_stride + x] = v.clamp(0, 255) as u8;
3187                }
3188            }
3189        }
3190    } else if interior_copy {
3191        let sx = src_x as usize;
3192        let sy = src_y as usize;
3193        for y in 0..bh {
3194            let src_off = (sy + y) * src_stride + sx;
3195            let dst_off = y * dst_stride;
3196            dst[dst_off..dst_off + bw].copy_from_slice(&src[src_off..src_off + bw]);
3197        }
3198    } else {
3199        for y in 0..bh {
3200            for x in 0..bw {
3201                dst[y * dst_stride + x] =
3202                    vc1_sample_clamped(src, src_stride, sw, sh, src_x + x as i32, src_y + y as i32)
3203                        as u8;
3204            }
3205        }
3206    }
3207}
3208
3209fn vc1_bilinear_qpel_block(
3210    dst: &mut [u8],
3211    dst_stride: usize,
3212    src: &[u8],
3213    src_stride: usize,
3214    sw: usize,
3215    sh: usize,
3216    src_x: i32,
3217    src_y: i32,
3218    bw: usize,
3219    bh: usize,
3220    fx: i32,
3221    fy: i32,
3222    denom: i32,
3223    rnd: bool,
3224) {
3225    // H.264-style chroma interpolation uses +32 before >>6 when rounded.
3226    // VC-1's no-round chroma primitive is *not* +31: FFmpeg/SMPTE use
3227    // +32-4 (+28) before >>6. Keep the generic half-pel case unchanged.
3228    let add = if denom == 8 && rnd {
3229        28
3230    } else {
3231        (denom * denom) / 2 - rnd as i32
3232    };
3233    let denom2 = denom * denom;
3234    let x0 = src_x as i64;
3235    let y0 = src_y as i64;
3236    let interior =
3237        x0 >= 0 && y0 >= 0 && x0 + (bw as i64) < sw as i64 && y0 + (bh as i64) < sh as i64;
3238
3239    if interior {
3240        let sx = src_x as usize;
3241        let sy = src_y as usize;
3242        let wa = (denom - fx) * (denom - fy);
3243        let wb = fx * (denom - fy);
3244        let wc = (denom - fx) * fy;
3245        let wd = fx * fy;
3246        for y in 0..bh {
3247            let row0 = (sy + y) * src_stride + sx;
3248            let row1 = row0 + src_stride;
3249            let dst_row = y * dst_stride;
3250            for x in 0..bw {
3251                let v = src[row0 + x] as i32 * wa
3252                    + src[row0 + x + 1] as i32 * wb
3253                    + src[row1 + x] as i32 * wc
3254                    + src[row1 + x + 1] as i32 * wd;
3255                dst[dst_row + x] = ((v + add) / denom2).clamp(0, 255) as u8;
3256            }
3257        }
3258    } else {
3259        for y in 0..bh {
3260            for x in 0..bw {
3261                let xx = src_x + x as i32;
3262                let yy = src_y + y as i32;
3263                let a = vc1_sample_clamped(src, src_stride, sw, sh, xx, yy);
3264                let b = vc1_sample_clamped(src, src_stride, sw, sh, xx + 1, yy);
3265                let c = vc1_sample_clamped(src, src_stride, sw, sh, xx, yy + 1);
3266                let d = vc1_sample_clamped(src, src_stride, sw, sh, xx + 1, yy + 1);
3267                let v = a * (denom - fx) * (denom - fy)
3268                    + b * fx * (denom - fy)
3269                    + c * (denom - fx) * fy
3270                    + d * fx * fy;
3271                dst[y * dst_stride + x] = ((v + add) / denom2).clamp(0, 255) as u8;
3272            }
3273        }
3274    }
3275}
3276
3277fn vc1_ic_value(v: u8, lumscale: u8, lumshift: u8, chroma: bool) -> u8 {
3278    let (scale, shift) = if lumscale == 0 {
3279        let mut sh = (255 - 2 * lumshift as i32) << 6;
3280        if lumshift > 31 {
3281            sh += 128 << 6;
3282        }
3283        (-64, sh)
3284    } else {
3285        let sh = if lumshift > 31 {
3286            (lumshift as i32 - 64) << 6
3287        } else {
3288            (lumshift as i32) << 6
3289        };
3290        (lumscale as i32 + 32, sh)
3291    };
3292    // FFmpeg INIT_LUT() deliberately applies LUMSHIFT only to luma.
3293    // Chroma is scaled around the neutral 128 centre and has no shift term:
3294    //   Y  = clip((scale * Y       + shift  + 32) >> 6)
3295    //   UV = clip((scale * (UV-128) + 128*64 + 32) >> 6)
3296    // Applying `shift` to Cb/Cr produces the green/magenta trails that are
3297    // most visible on intensity-compensated P pictures used for fades.
3298    if chroma {
3299        ((scale * (v as i32 - 128) + (128 << 6) + 32) >> 6).clamp(0, 255) as u8
3300    } else {
3301        ((scale * v as i32 + shift + 32) >> 6).clamp(0, 255) as u8
3302    }
3303}
3304
3305/// Apply the progressive Simple/Main intensity-compensation LUT to a reference
3306/// picture. FFmpeg applies scale+shift to luma, but chroma is only scaled around
3307/// the neutral 128 centre; LUMSHIFT is not part of the chroma LUT. Keeping this
3308/// as a reference-picture
3309/// operation lets P and B pictures share the exact same ordering with
3310/// RANGEREDFRM: range reduction first, intensity compensation second.
3311fn vc1_apply_ic_reference(frame: &mut YuvFrame, lumscale: u8, lumshift: u8) {
3312    for v in &mut frame.y {
3313        *v = vc1_ic_value(*v, lumscale, lumshift, false);
3314    }
3315    for v in &mut frame.cb {
3316        *v = vc1_ic_value(*v, lumscale, lumshift, true);
3317    }
3318    for v in &mut frame.cr {
3319        *v = vc1_ic_value(*v, lumscale, lumshift, true);
3320    }
3321}
3322
3323pub struct MacroblockDecoder {
3324    pub width: u32,
3325    pub height: u32,
3326    pub width_mb: u32,
3327    pub height_mb: u32,
3328    /// Reference frame for P/B decoding
3329    pub ref_frame: Option<YuvFrame>,
3330    // Exact VC-1 Simple/Main tables and per-picture predictor state.
3331    vc1_ac: [Vc1AcTable; 8],
3332    vc1_cbpcy: [VlcTable; 4],
3333    vc1_mvdata: [VlcTable; 4],
3334    vc1_ttmb: [VlcTable; 3],
3335    vc1_ttblk: [VlcTable; 3],
3336    vc1_subblkpat: [VlcTable; 3],
3337    vc1_esc3_level_length: u8,
3338    vc1_esc3_run_length: u8,
3339    vc1_coded_block: Vec<u8>,
3340    vc1_dc: Vec<[i32; 6]>,
3341    vc1_intra_blocks: Vec<[bool; 6]>,
3342    /// Signed post-inverse-transform blocks.  I pictures and P-picture intra
3343    /// blocks remain here until overlap/range processing is complete.
3344    vc1_recon_blocks: Vec<[i32; 64]>,
3345    /// Per-block residual coded-subblock pattern used by the VC-1 P loop filter.
3346    vc1_block_cbp: Vec<[u8; 6]>,
3347    /// Per-block transform type used by the VC-1 P loop filter.
3348    vc1_block_tt: Vec<[u8; 6]>,
3349    vc1_qscale: Vec<i32>,
3350    /// Advanced Profile CONDOVER=SELECT bitplane for the current I/BI picture.
3351    vc1_overflags: Vec<u8>,
3352    vc1_current_mvs: Vec<(i32, i32)>,
3353    vc1_mv4: Vec<[(i32, i32); 4]>,
3354    /// Derived chroma MV (FFmpeg luma_mv[]) for P-loop-filter decisions.
3355    vc1_chroma_mvs: Vec<(i32, i32)>,
3356    mv_pred: MvPredictor,
3357    vc1_bwd_anchor_mvs: Option<Vec<(i32, i32)>>,
3358    vc1_fwd_anchor_mvs: Option<Vec<(i32, i32)>>,
3359    vc1_rnd: bool,
3360    // ── WMV2 VLC tables (built lazily; shared with VC-1 decode machinery) ─────
3361    wmv2_inter: [VlcTable; 2], // ttcoef 0-1
3362    wmv2_intra: [VlcTable; 2],
3363    wmv2_cbpy: VlcTable,
3364    wmv2_cbpc: VlcTable,
3365    /// WMV2 reference frame (single-reference; no B-frame support)
3366    wmv2_ref: Option<YuvFrame>,
3367    // ── WMV2/MSMPEG4 (upstream-aligned) VLCs / state ─────────────────────────
3368    wmv2_mb_i_vlc: VlcTree,
3369    wmv2_dc_vlc: [[VlcTree; 2]; 2], // [dc_table_index][is_chroma]
3370    wmv2_coded_block: Vec<u8>,      // coded_block predictor grid (luma 8×8)
3371    wmv2_dc_pred: Wmv2DcPredBuffer,
3372    // ext-header flags (decode_ext_header)
3373    wmv2_mspel_bit: bool,
3374    wmv2_abt_flag: bool,
3375    wmv2_j_type_bit: bool,
3376    wmv2_top_left_mv_flag: bool,
3377    wmv2_per_mb_rl_bit: bool,
3378    // per-picture derived state (secondary picture header)
3379    wmv2_j_type: bool,
3380    wmv2_per_mb_rl_table: bool,
3381    wmv2_rl_table_index: u8,
3382    wmv2_rl_chroma_table_index: u8,
3383    wmv2_dc_table_index: usize,
3384
3385    // P-picture secondary header state (upstream wmv2dec.c)
3386    wmv2_cbp_table_index: usize,
3387    wmv2_mv_table_index: usize,
3388    wmv2_mspel: bool,
3389    wmv2_hshift: u8,
3390    wmv2_per_mb_abt: bool,
3391    wmv2_abt_type: u8,
3392    wmv2_skip_type: u8,
3393    wmv2_slice_height: usize,
3394    wmv2_mb_skip: Vec<bool>,
3395    wmv2_motion: Vec<(i32, i32)>,
3396
3397    // upstream MB and MV VLC tables
3398    wmv2_mb_non_intra_vlc: [VlcTree; 4],
3399    wmv2_mv_vlc: [VlcTree; 2],
3400    // upstream RL tables (run/level)
3401    wmv2_rl: [Wmv2Rl; 6],
3402    // WMV2 escape-3 adaptive lengths (reset each picture)
3403    wmv2_esc3_level_length: u8,
3404    wmv2_esc3_run_length: u8,
3405    // AC prediction buffer (16 values per block: [1..7] left, [9..15] top)
3406    wmv2_ac_val: Vec<[i16; 16]>,
3407    ac_pred: AcPredBuffer,
3408    /// Forward reference (anchor before B-frames in display order)
3409    fwd_ref: Option<YuvFrame>,
3410    /// Backward reference (anchor after B-frames in display order)
3411    bwd_ref: Option<YuvFrame>,
3412    /// Intensity compensation attached to the forward reference for the B
3413    /// pictures between the two anchors.  In FFmpeg this is `last_use_ic` /
3414    /// `last_luty` after the future anchor's P-picture header is parsed.
3415    /// The backward/current anchor uses the identity LUT in progressive
3416    /// Simple/Main profile.
3417    vc1_b_fwd_ic: Option<(u8, u8)>,
3418}
3419
3420impl MacroblockDecoder {
3421    pub fn new(width: u32, height: u32) -> Self {
3422        let mb_w = width.div_ceil(16) as usize;
3423        let mb_h = height.div_ceil(16) as usize;
3424
3425        // Build upstream MSMPEG4/WMV2 VLCs (MB I-table + DC tables).
3426        let wmv2_mb_i_vlc: VlcTree = {
3427            let mut t = VlcTree::new();
3428            for (sym, (code, len)) in FF_MSMP4_MB_I_TABLE.iter().enumerate() {
3429                t.insert(*code, *len, sym as i32);
3430            }
3431            t
3432        };
3433
3434        let wmv2_dc_vlc: [[VlcTree; 2]; 2] = std::array::from_fn(|ti| {
3435            std::array::from_fn(|ch| {
3436                let mut t = VlcTree::new();
3437                for (sym, (code, len)) in FF_MSMP4_DC_TABLES[ti][ch].iter().enumerate() {
3438                    t.insert(*code, *len, sym as i32);
3439                }
3440                t
3441            })
3442        });
3443
3444        // upstream mb_non_intra VLC tables (4 variants)
3445        let wmv2_mb_non_intra_vlc: [VlcTree; 4] = std::array::from_fn(|ti| {
3446            let mut t = VlcTree::new();
3447            for (sym, (code, len)) in FF_MB_NON_INTRA_TABLES[ti].iter().enumerate() {
3448                if *len != 0 {
3449                    t.insert(*code, *len, sym as i32);
3450                }
3451            }
3452            t
3453        });
3454
3455        // upstream motion vector VLC tables (2 variants)
3456        // Built exactly like ff_vlc_init_tables_from_lengths() + ff_vlc_init_from_lengths()
3457        // (msmpeg4dec.c msmpeg4_decode_init_static).
3458        let build_mv_from_lengths = |lens: &[u8; 1100], syms: &[u16; 1100]| -> VlcTree {
3459            let mut t = VlcTree::new();
3460            let mut code: u32 = 0;
3461            for i in 0..1100usize {
3462                let len = lens[i] as i32;
3463                if len == 0 {
3464                    continue;
3465                }
3466                let l = len.unsigned_abs() as u8;
3467                // upstream stores code left-aligned in a 32-bit word.
3468                let right_aligned = if l == 0 { 0 } else { code >> (32 - l) };
3469                if len > 0 {
3470                    t.insert(right_aligned, l, syms[i] as i32);
3471                }
3472                code = code.wrapping_add(1u32 << (32 - l));
3473            }
3474            t
3475        };
3476        let wmv2_mv_vlc: [VlcTree; 2] = [
3477            build_mv_from_lengths(&FF_MSMP4_MV_TABLE0_LENS, &FF_MSMP4_MV_TABLE0),
3478            build_mv_from_lengths(&FF_MSMP4_MV_TABLE1_LENS, &FF_MSMP4_MV_TABLE1),
3479        ];
3480
3481        // upstream RL tables (run/level)
3482        let wmv2_rl: [Wmv2Rl; 6] = std::array::from_fn(|i| Wmv2Rl::new(&FF_RL_BASES[i]));
3483        let wmv2_ac_val: Vec<[i16; 16]> = vec![[0i16; 16]; mb_w * mb_h * 6];
3484
3485        MacroblockDecoder {
3486            width,
3487            height,
3488            width_mb: width.div_ceil(16),
3489            height_mb: height.div_ceil(16),
3490            ref_frame: None,
3491            vc1_ac: ac_tables(),
3492            vc1_cbpcy: cbpcy_vlcs(),
3493            vc1_mvdata: mvdata_vlcs(),
3494            vc1_ttmb: ttmb_vlcs(),
3495            vc1_ttblk: ttblk_vlcs(),
3496            vc1_subblkpat: subblkpat_vlcs(),
3497            vc1_esc3_level_length: 0,
3498            vc1_esc3_run_length: 0,
3499            vc1_coded_block: vec![0u8; (2 * mb_w) * (2 * mb_h)],
3500            vc1_dc: vec![[0i32; 6]; mb_w * mb_h],
3501            vc1_intra_blocks: vec![[false; 6]; mb_w * mb_h],
3502            vc1_recon_blocks: vec![[0i32; 64]; mb_w * mb_h * 6],
3503            vc1_block_cbp: vec![[0u8; 6]; mb_w * mb_h],
3504            vc1_block_tt: vec![[0u8; 6]; mb_w * mb_h],
3505            vc1_qscale: vec![0i32; mb_w * mb_h],
3506            vc1_overflags: vec![0u8; mb_w * mb_h],
3507            vc1_current_mvs: vec![(0, 0); mb_w * mb_h],
3508            vc1_mv4: vec![[(0, 0); 4]; mb_w * mb_h],
3509            vc1_chroma_mvs: vec![(0, 0); mb_w * mb_h],
3510            mv_pred: MvPredictor::new(mb_w, mb_h),
3511            vc1_bwd_anchor_mvs: None,
3512            vc1_fwd_anchor_mvs: None,
3513            vc1_rnd: false,
3514            ac_pred: AcPredBuffer::new(mb_w, mb_h),
3515            fwd_ref: None,
3516            bwd_ref: None,
3517            vc1_b_fwd_ic: None,
3518            wmv2_inter: [wmv2_tcoef_inter_vlc(0), wmv2_tcoef_inter_vlc(1)],
3519            wmv2_intra: [wmv2_tcoef_intra_vlc(0), wmv2_tcoef_intra_vlc(1)],
3520            wmv2_cbpy: wmv2_cbpy_vlc(),
3521            wmv2_cbpc: wmv2_cbpc_p_vlc(),
3522            wmv2_ref: None,
3523
3524            // upstream-aligned WMV2/MSMPEG4 state
3525            wmv2_mb_i_vlc,
3526            wmv2_dc_vlc,
3527            wmv2_coded_block: vec![0u8; (2 * mb_w) * (2 * mb_h)],
3528            wmv2_dc_pred: Wmv2DcPredBuffer::new(mb_w, mb_h),
3529
3530            // ext-header flags (default false until set_extradata)
3531            wmv2_mspel_bit: false,
3532            wmv2_abt_flag: false,
3533            wmv2_j_type_bit: false,
3534            wmv2_top_left_mv_flag: false,
3535            wmv2_per_mb_rl_bit: false,
3536
3537            // per-picture derived state
3538            wmv2_j_type: false,
3539            wmv2_per_mb_rl_table: false,
3540            wmv2_rl_table_index: 0,
3541            wmv2_rl_chroma_table_index: 0,
3542            wmv2_dc_table_index: 0,
3543
3544            wmv2_cbp_table_index: 0,
3545            wmv2_mv_table_index: 0,
3546            wmv2_mspel: false,
3547            wmv2_hshift: 0,
3548            wmv2_per_mb_abt: false,
3549            wmv2_abt_type: 0,
3550            wmv2_skip_type: 0,
3551            wmv2_slice_height: mb_h.max(1),
3552            wmv2_mb_skip: vec![false; mb_w * mb_h],
3553            wmv2_motion: vec![(0, 0); mb_w * mb_h],
3554
3555            wmv2_mb_non_intra_vlc,
3556            wmv2_mv_vlc,
3557
3558            wmv2_rl,
3559            wmv2_esc3_level_length: 0,
3560            wmv2_esc3_run_length: 0,
3561            wmv2_ac_val,
3562        }
3563    }
3564
3565    pub fn decode_frame(
3566        &mut self,
3567        payload: &[u8],
3568        pic_hdr: &PictureHeader,
3569        seq: &SequenceHeader,
3570        frame: &mut YuvFrame,
3571    ) -> Result<()> {
3572        if let Some(rnd) = pic_hdr.rnd {
3573            // Advanced Profile carries RNDCTRL in every non-skipped picture.
3574            // Simple/Main derives the state by toggling it at picture boundaries.
3575            self.vc1_rnd = rnd;
3576        } else {
3577            match pic_hdr.frame_type {
3578                FrameType::I | FrameType::BI => self.vc1_rnd = true,
3579                FrameType::P => self.vc1_rnd = !self.vc1_rnd,
3580                _ => {}
3581            }
3582        }
3583
3584        match pic_hdr.frame_type {
3585            FrameType::I | FrameType::BI => {
3586                self.decode_intra(payload, pic_hdr, seq, frame)?;
3587                self.vc1_finish_i_reconstruction(frame, pic_hdr, seq);
3588                if seq.loop_filter {
3589                    vc1_apply_i_loop_filter(
3590                        frame,
3591                        pic_hdr.pquant as i32,
3592                        self.width_mb as usize,
3593                        self.height_mb as usize,
3594                    );
3595                }
3596            }
3597            FrameType::P => {
3598                self.decode_p(payload, pic_hdr, seq, frame)?;
3599                self.vc1_finish_p_reconstruction(frame, pic_hdr, seq);
3600                if seq.loop_filter {
3601                    self.vc1_apply_p_loop_filter(frame, pic_hdr.pquant as i32);
3602                }
3603            }
3604            FrameType::B => {
3605                self.decode_b(payload, pic_hdr, seq, frame)?;
3606                // FFmpeg uses the I-picture loop-filter traversal for
3607                // progressive B pictures.
3608                if seq.loop_filter {
3609                    vc1_apply_i_loop_filter(
3610                        frame,
3611                        pic_hdr.pquant as i32,
3612                        self.width_mb as usize,
3613                        self.height_mb as usize,
3614                    );
3615                }
3616            }
3617            FrameType::Skipped => {
3618                if let Some(ref rf) = self.ref_frame {
3619                    frame.y.copy_from_slice(&rf.y);
3620                    frame.cb.copy_from_slice(&rf.cb);
3621                    frame.cr.copy_from_slice(&rf.cr);
3622                }
3623            }
3624        }
3625
3626        // Anchor frames (I/P) become references.  RANGEREDFRM is a property
3627        // of the *current* picture's MC operation; references are never
3628        // destructively compressed when stored.
3629        match pic_hdr.frame_type {
3630            FrameType::B | FrameType::BI => {}
3631            _ => {
3632                self.fwd_ref = self.bwd_ref.take();
3633                self.bwd_ref = Some(frame.clone());
3634                self.ref_frame = Some(frame.clone());
3635                self.vc1_fwd_anchor_mvs = self.vc1_bwd_anchor_mvs.take();
3636                self.vc1_bwd_anchor_mvs = Some(self.vc1_current_mvs.clone());
3637
3638                // ff_vc1_parse_frame_header() records P-picture intensity
3639                // compensation on `last_luty`, i.e. on the older anchor that
3640                // becomes the forward reference for following B pictures.
3641                // B/BI pictures do not rotate or replace this state.
3642                self.vc1_b_fwd_ic = if pic_hdr.frame_type == FrameType::P
3643                    && pic_hdr.mv_mode == MvMode::IntensityComp
3644                {
3645                    Some((pic_hdr.lumscale, pic_hdr.lumshift))
3646                } else {
3647                    None
3648                };
3649            }
3650        }
3651        Ok(())
3652    }
3653
3654    // ─── VC-1 macroblock helpers ───────────────────────────────────────────
3655
3656    fn vc1_reset_picture_state(&mut self) {
3657        self.vc1_coded_block.fill(0);
3658        self.vc1_dc.fill([0; 6]);
3659        self.vc1_intra_blocks.fill([false; 6]);
3660        self.vc1_recon_blocks.fill([0; 64]);
3661        self.vc1_block_cbp.fill([0; 6]);
3662        self.vc1_block_tt.fill([0; 6]);
3663        self.vc1_qscale.fill(0);
3664        self.vc1_overflags.fill(0);
3665        self.vc1_current_mvs.fill((0, 0));
3666        self.vc1_mv4.fill([(0, 0); 4]);
3667        self.vc1_chroma_mvs.fill((0, 0));
3668        self.vc1_esc3_level_length = 0;
3669        self.vc1_esc3_run_length = 0;
3670        self.ac_pred.clear();
3671    }
3672
3673    fn vc1_finish_i_reconstruction(
3674        &mut self,
3675        frame: &mut YuvFrame,
3676        pic: &PictureHeader,
3677        seq: &SequenceHeader,
3678    ) {
3679        let mb_w = self.width_mb as usize;
3680        let mb_h = self.height_mb as usize;
3681
3682        if seq.profile == Profile::Advanced {
3683            // FFmpeg vc1_decode_i_blocks_adv(): Advanced Profile always uses
3684            // signed inverse-transform samples and `vc1_put_blocks_clamped(...,
3685            // 1)`, i.e. the +128 reconstruction path, even when overlap is
3686            // disabled. At low quantizers CONDOVER controls whether overlap is
3687            // off, enabled everywhere, or selected by the OVERFLAGS bitplane.
3688            if seq.overlap {
3689                if pic.pquant >= 9 || pic.condover == 1 {
3690                    vc1_apply_signed_overlap(
3691                        &mut self.vc1_recon_blocks,
3692                        &self.vc1_intra_blocks,
3693                        mb_w,
3694                        mb_h,
3695                        false,
3696                    );
3697                } else if pic.condover == 2 {
3698                    vc1_apply_advanced_selective_i_overlap(
3699                        &mut self.vc1_recon_blocks,
3700                        mb_w,
3701                        mb_h,
3702                        &self.vc1_overflags,
3703                    );
3704                }
3705            }
3706
3707            for r in 0..mb_h {
3708                for c in 0..mb_w {
3709                    for blk in 0..6usize {
3710                        let ri = vc1_recon_index(mb_w, r, c, blk);
3711                        write_intra_block(
3712                            frame,
3713                            r as u32,
3714                            c as u32,
3715                            blk,
3716                            &self.vc1_recon_blocks[ri],
3717                        );
3718                    }
3719                }
3720            }
3721            return;
3722        }
3723
3724        let overlap = seq.overlap && pic.pquant >= 9;
3725
3726        if overlap {
3727            vc1_apply_signed_overlap(
3728                &mut self.vc1_recon_blocks,
3729                &self.vc1_intra_blocks,
3730                mb_w,
3731                mb_h,
3732                pic.rangeredfrm,
3733            );
3734        }
3735
3736        // FFmpeg vc1_decode_i_blocks(): for the high-quant overlap path,
3737        // RANGEREDFRM multiplies the signed block after overlap and the result
3738        // is put with +128.  Without overlap, it uses (block - 64) * 2 and
3739        // put_pixels_clamped() (no signed +128 bias).
3740        for r in 0..mb_h {
3741            for c in 0..mb_w {
3742                for blk in 0..6usize {
3743                    let ri = vc1_recon_index(mb_w, r, c, blk);
3744                    let block = &mut self.vc1_recon_blocks[ri];
3745                    if pic.rangeredfrm && !overlap {
3746                        for v in block.iter_mut() {
3747                            *v = (*v - 64) * 2;
3748                        }
3749                    }
3750                    if overlap {
3751                        write_intra_block(frame, r as u32, c as u32, blk, block);
3752                    } else {
3753                        write_intra_block_unsigned(frame, r as u32, c as u32, blk, block);
3754                    }
3755                }
3756            }
3757        }
3758    }
3759
3760    fn vc1_finish_p_reconstruction(
3761        &mut self,
3762        frame: &mut YuvFrame,
3763        pic: &PictureHeader,
3764        seq: &SequenceHeader,
3765    ) {
3766        let mb_w = self.width_mb as usize;
3767        let mb_h = self.height_mb as usize;
3768
3769        // In a P picture FFmpeg multiplies RANGEREDFRM intra blocks by two
3770        // immediately after inverse transform, then performs overlap only when
3771        // both neighboring blocks are intra.  The multiply is done during
3772        // decode_p(); only overlap and delayed signed write remain here.
3773        if seq.overlap && pic.pquant >= 9 {
3774            vc1_apply_signed_overlap(
3775                &mut self.vc1_recon_blocks,
3776                &self.vc1_intra_blocks,
3777                mb_w,
3778                mb_h,
3779                false,
3780            );
3781        }
3782
3783        for r in 0..mb_h {
3784            for c in 0..mb_w {
3785                let mbi = r * mb_w + c;
3786                for blk in 0..6usize {
3787                    if !self.vc1_intra_blocks[mbi][blk] {
3788                        continue;
3789                    }
3790                    let ri = vc1_recon_index(mb_w, r, c, blk);
3791                    write_intra_block(frame, r as u32, c as u32, blk, &self.vc1_recon_blocks[ri]);
3792                }
3793            }
3794        }
3795    }
3796
3797    #[inline]
3798    fn vc1_p_loop_meta(
3799        &self,
3800        plane: usize,
3801        block_x: usize,
3802        block_y: usize,
3803    ) -> (bool, u8, u8, (i32, i32)) {
3804        let mb_w = self.width_mb as usize;
3805        if plane == 0 {
3806            let mb_col = block_x / 2;
3807            let mb_row = block_y / 2;
3808            let blk = (block_y & 1) * 2 + (block_x & 1);
3809            let mbi = mb_row * mb_w + mb_col;
3810            (
3811                self.vc1_intra_blocks[mbi][blk],
3812                self.vc1_block_cbp[mbi][blk],
3813                self.vc1_block_tt[mbi][blk],
3814                self.vc1_mv4[mbi][blk],
3815            )
3816        } else {
3817            let blk = if plane == 1 { 4 } else { 5 };
3818            let mbi = block_y * mb_w + block_x;
3819            (
3820                self.vc1_intra_blocks[mbi][blk],
3821                self.vc1_block_cbp[mbi][blk],
3822                self.vc1_block_tt[mbi][blk],
3823                self.vc1_chroma_mvs[mbi],
3824            )
3825        }
3826    }
3827
3828    #[inline]
3829    fn vc1_p_v_loop_filter_block(
3830        &self,
3831        plane: &mut [u8],
3832        width: usize,
3833        height: usize,
3834        plane_no: usize,
3835        bx: usize,
3836        by: usize,
3837        pq: i32,
3838    ) {
3839        let blocks_w = if plane_no == 0 {
3840            self.width_mb as usize * 2
3841        } else {
3842            self.width_mb as usize
3843        };
3844        let blocks_h = if plane_no == 0 {
3845            self.height_mb as usize * 2
3846        } else {
3847            self.height_mb as usize
3848        };
3849        if bx >= blocks_w || by >= blocks_h {
3850            return;
3851        }
3852
3853        let x0 = bx * 8;
3854        let y0 = by * 8;
3855        if x0 >= width || y0 >= height {
3856            return;
3857        }
3858        let top = self.vc1_p_loop_meta(plane_no, bx, by);
3859
3860        // ff_vc1_p_v_loop_filter(): the 8-pixel edge below this block.
3861        if by + 1 < blocks_h && (by + 1) * 8 < height {
3862            let bottom = self.vc1_p_loop_meta(plane_no, bx, by + 1);
3863            let edge_y = (by + 1) * 8;
3864            if top.0 || bottom.0 || top.3 != bottom.3 {
3865                vc1_filter_horizontal_boundary(plane, width, height, x0, edge_y, 8, pq);
3866            } else {
3867                let idx = (top.1 | (bottom.1 >> 2)) & 3;
3868                if (idx & 1) != 0 {
3869                    vc1_filter_horizontal_boundary(plane, width, height, x0 + 4, edge_y, 4, pq);
3870                }
3871                if (idx & 2) != 0 {
3872                    vc1_filter_horizontal_boundary(plane, width, height, x0, edge_y, 4, pq);
3873                }
3874            }
3875        }
3876
3877        // Internal 4-pixel transform edge of this 8x8 block.
3878        if matches!(top.2, TT_4X4 | TT_8X4) {
3879            let edge_y = y0 + 4;
3880            if edge_y < height {
3881                if (top.1 & 5) != 0 {
3882                    vc1_filter_horizontal_boundary(plane, width, height, x0 + 4, edge_y, 4, pq);
3883                }
3884                if (top.1 & 10) != 0 {
3885                    vc1_filter_horizontal_boundary(plane, width, height, x0, edge_y, 4, pq);
3886                }
3887            }
3888        }
3889    }
3890
3891    #[inline]
3892    fn vc1_p_h_loop_filter_block(
3893        &self,
3894        plane: &mut [u8],
3895        width: usize,
3896        height: usize,
3897        plane_no: usize,
3898        bx: usize,
3899        by: usize,
3900        pq: i32,
3901    ) {
3902        let blocks_w = if plane_no == 0 {
3903            self.width_mb as usize * 2
3904        } else {
3905            self.width_mb as usize
3906        };
3907        let blocks_h = if plane_no == 0 {
3908            self.height_mb as usize * 2
3909        } else {
3910            self.height_mb as usize
3911        };
3912        if bx >= blocks_w || by >= blocks_h {
3913            return;
3914        }
3915
3916        let x0 = bx * 8;
3917        let y0 = by * 8;
3918        if x0 >= width || y0 >= height {
3919            return;
3920        }
3921        let left = self.vc1_p_loop_meta(plane_no, bx, by);
3922
3923        // ff_vc1_p_h_loop_filter(): the 8-pixel edge right of this block.
3924        if bx + 1 < blocks_w && (bx + 1) * 8 < width {
3925            let right = self.vc1_p_loop_meta(plane_no, bx + 1, by);
3926            let edge_x = (bx + 1) * 8;
3927            if left.0 || right.0 || left.3 != right.3 {
3928                vc1_filter_vertical_boundary(plane, width, height, edge_x, y0, 8, pq);
3929            } else {
3930                let idx = (left.1 | (right.1 >> 1)) & 5;
3931                if (idx & 1) != 0 {
3932                    vc1_filter_vertical_boundary(plane, width, height, edge_x, y0 + 4, 4, pq);
3933                }
3934                if (idx & 4) != 0 {
3935                    vc1_filter_vertical_boundary(plane, width, height, edge_x, y0, 4, pq);
3936                }
3937            }
3938        }
3939
3940        // Internal 4-pixel transform edge of this 8x8 block.
3941        if matches!(left.2, TT_4X4 | TT_4X8) {
3942            let edge_x = x0 + 4;
3943            if edge_x < width {
3944                if (left.1 & 3) != 0 {
3945                    vc1_filter_vertical_boundary(plane, width, height, edge_x, y0 + 4, 4, pq);
3946                }
3947                if (left.1 & 12) != 0 {
3948                    vc1_filter_vertical_boundary(plane, width, height, edge_x, y0, 4, pq);
3949                }
3950            }
3951        }
3952    }
3953
3954    fn vc1_p_v_loop_filter_mb(&self, frame: &mut YuvFrame, mb_row: usize, mb_col: usize, pq: i32) {
3955        let w = frame.width as usize;
3956        let h = frame.height as usize;
3957        for local_y in 0..2 {
3958            for local_x in 0..2 {
3959                self.vc1_p_v_loop_filter_block(
3960                    &mut frame.y,
3961                    w,
3962                    h,
3963                    0,
3964                    mb_col * 2 + local_x,
3965                    mb_row * 2 + local_y,
3966                    pq,
3967                );
3968            }
3969        }
3970
3971        let cw = w / 2;
3972        let ch = h / 2;
3973        self.vc1_p_v_loop_filter_block(&mut frame.cb, cw, ch, 1, mb_col, mb_row, pq);
3974        self.vc1_p_v_loop_filter_block(&mut frame.cr, cw, ch, 2, mb_col, mb_row, pq);
3975    }
3976
3977    fn vc1_p_h_loop_filter_mb(&self, frame: &mut YuvFrame, mb_row: usize, mb_col: usize, pq: i32) {
3978        let w = frame.width as usize;
3979        let h = frame.height as usize;
3980        for local_y in 0..2 {
3981            for local_x in 0..2 {
3982                self.vc1_p_h_loop_filter_block(
3983                    &mut frame.y,
3984                    w,
3985                    h,
3986                    0,
3987                    mb_col * 2 + local_x,
3988                    mb_row * 2 + local_y,
3989                    pq,
3990                );
3991            }
3992        }
3993
3994        let cw = w / 2;
3995        let ch = h / 2;
3996        self.vc1_p_h_loop_filter_block(&mut frame.cb, cw, ch, 1, mb_col, mb_row, pq);
3997        self.vc1_p_h_loop_filter_block(&mut frame.cr, cw, ch, 2, mb_col, mb_row, pq);
3998    }
3999
4000    /// Reproduce the macroblock wavefront used by FFmpeg
4001    /// `ff_vc1_p_loop_filter()`.  P filtering trails overlap by two rows/two
4002    /// columns; running all V edges and then all H edges changes the samples at
4003    /// edge crossings and is not bit-exact.
4004    fn vc1_apply_p_loop_filter(&self, frame: &mut YuvFrame, pq: i32) {
4005        let mb_width = self.width_mb as usize;
4006        let mb_height = self.height_mb as usize;
4007        if mb_width == 0 || mb_height == 0 {
4008            return;
4009        }
4010
4011        for cur_y in 0..mb_height {
4012            for cur_x in 0..mb_width {
4013                // V-loop portion of ff_vc1_p_loop_filter().
4014                if cur_y >= 2 {
4015                    if cur_x >= 1 {
4016                        self.vc1_p_v_loop_filter_mb(frame, cur_y - 2, cur_x - 1, pq);
4017                    }
4018                    if cur_x + 1 == mb_width {
4019                        self.vc1_p_v_loop_filter_mb(frame, cur_y - 2, cur_x, pq);
4020                    }
4021                }
4022                if cur_y + 1 == mb_height {
4023                    if cur_x >= 1 {
4024                        if cur_y >= 1 {
4025                            self.vc1_p_v_loop_filter_mb(frame, cur_y - 1, cur_x - 1, pq);
4026                        }
4027                        self.vc1_p_v_loop_filter_mb(frame, cur_y, cur_x - 1, pq);
4028                    }
4029                    if cur_x + 1 == mb_width {
4030                        if cur_y >= 1 {
4031                            self.vc1_p_v_loop_filter_mb(frame, cur_y - 1, cur_x, pq);
4032                        }
4033                        self.vc1_p_v_loop_filter_mb(frame, cur_y, cur_x, pq);
4034                    }
4035                }
4036
4037                // H-loop portion.  It trails by one additional MB column.
4038                if cur_y >= 2 {
4039                    if cur_x >= 2 {
4040                        self.vc1_p_h_loop_filter_mb(frame, cur_y - 2, cur_x - 2, pq);
4041                    }
4042                    if cur_x + 1 == mb_width {
4043                        if cur_x >= 1 {
4044                            self.vc1_p_h_loop_filter_mb(frame, cur_y - 2, cur_x - 1, pq);
4045                        }
4046                        self.vc1_p_h_loop_filter_mb(frame, cur_y - 2, cur_x, pq);
4047                    }
4048                }
4049                if cur_y + 1 == mb_height {
4050                    if cur_y >= 1 {
4051                        if cur_x >= 2 {
4052                            self.vc1_p_h_loop_filter_mb(frame, cur_y - 1, cur_x - 2, pq);
4053                        }
4054                        if cur_x + 1 == mb_width {
4055                            if cur_x >= 1 {
4056                                self.vc1_p_h_loop_filter_mb(frame, cur_y - 1, cur_x - 1, pq);
4057                            }
4058                            self.vc1_p_h_loop_filter_mb(frame, cur_y - 1, cur_x, pq);
4059                        }
4060                    }
4061                    if cur_x >= 2 {
4062                        self.vc1_p_h_loop_filter_mb(frame, cur_y, cur_x - 2, pq);
4063                    }
4064                    if cur_x + 1 == mb_width {
4065                        if cur_x >= 1 {
4066                            self.vc1_p_h_loop_filter_mb(frame, cur_y, cur_x - 1, pq);
4067                        }
4068                        self.vc1_p_h_loop_filter_mb(frame, cur_y, cur_x, pq);
4069                    }
4070                }
4071            }
4072        }
4073    }
4074
4075    #[inline]
4076    fn vc1_coding_sets(pic: &PictureHeader) -> (usize, usize) {
4077        let intra = match pic.transacfrm2 {
4078            0 => {
4079                if pic.pqindex <= 8 {
4080                    6
4081                } else {
4082                    2
4083                }
4084            }
4085            1 => 0,
4086            _ => 4,
4087        };
4088        let inter = match pic.transacfrm {
4089            0 => {
4090                if pic.pqindex <= 8 {
4091                    7
4092                } else {
4093                    3
4094                }
4095            }
4096            1 => 1,
4097            _ => 5,
4098        };
4099        (intra, inter)
4100    }
4101
4102    #[inline]
4103    fn vc1_read_dc_diff(
4104        &self,
4105        br: &mut BitReader<'_>,
4106        pic: &PictureHeader,
4107        chroma: bool,
4108        quant: i32,
4109    ) -> Result<i32> {
4110        let table = &self.wmv2_dc_vlc[pic.dctab as usize][chroma as usize];
4111        let mut d = table
4112            .decode(br)
4113            .ok_or_else(|| DecoderError::InvalidData("invalid WMV3 DC VLC".into()))?;
4114        if d != 0 {
4115            let m = if quant == 1 || quant == 2 {
4116                3 - quant
4117            } else {
4118                0
4119            };
4120            if d == 119 {
4121                d = br
4122                    .read_bits((8 + m) as u8)
4123                    .ok_or_else(|| DecoderError::InvalidData("truncated WMV3 DC escape".into()))?
4124                    as i32;
4125            } else if m != 0 {
4126                let ext = br.read_bits(m as u8).ok_or_else(|| {
4127                    DecoderError::InvalidData("truncated WMV3 DC extension".into())
4128                })? as i32;
4129                d = (d << m) + ext - ((1 << m) - 1);
4130            }
4131            if br
4132                .read_bit()
4133                .ok_or_else(|| DecoderError::InvalidData("truncated WMV3 DC sign".into()))?
4134            {
4135                d = -d;
4136            }
4137        }
4138        Ok(d)
4139    }
4140
4141    fn vc1_decode_ac_coeff(
4142        &mut self,
4143        br: &mut BitReader<'_>,
4144        set: usize,
4145        pq: i32,
4146        dquantfrm: bool,
4147    ) -> Result<(usize, i32, bool)> {
4148        let set = set.min(7);
4149        let idx = self.vc1_ac[set]
4150            .decode_index(br)
4151            .ok_or_else(|| DecoderError::InvalidData("invalid WMV3 AC VLC".into()))?;
4152        let (run, level, last, sign) = if !self.vc1_ac[set].is_escape(idx) {
4153            let (r, l) = self.vc1_ac[set]
4154                .run_level(idx)
4155                .ok_or_else(|| DecoderError::InvalidData("invalid WMV3 AC symbol".into()))?;
4156            let sign = br
4157                .read_bit()
4158                .ok_or_else(|| DecoderError::InvalidData("truncated WMV3 AC sign".into()))?;
4159            (r as usize, l as i32, self.vc1_ac[set].is_last(idx), sign)
4160        } else {
4161            let escape = vc1_decode210_bits(br)?;
4162            if escape != 2 {
4163                let idx2 = self.vc1_ac[set].decode_index(br).ok_or_else(|| {
4164                    DecoderError::InvalidData("invalid WMV3 escaped AC VLC".into())
4165                })?;
4166                if self.vc1_ac[set].is_escape(idx2) {
4167                    return Err(DecoderError::InvalidData("nested WMV3 AC escape".into()));
4168                }
4169                let (r0, l0) = self.vc1_ac[set].run_level(idx2).ok_or_else(|| {
4170                    DecoderError::InvalidData("invalid WMV3 escaped AC symbol".into())
4171                })?;
4172                let last = self.vc1_ac[set].is_last(idx2);
4173                let (r, l) = if escape == 0 {
4174                    (
4175                        r0 as usize,
4176                        l0 as i32 + self.vc1_ac[set].max_level(last, r0 as usize) as i32,
4177                    )
4178                } else {
4179                    (
4180                        r0 as usize + self.vc1_ac[set].max_run(last, l0 as usize) as usize + 1,
4181                        l0 as i32,
4182                    )
4183                };
4184                let sign = br.read_bit().ok_or_else(|| {
4185                    DecoderError::InvalidData("truncated WMV3 escaped AC sign".into())
4186                })?;
4187                (r, l, last, sign)
4188            } else {
4189                let last = br.read_bit().ok_or_else(|| {
4190                    DecoderError::InvalidData("truncated WMV3 escape3 last".into())
4191                })?;
4192                if self.vc1_esc3_level_length == 0 {
4193                    let ll = if pq < 8 || dquantfrm {
4194                        let n = br.read_bits(3).ok_or_else(|| {
4195                            DecoderError::InvalidData("truncated WMV3 escape3 level length".into())
4196                        })? as u8;
4197                        if n == 0 {
4198                            br.read_bits(2).ok_or_else(|| {
4199                                DecoderError::InvalidData(
4200                                    "truncated WMV3 escape3 extended level length".into(),
4201                                )
4202                            })? as u8
4203                                + 8
4204                        } else {
4205                            n
4206                        }
4207                    } else {
4208                        vc1_read_unary_stop_one(br, 6)? + 2
4209                    };
4210                    self.vc1_esc3_level_length = ll;
4211                    self.vc1_esc3_run_length = 3 + br.read_bits(2).ok_or_else(|| {
4212                        DecoderError::InvalidData("truncated WMV3 escape3 run length".into())
4213                    })? as u8;
4214                }
4215                let r = br
4216                    .read_bits(self.vc1_esc3_run_length)
4217                    .ok_or_else(|| DecoderError::InvalidData("truncated WMV3 escape3 run".into()))?
4218                    as usize;
4219                let sign = br.read_bit().ok_or_else(|| {
4220                    DecoderError::InvalidData("truncated WMV3 escape3 sign".into())
4221                })?;
4222                let l = br.read_bits(self.vc1_esc3_level_length).ok_or_else(|| {
4223                    DecoderError::InvalidData("truncated WMV3 escape3 level".into())
4224                })? as i32;
4225                (r, l, last, sign)
4226            }
4227        };
4228        Ok((run, if sign { -level } else { level }, last))
4229    }
4230
4231    #[inline]
4232    fn vc1_mb_index(&self, r: usize, c: usize) -> Option<usize> {
4233        if r < self.height_mb as usize && c < self.width_mb as usize {
4234            Some(r * self.width_mb as usize + c)
4235        } else {
4236            None
4237        }
4238    }
4239
4240    fn vc1_luma_slot(&self, gx: isize, gy: isize) -> Option<(usize, usize)> {
4241        if gx < 0 || gy < 0 {
4242            return None;
4243        }
4244        let gx = gx as usize;
4245        let gy = gy as usize;
4246        let c = gx / 2;
4247        let r = gy / 2;
4248        let idx = self.vc1_mb_index(r, c)?;
4249        let blk = (gy & 1) * 2 + (gx & 1);
4250        Some((idx, blk))
4251    }
4252
4253    fn vc1_dc_neighbours(
4254        &self,
4255        r: usize,
4256        c: usize,
4257        blk: usize,
4258    ) -> (Option<i32>, Option<i32>, Option<i32>) {
4259        if blk < 4 {
4260            let gx = (c * 2 + (blk & 1)) as isize;
4261            let gy = (r * 2 + (blk >> 1)) as isize;
4262            let get = |x: isize, y: isize| self.vc1_luma_slot(x, y).map(|(i, b)| self.vc1_dc[i][b]);
4263            (get(gx, gy - 1), get(gx - 1, gy - 1), get(gx - 1, gy))
4264        } else {
4265            let get = |rr: isize, cc: isize| -> Option<i32> {
4266                if rr < 0 || cc < 0 {
4267                    return None;
4268                }
4269                self.vc1_mb_index(rr as usize, cc as usize)
4270                    .map(|i| self.vc1_dc[i][blk])
4271            };
4272            (
4273                get(r as isize - 1, c as isize),
4274                get(r as isize - 1, c as isize - 1),
4275                get(r as isize, c as isize - 1),
4276            )
4277        }
4278    }
4279
4280    fn vc1_i_dc_pred(
4281        &self,
4282        r: usize,
4283        c: usize,
4284        blk: usize,
4285        seq: &SequenceHeader,
4286        pq: i32,
4287    ) -> (i32, bool) {
4288        const DCPRED: [i32; 32] = [
4289            -1, 1024, 512, 341, 256, 205, 171, 146, 128, 114, 102, 93, 85, 79, 73, 68, 64, 60, 57,
4290            54, 51, 49, 47, 45, 43, 41, 39, 38, 37, 35, 34, 33,
4291        ];
4292        let (oa, ob, oc) = self.vc1_dc_neighbours(r, c, blk);
4293        let boundary = if pq < 9 || !seq.overlap {
4294            DCPRED[vc1_dc_scale(pq) as usize]
4295        } else {
4296            0
4297        };
4298        let a = oa.unwrap_or(boundary);
4299        let b = ob.unwrap_or(boundary);
4300        let cc = oc.unwrap_or(boundary);
4301        if (a - b).abs() <= (b - cc).abs() {
4302            (cc, true)
4303        } else {
4304            (a, false)
4305        }
4306    }
4307
4308    fn vc1_intra_neighbour(
4309        &self,
4310        r: usize,
4311        c: usize,
4312        blk: usize,
4313        top: bool,
4314    ) -> Option<(usize, usize)> {
4315        if blk < 4 {
4316            let gx = (c * 2 + (blk & 1)) as isize;
4317            let gy = (r * 2 + (blk >> 1)) as isize;
4318            let (x, y) = if top { (gx, gy - 1) } else { (gx - 1, gy) };
4319            self.vc1_luma_slot(x, y)
4320        } else {
4321            let (rr, cc) = if top {
4322                (r as isize - 1, c as isize)
4323            } else {
4324                (r as isize, c as isize - 1)
4325            };
4326            if rr < 0 || cc < 0 {
4327                return None;
4328            }
4329            self.vc1_mb_index(rr as usize, cc as usize)
4330                .map(|i| (i, blk))
4331        }
4332    }
4333
4334    fn vc1_inter_dc_pred(
4335        &self,
4336        r: usize,
4337        c: usize,
4338        blk: usize,
4339        mquant: i32,
4340        _halfqp: bool,
4341    ) -> (i32, bool, bool, bool, i32) {
4342        let cur_idx = self.vc1_mb_index(r, c).unwrap_or(0);
4343        let top_slot = self.vc1_intra_neighbour(r, c, blk, true);
4344        let left_slot = self.vc1_intra_neighbour(r, c, blk, false);
4345        let diag_slot = if blk < 4 {
4346            let gx = (c * 2 + (blk & 1)) as isize;
4347            let gy = (r * 2 + (blk >> 1)) as isize;
4348            self.vc1_luma_slot(gx - 1, gy - 1)
4349        } else if r > 0 && c > 0 {
4350            self.vc1_mb_index(r - 1, c - 1).map(|i| (i, blk))
4351        } else {
4352            None
4353        };
4354        let a_av = top_slot
4355            .map(|(i, b)| self.vc1_intra_blocks[i][b])
4356            .unwrap_or(false);
4357        let c_av = left_slot
4358            .map(|(i, b)| self.vc1_intra_blocks[i][b])
4359            .unwrap_or(false);
4360        let mut a = top_slot
4361            .filter(|(i, b)| self.vc1_intra_blocks[*i][*b])
4362            .map(|(i, b)| self.vc1_dc[i][b])
4363            .unwrap_or(0);
4364        let mut cc = left_slot
4365            .filter(|(i, b)| self.vc1_intra_blocks[*i][*b])
4366            .map(|(i, b)| self.vc1_dc[i][b])
4367            .unwrap_or(0);
4368        let mut b = diag_slot.map(|(i, b)| self.vc1_dc[i][b]).unwrap_or(0);
4369        if a_av && let Some((i, _)) = top_slot {
4370            a = vc1_rescale_dc_pred(a, mquant, self.vc1_qscale[i]);
4371        }
4372        if c_av && let Some((i, _)) = left_slot {
4373            cc = vc1_rescale_dc_pred(cc, mquant, self.vc1_qscale[i]);
4374        }
4375        // ff_vc1_pred_dc rescales B only when both directional predictors
4376        // are available and block 3 is not being decoded.
4377        if a_av
4378            && c_av
4379            && blk != 3
4380            && let Some((i, _)) = diag_slot
4381        {
4382            b = vc1_rescale_dc_pred(b, mquant, self.vc1_qscale[i]);
4383        }
4384        let (pred, left) = if c_av && (!a_av || (a - b).abs() <= (b - cc).abs()) {
4385            (cc, true)
4386        } else if a_av {
4387            (a, false)
4388        } else {
4389            (0, true)
4390        };
4391        let q2 = if left {
4392            left_slot
4393                .map(|(i, _)| self.vc1_qscale[i])
4394                .unwrap_or(self.vc1_qscale[cur_idx])
4395        } else {
4396            top_slot
4397                .map(|(i, _)| self.vc1_qscale[i])
4398                .unwrap_or(self.vc1_qscale[cur_idx])
4399        };
4400        (pred, left, a_av, c_av, q2)
4401    }
4402
4403    fn vc1_decode_intra_coeffs(
4404        &mut self,
4405        br: &mut BitReader<'_>,
4406        pic: &PictureHeader,
4407        seq: &SequenceHeader,
4408        r: usize,
4409        c: usize,
4410        blk: usize,
4411        coded: bool,
4412        mquant: i32,
4413        acpred: bool,
4414        pure_i: bool,
4415        set: usize,
4416    ) -> Result<[i32; 64]> {
4417        let quant = mquant.abs().clamp(1, 31);
4418        let dc_diff = self.vc1_read_dc_diff(br, pic, blk >= 4, quant)?;
4419        let (pred, mut left, a_av, c_av, q2) = if pure_i && seq.profile != Profile::Advanced {
4420            // Simple/Main I pictures use vc1_i_pred_dc(), which supplies the
4421            // profile's outer-boundary DC values rather than treating the
4422            // neighbours as unavailable.
4423            let (p, l) = self.vc1_i_dc_pred(r, c, blk, seq, quant);
4424            (p, l, true, true, mquant)
4425        } else {
4426            // Advanced I/BI pictures use ff_vc1_pred_dc() exactly like intra
4427            // macroblocks in inter pictures. This is required for the real
4428            // neighbour availability rules and per-MB DQuant rescaling.
4429            self.vc1_inter_dc_pred(r, c, blk, mquant, pic.halfqp)
4430        };
4431
4432        // ff_vc1_pred_dc() adjusts AC prediction direction when only one real
4433        // neighbour is available. Simple/Main pure-I uses vc1_i_pred_dc(), so
4434        // its edge handling stays in the legacy branch above.
4435        if !pure_i {
4436            if !a_av {
4437                left = true;
4438            }
4439            if !c_av {
4440                left = false;
4441            }
4442        }
4443        let use_pred = acpred
4444            && if pure_i && seq.profile != Profile::Advanced {
4445                true
4446            } else {
4447                a_av || c_av
4448            };
4449
4450        let dc = pred + dc_diff;
4451        let mbi = self.vc1_mb_index(r, c).unwrap();
4452        self.vc1_dc[mbi][blk] = dc;
4453        self.vc1_intra_blocks[mbi][blk] = true;
4454
4455        let mut coeff = [0i32; 64];
4456        coeff[0] = dc * vc1_dc_scale(quant);
4457
4458        if coded {
4459            // FFmpeg uses two different scan rules here:
4460            // vc1_decode_i_block() (true I/BI pictures) selects zz_8x8[1],
4461            // [2], or [3] according to AC prediction, while
4462            // vc1_decode_intra_block() (intra MB inside P/B) uses zz_8x8[0]
4463            // for progressive Simple/Main profile regardless of ACPRED.
4464            let scan: &[usize; 64] = if pure_i {
4465                // vc1_decode_i_block[_adv] selects the AC-prediction scan from
4466                // the signalled ACPRED bit even when an Advanced edge block
4467                // has no real neighbour and therefore cannot apply the
4468                // predictor itself.
4469                if acpred {
4470                    if left {
4471                        &FF_WMV1_SCANTABLE[3]
4472                    } else {
4473                        &FF_WMV1_SCANTABLE[2]
4474                    }
4475                } else {
4476                    &FF_WMV1_SCANTABLE[1]
4477                }
4478            } else {
4479                &FF_WMV1_SCANTABLE[0]
4480            };
4481
4482            let mut pos = 1usize;
4483            let mut last = false;
4484            while !last && pos < 64 {
4485                let (run, level, l) =
4486                    self.vc1_decode_ac_coeff(br, set, pic.pquant as i32, pic.dquant.enabled)?;
4487                let at = pos.saturating_add(run);
4488                if at > 63 {
4489                    break;
4490                }
4491                let dst = vc1_scan_index(scan, at, seq.uses_transposed_scans());
4492                coeff[dst] = level;
4493                pos = at + 1;
4494                last = l;
4495            }
4496        }
4497
4498        if use_pred {
4499            if left {
4500                // FFmpeg: block[k << left_blk_sh] += ac_val[k].
4501                // left_blk_sh is 3 for the legacy simple IDCT and 0 for the
4502                // transposed RES_FASTTX path.
4503                let mut p = if seq.uses_transposed_scans() {
4504                    self.ac_pred.pred_row(r, c, blk)
4505                } else {
4506                    self.ac_pred.pred_left_col(r, c, blk)
4507                };
4508                if q2 != 0 && q2 != mquant {
4509                    for v in &mut p {
4510                        *v = vc1_rescale_ac_pred(*v, mquant, q2, pic.halfqp);
4511                    }
4512                }
4513                let sh = if seq.uses_transposed_scans() { 0 } else { 3 };
4514                for k in 1..8 {
4515                    coeff[k << sh] += p[k - 1];
4516                }
4517            } else {
4518                // FFmpeg: block[k << top_blk_sh] += ac_val[k + 8].
4519                // top_blk_sh is 0 for the legacy simple IDCT and 3 for the
4520                // transposed RES_FASTTX path.
4521                let mut p = if seq.uses_transposed_scans() {
4522                    self.ac_pred.pred_col(r, c, blk)
4523                } else {
4524                    self.ac_pred.pred_top_row(r, c, blk)
4525                };
4526                if q2 != 0 && q2 != mquant {
4527                    for v in &mut p {
4528                        *v = vc1_rescale_ac_pred(*v, mquant, q2, pic.halfqp);
4529                    }
4530                }
4531                let sh = if seq.uses_transposed_scans() { 3 } else { 0 };
4532                for k in 1..8 {
4533                    coeff[k << sh] += p[k - 1];
4534                }
4535            }
4536        }
4537
4538        // Cache both physical coefficient axes before inverse quantization.
4539        // Which axis represents the left/top predictor is selected above from
4540        // RES_FASTTX, exactly like FFmpeg's left_blk_sh/top_blk_sh.
4541        self.ac_pred.store_row(
4542            r,
4543            c,
4544            blk,
4545            [
4546                coeff[1], coeff[2], coeff[3], coeff[4], coeff[5], coeff[6], coeff[7],
4547            ],
4548        );
4549        self.ac_pred.store_col(
4550            r,
4551            c,
4552            blk,
4553            [
4554                coeff[8], coeff[16], coeff[24], coeff[32], coeff[40], coeff[48], coeff[56],
4555            ],
4556        );
4557
4558        for k in 1..64 {
4559            if coeff[k] != 0 {
4560                coeff[k] = vc1_scale_level(coeff[k], mquant, pic.halfqp, pic.pqual_mode != 0);
4561            }
4562        }
4563        Ok(coeff)
4564    }
4565
4566    fn vc1_coded_block_pred(&mut self, r: usize, c: usize, blk: usize, diff: bool) -> bool {
4567        let bw = self.width_mb as usize * 2;
4568        let x = c * 2 + (blk & 1);
4569        let y = r * 2 + (blk >> 1);
4570        let idx = y * bw + x;
4571        let a = if x > 0 {
4572            self.vc1_coded_block[idx - 1]
4573        } else {
4574            0
4575        };
4576        let b = if x > 0 && y > 0 {
4577            self.vc1_coded_block[idx - bw - 1]
4578        } else {
4579            0
4580        };
4581        let cc = if y > 0 {
4582            self.vc1_coded_block[idx - bw]
4583        } else {
4584            0
4585        };
4586        let pred = if b == cc { a } else { cc };
4587        let v = pred ^ diff as u8;
4588        self.vc1_coded_block[idx] = v;
4589        v != 0
4590    }
4591
4592    fn vc1_read_mvdata(
4593        &self,
4594        br: &mut BitReader<'_>,
4595        pic: &PictureHeader,
4596        quarter_sample: bool,
4597    ) -> Result<Vc1MvData> {
4598        let vlc_bit = br.bits_read();
4599        let sym = self.vc1_mvdata[(pic.mvtab as usize).min(3)]
4600            .decode(br)
4601            .ok_or_else(|| {
4602                DecoderError::InvalidData(format!(
4603                    "invalid WMV3 MVDATA VLC at bit {vlc_bit} (left={})",
4604                    br.bits_left()
4605                ))
4606            })?;
4607        let mut index = 1 + sym;
4608        let has_coeffs = index > 36;
4609        if has_coeffs {
4610            index -= 37;
4611        }
4612        if index == 0 {
4613            return Ok(Vc1MvData {
4614                dx: 0,
4615                dy: 0,
4616                intra: false,
4617                has_coeffs,
4618            });
4619        }
4620
4621        let (_, _, _, _, kx, ky) = self.vc1_mv_params(pic);
4622        if index == 35 {
4623            let xb = (kx - 1 + quarter_sample as i32) as u8;
4624            let yb = (ky - 1 + quarter_sample as i32) as u8;
4625            let xbit = br.bits_read();
4626            let dx = br.read_bits(xb).ok_or_else(|| {
4627                DecoderError::InvalidData(format!(
4628                    "truncated WMV3 MVDATA x escape at bit {xbit}: need {xb}, left={}",
4629                    br.bits_left()
4630                ))
4631            })? as i32;
4632            let ybit = br.bits_read();
4633            let dy = br.read_bits(yb).ok_or_else(|| {
4634                DecoderError::InvalidData(format!(
4635                    "truncated WMV3 MVDATA y escape at bit {ybit}: need {yb}, left={}",
4636                    br.bits_left()
4637                ))
4638            })? as i32;
4639            return Ok(Vc1MvData {
4640                dx,
4641                dy,
4642                intra: false,
4643                has_coeffs,
4644            });
4645        }
4646        if index == 36 {
4647            return Ok(Vc1MvData {
4648                dx: 0,
4649                dy: 0,
4650                intra: true,
4651                has_coeffs,
4652            });
4653        }
4654
4655        const SIZE: [i32; 6] = [0, 2, 3, 4, 5, 8];
4656        const OFF: [i32; 6] = [0, 1, 3, 7, 15, 31];
4657        let dec = |br: &mut BitReader<'_>, g: i32, axis: char| -> Result<i32> {
4658            let gi = g as usize;
4659            let mut out = OFF[gi];
4660            let n = SIZE[gi] - ((!quarter_sample && gi == 5) as i32);
4661            if n > 0 {
4662                let bit = br.bits_read();
4663                let v = br.read_bits(n as u8).ok_or_else(|| {
4664                    DecoderError::InvalidData(format!(
4665                        "truncated WMV3 MVDATA {axis} component at bit {bit}: index={index} group={gi} need={n}, left={}",
4666                        br.bits_left()
4667                    ))
4668                })? as i32;
4669                let sign = -(v & 1);
4670                out = (sign ^ ((v >> 1) + out)) - sign;
4671            }
4672            Ok(out)
4673        };
4674        Ok(Vc1MvData {
4675            dx: dec(br, index % 6, 'x')?,
4676            dy: dec(br, index / 6, 'y')?,
4677            intra: false,
4678            has_coeffs,
4679        })
4680    }
4681
4682    #[inline]
4683    fn vc1_mv_params(&self, pic: &PictureHeader) -> (bool, bool, i32, i32, i32, i32) {
4684        let mode = if pic.mv_mode == MvMode::IntensityComp {
4685            pic.mv_mode2
4686        } else {
4687            pic.mv_mode
4688        };
4689        let quarter = !matches!(mode, MvMode::OneMvHpel | MvMode::OneMvHpelBilin);
4690        let mspel = mode != MvMode::OneMvHpelBilin;
4691
4692        // FFmpeg ff_vc1_parse_frame_header():
4693        //   k_x = mvrange + 9 + (mvrange >> 1)
4694        //   k_y = mvrange + 8
4695        // Thus MVRANGE=0 is 9/8, not 10/9.  The old values make an escape
4696        // MVDATA component consume one bit too many and desynchronise the MB layer.
4697        let r = pic.mvrange as i32;
4698        let mut kx = r + 9 + (r >> 1);
4699        let mut ky = r + 8;
4700        if self.width < 120 {
4701            kx = 9;
4702        }
4703        if self.height < 60 {
4704            ky = 8;
4705        }
4706        (quarter, mspel, 1 << (kx - 1), 1 << (ky - 1), kx, ky)
4707    }
4708
4709    fn vc1_predict_p_mv(
4710        &mut self,
4711        br: &mut BitReader<'_>,
4712        pic: &PictureHeader,
4713        seq: &SequenceHeader,
4714        r: usize,
4715        c: usize,
4716        n: usize,
4717        dmv: (i32, i32),
4718        mv1: bool,
4719        intra: bool,
4720    ) -> Result<(i32, i32)> {
4721        let (quarter, _, rx, ry, _, _) = self.vc1_mv_params(pic);
4722        let mut dx = dmv.0;
4723        let mut dy = dmv.1;
4724        if !quarter {
4725            dx *= 2;
4726            dy *= 2;
4727        }
4728
4729        let idx = self.vc1_mb_index(r, c).unwrap();
4730        if intra {
4731            self.vc1_mv4[idx][n] = (0, 0);
4732            if mv1 {
4733                self.vc1_mv4[idx] = [(0, 0); 4];
4734                self.vc1_current_mvs[idx] = (0, 0);
4735            }
4736            return Ok((0, 0));
4737        }
4738
4739        let gx = (c * 2 + (n & 1)) as isize;
4740        let gy = (r * 2 + (n >> 1)) as isize;
4741
4742        // ff_vc1_pred_mv() addresses motion_val through FFmpeg's padded
4743        // b8 grid, whose stride is 2 * mb_width + 1.  Do not apply its raw
4744        // pointer offsets directly to our compact 2 * mb_width grid: the
4745        // first-column 4MV block-0 special case deliberately crosses the
4746        // padding cell into the previous b8 row.  Translate the FFmpeg
4747        // address back into a compact luma slot instead.
4748        let b8_stride = self.width_mb as isize * 2 + 1;
4749        let ff_xy = b8_stride * gy + gx - 2;
4750        let slot_from_ff_index = |ff_index: isize| -> Option<(usize, usize)> {
4751            let shifted = ff_index + 2;
4752            let yy = shifted.div_euclid(b8_stride);
4753            let xx = shifted.rem_euclid(b8_stride);
4754            if yy < 0 || xx >= self.width_mb as isize * 2 {
4755                None
4756            } else {
4757                self.vc1_luma_slot(xx, yy)
4758            }
4759        };
4760
4761        // A = above, B = the FFmpeg `off` candidate, C = left.  Keep the
4762        // slots as well as the vectors because VC-1 hybrid prediction tests
4763        // the exact A/C block's intra state.
4764        let a_slot = slot_from_ff_index(ff_xy - b8_stride);
4765        let c_slot = slot_from_ff_index(ff_xy - 1);
4766        let boff: isize = if mv1 {
4767            if c + 1 >= self.width_mb as usize {
4768                -1
4769            } else {
4770                2
4771            }
4772        } else {
4773            match n {
4774                0 => {
4775                    if seq.res_rtm_flag {
4776                        if c > 0 { -1 } else { 1 }
4777                    } else if c > 0 {
4778                        -1
4779                    } else {
4780                        // Exact FFmpeg expression:
4781                        //   2 * mb_width - b8_stride - 1
4782                        2 * self.width_mb as isize - b8_stride - 1
4783                    }
4784                }
4785                1 => {
4786                    if c + 1 >= self.width_mb as usize {
4787                        -1
4788                    } else {
4789                        1
4790                    }
4791                }
4792                2 => 1,
4793                _ => -1,
4794            }
4795        };
4796        let b_slot = slot_from_ff_index(ff_xy - b8_stride + boff);
4797
4798        let a = a_slot.map(|(mi, bi)| self.vc1_mv4[mi][bi]);
4799        let b = b_slot.map(|(mi, bi)| self.vc1_mv4[mi][bi]);
4800        let cc = c_slot.map(|(mi, bi)| self.vc1_mv4[mi][bi]);
4801        let vals = [
4802            a.unwrap_or((0, 0)),
4803            b.unwrap_or((0, 0)),
4804            cc.unwrap_or((0, 0)),
4805        ];
4806        let valid = (a.is_some() as usize) + (b.is_some() as usize) + (cc.is_some() as usize);
4807        let (mut px, mut py) = if valid > 1 {
4808            (
4809                median3(vals[0].0, vals[1].0, vals[2].0),
4810                median3(vals[0].1, vals[1].1, vals[2].1),
4811            )
4812        } else if let Some(v) = a {
4813            v
4814        } else if let Some(v) = cc {
4815            v
4816        } else {
4817            b.unwrap_or((0, 0))
4818        };
4819
4820        // Pullback, SMPTE 421M 8.3.5.3.4.
4821        let lim = if mv1 { -60 } else { -28 };
4822        let qx = ((c as i32) << 6) + if !mv1 && matches!(n, 1 | 3) { 32 } else { 0 };
4823        let qy = ((r as i32) << 6) + if !mv1 && matches!(n, 2 | 3) { 32 } else { 0 };
4824        let xx = ((self.width_mb as i32) << 6) - 4;
4825        let yy = ((self.height_mb as i32) << 6) - 4;
4826        if qx + px < lim {
4827            px = lim - qx;
4828        }
4829        if qy + py < lim {
4830            py = lim - qy;
4831        }
4832        if qx + px > xx {
4833            px = xx - qx;
4834        }
4835        if qy + py > yy {
4836            py = yy - qy;
4837        }
4838
4839        // Hybrid prediction, SMPTE 421M 8.3.5.3.5 / FFmpeg ff_vc1_pred_mv().
4840        // This is a bitstream operation, not merely a predictor choice: both A
4841        // and C tests can independently require consumption of HYBRIDPRED.
4842        // The previous port only implemented the A test. If A did not cross
4843        // the threshold but C did, one bit was left unread and every following
4844        // macroblock was shifted by one bit, eventually ending in truncated
4845        // MVDATA.
4846        if let (Some(av), Some(cv)) = (a, cc) {
4847            let a_intra = a_slot
4848                .map(|(mi, bi)| self.vc1_intra_blocks[mi][bi])
4849                .unwrap_or(false);
4850            let c_intra = c_slot
4851                .map(|(mi, bi)| self.vc1_intra_blocks[mi][bi])
4852                .unwrap_or(false);
4853
4854            let mut sum = if a_intra {
4855                px.abs() + py.abs()
4856            } else {
4857                (px - av.0).abs() + (py - av.1).abs()
4858            };
4859
4860            let mut need_hybrid_bit = sum > 32;
4861            if !need_hybrid_bit {
4862                sum = if c_intra {
4863                    px.abs() + py.abs()
4864                } else {
4865                    (px - cv.0).abs() + (py - cv.1).abs()
4866                };
4867                need_hybrid_bit = sum > 32;
4868            }
4869
4870            if need_hybrid_bit {
4871                if br
4872                    .read_bit()
4873                    .ok_or_else(|| DecoderError::InvalidData("truncated WMV3 HYBRIDPRED".into()))?
4874                {
4875                    (px, py) = av;
4876                } else {
4877                    (px, py) = cv;
4878                }
4879            }
4880        }
4881
4882        let mv = (
4883            ((px + dx + rx) & ((rx << 1) - 1)) - rx,
4884            ((py + dy + ry) & ((ry << 1) - 1)) - ry,
4885        );
4886        self.vc1_mv4[idx][n] = mv;
4887        if mv1 {
4888            self.vc1_mv4[idx] = [mv; 4];
4889            self.vc1_current_mvs[idx] = mv;
4890        } else if n == 0 {
4891            self.vc1_current_mvs[idx] = mv;
4892        }
4893        Ok(mv)
4894    }
4895
4896    fn vc1_decode_p_residual(
4897        &mut self,
4898        br: &mut BitReader<'_>,
4899        pic: &PictureHeader,
4900        seq: &SequenceHeader,
4901        mquant: i32,
4902        ttmb: i32,
4903        first_block: bool,
4904    ) -> Result<([i32; 64], u8, u8)> {
4905        let tt_index = ((pic.pquant > 4) as usize) + ((pic.pquant > 12) as usize);
4906        let mut tt = if ttmb < 0 {
4907            let sym = self.vc1_ttblk[tt_index]
4908                .decode(br)
4909                .ok_or_else(|| DecoderError::InvalidData("invalid WMV3 TTBLK".into()))?
4910                as usize;
4911            TTBLK_TO_TT[tt_index][sym.min(7)]
4912        } else {
4913            (ttmb as u8) & 7
4914        };
4915
4916        let mut sub = 0u8;
4917        if tt == TT_4X4 {
4918            let sym = self.vc1_subblkpat[tt_index]
4919                .decode(br)
4920                .ok_or_else(|| DecoderError::InvalidData("invalid WMV3 SUBBLKPAT".into()))?
4921                as u8;
4922            sub = (!(sym.wrapping_add(1))) & 0x0f;
4923        }
4924        if tt != TT_8X8
4925            && tt != TT_4X4
4926            && ((pic.ttmbf || (ttmb >= 0 && ((ttmb as u8) & 8) != 0 && !first_block))
4927                || (!seq.res_rtm_flag && !first_block))
4928        {
4929            sub = vc1_decode012_bits(br)?;
4930            if sub != 0 {
4931                sub ^= 3;
4932            }
4933            if matches!(tt, TT_8X4_TOP | TT_8X4_BOTTOM) {
4934                tt = TT_8X4;
4935            }
4936            if matches!(tt, TT_4X8_LEFT | TT_4X8_RIGHT) {
4937                tt = TT_4X8;
4938            }
4939        }
4940        if tt == TT_8X4_TOP || tt == TT_8X4_BOTTOM {
4941            sub = 2 - (tt == TT_8X4_TOP) as u8;
4942            tt = TT_8X4;
4943        }
4944        if tt == TT_4X8_LEFT || tt == TT_4X8_RIGHT {
4945            sub = 2 - (tt == TT_4X8_LEFT) as u8;
4946            tt = TT_4X8;
4947        }
4948
4949        let (_, set) = Self::vc1_coding_sets(pic);
4950        let mut out = [0i32; 64];
4951        let mut decode_part = |this: &mut Self,
4952                               scan: &[usize],
4953                               base: usize,
4954                               limit: usize,
4955                               already_last: bool,
4956                               transpose_scan: bool|
4957         -> Result<()> {
4958            if already_last {
4959                return Ok(());
4960            }
4961            let mut pos = 0usize;
4962            let mut last = false;
4963            while !last && pos < limit {
4964                let (run, lev, l) =
4965                    this.vc1_decode_ac_coeff(br, set, pic.pquant as i32, pic.dquant.enabled)?;
4966                let at = pos.saturating_add(run);
4967                if at >= limit {
4968                    break;
4969                }
4970                let scan_index = if transpose_scan {
4971                    vc1_transpose_scan_index(scan[at])
4972                } else {
4973                    scan[at]
4974                };
4975                let idx = base + scan_index;
4976                if idx < 64 {
4977                    out[idx] = vc1_scale_level(lev, mquant, pic.halfqp, pic.pqual_mode != 0);
4978                }
4979                pos = at + 1;
4980                last = l;
4981            }
4982            Ok(())
4983        };
4984
4985        let pat = match tt {
4986            TT_8X8 => {
4987                decode_part(
4988                    self,
4989                    &FF_WMV1_SCANTABLE[0],
4990                    0,
4991                    64,
4992                    false,
4993                    seq.uses_transposed_scans(),
4994                )?;
4995                0x0f
4996            }
4997            TT_4X4 => {
4998                for j in 0..4usize {
4999                    let base = (j & 1) * 4 + (j & 2) * 16;
5000                    decode_part(
5001                        self,
5002                        &VC1_ZZ_4X4,
5003                        base,
5004                        16,
5005                        (sub & (1 << (3 - j))) != 0,
5006                        false,
5007                    )?;
5008                }
5009                (!sub) & 0x0f
5010            }
5011            TT_8X4 => {
5012                let scan: &[usize] = if seq.profile == Profile::Advanced {
5013                    &VC1_ZZ_8X4
5014                } else {
5015                    &FF_WMV2_SCANTABLE_A
5016                };
5017                for j in 0..2usize {
5018                    decode_part(self, scan, j * 32, 32, (sub & (1 << (1 - j))) != 0, false)?;
5019                }
5020                (!((sub & 2) * 6 + (sub & 1) * 3)) & 0x0f
5021            }
5022            TT_4X8 => {
5023                let scan: &[usize] = if seq.profile == Profile::Advanced {
5024                    &VC1_ZZ_4X8
5025                } else {
5026                    &FF_WMV2_SCANTABLE_B
5027                };
5028                for j in 0..2usize {
5029                    decode_part(self, scan, j * 4, 32, (sub & (1 << (1 - j))) != 0, false)?;
5030                }
5031                (!(sub * 5)) & 0x0f
5032            }
5033            _ => 0,
5034        };
5035
5036        apply_wmv3_idct(&mut out, tt, seq.uses_transposed_scans());
5037        Ok((out, tt, pat))
5038    }
5039
5040    fn vc1_mc_single(
5041        &self,
5042        frame: &mut YuvFrame,
5043        r: usize,
5044        c: usize,
5045        reference: &YuvFrame,
5046        mv: (i32, i32),
5047        pic: &PictureHeader,
5048        seq: &SequenceHeader,
5049        block: Option<usize>,
5050        interpolated_next: bool,
5051    ) {
5052        let (quarter, mspel, _, _, _, _) = self.vc1_mv_params(pic);
5053        let mx = mv.0;
5054        let my = mv.1;
5055        if !quarter {
5056            // Motion vectors are already stored internally in qpel units by
5057            // vc1_predict_p_mv(). Nothing to rescale here.
5058        }
5059
5060        let (bx, by, bw, bh) = if let Some(n) = block {
5061            (c * 16 + (n & 1) * 8, r * 16 + (n >> 1) * 8, 8usize, 8usize)
5062        } else {
5063            (c * 16, r * 16, 16usize, 16usize)
5064        };
5065
5066        // The caller prepares picture-level intensity compensation once.
5067        // Motion compensation itself must never rebuild a full reference frame
5068        // per macroblock.
5069        let src_y = reference.y.as_slice();
5070
5071        let sx = bx as i32 + (mx >> 2);
5072        let sy = by as i32 + (my >> 2);
5073        let fx = mx & 3;
5074        let fy = my & 3;
5075        // A macroblock prediction is at most 16x16. Avoid one heap
5076        // allocation for every 1MV/4MV luma prediction.
5077        let mut tmp_storage = [0u8; 16 * 16];
5078        let tmp = &mut tmp_storage[..bw * bh];
5079        if mspel {
5080            vc1_mspel_block(
5081                tmp,
5082                bw,
5083                src_y,
5084                self.width as usize,
5085                self.width as usize,
5086                self.height as usize,
5087                sx,
5088                sy,
5089                bw,
5090                bh,
5091                fx,
5092                fy,
5093                self.vc1_rnd,
5094            );
5095        } else {
5096            vc1_bilinear_qpel_block(
5097                tmp,
5098                bw,
5099                src_y,
5100                self.width as usize,
5101                self.width as usize,
5102                self.height as usize,
5103                sx,
5104                sy,
5105                bw,
5106                bh,
5107                fx & 2,
5108                fy & 2,
5109                4,
5110                self.vc1_rnd,
5111            );
5112        }
5113        for y in 0..bh {
5114            let dy = by + y;
5115            if dy >= self.height as usize {
5116                break;
5117            }
5118            for x in 0..bw {
5119                let dx = bx + x;
5120                if dx >= self.width as usize {
5121                    break;
5122                }
5123                frame.y[dy * self.width as usize + dx] = tmp[y * bw + x];
5124            }
5125        }
5126
5127        if block.is_some() {
5128            return;
5129        }
5130
5131        self.vc1_mc_chroma(frame, r, c, reference, mv, seq, interpolated_next);
5132    }
5133
5134    fn vc1_mc_chroma(
5135        &self,
5136        frame: &mut YuvFrame,
5137        r: usize,
5138        c: usize,
5139        reference: &YuvFrame,
5140        mv: (i32, i32),
5141        seq: &SequenceHeader,
5142        interpolated_next: bool,
5143    ) {
5144        let mx = mv.0;
5145        let my = mv.1;
5146        let cw = self.width as usize / 2;
5147        let ch = self.height as usize / 2;
5148        if cw == 0 || ch == 0 {
5149            return;
5150        }
5151        let mut uvmx = (mx + ((mx & 3) == 3) as i32) >> 1;
5152        let mut uvmy = (my + ((my & 3) == 3) as i32) >> 1;
5153        if seq.fastuvmc {
5154            if interpolated_next {
5155                // ff_vc1_interp_mc(): the backward/next predictor of an
5156                // interpolated B macroblock uses the opposite FASTUVMC
5157                // adjustment from ff_vc1_mc_1mv().  Reusing the forward rule
5158                // shifts U/V by one chroma quarter-pel and appears as green or
5159                // magenta motion trails.
5160                uvmx += if uvmx < 0 { -(uvmx & 1) } else { uvmx & 1 };
5161                uvmy += if uvmy < 0 { -(uvmy & 1) } else { uvmy & 1 };
5162            } else {
5163                uvmx += if uvmx < 0 { uvmx & 1 } else { -(uvmx & 1) };
5164                uvmy += if uvmy < 0 { uvmy & 1 } else { -(uvmy & 1) };
5165            }
5166        }
5167        let csx = (c * 8) as i32 + (uvmx >> 2);
5168        let csy = (r * 8) as i32 + (uvmy >> 2);
5169        let cfx = (uvmx & 3) << 1;
5170        let cfy = (uvmy & 3) << 1;
5171
5172        let cb = reference.cb.as_slice();
5173        let cr = reference.cr.as_slice();
5174        let mut tb = [0u8; 64];
5175        let mut tr = [0u8; 64];
5176        vc1_bilinear_qpel_block(
5177            &mut tb,
5178            8,
5179            cb,
5180            cw,
5181            cw,
5182            ch,
5183            csx,
5184            csy,
5185            8,
5186            8,
5187            cfx,
5188            cfy,
5189            8,
5190            self.vc1_rnd,
5191        );
5192        vc1_bilinear_qpel_block(
5193            &mut tr,
5194            8,
5195            cr,
5196            cw,
5197            cw,
5198            ch,
5199            csx,
5200            csy,
5201            8,
5202            8,
5203            cfx,
5204            cfy,
5205            8,
5206            self.vc1_rnd,
5207        );
5208        for y in 0..8 {
5209            for x in 0..8 {
5210                let dx = c * 8 + x;
5211                let dy = r * 8 + y;
5212                if dx < cw && dy < ch {
5213                    frame.cb[dy * cw + dx] = tb[y * 8 + x];
5214                    frame.cr[dy * cw + dx] = tr[y * 8 + x];
5215                }
5216            }
5217        }
5218    }
5219
5220    fn vc1_mc_4mv_chroma(
5221        &self,
5222        frame: &mut YuvFrame,
5223        r: usize,
5224        c: usize,
5225        reference: &YuvFrame,
5226        mvs: [(i32, i32); 4],
5227        intra: [bool; 4],
5228        pic: &PictureHeader,
5229        seq: &SequenceHeader,
5230    ) -> (i32, i32) {
5231        // VC-1 derives a single chroma MV from the non-intra luma blocks.
5232        // This follows FFmpeg get_chroma_mv(): median of 4, median of the
5233        // three valid vectors, or the arithmetic mean of the two valid ones.
5234        let mut valid = [(0i32, 0i32); 4];
5235        let mut count = 0usize;
5236        for i in 0..4 {
5237            if !intra[i] {
5238                valid[count] = mvs[i];
5239                count += 1;
5240            }
5241        }
5242        if count < 2 {
5243            // get_chroma_mv() returns zero for 0/1 valid luma MVs; chroma is
5244            // not motion compensated for this macroblock.
5245            for i in 0..4 {
5246                if !intra[i] {
5247                    self.vc1_mc_single(frame, r, c, reference, mvs[i], pic, seq, Some(i), false);
5248                }
5249            }
5250            return (0, 0);
5251        }
5252        #[inline]
5253        fn median4(a: i32, b: i32, c: i32, d: i32) -> i32 {
5254            if a < b {
5255                if c < d {
5256                    (b.min(d) + a.max(c)) / 2
5257                } else {
5258                    (b.min(c) + a.max(d)) / 2
5259                }
5260            } else if c < d {
5261                (a.min(d) + b.max(c)) / 2
5262            } else {
5263                (a.min(c) + b.max(d)) / 2
5264            }
5265        }
5266        let mv = match count {
5267            2 => ((valid[0].0 + valid[1].0) / 2, (valid[0].1 + valid[1].1) / 2),
5268            3 => (
5269                median3(valid[0].0, valid[1].0, valid[2].0),
5270                median3(valid[0].1, valid[1].1, valid[2].1),
5271            ),
5272            _ => (
5273                median4(valid[0].0, valid[1].0, valid[2].0, valid[3].0),
5274                median4(valid[0].1, valid[1].1, valid[2].1, valid[3].1),
5275            ),
5276        };
5277        // 4MV derives one chroma MV, but it must not run a fake 16x16 luma
5278        // prediction just to obtain U/V. The old path did that and then
5279        // restored all four 8x8 luma blocks, nearly doubling MC work.
5280        self.vc1_mc_chroma(frame, r, c, reference, mv, seq, false);
5281        // Now generate the four independently compensated luma blocks once.
5282        for i in 0..4 {
5283            if !intra[i] {
5284                self.vc1_mc_single(frame, r, c, reference, mvs[i], pic, seq, Some(i), false);
5285            }
5286        }
5287        vc1_loop_chroma_mv(mv)
5288    }
5289
5290    fn vc1_b_predict(
5291        &self,
5292        r: usize,
5293        c: usize,
5294        dmv: [(i32, i32); 2],
5295        direct: bool,
5296        mode: u8,
5297        intra: bool,
5298        pic: &PictureHeader,
5299        seq: &SequenceHeader,
5300        fwd_hist: &[(i32, i32)],
5301        bwd_hist: &[(i32, i32)],
5302    ) -> [(i32, i32); 2] {
5303        let (quarter, _, rx, ry, _, _) = self.vc1_mv_params(pic);
5304        let idx = r * self.width_mb as usize + c;
5305        if intra {
5306            return [(0, 0); 2];
5307        }
5308
5309        // ff_vc1_pred_b_mv() always begins with the direct-mode vectors
5310        // derived from the future anchor.  A non-direct MB only replaces the
5311        // direction(s) selected by BMVTYPE; the inactive direction remains
5312        // this scaled anchor vector and is still stored in B-picture MV
5313        // history for later predictors.
5314        let base = self
5315            .vc1_bwd_anchor_mvs
5316            .as_ref()
5317            .and_then(|v| v.get(idx))
5318            .copied()
5319            .unwrap_or((0, 0));
5320        let mut out = [
5321            (
5322                vc1_scale_b_mv(base.0, pic.bfrac_num, pic.bfrac_den, false, quarter),
5323                vc1_scale_b_mv(base.1, pic.bfrac_num, pic.bfrac_den, false, quarter),
5324            ),
5325            (
5326                vc1_scale_b_mv(base.0, pic.bfrac_num, pic.bfrac_den, true, quarter),
5327                vc1_scale_b_mv(base.1, pic.bfrac_num, pic.bfrac_den, true, quarter),
5328            ),
5329        ];
5330
5331        // Direct vectors use the progressive 1-MV qpel pullback (8.4.5.4):
5332        // [-60-qx, X-qx] / [-60-qy, Y-qy].  The previous port returned the
5333        // scaled vectors without this clipping.
5334        let qx6 = (c as i32) << 6;
5335        let qy6 = (r as i32) << 6;
5336        let x6 = ((self.width_mb as i32) << 6) - 4;
5337        let y6 = ((self.height_mb as i32) << 6) - 4;
5338        for mv in &mut out {
5339            mv.0 = mv.0.clamp(-60 - qx6, x6 - qx6);
5340            mv.1 = mv.1.clamp(-60 - qy6, y6 - qy6);
5341        }
5342        if direct {
5343            return out;
5344        }
5345
5346        let pred = |hist: &[(i32, i32)]| -> (i32, i32) {
5347            let w = self.width_mb as usize;
5348            if r > 0 {
5349                let a = hist[idx - w];
5350                if w == 1 {
5351                    return a;
5352                }
5353                let b = if c + 1 < w {
5354                    hist[idx - w + 1]
5355                } else {
5356                    hist[idx - w - 1]
5357                };
5358                // ff_vc1_pred_b_mv() sets C to a real zero vector on the
5359                // first macroblock column; it does not mark C unavailable.
5360                // That zero must therefore participate in median(A,B,C).
5361                let cc = if c > 0 { hist[idx - 1] } else { (0, 0) };
5362                (median3(a.0, b.0, cc.0), median3(a.1, b.1, cc.1))
5363            } else if c > 0 {
5364                hist[idx - 1]
5365            } else {
5366                (0, 0)
5367            }
5368        };
5369
5370        // For progressive Main Profile B pictures, only the direction(s)
5371        // selected by BMVTYPE are re-predicted.  This matches the two guarded
5372        // branches in ff_vc1_pred_b_mv().
5373        for dir in 0..2 {
5374            let active =
5375                (dir == 0 && (mode == 0 || mode == 2)) || (dir == 1 && (mode == 1 || mode == 2));
5376            if !active {
5377                continue;
5378            }
5379            let p = if dir == 0 {
5380                pred(fwd_hist)
5381            } else {
5382                pred(bwd_hist)
5383            };
5384            let (mut dx, mut dy) = dmv[dir];
5385            if !quarter {
5386                dx *= 2;
5387                dy *= 2;
5388            }
5389
5390            // ff_vc1_pred_b_mv(): Advanced Profile uses the 64-unit
5391            // macroblock coordinate domain (sh=6, MV=-60); older profiles use
5392            // sh=5 / MV=-28 for non-direct B prediction.
5393            let sh = if seq.profile == Profile::Advanced {
5394                6
5395            } else {
5396                5
5397            };
5398            let lim = 4 - (1 << sh);
5399            let qx = (c as i32) << sh;
5400            let qy = (r as i32) << sh;
5401            let xx = ((self.width_mb as i32) << sh) - 4;
5402            let yy = ((self.height_mb as i32) << sh) - 4;
5403            let mut px = p.0;
5404            let mut py = p.1;
5405            if qx + px < lim {
5406                px = lim - qx;
5407            }
5408            if qy + py < lim {
5409                py = lim - qy;
5410            }
5411            if qx + px > xx {
5412                px = xx - qx;
5413            }
5414            if qy + py > yy {
5415                py = yy - qy;
5416            }
5417            out[dir] = (
5418                ((px + dx + rx) & ((rx << 1) - 1)) - rx,
5419                ((py + dy + ry) & ((ry << 1) - 1)) - ry,
5420            );
5421        }
5422        out
5423    }
5424
5425    // ─── Intra frame ─────────────────────────────────────────────────────────
5426
5427    fn decode_intra(
5428        &mut self,
5429        payload: &[u8],
5430        pic: &PictureHeader,
5431        seq: &SequenceHeader,
5432        _frame: &mut YuvFrame,
5433    ) -> Result<()> {
5434        let mut br = BitReader::new_at(payload, pic.header_bits);
5435        self.vc1_reset_picture_state();
5436        let (intra_set, chroma_set) = Self::vc1_coding_sets(pic);
5437        let mb_w = self.width_mb as usize;
5438        let mb_h = self.height_mb as usize;
5439        let advanced = seq.profile == Profile::Advanced;
5440
5441        if advanced && !pic.overflags_raw {
5442            if let Some(ref flags) = pic.overflags_plane {
5443                let n = self.vc1_overflags.len().min(flags.len());
5444                self.vc1_overflags[..n].copy_from_slice(&flags[..n]);
5445            }
5446        }
5447
5448        for r in 0..mb_h {
5449            for c in 0..mb_w {
5450                let cbp = self
5451                    .wmv2_mb_i_vlc
5452                    .decode(&mut br)
5453                    .ok_or_else(|| DecoderError::InvalidData("invalid WMV3 I CBPCY".into()))?
5454                    as u8;
5455                let mbi = r * mb_w + c;
5456
5457                let acpred = if advanced {
5458                    if pic.acpred_raw {
5459                        br.read_bit().ok_or_else(|| {
5460                            DecoderError::InvalidData(
5461                                "truncated VC-1 Advanced raw ACPRED bit".into(),
5462                            )
5463                        })?
5464                    } else {
5465                        pic.acpred_plane
5466                            .as_ref()
5467                            .and_then(|plane| plane.get(mbi))
5468                            .copied()
5469                            .unwrap_or(0)
5470                            != 0
5471                    }
5472                } else {
5473                    br.read_bit()
5474                        .ok_or_else(|| DecoderError::InvalidData("truncated WMV3 ACPRED".into()))?
5475                };
5476
5477                if advanced && pic.condover == 2 && pic.overflags_raw {
5478                    self.vc1_overflags[mbi] = br.read_bit().ok_or_else(|| {
5479                        DecoderError::InvalidData(
5480                            "truncated VC-1 Advanced raw OVERFLAGS bit".into(),
5481                        )
5482                    })? as u8;
5483                }
5484
5485                // Advanced I/BI pictures use the same GET_MQUANT() macroblock
5486                // syntax as FFmpeg's vc1_decode_i_blocks_adv(). Simple/Main I
5487                // pictures keep the picture quantizer throughout the frame.
5488                let mquant = if advanced {
5489                    read_mquant(
5490                        &mut br,
5491                        &pic.dquant,
5492                        pic.pquant as i32,
5493                        c as u32,
5494                        r as u32,
5495                        self.width_mb,
5496                        self.height_mb,
5497                    )?
5498                } else {
5499                    pic.pquant as i32
5500                };
5501                self.vc1_qscale[mbi] = mquant;
5502
5503                for blk in 0..6usize {
5504                    let mut coded = ((cbp >> (5 - blk)) & 1) != 0;
5505                    if blk < 4 {
5506                        coded = self.vc1_coded_block_pred(r, c, blk, coded);
5507                    }
5508                    let mut coeff = self.vc1_decode_intra_coeffs(
5509                        &mut br,
5510                        pic,
5511                        seq,
5512                        r,
5513                        c,
5514                        blk,
5515                        coded,
5516                        mquant,
5517                        acpred,
5518                        true,
5519                        if blk < 4 { intra_set } else { chroma_set },
5520                    )?;
5521                    apply_wmv3_idct(&mut coeff, TT_8X8, seq.uses_transposed_scans());
5522                    let ri = vc1_recon_index(mb_w, r, c, blk);
5523                    self.vc1_recon_blocks[ri] = coeff;
5524                    self.vc1_intra_blocks[mbi][blk] = true;
5525                    self.vc1_block_cbp[mbi][blk] = 0x0f;
5526                }
5527            }
5528        }
5529        Ok(())
5530    }
5531
5532    fn decode_p(
5533        &mut self,
5534        payload: &[u8],
5535        pic: &PictureHeader,
5536        seq: &SequenceHeader,
5537        frame: &mut YuvFrame,
5538    ) -> Result<()> {
5539        let mut reference = match self.ref_frame.clone() {
5540            Some(v) => v,
5541            None => return Ok(()),
5542        };
5543
5544        // FFmpeg vc1_mc.c applies range reduction to the reference samples
5545        // when the *current* picture has RANGEREDFRM set.  It never mutates
5546        // the stored reference frame.  The operation precedes intensity
5547        // compensation.
5548        if pic.rangeredfrm {
5549            vc1_range_reduce_reference(&mut reference);
5550        }
5551
5552        // Intensity compensation is picture-level state.  Build the transformed
5553        // reference once for this P picture and reuse it for every macroblock.
5554        if pic.mv_mode == MvMode::IntensityComp {
5555            vc1_apply_ic_reference(&mut reference, pic.lumscale, pic.lumshift);
5556        }
5557
5558        let mut br = BitReader::new_at(payload, pic.header_bits);
5559        self.vc1_reset_picture_state();
5560        let (intra_set, _) = Self::vc1_coding_sets(pic);
5561        let (quarter, _, _, _, _, _) = self.vc1_mv_params(pic);
5562        let mixed = pic.mv_mode == MvMode::MixedMv
5563            || (pic.mv_mode == MvMode::IntensityComp && pic.mv_mode2 == MvMode::MixedMv);
5564        let skip_plane = pic.skipmb_plane.as_ref();
5565        let mvtype_plane = pic.mvtypemb_plane.as_ref();
5566        let mb_w = self.width_mb as usize;
5567
5568        for r in 0..self.height_mb as usize {
5569            for c in 0..mb_w {
5570                let mbi = r * mb_w + c;
5571                let fourmv = if mixed {
5572                    if pic.mvtypemb_raw {
5573                        br.read_bit().ok_or_else(|| {
5574                            DecoderError::InvalidData("truncated WMV3 MVTYPE".into())
5575                        })?
5576                    } else {
5577                        mvtype_plane.and_then(|p| p.get(mbi)).copied().unwrap_or(0) != 0
5578                    }
5579                } else {
5580                    false
5581                };
5582                let skipped = if pic.skipmb_raw {
5583                    br.read_bit()
5584                        .ok_or_else(|| DecoderError::InvalidData("truncated WMV3 SKIPMB".into()))?
5585                } else {
5586                    skip_plane.and_then(|p| p.get(mbi)).copied().unwrap_or(0) != 0
5587                };
5588
5589                if !fourmv {
5590                    if skipped {
5591                        let mv =
5592                            self.vc1_predict_p_mv(&mut br, pic, seq, r, c, 0, (0, 0), true, false)?;
5593                        self.vc1_chroma_mvs[mbi] = vc1_loop_chroma_mv(mv);
5594                        self.vc1_mc_single(frame, r, c, &reference, mv, pic, seq, None, false);
5595                        continue;
5596                    }
5597
5598                    let md = self.vc1_read_mvdata(&mut br, pic, quarter)?;
5599                    let mv = self.vc1_predict_p_mv(
5600                        &mut br,
5601                        pic,
5602                        seq,
5603                        r,
5604                        c,
5605                        0,
5606                        (md.dx, md.dy),
5607                        true,
5608                        md.intra,
5609                    )?;
5610                    let mut mquant = pic.pquant as i32;
5611                    let mut cbp = 0u8;
5612                    let mut acpred = false;
5613
5614                    if md.intra && !md.has_coeffs {
5615                        mquant = read_mquant(
5616                            &mut br,
5617                            &pic.dquant,
5618                            pic.pquant as i32,
5619                            c as u32,
5620                            r as u32,
5621                            self.width_mb,
5622                            self.height_mb,
5623                        )?;
5624                        acpred = br.read_bit().ok_or_else(|| {
5625                            DecoderError::InvalidData("truncated WMV3 ACPRED".into())
5626                        })?;
5627                    } else if md.has_coeffs {
5628                        if md.intra {
5629                            acpred = br.read_bit().ok_or_else(|| {
5630                                DecoderError::InvalidData("truncated WMV3 ACPRED".into())
5631                            })?;
5632                        }
5633                        cbp = self.vc1_cbpcy[(pic.cbptab as usize).min(3)]
5634                            .decode(&mut br)
5635                            .ok_or_else(|| {
5636                                DecoderError::InvalidData("invalid WMV3 P CBPCY".into())
5637                            })? as u8;
5638                        mquant = read_mquant(
5639                            &mut br,
5640                            &pic.dquant,
5641                            pic.pquant as i32,
5642                            c as u32,
5643                            r as u32,
5644                            self.width_mb,
5645                            self.height_mb,
5646                        )?;
5647                    }
5648
5649                    self.vc1_qscale[mbi] = mquant;
5650                    if md.intra {
5651                        self.vc1_intra_blocks[mbi] = [true; 6];
5652                        self.vc1_chroma_mvs[mbi] = (0, 0);
5653                    } else {
5654                        self.vc1_chroma_mvs[mbi] = vc1_loop_chroma_mv(mv);
5655                        self.vc1_mc_single(frame, r, c, &reference, mv, pic, seq, None, false);
5656                    }
5657
5658                    let mut ttmb = pic.ttfrm as i32;
5659                    if !pic.ttmbf && !md.intra && md.has_coeffs {
5660                        ttmb = self.vc1_ttmb
5661                            [((pic.pquant > 4) as usize) + ((pic.pquant > 12) as usize)]
5662                            .decode(&mut br)
5663                            .ok_or_else(|| DecoderError::InvalidData("invalid WMV3 TTMB".into()))?;
5664                    }
5665
5666                    let mut first = true;
5667                    for blk in 0..6usize {
5668                        let coded = ((cbp >> (5 - blk)) & 1) != 0;
5669                        if md.intra {
5670                            let mut co = self.vc1_decode_intra_coeffs(
5671                                &mut br,
5672                                pic,
5673                                seq,
5674                                r,
5675                                c,
5676                                blk,
5677                                coded,
5678                                mquant,
5679                                acpred,
5680                                false,
5681                                if blk < 4 {
5682                                    intra_set
5683                                } else {
5684                                    Self::vc1_coding_sets(pic).1
5685                                },
5686                            )?;
5687                            apply_wmv3_idct(&mut co, TT_8X8, seq.uses_transposed_scans());
5688                            if pic.rangeredfrm {
5689                                for v in &mut co {
5690                                    *v *= 2;
5691                                }
5692                            }
5693                            self.vc1_recon_blocks[vc1_recon_index(mb_w, r, c, blk)] = co;
5694                            self.vc1_block_cbp[mbi][blk] = 0x0f;
5695                        } else if coded {
5696                            let (co, tt, pat) =
5697                                self.vc1_decode_p_residual(&mut br, pic, seq, mquant, ttmb, first)?;
5698                            add_residual_block(frame, r as u32, c as u32, blk, &co);
5699                            self.vc1_block_tt[mbi][blk] = tt;
5700                            self.vc1_block_cbp[mbi][blk] = pat;
5701                            if !pic.ttmbf && ttmb < 8 {
5702                                ttmb = -1;
5703                            }
5704                            first = false;
5705                        }
5706                    }
5707                } else {
5708                    if skipped {
5709                        let mut mvs = [(0, 0); 4];
5710                        for (i, mv) in mvs.iter_mut().enumerate() {
5711                            *mv = self.vc1_predict_p_mv(
5712                                &mut br,
5713                                pic,
5714                                seq,
5715                                r,
5716                                c,
5717                                i,
5718                                (0, 0),
5719                                false,
5720                                false,
5721                            )?;
5722                            // FFmpeg vc1_decode_p_mb(), skipped 4MV path:
5723                            // every luma 8x8 block is motion-compensated before
5724                            // deriving the single chroma MV.  The previous Rust
5725                            // port only predicted/stored the four MVs and ran
5726                            // chroma MC, leaving Y as the freshly allocated
5727                            // frame contents.
5728                            self.vc1_mc_single(
5729                                frame,
5730                                r,
5731                                c,
5732                                &reference,
5733                                *mv,
5734                                pic,
5735                                seq,
5736                                Some(i),
5737                                false,
5738                            );
5739                        }
5740                        self.vc1_current_mvs[mbi] = mvs[0];
5741                        self.vc1_chroma_mvs[mbi] = self
5742                            .vc1_mc_4mv_chroma(frame, r, c, &reference, mvs, [false; 4], pic, seq);
5743                        continue;
5744                    }
5745
5746                    let cbp0 = self.vc1_cbpcy[(pic.cbptab as usize).min(3)]
5747                        .decode(&mut br)
5748                        .ok_or_else(|| DecoderError::InvalidData("invalid WMV3 4MV CBPCY".into()))?
5749                        as u8;
5750                    let mut intra = [false; 4];
5751                    let mut coded = [false; 6];
5752                    let mut mvs = [(0, 0); 4];
5753                    for i in 0..4usize {
5754                        let present = ((cbp0 >> (5 - i)) & 1) != 0;
5755                        let md = if present {
5756                            self.vc1_read_mvdata(&mut br, pic, quarter)?
5757                        } else {
5758                            Vc1MvData::default()
5759                        };
5760                        intra[i] = md.intra;
5761                        coded[i] = if present { md.has_coeffs } else { false };
5762                        mvs[i] = self.vc1_predict_p_mv(
5763                            &mut br,
5764                            pic,
5765                            seq,
5766                            r,
5767                            c,
5768                            i,
5769                            (md.dx, md.dy),
5770                            false,
5771                            md.intra,
5772                        )?;
5773                        // ff_vc1_pred_mv() is immediately followed by
5774                        // ff_vc1_mc_4mv_luma() for every non-intra luma block.
5775                        // Chroma MC below is not a substitute for this: it only
5776                        // fills Cb/Cr.  Missing this call leaves 4MV P-picture
5777                        // luma blocks without their reference prediction, so
5778                        // residuals alone appear as block-shaped ghosts/trails.
5779                        if !md.intra {
5780                            self.vc1_mc_single(
5781                                frame,
5782                                r,
5783                                c,
5784                                &reference,
5785                                mvs[i],
5786                                pic,
5787                                seq,
5788                                Some(i),
5789                                false,
5790                            );
5791                        }
5792                    }
5793                    coded[4] = (cbp0 & 2) != 0;
5794                    coded[5] = (cbp0 & 1) != 0;
5795
5796                    let ni = intra.iter().filter(|x| **x).count();
5797                    let chroma_intra = ni >= 3;
5798                    self.vc1_intra_blocks[mbi] = [
5799                        intra[0],
5800                        intra[1],
5801                        intra[2],
5802                        intra[3],
5803                        chroma_intra,
5804                        chroma_intra,
5805                    ];
5806                    let coded_inter = (0..4).any(|i| coded[i] && !intra[i])
5807                        || (!chroma_intra && (coded[4] || coded[5]));
5808                    let mut mquant = pic.pquant as i32;
5809                    if ni > 0 || coded_inter {
5810                        mquant = read_mquant(
5811                            &mut br,
5812                            &pic.dquant,
5813                            pic.pquant as i32,
5814                            c as u32,
5815                            r as u32,
5816                            self.width_mb,
5817                            self.height_mb,
5818                        )?;
5819                    }
5820                    self.vc1_qscale[mbi] = mquant;
5821
5822                    let need_acpred =
5823                        self.vc1_intra_blocks[mbi].iter().enumerate().any(|(i, v)| {
5824                            *v && (self
5825                                .vc1_intra_neighbour(r, c, i, true)
5826                                .map(|(mi, b)| self.vc1_intra_blocks[mi][b])
5827                                .unwrap_or(false)
5828                                || self
5829                                    .vc1_intra_neighbour(r, c, i, false)
5830                                    .map(|(mi, b)| self.vc1_intra_blocks[mi][b])
5831                                    .unwrap_or(false))
5832                        });
5833                    let acpred = if need_acpred {
5834                        br.read_bit().ok_or_else(|| {
5835                            DecoderError::InvalidData("truncated WMV3 4MV ACPRED".into())
5836                        })?
5837                    } else {
5838                        false
5839                    };
5840
5841                    let mut ttmb = pic.ttfrm as i32;
5842                    if !pic.ttmbf && coded_inter {
5843                        ttmb = self.vc1_ttmb
5844                            [((pic.pquant > 4) as usize) + ((pic.pquant > 12) as usize)]
5845                            .decode(&mut br)
5846                            .ok_or_else(|| {
5847                                DecoderError::InvalidData("invalid WMV3 4MV TTMB".into())
5848                            })?;
5849                    }
5850
5851                    self.vc1_chroma_mvs[mbi] =
5852                        self.vc1_mc_4mv_chroma(frame, r, c, &reference, mvs, intra, pic, seq);
5853
5854                    let mut first = true;
5855                    for blk in 0..6usize {
5856                        let is_intra = if blk < 4 { intra[blk] } else { chroma_intra };
5857                        if is_intra {
5858                            let mut co = self.vc1_decode_intra_coeffs(
5859                                &mut br,
5860                                pic,
5861                                seq,
5862                                r,
5863                                c,
5864                                blk,
5865                                coded[blk],
5866                                mquant,
5867                                acpred,
5868                                false,
5869                                if blk < 4 {
5870                                    intra_set
5871                                } else {
5872                                    Self::vc1_coding_sets(pic).1
5873                                },
5874                            )?;
5875                            apply_wmv3_idct(&mut co, TT_8X8, seq.uses_transposed_scans());
5876                            if pic.rangeredfrm {
5877                                for v in &mut co {
5878                                    *v *= 2;
5879                                }
5880                            }
5881                            self.vc1_recon_blocks[vc1_recon_index(mb_w, r, c, blk)] = co;
5882                            self.vc1_block_cbp[mbi][blk] = 0x0f;
5883                        } else if coded[blk] {
5884                            let (co, tt, pat) =
5885                                self.vc1_decode_p_residual(&mut br, pic, seq, mquant, ttmb, first)?;
5886                            add_residual_block(frame, r as u32, c as u32, blk, &co);
5887                            self.vc1_block_tt[mbi][blk] = tt;
5888                            self.vc1_block_cbp[mbi][blk] = pat;
5889                            if !pic.ttmbf && ttmb < 8 {
5890                                ttmb = -1;
5891                            }
5892                            first = false;
5893                        }
5894                    }
5895                    self.vc1_current_mvs[mbi] = mvs[0];
5896                }
5897            }
5898        }
5899        Ok(())
5900    }
5901
5902    fn decode_b(
5903        &mut self,
5904        payload: &[u8],
5905        pic: &PictureHeader,
5906        seq: &SequenceHeader,
5907        frame: &mut YuvFrame,
5908    ) -> Result<()> {
5909        let mut fwd = match self.fwd_ref.clone() {
5910            Some(v) => v,
5911            None => return Ok(()),
5912        };
5913        let mut bwd = match self.bwd_ref.clone() {
5914            Some(v) => v,
5915            None => return Ok(()),
5916        };
5917        // FFmpeg vc1_mc.c range-reduces the reference samples used by the
5918        // current picture before interpolation.  Work on local reference
5919        // copies so the stored anchors remain untouched.
5920        if pic.rangeredfrm {
5921            vc1_range_reduce_reference(&mut fwd);
5922            vc1_range_reduce_reference(&mut bwd);
5923        }
5924        // The future P anchor may have declared intensity compensation for the
5925        // older anchor. FFmpeg keeps that transform in `last_luty` and applies
5926        // it whenever a B picture samples the forward/last reference.  The
5927        // previous port applied IC while decoding the P anchor itself but threw
5928        // this state away before the intervening B pictures, producing chroma
5929        // trails during fades.
5930        if let Some((lumscale, lumshift)) = self.vc1_b_fwd_ic {
5931            vc1_apply_ic_reference(&mut fwd, lumscale, lumshift);
5932        }
5933        let mut br = BitReader::new_at(payload, pic.header_bits);
5934        self.vc1_reset_picture_state();
5935        let (intra_set, _) = Self::vc1_coding_sets(pic);
5936        let (quarter, _, _, _, _, _) = self.vc1_mv_params(pic);
5937        let nmb = self.width_mb as usize * self.height_mb as usize;
5938        let mut fhist = vec![(0, 0); nmb];
5939        let mut bhist = vec![(0, 0); nmb];
5940        for r in 0..self.height_mb as usize {
5941            for c in 0..self.width_mb as usize {
5942                let idx = r * self.width_mb as usize + c;
5943                let direct = if pic.directmb_raw {
5944                    br.read_bit().ok_or_else(|| {
5945                        DecoderError::InvalidData("truncated WMV3 DIRECTMB".into())
5946                    })?
5947                } else {
5948                    pic.directmb_plane
5949                        .as_ref()
5950                        .and_then(|p| p.get(idx))
5951                        .copied()
5952                        .unwrap_or(0)
5953                        != 0
5954                };
5955                let skipped = if pic.skipmb_raw {
5956                    br.read_bit().ok_or_else(|| {
5957                        DecoderError::InvalidData("truncated WMV3 B SKIPMB".into())
5958                    })?
5959                } else {
5960                    pic.skipmb_plane
5961                        .as_ref()
5962                        .and_then(|p| p.get(idx))
5963                        .copied()
5964                        .unwrap_or(0)
5965                        != 0
5966                };
5967                let mut dmv = [(0, 0); 2];
5968                let mut md = Vc1MvData::default();
5969                if !direct && !skipped {
5970                    md = self.vc1_read_mvdata(&mut br, pic, quarter)?;
5971                    dmv[0] = (md.dx, md.dy);
5972                    dmv[1] = dmv[0];
5973                }
5974                let mut mode = 1u8;
5975                if !direct && (skipped || !md.intra) {
5976                    let t = vc1_decode012_bits(&mut br)?;
5977                    mode = match t {
5978                        0 => {
5979                            if pic.bfrac_num * 2 >= pic.bfrac_den {
5980                                1
5981                            } else {
5982                                0
5983                            }
5984                        }
5985                        1 => {
5986                            if pic.bfrac_num * 2 >= pic.bfrac_den {
5987                                0
5988                            } else {
5989                                1
5990                            }
5991                        }
5992                        _ => {
5993                            dmv[0] = (0, 0);
5994                            2
5995                        }
5996                    };
5997                }
5998                if skipped {
5999                    if direct {
6000                        mode = 2;
6001                    }
6002                    let mv = self
6003                        .vc1_b_predict(r, c, dmv, direct, mode, false, pic, seq, &fhist, &bhist);
6004                    fhist[idx] = mv[0];
6005                    bhist[idx] = mv[1];
6006                    if direct || mode == 2 {
6007                        self.vc1_mc_blend(frame, r, c, &fwd, &bwd, mv[0], mv[1], pic, seq);
6008                    } else if mode == 0 {
6009                        self.vc1_mc_single(frame, r, c, &fwd, mv[0], pic, seq, None, false);
6010                    } else {
6011                        self.vc1_mc_single(frame, r, c, &bwd, mv[1], pic, seq, None, false);
6012                    }
6013                    continue;
6014                }
6015                let mut cbp = 0u8;
6016                let mut mquant = pic.pquant as i32;
6017                let mut acpred = false;
6018                let mut ttmb = pic.ttfrm as i32;
6019                let mv;
6020                if direct {
6021                    cbp = self.vc1_cbpcy[(pic.cbptab as usize).min(3)]
6022                        .decode(&mut br)
6023                        .ok_or_else(|| {
6024                            DecoderError::InvalidData("invalid WMV3 direct CBPCY".into())
6025                        })? as u8;
6026                    mquant = read_mquant(
6027                        &mut br,
6028                        &pic.dquant,
6029                        pic.pquant as i32,
6030                        c as u32,
6031                        r as u32,
6032                        self.width_mb,
6033                        self.height_mb,
6034                    )?;
6035                    if !pic.ttmbf {
6036                        ttmb = self.vc1_ttmb
6037                            [((pic.pquant > 4) as usize) + ((pic.pquant > 12) as usize)]
6038                            .decode(&mut br)
6039                            .ok_or_else(|| {
6040                                DecoderError::InvalidData("invalid WMV3 B TTMB".into())
6041                            })?;
6042                    }
6043                    dmv = [(0, 0); 2];
6044                    mv = self.vc1_b_predict(r, c, dmv, true, 2, false, pic, seq, &fhist, &bhist);
6045                    self.vc1_mc_blend(frame, r, c, &fwd, &bwd, mv[0], mv[1], pic, seq);
6046                } else if !md.has_coeffs && !md.intra {
6047                    mv = self
6048                        .vc1_b_predict(r, c, dmv, false, mode, md.intra, pic, seq, &fhist, &bhist);
6049                    if mode == 2 {
6050                        self.vc1_mc_blend(frame, r, c, &fwd, &bwd, mv[0], mv[1], pic, seq);
6051                    } else if mode == 0 {
6052                        self.vc1_mc_single(frame, r, c, &fwd, mv[0], pic, seq, None, false);
6053                    } else {
6054                        self.vc1_mc_single(frame, r, c, &bwd, mv[1], pic, seq, None, false);
6055                    }
6056                    fhist[idx] = mv[0];
6057                    bhist[idx] = mv[1];
6058                    continue;
6059                } else if md.intra && !md.has_coeffs {
6060                    mquant = read_mquant(
6061                        &mut br,
6062                        &pic.dquant,
6063                        pic.pquant as i32,
6064                        c as u32,
6065                        r as u32,
6066                        self.width_mb,
6067                        self.height_mb,
6068                    )?;
6069                    acpred = br.read_bit().ok_or_else(|| {
6070                        DecoderError::InvalidData("truncated WMV3 B ACPRED".into())
6071                    })?;
6072                    mv = self
6073                        .vc1_b_predict(r, c, dmv, false, mode, md.intra, pic, seq, &fhist, &bhist);
6074                } else {
6075                    if mode == 2 {
6076                        let md2 = self.vc1_read_mvdata(&mut br, pic, quarter)?;
6077                        dmv[0] = (md2.dx, md2.dy);
6078                        if !md2.has_coeffs {
6079                            let mv2 = self.vc1_b_predict(
6080                                r, c, dmv, false, mode, md2.intra, pic, seq, &fhist, &bhist,
6081                            );
6082                            self.vc1_mc_blend(frame, r, c, &fwd, &bwd, mv2[0], mv2[1], pic, seq);
6083                            fhist[idx] = mv2[0];
6084                            bhist[idx] = mv2[1];
6085                            continue;
6086                        }
6087                        md.has_coeffs = md2.has_coeffs;
6088                        md.intra = md2.intra;
6089                    }
6090                    mv = self
6091                        .vc1_b_predict(r, c, dmv, false, mode, md.intra, pic, seq, &fhist, &bhist);
6092                    if !md.intra {
6093                        if mode == 2 {
6094                            self.vc1_mc_blend(frame, r, c, &fwd, &bwd, mv[0], mv[1], pic, seq);
6095                        } else if mode == 0 {
6096                            self.vc1_mc_single(frame, r, c, &fwd, mv[0], pic, seq, None, false);
6097                        } else {
6098                            self.vc1_mc_single(frame, r, c, &bwd, mv[1], pic, seq, None, false);
6099                        }
6100                    }
6101                    if md.intra {
6102                        acpred = br.read_bit().ok_or_else(|| {
6103                            DecoderError::InvalidData("truncated WMV3 B ACPRED".into())
6104                        })?;
6105                    }
6106                    cbp = self.vc1_cbpcy[(pic.cbptab as usize).min(3)]
6107                        .decode(&mut br)
6108                        .ok_or_else(|| DecoderError::InvalidData("invalid WMV3 B CBPCY".into()))?
6109                        as u8;
6110                    mquant = read_mquant(
6111                        &mut br,
6112                        &pic.dquant,
6113                        pic.pquant as i32,
6114                        c as u32,
6115                        r as u32,
6116                        self.width_mb,
6117                        self.height_mb,
6118                    )?;
6119                    if !pic.ttmbf && !md.intra && md.has_coeffs {
6120                        ttmb = self.vc1_ttmb
6121                            [((pic.pquant > 4) as usize) + ((pic.pquant > 12) as usize)]
6122                            .decode(&mut br)
6123                            .ok_or_else(|| {
6124                                DecoderError::InvalidData("invalid WMV3 B TTMB".into())
6125                            })?;
6126                    }
6127                }
6128                self.vc1_qscale[idx] = mquant;
6129                fhist[idx] = mv[0];
6130                bhist[idx] = mv[1];
6131                let mut first = true;
6132                for blk in 0..6 {
6133                    let coded = ((cbp >> (5 - blk)) & 1) != 0;
6134                    if md.intra {
6135                        self.vc1_intra_blocks[idx][blk] = true;
6136                        let mut co = self.vc1_decode_intra_coeffs(
6137                            &mut br,
6138                            pic,
6139                            seq,
6140                            r,
6141                            c,
6142                            blk,
6143                            coded,
6144                            mquant,
6145                            acpred,
6146                            false,
6147                            if blk < 4 {
6148                                intra_set
6149                            } else {
6150                                Self::vc1_coding_sets(pic).1
6151                            },
6152                        )?;
6153                        apply_wmv3_idct(&mut co, TT_8X8, seq.uses_transposed_scans());
6154                        if pic.rangeredfrm {
6155                            for v in &mut co {
6156                                *v *= 2;
6157                            }
6158                        }
6159                        write_intra_block(frame, r as u32, c as u32, blk, &co);
6160                    } else if coded {
6161                        let (co, _, _) =
6162                            self.vc1_decode_p_residual(&mut br, pic, seq, mquant, ttmb, first)?;
6163                        add_residual_block(frame, r as u32, c as u32, blk, &co);
6164                        if !pic.ttmbf && ttmb < 8 {
6165                            ttmb = -1;
6166                        }
6167                        first = false;
6168                    }
6169                }
6170            }
6171        }
6172        Ok(())
6173    }
6174
6175    fn vc1_mc_blend(
6176        &self,
6177        frame: &mut YuvFrame,
6178        r: usize,
6179        c: usize,
6180        fwd: &YuvFrame,
6181        bwd: &YuvFrame,
6182        fmv: (i32, i32),
6183        bmv: (i32, i32),
6184        pic: &PictureHeader,
6185        seq: &SequenceHeader,
6186    ) {
6187        // Produce the forward prediction directly into the destination and keep
6188        // only this macroblock, then overwrite with the backward prediction and
6189        // average.  The old code allocated two full-resolution YUV frames for
6190        // every B-picture macroblock.
6191        self.vc1_mc_single(frame, r, c, fwd, fmv, pic, seq, None, false);
6192
6193        let x0 = c * 16;
6194        let y0 = r * 16;
6195        let mut fy = [0u8; 16 * 16];
6196        for y in 0..16 {
6197            let py = y0 + y;
6198            if py >= self.height as usize {
6199                break;
6200            }
6201            for x in 0..16 {
6202                let px = x0 + x;
6203                if px >= self.width as usize {
6204                    break;
6205                }
6206                fy[y * 16 + x] = frame.y[py * self.width as usize + px];
6207            }
6208        }
6209
6210        let cw = self.width as usize / 2;
6211        let ch = self.height as usize / 2;
6212        let cx0 = c * 8;
6213        let cy0 = r * 8;
6214        let mut fu = [0u8; 8 * 8];
6215        let mut fv = [0u8; 8 * 8];
6216        for y in 0..8 {
6217            let py = cy0 + y;
6218            if py >= ch {
6219                break;
6220            }
6221            for x in 0..8 {
6222                let px = cx0 + x;
6223                if px >= cw {
6224                    break;
6225                }
6226                let i = py * cw + px;
6227                fu[y * 8 + x] = frame.cb[i];
6228                fv[y * 8 + x] = frame.cr[i];
6229            }
6230        }
6231
6232        self.vc1_mc_single(frame, r, c, bwd, bmv, pic, seq, None, true);
6233
6234        for y in 0..16 {
6235            let py = y0 + y;
6236            if py >= self.height as usize {
6237                break;
6238            }
6239            for x in 0..16 {
6240                let px = x0 + x;
6241                if px >= self.width as usize {
6242                    break;
6243                }
6244                let i = py * self.width as usize + px;
6245                let sum = fy[y * 16 + x] as u16 + frame.y[i] as u16;
6246                // FFmpeg's avg VC-1 MC operator is always
6247                // (dst + clip(pred) + 1) >> 1.  `v->rnd` controls the
6248                // interpolation filter that produced `pred`; it does not
6249                // change the final forward/backward average.
6250                frame.y[i] = ((sum + 1) >> 1) as u8;
6251            }
6252        }
6253        for y in 0..8 {
6254            let py = cy0 + y;
6255            if py >= ch {
6256                break;
6257            }
6258            for x in 0..8 {
6259                let px = cx0 + x;
6260                if px >= cw {
6261                    break;
6262                }
6263                let i = py * cw + px;
6264                let us = fu[y * 8 + x] as u16 + frame.cb[i] as u16;
6265                let vs = fv[y * 8 + x] as u16 + frame.cr[i] as u16;
6266                frame.cb[i] = ((us + 1) >> 1) as u8;
6267                frame.cr[i] = ((vs + 1) >> 1) as u8;
6268            }
6269        }
6270    }
6271}
6272
6273// ═══════════════════════════════════════════════════════════════════════════════
6274// WMV2 (MS-MPEG4 V8) Decode Entry Points
6275// ═══════════════════════════════════════════════════════════════════════════════
6276//
6277// Public interface: MacroblockDecoder::decode_wmv2_frame()
6278//
6279// WMV2 simplifications vs VC-1:
6280//   • No B-frames, no BFRACTION, no overlap filter, no loop filter flag
6281//   • No TRANSACFRM/CBPTAB/MVTAB in seqhdr; ttcoef from frame header
6282//   • DC: 8-bit absolute (no VLC), sign separate
6283//   • AC escape: Mode-3 only (1-bit last, 6-bit run, 8-bit level, 1-bit sign)
6284//   • IDCT: same VC-1 integer transform reused
6285//   • Motion: half-pel bilinear (same MC as VC-1)
6286
6287// ─── WMV2 DC scale tables ───────────────────────────────────────────────────
6288// WMV2 uses MPEG-4 style DC scaling tables (much smaller than VC-1's ×128 domain
6289// tables). Using VC-1 DC step tables here will massively over-scale DC and
6290// saturate the reconstructed picture.
6291//
6292// These tables match the conventional MPEG-4 Part 2 DC scale tables.
6293// (They are also used by MSMPEG4/WMV1-family decoders.)
6294#[inline(always)]
6295fn wmv2_dc_scale(pquant: i32, is_luma: bool) -> i32 {
6296    // upstream: ff_wmv1_y_dc_scale_table / ff_wmv1_c_dc_scale_table (used for WMV1/WMV2).
6297    const Y: [i32; 32] = [
6298        0, 8, 8, 8, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18,
6299        18, 19, 19, 20, 20, 21, 21,
6300    ];
6301    const C: [i32; 32] = [
6302        0, 8, 8, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18,
6303        18, 19, 19, 20, 20, 21, 21, 22,
6304    ];
6305    let idx = pquant.clamp(1, 31) as usize;
6306    if is_luma { Y[idx] } else { C[idx] }
6307}
6308
6309#[inline(always)]
6310fn decode012(br: &mut BitReader<'_>) -> u8 {
6311    // upstream get_bits.h: n=get_bits1(); if n==0 return 0; else return get_bits1()+1;
6312    match br.read_bit() {
6313        Some(false) => 0,
6314        Some(true) => br.read_bit().map(|b| if b { 2 } else { 1 }).unwrap_or(0),
6315        None => 0,
6316    }
6317}
6318
6319#[inline(always)]
6320fn wmv2_get_cbp_table_index(qscale: i32, cbp_index: u8) -> usize {
6321    // upstream wmv2.h wmv2_get_cbp_table_index
6322    const MAP: [[u8; 3]; 3] = [[0, 2, 1], [1, 0, 2], [2, 1, 0]];
6323    let a = if qscale > 10 { 1 } else { 0 };
6324    let b = if qscale > 20 { 1 } else { 0 };
6325    let row = (a + b) as usize;
6326    MAP[row][(cbp_index as usize).min(2)] as usize
6327}
6328
6329impl MacroblockDecoder {
6330    /// Decode one WMV2 frame. `hdr` is the already-parsed per-frame header.
6331    /// This is the public entry point called from main.rs.
6332    /// Parse WMV2 ext-header from ASF extradata (upstream decode_ext_header).
6333    ///
6334    /// If extradata is missing/short, we keep all flags at default false.
6335    pub fn wmv2_set_extradata(&mut self, extradata: &[u8]) {
6336        if extradata.len() < 4 {
6337            return;
6338        }
6339        let mut br = BitReader::new(&extradata[..4]);
6340        let _fps = br.read_bits(5).unwrap_or(0);
6341        let _bit_rate = br.read_bits(11).unwrap_or(0) * 1024;
6342        self.wmv2_mspel_bit = br.read_bit().unwrap_or(false);
6343        let _loop_filter = br.read_bit().unwrap_or(false);
6344        self.wmv2_abt_flag = br.read_bit().unwrap_or(false);
6345        self.wmv2_j_type_bit = br.read_bit().unwrap_or(false);
6346        self.wmv2_top_left_mv_flag = br.read_bit().unwrap_or(false);
6347        self.wmv2_per_mb_rl_bit = br.read_bit().unwrap_or(false);
6348        let code = br.read_bits(3).unwrap_or(0) as usize;
6349        if code == 0 {
6350            return;
6351        }
6352        let mb_h = self.height_mb as usize;
6353        self.wmv2_slice_height = mb_h / code;
6354    }
6355
6356    pub fn wmv2_copy_ref(&self, out: &mut YuvFrame) -> bool {
6357        let Some(r) = self.wmv2_ref.as_ref() else {
6358            return false;
6359        };
6360        if out.width != r.width || out.height != r.height {
6361            *out = r.clone();
6362            return true;
6363        }
6364        if out.y.len() == r.y.len() {
6365            out.y.copy_from_slice(&r.y);
6366        } else {
6367            out.y = r.y.clone();
6368        }
6369        if out.cb.len() == r.cb.len() {
6370            out.cb.copy_from_slice(&r.cb);
6371        } else {
6372            out.cb = r.cb.clone();
6373        }
6374        if out.cr.len() == r.cr.len() {
6375            out.cr.copy_from_slice(&r.cr);
6376        } else {
6377            out.cr = r.cr.clone();
6378        }
6379        true
6380    }
6381
6382    pub fn decode_wmv2_frame(
6383        &mut self,
6384        payload: &[u8],
6385        hdr: &Wmv2FrameHeader,
6386        params: &Wmv2Params,
6387        frame: &mut YuvFrame,
6388    ) -> Result<()> {
6389        // resize if needed
6390        if self.width != params.width || self.height != params.height {
6391            *self = MacroblockDecoder::new(params.width, params.height);
6392        }
6393
6394        if hdr.frame_skipped {
6395            let _ = self.wmv2_copy_ref(frame);
6396            return Ok(());
6397        }
6398        match hdr.frame_type {
6399            Wmv2FrameType::I => self.wmv2_decode_intra(payload, hdr, frame),
6400            Wmv2FrameType::P => self.wmv2_decode_p(payload, hdr, frame),
6401        }
6402    }
6403
6404    /// Heuristic probe: try to parse a few macroblock headers after `hdr.header_bits`.
6405    /// Used to disambiguate ASF framing-byte offsets when the picture header can be
6406    /// (mis-)parsed at multiple byte offsets.
6407    ///
6408    /// Returns a "score" = number of MB headers successfully parsed (higher is better).
6409    pub fn probe_wmv2_payload(&self, payload: &[u8], hdr: &Wmv2FrameHeader) -> usize {
6410        let mut br = BitReader::new_at(payload, hdr.header_bits);
6411
6412        // upstream-aligned quick probe for I-frames: only consume secondary header + MB header + 6×DC.
6413        if hdr.frame_type == Wmv2FrameType::I {
6414            let mut br = BitReader::new_at(payload, hdr.header_bits);
6415            // secondary picture header (I branch)
6416            let j_type = if self.wmv2_j_type_bit {
6417                br.read_bit().unwrap_or(false)
6418            } else {
6419                false
6420            };
6421            if j_type {
6422                return 1;
6423            }
6424            let per_mb_rl_table = if self.wmv2_per_mb_rl_bit {
6425                br.read_bit().unwrap_or(false)
6426            } else {
6427                false
6428            };
6429            if !per_mb_rl_table {
6430                let _ = decode012(&mut br);
6431                let _ = decode012(&mut br);
6432            }
6433            let dc_table_index = br.read_bit().unwrap_or(false) as usize;
6434            let code = match self.wmv2_mb_i_vlc.decode(&mut br) {
6435                Some(v) => v as u32,
6436                None => return 0,
6437            };
6438            let _ = code;
6439            let _ac_pred = br.read_bit().unwrap_or(false);
6440            let _ = _ac_pred;
6441            if per_mb_rl_table && code != 0 {
6442                let _ = decode012(&mut br);
6443            }
6444            // DCs
6445            const DC_MAX: i32 = 119;
6446            for blk in 0..6usize {
6447                let is_chroma = blk >= 4;
6448                let tbl = &self.wmv2_dc_vlc[dc_table_index][if is_chroma { 1 } else { 0 }];
6449                let level = match tbl.decode(&mut br) {
6450                    Some(v) => v,
6451                    None => return 0,
6452                };
6453                if level == DC_MAX {
6454                    let _ = br.read_bits(8);
6455                    let _ = br.read_bit();
6456                } else if level != 0 {
6457                    let _ = br.read_bit();
6458                }
6459            }
6460            return 1;
6461        }
6462
6463        // upstream-aligned quick probe for P-frames: consume secondary header + first MB header.
6464        if hdr.frame_type == Wmv2FrameType::P {
6465            let mut br = BitReader::new_at(payload, hdr.header_bits);
6466            let mb_w = self.width_mb as usize;
6467            let mb_h = self.height_mb as usize;
6468            let qscale = hdr.pquant as i32;
6469
6470            // skip map (only check first MB skip flag)
6471            let skip_type = br.read_bits(2).unwrap_or(0) as u8;
6472            let first_skip = match skip_type {
6473                0 => false,
6474                1 => br.read_bit().unwrap_or(false),
6475                2 => {
6476                    let all = br.read_bit().unwrap_or(false);
6477                    if all {
6478                        true
6479                    } else {
6480                        br.read_bit().unwrap_or(false)
6481                    }
6482                }
6483                3 => {
6484                    let all = br.read_bit().unwrap_or(false);
6485                    if all {
6486                        true
6487                    } else {
6488                        br.read_bit().unwrap_or(false)
6489                    }
6490                }
6491                _ => false,
6492            };
6493
6494            // Drain remaining skip bits quickly (best-effort) to reach cbp_index.
6495            // We only do a lightweight skip consumption to keep probe cheap.
6496            if skip_type == 1 {
6497                let _ = mb_w * mb_h;
6498            }
6499
6500            let cbp_index = decode012(&mut br);
6501            let cbp_table_index = wmv2_get_cbp_table_index(qscale, cbp_index);
6502
6503            let _mspel = if self.wmv2_mspel_bit {
6504                br.read_bit().unwrap_or(false)
6505            } else {
6506                false
6507            };
6508            if self.wmv2_abt_flag {
6509                let per_mb_abt = br.read_bit().unwrap_or(false) ^ true;
6510                if !per_mb_abt {
6511                    let _ = decode012(&mut br);
6512                }
6513            }
6514            let per_mb_rl_table = if self.wmv2_per_mb_rl_bit {
6515                br.read_bit().unwrap_or(false)
6516            } else {
6517                false
6518            };
6519            if !per_mb_rl_table {
6520                let _ = decode012(&mut br);
6521            }
6522            let dc_table_index = br.read_bit().unwrap_or(false) as usize;
6523            let mv_table_index = br.read_bit().unwrap_or(false) as usize;
6524
6525            if first_skip {
6526                return 1;
6527            }
6528
6529            let code = match self.wmv2_mb_non_intra_vlc[cbp_table_index.min(3)].decode(&mut br) {
6530                Some(v) => v,
6531                None => return 0,
6532            };
6533            let mb_intra = (code & 0x40) == 0;
6534            let cbp = (code & 0x3f) as u8;
6535
6536            if mb_intra {
6537                let _ac_pred = br.read_bit().unwrap_or(false);
6538                if per_mb_rl_table && cbp != 0 {
6539                    let _ = decode012(&mut br);
6540                }
6541                // Decode one DC to validate DC VLC table.
6542                const DC_MAX: i32 = 119;
6543                let tbl = &self.wmv2_dc_vlc[dc_table_index][0];
6544                let level = match tbl.decode(&mut br) {
6545                    Some(v) => v,
6546                    None => return 0,
6547                };
6548                if level == DC_MAX {
6549                    let _ = br.read_bits(8);
6550                    let _ = br.read_bit();
6551                } else if level != 0 {
6552                    let _ = br.read_bit();
6553                }
6554            } else {
6555                // Decode one MV symbol.
6556                let tbl = &self.wmv2_mv_vlc[mv_table_index.min(1)];
6557                let sym = match tbl.decode(&mut br) {
6558                    Some(v) => v as u16,
6559                    None => return 0,
6560                };
6561                if sym == 0 {
6562                    let _ = br.read_bits(12);
6563                }
6564            }
6565            return 1;
6566        }
6567
6568        let max_mb = (self.width_mb as usize * self.height_mb as usize).min(64);
6569        let mut score: usize = 0;
6570
6571        // Use ttcoef=0 tables for probing; this is only a syntactic plausibility check.
6572        let ac_intra = &self.wmv2_intra[0];
6573        let ac_inter = &self.wmv2_inter[0];
6574
6575        for _ in 0..max_mb {
6576            if br.is_empty() {
6577                break;
6578            }
6579
6580            let cbpc_sym = match self.wmv2_cbpc.decode(&mut br) {
6581                Some(v) => v,
6582                None => break,
6583            };
6584            if cbpc_sym == -1 {
6585                score += 1;
6586                continue;
6587            }
6588            if !(0..=3).contains(&cbpc_sym) {
6589                break;
6590            }
6591
6592            let is_intra = match br.read_bit() {
6593                Some(b) => b,
6594                None => break,
6595            };
6596
6597            let cbpy_raw = match self.wmv2_cbpy.decode(&mut br) {
6598                Some(v @ 0..=15) => v as u8,
6599                _ => break,
6600            };
6601
6602            let cbpy = if is_intra { cbpy_raw } else { cbpy_raw ^ 0x0F };
6603            let cbp: u8 = (cbpy << 2) | (cbpc_sym as u8 & 0x03);
6604
6605            if cbp != 0 {
6606                let vlc = if is_intra { ac_intra } else { ac_inter };
6607                let sym = match vlc.decode(&mut br) {
6608                    Some(s) => s,
6609                    None => break,
6610                };
6611                if sym == VLC_ESCAPE {
6612                    // Consume escape payload (mode 1/2/3) so probing stays in sync.
6613                    let _ = decode_escape_coeff(&mut br, vlc);
6614                } else {
6615                    // Normal coefficient: single sign bit follows.
6616                    let _ = br.read_bit();
6617                }
6618            }
6619
6620            score += 1;
6621        }
6622
6623        score
6624    }
6625
6626    // ── WMV2/MSMPEG4 helpers (upstream-aligned) ─────────────────────────────
6627
6628    #[inline(always)]
6629    fn wmv2_coded_block_pred(&self, mb_row: usize, mb_col: usize, blk: usize) -> u8 {
6630        // Equivalent to upstream ff_msmpeg4_coded_block_pred(), but on a compact grid.
6631        let bw = (self.width_mb as usize) * 2;
6632        let bx = mb_col * 2 + (blk & 1);
6633        let by = mb_row * 2 + (blk >> 1);
6634        let idx = by * bw + bx;
6635        let a = if bx > 0 {
6636            self.wmv2_coded_block[idx - 1]
6637        } else {
6638            0
6639        };
6640        let b = if bx > 0 && by > 0 {
6641            self.wmv2_coded_block[idx - 1 - bw]
6642        } else {
6643            0
6644        };
6645        let c = if by > 0 {
6646            self.wmv2_coded_block[idx - bw]
6647        } else {
6648            0
6649        };
6650        if b == c { a } else { c }
6651    }
6652
6653    #[inline(always)]
6654    fn wmv2_coded_block_store(&mut self, mb_row: usize, mb_col: usize, blk: usize, v: u8) {
6655        let bw = (self.width_mb as usize) * 2;
6656        let bx = mb_col * 2 + (blk & 1);
6657        let by = mb_row * 2 + (blk >> 1);
6658        let idx = by * bw + bx;
6659        if idx < self.wmv2_coded_block.len() {
6660            self.wmv2_coded_block[idx] = v;
6661        }
6662    }
6663
6664    #[inline(always)]
6665    fn wmv2_decode_dc_diff(&self, br: &mut BitReader<'_>, is_chroma: bool) -> i32 {
6666        // upstream msmpeg4_decode_dc() for v3+/WMV2: VLC magnitude + optional sign; DC_MAX escape.
6667        const DC_MAX: i32 = 119;
6668        let tbl = &self.wmv2_dc_vlc[self.wmv2_dc_table_index][if is_chroma { 1 } else { 0 }];
6669        let mut level = tbl.decode(br).unwrap_or(0);
6670        if level == DC_MAX {
6671            let v = br.read_bits(8).unwrap_or(0) as i32;
6672            let sign = br.read_bit().unwrap_or(false);
6673            return if sign { -v } else { v };
6674        }
6675        if level != 0 {
6676            let sign = br.read_bit().unwrap_or(false);
6677            if sign {
6678                level = -level;
6679            }
6680        }
6681        level
6682    }
6683
6684    #[inline(always)]
6685    fn wmv2_reset_picture_state(&mut self) {
6686        self.wmv2_esc3_level_length = 0;
6687        self.wmv2_esc3_run_length = 0;
6688        for v in self.wmv2_ac_val.iter_mut() {
6689            *v = [0i16; 16];
6690        }
6691    }
6692
6693    #[inline(always)]
6694    fn wmv2_ac_val_idx(&self, mb_row: usize, mb_col: usize, blk: usize) -> usize {
6695        let mb_w = self.width_mb as usize;
6696        (mb_row * mb_w + mb_col) * 6 + blk
6697    }
6698
6699    #[inline(always)]
6700    fn wmv2_get_ac_val(&self, mb_row: usize, mb_col: usize, blk: usize) -> [i16; 16] {
6701        let idx = self.wmv2_ac_val_idx(mb_row, mb_col, blk);
6702        if idx < self.wmv2_ac_val.len() {
6703            self.wmv2_ac_val[idx]
6704        } else {
6705            [0i16; 16]
6706        }
6707    }
6708
6709    #[inline(always)]
6710    fn wmv2_set_ac_val(&mut self, mb_row: usize, mb_col: usize, blk: usize, v: [i16; 16]) {
6711        let idx = self.wmv2_ac_val_idx(mb_row, mb_col, blk);
6712        if idx < self.wmv2_ac_val.len() {
6713            self.wmv2_ac_val[idx] = v;
6714        }
6715    }
6716
6717    #[inline(always)]
6718    fn wmv2_pred_ac(
6719        &mut self,
6720        mb_row: usize,
6721        mb_col: usize,
6722        blk: usize,
6723        dc_pred_dir: i32,
6724        ac_pred: bool,
6725        block: &mut [i16; 64],
6726    ) {
6727        // Direct port of upstream ff_mpeg4_pred_ac() behavior for MSMPEG4/WMV2.
6728        // We keep identity idct_permutation (our scan tables are already permutated).
6729        // ac_val stores 16 values per block: [1..7] left column, [9..15] top row.
6730
6731        let mut cur = self.wmv2_get_ac_val(mb_row, mb_col, blk);
6732
6733        if ac_pred {
6734            if dc_pred_dir == 0 {
6735                // Left prediction: add first column from left neighbor.
6736                let (src_r, src_c, src_b) = match blk {
6737                    1 => (mb_row, mb_col, 0),
6738                    3 => (mb_row, mb_col, 2),
6739                    0 => (mb_row, mb_col.saturating_sub(1), 1),
6740                    2 => (mb_row, mb_col.saturating_sub(1), 3),
6741                    4 | 5 => (mb_row, mb_col.saturating_sub(1), blk),
6742                    _ => (mb_row, mb_col.saturating_sub(1), blk),
6743                };
6744                if (blk == 1 || blk == 3) || mb_col > 0 {
6745                    let src = self.wmv2_get_ac_val(src_r, src_c, src_b);
6746                    for i in 1..8usize {
6747                        let idx = i << 3;
6748                        block[idx] = block[idx].wrapping_add(src[i]);
6749                    }
6750                }
6751            } else {
6752                // Top prediction: add first row from top neighbor.
6753                let (src_r, src_c, src_b) = match blk {
6754                    2 => (mb_row, mb_col, 0),
6755                    3 => (mb_row, mb_col, 1),
6756                    0 => (mb_row.saturating_sub(1), mb_col, 2),
6757                    1 => (mb_row.saturating_sub(1), mb_col, 3),
6758                    4 | 5 => (mb_row.saturating_sub(1), mb_col, blk),
6759                    _ => (mb_row.saturating_sub(1), mb_col, blk),
6760                };
6761                if (blk == 2 || blk == 3) || mb_row > 0 {
6762                    let src = self.wmv2_get_ac_val(src_r, src_c, src_b);
6763                    for i in 1..8usize {
6764                        block[i] = block[i].wrapping_add(src[8 + i]);
6765                    }
6766                }
6767            }
6768        }
6769
6770        // Store our AC predictors for future blocks.
6771        for i in 1..8usize {
6772            cur[i] = block[i << 3];
6773        }
6774        cur[9..16].copy_from_slice(&block[1..8]);
6775        self.wmv2_set_ac_val(mb_row, mb_col, blk, cur);
6776    }
6777
6778    #[inline(always)]
6779    fn wmv2_unquantize_h263_intra(&self, block: &mut [i16; 64], qscale: i32, dc_scale: i32) {
6780        // Direct port of upstream dct_unquantize_h263_intra_c().
6781        let qmul = qscale << 1;
6782        let qadd = (qscale - 1) | 1;
6783
6784        block[0] = ((block[0] as i32) * dc_scale) as i16;
6785        for i in 1..64usize {
6786            let mut level = block[i] as i32;
6787            if level != 0 {
6788                if level < 0 {
6789                    level = level * qmul - qadd;
6790                } else {
6791                    level = level * qmul + qadd;
6792                }
6793                block[i] = level as i16;
6794            }
6795        }
6796    }
6797
6798    fn wmv2_decode_block_intra_ref(
6799        &mut self,
6800        br: &mut BitReader<'_>,
6801        mb_row: usize,
6802        mb_col: usize,
6803        blk: usize,
6804        coded: bool,
6805        qscale: i32,
6806        ac_pred: bool,
6807    ) -> Result<[i16; 64]> {
6808        let is_luma = blk < 4;
6809        let dc_scale = wmv2_dc_scale(qscale, is_luma);
6810
6811        // DC diff VLC + sign, predictor in DC level domain.
6812        let diff = self.wmv2_decode_dc_diff(br, !is_luma);
6813        let (pred_level, dir) = self.wmv2_dc_pred.predict(mb_row, mb_col, blk, dc_scale);
6814        let level = pred_level + diff;
6815        self.wmv2_dc_pred
6816            .store(mb_row, mb_col, blk, level * dc_scale);
6817
6818        let mut block = [0i16; 64];
6819        block[0] = level as i16;
6820
6821        // Choose RL table.
6822        let rl = if is_luma {
6823            &self.wmv2_rl[(self.wmv2_rl_table_index as usize).min(2)]
6824        } else {
6825            &self.wmv2_rl[3 + (self.wmv2_rl_chroma_table_index as usize).min(2)]
6826        };
6827
6828        // Scan table selection.
6829        let scan = if ac_pred {
6830            if dir == 0 {
6831                &FF_WMV1_SCANTABLE[3] // intra_v
6832            } else {
6833                &FF_WMV1_SCANTABLE[2] // intra_h
6834            }
6835        } else {
6836            &FF_WMV1_SCANTABLE[1] // intra default
6837        };
6838
6839        let mut i: i32 = 0;
6840        let qmul: i32 = 1;
6841        let run_diff: i32 = 1; // msmpeg4_version >= WMV1
6842
6843        if coded {
6844            loop {
6845                let (mut level_uq, mut run) = rl
6846                    .decode_sym(br, 0)
6847                    .ok_or_else(|| DecoderError::InvalidData("WMV2: tcoeff VLC underrun".into()))?;
6848
6849                if level_uq == 0 {
6850                    // escape: prefix bits decide which escape.
6851                    let b0 = br.peek_bits(1).unwrap_or(0);
6852                    if b0 == 1 {
6853                        // escape1: prefix '1'
6854                        br.skip_bits(1);
6855                        let (lvl2, run2) = rl.decode_sym(br, 0).ok_or_else(|| {
6856                            DecoderError::InvalidData("WMV2: escape1 VLC underrun".into())
6857                        })?;
6858                        level_uq = lvl2;
6859                        run = run2;
6860                        i += run;
6861                        let last = ((run >> 7) & 1) as usize;
6862                        let base_run = ((run - 1) & 63) as usize;
6863                        level_uq += rl.max_level_for(last, base_run) * qmul;
6864                        let sign = br.read_bit().unwrap_or(false);
6865                        if sign {
6866                            level_uq = -level_uq;
6867                        }
6868                    } else {
6869                        let b1 = br.peek_bits(2).unwrap_or(0) & 1;
6870                        if b1 == 1 {
6871                            // escape2: prefix '01'
6872                            br.skip_bits(2);
6873                            let (lvl2, run2) = rl.decode_sym(br, 0).ok_or_else(|| {
6874                                DecoderError::InvalidData("WMV2: escape2 VLC underrun".into())
6875                            })?;
6876                            level_uq = lvl2;
6877                            run = run2;
6878                            let last = ((run >> 7) & 1) as usize;
6879                            let base_level = (level_uq / qmul).unsigned_abs() as usize;
6880                            i += run + rl.max_run_for(last, base_level) + run_diff;
6881                            let sign = br.read_bit().unwrap_or(false);
6882                            if sign {
6883                                level_uq = -level_uq;
6884                            }
6885                        } else {
6886                            // escape3: prefix '00'
6887                            br.skip_bits(2);
6888                            let last = br.read_bit().unwrap_or(false);
6889                            if self.wmv2_esc3_level_length == 0 {
6890                                // derive esc3 lengths (WMV2: msmpeg4_version > V3)
6891                                let ll: u8 = if qscale < 8 {
6892                                    let mut x = br.read_bits(3).unwrap_or(0) as u8;
6893                                    if x == 0 {
6894                                        x = 8 + br.read_bits(1).unwrap_or(0) as u8;
6895                                    }
6896                                    x
6897                                } else {
6898                                    let mut x: u8 = 2;
6899                                    while x < 8 && br.peek_bits(1).unwrap_or(1) == 0 {
6900                                        br.skip_bits(1);
6901                                        x += 1;
6902                                    }
6903                                    if x < 8 {
6904                                        br.skip_bits(1);
6905                                    }
6906                                    x
6907                                };
6908                                self.wmv2_esc3_level_length = ll;
6909                                self.wmv2_esc3_run_length =
6910                                    (br.read_bits(2).unwrap_or(0) as u8) + 3;
6911                            }
6912                            let run_abs =
6913                                br.read_bits(self.wmv2_esc3_run_length).unwrap_or(0) as i32;
6914                            let sign = br.read_bit().unwrap_or(false);
6915                            let mut lvl_abs =
6916                                br.read_bits(self.wmv2_esc3_level_length).unwrap_or(0) as i32;
6917                            if sign {
6918                                lvl_abs = -lvl_abs;
6919                            }
6920                            level_uq = lvl_abs;
6921                            i += run_abs + 1;
6922                            if last {
6923                                i += 192;
6924                            }
6925                        }
6926                    }
6927                } else {
6928                    i += run;
6929                    let sign = br.read_bit().unwrap_or(false);
6930                    if sign {
6931                        level_uq = -level_uq;
6932                    }
6933                }
6934
6935                if i > 62 {
6936                    i -= 192;
6937                    if (i & !63) != 0 {
6938                        i = 63;
6939                    }
6940                    if i < 0 {
6941                        return Err(DecoderError::InvalidData(
6942                            "WMV2: negative coeff index (bitstream damaged)".into(),
6943                        ));
6944                    }
6945                    let pos = scan[i as usize];
6946                    if pos < 64 {
6947                        block[pos] = level_uq as i16;
6948                    }
6949                    break;
6950                }
6951
6952                if i < 0 {
6953                    return Err(DecoderError::InvalidData(
6954                        "WMV2: negative coeff index (bitstream damaged)".into(),
6955                    ));
6956                }
6957                let pos = scan[i as usize];
6958                if pos < 64 {
6959                    block[pos] = level_uq as i16;
6960                }
6961            }
6962        }
6963
6964        // AC prediction always runs (even if not coded).
6965        self.wmv2_pred_ac(mb_row, mb_col, blk, dir, ac_pred, &mut block);
6966
6967        // H.263 intra unquantization to match upstream pipeline.
6968        self.wmv2_unquantize_h263_intra(&mut block, qscale, dc_scale);
6969
6970        Ok(block)
6971    }
6972    fn wmv2_decode_block_inter_ref(
6973        &mut self,
6974        br: &mut BitReader<'_>,
6975        blk: usize,
6976        coded: bool,
6977        qscale: i32,
6978        scan: &[usize; 64],
6979    ) -> Result<[i16; 64]> {
6980        let mut block = [0i16; 64];
6981        if !coded {
6982            return Ok(block);
6983        }
6984
6985        let rl = &self.wmv2_rl[3 + (self.wmv2_rl_table_index as usize).min(2)];
6986
6987        let qmul = qscale << 1;
6988        let qadd = (qscale - 1) | 1;
6989        let run_diff: i32 = 1; // wmv2 != v2
6990
6991        let mut i: i32 = -1;
6992
6993        loop {
6994            let (mut level_uq, mut run) = rl.decode_sym(br, qscale).ok_or_else(|| {
6995                DecoderError::InvalidData("WMV2: inter tcoeff VLC underrun".into())
6996            })?;
6997
6998            if level_uq == 0 {
6999                // escape
7000                let b0 = br.peek_bits(1).unwrap_or(0);
7001                if b0 == 1 {
7002                    // escape1
7003                    br.skip_bits(1);
7004                    let (lvl2, run2) = rl.decode_sym(br, qscale).ok_or_else(|| {
7005                        DecoderError::InvalidData("WMV2: inter escape1 VLC underrun".into())
7006                    })?;
7007                    level_uq = lvl2;
7008                    run = run2;
7009                    i += run;
7010                    let last = ((run >> 7) & 1) as usize;
7011                    let base_run = ((run - 1) & 63) as usize;
7012                    level_uq += rl.max_level_for(last, base_run) * qmul;
7013                    let sign = br.read_bit().unwrap_or(false);
7014                    if sign {
7015                        level_uq = -level_uq;
7016                    }
7017                } else {
7018                    let b1 = br.peek_bits(2).unwrap_or(0) & 1;
7019                    if b1 == 1 {
7020                        // escape2
7021                        br.skip_bits(2);
7022                        let (lvl2, run2) = rl.decode_sym(br, qscale).ok_or_else(|| {
7023                            DecoderError::InvalidData("WMV2: inter escape2 VLC underrun".into())
7024                        })?;
7025                        level_uq = lvl2;
7026                        run = run2;
7027                        let last = ((run >> 7) & 1) as usize;
7028                        let base_level = (level_uq / qmul).unsigned_abs() as usize;
7029                        i += run + rl.max_run_for(last, base_level) + run_diff;
7030                        let sign = br.read_bit().unwrap_or(false);
7031                        if sign {
7032                            level_uq = -level_uq;
7033                        }
7034                    } else {
7035                        // escape3
7036                        br.skip_bits(2);
7037                        let last = br.read_bit().unwrap_or(false);
7038                        if self.wmv2_esc3_level_length == 0 {
7039                            let ll: u8 = if qscale < 8 {
7040                                let mut x = br.read_bits(3).unwrap_or(0) as u8;
7041                                if x == 0 {
7042                                    x = 8 + br.read_bits(1).unwrap_or(0) as u8;
7043                                }
7044                                x
7045                            } else {
7046                                let mut x: u8 = 2;
7047                                while x < 8 && br.peek_bits(1).unwrap_or(1) == 0 {
7048                                    br.skip_bits(1);
7049                                    x += 1;
7050                                }
7051                                if x < 8 {
7052                                    br.skip_bits(1);
7053                                }
7054                                x
7055                            };
7056                            self.wmv2_esc3_level_length = ll;
7057                            self.wmv2_esc3_run_length = (br.read_bits(2).unwrap_or(0) as u8) + 3;
7058                        }
7059                        let run_abs = br.read_bits(self.wmv2_esc3_run_length).unwrap_or(0) as i32;
7060                        let sign = br.read_bit().unwrap_or(false);
7061                        let mut lvl_abs =
7062                            br.read_bits(self.wmv2_esc3_level_length).unwrap_or(0) as i32;
7063                        if sign {
7064                            lvl_abs = -lvl_abs;
7065                        }
7066                        if lvl_abs > 0 {
7067                            level_uq = lvl_abs * qmul + qadd;
7068                        } else {
7069                            level_uq = lvl_abs * qmul - qadd;
7070                        }
7071                        i += run_abs + 1;
7072                        if last {
7073                            i += 192;
7074                        }
7075                    }
7076                }
7077            } else {
7078                i += run;
7079                let sign = br.read_bit().unwrap_or(false);
7080                if sign {
7081                    level_uq = -level_uq;
7082                }
7083            }
7084
7085            if i > 62 {
7086                i -= 192;
7087                if (i & !63) != 0 {
7088                    i = 63;
7089                }
7090                if i < 0 {
7091                    return Err(DecoderError::InvalidData(
7092                        "WMV2: negative coeff index (bitstream damaged)".into(),
7093                    ));
7094                }
7095                let pos = scan[i as usize];
7096                if pos < 64 {
7097                    block[pos] = level_uq as i16;
7098                }
7099                break;
7100            }
7101
7102            if i < 0 {
7103                return Err(DecoderError::InvalidData(
7104                    "WMV2: negative coeff index (bitstream damaged)".into(),
7105                ));
7106            }
7107            let pos = scan[i as usize];
7108            if pos < 64 {
7109                block[pos] = level_uq as i16;
7110            }
7111        }
7112
7113        let _ = blk;
7114        Ok(block)
7115    }
7116    fn wmv2_parse_mb_skip(
7117        &mut self,
7118        br: &mut BitReader<'_>,
7119        mb_w: usize,
7120        mb_h: usize,
7121    ) -> Result<()> {
7122        // upstream wmv2dec.c parse_mb_skip
7123        let skip_type = br
7124            .read_bits(2)
7125            .ok_or_else(|| DecoderError::InvalidData("WMV2: missing skip_type".into()))?
7126            as u8;
7127        self.wmv2_skip_type = skip_type;
7128        if self.wmv2_mb_skip.len() != mb_w * mb_h {
7129            self.wmv2_mb_skip.resize(mb_w * mb_h, false);
7130        }
7131        for v in self.wmv2_mb_skip.iter_mut() {
7132            *v = false;
7133        }
7134
7135        match skip_type {
7136            0 => {
7137                // SKIP_TYPE_NONE
7138            }
7139            1 => {
7140                // SKIP_TYPE_MPEG: 1 bit per MB
7141                if br.bits_left() < (mb_w * mb_h) as isize {
7142                    return Err(DecoderError::InvalidData("WMV2: skip map truncated".into()));
7143                }
7144                for y in 0..mb_h {
7145                    for x in 0..mb_w {
7146                        let b = br.read_bit().unwrap_or(false);
7147                        self.wmv2_mb_skip[y * mb_w + x] = b;
7148                    }
7149                }
7150            }
7151            2 => {
7152                // SKIP_TYPE_ROW
7153                for y in 0..mb_h {
7154                    let all = br.read_bit().ok_or_else(|| {
7155                        DecoderError::InvalidData("WMV2: skip row flag missing".into())
7156                    })?;
7157                    if all {
7158                        for x in 0..mb_w {
7159                            self.wmv2_mb_skip[y * mb_w + x] = true;
7160                        }
7161                    } else {
7162                        for x in 0..mb_w {
7163                            let b = br.read_bit().unwrap_or(false);
7164                            self.wmv2_mb_skip[y * mb_w + x] = b;
7165                        }
7166                    }
7167                }
7168            }
7169            3 => {
7170                // SKIP_TYPE_COL
7171                for x in 0..mb_w {
7172                    let all = br.read_bit().ok_or_else(|| {
7173                        DecoderError::InvalidData("WMV2: skip col flag missing".into())
7174                    })?;
7175                    if all {
7176                        for y in 0..mb_h {
7177                            self.wmv2_mb_skip[y * mb_w + x] = true;
7178                        }
7179                    } else {
7180                        for y in 0..mb_h {
7181                            let b = br.read_bit().unwrap_or(false);
7182                            self.wmv2_mb_skip[y * mb_w + x] = b;
7183                        }
7184                    }
7185                }
7186            }
7187            _ => {}
7188        }
7189
7190        // upstream also checks coded_mb_count against bits_left; keep a light version.
7191        let coded = self.wmv2_mb_skip.iter().filter(|s| !**s).count();
7192        if coded as isize > br.bits_left() {
7193            return Err(DecoderError::InvalidData(
7194                "WMV2: coded MB count exceeds remaining bits".into(),
7195            ));
7196        }
7197        Ok(())
7198    }
7199
7200    #[inline(always)]
7201    fn wmv2_motion_get(&self, mb_row: isize, mb_col: isize) -> (i32, i32) {
7202        if mb_row < 0 || mb_col < 0 {
7203            return (0, 0);
7204        }
7205        let mb_w = self.width_mb as isize;
7206        let mb_h = self.height_mb as isize;
7207        if mb_row >= mb_h || mb_col >= mb_w {
7208            return (0, 0);
7209        }
7210        let idx = (mb_row as usize) * (mb_w as usize) + (mb_col as usize);
7211        if idx < self.wmv2_motion.len() {
7212            self.wmv2_motion[idx]
7213        } else {
7214            (0, 0)
7215        }
7216    }
7217
7218    #[inline(always)]
7219    fn wmv2_motion_set(&mut self, mb_row: usize, mb_col: usize, mv: (i32, i32)) {
7220        let mb_w = self.width_mb as usize;
7221        let idx = mb_row * mb_w + mb_col;
7222        if self.wmv2_motion.len() != mb_w * (self.height_mb as usize) {
7223            self.wmv2_motion
7224                .resize(mb_w * (self.height_mb as usize), (0, 0));
7225        }
7226        if idx < self.wmv2_motion.len() {
7227            self.wmv2_motion[idx] = mv;
7228        }
7229    }
7230
7231    #[inline(always)]
7232    fn wmv2_pred_motion(
7233        &self,
7234        br: &mut BitReader<'_>,
7235        mb_row: usize,
7236        mb_col: usize,
7237        first_slice_line: bool,
7238    ) -> (i32, i32) {
7239        // upstream wmv2dec.c wmv2_pred_motion (MB-level approximation).
7240        let a = self.wmv2_motion_get(mb_row as isize, mb_col as isize - 1);
7241        let b = self.wmv2_motion_get(mb_row as isize - 1, mb_col as isize);
7242        let c = self.wmv2_motion_get(mb_row as isize - 1, mb_col as isize + 1);
7243
7244        let diff =
7245            if mb_col != 0 && !first_slice_line && !self.wmv2_mspel && self.wmv2_top_left_mv_flag {
7246                let dx = (a.0 - b.0).abs();
7247                let dy = (a.1 - b.1).abs();
7248                dx.max(dy)
7249            } else {
7250                0
7251            };
7252
7253        let t = if diff >= 8 {
7254            if br.read_bit().unwrap_or(false) { 1 } else { 0 }
7255        } else {
7256            2
7257        };
7258
7259        match t {
7260            0 => a,
7261            1 => b,
7262            _ => {
7263                if first_slice_line {
7264                    a
7265                } else {
7266                    (mid_pred(a.0, b.0, c.0), mid_pred(a.1, b.1, c.1))
7267                }
7268            }
7269        }
7270    }
7271
7272    #[inline(always)]
7273    fn wmv2_decode_motion_ref(&self, br: &mut BitReader<'_>, pred: (i32, i32)) -> (i32, i32) {
7274        // Direct port of upstream msmpeg4dec.c ff_msmpeg4_decode_motion.
7275        let tbl = &self.wmv2_mv_vlc[self.wmv2_mv_table_index.min(1)];
7276        let sym = tbl.decode(br).unwrap_or(0) as u16;
7277        let (mut mx, mut my) = if sym != 0 {
7278            ((sym >> 8) as i32, (sym & 0xff) as i32)
7279        } else {
7280            // Escape: 6-bit mx + 6-bit my.
7281            (
7282                br.read_bits(6).unwrap_or(0) as i32,
7283                br.read_bits(6).unwrap_or(0) as i32,
7284            )
7285        };
7286
7287        mx += pred.0 - 32;
7288        my += pred.1 - 32;
7289        // WARNING: they do not do exactly modulo encoding.
7290        if mx <= -64 {
7291            mx += 64;
7292        } else if mx >= 64 {
7293            mx -= 64;
7294        }
7295        if my <= -64 {
7296            my += 64;
7297        } else if my >= 64 {
7298            my -= 64;
7299        }
7300        (mx, my)
7301    }
7302
7303    // ── WMV2 I-frame ──────────────────────────────────────────────────────────
7304
7305    fn wmv2_decode_intra(
7306        &mut self,
7307        payload: &[u8],
7308        hdr: &Wmv2FrameHeader,
7309        frame: &mut YuvFrame,
7310    ) -> Result<()> {
7311        // Start at picture header end.
7312        let mut br = BitReader::new_at(payload, hdr.header_bits);
7313
7314        // upstream: ff_wmv2_decode_secondary_picture_header() (I-picture branch).
7315        // We parse/consume the fields that affect alignment and DC VLC selection.
7316        self.wmv2_j_type = if self.wmv2_j_type_bit {
7317            br.read_bit().unwrap_or(false)
7318        } else {
7319            false
7320        };
7321        if self.wmv2_j_type {
7322            // IntraX8 (j_type) is not handled in this A build.
7323            return Ok(());
7324        }
7325
7326        self.wmv2_per_mb_rl_table = if self.wmv2_per_mb_rl_bit {
7327            br.read_bit().unwrap_or(false)
7328        } else {
7329            false
7330        };
7331        if !self.wmv2_per_mb_rl_table {
7332            self.wmv2_rl_chroma_table_index = decode012(&mut br);
7333            self.wmv2_rl_table_index = decode012(&mut br);
7334        }
7335        self.wmv2_dc_table_index = br.read_bit().unwrap_or(false) as usize;
7336
7337        let mb_w = self.width_mb as usize;
7338        let mb_h = self.height_mb as usize;
7339
7340        // Reset predictors.
7341        self.wmv2_dc_pred = Wmv2DcPredBuffer::new(mb_w, mb_h);
7342        for v in self.wmv2_coded_block.iter_mut() {
7343            *v = 0;
7344        }
7345
7346        self.wmv2_reset_picture_state();
7347
7348        let qscale = hdr.pquant as i32;
7349        // WMV2 picture header variant used here (upstream-min) does not carry ttcoef;
7350        // keep using intra VLC set 0 to get the stream back in sync.
7351
7352        for mb_row in 0..mb_h {
7353            for mb_col in 0..mb_w {
7354                if br.is_empty() {
7355                    break;
7356                }
7357
7358                // upstream: code = get_vlc2(ff_msmp4_mb_i_vlc)
7359                let code = self.wmv2_mb_i_vlc.decode(&mut br).unwrap_or(0) as u32;
7360
7361                // Predict coded block pattern.
7362                let mut cbp: u8 = 0;
7363                for i in 0..6usize {
7364                    let mut val = ((code >> (5 - i)) & 1) as u8;
7365                    if i < 4 {
7366                        let pred = self.wmv2_coded_block_pred(mb_row, mb_col, i);
7367                        val ^= pred;
7368                        self.wmv2_coded_block_store(mb_row, mb_col, i, val);
7369                    }
7370                    cbp |= val << (5 - i);
7371                }
7372
7373                // upstream: h->c.ac_pred = get_bits1();
7374                let ac_pred = br.read_bit().unwrap_or(false);
7375
7376                // upstream: if (per_mb_rl_table && cbp) rl_table_index = decode012();
7377                if self.wmv2_per_mb_rl_table && cbp != 0 {
7378                    let rl_idx = decode012(&mut br);
7379                    self.wmv2_rl_table_index = rl_idx;
7380                    self.wmv2_rl_chroma_table_index = rl_idx;
7381                }
7382
7383                for blk in 0..6usize {
7384                    let coded = ((cbp >> (5 - blk)) & 1) != 0;
7385                    let mut block = self.wmv2_decode_block_intra_ref(
7386                        &mut br, mb_row, mb_col, blk, coded, qscale, ac_pred,
7387                    )?;
7388
7389                    let (is_luma, bx, by, stride, _ph) =
7390                        block_coords(mb_row as u32, mb_col as u32, blk, frame.width, frame.height);
7391                    let plane: &mut Vec<u8> = if is_luma {
7392                        &mut frame.y
7393                    } else if blk == 4 {
7394                        &mut frame.cb
7395                    } else {
7396                        &mut frame.cr
7397                    };
7398                    let dst_off = by * stride + bx;
7399                    wmv2dsp::wmv2_idct_put(plane, dst_off, stride, &mut block);
7400                }
7401            }
7402        }
7403
7404        self.wmv2_ref = Some(frame.clone());
7405        Ok(())
7406    }
7407    // ── WMV2 P-frame ──────────────────────────────────────────────────────────
7408
7409    fn wmv2_decode_p(
7410        &mut self,
7411        payload: &[u8],
7412        hdr: &Wmv2FrameHeader,
7413        frame: &mut YuvFrame,
7414    ) -> Result<()> {
7415        // Start at picture header end.
7416        let mut br = BitReader::new_at(payload, hdr.header_bits);
7417
7418        let mb_w = self.width_mb as usize;
7419        let mb_h = self.height_mb as usize;
7420        let qscale = hdr.pquant as i32;
7421
7422        // upstream: ff_wmv2_decode_secondary_picture_header() (P-picture branch).
7423        self.wmv2_j_type = false;
7424        self.wmv2_parse_mb_skip(&mut br, mb_w, mb_h)?;
7425        let cbp_index = decode012(&mut br);
7426        self.wmv2_cbp_table_index = wmv2_get_cbp_table_index(qscale, cbp_index);
7427
7428        self.wmv2_mspel = if self.wmv2_mspel_bit {
7429            br.read_bit().unwrap_or(false)
7430        } else {
7431            false
7432        };
7433
7434        if self.wmv2_abt_flag {
7435            self.wmv2_per_mb_abt = br.read_bit().unwrap_or(false) ^ true;
7436            if !self.wmv2_per_mb_abt {
7437                self.wmv2_abt_type = decode012(&mut br);
7438            }
7439        } else {
7440            self.wmv2_per_mb_abt = false;
7441            self.wmv2_abt_type = 0;
7442        }
7443
7444        self.wmv2_per_mb_rl_table = if self.wmv2_per_mb_rl_bit {
7445            br.read_bit().unwrap_or(false)
7446        } else {
7447            false
7448        };
7449        if !self.wmv2_per_mb_rl_table {
7450            self.wmv2_rl_table_index = decode012(&mut br);
7451            self.wmv2_rl_chroma_table_index = self.wmv2_rl_table_index;
7452        }
7453        if br.bits_left() < 2 {
7454            return Err(DecoderError::InvalidData(
7455                "WMV2: truncated secondary header".into(),
7456            ));
7457        }
7458        self.wmv2_dc_table_index = br.read_bit().unwrap_or(false) as usize;
7459        self.wmv2_mv_table_index = br.read_bit().unwrap_or(false) as usize;
7460
7461        // Reset predictors for this picture.
7462        self.wmv2_dc_pred = Wmv2DcPredBuffer::new(mb_w, mb_h);
7463        if self.wmv2_motion.len() != mb_w * mb_h {
7464            self.wmv2_motion.resize(mb_w * mb_h, (0, 0));
7465        }
7466        for v in self.wmv2_motion.iter_mut() {
7467            *v = (0, 0);
7468        }
7469
7470        self.wmv2_reset_picture_state();
7471
7472        let reference = match &self.wmv2_ref {
7473            Some(r) => r.clone(),
7474            None => YuvFrame::new(frame.width, frame.height),
7475        };
7476
7477        for mb_row in 0..mb_h {
7478            let first_slice_line =
7479                self.wmv2_slice_height != 0 && (mb_row % self.wmv2_slice_height == 0);
7480            for mb_col in 0..mb_w {
7481                if br.bits_left() <= 0 {
7482                    break;
7483                }
7484                let mi = mb_row * mb_w + mb_col;
7485                if mi < self.wmv2_mb_skip.len() && self.wmv2_mb_skip[mi] {
7486                    if self.wmv2_mspel {
7487                        wmv2_mspel_motion_mb(frame, &reference, mb_row, mb_col, 0, 0, 0);
7488                    } else {
7489                        motion_compensate_mb(frame, &reference, mb_row, mb_col, 0, 0);
7490                    }
7491                    self.wmv2_motion_set(mb_row, mb_col, (0, 0));
7492                    continue;
7493                }
7494
7495                let code = self.wmv2_mb_non_intra_vlc[self.wmv2_cbp_table_index.min(3)]
7496                    .decode(&mut br)
7497                    .ok_or_else(|| {
7498                        DecoderError::InvalidData("WMV2: MB header VLC underrun".into())
7499                    })?;
7500
7501                let mb_intra = (code & 0x40) == 0;
7502                let cbp = (code & 0x3f) as u8;
7503
7504                if !mb_intra {
7505                    let pred = self.wmv2_pred_motion(&mut br, mb_row, mb_col, first_slice_line);
7506
7507                    if cbp != 0 && self.wmv2_per_mb_rl_table {
7508                        self.wmv2_rl_table_index = decode012(&mut br);
7509                        self.wmv2_rl_chroma_table_index = self.wmv2_rl_table_index;
7510                    }
7511
7512                    let mut per_block_abt = false;
7513                    let mut abt_type = self.wmv2_abt_type;
7514                    if cbp != 0 && self.wmv2_abt_flag && self.wmv2_per_mb_abt {
7515                        per_block_abt = br.read_bit().unwrap_or(false);
7516                        if !per_block_abt {
7517                            abt_type = decode012(&mut br);
7518                        }
7519                    }
7520
7521                    let (mx, my) = self.wmv2_decode_motion_ref(&mut br, pred);
7522                    self.wmv2_hshift = if (((mx | my) & 1) != 0) && self.wmv2_mspel {
7523                        br.read_bit().unwrap_or(false) as u8
7524                    } else {
7525                        0
7526                    };
7527                    self.wmv2_motion_set(mb_row, mb_col, (mx, my));
7528
7529                    if self.wmv2_mspel {
7530                        wmv2_mspel_motion_mb(
7531                            frame,
7532                            &reference,
7533                            mb_row,
7534                            mb_col,
7535                            mx,
7536                            my,
7537                            self.wmv2_hshift,
7538                        );
7539                    } else {
7540                        motion_compensate_mb(frame, &reference, mb_row, mb_col, mx, my);
7541                    }
7542
7543                    for blk in 0..6usize {
7544                        if (cbp >> (5 - blk)) & 1 == 0 {
7545                            continue;
7546                        }
7547
7548                        let mut cur_abt = abt_type;
7549                        if per_block_abt {
7550                            cur_abt = decode012(&mut br);
7551                        }
7552
7553                        // upstream: wmv2_decode_inter_block + wmv2_add_block
7554
7555                        if cur_abt == 0 {
7556                            let scan = &FF_WMV1_SCANTABLE[0];
7557
7558                            let mut block =
7559                                self.wmv2_decode_block_inter_ref(&mut br, blk, true, qscale, scan)?;
7560
7561                            let (is_luma, bx, by, stride, _ph) = block_coords(
7562                                mb_row as u32,
7563                                mb_col as u32,
7564                                blk,
7565                                frame.width,
7566                                frame.height,
7567                            );
7568
7569                            let plane: &mut Vec<u8> = if is_luma {
7570                                &mut frame.y
7571                            } else if blk == 4 {
7572                                &mut frame.cb
7573                            } else {
7574                                &mut frame.cr
7575                            };
7576
7577                            let dst_off = by * stride + bx;
7578
7579                            wmv2dsp::wmv2_idct_add(plane, dst_off, stride, &mut block);
7580                        } else {
7581                            const SUB_CBP_TABLE: [u8; 3] = [2, 3, 1];
7582
7583                            let scantable = if cur_abt == 1 {
7584                                &FF_WMV2_SCANTABLE_A
7585                            } else {
7586                                &FF_WMV2_SCANTABLE_B
7587                            };
7588
7589                            let sub_cbp = SUB_CBP_TABLE[decode012(&mut br) as usize];
7590
7591                            let mut block1 = [0i16; 64];
7592
7593                            let mut block2 = [0i16; 64];
7594
7595                            if (sub_cbp & 1) != 0 {
7596                                block1 = self.wmv2_decode_block_inter_ref(
7597                                    &mut br, blk, true, qscale, scantable,
7598                                )?;
7599                            }
7600
7601                            if (sub_cbp & 2) != 0 {
7602                                block2 = self.wmv2_decode_block_inter_ref(
7603                                    &mut br, blk, true, qscale, scantable,
7604                                )?;
7605                            }
7606
7607                            let (is_luma, bx, by, stride, _ph) = block_coords(
7608                                mb_row as u32,
7609                                mb_col as u32,
7610                                blk,
7611                                frame.width,
7612                                frame.height,
7613                            );
7614
7615                            let plane: &mut Vec<u8> = if is_luma {
7616                                &mut frame.y
7617                            } else if blk == 4 {
7618                                &mut frame.cb
7619                            } else {
7620                                &mut frame.cr
7621                            };
7622
7623                            let dst_off = by * stride + bx;
7624
7625                            match cur_abt {
7626                                1 => {
7627                                    // 8x4 + 8x4 (top/bottom)
7628
7629                                    ffidct::ff_simple_idct84_add(
7630                                        plane,
7631                                        dst_off,
7632                                        stride,
7633                                        &mut block1,
7634                                    );
7635
7636                                    ffidct::ff_simple_idct84_add(
7637                                        plane,
7638                                        dst_off + 4 * stride,
7639                                        stride,
7640                                        &mut block2,
7641                                    );
7642                                }
7643
7644                                2 => {
7645                                    // 4x8 + 4x8 (left/right)
7646
7647                                    ffidct::ff_simple_idct48_add(
7648                                        plane,
7649                                        dst_off,
7650                                        stride,
7651                                        &mut block1,
7652                                    );
7653
7654                                    ffidct::ff_simple_idct48_add(
7655                                        plane,
7656                                        dst_off + 4,
7657                                        stride,
7658                                        &mut block2,
7659                                    );
7660                                }
7661
7662                                _ => {}
7663                            }
7664                        }
7665                    }
7666                } else {
7667                    // Intra MB in P-picture.
7668                    let ac_pred = br.read_bit().unwrap_or(false);
7669                    if self.wmv2_per_mb_rl_table && cbp != 0 {
7670                        let rl_idx = decode012(&mut br);
7671                        self.wmv2_rl_table_index = rl_idx;
7672                        self.wmv2_rl_chroma_table_index = rl_idx;
7673                    }
7674
7675                    for blk in 0..6usize {
7676                        let coded = ((cbp >> (5 - blk)) & 1) != 0;
7677                        let mut block = self.wmv2_decode_block_intra_ref(
7678                            &mut br, mb_row, mb_col, blk, coded, qscale, ac_pred,
7679                        )?;
7680
7681                        let (is_luma, bx, by, stride, _ph) = block_coords(
7682                            mb_row as u32,
7683                            mb_col as u32,
7684                            blk,
7685                            frame.width,
7686                            frame.height,
7687                        );
7688                        let plane: &mut Vec<u8> = if is_luma {
7689                            &mut frame.y
7690                        } else if blk == 4 {
7691                            &mut frame.cb
7692                        } else {
7693                            &mut frame.cr
7694                        };
7695                        let dst_off = by * stride + bx;
7696                        wmv2dsp::wmv2_idct_put(plane, dst_off, stride, &mut block);
7697                    }
7698                    self.wmv2_motion_set(mb_row, mb_col, (0, 0));
7699                }
7700            }
7701        }
7702
7703        self.wmv2_ref = Some(frame.clone());
7704        Ok(())
7705    }
7706}
7707
7708// ─── WMV2 AC block decoder ────────────────────────────────────────────────────
7709// Decodes AC coefficients using WMV2 TCOEF VLC.
7710// For intra: fills coeff[1..63] (coeff[0] is DC, already set by caller).
7711// For inter: fills coeff[0..63] (all AC).
7712// Escape is Mode-3 only: 1-bit LAST + 6-bit RUN + 8-bit |LEVEL| + 1-bit SIGN.
7713
7714fn wmv2_decode_ac_block(
7715    br: &mut BitReader<'_>,
7716    ac_vlc: &VlcTable,
7717    pquant: i32,
7718    coeff: &mut [i32; 64],
7719    is_intra: bool,
7720) {
7721    // WMV2/MSMPEG4 uses the standard zig-zag scan by default.
7722    // (AC prediction, if implemented, switches to horizontal/vertical scans.)
7723    let scan = &ZIGZAG;
7724    let mut idx = if is_intra { 1usize } else { 0 };
7725
7726    while let Some(sym) = ac_vlc.decode(br) {
7727        let (run, signed_level, last) = if sym == VLC_ESCAPE {
7728            // WMV2/MSMPEG4 uses the same 3-mode escape structure as VC-1:
7729            //   0  -> mode1 (level offset)
7730            //   10 -> mode2 (run offset)
7731            //   11 -> mode3 (absolute)
7732            decode_escape_coeff(br, ac_vlc)
7733        } else {
7734            let (r, l, last) = unpack_rl(sym);
7735            let sign = br.read_bit().unwrap_or(false);
7736            (r, if sign { -(l as i32) } else { l as i32 }, last)
7737        };
7738
7739        idx = idx.saturating_add(run as usize);
7740        if idx >= 64 {
7741            break;
7742        }
7743
7744        // Uniform quantization.
7745        let q = iquant_uniform(signed_level, pquant, false);
7746        coeff[scan[idx]] = q;
7747
7748        idx += 1;
7749        if last || br.is_empty() {
7750            break;
7751        }
7752    }
7753}
7754
7755// ─── WMV2 MV reader ───────────────────────────────────────────────────────────
7756// Reads a differential MV using a fixed 7-bit Huffman code (simplified from
7757// H.263 MVD table) then adds the median predictor.
7758
7759fn wmv2_read_mv(
7760    br: &mut BitReader<'_>,
7761    mv_pred: &MvPredictor,
7762    mb_row: usize,
7763    mb_col: usize,
7764    mv_range: i32,
7765) -> (i32, i32) {
7766    let (px, py) = mv_pred.predict(mb_row, mb_col);
7767    let dx = wmv2_read_mv_component(br, mv_range);
7768    let dy = wmv2_read_mv_component(br, mv_range);
7769    (px + dx, py + dy)
7770}
7771
7772/// Read one MV component using H.263-style VLC differential coding.
7773/// Values are half-pel units in range [-mv_range, mv_range-1].
7774fn wmv2_read_mv_component(br: &mut BitReader<'_>, mv_range: i32) -> i32 {
7775    // H.263 MVD VLC: unary + suffix
7776    // Code for 0:     "1"         (1 bit)
7777    // Code for ±1:    "010"/"011" (3 bits)
7778    // Code for ±2:    "00110"/"00111"
7779    // etc.  — this is a simple magnitude + sign scheme
7780    let mag = {
7781        let mut m = 0i32;
7782        loop {
7783            if br.read_bit().unwrap_or(true) {
7784                break;
7785            }
7786            m += 1;
7787            if m >= mv_range {
7788                break;
7789            }
7790        }
7791        m
7792    };
7793    if mag == 0 {
7794        return 0;
7795    }
7796    let sign = br.read_bit().unwrap_or(false);
7797    if sign { -mag } else { mag }
7798}