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, PictureHeader, SequenceHeader};
24use crate::vlc::{
25    cbpcy_i_vlc, cbpcy_p_vlc, dc_chroma_vlc, dc_luma_vlc, inter_tcoef_vlc, intra_tcoef_vlc,
26    mv_diff_vlc, ttblk_vlc, ttmb_vlc, unpack_rl, wmv2_cbpc_p_vlc, wmv2_cbpy_vlc,
27    wmv2_tcoef_inter_vlc, wmv2_tcoef_intra_vlc, VlcTable, SCAN_INTRA, SCAN_VERT, VLC_ESCAPE,
28    ZIGZAG,
29};
30use crate::vlc_tree::VlcTree;
31use crate::wmv2::{Wmv2FrameHeader, Wmv2FrameType, Wmv2Params};
32
33// ─── Frame buffer ────────────────────────────────────────────────────────────
34
35#[derive(Clone, Debug)]
36pub struct YuvFrame {
37    pub width: u32,
38    pub height: u32,
39    pub y: Vec<u8>,
40    pub cb: Vec<u8>,
41    pub cr: Vec<u8>,
42}
43
44impl YuvFrame {
45    pub fn new(width: u32, height: u32) -> Self {
46        let y_sz = (width * height) as usize;
47        let uv_sz = y_sz / 4;
48        YuvFrame {
49            width,
50            height,
51            y: vec![16u8; y_sz],
52            cb: vec![128u8; uv_sz],
53            cr: vec![128u8; uv_sz],
54        }
55    }
56
57    pub fn to_planar_u8(&self) -> Vec<u8> {
58        let mut out = Vec::with_capacity(self.y.len() + self.cb.len() + self.cr.len());
59        out.extend_from_slice(&self.y);
60        out.extend_from_slice(&self.cb);
61        out.extend_from_slice(&self.cr);
62        out
63    }
64
65    pub fn clear(&mut self) {
66        self.y.fill(16);
67        self.cb.fill(128);
68        self.cr.fill(128);
69    }
70}
71
72// ─── VC-1 IDCT ───────────────────────────────────────────────────────────────
73// SMPTE 421M §4.4.1 — Simple/Main profile integer IDCT.
74//
75// Exact butterfly constants: 12, 6, 16, 15, 9, 4  (no floating point).
76// Row pass produces values × 8; column pass divides by 128 (>>7).
77// Total normalization: ×8 / 128 = 1/16  per spatial pixel per coefficient.
78//
79
80/// One-dimensional 8-point VC-1 inverse DCT row kernel.
81/// Input/output in-place.  Output is NOT yet shifted (caller does >>7 in col pass).
82#[inline(always)]
83fn idct_row8(b: &mut [i32; 8]) {
84    // Even part
85    let t1 = 12 * b[2] + 6 * b[6];
86    let t2 = 6 * b[2] - 12 * b[6];
87    let mut s0 = b[0] + b[4];
88    let mut s1 = b[0] - b[4];
89    let s2 = s1 + (t2 >> 3);
90    let s3 = s0 - (t1 >> 3);
91    s0 += t1 >> 3;
92    s1 -= t2 >> 3;
93
94    // Odd part
95    let t0 = 16 * b[1] + 15 * b[3] + 9 * b[5] + 4 * b[7];
96    let t1 = 15 * b[1] - 4 * b[3] - 16 * b[5] - 9 * b[7];
97    let t2 = 9 * b[1] - 16 * b[3] + 4 * b[5] + 15 * b[7];
98    let t3 = 4 * b[1] - 9 * b[3] + 15 * b[5] - 16 * b[7];
99
100    b[0] = s0 + (t0 >> 3);
101    b[1] = s2 + (t2 >> 3);
102    b[2] = s1 + (t3 >> 3);
103    b[3] = s3 + (t1 >> 3);
104    b[4] = s3 - (t1 >> 3);
105    b[5] = s1 - (t3 >> 3);
106    b[6] = s2 - (t2 >> 3);
107    b[7] = s0 - (t0 >> 3);
108}
109
110/// One-dimensional 4-point VC-1 inverse DCT row kernel (SMPTE 421M §4.4.2).
111#[inline(always)]
112fn idct_row4(b: &[i32; 4]) -> [i32; 4] {
113    let t0 = 17 * b[0] + 17 * b[2];
114    let t1 = 17 * b[0] - 17 * b[2];
115    let t2 = 22 * b[1] + 10 * b[3];
116    let t3 = 10 * b[1] - 22 * b[3];
117    [t0 + t2, t1 + t3, t1 - t3, t0 - t2]
118}
119
120pub fn idct8x8(blk: &mut [i32; 64]) {
121    // Row pass (no shift — values grow by ×8 nominal)
122    for r in 0..8 {
123        let o = r * 8;
124        let mut row = [
125            blk[o],
126            blk[o + 1],
127            blk[o + 2],
128            blk[o + 3],
129            blk[o + 4],
130            blk[o + 5],
131            blk[o + 6],
132            blk[o + 7],
133        ];
134        idct_row8(&mut row);
135        blk[o..o + 8].copy_from_slice(&row);
136    }
137    // Column pass + final >>7 rounding shift
138    for c in 0..8 {
139        let mut col = [
140            blk[c],
141            blk[c + 8],
142            blk[c + 16],
143            blk[c + 24],
144            blk[c + 32],
145            blk[c + 40],
146            blk[c + 48],
147            blk[c + 56],
148        ];
149        idct_row8(&mut col);
150        for r in 0..8 {
151            blk[c + r * 8] = (col[r] + 64) >> 7;
152        }
153    }
154}
155
156fn idct8x4(blk: &mut [i32; 64]) {
157    // Row pass (8 wide, 4 high)
158    for r in 0..4 {
159        let o = r * 8;
160        let mut row = [
161            blk[o],
162            blk[o + 1],
163            blk[o + 2],
164            blk[o + 3],
165            blk[o + 4],
166            blk[o + 5],
167            blk[o + 6],
168            blk[o + 7],
169        ];
170        idct_row8(&mut row);
171        blk[o..o + 8].copy_from_slice(&row);
172    }
173    // Column pass (only 4 rows), with >>7
174    for c in 0..8 {
175        let col4 = [blk[c], blk[c + 8], blk[c + 16], blk[c + 24]];
176        let out = idct_row4(&col4);
177        for r in 0..4 {
178            blk[c + r * 8] = (out[r] + 64) >> 7;
179        }
180        let _ = col4[0]; // suppress unused warning
181    }
182}
183
184fn idct4x8(blk: &mut [i32; 64]) {
185    // Row pass (only 4 wide)
186    for r in 0..8 {
187        let o = r * 8;
188        let col4 = [blk[o], blk[o + 1], blk[o + 2], blk[o + 3]];
189        let out = idct_row4(&col4);
190        for c in 0..4 {
191            blk[o + c] = out[c];
192        }
193    }
194    // Column pass (8 rows), with >>7
195    for c in 0..4 {
196        let mut col = [
197            blk[c],
198            blk[c + 8],
199            blk[c + 16],
200            blk[c + 24],
201            blk[c + 32],
202            blk[c + 40],
203            blk[c + 48],
204            blk[c + 56],
205        ];
206        idct_row8(&mut col);
207        for r in 0..8 {
208            blk[c + r * 8] = (col[r] + 64) >> 7;
209        }
210    }
211}
212
213fn idct4x4(blk: &mut [i32; 64]) {
214    // Row pass (4 wide)
215    for r in 0..4 {
216        let o = r * 8;
217        let col4 = [blk[o], blk[o + 1], blk[o + 2], blk[o + 3]];
218        let out = idct_row4(&col4);
219        for c in 0..4 {
220            blk[o + c] = out[c];
221        }
222    }
223    // Column pass (4 rows), with >>7
224    for c in 0..4 {
225        let col4 = [blk[c], blk[c + 8], blk[c + 16], blk[c + 24]];
226        let out = idct_row4(&col4);
227        for r in 0..4 {
228            blk[c + r * 8] = (out[r] + 64) >> 7;
229        }
230    }
231}
232
233/// Apply IDCT according to transform type.
234/// tt: 0=8x8, 1=8x4_top, 2=8x4_bot, 3=4x8_left, 4=4x8_right, 5=4x4, 6=per_block
235pub fn apply_idct(blk: &mut [i32; 64], tt: u8) {
236    match tt {
237        0 => idct8x8(blk),
238        1 | 2 => idct8x4(blk),
239        3 | 4 => idct4x8(blk),
240        5 | 6 => idct4x4(blk),
241        _ => idct8x8(blk),
242    }
243}
244
245// ─── Inverse quantization ────────────────────────────────────────────────────
246// SMPTE 421M §8.1.4.  Two modes: uniform and non-uniform.
247
248fn iquant_uniform(level: i32, pquant: i32, halfqp: bool) -> i32 {
249    if level == 0 {
250        return 0;
251    }
252    let step = 2 * pquant;
253    let base = step * level.abs() + pquant;
254    let delta = if halfqp { pquant } else { 0 };
255    let result = if level > 0 {
256        base + delta
257    } else {
258        -(base + delta)
259    };
260    result.clamp(-2048, 2047)
261}
262
263fn iquant_nonuniform(level: i32, pquant: i32) -> i32 {
264    if level == 0 {
265        return 0;
266    }
267    // Non-uniform quantizer step table from SMPTE 421M Table 3
268    const STEP: [i32; 32] = [
269        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,
270        27, 29, 31, 33, 35, 37, 63,
271    ];
272    let step = STEP[(pquant as usize).min(31)];
273    let result = step * level.abs() + pquant;
274    if level > 0 {
275        result.clamp(-2048, 2047)
276    } else {
277        (-result).clamp(-2048, 2047)
278    }
279}
280
281// ─── DC step-size tables (SMPTE 421M Table 3) ───────────────────────────────
282// Indexed by pquant (0 unused, 1..31).
283// Luma and chroma have separate tables.
284// The value is multiplied by 128 here to match the IDCT normalization domain
285// (IDCT output = input / 128, so DC_recon must be in the ×128 domain).
286
287const DC_STEP_LUMA: [i32; 32] = [
288    0, // 0: unused
289    128, 256, 384, 512, 640, 768, 896, 1024, // pquant 1-8:  step = pquant
290    1152, 1280, 1408, 1536, 1664, 1792, 1920, 2048, // 9-16
291    2176, 2304, 2432, 2560, 2688, 2816, 2944, 3072, // 17-24
292    3328, 3584, 3840, 4096, 4352, 4608, 8192, // 25-31
293];
294
295const DC_STEP_CHROMA: [i32; 32] = [
296    0, // 0: unused
297    128, 128, 128, 256, 256, 384, 384, 512, // pquant 1-8
298    512, 640, 640, 768, 768, 896, 896, 1024, // 9-16
299    1024, 1152, 1152, 1280, 1280, 1408, 1408, 1536, // 17-24
300    1664, 1792, 1920, 2048, 2176, 2304, 4096, // 25-31
301];
302
303#[inline]
304fn dc_step(pquant: i32, is_luma: bool) -> i32 {
305    let idx = pquant.clamp(1, 31) as usize;
306    if is_luma {
307        DC_STEP_LUMA[idx]
308    } else {
309        DC_STEP_CHROMA[idx]
310    }
311}
312
313// ─── Loop filter (deblocking) ────────────────────────────────────────────────
314// SMPTE 421M §8.6 — Simple/Main profile deblocking filter.
315//
316// Applied at every 8-pixel block boundary in the decoded frame.
317// Modifies the two pixels straddling each boundary to reduce blocking artefacts.
318//
319//   d   = (p1 - 2*p2 + 2*p3 - p4 + 4) >> 3
320//   d   = clamp(d, -p2, 255 - p3)
321//   p2 += d;  p3 -= d
322
323#[inline(always)]
324fn lf_filter4(p: &mut [u8], a: usize, b: usize, c: usize, d: usize) {
325    let p1 = p[a] as i32;
326    let p2 = p[b] as i32;
327    let p3 = p[c] as i32;
328    let p4 = p[d] as i32;
329    let mut delta = (p1 - 2 * p2 + 2 * p3 - p4 + 4) >> 3;
330    delta = delta.clamp(-p2, 255 - p3);
331    p[b] = (p2 + delta) as u8;
332    p[c] = (p3 - delta) as u8;
333}
334
335/// Apply deblocking loop filter to one plane.
336/// `stride`: number of pixels per row (= width for luma, width/2 for chroma).
337/// `block_size`: 8 for luma, 8 for chroma (chroma plane is already half-size).
338fn loop_filter_plane(plane: &mut Vec<u8>, stride: usize, height: usize) {
339    let w = stride;
340    let h = height;
341    if w < 16 || h < 16 {
342        return;
343    } // nothing to filter
344
345    // ── Vertical boundaries (filter horizontal rows) ───────────────────────
346    // At column boundaries x = 8, 16, 24, ...
347    for x in (8..w - 1).step_by(8) {
348        for y in 0..h {
349            let base = y * w;
350            // Pixels: x-2, x-1, x, x+1
351            if x + 1 < w {
352                lf_filter4(plane, base + x - 2, base + x - 1, base + x, base + x + 1);
353            }
354        }
355    }
356
357    // ── Horizontal boundaries (filter vertical columns) ────────────────────
358    // At row boundaries y = 8, 16, 24, ...
359    for y in (8..h - 1).step_by(8) {
360        for x in 0..w {
361            // Pixels in column x at rows y-2, y-1, y, y+1
362            let a = (y - 2) * w + x;
363            let b = (y - 1) * w + x;
364            let c = y * w + x;
365            let d = (y + 1) * w + x;
366            lf_filter4(plane, a, b, c, d);
367        }
368    }
369}
370
371/// Apply loop filter to a decoded YUV frame (luma + both chroma planes).
372pub fn apply_loop_filter(frame: &mut YuvFrame) {
373    let w = frame.width as usize;
374    let h = frame.height as usize;
375    let cw = (w + 1) / 2;
376    let ch = (h + 1) / 2;
377    loop_filter_plane(&mut frame.y, w, h);
378    loop_filter_plane(&mut frame.cb, cw, ch);
379    loop_filter_plane(&mut frame.cr, cw, ch);
380}
381
382// ─── Coefficient decoder ─────────────────────────────────────────────────────
383
384/// Read raw DC differential (before prediction).
385/// Returns the signed differential value (NOT yet scaled / predicted).
386fn read_dc_diff(br: &mut BitReader<'_>, dc_vlc: &VlcTable) -> i32 {
387    let dc_size = match dc_vlc.decode(br) {
388        Some(s) if s >= 0 => s as u8,
389        _ => return 0,
390    };
391    if dc_size == 0 {
392        return 0;
393    }
394    let raw = br.read_bits(dc_size).unwrap_or(0) as i32;
395    // MSB=0 → negative (one's complement offset per SMPTE 421M §8.1.4.4)
396    if raw & (1 << (dc_size - 1)) != 0 {
397        raw
398    } else {
399        raw - (1 << dc_size) + 1
400    }
401}
402
403// ─── DC Prediction buffer ────────────────────────────────────────────────────
404// SMPTE 421M §8.1.4.6.
405//
406// For each macroblock position we store the reconstructed (post-IDCT) DC
407// value for each of the 6 blocks (Y0 Y1 Y2 Y3 Cb Cr) in the "DC scale"
408// domain (i.e. the integer value before the final /8 normalisation step).
409//
410// Prediction direction is chosen per-block by comparing the gradient
411// magnitudes of the left and top neighbours.
412
413#[derive(Clone)]
414pub struct DcPredBuffer {
415    mb_w: usize,
416    /// Stored as reconstructed DC * 8 / pquant to stay in coeff domain.
417    /// Layout: [mb_row * mb_w + mb_col][blk 0..6]
418    dc: Vec<[i32; 6]>,
419}
420
421impl DcPredBuffer {
422    pub fn new(mb_w: usize, mb_h: usize) -> Self {
423        DcPredBuffer {
424            mb_w,
425            dc: vec![[1024i32; 6]; mb_w * mb_h],
426        }
427    }
428
429    /// Return the predicted DC for block `blk` at (mb_row, mb_col).
430    /// Also decides prediction direction (horizontal vs vertical).
431    /// Returns (pred_value, use_left: bool).
432    pub fn predict(&self, mb_row: usize, mb_col: usize, blk: usize) -> (i32, bool) {
433        // Neighbour block positions in the DC grid (SMPTE 421M Fig. 8-4)
434        // For luma: blocks are arranged as:
435        //   0 1
436        //   2 3
437        // Left-of-block:  blk 0←left_mb.blk1,  blk 1←same_mb.blk0,
438        //                  blk 2←left_mb.blk3,  blk 3←same_mb.blk2
439        // Top-of-block:   blk 0←top_mb.blk2,   blk 1←top_mb.blk3,
440        //                  blk 2←same_mb.blk0,  blk 3←same_mb.blk1
441        // Chroma (blk 4/5): left = left_mb.same_blk, top = top_mb.same_blk
442        let (dc_left, dc_top, dc_topleft) = self.dc_neighbours(mb_row, mb_col, blk);
443
444        // Gradient: |A - C| (horizontal) vs |B - C| (vertical)
445        // A = left, B = top, C = top-left
446        let grad_h = (dc_left - dc_topleft).unsigned_abs();
447        let grad_v = (dc_top - dc_topleft).unsigned_abs();
448
449        if grad_v <= grad_h {
450            // Predict from top (vertical predictor)
451            (dc_top, false)
452        } else {
453            // Predict from left (horizontal predictor)
454            (dc_left, true)
455        }
456    }
457
458    fn dc_neighbours(&self, mb_row: usize, mb_col: usize, blk: usize) -> (i32, i32, i32) {
459        // Helper: get stored DC for a possibly-out-of-bounds MB/blk
460        let get = |r: isize, c: isize, b: usize| -> i32 {
461            if r < 0 || c < 0 {
462                return 1024;
463            } // mid-gray default
464            let idx = r as usize * self.mb_w + c as usize;
465            if idx >= self.dc.len() {
466                return 1024;
467            }
468            self.dc[idx][b]
469        };
470
471        let r = mb_row as isize;
472        let c = mb_col as isize;
473
474        match blk {
475            // ── luma ───────────────────────────────────────────────────────
476            0 => {
477                let left = get(r, c - 1, 1); // right half of left MB
478                let top = get(r - 1, c, 2); // bottom-left of top MB
479                let topleft = get(r - 1, c - 1, 3);
480                (left, top, topleft)
481            }
482            1 => {
483                // SMPTE 421M §8.1.4.6 Fig 8-4: topleft = blk3 of top-left MB
484                let left = get(r, c, 0);
485                let top = get(r - 1, c, 3);
486                let topleft = get(r - 1, c - 1, 3);
487                (left, top, topleft)
488            }
489            2 => {
490                let left = get(r, c - 1, 3);
491                let top = get(r, c, 0); // blk0 of same MB
492                let topleft = get(r, c - 1, 1);
493                (left, top, topleft)
494            }
495            3 => {
496                let left = get(r, c, 2);
497                let top = get(r, c, 1);
498                let topleft = get(r, c, 0);
499                (left, top, topleft)
500            }
501            // ── chroma ─────────────────────────────────────────────────────
502            _ => {
503                let left = get(r, c - 1, blk);
504                let top = get(r - 1, c, blk);
505                let topleft = get(r - 1, c - 1, blk);
506                (left, top, topleft)
507            }
508        }
509    }
510
511    /// Store the reconstructed DC value (in coeff domain) for later prediction.
512    pub fn store(&mut self, mb_row: usize, mb_col: usize, blk: usize, dc_recon: i32) {
513        let idx = mb_row * self.mb_w + mb_col;
514        if idx < self.dc.len() {
515            self.dc[idx][blk] = dc_recon;
516        }
517    }
518}
519
520// ─── WMV2/MSMPEG4 DC predictor (upstream ff_msmpeg4_pred_dc logic) ─────────────
521
522/// Predictor storage is in "scaled DC coefficient" domain (level * dc_scale).
523/// Default value 1024 corresponds to mid-gray (128) with scale=8.
524pub struct Wmv2DcPredBuffer {
525    mb_w: usize,
526    dc: Vec<[i32; 6]>,
527}
528
529impl Wmv2DcPredBuffer {
530    pub fn new(mb_w: usize, mb_h: usize) -> Self {
531        Wmv2DcPredBuffer {
532            mb_w,
533            dc: vec![[1024i32; 6]; mb_w * mb_h],
534        }
535    }
536
537    fn neighbours(&self, mb_row: usize, mb_col: usize, blk: usize) -> (i32, i32, i32) {
538        // upstream alignment note:
539        //   ff_msmpeg4_pred_dc() predicts in the *8x8 block grid* (block_index[n]) with
540        //   neighbours A(left), B(above-left), C(above):
541        //       B C
542        //       A X
543        // Our storage is per-macroblock ([i32; 6]), so we emulate upstream's block-grid
544        // addressing for luma blocks (0..3) and macroblock-grid for chroma (4..5).
545
546        #[inline(always)]
547        fn get_luma(dc: &Vec<[i32; 6]>, mb_w: usize, bx: isize, by: isize) -> i32 {
548            if bx < 0 || by < 0 {
549                return 1024;
550            }
551            let mb_x = (bx >> 1) as usize;
552            let mb_y = (by >> 1) as usize;
553            let idx = mb_y * mb_w + mb_x;
554            if idx >= dc.len() {
555                return 1024;
556            }
557            let sub_x = (bx & 1) as usize;
558            let sub_y = (by & 1) as usize;
559            let b = (sub_y << 1) | sub_x; // 0..3
560            dc[idx][b]
561        }
562
563        #[inline(always)]
564        fn get_chroma(
565            dc: &Vec<[i32; 6]>,
566            mb_w: usize,
567            mb_x: isize,
568            mb_y: isize,
569            blk: usize,
570        ) -> i32 {
571            if mb_x < 0 || mb_y < 0 {
572                return 1024;
573            }
574            let idx = (mb_y as usize) * mb_w + (mb_x as usize);
575            if idx >= dc.len() {
576                return 1024;
577            }
578            dc[idx][blk]
579        }
580
581        if blk < 4 {
582            let bx = (mb_col as isize) * 2 + ((blk & 1) as isize);
583            let by = (mb_row as isize) * 2 + ((blk >> 1) as isize);
584            let a = get_luma(&self.dc, self.mb_w, bx - 1, by);
585            let b = get_luma(&self.dc, self.mb_w, bx - 1, by - 1);
586            let c = get_luma(&self.dc, self.mb_w, bx, by - 1);
587            (a, b, c)
588        } else {
589            let mx = mb_col as isize;
590            let my = mb_row as isize;
591            let a = get_chroma(&self.dc, self.mb_w, mx - 1, my, blk);
592            let b = get_chroma(&self.dc, self.mb_w, mx - 1, my - 1, blk);
593            let c = get_chroma(&self.dc, self.mb_w, mx, my - 1, blk);
594            (a, b, c)
595        }
596    }
597
598    /// Returns (pred_level, dir). dir=0 => left, dir=1 => top.
599    pub fn predict(&self, mb_row: usize, mb_col: usize, blk: usize, scale: i32) -> (i32, i32) {
600        let (a0, b0, c0) = self.neighbours(mb_row, mb_col, blk);
601        // Convert from scaled DC to level domain with rounding: (x + scale/2) / scale.
602        let a = (a0 + (scale >> 1)) / scale;
603        let b = (b0 + (scale >> 1)) / scale;
604        let c = (c0 + (scale >> 1)) / scale;
605
606        // WMV2/MSMPEG4 version > V3 uses STRICT '<' (see upstream ff_msmpeg4_pred_dc).
607        if (a - b).abs() < (b - c).abs() {
608            (c, 1)
609        } else {
610            (a, 0)
611        }
612    }
613
614    pub fn store(&mut self, mb_row: usize, mb_col: usize, blk: usize, dc_coeff_scaled: i32) {
615        let idx = mb_row * self.mb_w + mb_col;
616        if idx < self.dc.len() {
617            self.dc[idx][blk] = dc_coeff_scaled;
618        }
619    }
620}
621
622// ─── AC escape decoder ───────────────────────────────────────────────────────
623// SMPTE 421M §8.1.4.5 — Three escape modes following VLC_ESCAPE sentinel.
624//
625//   After VLC_ESCAPE, read mode bits:
626//     "0"  → Mode 1: level offset
627//     "10" → Mode 2: run offset
628//     "11" → Mode 3: absolute fixed-length
629//
630// Returns (run, signed_level, last).
631#[inline]
632fn decode_escape_coeff(br: &mut BitReader<'_>, ac_vlc: &VlcTable) -> (u8, i32, bool) {
633    let mode = {
634        let b0 = br.read_bit().unwrap_or(false);
635        if !b0 {
636            1u8
637        } else {
638            let b1 = br.read_bit().unwrap_or(false);
639            if b1 {
640                3
641            } else {
642                2
643            }
644        }
645    };
646    match mode {
647        1 => {
648            // Mode 1: level offset — VLC gives (run, base_level, last)
649            let sym = ac_vlc.decode(br).unwrap_or(0);
650            if sym == VLC_ESCAPE {
651                return (0, 0, true);
652            }
653            let (run, base_level, last) = unpack_rl(sym);
654            let sign = br.read_bit().unwrap_or(false);
655            let offset = ac_vlc.max_level(run as usize, last) as i32 + 1;
656            let level = base_level as i32 + offset;
657            (run, if sign { -level } else { level }, last)
658        }
659        2 => {
660            // Mode 2: run offset — VLC gives (base_run, level, last)
661            let sym = ac_vlc.decode(br).unwrap_or(0);
662            if sym == VLC_ESCAPE {
663                return (0, 0, true);
664            }
665            let (base_run, level, last) = unpack_rl(sym);
666            let sign = br.read_bit().unwrap_or(false);
667            let offset = ac_vlc.max_run(level as usize, last) as i32 + 1;
668            let run = (base_run as i32 + offset).min(63) as u8;
669            let sl = level as i32;
670            (run, if sign { -sl } else { sl }, last)
671        }
672        _ => {
673            // Mode 3: absolute — 1-bit LAST + 6-bit RUN + 8-bit |LEVEL| + 1-bit SIGN
674            let last = br.read_bit().unwrap_or(false);
675            let run = br.read_bits(6).unwrap_or(0) as u8;
676            let level = br.read_bits(8).unwrap_or(1).max(1) as i32;
677            let sign = br.read_bit().unwrap_or(false);
678            (run, if sign { -level } else { level }, last)
679        }
680    }
681}
682
683/// Decode one 8×8 block of AC+DC coefficients.
684/// `is_intra`: use intra table / scan, otherwise inter.
685/// `is_luma`:  use luma DC VLC.
686/// Returns filled `[i32; 64]` in natural order (not zigzag).
687fn decode_block(
688    br: &mut BitReader<'_>,
689    is_intra: bool,
690    is_luma: bool,
691    pquant: i32,
692    halfqp: bool,
693    uniform: bool,
694    tt: u8,
695    dc_luma: &VlcTable,
696    dc_chroma: &VlcTable,
697    ac_intra: &VlcTable,
698    ac_inter: &VlcTable,
699) -> [i32; 64] {
700    let mut blk = [0i32; 64];
701    let scan: &[usize; 64] = if is_intra { &SCAN_INTRA } else { &ZIGZAG };
702
703    // DC coefficient (intra only)
704    if is_intra {
705        let dc_vlc = if is_luma { dc_luma } else { dc_chroma };
706        blk[0] = read_dc_diff(br, dc_vlc);
707    }
708
709    // AC coefficients
710    let ac_vlc = if is_intra { ac_intra } else { ac_inter };
711    let mut idx = if is_intra { 1usize } else { 0 };
712
713    loop {
714        let sym = match ac_vlc.decode(br) {
715            Some(s) => s,
716            None => break,
717        };
718
719        let (run, signed_level, last) = if sym == VLC_ESCAPE {
720            decode_escape_coeff(br, ac_vlc)
721        } else {
722            let (r, l, last) = unpack_rl(sym);
723            let sign = br.read_bit().unwrap_or(false);
724            (r, if sign { -(l as i32) } else { l as i32 }, last)
725        };
726
727        idx += run as usize;
728        if idx >= 64 {
729            break;
730        }
731
732        let mag = signed_level.abs();
733        let qval = if uniform {
734            iquant_uniform(mag, pquant, halfqp)
735        } else {
736            iquant_nonuniform(mag, pquant)
737        };
738        let signed_val = if signed_level < 0 { -qval } else { qval };
739
740        // Use appropriate scan based on transform type
741        let scan_order: &[usize; 64] = match tt {
742            3 | 4 => &SCAN_VERT,
743            _ => scan,
744        };
745        let pos = scan_order.get(idx).copied().unwrap_or(idx);
746        blk[pos] = signed_val;
747        idx += 1;
748
749        if last || br.is_empty() {
750            break;
751        }
752    }
753
754    blk
755}
756
757/// Decode only the AC coefficients of one intra block (DC is handled separately).
758fn decode_block_ac(
759    br: &mut BitReader<'_>,
760    _is_luma: bool,
761    pquant: i32,
762    halfqp: bool,
763    uniform: bool,
764    tt: u8,
765    ac_vlc: &VlcTable,
766) -> [i32; 64] {
767    let mut blk = [0i32; 64];
768    let mut idx = 1usize; // start at 1, skip DC slot
769
770    loop {
771        let sym = match ac_vlc.decode(br) {
772            Some(s) => s,
773            None => break,
774        };
775
776        let (run, signed_level, last) = if sym == VLC_ESCAPE {
777            decode_escape_coeff(br, ac_vlc)
778        } else {
779            let (r, l, last) = unpack_rl(sym);
780            let sign = br.read_bit().unwrap_or(false);
781            (r, if sign { -(l as i32) } else { l as i32 }, last)
782        };
783
784        idx += run as usize;
785        if idx >= 64 {
786            break;
787        }
788
789        let mag = signed_level.abs();
790        let qval = if uniform {
791            iquant_uniform(mag, pquant, halfqp)
792        } else {
793            iquant_nonuniform(mag, pquant)
794        };
795        let sval = if signed_level < 0 { -qval } else { qval };
796
797        let scan_order: &[usize; 64] = match tt {
798            3 | 4 => &SCAN_VERT,
799            _ => &SCAN_INTRA,
800        };
801        let pos = scan_order.get(idx).copied().unwrap_or(idx);
802        blk[pos] = sval;
803        idx += 1;
804
805        if last || br.is_empty() {
806            break;
807        }
808    }
809    blk
810}
811
812// ─── AC Prediction buffer ────────────────────────────────────────────────────
813// SMPTE 421M §8.1.4.7.
814//
815// For each macroblock/block we cache the first row (AC[1..7]) and first
816// column (AC[8,16,24,32,40,48,56]) of reconstructed coefficients (pre-IDCT,
817// post-IQ) so they can be used as predictors for neighbouring blocks.
818
819#[derive(Clone)]
820pub struct AcPredBuffer {
821    mb_w: usize,
822    /// First row of coefficients for each MB×block: [mb_idx][blk][0..7]
823    row: Vec<[[i32; 7]; 6]>,
824    /// First col of coefficients for each MB×block: [mb_idx][blk][0..7]
825    col: Vec<[[i32; 7]; 6]>,
826}
827
828impl AcPredBuffer {
829    pub fn new(mb_w: usize, mb_h: usize) -> Self {
830        let n = mb_w * mb_h;
831        AcPredBuffer {
832            mb_w,
833            row: vec![[[0i32; 7]; 6]; n],
834            col: vec![[[0i32; 7]; 6]; n],
835        }
836    }
837
838    /// Get the AC predictor row (indices 1..7 of the reconstructed block).
839    /// Returns the first row of the left neighbour (for horizontal prediction).
840    pub fn pred_row(&self, mb_row: usize, mb_col: usize, blk: usize) -> [i32; 7] {
841        let (src_mb_r, src_mb_c, src_blk) = Self::left_neighbour(mb_row, mb_col, blk);
842        if src_mb_r as isize >= 0 && src_mb_c as isize >= 0 {
843            let idx = src_mb_r * self.mb_w + src_mb_c;
844            if idx < self.row.len() {
845                return self.row[idx][src_blk];
846            }
847        }
848        [0i32; 7]
849    }
850
851    /// Get the AC predictor column (rows 1..7 of the reconstructed block).
852    /// Returns the first column of the top neighbour (for vertical prediction).
853    pub fn pred_col(&self, mb_row: usize, mb_col: usize, blk: usize) -> [i32; 7] {
854        let (src_mb_r, src_mb_c, src_blk) = Self::top_neighbour(mb_row, mb_col, blk);
855        if src_mb_r as isize >= 0 && src_mb_c as isize >= 0 {
856            let idx = src_mb_r * self.mb_w + src_mb_c;
857            if idx < self.col.len() {
858                return self.col[idx][src_blk];
859            }
860        }
861        [0i32; 7]
862    }
863
864    pub fn store_row(&mut self, mb_row: usize, mb_col: usize, blk: usize, row: [i32; 7]) {
865        let idx = mb_row * self.mb_w + mb_col;
866        if idx < self.row.len() {
867            self.row[idx][blk] = row;
868        }
869    }
870
871    pub fn store_col(&mut self, mb_row: usize, mb_col: usize, blk: usize, col: [i32; 7]) {
872        let idx = mb_row * self.mb_w + mb_col;
873        if idx < self.col.len() {
874            self.col[idx][blk] = col;
875        }
876    }
877
878    /// Left neighbour source: same logic as DC prediction neighbour mapping.
879    fn left_neighbour(mb_row: usize, mb_col: usize, blk: usize) -> (usize, usize, usize) {
880        match blk {
881            0 => (mb_row, mb_col.wrapping_sub(1), 1),
882            1 => (mb_row, mb_col, 0),
883            2 => (mb_row, mb_col.wrapping_sub(1), 3),
884            3 => (mb_row, mb_col, 2),
885            _ => (mb_row, mb_col.wrapping_sub(1), blk),
886        }
887    }
888
889    fn top_neighbour(mb_row: usize, mb_col: usize, blk: usize) -> (usize, usize, usize) {
890        match blk {
891            0 => (mb_row.wrapping_sub(1), mb_col, 2),
892            1 => (mb_row.wrapping_sub(1), mb_col, 3),
893            2 => (mb_row, mb_col, 0),
894            3 => (mb_row, mb_col, 1),
895            _ => (mb_row.wrapping_sub(1), mb_col, blk),
896        }
897    }
898}
899
900// ─── MV Predictor ────────────────────────────────────────────────────────────
901// SMPTE 421M §8.3.5.3.
902//
903// The MV predictor for 1-MV macroblocks is the median of three neighbouring
904// MVs: left (A), top (B), and top-right (C).  When a neighbour is out-of-
905// frame or skipped, its MV is treated as (0,0).
906
907#[derive(Clone, Default)]
908pub struct MvPredictor {
909    mb_w: usize,
910    /// Stored MVs per MB: (mvx, mvy) in half-pixel units
911    mvs: Vec<(i32, i32)>,
912    /// Whether each MB was skipped (skipped MBs propagate MV=0)
913    skipped: Vec<bool>,
914}
915
916impl MvPredictor {
917    pub fn new(mb_w: usize, mb_h: usize) -> Self {
918        let n = mb_w * mb_h;
919        MvPredictor {
920            mb_w,
921            mvs: vec![(0, 0); n],
922            skipped: vec![true; n],
923        }
924    }
925
926    /// Compute the predicted MV for (mb_row, mb_col) from three neighbours.
927    pub fn predict(&self, mb_row: usize, mb_col: usize) -> (i32, i32) {
928        let get = |r: isize, c: isize| -> (i32, i32) {
929            if r < 0 || c < 0 {
930                return (0, 0);
931            }
932            let idx = r as usize * self.mb_w + c as usize;
933            if idx >= self.mvs.len() || self.skipped[idx] {
934                return (0, 0);
935            }
936            self.mvs[idx]
937        };
938
939        let r = mb_row as isize;
940        let c = mb_col as isize;
941
942        let (ax, ay) = get(r, c - 1); // left
943        let (bx, by) = get(r - 1, c); // top
944        let (cx, cy) = get(r - 1, c + 1); // top-right (or top-left if rightmost)
945                                          // If top-right is out of bounds, use top-left instead (per spec)
946        let (cx, cy) = if c + 1 >= self.mb_w as isize {
947            get(r - 1, c - 1)
948        } else {
949            (cx, cy)
950        };
951
952        (median3(ax, bx, cx), median3(ay, by, cy))
953    }
954
955    pub fn store(&mut self, mb_row: usize, mb_col: usize, mv: (i32, i32), skipped: bool) {
956        let idx = mb_row * self.mb_w + mb_col;
957        if idx < self.mvs.len() {
958            self.mvs[idx] = mv;
959            self.skipped[idx] = skipped;
960        }
961    }
962}
963
964#[inline]
965fn median3(a: i32, b: i32, c: i32) -> i32 {
966    // Returns the median of three values
967    if (a <= b && b <= c) || (c <= b && b <= a) {
968        b
969    } else if (b <= a && a <= c) || (c <= a && a <= b) {
970        a
971    } else {
972        c
973    }
974}
975
976#[inline(always)]
977fn mid_pred(a: i32, b: i32, c: i32) -> i32 {
978    // upstream mid_pred() helper.
979    median3(a, b, c)
980}
981
982// ─── Overlap smoothing filter (Main profile) ─────────────────────────────────
983// SMPTE 421M §6.2.1.  Applied at 8×8 block boundaries post-IDCT.
984
985#[allow(dead_code)]
986fn overlap_filter_h(a: &mut [i16], b: &mut [i16]) {
987    // 4-tap filter across horizontal boundary between a[] and b[]
988    // a holds last 4 samples of left block, b holds first 4 of right block
989    for i in 0..4 {
990        let x0 = a[i] as i32;
991        let x1 = b[i] as i32;
992        a[i] = ((9 * x0 + 3 * x1 + 8) >> 4) as i16;
993        b[i] = ((3 * x0 + 9 * x1 + 8) >> 4) as i16;
994    }
995}
996
997#[allow(dead_code)]
998fn overlap_filter_v(a: &mut i16, b: &mut i16) {
999    let x0 = *a as i32;
1000    let x1 = *b as i32;
1001    *a = ((9 * x0 + 3 * x1 + 8) >> 4) as i16;
1002    *b = ((3 * x0 + 9 * x1 + 8) >> 4) as i16;
1003}
1004
1005pub fn apply_overlap_filter(frame: &mut YuvFrame) {
1006    let w = frame.width as usize;
1007    let h = frame.height as usize;
1008    let cw = w / 2;
1009    let ch = h / 2;
1010
1011    // Convert to i16 for filtering, then back to u8
1012    let mut y_i16: Vec<i16> = frame.y.iter().map(|&v| v as i16).collect();
1013
1014    // Horizontal block boundaries (every 8 columns)
1015    for row in 0..h {
1016        for col in (8..w).step_by(8) {
1017            for k in 0..4 {
1018                let ia = row * w + col - 4 + k;
1019                let ib = row * w + col + k;
1020                if ib < y_i16.len() {
1021                    let a = y_i16[ia] as i32;
1022                    let b = y_i16[ib] as i32;
1023                    y_i16[ia] = ((9 * a + 3 * b + 8) >> 4) as i16;
1024                    y_i16[ib] = ((3 * a + 9 * b + 8) >> 4) as i16;
1025                }
1026            }
1027        }
1028    }
1029
1030    // Vertical block boundaries
1031    for row in (8..h).step_by(8) {
1032        for col in 0..w {
1033            let a = y_i16[(row - 1) * w + col] as i32;
1034            let b = y_i16[row * w + col] as i32;
1035            y_i16[(row - 1) * w + col] = ((9 * a + 3 * b + 8) >> 4) as i16;
1036            y_i16[row * w + col] = ((3 * a + 9 * b + 8) >> 4) as i16;
1037        }
1038    }
1039
1040    // Write back
1041    for (dst, src) in frame.y.iter_mut().zip(y_i16.iter()) {
1042        *dst = (*src).clamp(0, 255) as u8;
1043    }
1044
1045    // Chroma (same logic, half size)
1046    let mut cb_i16: Vec<i16> = frame.cb.iter().map(|&v| v as i16).collect();
1047    let mut cr_i16: Vec<i16> = frame.cr.iter().map(|&v| v as i16).collect();
1048    for plane in [&mut cb_i16, &mut cr_i16] {
1049        for row in 0..ch {
1050            for col in (8..cw).step_by(8) {
1051                for k in 0..4 {
1052                    let a = plane[row * cw + col - 4 + k] as i32;
1053                    let b = plane[row * cw + col + k] as i32;
1054                    plane[row * cw + col - 4 + k] = ((9 * a + 3 * b + 8) >> 4) as i16;
1055                    plane[row * cw + col + k] = ((3 * a + 9 * b + 8) >> 4) as i16;
1056                }
1057            }
1058        }
1059        for row in (8..ch).step_by(8) {
1060            for col in 0..cw {
1061                let a = plane[(row - 1) * cw + col] as i32;
1062                let b = plane[row * cw + col] as i32;
1063                plane[(row - 1) * cw + col] = ((9 * a + 3 * b + 8) >> 4) as i16;
1064                plane[row * cw + col] = ((3 * a + 9 * b + 8) >> 4) as i16;
1065            }
1066        }
1067    }
1068    for (dst, src) in frame.cb.iter_mut().zip(cb_i16.iter()) {
1069        *dst = (*src).clamp(0, 255) as u8;
1070    }
1071    for (dst, src) in frame.cr.iter_mut().zip(cr_i16.iter()) {
1072        *dst = (*src).clamp(0, 255) as u8;
1073    }
1074}
1075
1076// ─── Motion compensation ─────────────────────────────────────────────────────
1077// Half-pixel bilinear interpolation per SMPTE 421M §7.3.
1078
1079fn mc_luma(
1080    dst: &mut [u8],
1081    dst_stride: usize,
1082    src: &[u8],
1083    src_stride: usize,
1084    src_w: usize,
1085    src_h: usize,
1086    x: i32,
1087    y: i32,
1088    w: usize,
1089    h: usize,
1090) {
1091    // x and y in half-pixel units
1092    let xh = x & 1 != 0;
1093    let yh = y & 1 != 0;
1094    let x0 = (x >> 1) as isize;
1095    let y0 = (y >> 1) as isize;
1096
1097    for dy in 0..h {
1098        for dx in 0..w {
1099            let sx = (x0 + dx as isize).clamp(0, src_w as isize - 1) as usize;
1100            let sy = (y0 + dy as isize).clamp(0, src_h as isize - 1) as usize;
1101            let sx1 = (sx + 1).min(src_w - 1);
1102            let sy1 = (sy + 1).min(src_h - 1);
1103
1104            let p00 = src[sy * src_stride + sx] as i32;
1105            let p10 = src[sy * src_stride + sx1] as i32;
1106            let p01 = src[sy1 * src_stride + sx] as i32;
1107            let p11 = src[sy1 * src_stride + sx1] as i32;
1108
1109            let val = match (xh, yh) {
1110                (false, false) => p00,
1111                (true, false) => (p00 + p10 + 1) >> 1,
1112                (false, true) => (p00 + p01 + 1) >> 1,
1113                (true, true) => (p00 + p10 + p01 + p11 + 2) >> 2,
1114            };
1115            dst[dy * dst_stride + dx] = val.clamp(0, 255) as u8;
1116        }
1117    }
1118}
1119
1120// ─── DQUANT: macroblock-level differential quantizer ────────────────────────
1121// SMPTE 421M §8.1.4.10 / §8.3.7.
1122//
1123// When seq.dquant != 0, each macroblock may override the frame-level PQUANT.
1124// dquant=1: 1-bit flag; if set, read 2-bit MQUANT (absolute value).
1125// dquant=2: always present 2-bit MQDIFF; if == 7, read 5-bit MQUANT absolute.
1126
1127fn read_mquant(br: &mut BitReader<'_>, dquant: u8, pquant: i32) -> i32 {
1128    match dquant {
1129        0 => pquant, // no per-MB quant
1130        1 => {
1131            // 1-bit DQUANT flag; if 1, read 2-bit delta
1132            if br.read_bit().unwrap_or(false) {
1133                let mqdiff = br.read_bits(2).unwrap_or(0) as i32;
1134                // mqdiff: 0=+2, 1=-2, 2=+4, 3=-4 relative to pquant
1135                let delta = match mqdiff {
1136                    0 => 2,
1137                    1 => -2,
1138                    2 => 4,
1139                    _ => -4,
1140                };
1141                (pquant + delta).clamp(1, 31)
1142            } else {
1143                pquant
1144            }
1145        }
1146        _ => {
1147            // dquant==2: always read 3-bit MQDIFF
1148            let mqdiff = br.read_bits(3).unwrap_or(0);
1149            if mqdiff == 7 {
1150                // escape: read 5-bit absolute MQUANT
1151                br.read_bits(5).unwrap_or(pquant as u32) as i32
1152            } else {
1153                // relative to pquant: +1..+6
1154                (pquant + mqdiff as i32).clamp(1, 31)
1155            }
1156        }
1157    }
1158}
1159
1160// ─── Range Reduction / Expansion ─────────────────────────────────────────────
1161// SMPTE 421M §7.1.1.9.
1162//
1163// RANGEREDFRM=1 means the encoder reduced the dynamic range before coding.
1164// The decoder must expand it back.  Applied to the reconstructed frame.
1165
1166pub fn apply_rangered_expand(frame: &mut YuvFrame) {
1167    // Expand: x' = (x - 128) * 2 + 128  (clamp 0..255)
1168    for p in frame.y.iter_mut() {
1169        *p = ((*p as i32 - 128) * 2 + 128).clamp(0, 255) as u8;
1170    }
1171    for p in frame.cb.iter_mut() {
1172        *p = ((*p as i32 - 128) * 2 + 128).clamp(0, 255) as u8;
1173    }
1174    for p in frame.cr.iter_mut() {
1175        *p = ((*p as i32 - 128) * 2 + 128).clamp(0, 255) as u8;
1176    }
1177}
1178
1179/// Compress: applied to reference frame before motion compensation when
1180/// the current frame does NOT have RANGEREDFRM but the reference did.
1181pub fn apply_rangered_compress(frame: &mut YuvFrame) {
1182    for p in frame.y.iter_mut() {
1183        *p = ((*p as i32 - 128).div_euclid(2) + 128).clamp(0, 255) as u8;
1184    }
1185    for p in frame.cb.iter_mut() {
1186        *p = ((*p as i32 - 128).div_euclid(2) + 128).clamp(0, 255) as u8;
1187    }
1188    for p in frame.cr.iter_mut() {
1189        *p = ((*p as i32 - 128).div_euclid(2) + 128).clamp(0, 255) as u8;
1190    }
1191}
1192
1193// ─── Write helpers ───────────────────────────────────────────────────────────
1194
1195/// Block (mb_row, mb_col, blk_idx) → (plane ref, x, y, stride, plane_h)
1196fn block_coords(
1197    mb_row: u32,
1198    mb_col: u32,
1199    blk: usize,
1200    width: u32,
1201    height: u32,
1202) -> (bool, usize, usize, usize, usize) {
1203    // Returns (is_luma, px, py, stride, plane_height)
1204    let (is_luma, bx, by) = match blk {
1205        0 => (true, (mb_col * 16) as usize, (mb_row * 16) as usize),
1206        1 => (true, (mb_col * 16 + 8) as usize, (mb_row * 16) as usize),
1207        2 => (true, (mb_col * 16) as usize, (mb_row * 16 + 8) as usize),
1208        3 => (true, (mb_col * 16 + 8) as usize, (mb_row * 16 + 8) as usize),
1209        _ => (false, (mb_col * 8) as usize, (mb_row * 8) as usize),
1210    };
1211    let stride = if is_luma {
1212        width as usize
1213    } else {
1214        (width / 2) as usize
1215    };
1216    let ph = if is_luma {
1217        height as usize
1218    } else {
1219        (height / 2) as usize
1220    };
1221    (is_luma, bx, by, stride, ph)
1222}
1223
1224fn write_intra_block(
1225    frame: &mut YuvFrame,
1226    mb_row: u32,
1227    mb_col: u32,
1228    blk: usize,
1229    coeff: &[i32; 64],
1230) {
1231    let (is_luma, bx, by, stride, ph) =
1232        block_coords(mb_row, mb_col, blk, frame.width, frame.height);
1233    let plane: &mut Vec<u8> = if is_luma {
1234        &mut frame.y
1235    } else if blk == 4 {
1236        &mut frame.cb
1237    } else {
1238        &mut frame.cr
1239    };
1240    for r in 0..8 {
1241        if by + r >= ph {
1242            break;
1243        }
1244        for c in 0..8 {
1245            if bx + c >= stride {
1246                break;
1247            }
1248            let idx = (by + r) * stride + (bx + c);
1249            plane[idx] = (128 + coeff[r * 8 + c]).clamp(0, 255) as u8;
1250        }
1251    }
1252}
1253
1254#[inline]
1255fn write_block_to_frame(
1256    frame: &mut YuvFrame,
1257    mb_row: usize,
1258    mb_col: usize,
1259    blk: usize,
1260    coeff: &[i32; 64],
1261) {
1262    write_intra_block(frame, mb_row as u32, mb_col as u32, blk, coeff);
1263}
1264
1265// WMV2 path uses upstream's Simple IDCT (int16). Provide i16 write/add helpers.
1266fn write_intra_block_i16(
1267    frame: &mut YuvFrame,
1268    mb_row: u32,
1269    mb_col: u32,
1270    blk: usize,
1271    coeff: &[i16; 64],
1272) {
1273    let (is_luma, bx, by, stride, ph) =
1274        block_coords(mb_row, mb_col, blk, frame.width, frame.height);
1275    let plane: &mut Vec<u8> = if is_luma {
1276        &mut frame.y
1277    } else if blk == 4 {
1278        &mut frame.cb
1279    } else {
1280        &mut frame.cr
1281    };
1282    for r in 0..8usize {
1283        if by + r >= ph {
1284            break;
1285        }
1286        for c in 0..8usize {
1287            if bx + c >= stride {
1288                break;
1289            }
1290            let idx = (by + r) * stride + (bx + c);
1291            let v = coeff[r * 8 + c] as i32;
1292            plane[idx] = (v + 128).clamp(0, 255) as u8;
1293        }
1294    }
1295}
1296
1297fn add_residual_block_i16(
1298    frame: &mut YuvFrame,
1299    mb_row: u32,
1300    mb_col: u32,
1301    blk: usize,
1302    coeff: &[i16; 64],
1303) {
1304    let (is_luma, bx, by, stride, ph) =
1305        block_coords(mb_row, mb_col, blk, frame.width, frame.height);
1306    let plane: &mut Vec<u8> = if is_luma {
1307        &mut frame.y
1308    } else if blk == 4 {
1309        &mut frame.cb
1310    } else {
1311        &mut frame.cr
1312    };
1313    for r in 0..8usize {
1314        if by + r >= ph {
1315            break;
1316        }
1317        for c in 0..8usize {
1318            if bx + c >= stride {
1319                break;
1320            }
1321            let idx = (by + r) * stride + (bx + c);
1322            let v = plane[idx] as i32 + coeff[r * 8 + c] as i32;
1323            plane[idx] = v.clamp(0, 255) as u8;
1324        }
1325    }
1326}
1327
1328/// Motion compensate one 16×16 macroblock from `reference` into `dst`.
1329/// Motion vectors are in half-pel units (like H.263/MSMPEG4/WMV2).
1330fn motion_compensate_mb(
1331    dst: &mut YuvFrame,
1332    reference: &YuvFrame,
1333    mb_row: usize,
1334    mb_col: usize,
1335    mvx: i32,
1336    mvy: i32,
1337) {
1338    let fw = dst.width as usize;
1339    let fh = dst.height as usize;
1340    if fw == 0 || fh == 0 {
1341        return;
1342    }
1343    let cw = fw / 2;
1344    let ch = fh / 2;
1345
1346    // ── Luma (16×16) ────────────────────────────────────────────────────────
1347    let dst_x = mb_col * 16;
1348    let dst_y = mb_row * 16;
1349    let src_x = dst_x as i32 * 2 + mvx; // half-pel coordinate
1350    let src_y = dst_y as i32 * 2 + mvy;
1351
1352    if reference.y.len() == fw * fh && dst.y.len() == fw * fh {
1353        let mut tmp = [0u8; 256];
1354        mc_luma(&mut tmp, 16, &reference.y, fw, fw, fh, src_x, src_y, 16, 16);
1355        for r in 0..16 {
1356            if dst_y + r >= fh {
1357                break;
1358            }
1359            if dst_x >= fw {
1360                break;
1361            }
1362            let d_off = (dst_y + r) * fw + dst_x;
1363            let s_off = r * 16;
1364            let max = (fw - dst_x).min(16);
1365            dst.y[d_off..d_off + max].copy_from_slice(&tmp[s_off..s_off + max]);
1366        }
1367    }
1368
1369    // ── Chroma (8×8) ───────────────────────────────────────────────────────
1370    // upstream (ff_mspel_motion): motion vectors are in half-luma-pel units.
1371    // For 4:2:0 chroma, 1 chroma pixel = 2 luma pixels, so the same MV value
1372    // corresponds to quarter-chroma-pel units.
1373    // upstream collapses the 2-bit chroma fraction to a boolean (any non-zero
1374    // fractional part triggers half-chroma interpolation):
1375    //   dxy |= (motion_x & 3) != 0
1376    //   mx  = motion_x >> 2
1377    // We reproduce that mapping here by converting to half-chroma-pel coords.
1378    if reference.cb.len() == cw * ch && dst.cb.len() == cw * ch {
1379        let dst_xc = mb_col * 8;
1380        let dst_yc = mb_row * 8;
1381
1382        let mx = mvx >> 2;
1383        let my = mvy >> 2;
1384        let xh = (mvx & 3) != 0;
1385        let yh = (mvy & 3) != 0;
1386
1387        // Half-chroma-pel coordinate for mc_luma().
1388        let src_xc = (dst_xc as i32 + mx) * 2 + if xh { 1 } else { 0 };
1389        let src_yc = (dst_yc as i32 + my) * 2 + if yh { 1 } else { 0 };
1390
1391        let mut tmp_cb = [0u8; 64];
1392        let mut tmp_cr = [0u8; 64];
1393        mc_luma(
1394            &mut tmp_cb,
1395            8,
1396            &reference.cb,
1397            cw,
1398            cw,
1399            ch,
1400            src_xc,
1401            src_yc,
1402            8,
1403            8,
1404        );
1405        mc_luma(
1406            &mut tmp_cr,
1407            8,
1408            &reference.cr,
1409            cw,
1410            cw,
1411            ch,
1412            src_xc,
1413            src_yc,
1414            8,
1415            8,
1416        );
1417
1418        for r in 0..8 {
1419            if dst_yc + r >= ch {
1420                break;
1421            }
1422            if dst_xc >= cw {
1423                break;
1424            }
1425            let d_off = (dst_yc + r) * cw + dst_xc;
1426            let s_off = r * 8;
1427            let max = (cw - dst_xc).min(8);
1428            dst.cb[d_off..d_off + max].copy_from_slice(&tmp_cb[s_off..s_off + max]);
1429            dst.cr[d_off..d_off + max].copy_from_slice(&tmp_cr[s_off..s_off + max]);
1430        }
1431    }
1432}
1433
1434// ── WMV2 MSPEL motion compensation (direct port of upstream ff_mspel_motion + wmv2_mspel_init) ──
1435
1436#[inline]
1437fn clip_u8(v: i32) -> u8 {
1438    if v < 0 {
1439        0
1440    } else if v > 255 {
1441        255
1442    } else {
1443        v as u8
1444    }
1445}
1446
1447#[inline]
1448fn rnd_avg_u8(a: u8, b: u8) -> u8 {
1449    ((a as u16 + b as u16 + 1) >> 1) as u8
1450}
1451
1452#[inline]
1453fn no_rnd_avg_u8(a: u8, b: u8) -> u8 {
1454    ((a as u16 + b as u16) >> 1) as u8
1455}
1456
1457fn wmv2_mspel8_h_lowpass(
1458    dst: &mut [u8],
1459    dst_off: usize,
1460    dst_stride: usize,
1461    src: &[u8],
1462    src_off: usize,
1463    src_stride: usize,
1464    h: usize,
1465) {
1466    for i in 0..h {
1467        let so = src_off + i * src_stride;
1468        let doff = dst_off + i * dst_stride;
1469        // dst[0..8]
1470        dst[doff + 0] = clip_u8(
1471            ((9 * (src[so + 0] as i32 + src[so + 1] as i32)
1472                - (src[so - 1] as i32 + src[so + 2] as i32)
1473                + 8)
1474                >> 4),
1475        );
1476        dst[doff + 1] = clip_u8(
1477            ((9 * (src[so + 1] as i32 + src[so + 2] as i32)
1478                - (src[so + 0] as i32 + src[so + 3] as i32)
1479                + 8)
1480                >> 4),
1481        );
1482        dst[doff + 2] = clip_u8(
1483            ((9 * (src[so + 2] as i32 + src[so + 3] as i32)
1484                - (src[so + 1] as i32 + src[so + 4] as i32)
1485                + 8)
1486                >> 4),
1487        );
1488        dst[doff + 3] = clip_u8(
1489            ((9 * (src[so + 3] as i32 + src[so + 4] as i32)
1490                - (src[so + 2] as i32 + src[so + 5] as i32)
1491                + 8)
1492                >> 4),
1493        );
1494        dst[doff + 4] = clip_u8(
1495            ((9 * (src[so + 4] as i32 + src[so + 5] as i32)
1496                - (src[so + 3] as i32 + src[so + 6] as i32)
1497                + 8)
1498                >> 4),
1499        );
1500        dst[doff + 5] = clip_u8(
1501            ((9 * (src[so + 5] as i32 + src[so + 6] as i32)
1502                - (src[so + 4] as i32 + src[so + 7] as i32)
1503                + 8)
1504                >> 4),
1505        );
1506        dst[doff + 6] = clip_u8(
1507            ((9 * (src[so + 6] as i32 + src[so + 7] as i32)
1508                - (src[so + 5] as i32 + src[so + 8] as i32)
1509                + 8)
1510                >> 4),
1511        );
1512        dst[doff + 7] = clip_u8(
1513            ((9 * (src[so + 7] as i32 + src[so + 8] as i32)
1514                - (src[so + 6] as i32 + src[so + 9] as i32)
1515                + 8)
1516                >> 4),
1517        );
1518    }
1519}
1520
1521fn wmv2_mspel8_v_lowpass(
1522    dst: &mut [u8],
1523    dst_off: usize,
1524    dst_stride: usize,
1525    src: &[u8],
1526    src_off: usize,
1527    src_stride: usize,
1528    w: usize,
1529) {
1530    for i in 0..w {
1531        let so = src_off + i;
1532        let s_1 = src[so - src_stride] as i32;
1533        let s0 = src[so] as i32;
1534        let s1 = src[so + src_stride] as i32;
1535        let s2 = src[so + 2 * src_stride] as i32;
1536        let s3 = src[so + 3 * src_stride] as i32;
1537        let s4 = src[so + 4 * src_stride] as i32;
1538        let s5 = src[so + 5 * src_stride] as i32;
1539        let s6 = src[so + 6 * src_stride] as i32;
1540        let s7 = src[so + 7 * src_stride] as i32;
1541        let s8 = src[so + 8 * src_stride] as i32;
1542        let s9 = src[so + 9 * src_stride] as i32;
1543
1544        let do0 = dst_off + i + 0 * dst_stride;
1545        let do1 = dst_off + i + 1 * dst_stride;
1546        let do2 = dst_off + i + 2 * dst_stride;
1547        let do3 = dst_off + i + 3 * dst_stride;
1548        let do4 = dst_off + i + 4 * dst_stride;
1549        let do5 = dst_off + i + 5 * dst_stride;
1550        let do6 = dst_off + i + 6 * dst_stride;
1551        let do7 = dst_off + i + 7 * dst_stride;
1552
1553        dst[do0] = clip_u8(((9 * (s0 + s1) - (s_1 + s2) + 8) >> 4));
1554        dst[do1] = clip_u8(((9 * (s1 + s2) - (s0 + s3) + 8) >> 4));
1555        dst[do2] = clip_u8(((9 * (s2 + s3) - (s1 + s4) + 8) >> 4));
1556        dst[do3] = clip_u8(((9 * (s3 + s4) - (s2 + s5) + 8) >> 4));
1557        dst[do4] = clip_u8(((9 * (s4 + s5) - (s3 + s6) + 8) >> 4));
1558        dst[do5] = clip_u8(((9 * (s5 + s6) - (s4 + s7) + 8) >> 4));
1559        dst[do6] = clip_u8(((9 * (s6 + s7) - (s5 + s8) + 8) >> 4));
1560        dst[do7] = clip_u8(((9 * (s7 + s8) - (s6 + s9) + 8) >> 4));
1561    }
1562}
1563
1564#[inline]
1565fn put_pixels8x8(dst: &mut [u8], dst_off: usize, src: &[u8], src_off: usize, stride: usize) {
1566    for y in 0..8 {
1567        let d = dst_off + y * stride;
1568        let s = src_off + y * stride;
1569        dst[d..d + 8].copy_from_slice(&src[s..s + 8]);
1570    }
1571}
1572
1573#[inline]
1574fn put_pixels8_l2_8_no_rnd(
1575    dst: &mut [u8],
1576    dst_off: usize,
1577    src1: &[u8],
1578    src1_off: usize,
1579    src2: &[u8],
1580    src2_off: usize,
1581    dst_stride: usize,
1582    src1_stride: usize,
1583    src2_stride: usize,
1584    h: usize,
1585) {
1586    for y in 0..h {
1587        let d = dst_off + y * dst_stride;
1588        let s1 = src1_off + y * src1_stride;
1589        let s2 = src2_off + y * src2_stride;
1590        for x in 0..8 {
1591            dst[d + x] = no_rnd_avg_u8(src1[s1 + x], src2[s2 + x]);
1592        }
1593    }
1594}
1595
1596fn put_mspel8_mc10(dst: &mut [u8], dst_off: usize, src: &[u8], src_off: usize, stride: usize) {
1597    let mut half = [0u8; 64];
1598    wmv2_mspel8_h_lowpass(&mut half, 0, 8, src, src_off, stride, 8);
1599    put_pixels8_l2_8_no_rnd(dst, dst_off, src, src_off, &half, 0, stride, stride, 8, 8);
1600}
1601
1602fn put_mspel8_mc20(dst: &mut [u8], dst_off: usize, src: &[u8], src_off: usize, stride: usize) {
1603    wmv2_mspel8_h_lowpass(dst, dst_off, stride, src, src_off, stride, 8);
1604}
1605
1606fn put_mspel8_mc30(dst: &mut [u8], dst_off: usize, src: &[u8], src_off: usize, stride: usize) {
1607    let mut half = [0u8; 64];
1608    wmv2_mspel8_h_lowpass(&mut half, 0, 8, src, src_off, stride, 8);
1609    put_pixels8_l2_8_no_rnd(
1610        dst,
1611        dst_off,
1612        src,
1613        src_off + 1,
1614        &half,
1615        0,
1616        stride,
1617        stride,
1618        8,
1619        8,
1620    );
1621}
1622
1623fn put_mspel8_mc02(dst: &mut [u8], dst_off: usize, src: &[u8], src_off: usize, stride: usize) {
1624    wmv2_mspel8_v_lowpass(dst, dst_off, stride, src, src_off, stride, 8);
1625}
1626
1627fn put_mspel8_mc12(dst: &mut [u8], dst_off: usize, src: &[u8], src_off: usize, stride: usize) {
1628    let mut half_h = [0u8; 88];
1629    let mut half_v = [0u8; 64];
1630    let mut half_hv = [0u8; 64];
1631    // h_lowpass(halfH, src - stride, 8, stride, 11)
1632    wmv2_mspel8_h_lowpass(&mut half_h, 0, 8, src, src_off - stride, stride, 11);
1633    // v_lowpass(halfV, src, 8, stride, 8)
1634    wmv2_mspel8_v_lowpass(&mut half_v, 0, 8, src, src_off, stride, 8);
1635    // v_lowpass(halfHV, halfH + 8, 8, 8, 8)
1636    wmv2_mspel8_v_lowpass(&mut half_hv, 0, 8, &half_h, 8, 8, 8);
1637    put_pixels8_l2_8_no_rnd(dst, dst_off, &half_v, 0, &half_hv, 0, stride, 8, 8, 8);
1638}
1639
1640fn put_mspel8_mc22(dst: &mut [u8], dst_off: usize, src: &[u8], src_off: usize, stride: usize) {
1641    let mut half_h = [0u8; 88];
1642    wmv2_mspel8_h_lowpass(&mut half_h, 0, 8, src, src_off - stride, stride, 11);
1643    wmv2_mspel8_v_lowpass(dst, dst_off, stride, &half_h, 8, 8, 8);
1644}
1645
1646fn put_mspel8_mc32(dst: &mut [u8], dst_off: usize, src: &[u8], src_off: usize, stride: usize) {
1647    let mut half_h = [0u8; 88];
1648    let mut half_v = [0u8; 64];
1649    let mut half_hv = [0u8; 64];
1650    wmv2_mspel8_h_lowpass(&mut half_h, 0, 8, src, src_off - stride, stride, 11);
1651    wmv2_mspel8_v_lowpass(&mut half_v, 0, 8, src, src_off + 1, stride, 8);
1652    wmv2_mspel8_v_lowpass(&mut half_hv, 0, 8, &half_h, 8, 8, 8);
1653    put_pixels8_l2_8_no_rnd(dst, dst_off, &half_v, 0, &half_hv, 0, stride, 8, 8, 8);
1654}
1655
1656#[inline]
1657fn wmv2_put_mspel_pixels(
1658    dxy: usize,
1659    dst: &mut [u8],
1660    dst_off: usize,
1661    src: &[u8],
1662    src_off: usize,
1663    stride: usize,
1664) {
1665    match dxy {
1666        0 => put_pixels8x8(dst, dst_off, src, src_off, stride),
1667        1 => put_mspel8_mc10(dst, dst_off, src, src_off, stride),
1668        2 => put_mspel8_mc20(dst, dst_off, src, src_off, stride),
1669        3 => put_mspel8_mc30(dst, dst_off, src, src_off, stride),
1670        4 => put_mspel8_mc02(dst, dst_off, src, src_off, stride),
1671        5 => put_mspel8_mc12(dst, dst_off, src, src_off, stride),
1672        6 => put_mspel8_mc22(dst, dst_off, src, src_off, stride),
1673        7 => put_mspel8_mc32(dst, dst_off, src, src_off, stride),
1674        _ => put_pixels8x8(dst, dst_off, src, src_off, stride),
1675    }
1676}
1677
1678fn emulated_edge_mc(
1679    buf: &mut [u8],
1680    buf_stride: usize,
1681    src: &[u8],
1682    src_stride: usize,
1683    block_w: usize,
1684    block_h: usize,
1685    src_x: i32,
1686    src_y: i32,
1687    h_edge: usize,
1688    v_edge: usize,
1689) {
1690    let max_x = (h_edge as i32 - 1).max(0);
1691    let max_y = (v_edge as i32 - 1).max(0);
1692    for y in 0..block_h {
1693        let sy = (src_y + y as i32).clamp(0, max_y) as usize;
1694        let drow = y * buf_stride;
1695        let srow = sy * src_stride;
1696        for x in 0..block_w {
1697            let sx = (src_x + x as i32).clamp(0, max_x) as usize;
1698            buf[drow + x] = src[srow + sx];
1699        }
1700    }
1701}
1702
1703#[inline]
1704fn chroma_put_pixels(
1705    dst: &mut [u8],
1706    dst_off: usize,
1707    src: &[u8],
1708    src_off: usize,
1709    stride: usize,
1710    h: usize,
1711) {
1712    for y in 0..h {
1713        let d = dst_off + y * stride;
1714        let s = src_off + y * stride;
1715        dst[d..d + 8].copy_from_slice(&src[s..s + 8]);
1716    }
1717}
1718
1719#[inline]
1720fn chroma_put_x2(
1721    dst: &mut [u8],
1722    dst_off: usize,
1723    src: &[u8],
1724    src_off: usize,
1725    stride: usize,
1726    h: usize,
1727) {
1728    for y in 0..h {
1729        let d = dst_off + y * stride;
1730        let s = src_off + y * stride;
1731        for x in 0..8 {
1732            dst[d + x] = rnd_avg_u8(src[s + x], src[s + x + 1]);
1733        }
1734    }
1735}
1736
1737#[inline]
1738fn chroma_put_y2(
1739    dst: &mut [u8],
1740    dst_off: usize,
1741    src: &[u8],
1742    src_off: usize,
1743    stride: usize,
1744    h: usize,
1745) {
1746    for y in 0..h {
1747        let d = dst_off + y * stride;
1748        let s = src_off + y * stride;
1749        let s2 = s + stride;
1750        for x in 0..8 {
1751            dst[d + x] = rnd_avg_u8(src[s + x], src[s2 + x]);
1752        }
1753    }
1754}
1755
1756#[inline]
1757fn chroma_put_xy2(
1758    dst: &mut [u8],
1759    dst_off: usize,
1760    src: &[u8],
1761    src_off: usize,
1762    stride: usize,
1763    h: usize,
1764) {
1765    for y in 0..h {
1766        let d = dst_off + y * stride;
1767        let s = src_off + y * stride;
1768        let s2 = s + stride;
1769        for x in 0..8 {
1770            let a = src[s + x] as u16;
1771            let b = src[s + x + 1] as u16;
1772            let c = src[s2 + x] as u16;
1773            let e = src[s2 + x + 1] as u16;
1774            dst[d + x] = ((a + b + c + e + 2) >> 2) as u8;
1775        }
1776    }
1777}
1778
1779/// Direct port of upstream `ff_mspel_motion` for WMV2 (MV in half-luma-pel units).
1780fn wmv2_mspel_motion_mb(
1781    dst: &mut YuvFrame,
1782    reference: &YuvFrame,
1783    mb_row: usize,
1784    mb_col: usize,
1785    motion_x: i32,
1786    motion_y: i32,
1787    hshift: u8,
1788) {
1789    let fw = dst.width as usize;
1790    let fh = dst.height as usize;
1791    if fw == 0 || fh == 0 {
1792        return;
1793    }
1794    let cw = fw / 2;
1795    let ch = fh / 2;
1796
1797    // ---- Luma ----
1798    let mut dxy = (((motion_y & 1) << 1) | (motion_x & 1)) as i32;
1799    dxy = 2 * dxy + hshift as i32;
1800
1801    let mut src_x = mb_col as i32 * 16 + (motion_x >> 1);
1802    let mut src_y = mb_row as i32 * 16 + (motion_y >> 1);
1803
1804    // clip to [-16, width] / [-16, height]
1805    if src_x < -16 {
1806        src_x = -16;
1807    }
1808    if src_x > dst.width as i32 {
1809        src_x = dst.width as i32;
1810    }
1811    if src_y < -16 {
1812        src_y = -16;
1813    }
1814    if src_y > dst.height as i32 {
1815        src_y = dst.height as i32;
1816    }
1817
1818    if src_x <= -16 || src_x >= dst.width as i32 {
1819        dxy &= !3;
1820    }
1821    if src_y <= -16 || src_y >= dst.height as i32 {
1822        dxy &= !4;
1823    }
1824
1825    let linesize = fw;
1826    let mut src_plane: &[u8] = &reference.y;
1827    let mut src_off: usize;
1828
1829    // edge condition: same as upstream (using h_edge_pos=width, v_edge_pos=height)
1830    if src_x < 1
1831        || src_y < 1
1832        || src_x + 17 >= dst.width as i32
1833        || src_y + 16 + 1 >= dst.height as i32
1834    {
1835        let mut edge = vec![0u8; linesize * 19];
1836        emulated_edge_mc(
1837            &mut edge,
1838            linesize,
1839            &reference.y,
1840            linesize,
1841            19,
1842            19,
1843            src_x - 1,
1844            src_y - 1,
1845            fw,
1846            fh,
1847        );
1848        src_plane = edge.as_slice();
1849        src_off = 1 + linesize;
1850        // keep edge alive via scope capture
1851        // (we rebind below for actual reads)
1852        // NOTE: src_plane points into `edge` which must live for the rest of this function.
1853        // Rust ensures this because `edge` is in this scope.
1854
1855        // Use the edge buffer for the remainder of this luma section.
1856        let dst_x = mb_col * 16;
1857        let dst_y = mb_row * 16;
1858        let dxyu = (dxy as usize).min(7);
1859
1860        // 4x 8x8 blocks
1861        let dst00 = dst_y * linesize + dst_x;
1862        let src00 = src_off;
1863        wmv2_put_mspel_pixels(dxyu, &mut dst.y, dst00, src_plane, src00, linesize);
1864        wmv2_put_mspel_pixels(dxyu, &mut dst.y, dst00 + 8, src_plane, src00 + 8, linesize);
1865        wmv2_put_mspel_pixels(
1866            dxyu,
1867            &mut dst.y,
1868            dst00 + 8 * linesize,
1869            src_plane,
1870            src00 + 8 * linesize,
1871            linesize,
1872        );
1873        wmv2_put_mspel_pixels(
1874            dxyu,
1875            &mut dst.y,
1876            dst00 + 8 + 8 * linesize,
1877            src_plane,
1878            src00 + 8 + 8 * linesize,
1879            linesize,
1880        );
1881
1882        // ---- Chroma (still within edge scope) ----
1883        if dst.cb.is_empty() || reference.cb.is_empty() {
1884            return;
1885        }
1886
1887        let mut cdxy = 0usize;
1888        if (motion_x & 3) != 0 {
1889            cdxy |= 1;
1890        }
1891        if (motion_y & 3) != 0 {
1892            cdxy |= 2;
1893        }
1894        let mx = motion_x >> 2;
1895        let my = motion_y >> 2;
1896
1897        let mut csrc_x = mb_col as i32 * 8 + mx;
1898        let mut csrc_y = mb_row as i32 * 8 + my;
1899
1900        if csrc_x < -8 {
1901            csrc_x = -8;
1902        }
1903        if csrc_x > (dst.width as i32 >> 1) {
1904            csrc_x = dst.width as i32 >> 1;
1905        }
1906        if csrc_y < -8 {
1907            csrc_y = -8;
1908        }
1909        if csrc_y > (dst.height as i32 >> 1) {
1910            csrc_y = dst.height as i32 >> 1;
1911        }
1912
1913        if csrc_x == (dst.width as i32 >> 1) {
1914            cdxy &= !1;
1915        }
1916        if csrc_y == (dst.height as i32 >> 1) {
1917            cdxy &= !2;
1918        }
1919
1920        let uvlinesize = cw;
1921
1922        let mut edge_uv = vec![0u8; uvlinesize * 9];
1923        // cb
1924        emulated_edge_mc(
1925            &mut edge_uv,
1926            uvlinesize,
1927            &reference.cb,
1928            uvlinesize,
1929            9,
1930            9,
1931            csrc_x,
1932            csrc_y,
1933            cw,
1934            ch,
1935        );
1936        let dst_xc = mb_col * 8;
1937        let dst_yc = mb_row * 8;
1938        let dst_cb_off = dst_yc * uvlinesize + dst_xc;
1939        match cdxy {
1940            0 => chroma_put_pixels(&mut dst.cb, dst_cb_off, &edge_uv, 0, uvlinesize, 8),
1941            1 => chroma_put_x2(&mut dst.cb, dst_cb_off, &edge_uv, 0, uvlinesize, 8),
1942            2 => chroma_put_y2(&mut dst.cb, dst_cb_off, &edge_uv, 0, uvlinesize, 8),
1943            _ => chroma_put_xy2(&mut dst.cb, dst_cb_off, &edge_uv, 0, uvlinesize, 8),
1944        }
1945
1946        // cr
1947        emulated_edge_mc(
1948            &mut edge_uv,
1949            uvlinesize,
1950            &reference.cr,
1951            uvlinesize,
1952            9,
1953            9,
1954            csrc_x,
1955            csrc_y,
1956            cw,
1957            ch,
1958        );
1959        let dst_cr_off = dst_yc * uvlinesize + dst_xc;
1960        match cdxy {
1961            0 => chroma_put_pixels(&mut dst.cr, dst_cr_off, &edge_uv, 0, uvlinesize, 8),
1962            1 => chroma_put_x2(&mut dst.cr, dst_cr_off, &edge_uv, 0, uvlinesize, 8),
1963            2 => chroma_put_y2(&mut dst.cr, dst_cr_off, &edge_uv, 0, uvlinesize, 8),
1964            _ => chroma_put_xy2(&mut dst.cr, dst_cr_off, &edge_uv, 0, uvlinesize, 8),
1965        }
1966        return;
1967    }
1968
1969    // non-emu luma
1970    src_off = (src_y as usize) * linesize + (src_x as usize);
1971    let dst_x = mb_col * 16;
1972    let dst_y = mb_row * 16;
1973    let dxyu = (dxy as usize).min(7);
1974
1975    let dst00 = dst_y * linesize + dst_x;
1976    wmv2_put_mspel_pixels(dxyu, &mut dst.y, dst00, src_plane, src_off, linesize);
1977    wmv2_put_mspel_pixels(
1978        dxyu,
1979        &mut dst.y,
1980        dst00 + 8,
1981        src_plane,
1982        src_off + 8,
1983        linesize,
1984    );
1985    wmv2_put_mspel_pixels(
1986        dxyu,
1987        &mut dst.y,
1988        dst00 + 8 * linesize,
1989        src_plane,
1990        src_off + 8 * linesize,
1991        linesize,
1992    );
1993    wmv2_put_mspel_pixels(
1994        dxyu,
1995        &mut dst.y,
1996        dst00 + 8 + 8 * linesize,
1997        src_plane,
1998        src_off + 8 + 8 * linesize,
1999        linesize,
2000    );
2001
2002    // ---- Chroma ----
2003    if dst.cb.is_empty() || reference.cb.is_empty() {
2004        return;
2005    }
2006
2007    let mut cdxy = 0usize;
2008    if (motion_x & 3) != 0 {
2009        cdxy |= 1;
2010    }
2011    if (motion_y & 3) != 0 {
2012        cdxy |= 2;
2013    }
2014    let mx = motion_x >> 2;
2015    let my = motion_y >> 2;
2016
2017    let mut csrc_x = mb_col as i32 * 8 + mx;
2018    let mut csrc_y = mb_row as i32 * 8 + my;
2019
2020    if csrc_x < -8 {
2021        csrc_x = -8;
2022    }
2023    if csrc_x > (dst.width as i32 >> 1) {
2024        csrc_x = dst.width as i32 >> 1;
2025    }
2026    if csrc_y < -8 {
2027        csrc_y = -8;
2028    }
2029    if csrc_y > (dst.height as i32 >> 1) {
2030        csrc_y = dst.height as i32 >> 1;
2031    }
2032
2033    if csrc_x == (dst.width as i32 >> 1) {
2034        cdxy &= !1;
2035    }
2036    if csrc_y == (dst.height as i32 >> 1) {
2037        cdxy &= !2;
2038    }
2039
2040    let uvlinesize = cw;
2041    let need_emu_uv =
2042        csrc_x < 0 || csrc_y < 0 || csrc_x + 9 >= cw as i32 || csrc_y + 9 >= ch as i32;
2043    if need_emu_uv {
2044        let mut edge_uv = vec![0u8; uvlinesize * 9];
2045        let dst_xc = mb_col * 8;
2046        let dst_yc = mb_row * 8;
2047        let dst_cb_off = dst_yc * uvlinesize + dst_xc;
2048        emulated_edge_mc(
2049            &mut edge_uv,
2050            uvlinesize,
2051            &reference.cb,
2052            uvlinesize,
2053            9,
2054            9,
2055            csrc_x,
2056            csrc_y,
2057            cw,
2058            ch,
2059        );
2060        match cdxy {
2061            0 => chroma_put_pixels(&mut dst.cb, dst_cb_off, &edge_uv, 0, uvlinesize, 8),
2062            1 => chroma_put_x2(&mut dst.cb, dst_cb_off, &edge_uv, 0, uvlinesize, 8),
2063            2 => chroma_put_y2(&mut dst.cb, dst_cb_off, &edge_uv, 0, uvlinesize, 8),
2064            _ => chroma_put_xy2(&mut dst.cb, dst_cb_off, &edge_uv, 0, uvlinesize, 8),
2065        }
2066
2067        let dst_cr_off = dst_yc * uvlinesize + dst_xc;
2068        emulated_edge_mc(
2069            &mut edge_uv,
2070            uvlinesize,
2071            &reference.cr,
2072            uvlinesize,
2073            9,
2074            9,
2075            csrc_x,
2076            csrc_y,
2077            cw,
2078            ch,
2079        );
2080        match cdxy {
2081            0 => chroma_put_pixels(&mut dst.cr, dst_cr_off, &edge_uv, 0, uvlinesize, 8),
2082            1 => chroma_put_x2(&mut dst.cr, dst_cr_off, &edge_uv, 0, uvlinesize, 8),
2083            2 => chroma_put_y2(&mut dst.cr, dst_cr_off, &edge_uv, 0, uvlinesize, 8),
2084            _ => chroma_put_xy2(&mut dst.cr, dst_cr_off, &edge_uv, 0, uvlinesize, 8),
2085        }
2086        return;
2087    }
2088    let coff = (csrc_y as usize) * uvlinesize + (csrc_x as usize);
2089    let dst_xc = mb_col * 8;
2090    let dst_yc = mb_row * 8;
2091    let dst_cb_off = dst_yc * uvlinesize + dst_xc;
2092
2093    match cdxy {
2094        0 => chroma_put_pixels(&mut dst.cb, dst_cb_off, &reference.cb, coff, uvlinesize, 8),
2095        1 => chroma_put_x2(&mut dst.cb, dst_cb_off, &reference.cb, coff, uvlinesize, 8),
2096        2 => chroma_put_y2(&mut dst.cb, dst_cb_off, &reference.cb, coff, uvlinesize, 8),
2097        _ => chroma_put_xy2(&mut dst.cb, dst_cb_off, &reference.cb, coff, uvlinesize, 8),
2098    }
2099
2100    let dst_cr_off = dst_yc * uvlinesize + dst_xc;
2101    match cdxy {
2102        0 => chroma_put_pixels(&mut dst.cr, dst_cr_off, &reference.cr, coff, uvlinesize, 8),
2103        1 => chroma_put_x2(&mut dst.cr, dst_cr_off, &reference.cr, coff, uvlinesize, 8),
2104        2 => chroma_put_y2(&mut dst.cr, dst_cr_off, &reference.cr, coff, uvlinesize, 8),
2105        _ => chroma_put_xy2(&mut dst.cr, dst_cr_off, &reference.cr, coff, uvlinesize, 8),
2106    }
2107}
2108
2109fn add_residual_block(
2110    frame: &mut YuvFrame,
2111    mb_row: u32,
2112    mb_col: u32,
2113    blk: usize,
2114    coeff: &[i32; 64],
2115) {
2116    let (is_luma, bx, by, stride, ph) =
2117        block_coords(mb_row, mb_col, blk, frame.width, frame.height);
2118    let plane: &mut Vec<u8> = if is_luma {
2119        &mut frame.y
2120    } else if blk == 4 {
2121        &mut frame.cb
2122    } else {
2123        &mut frame.cr
2124    };
2125    for r in 0..8 {
2126        if by + r >= ph {
2127            break;
2128        }
2129        for c in 0..8 {
2130            if bx + c >= stride {
2131                break;
2132            }
2133            let idx = (by + r) * stride + (bx + c);
2134            plane[idx] = (plane[idx] as i32 + coeff[r * 8 + c]).clamp(0, 255) as u8;
2135        }
2136    }
2137}
2138
2139// ─── Macroblock Decoder ───────────────────────────────────────────────────────
2140
2141// ─── upstream RLTable (WMV1/2/MSMPEG4) ───────────────────────────────────
2142
2143const FF_RL_MAX_RUN: usize = 64;
2144const FF_RL_MAX_LEVEL: usize = 64;
2145
2146#[derive(Clone)]
2147struct Wmv2Rl {
2148    n: usize,
2149    last: usize,
2150    vlc: VlcTree,
2151    run: &'static [u8],
2152    level: &'static [u8],
2153    max_level: [[u8; FF_RL_MAX_RUN + 1]; 2],
2154    max_run: [[u8; FF_RL_MAX_LEVEL + 1]; 2],
2155}
2156
2157impl Wmv2Rl {
2158    fn new(base: &crate::na_rl_tables::RlBase) -> Self {
2159        let mut t = VlcTree::new();
2160        for (idx, (code, len)) in base.vlc.iter().enumerate() {
2161            if *len != 0 {
2162                t.insert(*code, *len, idx as i32);
2163            }
2164        }
2165
2166        let mut max_level = [[0u8; FF_RL_MAX_RUN + 1]; 2];
2167        let mut max_run = [[0u8; FF_RL_MAX_LEVEL + 1]; 2];
2168
2169        for last_flag in 0..2usize {
2170            let (start, end) = if last_flag == 0 {
2171                (0usize, base.last)
2172            } else {
2173                (base.last, base.n)
2174            };
2175            for i in start..end {
2176                let r = base.run[i] as usize;
2177                let l = base.level[i] as usize;
2178                if r <= FF_RL_MAX_RUN && l <= FF_RL_MAX_LEVEL {
2179                    if base.level[i] > max_level[last_flag][r] {
2180                        max_level[last_flag][r] = base.level[i];
2181                    }
2182                    if base.run[i] > max_run[last_flag][l] {
2183                        max_run[last_flag][l] = base.run[i];
2184                    }
2185                }
2186            }
2187        }
2188
2189        Wmv2Rl {
2190            n: base.n,
2191            last: base.last,
2192            vlc: t,
2193            run: base.run,
2194            level: base.level,
2195            max_level,
2196            max_run,
2197        }
2198    }
2199
2200    #[inline(always)]
2201    fn decode_sym(&self, br: &mut BitReader<'_>, qscale: i32) -> Option<(i32, i32)> {
2202        let idx = self.vlc.decode(br)? as usize;
2203        if idx == self.n {
2204            // Match upstream ff_rl_init_vlc(): escape maps to level==0, run==66.
2205            // Using run==0 can underflow i (starts at -1) and trigger OOB.
2206            return Some((0, 66));
2207        }
2208        let (qmul, qadd) = if qscale == 0 {
2209            (1i32, 0i32)
2210        } else {
2211            (qscale * 2, (qscale - 1) | 1)
2212        };
2213        let mut run = (self.run[idx] as i32) + 1;
2214        let level = (self.level[idx] as i32) * qmul + qadd;
2215        if idx >= self.last {
2216            run += 192;
2217        }
2218        Some((level, run))
2219    }
2220
2221    #[inline(always)]
2222    fn max_level_for(&self, last: usize, run: usize) -> i32 {
2223        self.max_level[last.min(1)][run.min(FF_RL_MAX_RUN)] as i32
2224    }
2225
2226    #[inline(always)]
2227    fn max_run_for(&self, last: usize, level: usize) -> i32 {
2228        self.max_run[last.min(1)][level.min(FF_RL_MAX_LEVEL)] as i32
2229    }
2230}
2231
2232pub struct MacroblockDecoder {
2233    pub width: u32,
2234    pub height: u32,
2235    pub width_mb: u32,
2236    pub height_mb: u32,
2237    /// Reference frame for P/B decoding
2238    pub ref_frame: Option<YuvFrame>,
2239    // Lazily-built VLC tables
2240    dc_luma: VlcTable,
2241    dc_chroma: VlcTable,
2242    ac_inter: [VlcTable; 4],
2243    ac_intra: [VlcTable; 4],
2244    cbpcy_i: VlcTable,
2245    cbpcy_p: [VlcTable; 2], // CBPTAB 0-1
2246    ttmb: VlcTable,
2247    ttblk: VlcTable,
2248    mv_vlc: [VlcTable; 4], // MVTAB 0-3
2249    dc_pred: DcPredBuffer,
2250    mv_pred: MvPredictor,
2251    // ── WMV2 VLC tables (built lazily; shared with VC-1 decode machinery) ─────
2252    wmv2_inter: [VlcTable; 2], // ttcoef 0-1
2253    wmv2_intra: [VlcTable; 2],
2254    wmv2_cbpy: VlcTable,
2255    wmv2_cbpc: VlcTable,
2256    /// WMV2 reference frame (single-reference; no B-frame support)
2257    wmv2_ref: Option<YuvFrame>,
2258    // ── WMV2/MSMPEG4 (upstream-aligned) VLCs / state ─────────────────────────
2259    wmv2_mb_i_vlc: VlcTree,
2260    wmv2_dc_vlc: [[VlcTree; 2]; 2], // [dc_table_index][is_chroma]
2261    wmv2_coded_block: Vec<u8>,      // coded_block predictor grid (luma 8×8)
2262    wmv2_dc_pred: Wmv2DcPredBuffer,
2263    // ext-header flags (decode_ext_header)
2264    wmv2_mspel_bit: bool,
2265    wmv2_abt_flag: bool,
2266    wmv2_j_type_bit: bool,
2267    wmv2_top_left_mv_flag: bool,
2268    wmv2_per_mb_rl_bit: bool,
2269    // per-picture derived state (secondary picture header)
2270    wmv2_j_type: bool,
2271    wmv2_per_mb_rl_table: bool,
2272    wmv2_rl_table_index: u8,
2273    wmv2_rl_chroma_table_index: u8,
2274    wmv2_dc_table_index: usize,
2275
2276    // P-picture secondary header state (upstream wmv2dec.c)
2277    wmv2_cbp_table_index: usize,
2278    wmv2_mv_table_index: usize,
2279    wmv2_mspel: bool,
2280    wmv2_hshift: u8,
2281    wmv2_per_mb_abt: bool,
2282    wmv2_abt_type: u8,
2283    wmv2_skip_type: u8,
2284    wmv2_slice_height: usize,
2285    wmv2_mb_skip: Vec<bool>,
2286    wmv2_motion: Vec<(i32, i32)>,
2287
2288    // upstream MB and MV VLC tables
2289    wmv2_mb_non_intra_vlc: [VlcTree; 4],
2290    wmv2_mv_vlc: [VlcTree; 2],
2291    // upstream RL tables (run/level)
2292    wmv2_rl: [Wmv2Rl; 6],
2293    // WMV2 escape-3 adaptive lengths (reset each picture)
2294    wmv2_esc3_level_length: u8,
2295    wmv2_esc3_run_length: u8,
2296    // AC prediction buffer (16 values per block: [1..7] left, [9..15] top)
2297    wmv2_ac_val: Vec<[i16; 16]>,
2298    /// Whether the last stored reference frame had RANGEREDFRM applied
2299    ref_rangeredfrm: bool,
2300    ac_pred: AcPredBuffer,
2301    /// Forward reference (anchor before B-frames in display order)
2302    fwd_ref: Option<YuvFrame>,
2303    /// Backward reference (anchor after B-frames in display order)
2304    bwd_ref: Option<YuvFrame>,
2305}
2306
2307impl MacroblockDecoder {
2308    pub fn new(width: u32, height: u32) -> Self {
2309        let mb_w = ((width + 15) / 16) as usize;
2310        let mb_h = ((height + 15) / 16) as usize;
2311
2312        // Build upstream MSMPEG4/WMV2 VLCs (MB I-table + DC tables).
2313        let wmv2_mb_i_vlc: VlcTree = {
2314            let mut t = VlcTree::new();
2315            for (sym, (code, len)) in FF_MSMP4_MB_I_TABLE.iter().enumerate() {
2316                t.insert(*code, *len, sym as i32);
2317            }
2318            t
2319        };
2320
2321        let wmv2_dc_vlc: [[VlcTree; 2]; 2] = std::array::from_fn(|ti| {
2322            std::array::from_fn(|ch| {
2323                let mut t = VlcTree::new();
2324                for (sym, (code, len)) in FF_MSMP4_DC_TABLES[ti][ch].iter().enumerate() {
2325                    t.insert(*code, *len, sym as i32);
2326                }
2327                t
2328            })
2329        });
2330
2331        // upstream mb_non_intra VLC tables (4 variants)
2332        let wmv2_mb_non_intra_vlc: [VlcTree; 4] = std::array::from_fn(|ti| {
2333            let mut t = VlcTree::new();
2334            for (sym, (code, len)) in FF_MB_NON_INTRA_TABLES[ti].iter().enumerate() {
2335                if *len != 0 {
2336                    t.insert(*code, *len, sym as i32);
2337                }
2338            }
2339            t
2340        });
2341
2342        // upstream motion vector VLC tables (2 variants)
2343        // Built exactly like ff_vlc_init_tables_from_lengths() + ff_vlc_init_from_lengths()
2344        // (msmpeg4dec.c msmpeg4_decode_init_static).
2345        let build_mv_from_lengths = |lens: &[u8; 1100], syms: &[u16; 1100]| -> VlcTree {
2346            let mut t = VlcTree::new();
2347            let mut code: u32 = 0;
2348            for i in 0..1100usize {
2349                let len = lens[i] as i32;
2350                if len == 0 {
2351                    continue;
2352                }
2353                let l = len.abs() as u8;
2354                // upstream stores code left-aligned in a 32-bit word.
2355                let right_aligned = if l == 0 { 0 } else { code >> (32 - l) };
2356                if len > 0 {
2357                    t.insert(right_aligned, l, syms[i] as i32);
2358                }
2359                code = code.wrapping_add(1u32 << (32 - l));
2360            }
2361            t
2362        };
2363        let wmv2_mv_vlc: [VlcTree; 2] = [
2364            build_mv_from_lengths(&FF_MSMP4_MV_TABLE0_LENS, &FF_MSMP4_MV_TABLE0),
2365            build_mv_from_lengths(&FF_MSMP4_MV_TABLE1_LENS, &FF_MSMP4_MV_TABLE1),
2366        ];
2367
2368        // upstream RL tables (run/level)
2369        let wmv2_rl: [Wmv2Rl; 6] = std::array::from_fn(|i| Wmv2Rl::new(&FF_RL_BASES[i]));
2370        let wmv2_ac_val: Vec<[i16; 16]> = vec![[0i16; 16]; mb_w * mb_h * 6];
2371
2372        MacroblockDecoder {
2373            width,
2374            height,
2375            width_mb: (width + 15) / 16,
2376            height_mb: (height + 15) / 16,
2377            ref_frame: None,
2378            dc_luma: dc_luma_vlc(),
2379            dc_chroma: dc_chroma_vlc(),
2380            ac_inter: [
2381                inter_tcoef_vlc(0),
2382                inter_tcoef_vlc(1),
2383                inter_tcoef_vlc(2),
2384                inter_tcoef_vlc(3),
2385            ],
2386            ac_intra: [
2387                intra_tcoef_vlc(0),
2388                intra_tcoef_vlc(1),
2389                intra_tcoef_vlc(2),
2390                intra_tcoef_vlc(3),
2391            ],
2392            cbpcy_i: cbpcy_i_vlc(),
2393            cbpcy_p: [cbpcy_p_vlc(0), cbpcy_p_vlc(1)],
2394            ttmb: ttmb_vlc(),
2395            ttblk: ttblk_vlc(),
2396            mv_vlc: [
2397                mv_diff_vlc(0),
2398                mv_diff_vlc(1),
2399                mv_diff_vlc(2),
2400                mv_diff_vlc(3),
2401            ],
2402            dc_pred: DcPredBuffer::new(mb_w, mb_h),
2403            mv_pred: MvPredictor::new(mb_w, mb_h),
2404            ref_rangeredfrm: false,
2405            ac_pred: AcPredBuffer::new(mb_w, mb_h),
2406            fwd_ref: None,
2407            bwd_ref: None,
2408            wmv2_inter: [wmv2_tcoef_inter_vlc(0), wmv2_tcoef_inter_vlc(1)],
2409            wmv2_intra: [wmv2_tcoef_intra_vlc(0), wmv2_tcoef_intra_vlc(1)],
2410            wmv2_cbpy: wmv2_cbpy_vlc(),
2411            wmv2_cbpc: wmv2_cbpc_p_vlc(),
2412            wmv2_ref: None,
2413
2414            // upstream-aligned WMV2/MSMPEG4 state
2415            wmv2_mb_i_vlc,
2416            wmv2_dc_vlc,
2417            wmv2_coded_block: vec![0u8; (2 * mb_w) * (2 * mb_h)],
2418            wmv2_dc_pred: Wmv2DcPredBuffer::new(mb_w, mb_h),
2419
2420            // ext-header flags (default false until set_extradata)
2421            wmv2_mspel_bit: false,
2422            wmv2_abt_flag: false,
2423            wmv2_j_type_bit: false,
2424            wmv2_top_left_mv_flag: false,
2425            wmv2_per_mb_rl_bit: false,
2426
2427            // per-picture derived state
2428            wmv2_j_type: false,
2429            wmv2_per_mb_rl_table: false,
2430            wmv2_rl_table_index: 0,
2431            wmv2_rl_chroma_table_index: 0,
2432            wmv2_dc_table_index: 0,
2433
2434            wmv2_cbp_table_index: 0,
2435            wmv2_mv_table_index: 0,
2436            wmv2_mspel: false,
2437            wmv2_hshift: 0,
2438            wmv2_per_mb_abt: false,
2439            wmv2_abt_type: 0,
2440            wmv2_skip_type: 0,
2441            wmv2_slice_height: mb_h.max(1),
2442            wmv2_mb_skip: vec![false; mb_w * mb_h],
2443            wmv2_motion: vec![(0, 0); mb_w * mb_h],
2444
2445            wmv2_mb_non_intra_vlc,
2446            wmv2_mv_vlc,
2447
2448            wmv2_rl,
2449            wmv2_esc3_level_length: 0,
2450            wmv2_esc3_run_length: 0,
2451            wmv2_ac_val,
2452        }
2453    }
2454
2455    pub fn decode_frame(
2456        &mut self,
2457        payload: &[u8],
2458        pic_hdr: &PictureHeader,
2459        seq: &SequenceHeader,
2460        frame: &mut YuvFrame,
2461    ) -> Result<()> {
2462        match pic_hdr.frame_type {
2463            FrameType::I | FrameType::BI => {
2464                self.decode_intra(payload, pic_hdr, seq, frame)?;
2465                if seq.overlap && pic_hdr.pquant >= 9 {
2466                    apply_overlap_filter(frame);
2467                }
2468                if seq.loop_filter {
2469                    apply_loop_filter(frame);
2470                }
2471            }
2472            FrameType::P => {
2473                if seq.rangered {
2474                    let cur_rr = pic_hdr.rangeredfrm;
2475                    let ref_rr = self.ref_rangeredfrm;
2476                    if ref_rr && !cur_rr {
2477                        if let Some(ref mut rf) = self.ref_frame {
2478                            apply_rangered_compress(rf);
2479                        }
2480                    }
2481                }
2482                self.decode_p(payload, pic_hdr, seq, frame)?;
2483                if seq.loop_filter {
2484                    apply_loop_filter(frame);
2485                }
2486            }
2487            FrameType::B => {
2488                self.decode_b(payload, pic_hdr, seq, frame)?;
2489                if seq.loop_filter {
2490                    apply_loop_filter(frame);
2491                }
2492            }
2493            FrameType::Skipped => {
2494                if let Some(ref rf) = self.ref_frame {
2495                    frame.y.copy_from_slice(&rf.y);
2496                    frame.cb.copy_from_slice(&rf.cb);
2497                    frame.cr.copy_from_slice(&rf.cr);
2498                }
2499            }
2500        }
2501
2502        // Post-decode: expand range if RANGEREDFRM
2503        if seq.rangered && pic_hdr.rangeredfrm {
2504            apply_rangered_expand(frame);
2505        }
2506        self.ref_rangeredfrm = pic_hdr.rangeredfrm;
2507
2508        // Update reference frame chain.
2509        // Anchor frames (I/P) become forward reference for upcoming B-frames
2510        // and also get stored as the backward reference.
2511        match pic_hdr.frame_type {
2512            FrameType::B | FrameType::BI => {
2513                // B-frames don't update the anchor chain
2514            }
2515            _ => {
2516                // Current forward becomes previous, new frame becomes forward anchor
2517                self.fwd_ref = self.bwd_ref.take();
2518                self.bwd_ref = Some(frame.clone());
2519                self.ref_frame = Some(frame.clone());
2520            }
2521        }
2522        Ok(())
2523    }
2524
2525    // ─── Intra frame ─────────────────────────────────────────────────────────
2526
2527    fn decode_intra(
2528        &mut self,
2529        payload: &[u8],
2530        pic_hdr: &PictureHeader,
2531        seq: &SequenceHeader,
2532        frame: &mut YuvFrame,
2533    ) -> Result<()> {
2534        // The ASF payload includes the picture header and bitplanes.
2535        // Start macroblock decoding exactly at the macroblock layer.
2536        let mut br = BitReader::new_at(payload, pic_hdr.header_bits);
2537        let pquant = pic_hdr.pquant as i32;
2538        let halfqp = pic_hdr.halfqp;
2539        let uniform = seq.quantizer_mode != crate::vc1::QuantizerMode::NonUniform;
2540
2541        // Reset DC and AC prediction buffers for this frame
2542        let mb_w = self.width_mb as usize;
2543        let mb_h = self.height_mb as usize;
2544        self.dc_pred = DcPredBuffer::new(mb_w, mb_h);
2545        self.ac_pred = AcPredBuffer::new(mb_w, mb_h);
2546
2547        for mb_row in 0..self.height_mb {
2548            for mb_col in 0..self.width_mb {
2549                if br.is_empty() {
2550                    return Ok(());
2551                }
2552
2553                // CBPCY: 6-bit coded-block pattern
2554                let cbp = self.cbpcy_i.decode(&mut br).unwrap_or(0) as u8;
2555
2556                // Per-MB quantizer override (DQUANT)
2557                let mb_pquant = read_mquant(&mut br, seq.dquant, pquant);
2558
2559                // Transform type for this MB
2560                let mb_tt = if seq.vstransform {
2561                    self.ttmb.decode(&mut br).unwrap_or(0) as u8
2562                } else {
2563                    0
2564                };
2565
2566                for blk in 0..6usize {
2567                    let is_luma = blk < 4;
2568
2569                    // ── DC prediction (SMPTE 421M §8.1.4.6) ──────────────────
2570                    let dc_vlc = if is_luma {
2571                        &self.dc_luma
2572                    } else {
2573                        &self.dc_chroma
2574                    };
2575                    let dc_diff = read_dc_diff(&mut br, dc_vlc);
2576
2577                    // DC step from SMPTE 421M Table 3 (×128 domain for IDCT)
2578                    let dc_scale = dc_step(mb_pquant, is_luma);
2579                    let (dc_pred_val, _use_left) =
2580                        self.dc_pred.predict(mb_row as usize, mb_col as usize, blk);
2581
2582                    let dc_recon = dc_pred_val + dc_diff * dc_scale;
2583                    self.dc_pred
2584                        .store(mb_row as usize, mb_col as usize, blk, dc_recon);
2585
2586                    let coded = (cbp >> (5 - blk)) & 1 != 0;
2587                    let blk_tt = if coded && mb_tt == 6 {
2588                        self.ttblk.decode(&mut br).unwrap_or(0) as u8
2589                    } else {
2590                        mb_tt
2591                    };
2592
2593                    let mut coeff = if coded {
2594                        decode_block_ac(
2595                            &mut br,
2596                            is_luma,
2597                            mb_pquant,
2598                            halfqp,
2599                            uniform,
2600                            blk_tt,
2601                            &self.ac_intra[seq.transacfrm2 as usize],
2602                        )
2603                    } else {
2604                        [0i32; 64]
2605                    };
2606
2607                    // Place reconstructed DC into coeff[0]
2608                    coeff[0] = dc_recon;
2609
2610                    // ── AC Prediction (SMPTE 421M §8.1.4.7) ──────────────────
2611                    // The direction follows the DC predictor choice:
2612                    // use_left=true  → horizontal: add pred_row to coeff[1..7]
2613                    // use_left=false → vertical:   add pred_col to coeff[8,16,..56]
2614                    if _use_left {
2615                        // Horizontal: predictor is first row of left neighbour
2616                        let pred = self.ac_pred.pred_row(mb_row as usize, mb_col as usize, blk);
2617                        for i in 0..7 {
2618                            coeff[i + 1] += pred[i];
2619                        }
2620                        // Store our first row for future left→right neighbours
2621                        let our_row = [
2622                            coeff[1], coeff[2], coeff[3], coeff[4], coeff[5], coeff[6], coeff[7],
2623                        ];
2624                        self.ac_pred
2625                            .store_row(mb_row as usize, mb_col as usize, blk, our_row);
2626                    } else {
2627                        // Vertical: predictor is first column of top neighbour
2628                        let pred = self.ac_pred.pred_col(mb_row as usize, mb_col as usize, blk);
2629                        for i in 0..7 {
2630                            coeff[(i + 1) * 8] += pred[i];
2631                        }
2632                        let our_col = [
2633                            coeff[8], coeff[16], coeff[24], coeff[32], coeff[40], coeff[48],
2634                            coeff[56],
2635                        ];
2636                        self.ac_pred
2637                            .store_col(mb_row as usize, mb_col as usize, blk, our_col);
2638                    }
2639
2640                    apply_idct(&mut coeff, blk_tt);
2641                    write_intra_block(frame, mb_row, mb_col, blk, &coeff);
2642                }
2643            }
2644        }
2645        Ok(())
2646    }
2647
2648    // ─── P frame ─────────────────────────────────────────────────────────────
2649
2650    fn decode_p(
2651        &mut self,
2652        payload: &[u8],
2653        pic_hdr: &PictureHeader,
2654        seq: &SequenceHeader,
2655        frame: &mut YuvFrame,
2656    ) -> Result<()> {
2657        // Start from reference frame copy
2658        if let Some(ref rf) = self.ref_frame {
2659            frame.y.copy_from_slice(&rf.y);
2660            frame.cb.copy_from_slice(&rf.cb);
2661            frame.cr.copy_from_slice(&rf.cr);
2662        }
2663
2664        let mut br = BitReader::new_at(payload, pic_hdr.header_bits);
2665        let pquant = pic_hdr.pquant as i32;
2666        let halfqp = pic_hdr.halfqp;
2667        let uniform = seq.quantizer_mode != crate::vc1::QuantizerMode::NonUniform;
2668        let mv_vlc = &self.mv_vlc[seq.mvtab as usize];
2669
2670        // MV range (quarter-pel)
2671        let mv_scale = 1i32 << (pic_hdr.mvrange as i32 + 1);
2672
2673        let ref_y = self
2674            .ref_frame
2675            .as_ref()
2676            .map(|f| f.y.clone())
2677            .unwrap_or_default();
2678        let ref_cb = self
2679            .ref_frame
2680            .as_ref()
2681            .map(|f| f.cb.clone())
2682            .unwrap_or_default();
2683        let ref_cr = self
2684            .ref_frame
2685            .as_ref()
2686            .map(|f| f.cr.clone())
2687            .unwrap_or_default();
2688        let fw = self.width as usize;
2689        let fh = self.height as usize;
2690
2691        // Reset MV predictor for this frame
2692        let mb_w = self.width_mb as usize;
2693        let mb_h = self.height_mb as usize;
2694        self.mv_pred = MvPredictor::new(mb_w, mb_h);
2695
2696        // Read skipped-MB bitplane from picture header
2697        let skip_plane = pic_hdr.skipmb_plane.clone().unwrap_or_default();
2698
2699        for mb_row in 0..self.height_mb {
2700            for mb_col in 0..self.width_mb {
2701                if br.is_empty() {
2702                    return Ok(());
2703                }
2704
2705                let mb_idx = mb_row as usize * mb_w + mb_col as usize;
2706
2707                // Skipped macroblock (from bitplane or inline 1-bit flag)
2708                let skipped = if skip_plane.is_empty() {
2709                    br.read_bit().unwrap_or(false)
2710                } else {
2711                    skip_plane.get(mb_idx).copied().unwrap_or(0) != 0
2712                };
2713
2714                if skipped {
2715                    // Use predicted MV for skipped MB (copy from reference)
2716                    let (pvx, pvy) = self.mv_pred.predict(mb_row as usize, mb_col as usize);
2717                    self.mv_pred
2718                        .store(mb_row as usize, mb_col as usize, (pvx, pvy), true);
2719                    continue;
2720                }
2721
2722                // Median MV predictor from A (left), B (top), C (top-right)
2723                let (pvx, pvy) = self.mv_pred.predict(mb_row as usize, mb_col as usize);
2724
2725                // Motion vector differential
2726                let dmvx = Self::read_mv_diff(&mut br, mv_vlc, mv_scale);
2727                let dmvy = Self::read_mv_diff(&mut br, mv_vlc, mv_scale);
2728                let mvx = pvx + dmvx;
2729                let mvy = pvy + dmvy;
2730
2731                // Store for future neighbours
2732                self.mv_pred
2733                    .store(mb_row as usize, mb_col as usize, (mvx, mvy), false);
2734
2735                // Luma motion compensation (16×16)
2736                {
2737                    let dst_x = (mb_col * 16) as usize;
2738                    let dst_y = (mb_row * 16) as usize;
2739                    let src_x = (dst_x as i32 * 2 + mvx) as i32; // half-pel
2740                    let src_y = (dst_y as i32 * 2 + mvy) as i32;
2741                    let mut tmp = [0u8; 256];
2742                    if ref_y.len() == fw * fh {
2743                        mc_luma(&mut tmp, 16, &ref_y, fw, fw, fh, src_x, src_y, 16, 16);
2744                        let dst = &mut frame.y[dst_y * fw + dst_x..];
2745                        for r in 0..16 {
2746                            dst[r * fw..r * fw + 16].copy_from_slice(&tmp[r * 16..r * 16 + 16]);
2747                        }
2748                    }
2749                }
2750
2751                // Chroma MC (8×8, MV /2 with optional FASTUVMC rounding)
2752                {
2753                    let cw = fw / 2;
2754                    let ch = fh / 2;
2755                    let dst_x = (mb_col * 8) as usize;
2756                    let dst_y = (mb_row * 8) as usize;
2757                    // Chroma MV is luma MV / 2 (half-pel chroma = quarter-pel luma)
2758                    let cmvx_raw = mvx / 2;
2759                    let cmvy_raw = mvy / 2;
2760                    // FASTUVMC: round chroma MVs so that fractional part is 0 or ½ pel
2761                    // i.e. strip the quarter-pel bit, rounding toward zero.
2762                    let (cmvx, cmvy) = if seq.fastuvmc {
2763                        // round: remove lowest half-pel bit, biased toward zero
2764                        let round = |v: i32| -> i32 {
2765                            if v >= 0 {
2766                                v & !1
2767                            } else {
2768                                -((-v) & !1)
2769                            }
2770                        };
2771                        (round(cmvx_raw), round(cmvy_raw))
2772                    } else {
2773                        (cmvx_raw, cmvy_raw)
2774                    };
2775                    let src_x = (dst_x as i32 * 2 + cmvx) as i32;
2776                    let src_y = (dst_y as i32 * 2 + cmvy) as i32;
2777                    let mut tmp_cb = [0u8; 64];
2778                    let mut tmp_cr = [0u8; 64];
2779                    if ref_cb.len() == cw * ch {
2780                        mc_luma(&mut tmp_cb, 8, &ref_cb, cw, cw, ch, src_x, src_y, 8, 8);
2781                        mc_luma(&mut tmp_cr, 8, &ref_cr, cw, cw, ch, src_x, src_y, 8, 8);
2782                        let dcb = &mut frame.cb[dst_y * cw + dst_x..];
2783                        let dcr = &mut frame.cr[dst_y * cw + dst_x..];
2784                        for r in 0..8 {
2785                            dcb[r * cw..r * cw + 8].copy_from_slice(&tmp_cb[r * 8..r * 8 + 8]);
2786                            dcr[r * cw..r * cw + 8].copy_from_slice(&tmp_cr[r * 8..r * 8 + 8]);
2787                        }
2788                    }
2789                }
2790
2791                // Residual (CBPCY + coefficients)
2792                let cbp = self.cbpcy_p[(seq.cbptab as usize).min(1)]
2793                    .decode(&mut br)
2794                    .unwrap_or(0) as u8;
2795                // Per-MB quantizer (DQUANT)
2796                let mb_pquant = read_mquant(&mut br, seq.dquant, pquant);
2797                let mb_tt = if seq.vstransform {
2798                    self.ttmb.decode(&mut br).unwrap_or(0) as u8
2799                } else {
2800                    0
2801                };
2802
2803                for blk in 0..6usize {
2804                    if (cbp >> (5 - blk)) & 1 == 0 {
2805                        continue;
2806                    }
2807                    let is_luma = blk < 4;
2808                    let blk_tt = if mb_tt == 6 {
2809                        self.ttblk.decode(&mut br).unwrap_or(0) as u8
2810                    } else {
2811                        mb_tt
2812                    };
2813
2814                    let mut coeff = decode_block(
2815                        &mut br,
2816                        false,
2817                        is_luma,
2818                        mb_pquant,
2819                        halfqp,
2820                        uniform,
2821                        blk_tt,
2822                        &self.dc_luma,
2823                        &self.dc_chroma,
2824                        &self.ac_intra[seq.transacfrm2 as usize],
2825                        &self.ac_inter[seq.transacfrm as usize],
2826                    );
2827                    apply_idct(&mut coeff, blk_tt);
2828                    add_residual_block(frame, mb_row as u32, mb_col as u32, blk, &coeff);
2829                }
2830            }
2831        }
2832        Ok(())
2833    }
2834
2835    // ─── B frame ─────────────────────────────────────────────────────────────
2836    // SMPTE 421M §8.4.
2837    // Each MB can be: direct (interpolated from fwd+bwd), forward, backward,
2838    // bidirectional, or intra.  We decode the MB type and both MV differentials
2839    // then blend fwd and bwd MC results.
2840
2841    fn decode_b(
2842        &mut self,
2843        payload: &[u8],
2844        pic_hdr: &PictureHeader,
2845        seq: &SequenceHeader,
2846        frame: &mut YuvFrame,
2847    ) -> Result<()> {
2848        let fwd = match &self.fwd_ref {
2849            Some(f) => f.clone(),
2850            None => return Ok(()), // no anchor yet
2851        };
2852        let bwd = match &self.bwd_ref {
2853            Some(f) => f.clone(),
2854            None => return Ok(()), // no backward anchor
2855        };
2856
2857        let mut br = BitReader::new(payload);
2858        let pquant = pic_hdr.pquant as i32;
2859        let halfqp = pic_hdr.halfqp;
2860        let uniform = seq.quantizer_mode != crate::vc1::QuantizerMode::NonUniform;
2861        let mv_vlc = &self.mv_vlc[seq.mvtab as usize];
2862        let mv_scale = 1i32 << (pic_hdr.mvrange as i32 + 1);
2863
2864        let fw = self.width as usize;
2865        let fh = self.height as usize;
2866        let mb_w = self.width_mb as usize;
2867        let mb_h = self.height_mb as usize;
2868        self.mv_pred = MvPredictor::new(mb_w, mb_h);
2869
2870        // Direct-mode and skip bitplanes
2871        let direct_plane = pic_hdr.directmb_plane.clone().unwrap_or_default();
2872        let skip_plane = pic_hdr.skipmb_plane.clone().unwrap_or_default();
2873
2874        // Temporal MV scaling from BFRACTION (SMPTE 421M §8.4.1.3).
2875        let direct_scale_num = pic_hdr.bfrac_num;
2876        let direct_scale_den = pic_hdr.bfrac_den;
2877
2878        for mb_row in 0..self.height_mb {
2879            for mb_col in 0..self.width_mb {
2880                if br.is_empty() {
2881                    return Ok(());
2882                }
2883
2884                let mb_idx = mb_row as usize * mb_w + mb_col as usize;
2885                let is_direct = direct_plane.get(mb_idx).copied().unwrap_or(0) != 0;
2886                let is_skip = skip_plane.get(mb_idx).copied().unwrap_or(0) != 0;
2887
2888                if is_skip || is_direct {
2889                    // Direct / skip: interpolate fwd + bwd with equal weight
2890                    let (pvx, pvy) = self.mv_pred.predict(mb_row as usize, mb_col as usize);
2891                    // Scale fwd MV and derive bwd MV
2892                    let fvx = pvx * direct_scale_num / direct_scale_den;
2893                    let fvy = pvy * direct_scale_num / direct_scale_den;
2894                    let bvx = fvx - pvx;
2895                    let bvy = fvy - pvy;
2896
2897                    self.mc_blend_mb(
2898                        frame, mb_row, mb_col, &fwd, &bwd, fvx, fvy, bvx, bvy, fw, fh,
2899                    );
2900                    self.mv_pred
2901                        .store(mb_row as usize, mb_col as usize, (pvx, pvy), is_skip);
2902                    continue;
2903                }
2904
2905                // Read MB type: 2 bits
2906                // 00=intra, 01=backward, 10=forward, 11=bidirectional
2907                let mb_type = br.read_bits(2).unwrap_or(2);
2908
2909                if mb_type == 0 {
2910                    // Intra MB in B-frame (rare)
2911                    let cbp = self.cbpcy_i.decode(&mut br).unwrap_or(0) as u8;
2912                    let mb_pquant = read_mquant(&mut br, seq.dquant, pquant);
2913                    let mb_tt = if seq.vstransform {
2914                        self.ttmb.decode(&mut br).unwrap_or(0) as u8
2915                    } else {
2916                        0
2917                    };
2918                    for blk in 0..6usize {
2919                        let coded = (cbp >> (5 - blk)) & 1 != 0;
2920                        if coded {
2921                            let blk_tt = if mb_tt == 6 {
2922                                self.ttblk.decode(&mut br).unwrap_or(0) as u8
2923                            } else {
2924                                mb_tt
2925                            };
2926                            let mut coeff = decode_block(
2927                                &mut br,
2928                                true,
2929                                blk < 4,
2930                                mb_pquant,
2931                                halfqp,
2932                                uniform,
2933                                blk_tt,
2934                                &self.dc_luma,
2935                                &self.dc_chroma,
2936                                &self.ac_intra[seq.transacfrm2 as usize],
2937                                &self.ac_inter[seq.transacfrm as usize],
2938                            );
2939                            apply_idct(&mut coeff, blk_tt);
2940                            write_intra_block(frame, mb_row, mb_col, blk, &coeff);
2941                        }
2942                    }
2943                    self.mv_pred
2944                        .store(mb_row as usize, mb_col as usize, (0, 0), false);
2945                    continue;
2946                }
2947
2948                // Motion vectors
2949                let use_fwd = mb_type == 2 || mb_type == 3;
2950                let use_bwd = mb_type == 1 || mb_type == 3;
2951
2952                let (pvx, pvy) = self.mv_pred.predict(mb_row as usize, mb_col as usize);
2953
2954                let (fvx, fvy) = if use_fwd {
2955                    let dx = Self::read_mv_diff(&mut br, mv_vlc, mv_scale);
2956                    let dy = Self::read_mv_diff(&mut br, mv_vlc, mv_scale);
2957                    (pvx + dx, pvy + dy)
2958                } else {
2959                    (0, 0)
2960                };
2961
2962                let (bvx, bvy) = if use_bwd {
2963                    let dx = Self::read_mv_diff(&mut br, mv_vlc, mv_scale);
2964                    let dy = Self::read_mv_diff(&mut br, mv_vlc, mv_scale);
2965                    (pvx + dx, pvy + dy)
2966                } else {
2967                    (0, 0)
2968                };
2969
2970                self.mv_pred.store(
2971                    mb_row as usize,
2972                    mb_col as usize,
2973                    if use_fwd { (fvx, fvy) } else { (bvx, bvy) },
2974                    false,
2975                );
2976
2977                if use_fwd && use_bwd {
2978                    self.mc_blend_mb(
2979                        frame, mb_row, mb_col, &fwd, &bwd, fvx, fvy, bvx, bvy, fw, fh,
2980                    );
2981                } else if use_fwd {
2982                    self.mc_single_mb(frame, mb_row, mb_col, &fwd, fvx, fvy, fw, fh);
2983                } else {
2984                    self.mc_single_mb(frame, mb_row, mb_col, &bwd, bvx, bvy, fw, fh);
2985                }
2986
2987                // Residual
2988                let cbp = self.cbpcy_p[(seq.cbptab as usize).min(1)]
2989                    .decode(&mut br)
2990                    .unwrap_or(0) as u8;
2991                let mb_pquant = read_mquant(&mut br, seq.dquant, pquant);
2992                let mb_tt = if seq.vstransform {
2993                    self.ttmb.decode(&mut br).unwrap_or(0) as u8
2994                } else {
2995                    0
2996                };
2997                for blk in 0..6usize {
2998                    if (cbp >> (5 - blk)) & 1 == 0 {
2999                        continue;
3000                    }
3001                    let blk_tt = if mb_tt == 6 {
3002                        self.ttblk.decode(&mut br).unwrap_or(0) as u8
3003                    } else {
3004                        mb_tt
3005                    };
3006                    let mut coeff = decode_block(
3007                        &mut br,
3008                        false,
3009                        blk < 4,
3010                        mb_pquant,
3011                        halfqp,
3012                        uniform,
3013                        blk_tt,
3014                        &self.dc_luma,
3015                        &self.dc_chroma,
3016                        &self.ac_intra[seq.transacfrm2 as usize],
3017                        &self.ac_inter[seq.transacfrm as usize],
3018                    );
3019                    apply_idct(&mut coeff, blk_tt);
3020                    add_residual_block(frame, mb_row as u32, mb_col as u32, blk, &coeff);
3021                }
3022            }
3023        }
3024        Ok(())
3025    }
3026
3027    /// Copy one 16×16 luma + 8×8 chroma macroblock from ref with given MV.
3028    fn mc_single_mb(
3029        &self,
3030        frame: &mut YuvFrame,
3031        mb_row: u32,
3032        mb_col: u32,
3033        refp: &YuvFrame,
3034        mvx: i32,
3035        mvy: i32,
3036        fw: usize,
3037        fh: usize,
3038    ) {
3039        let dst_x = (mb_col * 16) as usize;
3040        let dst_y = (mb_row * 16) as usize;
3041        let src_x = dst_x as i32 * 2 + mvx;
3042        let src_y = dst_y as i32 * 2 + mvy;
3043        let mut tmp = [0u8; 256];
3044        mc_luma(&mut tmp, 16, &refp.y, fw, fw, fh, src_x, src_y, 16, 16);
3045        let dst = &mut frame.y[dst_y * fw + dst_x..];
3046        for r in 0..16 {
3047            dst[r * fw..r * fw + 16].copy_from_slice(&tmp[r * 16..r * 16 + 16]);
3048        }
3049
3050        let cw = fw / 2;
3051        let ch = fh / 2;
3052        let cdst_x = (mb_col * 8) as usize;
3053        let cdst_y = (mb_row * 8) as usize;
3054        let csrc_x = cdst_x as i32 * 2 + mvx / 2;
3055        let csrc_y = cdst_y as i32 * 2 + mvy / 2;
3056        let mut tmp_cb = [0u8; 64];
3057        let mut tmp_cr = [0u8; 64];
3058        mc_luma(&mut tmp_cb, 8, &refp.cb, cw, cw, ch, csrc_x, csrc_y, 8, 8);
3059        mc_luma(&mut tmp_cr, 8, &refp.cr, cw, cw, ch, csrc_x, csrc_y, 8, 8);
3060        let dcb = &mut frame.cb[cdst_y * cw + cdst_x..];
3061        let dcr = &mut frame.cr[cdst_y * cw + cdst_x..];
3062        for r in 0..8 {
3063            dcb[r * cw..r * cw + 8].copy_from_slice(&tmp_cb[r * 8..r * 8 + 8]);
3064            dcr[r * cw..r * cw + 8].copy_from_slice(&tmp_cr[r * 8..r * 8 + 8]);
3065        }
3066    }
3067
3068    /// Bidirectional blend: average of forward and backward MC.
3069    fn mc_blend_mb(
3070        &self,
3071        frame: &mut YuvFrame,
3072        mb_row: u32,
3073        mb_col: u32,
3074        fwd: &YuvFrame,
3075        bwd: &YuvFrame,
3076        fvx: i32,
3077        fvy: i32,
3078        bvx: i32,
3079        bvy: i32,
3080        fw: usize,
3081        fh: usize,
3082    ) {
3083        let dst_x = (mb_col * 16) as usize;
3084        let dst_y = (mb_row * 16) as usize;
3085        let fsrc_x = dst_x as i32 * 2 + fvx;
3086        let fsrc_y = dst_y as i32 * 2 + fvy;
3087        let bsrc_x = dst_x as i32 * 2 + bvx;
3088        let bsrc_y = dst_y as i32 * 2 + bvy;
3089
3090        let mut ftmp = [0u8; 256];
3091        let mut btmp = [0u8; 256];
3092        mc_luma(&mut ftmp, 16, &fwd.y, fw, fw, fh, fsrc_x, fsrc_y, 16, 16);
3093        mc_luma(&mut btmp, 16, &bwd.y, fw, fw, fh, bsrc_x, bsrc_y, 16, 16);
3094        let dst = &mut frame.y[dst_y * fw + dst_x..];
3095        for r in 0..16 {
3096            for c in 0..16 {
3097                dst[r * fw + c] =
3098                    ((ftmp[r * 16 + c] as u16 + btmp[r * 16 + c] as u16 + 1) >> 1) as u8;
3099            }
3100        }
3101
3102        let cw = fw / 2;
3103        let ch = fh / 2;
3104        let cdst_x = (mb_col * 8) as usize;
3105        let cdst_y = (mb_row * 8) as usize;
3106        let cfsrc_x = cdst_x as i32 * 2 + fvx / 2;
3107        let cfsrc_y = cdst_y as i32 * 2 + fvy / 2;
3108        let cbsrc_x = cdst_x as i32 * 2 + bvx / 2;
3109        let cbsrc_y = cdst_y as i32 * 2 + bvy / 2;
3110        let mut fcb = [0u8; 64];
3111        let mut fcrb = [0u8; 64];
3112        let mut bcb = [0u8; 64];
3113        let mut bcrb = [0u8; 64];
3114        mc_luma(&mut fcb, 8, &fwd.cb, cw, cw, ch, cfsrc_x, cfsrc_y, 8, 8);
3115        mc_luma(&mut fcrb, 8, &fwd.cr, cw, cw, ch, cfsrc_x, cfsrc_y, 8, 8);
3116        mc_luma(&mut bcb, 8, &bwd.cb, cw, cw, ch, cbsrc_x, cbsrc_y, 8, 8);
3117        mc_luma(&mut bcrb, 8, &bwd.cr, cw, cw, ch, cbsrc_x, cbsrc_y, 8, 8);
3118        let dcb = &mut frame.cb[cdst_y * cw + cdst_x..];
3119        let dcr = &mut frame.cr[cdst_y * cw + cdst_x..];
3120        for r in 0..8 {
3121            for c in 0..8 {
3122                dcb[r * cw + c] = ((fcb[r * 8 + c] as u16 + bcb[r * 8 + c] as u16 + 1) >> 1) as u8;
3123                dcr[r * cw + c] =
3124                    ((fcrb[r * 8 + c] as u16 + bcrb[r * 8 + c] as u16 + 1) >> 1) as u8;
3125            }
3126        }
3127    }
3128
3129    fn read_mv_diff(br: &mut BitReader<'_>, mv_vlc: &VlcTable, scale: i32) -> i32 {
3130        let sym = mv_vlc.decode(br).unwrap_or(0);
3131        if sym == i32::MIN {
3132            // Fixed-length escape
3133            br.read_bits_signed(17).unwrap_or(0)
3134        } else {
3135            sym * scale / 4 // convert to quarter-pel
3136        }
3137    }
3138}
3139
3140// ═══════════════════════════════════════════════════════════════════════════════
3141// WMV2 (MS-MPEG4 V8) Decode Entry Points
3142// ═══════════════════════════════════════════════════════════════════════════════
3143//
3144// Public interface: MacroblockDecoder::decode_wmv2_frame()
3145//
3146// WMV2 simplifications vs VC-1:
3147//   • No B-frames, no BFRACTION, no overlap filter, no loop filter flag
3148//   • No TRANSACFRM/CBPTAB/MVTAB in seqhdr; ttcoef from frame header
3149//   • DC: 8-bit absolute (no VLC), sign separate
3150//   • AC escape: Mode-3 only (1-bit last, 6-bit run, 8-bit level, 1-bit sign)
3151//   • IDCT: same VC-1 integer transform reused
3152//   • Motion: half-pel bilinear (same MC as VC-1)
3153
3154// ─── WMV2 DC scale tables ───────────────────────────────────────────────────
3155// WMV2 uses MPEG-4 style DC scaling tables (much smaller than VC-1's ×128 domain
3156// tables). Using VC-1 DC step tables here will massively over-scale DC and
3157// saturate the reconstructed picture.
3158//
3159// These tables match the conventional MPEG-4 Part 2 DC scale tables.
3160// (They are also used by MSMPEG4/WMV1-family decoders.)
3161#[inline(always)]
3162fn wmv2_dc_scale(pquant: i32, is_luma: bool) -> i32 {
3163    // upstream: ff_wmv1_y_dc_scale_table / ff_wmv1_c_dc_scale_table (used for WMV1/WMV2).
3164    const Y: [i32; 32] = [
3165        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,
3166        18, 19, 19, 20, 20, 21, 21,
3167    ];
3168    const C: [i32; 32] = [
3169        0, 8, 8, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18,
3170        18, 19, 19, 20, 20, 21, 21, 22,
3171    ];
3172    let idx = pquant.clamp(1, 31) as usize;
3173    if is_luma {
3174        Y[idx]
3175    } else {
3176        C[idx]
3177    }
3178}
3179
3180#[inline(always)]
3181fn decode012(br: &mut BitReader<'_>) -> u8 {
3182    // upstream get_bits.h: n=get_bits1(); if n==0 return 0; else return get_bits1()+1;
3183    match br.read_bit() {
3184        Some(false) => 0,
3185        Some(true) => br.read_bit().map(|b| if b { 2 } else { 1 }).unwrap_or(0),
3186        None => 0,
3187    }
3188}
3189
3190#[inline(always)]
3191fn wmv2_get_cbp_table_index(qscale: i32, cbp_index: u8) -> usize {
3192    // upstream wmv2.h wmv2_get_cbp_table_index
3193    const MAP: [[u8; 3]; 3] = [[0, 2, 1], [1, 0, 2], [2, 1, 0]];
3194    let a = if qscale > 10 { 1 } else { 0 };
3195    let b = if qscale > 20 { 1 } else { 0 };
3196    let row = (a + b) as usize;
3197    MAP[row][(cbp_index as usize).min(2)] as usize
3198}
3199
3200impl MacroblockDecoder {
3201    /// Decode one WMV2 frame. `hdr` is the already-parsed per-frame header.
3202    /// This is the public entry point called from main.rs.
3203    /// Parse WMV2 ext-header from ASF extradata (upstream decode_ext_header).
3204    ///
3205    /// If extradata is missing/short, we keep all flags at default false.
3206    pub fn wmv2_set_extradata(&mut self, extradata: &[u8]) {
3207        if extradata.len() < 4 {
3208            return;
3209        }
3210        let mut br = BitReader::new(&extradata[..4]);
3211        let _fps = br.read_bits(5).unwrap_or(0);
3212        let _bit_rate = br.read_bits(11).unwrap_or(0) * 1024;
3213        self.wmv2_mspel_bit = br.read_bit().unwrap_or(false);
3214        let _loop_filter = br.read_bit().unwrap_or(false);
3215        self.wmv2_abt_flag = br.read_bit().unwrap_or(false);
3216        self.wmv2_j_type_bit = br.read_bit().unwrap_or(false);
3217        self.wmv2_top_left_mv_flag = br.read_bit().unwrap_or(false);
3218        self.wmv2_per_mb_rl_bit = br.read_bit().unwrap_or(false);
3219        let code = br.read_bits(3).unwrap_or(0) as usize;
3220        if code == 0 {
3221            return;
3222        }
3223        let mb_h = self.height_mb as usize;
3224        self.wmv2_slice_height = mb_h / code;
3225    }
3226
3227    pub fn wmv2_copy_ref(&self, out: &mut YuvFrame) -> bool {
3228        let Some(r) = self.wmv2_ref.as_ref() else {
3229            return false;
3230        };
3231        if out.width != r.width || out.height != r.height {
3232            *out = r.clone();
3233            return true;
3234        }
3235        if out.y.len() == r.y.len() {
3236            out.y.copy_from_slice(&r.y);
3237        } else {
3238            out.y = r.y.clone();
3239        }
3240        if out.cb.len() == r.cb.len() {
3241            out.cb.copy_from_slice(&r.cb);
3242        } else {
3243            out.cb = r.cb.clone();
3244        }
3245        if out.cr.len() == r.cr.len() {
3246            out.cr.copy_from_slice(&r.cr);
3247        } else {
3248            out.cr = r.cr.clone();
3249        }
3250        true
3251    }
3252
3253    pub fn decode_wmv2_frame(
3254        &mut self,
3255        payload: &[u8],
3256        hdr: &Wmv2FrameHeader,
3257        params: &Wmv2Params,
3258        frame: &mut YuvFrame,
3259    ) -> Result<()> {
3260        // resize if needed
3261        if self.width != params.width || self.height != params.height {
3262            *self = MacroblockDecoder::new(params.width, params.height);
3263        }
3264
3265        if hdr.frame_skipped {
3266            let _ = self.wmv2_copy_ref(frame);
3267            return Ok(());
3268        }
3269        match hdr.frame_type {
3270            Wmv2FrameType::I => self.wmv2_decode_intra(payload, hdr, frame),
3271            Wmv2FrameType::P => self.wmv2_decode_p(payload, hdr, frame),
3272        }
3273    }
3274
3275    /// Heuristic probe: try to parse a few macroblock headers after `hdr.header_bits`.
3276    /// Used to disambiguate ASF framing-byte offsets when the picture header can be
3277    /// (mis-)parsed at multiple byte offsets.
3278    ///
3279    /// Returns a "score" = number of MB headers successfully parsed (higher is better).
3280    pub fn probe_wmv2_payload(&self, payload: &[u8], hdr: &Wmv2FrameHeader) -> usize {
3281        let mut br = BitReader::new_at(payload, hdr.header_bits);
3282
3283        // upstream-aligned quick probe for I-frames: only consume secondary header + MB header + 6×DC.
3284        if hdr.frame_type == Wmv2FrameType::I {
3285            let mut br = BitReader::new_at(payload, hdr.header_bits);
3286            // secondary picture header (I branch)
3287            let j_type = if self.wmv2_j_type_bit {
3288                br.read_bit().unwrap_or(false)
3289            } else {
3290                false
3291            };
3292            if j_type {
3293                return 1;
3294            }
3295            let per_mb_rl_table = if self.wmv2_per_mb_rl_bit {
3296                br.read_bit().unwrap_or(false)
3297            } else {
3298                false
3299            };
3300            if !per_mb_rl_table {
3301                let _ = decode012(&mut br);
3302                let _ = decode012(&mut br);
3303            }
3304            let dc_table_index = br.read_bit().unwrap_or(false) as usize;
3305            let code = match self.wmv2_mb_i_vlc.decode(&mut br) {
3306                Some(v) => v as u32,
3307                None => return 0,
3308            };
3309            let _ = code;
3310            let _ac_pred = br.read_bit().unwrap_or(false);
3311            let _ = _ac_pred;
3312            if per_mb_rl_table && code != 0 {
3313                let _ = decode012(&mut br);
3314            }
3315            // DCs
3316            const DC_MAX: i32 = 119;
3317            for blk in 0..6usize {
3318                let is_chroma = blk >= 4;
3319                let tbl = &self.wmv2_dc_vlc[dc_table_index][if is_chroma { 1 } else { 0 }];
3320                let mut level = match tbl.decode(&mut br) {
3321                    Some(v) => v,
3322                    None => return 0,
3323                };
3324                if level == DC_MAX {
3325                    let _ = br.read_bits(8);
3326                    let _ = br.read_bit();
3327                } else if level != 0 {
3328                    let _ = br.read_bit();
3329                }
3330            }
3331            return 1;
3332        }
3333
3334        // upstream-aligned quick probe for P-frames: consume secondary header + first MB header.
3335        if hdr.frame_type == Wmv2FrameType::P {
3336            let mut br = BitReader::new_at(payload, hdr.header_bits);
3337            let mb_w = self.width_mb as usize;
3338            let mb_h = self.height_mb as usize;
3339            let qscale = hdr.pquant as i32;
3340
3341            // skip map (only check first MB skip flag)
3342            let skip_type = br.read_bits(2).unwrap_or(0) as u8;
3343            let first_skip = match skip_type {
3344                0 => false,
3345                1 => br.read_bit().unwrap_or(false),
3346                2 => {
3347                    let all = br.read_bit().unwrap_or(false);
3348                    if all {
3349                        true
3350                    } else {
3351                        br.read_bit().unwrap_or(false)
3352                    }
3353                }
3354                3 => {
3355                    let all = br.read_bit().unwrap_or(false);
3356                    if all {
3357                        true
3358                    } else {
3359                        br.read_bit().unwrap_or(false)
3360                    }
3361                }
3362                _ => false,
3363            };
3364
3365            // Drain remaining skip bits quickly (best-effort) to reach cbp_index.
3366            // We only do a lightweight skip consumption to keep probe cheap.
3367            if skip_type == 1 {
3368                let _ = mb_w * mb_h;
3369            }
3370
3371            let cbp_index = decode012(&mut br);
3372            let cbp_table_index = wmv2_get_cbp_table_index(qscale, cbp_index);
3373
3374            let _mspel = if self.wmv2_mspel_bit {
3375                br.read_bit().unwrap_or(false)
3376            } else {
3377                false
3378            };
3379            if self.wmv2_abt_flag {
3380                let per_mb_abt = br.read_bit().unwrap_or(false) ^ true;
3381                if !per_mb_abt {
3382                    let _ = decode012(&mut br);
3383                }
3384            }
3385            let per_mb_rl_table = if self.wmv2_per_mb_rl_bit {
3386                br.read_bit().unwrap_or(false)
3387            } else {
3388                false
3389            };
3390            if !per_mb_rl_table {
3391                let _ = decode012(&mut br);
3392            }
3393            let dc_table_index = br.read_bit().unwrap_or(false) as usize;
3394            let mv_table_index = br.read_bit().unwrap_or(false) as usize;
3395
3396            if first_skip {
3397                return 1;
3398            }
3399
3400            let code = match self.wmv2_mb_non_intra_vlc[cbp_table_index.min(3)].decode(&mut br) {
3401                Some(v) => v as i32,
3402                None => return 0,
3403            };
3404            let mb_intra = (code & 0x40) == 0;
3405            let cbp = (code & 0x3f) as u8;
3406
3407            if mb_intra {
3408                let _ac_pred = br.read_bit().unwrap_or(false);
3409                if per_mb_rl_table && cbp != 0 {
3410                    let _ = decode012(&mut br);
3411                }
3412                // Decode one DC to validate DC VLC table.
3413                const DC_MAX: i32 = 119;
3414                let tbl = &self.wmv2_dc_vlc[dc_table_index][0];
3415                let mut level = match tbl.decode(&mut br) {
3416                    Some(v) => v,
3417                    None => return 0,
3418                };
3419                if level == DC_MAX {
3420                    let _ = br.read_bits(8);
3421                    let _ = br.read_bit();
3422                } else if level != 0 {
3423                    let _ = br.read_bit();
3424                }
3425            } else {
3426                // Decode one MV symbol.
3427                let tbl = &self.wmv2_mv_vlc[mv_table_index.min(1)];
3428                let sym = match tbl.decode(&mut br) {
3429                    Some(v) => v as u16,
3430                    None => return 0,
3431                };
3432                if sym == 0 {
3433                    let _ = br.read_bits(12);
3434                }
3435            }
3436            return 1;
3437        }
3438
3439        let max_mb = (self.width_mb as usize * self.height_mb as usize).min(64);
3440        let mut score: usize = 0;
3441
3442        // Use ttcoef=0 tables for probing; this is only a syntactic plausibility check.
3443        let ac_intra = &self.wmv2_intra[0];
3444        let ac_inter = &self.wmv2_inter[0];
3445
3446        for _ in 0..max_mb {
3447            if br.is_empty() {
3448                break;
3449            }
3450
3451            let cbpc_sym = match self.wmv2_cbpc.decode(&mut br) {
3452                Some(v) => v,
3453                None => break,
3454            };
3455            if cbpc_sym == -1 {
3456                score += 1;
3457                continue;
3458            }
3459            if cbpc_sym < 0 || cbpc_sym > 3 {
3460                break;
3461            }
3462
3463            let is_intra = match br.read_bit() {
3464                Some(b) => b,
3465                None => break,
3466            };
3467
3468            let cbpy_raw = match self.wmv2_cbpy.decode(&mut br) {
3469                Some(v) if v >= 0 && v <= 15 => v as u8,
3470                _ => break,
3471            };
3472
3473            let cbpy = if is_intra { cbpy_raw } else { cbpy_raw ^ 0x0F };
3474            let cbp: u8 = (cbpy << 2) | (cbpc_sym as u8 & 0x03);
3475
3476            if cbp != 0 {
3477                let vlc = if is_intra { ac_intra } else { ac_inter };
3478                let sym = match vlc.decode(&mut br) {
3479                    Some(s) => s,
3480                    None => break,
3481                };
3482                if sym == VLC_ESCAPE {
3483                    // Consume escape payload (mode 1/2/3) so probing stays in sync.
3484                    let _ = decode_escape_coeff(&mut br, vlc);
3485                } else {
3486                    // Normal coefficient: single sign bit follows.
3487                    let _ = br.read_bit();
3488                }
3489            }
3490
3491            score += 1;
3492        }
3493
3494        score
3495    }
3496
3497    // ── WMV2/MSMPEG4 helpers (upstream-aligned) ─────────────────────────────
3498
3499    #[inline(always)]
3500    fn wmv2_coded_block_pred(&self, mb_row: usize, mb_col: usize, blk: usize) -> u8 {
3501        // Equivalent to upstream ff_msmpeg4_coded_block_pred(), but on a compact grid.
3502        let bw = (self.width_mb as usize) * 2;
3503        let bx = mb_col * 2 + (blk & 1);
3504        let by = mb_row * 2 + (blk >> 1);
3505        let idx = by * bw + bx;
3506        let a = if bx > 0 {
3507            self.wmv2_coded_block[idx - 1]
3508        } else {
3509            0
3510        };
3511        let b = if bx > 0 && by > 0 {
3512            self.wmv2_coded_block[idx - 1 - bw]
3513        } else {
3514            0
3515        };
3516        let c = if by > 0 {
3517            self.wmv2_coded_block[idx - bw]
3518        } else {
3519            0
3520        };
3521        if b == c {
3522            a
3523        } else {
3524            c
3525        }
3526    }
3527
3528    #[inline(always)]
3529    fn wmv2_coded_block_store(&mut self, mb_row: usize, mb_col: usize, blk: usize, v: u8) {
3530        let bw = (self.width_mb as usize) * 2;
3531        let bx = mb_col * 2 + (blk & 1);
3532        let by = mb_row * 2 + (blk >> 1);
3533        let idx = by * bw + bx;
3534        if idx < self.wmv2_coded_block.len() {
3535            self.wmv2_coded_block[idx] = v;
3536        }
3537    }
3538
3539    #[inline(always)]
3540    fn wmv2_decode_dc_diff(&self, br: &mut BitReader<'_>, is_chroma: bool) -> i32 {
3541        // upstream msmpeg4_decode_dc() for v3+/WMV2: VLC magnitude + optional sign; DC_MAX escape.
3542        const DC_MAX: i32 = 119;
3543        let tbl = &self.wmv2_dc_vlc[self.wmv2_dc_table_index][if is_chroma { 1 } else { 0 }];
3544        let mut level = tbl.decode(br).unwrap_or(0);
3545        if level == DC_MAX {
3546            let v = br.read_bits(8).unwrap_or(0) as i32;
3547            let sign = br.read_bit().unwrap_or(false);
3548            return if sign { -v } else { v };
3549        }
3550        if level != 0 {
3551            let sign = br.read_bit().unwrap_or(false);
3552            if sign {
3553                level = -level;
3554            }
3555        }
3556        level
3557    }
3558
3559    #[inline(always)]
3560    fn wmv2_reset_picture_state(&mut self) {
3561        self.wmv2_esc3_level_length = 0;
3562        self.wmv2_esc3_run_length = 0;
3563        for v in self.wmv2_ac_val.iter_mut() {
3564            *v = [0i16; 16];
3565        }
3566    }
3567
3568    #[inline(always)]
3569    fn wmv2_ac_val_idx(&self, mb_row: usize, mb_col: usize, blk: usize) -> usize {
3570        let mb_w = self.width_mb as usize;
3571        (mb_row * mb_w + mb_col) * 6 + blk
3572    }
3573
3574    #[inline(always)]
3575    fn wmv2_get_ac_val(&self, mb_row: usize, mb_col: usize, blk: usize) -> [i16; 16] {
3576        let idx = self.wmv2_ac_val_idx(mb_row, mb_col, blk);
3577        if idx < self.wmv2_ac_val.len() {
3578            self.wmv2_ac_val[idx]
3579        } else {
3580            [0i16; 16]
3581        }
3582    }
3583
3584    #[inline(always)]
3585    fn wmv2_set_ac_val(&mut self, mb_row: usize, mb_col: usize, blk: usize, v: [i16; 16]) {
3586        let idx = self.wmv2_ac_val_idx(mb_row, mb_col, blk);
3587        if idx < self.wmv2_ac_val.len() {
3588            self.wmv2_ac_val[idx] = v;
3589        }
3590    }
3591
3592    #[inline(always)]
3593    fn wmv2_pred_ac(
3594        &mut self,
3595        mb_row: usize,
3596        mb_col: usize,
3597        blk: usize,
3598        dc_pred_dir: i32,
3599        ac_pred: bool,
3600        block: &mut [i16; 64],
3601    ) {
3602        // Direct port of upstream ff_mpeg4_pred_ac() behavior for MSMPEG4/WMV2.
3603        // We keep identity idct_permutation (our scan tables are already permutated).
3604        // ac_val stores 16 values per block: [1..7] left column, [9..15] top row.
3605
3606        let mut cur = self.wmv2_get_ac_val(mb_row, mb_col, blk);
3607
3608        if ac_pred {
3609            if dc_pred_dir == 0 {
3610                // Left prediction: add first column from left neighbor.
3611                let (src_r, src_c, src_b) = match blk {
3612                    1 => (mb_row, mb_col, 0),
3613                    3 => (mb_row, mb_col, 2),
3614                    0 => (mb_row, mb_col.saturating_sub(1), 1),
3615                    2 => (mb_row, mb_col.saturating_sub(1), 3),
3616                    4 | 5 => (mb_row, mb_col.saturating_sub(1), blk),
3617                    _ => (mb_row, mb_col.saturating_sub(1), blk),
3618                };
3619                if (blk == 1 || blk == 3) || mb_col > 0 {
3620                    let src = self.wmv2_get_ac_val(src_r, src_c, src_b);
3621                    for i in 1..8usize {
3622                        let idx = i << 3;
3623                        block[idx] = block[idx].wrapping_add(src[i]);
3624                    }
3625                }
3626            } else {
3627                // Top prediction: add first row from top neighbor.
3628                let (src_r, src_c, src_b) = match blk {
3629                    2 => (mb_row, mb_col, 0),
3630                    3 => (mb_row, mb_col, 1),
3631                    0 => (mb_row.saturating_sub(1), mb_col, 2),
3632                    1 => (mb_row.saturating_sub(1), mb_col, 3),
3633                    4 | 5 => (mb_row.saturating_sub(1), mb_col, blk),
3634                    _ => (mb_row.saturating_sub(1), mb_col, blk),
3635                };
3636                if (blk == 2 || blk == 3) || mb_row > 0 {
3637                    let src = self.wmv2_get_ac_val(src_r, src_c, src_b);
3638                    for i in 1..8usize {
3639                        block[i] = block[i].wrapping_add(src[8 + i]);
3640                    }
3641                }
3642            }
3643        }
3644
3645        // Store our AC predictors for future blocks.
3646        for i in 1..8usize {
3647            cur[i] = block[i << 3];
3648        }
3649        for i in 1..8usize {
3650            cur[8 + i] = block[i];
3651        }
3652        self.wmv2_set_ac_val(mb_row, mb_col, blk, cur);
3653    }
3654
3655    #[inline(always)]
3656    fn wmv2_unquantize_h263_intra(&self, block: &mut [i16; 64], qscale: i32, dc_scale: i32) {
3657        // Direct port of upstream dct_unquantize_h263_intra_c().
3658        let qmul = qscale << 1;
3659        let qadd = (qscale - 1) | 1;
3660
3661        block[0] = ((block[0] as i32) * dc_scale) as i16;
3662        for i in 1..64usize {
3663            let mut level = block[i] as i32;
3664            if level != 0 {
3665                if level < 0 {
3666                    level = level * qmul - qadd;
3667                } else {
3668                    level = level * qmul + qadd;
3669                }
3670                block[i] = level as i16;
3671            }
3672        }
3673    }
3674
3675    fn wmv2_decode_block_intra_ref(
3676        &mut self,
3677        br: &mut BitReader<'_>,
3678        mb_row: usize,
3679        mb_col: usize,
3680        blk: usize,
3681        coded: bool,
3682        qscale: i32,
3683        ac_pred: bool,
3684    ) -> Result<[i16; 64]> {
3685        let is_luma = blk < 4;
3686        let dc_scale = wmv2_dc_scale(qscale, is_luma);
3687
3688        // DC diff VLC + sign, predictor in DC level domain.
3689        let diff = self.wmv2_decode_dc_diff(br, !is_luma);
3690        let (pred_level, dir) = self.wmv2_dc_pred.predict(mb_row, mb_col, blk, dc_scale);
3691        let level = pred_level + diff;
3692        self.wmv2_dc_pred
3693            .store(mb_row, mb_col, blk, level * dc_scale);
3694
3695        let mut block = [0i16; 64];
3696        block[0] = level as i16;
3697
3698        // Choose RL table.
3699        let rl = if is_luma {
3700            &self.wmv2_rl[(self.wmv2_rl_table_index as usize).min(2)]
3701        } else {
3702            &self.wmv2_rl[3 + (self.wmv2_rl_chroma_table_index as usize).min(2)]
3703        };
3704
3705        // Scan table selection.
3706        let scan = if ac_pred {
3707            if dir == 0 {
3708                &FF_WMV1_SCANTABLE[3] // intra_v
3709            } else {
3710                &FF_WMV1_SCANTABLE[2] // intra_h
3711            }
3712        } else {
3713            &FF_WMV1_SCANTABLE[1] // intra default
3714        };
3715
3716        let mut i: i32 = 0;
3717        let qmul: i32 = 1;
3718        let run_diff: i32 = 1; // msmpeg4_version >= WMV1
3719
3720        if coded {
3721            loop {
3722                let (mut level_uq, mut run) = rl
3723                    .decode_sym(br, 0)
3724                    .ok_or_else(|| DecoderError::InvalidData("WMV2: tcoeff VLC underrun".into()))?;
3725
3726                if level_uq == 0 {
3727                    // escape: prefix bits decide which escape.
3728                    let b0 = br.peek_bits(1).unwrap_or(0);
3729                    if b0 == 1 {
3730                        // escape1: prefix '1'
3731                        br.skip_bits(1);
3732                        let (lvl2, run2) = rl.decode_sym(br, 0).ok_or_else(|| {
3733                            DecoderError::InvalidData("WMV2: escape1 VLC underrun".into())
3734                        })?;
3735                        level_uq = lvl2;
3736                        run = run2;
3737                        i += run;
3738                        let last = ((run >> 7) & 1) as usize;
3739                        let base_run = ((run - 1) & 63) as usize;
3740                        level_uq += rl.max_level_for(last, base_run) * qmul;
3741                        let sign = br.read_bit().unwrap_or(false);
3742                        if sign {
3743                            level_uq = -level_uq;
3744                        }
3745                    } else {
3746                        let b1 = br.peek_bits(2).unwrap_or(0) & 1;
3747                        if b1 == 1 {
3748                            // escape2: prefix '01'
3749                            br.skip_bits(2);
3750                            let (lvl2, run2) = rl.decode_sym(br, 0).ok_or_else(|| {
3751                                DecoderError::InvalidData("WMV2: escape2 VLC underrun".into())
3752                            })?;
3753                            level_uq = lvl2;
3754                            run = run2;
3755                            let last = ((run >> 7) & 1) as usize;
3756                            let base_level = (level_uq / qmul).abs() as usize;
3757                            i += run + rl.max_run_for(last, base_level) + run_diff;
3758                            let sign = br.read_bit().unwrap_or(false);
3759                            if sign {
3760                                level_uq = -level_uq;
3761                            }
3762                        } else {
3763                            // escape3: prefix '00'
3764                            br.skip_bits(2);
3765                            let last = br.read_bit().unwrap_or(false);
3766                            if self.wmv2_esc3_level_length == 0 {
3767                                // derive esc3 lengths (WMV2: msmpeg4_version > V3)
3768                                let ll: u8 = if qscale < 8 {
3769                                    let mut x = br.read_bits(3).unwrap_or(0) as u8;
3770                                    if x == 0 {
3771                                        x = 8 + br.read_bits(1).unwrap_or(0) as u8;
3772                                    }
3773                                    x
3774                                } else {
3775                                    let mut x: u8 = 2;
3776                                    while x < 8 && br.peek_bits(1).unwrap_or(1) == 0 {
3777                                        br.skip_bits(1);
3778                                        x += 1;
3779                                    }
3780                                    if x < 8 {
3781                                        br.skip_bits(1);
3782                                    }
3783                                    x
3784                                };
3785                                self.wmv2_esc3_level_length = ll;
3786                                self.wmv2_esc3_run_length =
3787                                    (br.read_bits(2).unwrap_or(0) as u8) + 3;
3788                            }
3789                            let run_abs =
3790                                br.read_bits(self.wmv2_esc3_run_length).unwrap_or(0) as i32;
3791                            let sign = br.read_bit().unwrap_or(false);
3792                            let mut lvl_abs =
3793                                br.read_bits(self.wmv2_esc3_level_length).unwrap_or(0) as i32;
3794                            if sign {
3795                                lvl_abs = -lvl_abs;
3796                            }
3797                            level_uq = lvl_abs;
3798                            i += run_abs + 1;
3799                            if last {
3800                                i += 192;
3801                            }
3802                        }
3803                    }
3804                } else {
3805                    i += run;
3806                    let sign = br.read_bit().unwrap_or(false);
3807                    if sign {
3808                        level_uq = -level_uq;
3809                    }
3810                }
3811
3812                if i > 62 {
3813                    i -= 192;
3814                    if (i & !63) != 0 {
3815                        i = 63;
3816                    }
3817                    if i < 0 {
3818                        return Err(DecoderError::InvalidData(
3819                            "WMV2: negative coeff index (bitstream damaged)".into(),
3820                        ));
3821                    }
3822                    let pos = scan[i as usize] as usize;
3823                    if pos < 64 {
3824                        block[pos] = level_uq as i16;
3825                    }
3826                    break;
3827                }
3828
3829                if i < 0 {
3830                    return Err(DecoderError::InvalidData(
3831                        "WMV2: negative coeff index (bitstream damaged)".into(),
3832                    ));
3833                }
3834                let pos = scan[i as usize] as usize;
3835                if pos < 64 {
3836                    block[pos] = level_uq as i16;
3837                }
3838            }
3839        }
3840
3841        // AC prediction always runs (even if not coded).
3842        self.wmv2_pred_ac(mb_row, mb_col, blk, dir, ac_pred, &mut block);
3843
3844        // H.263 intra unquantization to match upstream pipeline.
3845        self.wmv2_unquantize_h263_intra(&mut block, qscale, dc_scale);
3846
3847        Ok(block)
3848    }
3849    fn wmv2_decode_block_inter_ref(
3850        &mut self,
3851        br: &mut BitReader<'_>,
3852        blk: usize,
3853        coded: bool,
3854        qscale: i32,
3855        scan: &[usize; 64],
3856    ) -> Result<[i16; 64]> {
3857        let mut block = [0i16; 64];
3858        if !coded {
3859            return Ok(block);
3860        }
3861
3862        let rl = &self.wmv2_rl[3 + (self.wmv2_rl_table_index as usize).min(2)];
3863
3864        let qmul = qscale << 1;
3865        let qadd = (qscale - 1) | 1;
3866        let run_diff: i32 = 1; // wmv2 != v2
3867
3868        let mut i: i32 = -1;
3869
3870        loop {
3871            let (mut level_uq, mut run) = rl.decode_sym(br, qscale).ok_or_else(|| {
3872                DecoderError::InvalidData("WMV2: inter tcoeff VLC underrun".into())
3873            })?;
3874
3875            if level_uq == 0 {
3876                // escape
3877                let b0 = br.peek_bits(1).unwrap_or(0);
3878                if b0 == 1 {
3879                    // escape1
3880                    br.skip_bits(1);
3881                    let (lvl2, run2) = rl.decode_sym(br, qscale).ok_or_else(|| {
3882                        DecoderError::InvalidData("WMV2: inter escape1 VLC underrun".into())
3883                    })?;
3884                    level_uq = lvl2;
3885                    run = run2;
3886                    i += run;
3887                    let last = ((run >> 7) & 1) as usize;
3888                    let base_run = ((run - 1) & 63) as usize;
3889                    level_uq += rl.max_level_for(last, base_run) * qmul;
3890                    let sign = br.read_bit().unwrap_or(false);
3891                    if sign {
3892                        level_uq = -level_uq;
3893                    }
3894                } else {
3895                    let b1 = br.peek_bits(2).unwrap_or(0) & 1;
3896                    if b1 == 1 {
3897                        // escape2
3898                        br.skip_bits(2);
3899                        let (lvl2, run2) = rl.decode_sym(br, qscale).ok_or_else(|| {
3900                            DecoderError::InvalidData("WMV2: inter escape2 VLC underrun".into())
3901                        })?;
3902                        level_uq = lvl2;
3903                        run = run2;
3904                        let last = ((run >> 7) & 1) as usize;
3905                        let base_level = (level_uq / qmul).abs() as usize;
3906                        i += run + rl.max_run_for(last, base_level) + run_diff;
3907                        let sign = br.read_bit().unwrap_or(false);
3908                        if sign {
3909                            level_uq = -level_uq;
3910                        }
3911                    } else {
3912                        // escape3
3913                        br.skip_bits(2);
3914                        let last = br.read_bit().unwrap_or(false);
3915                        if self.wmv2_esc3_level_length == 0 {
3916                            let ll: u8 = if qscale < 8 {
3917                                let mut x = br.read_bits(3).unwrap_or(0) as u8;
3918                                if x == 0 {
3919                                    x = 8 + br.read_bits(1).unwrap_or(0) as u8;
3920                                }
3921                                x
3922                            } else {
3923                                let mut x: u8 = 2;
3924                                while x < 8 && br.peek_bits(1).unwrap_or(1) == 0 {
3925                                    br.skip_bits(1);
3926                                    x += 1;
3927                                }
3928                                if x < 8 {
3929                                    br.skip_bits(1);
3930                                }
3931                                x
3932                            };
3933                            self.wmv2_esc3_level_length = ll;
3934                            self.wmv2_esc3_run_length = (br.read_bits(2).unwrap_or(0) as u8) + 3;
3935                        }
3936                        let run_abs = br.read_bits(self.wmv2_esc3_run_length).unwrap_or(0) as i32;
3937                        let sign = br.read_bit().unwrap_or(false);
3938                        let mut lvl_abs =
3939                            br.read_bits(self.wmv2_esc3_level_length).unwrap_or(0) as i32;
3940                        if sign {
3941                            lvl_abs = -lvl_abs;
3942                        }
3943                        if lvl_abs > 0 {
3944                            level_uq = lvl_abs * qmul + qadd;
3945                        } else {
3946                            level_uq = lvl_abs * qmul - qadd;
3947                        }
3948                        i += run_abs + 1;
3949                        if last {
3950                            i += 192;
3951                        }
3952                    }
3953                }
3954            } else {
3955                i += run;
3956                let sign = br.read_bit().unwrap_or(false);
3957                if sign {
3958                    level_uq = -level_uq;
3959                }
3960            }
3961
3962            if i > 62 {
3963                i -= 192;
3964                if (i & !63) != 0 {
3965                    i = 63;
3966                }
3967                if i < 0 {
3968                    return Err(DecoderError::InvalidData(
3969                        "WMV2: negative coeff index (bitstream damaged)".into(),
3970                    ));
3971                }
3972                let pos = scan[i as usize] as usize;
3973                if pos < 64 {
3974                    block[pos] = level_uq as i16;
3975                }
3976                break;
3977            }
3978
3979            if i < 0 {
3980                return Err(DecoderError::InvalidData(
3981                    "WMV2: negative coeff index (bitstream damaged)".into(),
3982                ));
3983            }
3984            let pos = scan[i as usize] as usize;
3985            if pos < 64 {
3986                block[pos] = level_uq as i16;
3987            }
3988        }
3989
3990        let _ = blk;
3991        Ok(block)
3992    }
3993    fn wmv2_parse_mb_skip(
3994        &mut self,
3995        br: &mut BitReader<'_>,
3996        mb_w: usize,
3997        mb_h: usize,
3998    ) -> Result<()> {
3999        // upstream wmv2dec.c parse_mb_skip
4000        let skip_type = br
4001            .read_bits(2)
4002            .ok_or_else(|| DecoderError::InvalidData("WMV2: missing skip_type".into()))?
4003            as u8;
4004        self.wmv2_skip_type = skip_type;
4005        if self.wmv2_mb_skip.len() != mb_w * mb_h {
4006            self.wmv2_mb_skip.resize(mb_w * mb_h, false);
4007        }
4008        for v in self.wmv2_mb_skip.iter_mut() {
4009            *v = false;
4010        }
4011
4012        match skip_type {
4013            0 => {
4014                // SKIP_TYPE_NONE
4015            }
4016            1 => {
4017                // SKIP_TYPE_MPEG: 1 bit per MB
4018                if br.bits_left() < (mb_w * mb_h) as isize {
4019                    return Err(DecoderError::InvalidData("WMV2: skip map truncated".into()));
4020                }
4021                for y in 0..mb_h {
4022                    for x in 0..mb_w {
4023                        let b = br.read_bit().unwrap_or(false);
4024                        self.wmv2_mb_skip[y * mb_w + x] = b;
4025                    }
4026                }
4027            }
4028            2 => {
4029                // SKIP_TYPE_ROW
4030                for y in 0..mb_h {
4031                    let all = br.read_bit().ok_or_else(|| {
4032                        DecoderError::InvalidData("WMV2: skip row flag missing".into())
4033                    })?;
4034                    if all {
4035                        for x in 0..mb_w {
4036                            self.wmv2_mb_skip[y * mb_w + x] = true;
4037                        }
4038                    } else {
4039                        for x in 0..mb_w {
4040                            let b = br.read_bit().unwrap_or(false);
4041                            self.wmv2_mb_skip[y * mb_w + x] = b;
4042                        }
4043                    }
4044                }
4045            }
4046            3 => {
4047                // SKIP_TYPE_COL
4048                for x in 0..mb_w {
4049                    let all = br.read_bit().ok_or_else(|| {
4050                        DecoderError::InvalidData("WMV2: skip col flag missing".into())
4051                    })?;
4052                    if all {
4053                        for y in 0..mb_h {
4054                            self.wmv2_mb_skip[y * mb_w + x] = true;
4055                        }
4056                    } else {
4057                        for y in 0..mb_h {
4058                            let b = br.read_bit().unwrap_or(false);
4059                            self.wmv2_mb_skip[y * mb_w + x] = b;
4060                        }
4061                    }
4062                }
4063            }
4064            _ => {}
4065        }
4066
4067        // upstream also checks coded_mb_count against bits_left; keep a light version.
4068        let coded = self.wmv2_mb_skip.iter().filter(|s| !**s).count();
4069        if coded as isize > br.bits_left() {
4070            return Err(DecoderError::InvalidData(
4071                "WMV2: coded MB count exceeds remaining bits".into(),
4072            ));
4073        }
4074        Ok(())
4075    }
4076
4077    #[inline(always)]
4078    fn wmv2_motion_get(&self, mb_row: isize, mb_col: isize) -> (i32, i32) {
4079        if mb_row < 0 || mb_col < 0 {
4080            return (0, 0);
4081        }
4082        let mb_w = self.width_mb as isize;
4083        let mb_h = self.height_mb as isize;
4084        if mb_row >= mb_h || mb_col >= mb_w {
4085            return (0, 0);
4086        }
4087        let idx = (mb_row as usize) * (mb_w as usize) + (mb_col as usize);
4088        if idx < self.wmv2_motion.len() {
4089            self.wmv2_motion[idx]
4090        } else {
4091            (0, 0)
4092        }
4093    }
4094
4095    #[inline(always)]
4096    fn wmv2_motion_set(&mut self, mb_row: usize, mb_col: usize, mv: (i32, i32)) {
4097        let mb_w = self.width_mb as usize;
4098        let idx = mb_row * mb_w + mb_col;
4099        if self.wmv2_motion.len() != mb_w * (self.height_mb as usize) {
4100            self.wmv2_motion
4101                .resize(mb_w * (self.height_mb as usize), (0, 0));
4102        }
4103        if idx < self.wmv2_motion.len() {
4104            self.wmv2_motion[idx] = mv;
4105        }
4106    }
4107
4108    #[inline(always)]
4109    fn wmv2_pred_motion(
4110        &self,
4111        br: &mut BitReader<'_>,
4112        mb_row: usize,
4113        mb_col: usize,
4114        first_slice_line: bool,
4115    ) -> (i32, i32) {
4116        // upstream wmv2dec.c wmv2_pred_motion (MB-level approximation).
4117        let a = self.wmv2_motion_get(mb_row as isize, mb_col as isize - 1);
4118        let b = self.wmv2_motion_get(mb_row as isize - 1, mb_col as isize);
4119        let c = self.wmv2_motion_get(mb_row as isize - 1, mb_col as isize + 1);
4120
4121        let diff =
4122            if mb_col != 0 && !first_slice_line && !self.wmv2_mspel && self.wmv2_top_left_mv_flag {
4123                let dx = (a.0 - b.0).abs();
4124                let dy = (a.1 - b.1).abs();
4125                dx.max(dy)
4126            } else {
4127                0
4128            };
4129
4130        let t = if diff >= 8 {
4131            if br.read_bit().unwrap_or(false) {
4132                1
4133            } else {
4134                0
4135            }
4136        } else {
4137            2
4138        };
4139
4140        match t {
4141            0 => a,
4142            1 => b,
4143            _ => {
4144                if first_slice_line {
4145                    a
4146                } else {
4147                    (mid_pred(a.0, b.0, c.0), mid_pred(a.1, b.1, c.1))
4148                }
4149            }
4150        }
4151    }
4152
4153    #[inline(always)]
4154    fn wmv2_decode_motion_ref(&self, br: &mut BitReader<'_>, pred: (i32, i32)) -> (i32, i32) {
4155        // Direct port of upstream msmpeg4dec.c ff_msmpeg4_decode_motion.
4156        let tbl = &self.wmv2_mv_vlc[self.wmv2_mv_table_index.min(1)];
4157        let sym = tbl.decode(br).unwrap_or(0) as u16;
4158        let (mut mx, mut my) = if sym != 0 {
4159            ((sym >> 8) as i32, (sym & 0xff) as i32)
4160        } else {
4161            // Escape: 6-bit mx + 6-bit my.
4162            (
4163                br.read_bits(6).unwrap_or(0) as i32,
4164                br.read_bits(6).unwrap_or(0) as i32,
4165            )
4166        };
4167
4168        mx += pred.0 - 32;
4169        my += pred.1 - 32;
4170        // WARNING: they do not do exactly modulo encoding.
4171        if mx <= -64 {
4172            mx += 64;
4173        } else if mx >= 64 {
4174            mx -= 64;
4175        }
4176        if my <= -64 {
4177            my += 64;
4178        } else if my >= 64 {
4179            my -= 64;
4180        }
4181        (mx, my)
4182    }
4183
4184    // ── WMV2 I-frame ──────────────────────────────────────────────────────────
4185
4186    fn wmv2_decode_intra(
4187        &mut self,
4188        payload: &[u8],
4189        hdr: &Wmv2FrameHeader,
4190        frame: &mut YuvFrame,
4191    ) -> Result<()> {
4192        // Start at picture header end.
4193        let mut br = BitReader::new_at(payload, hdr.header_bits);
4194
4195        // upstream: ff_wmv2_decode_secondary_picture_header() (I-picture branch).
4196        // We parse/consume the fields that affect alignment and DC VLC selection.
4197        self.wmv2_j_type = if self.wmv2_j_type_bit {
4198            br.read_bit().unwrap_or(false)
4199        } else {
4200            false
4201        };
4202        if self.wmv2_j_type {
4203            // IntraX8 (j_type) is not handled in this A build.
4204            return Ok(());
4205        }
4206
4207        self.wmv2_per_mb_rl_table = if self.wmv2_per_mb_rl_bit {
4208            br.read_bit().unwrap_or(false)
4209        } else {
4210            false
4211        };
4212        if !self.wmv2_per_mb_rl_table {
4213            self.wmv2_rl_chroma_table_index = decode012(&mut br);
4214            self.wmv2_rl_table_index = decode012(&mut br);
4215        }
4216        self.wmv2_dc_table_index = br.read_bit().unwrap_or(false) as usize;
4217
4218        let mb_w = self.width_mb as usize;
4219        let mb_h = self.height_mb as usize;
4220
4221        // Reset predictors.
4222        self.wmv2_dc_pred = Wmv2DcPredBuffer::new(mb_w, mb_h);
4223        for v in self.wmv2_coded_block.iter_mut() {
4224            *v = 0;
4225        }
4226
4227        self.wmv2_reset_picture_state();
4228
4229        let qscale = hdr.pquant as i32;
4230        // WMV2 picture header variant used here (upstream-min) does not carry ttcoef;
4231        // keep using intra VLC set 0 to get the stream back in sync.
4232
4233        for mb_row in 0..mb_h {
4234            for mb_col in 0..mb_w {
4235                if br.is_empty() {
4236                    break;
4237                }
4238
4239                // upstream: code = get_vlc2(ff_msmp4_mb_i_vlc)
4240                let code = self.wmv2_mb_i_vlc.decode(&mut br).unwrap_or(0) as u32;
4241
4242                // Predict coded block pattern.
4243                let mut cbp: u8 = 0;
4244                for i in 0..6usize {
4245                    let mut val = ((code >> (5 - i)) & 1) as u8;
4246                    if i < 4 {
4247                        let pred = self.wmv2_coded_block_pred(mb_row, mb_col, i);
4248                        val ^= pred;
4249                        self.wmv2_coded_block_store(mb_row, mb_col, i, val);
4250                    }
4251                    cbp |= val << (5 - i);
4252                }
4253
4254                // upstream: h->c.ac_pred = get_bits1();
4255                let ac_pred = br.read_bit().unwrap_or(false);
4256
4257                // upstream: if (per_mb_rl_table && cbp) rl_table_index = decode012();
4258                if self.wmv2_per_mb_rl_table && cbp != 0 {
4259                    let rl_idx = decode012(&mut br);
4260                    self.wmv2_rl_table_index = rl_idx;
4261                    self.wmv2_rl_chroma_table_index = rl_idx;
4262                }
4263
4264                for blk in 0..6usize {
4265                    let coded = ((cbp >> (5 - blk)) & 1) != 0;
4266                    let mut block = self.wmv2_decode_block_intra_ref(
4267                        &mut br, mb_row, mb_col, blk, coded, qscale, ac_pred,
4268                    )?;
4269
4270                    let (is_luma, bx, by, stride, _ph) =
4271                        block_coords(mb_row as u32, mb_col as u32, blk, frame.width, frame.height);
4272                    let plane: &mut Vec<u8> = if is_luma {
4273                        &mut frame.y
4274                    } else if blk == 4 {
4275                        &mut frame.cb
4276                    } else {
4277                        &mut frame.cr
4278                    };
4279                    let dst_off = by * stride + bx;
4280                    wmv2dsp::wmv2_idct_put(plane, dst_off, stride, &mut block);
4281                }
4282            }
4283        }
4284
4285        self.wmv2_ref = Some(frame.clone());
4286        Ok(())
4287    }
4288    // ── WMV2 P-frame ──────────────────────────────────────────────────────────
4289
4290    fn wmv2_decode_p(
4291        &mut self,
4292        payload: &[u8],
4293        hdr: &Wmv2FrameHeader,
4294        frame: &mut YuvFrame,
4295    ) -> Result<()> {
4296        // Start at picture header end.
4297        let mut br = BitReader::new_at(payload, hdr.header_bits);
4298
4299        let mb_w = self.width_mb as usize;
4300        let mb_h = self.height_mb as usize;
4301        let qscale = hdr.pquant as i32;
4302
4303        // upstream: ff_wmv2_decode_secondary_picture_header() (P-picture branch).
4304        self.wmv2_j_type = false;
4305        self.wmv2_parse_mb_skip(&mut br, mb_w, mb_h)?;
4306        let cbp_index = decode012(&mut br);
4307        self.wmv2_cbp_table_index = wmv2_get_cbp_table_index(qscale, cbp_index);
4308
4309        self.wmv2_mspel = if self.wmv2_mspel_bit {
4310            br.read_bit().unwrap_or(false)
4311        } else {
4312            false
4313        };
4314
4315        if self.wmv2_abt_flag {
4316            self.wmv2_per_mb_abt = br.read_bit().unwrap_or(false) ^ true;
4317            if !self.wmv2_per_mb_abt {
4318                self.wmv2_abt_type = decode012(&mut br);
4319            }
4320        } else {
4321            self.wmv2_per_mb_abt = false;
4322            self.wmv2_abt_type = 0;
4323        }
4324
4325        self.wmv2_per_mb_rl_table = if self.wmv2_per_mb_rl_bit {
4326            br.read_bit().unwrap_or(false)
4327        } else {
4328            false
4329        };
4330        if !self.wmv2_per_mb_rl_table {
4331            self.wmv2_rl_table_index = decode012(&mut br);
4332            self.wmv2_rl_chroma_table_index = self.wmv2_rl_table_index;
4333        }
4334        if br.bits_left() < 2 {
4335            return Err(DecoderError::InvalidData(
4336                "WMV2: truncated secondary header".into(),
4337            ));
4338        }
4339        self.wmv2_dc_table_index = br.read_bit().unwrap_or(false) as usize;
4340        self.wmv2_mv_table_index = br.read_bit().unwrap_or(false) as usize;
4341
4342        // Reset predictors for this picture.
4343        self.wmv2_dc_pred = Wmv2DcPredBuffer::new(mb_w, mb_h);
4344        if self.wmv2_motion.len() != mb_w * mb_h {
4345            self.wmv2_motion.resize(mb_w * mb_h, (0, 0));
4346        }
4347        for v in self.wmv2_motion.iter_mut() {
4348            *v = (0, 0);
4349        }
4350
4351        self.wmv2_reset_picture_state();
4352
4353        let reference = match &self.wmv2_ref {
4354            Some(r) => r.clone(),
4355            None => YuvFrame::new(frame.width, frame.height),
4356        };
4357
4358        for mb_row in 0..mb_h {
4359            let first_slice_line =
4360                self.wmv2_slice_height != 0 && (mb_row % self.wmv2_slice_height == 0);
4361            for mb_col in 0..mb_w {
4362                if br.bits_left() <= 0 {
4363                    break;
4364                }
4365                let mi = mb_row * mb_w + mb_col;
4366                if mi < self.wmv2_mb_skip.len() && self.wmv2_mb_skip[mi] {
4367                    if self.wmv2_mspel {
4368                        wmv2_mspel_motion_mb(frame, &reference, mb_row, mb_col, 0, 0, 0);
4369                    } else {
4370                        motion_compensate_mb(frame, &reference, mb_row, mb_col, 0, 0);
4371                    }
4372                    self.wmv2_motion_set(mb_row, mb_col, (0, 0));
4373                    continue;
4374                }
4375
4376                let code = self.wmv2_mb_non_intra_vlc[self.wmv2_cbp_table_index.min(3)]
4377                    .decode(&mut br)
4378                    .ok_or_else(|| {
4379                        DecoderError::InvalidData("WMV2: MB header VLC underrun".into())
4380                    })? as i32;
4381
4382                let mb_intra = (code & 0x40) == 0;
4383                let cbp = (code & 0x3f) as u8;
4384
4385                if !mb_intra {
4386                    let pred = self.wmv2_pred_motion(&mut br, mb_row, mb_col, first_slice_line);
4387
4388                    if cbp != 0 {
4389                        if self.wmv2_per_mb_rl_table {
4390                            self.wmv2_rl_table_index = decode012(&mut br);
4391                            self.wmv2_rl_chroma_table_index = self.wmv2_rl_table_index;
4392                        }
4393                    }
4394
4395                    let mut per_block_abt = false;
4396                    let mut abt_type = self.wmv2_abt_type;
4397                    if cbp != 0 && self.wmv2_abt_flag && self.wmv2_per_mb_abt {
4398                        per_block_abt = br.read_bit().unwrap_or(false);
4399                        if !per_block_abt {
4400                            abt_type = decode012(&mut br);
4401                        }
4402                    }
4403
4404                    let (mx, my) = self.wmv2_decode_motion_ref(&mut br, pred);
4405                    self.wmv2_hshift = if (((mx | my) & 1) != 0) && self.wmv2_mspel {
4406                        br.read_bit().unwrap_or(false) as u8
4407                    } else {
4408                        0
4409                    };
4410                    self.wmv2_motion_set(mb_row, mb_col, (mx, my));
4411
4412                    if self.wmv2_mspel {
4413                        wmv2_mspel_motion_mb(
4414                            frame,
4415                            &reference,
4416                            mb_row,
4417                            mb_col,
4418                            mx,
4419                            my,
4420                            self.wmv2_hshift,
4421                        );
4422                    } else {
4423                        motion_compensate_mb(frame, &reference, mb_row, mb_col, mx, my);
4424                    }
4425
4426                    for blk in 0..6usize {
4427                        if (cbp >> (5 - blk)) & 1 == 0 {
4428                            continue;
4429                        }
4430
4431                        let mut cur_abt = abt_type;
4432                        if per_block_abt {
4433                            cur_abt = decode012(&mut br);
4434                        }
4435
4436                        // upstream: wmv2_decode_inter_block + wmv2_add_block
4437
4438                        if cur_abt == 0 {
4439                            let scan = &FF_WMV1_SCANTABLE[0];
4440
4441                            let mut block =
4442                                self.wmv2_decode_block_inter_ref(&mut br, blk, true, qscale, scan)?;
4443
4444                            let (is_luma, bx, by, stride, _ph) = block_coords(
4445                                mb_row as u32,
4446                                mb_col as u32,
4447                                blk,
4448                                frame.width,
4449                                frame.height,
4450                            );
4451
4452                            let plane: &mut Vec<u8> = if is_luma {
4453                                &mut frame.y
4454                            } else if blk == 4 {
4455                                &mut frame.cb
4456                            } else {
4457                                &mut frame.cr
4458                            };
4459
4460                            let dst_off = by * stride + bx;
4461
4462                            wmv2dsp::wmv2_idct_add(plane, dst_off, stride, &mut block);
4463                        } else {
4464                            const SUB_CBP_TABLE: [u8; 3] = [2, 3, 1];
4465
4466                            let scantable = if cur_abt == 1 {
4467                                &FF_WMV2_SCANTABLE_A
4468                            } else {
4469                                &FF_WMV2_SCANTABLE_B
4470                            };
4471
4472                            let sub_cbp = SUB_CBP_TABLE[decode012(&mut br) as usize];
4473
4474                            let mut block1 = [0i16; 64];
4475
4476                            let mut block2 = [0i16; 64];
4477
4478                            if (sub_cbp & 1) != 0 {
4479                                block1 = self.wmv2_decode_block_inter_ref(
4480                                    &mut br, blk, true, qscale, scantable,
4481                                )?;
4482                            }
4483
4484                            if (sub_cbp & 2) != 0 {
4485                                block2 = self.wmv2_decode_block_inter_ref(
4486                                    &mut br, blk, true, qscale, scantable,
4487                                )?;
4488                            }
4489
4490                            let (is_luma, bx, by, stride, _ph) = block_coords(
4491                                mb_row as u32,
4492                                mb_col as u32,
4493                                blk,
4494                                frame.width,
4495                                frame.height,
4496                            );
4497
4498                            let plane: &mut Vec<u8> = if is_luma {
4499                                &mut frame.y
4500                            } else if blk == 4 {
4501                                &mut frame.cb
4502                            } else {
4503                                &mut frame.cr
4504                            };
4505
4506                            let dst_off = by * stride + bx;
4507
4508                            match cur_abt {
4509                                1 => {
4510                                    // 8x4 + 8x4 (top/bottom)
4511
4512                                    ffidct::ff_simple_idct84_add(
4513                                        plane,
4514                                        dst_off,
4515                                        stride,
4516                                        &mut block1,
4517                                    );
4518
4519                                    ffidct::ff_simple_idct84_add(
4520                                        plane,
4521                                        dst_off + 4 * stride,
4522                                        stride,
4523                                        &mut block2,
4524                                    );
4525                                }
4526
4527                                2 => {
4528                                    // 4x8 + 4x8 (left/right)
4529
4530                                    ffidct::ff_simple_idct48_add(
4531                                        plane,
4532                                        dst_off,
4533                                        stride,
4534                                        &mut block1,
4535                                    );
4536
4537                                    ffidct::ff_simple_idct48_add(
4538                                        plane,
4539                                        dst_off + 4,
4540                                        stride,
4541                                        &mut block2,
4542                                    );
4543                                }
4544
4545                                _ => {}
4546                            }
4547                        }
4548                    }
4549                } else {
4550                    // Intra MB in P-picture.
4551                    let ac_pred = br.read_bit().unwrap_or(false);
4552                    if self.wmv2_per_mb_rl_table && cbp != 0 {
4553                        let rl_idx = decode012(&mut br);
4554                        self.wmv2_rl_table_index = rl_idx;
4555                        self.wmv2_rl_chroma_table_index = rl_idx;
4556                    }
4557
4558                    for blk in 0..6usize {
4559                        let coded = ((cbp >> (5 - blk)) & 1) != 0;
4560                        let mut block = self.wmv2_decode_block_intra_ref(
4561                            &mut br, mb_row, mb_col, blk, coded, qscale, ac_pred,
4562                        )?;
4563
4564                        let (is_luma, bx, by, stride, _ph) = block_coords(
4565                            mb_row as u32,
4566                            mb_col as u32,
4567                            blk,
4568                            frame.width,
4569                            frame.height,
4570                        );
4571                        let plane: &mut Vec<u8> = if is_luma {
4572                            &mut frame.y
4573                        } else if blk == 4 {
4574                            &mut frame.cb
4575                        } else {
4576                            &mut frame.cr
4577                        };
4578                        let dst_off = by * stride + bx;
4579                        wmv2dsp::wmv2_idct_put(plane, dst_off, stride, &mut block);
4580                    }
4581                    self.wmv2_motion_set(mb_row, mb_col, (0, 0));
4582                }
4583            }
4584        }
4585
4586        self.wmv2_ref = Some(frame.clone());
4587        Ok(())
4588    }
4589}
4590
4591// ─── WMV2 AC block decoder ────────────────────────────────────────────────────
4592// Decodes AC coefficients using WMV2 TCOEF VLC.
4593// For intra: fills coeff[1..63] (coeff[0] is DC, already set by caller).
4594// For inter: fills coeff[0..63] (all AC).
4595// Escape is Mode-3 only: 1-bit LAST + 6-bit RUN + 8-bit |LEVEL| + 1-bit SIGN.
4596
4597fn wmv2_decode_ac_block(
4598    br: &mut BitReader<'_>,
4599    ac_vlc: &VlcTable,
4600    pquant: i32,
4601    coeff: &mut [i32; 64],
4602    is_intra: bool,
4603) {
4604    // WMV2/MSMPEG4 uses the standard zig-zag scan by default.
4605    // (AC prediction, if implemented, switches to horizontal/vertical scans.)
4606    let scan = &ZIGZAG;
4607    let mut idx = if is_intra { 1usize } else { 0 };
4608
4609    loop {
4610        let sym = match ac_vlc.decode(br) {
4611            Some(s) => s,
4612            None => break,
4613        };
4614
4615        let (run, signed_level, last) = if sym == VLC_ESCAPE {
4616            // WMV2/MSMPEG4 uses the same 3-mode escape structure as VC-1:
4617            //   0  -> mode1 (level offset)
4618            //   10 -> mode2 (run offset)
4619            //   11 -> mode3 (absolute)
4620            decode_escape_coeff(br, ac_vlc)
4621        } else {
4622            let (r, l, last) = unpack_rl(sym);
4623            let sign = br.read_bit().unwrap_or(false);
4624            (r, if sign { -(l as i32) } else { l as i32 }, last)
4625        };
4626
4627        idx = idx.saturating_add(run as usize);
4628        if idx >= 64 {
4629            break;
4630        }
4631
4632        // Uniform quantization.
4633        let q = iquant_uniform(signed_level, pquant, false);
4634        coeff[scan[idx]] = q;
4635
4636        idx += 1;
4637        if last || br.is_empty() {
4638            break;
4639        }
4640    }
4641}
4642
4643// ─── WMV2 MV reader ───────────────────────────────────────────────────────────
4644// Reads a differential MV using a fixed 7-bit Huffman code (simplified from
4645// H.263 MVD table) then adds the median predictor.
4646
4647fn wmv2_read_mv(
4648    br: &mut BitReader<'_>,
4649    mv_pred: &MvPredictor,
4650    mb_row: usize,
4651    mb_col: usize,
4652    mv_range: i32,
4653) -> (i32, i32) {
4654    let (px, py) = mv_pred.predict(mb_row, mb_col);
4655    let dx = wmv2_read_mv_component(br, mv_range);
4656    let dy = wmv2_read_mv_component(br, mv_range);
4657    (px + dx, py + dy)
4658}
4659
4660/// Read one MV component using H.263-style VLC differential coding.
4661/// Values are half-pel units in range [-mv_range, mv_range-1].
4662fn wmv2_read_mv_component(br: &mut BitReader<'_>, mv_range: i32) -> i32 {
4663    // H.263 MVD VLC: unary + suffix
4664    // Code for 0:     "1"         (1 bit)
4665    // Code for ±1:    "010"/"011" (3 bits)
4666    // Code for ±2:    "00110"/"00111"
4667    // etc.  — this is a simple magnitude + sign scheme
4668    let mag = {
4669        let mut m = 0i32;
4670        loop {
4671            if br.read_bit().unwrap_or(true) {
4672                break;
4673            }
4674            m += 1;
4675            if m >= mv_range {
4676                break;
4677            }
4678        }
4679        m
4680    };
4681    if mag == 0 {
4682        return 0;
4683    }
4684    let sign = br.read_bit().unwrap_or(false);
4685    if sign {
4686        -mag
4687    } else {
4688        mag
4689    }
4690}