Skip to main content

atmos/os_lib/
h264_decode.rs

1// h264_decode.rs - H.264 baseline スライス/マクロブロック層 + CAVLC 残差復号 + 再構成。
2//
3// h264.rs(BitReader / SPS-PPS / イントラ予測 / IDCT)の上に、I スライスの
4// 画素復元を実装する。検証はゴールデン素材(h264_golden.rs)で行う。
5// 現状の対応: I スライス、I_16x16 と I_4x4、CAVLC、4:2:0。
6// 未対応: P/B、デブロッキング、CABAC、スライスグループ。
7#![allow(dead_code, clippy::needless_range_loop)]
8
9use super::h264::{
10    dequant_4x4, extract_rbsp, intra_16x16_predict, intra_4x4_predict, intra_chroma_predict,
11    inverse_transform_4x4, parse_avcc, parse_pps, parse_sps, split_annexb, split_avcc, BitReader,
12    Pps, Sps, NORM_ADJUST_4X4,
13};
14use super::h264_cabac::CabacEngine;
15use super::h264_slice::{parse_slice_header, PocState, PredWeightTable, SliceKind};
16use alloc::vec::Vec;
17
18// 4x4 ジグザグスキャン(スキャン位置 -> ラスタ index)。
19const ZIGZAG_4X4: [usize; 16] = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15];
20
21// 1 MB 内の 16 個の輝度 4x4 ブロックのラスタ左上座標(画素)。
22// ブロック index は標準の 4x4 luma block 走査順(8x8 ごとに z スキャン)。
23const LUMA_4X4_BLOCK_XY: [(usize, usize); 16] = [
24    (0, 0),
25    (4, 0),
26    (0, 4),
27    (4, 4),
28    (8, 0),
29    (12, 0),
30    (8, 4),
31    (12, 4),
32    (0, 8),
33    (4, 8),
34    (0, 12),
35    (4, 12),
36    (8, 8),
37    (12, 8),
38    (8, 12),
39    (12, 12),
40];
41
42fn clip1(v: i32) -> i32 {
43    v.clamp(0, 255)
44}
45
46// ===================== CAVLC テーブル =====================
47// 各エントリ: (len, code, total_coeff, trailing_ones)。プレフィックスフリーなので
48// ビットを1つずつ読みながら (len, code) 完全一致で確定する。
49
50type TokEntry = (u8, u16, u8, u8);
51
52// Table 9-5: 0 <= nC < 2
53const COEFF_TOKEN_0: &[TokEntry] = &[
54    (1, 0b1, 0, 0),
55    (6, 0b000101, 1, 0),
56    (2, 0b01, 1, 1),
57    (8, 0b00000111, 2, 0),
58    (6, 0b000100, 2, 1),
59    (3, 0b001, 2, 2),
60    (9, 0b000000111, 3, 0),
61    (8, 0b00000110, 3, 1),
62    (7, 0b0000101, 3, 2),
63    (5, 0b00011, 3, 3),
64    (10, 0b0000000111, 4, 0),
65    (9, 0b000000110, 4, 1),
66    (8, 0b00000101, 4, 2),
67    (6, 0b000011, 4, 3),
68    (11, 0b00000000111, 5, 0),
69    (10, 0b0000000110, 5, 1),
70    (9, 0b000000101, 5, 2),
71    (7, 0b0000100, 5, 3),
72    (13, 0b0000000001111, 6, 0),
73    (11, 0b00000000110, 6, 1),
74    (10, 0b0000000101, 6, 2),
75    (8, 0b00000100, 6, 3),
76    (13, 0b0000000001011, 7, 0),
77    (13, 0b0000000001110, 7, 1),
78    (11, 0b00000000101, 7, 2),
79    (9, 0b000000100, 7, 3),
80    (13, 0b0000000001000, 8, 0),
81    (13, 0b0000000001010, 8, 1),
82    (13, 0b0000000001101, 8, 2),
83    (10, 0b0000000100, 8, 3),
84    (14, 0b00000000001111, 9, 0),
85    (14, 0b00000000001110, 9, 1),
86    (13, 0b0000000001001, 9, 2),
87    (11, 0b00000000100, 9, 3),
88    (14, 0b00000000001011, 10, 0),
89    (14, 0b00000000001010, 10, 1),
90    (14, 0b00000000001101, 10, 2),
91    (13, 0b0000000001100, 10, 3),
92    (15, 0b000000000001111, 11, 0),
93    (15, 0b000000000001110, 11, 1),
94    (14, 0b00000000001001, 11, 2),
95    (14, 0b00000000001100, 11, 3),
96    (15, 0b000000000001011, 12, 0),
97    (15, 0b000000000001010, 12, 1),
98    (15, 0b000000000001101, 12, 2),
99    (14, 0b00000000001000, 12, 3),
100    (16, 0b0000000000001111, 13, 0),
101    (15, 0b000000000000001, 13, 1),
102    (15, 0b000000000001001, 13, 2),
103    (15, 0b000000000001100, 13, 3),
104    (16, 0b0000000000001011, 14, 0),
105    (16, 0b0000000000001110, 14, 1),
106    (16, 0b0000000000001101, 14, 2),
107    (15, 0b000000000001000, 14, 3),
108    (16, 0b0000000000000111, 15, 0),
109    (16, 0b0000000000001010, 15, 1),
110    (16, 0b0000000000001001, 15, 2),
111    (16, 0b0000000000001100, 15, 3),
112    (16, 0b0000000000000100, 16, 0),
113    (16, 0b0000000000000110, 16, 1),
114    (16, 0b0000000000000101, 16, 2),
115    (16, 0b0000000000001000, 16, 3),
116];
117
118// Table 9-5: chroma DC, 4:2:0(nC == -1)
119const COEFF_TOKEN_CHROMA_DC: &[TokEntry] = &[
120    (2, 0b01, 0, 0),
121    (6, 0b000111, 1, 0),
122    (1, 0b1, 1, 1),
123    (6, 0b000100, 2, 0),
124    (6, 0b000110, 2, 1),
125    (3, 0b001, 2, 2),
126    (6, 0b000011, 3, 0),
127    (7, 0b0000011, 3, 1),
128    (7, 0b0000010, 3, 2),
129    (6, 0b000101, 3, 3),
130    (6, 0b000010, 4, 0),
131    (8, 0b00000011, 4, 1),
132    (8, 0b00000010, 4, 2),
133    (7, 0b0000000, 4, 3),
134];
135
136/// coeff_token を読み (total_coeff, trailing_ones) を返す。table は上記のいずれか。
137fn read_coeff_token(r: &mut BitReader, table: &[TokEntry]) -> Option<(u8, u8)> {
138    let mut code: u16 = 0;
139    for len in 1..=16u8 {
140        code = (code << 1) | (r.read_bit() as u16);
141        for &(l, c, tc, t1) in table {
142            if l == len && c == code {
143                return Some((tc, t1));
144            }
145        }
146    }
147    None
148}
149
150// Table 9-5: 2 <= nC < 4
151const COEFF_TOKEN_2: &[TokEntry] = &[
152    (2, 0b11, 0, 0),
153    (6, 0b001011, 1, 0),
154    (2, 0b10, 1, 1),
155    (6, 0b000111, 2, 0),
156    (5, 0b00111, 2, 1),
157    (3, 0b011, 2, 2),
158    (7, 0b0000111, 3, 0),
159    (6, 0b001010, 3, 1),
160    (6, 0b001001, 3, 2),
161    (4, 0b0101, 3, 3),
162    (8, 0b00000111, 4, 0),
163    (6, 0b000110, 4, 1),
164    (6, 0b000101, 4, 2),
165    (4, 0b0100, 4, 3),
166    (8, 0b00000100, 5, 0),
167    (7, 0b0000110, 5, 1),
168    (7, 0b0000101, 5, 2),
169    (5, 0b00110, 5, 3),
170    (9, 0b000000111, 6, 0),
171    (8, 0b00000110, 6, 1),
172    (8, 0b00000101, 6, 2),
173    (6, 0b001000, 6, 3),
174    (11, 0b00000001111, 7, 0),
175    (9, 0b000000110, 7, 1),
176    (9, 0b000000101, 7, 2),
177    (6, 0b000100, 7, 3),
178    (11, 0b00000001011, 8, 0),
179    (11, 0b00000001110, 8, 1),
180    (11, 0b00000001101, 8, 2),
181    (7, 0b0000100, 8, 3),
182    (12, 0b000000001111, 9, 0),
183    (11, 0b00000001010, 9, 1),
184    (11, 0b00000001001, 9, 2),
185    (9, 0b000000100, 9, 3),
186    (12, 0b000000001011, 10, 0),
187    (12, 0b000000001110, 10, 1),
188    (12, 0b000000001101, 10, 2),
189    (11, 0b00000001100, 10, 3),
190    (12, 0b000000001000, 11, 0),
191    (12, 0b000000001010, 11, 1),
192    (12, 0b000000001001, 11, 2),
193    (11, 0b00000001000, 11, 3),
194    (13, 0b0000000001111, 12, 0),
195    (13, 0b0000000001110, 12, 1),
196    (13, 0b0000000001101, 12, 2),
197    (12, 0b000000001100, 12, 3),
198    (13, 0b0000000001011, 13, 0),
199    (13, 0b0000000001010, 13, 1),
200    (13, 0b0000000001001, 13, 2),
201    (13, 0b0000000001100, 13, 3),
202    (13, 0b0000000000111, 14, 0),
203    (14, 0b00000000001011, 14, 1),
204    (13, 0b0000000000110, 14, 2),
205    (13, 0b0000000001000, 14, 3),
206    (14, 0b00000000001001, 15, 0),
207    (14, 0b00000000001000, 15, 1),
208    (14, 0b00000000001010, 15, 2),
209    (13, 0b0000000000001, 15, 3),
210    (14, 0b00000000000111, 16, 0),
211    (14, 0b00000000000110, 16, 1),
212    (14, 0b00000000000101, 16, 2),
213    (14, 0b00000000000100, 16, 3),
214];
215
216// Table 9-5: 4 <= nC < 8
217const COEFF_TOKEN_4: &[TokEntry] = &[
218    (4, 0b1111, 0, 0),
219    (6, 0b001111, 1, 0),
220    (4, 0b1110, 1, 1),
221    (6, 0b001011, 2, 0),
222    (5, 0b01111, 2, 1),
223    (4, 0b1101, 2, 2),
224    (6, 0b001000, 3, 0),
225    (5, 0b01100, 3, 1),
226    (5, 0b01110, 3, 2),
227    (4, 0b1100, 3, 3),
228    (7, 0b0001111, 4, 0),
229    (5, 0b01010, 4, 1),
230    (5, 0b01011, 4, 2),
231    (4, 0b1011, 4, 3),
232    (7, 0b0001011, 5, 0),
233    (5, 0b01000, 5, 1),
234    (5, 0b01001, 5, 2),
235    (4, 0b1010, 5, 3),
236    (7, 0b0001001, 6, 0),
237    (6, 0b001110, 6, 1),
238    (6, 0b001101, 6, 2),
239    (4, 0b1001, 6, 3),
240    (7, 0b0001000, 7, 0),
241    (6, 0b001010, 7, 1),
242    (6, 0b001001, 7, 2),
243    (4, 0b1000, 7, 3),
244    (8, 0b00001111, 8, 0),
245    (7, 0b0001110, 8, 1),
246    (7, 0b0001101, 8, 2),
247    (5, 0b01101, 8, 3),
248    (8, 0b00001011, 9, 0),
249    (8, 0b00001110, 9, 1),
250    (7, 0b0001010, 9, 2),
251    (6, 0b001100, 9, 3),
252    (9, 0b000001111, 10, 0),
253    (8, 0b00001010, 10, 1),
254    (8, 0b00001101, 10, 2),
255    (7, 0b0001100, 10, 3),
256    (9, 0b000001011, 11, 0),
257    (9, 0b000001110, 11, 1),
258    (8, 0b00001001, 11, 2),
259    (8, 0b00001100, 11, 3),
260    (9, 0b000001000, 12, 0),
261    (9, 0b000001010, 12, 1),
262    (9, 0b000001101, 12, 2),
263    (8, 0b00001000, 12, 3),
264    (10, 0b0000001101, 13, 0),
265    (9, 0b000000111, 13, 1),
266    (9, 0b000001001, 13, 2),
267    (9, 0b000001100, 13, 3),
268    (10, 0b0000001001, 14, 0),
269    (10, 0b0000001100, 14, 1),
270    (10, 0b0000001011, 14, 2),
271    (10, 0b0000001010, 14, 3),
272    (10, 0b0000000101, 15, 0),
273    (10, 0b0000001000, 15, 1),
274    (10, 0b0000000111, 15, 2),
275    (10, 0b0000000110, 15, 3),
276    (10, 0b0000000001, 16, 0),
277    (10, 0b0000000100, 16, 1),
278    (10, 0b0000000011, 16, 2),
279    (10, 0b0000000010, 16, 3),
280];
281
282/// coeff_token を nC に応じて復号。nC>=8 は 6bit FLC、nC==-1 はクロマ DC 表。
283fn read_coeff_token_nc(r: &mut BitReader, n_c: i32) -> Option<(u8, u8)> {
284    if n_c == -1 {
285        return read_coeff_token(r, COEFF_TOKEN_CHROMA_DC);
286    }
287    if n_c >= 8 {
288        let code = r.read_bits(6);
289        if code == 3 {
290            return Some((0, 0));
291        }
292        return Some(((code >> 2) as u8 + 1, (code & 3) as u8));
293    }
294    let table = if n_c < 2 {
295        COEFF_TOKEN_0
296    } else if n_c < 4 {
297        COEFF_TOKEN_2
298    } else {
299        COEFF_TOKEN_4
300    };
301    read_coeff_token(r, table)
302}
303
304// Table 9-4: coded_block_pattern の me(v) 復号。codeNum -> cbp(Intra_4x4, ChromaArrayType=1/2)。
305const CBP_INTRA_MAP: [u8; 48] = [
306    47, 31, 15, 0, 23, 27, 29, 30, 7, 11, 13, 14, 39, 43, 45, 46, 16, 3, 5, 10, 12, 19, 21, 26, 28,
307    35, 37, 42, 44, 1, 2, 4, 8, 17, 18, 20, 24, 6, 9, 22, 25, 32, 33, 34, 36, 40, 38, 41,
308];
309
310// Table 9-4: coded_block_pattern の me(v) 復号(インター MB 用、ffmpeg golomb_to_inter_cbp)。
311const CBP_INTER_MAP: [u8; 48] = [
312    0, 16, 1, 2, 4, 8, 32, 3, 5, 10, 12, 15, 47, 7, 11, 13, 14, 6, 9, 31, 35, 37, 42, 44, 33, 34,
313    36, 40, 39, 43, 45, 46, 17, 18, 20, 24, 19, 21, 26, 28, 23, 27, 29, 30, 22, 25, 38, 41,
314];
315
316// デブロッキング閾値(Table 8-16 / 8-17、ffmpeg の値から生成)。
317const ALPHA_TABLE: [u8; 52] = [
318    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 5, 6, 7, 8, 9, 10, 12, 13, 15, 17, 20,
319    22, 25, 28, 32, 36, 40, 45, 50, 56, 63, 71, 80, 90, 101, 113, 127, 144, 162, 182, 203, 226,
320    255, 255,
321];
322const BETA_TABLE: [u8; 52] = [
323    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 6, 6, 7, 7, 8, 8,
324    9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18,
325];
326// TC0_TABLE[indexA][bS-1](bS=1,2,3)。
327const TC0_TABLE: [[u8; 3]; 52] = [
328    [0, 0, 0],
329    [0, 0, 0],
330    [0, 0, 0],
331    [0, 0, 0],
332    [0, 0, 0],
333    [0, 0, 0],
334    [0, 0, 0],
335    [0, 0, 0],
336    [0, 0, 0],
337    [0, 0, 0],
338    [0, 0, 0],
339    [0, 0, 0],
340    [0, 0, 0],
341    [0, 0, 0],
342    [0, 0, 0],
343    [0, 0, 0],
344    [0, 0, 0],
345    [0, 0, 1],
346    [0, 0, 1],
347    [0, 0, 1],
348    [0, 0, 1],
349    [0, 1, 1],
350    [0, 1, 1],
351    [1, 1, 1],
352    [1, 1, 1],
353    [1, 1, 1],
354    [1, 1, 1],
355    [1, 1, 2],
356    [1, 1, 2],
357    [1, 1, 2],
358    [1, 1, 2],
359    [1, 2, 3],
360    [1, 2, 3],
361    [2, 2, 3],
362    [2, 2, 4],
363    [2, 3, 4],
364    [2, 3, 4],
365    [3, 3, 5],
366    [3, 4, 6],
367    [3, 4, 6],
368    [4, 5, 7],
369    [4, 5, 8],
370    [4, 6, 9],
371    [5, 7, 10],
372    [6, 8, 11],
373    [6, 8, 13],
374    [7, 10, 14],
375    [8, 11, 16],
376    [9, 12, 18],
377    [10, 13, 20],
378    [11, 15, 23],
379    [13, 17, 25],
380];
381
382// Table 9-7/9-8: total_zeros(maxNumCoeff=16)。行 = total_coeff(1..15)、
383// 各行 entry: (len, code) を total_zeros=0,1,2,... の順で並べる。
384const TOTAL_ZEROS_16: &[&[(u8, u16)]] = &[
385    &[
386        (1, 0b1),
387        (3, 0b011),
388        (3, 0b010),
389        (4, 0b0011),
390        (4, 0b0010),
391        (5, 0b00011),
392        (5, 0b00010),
393        (6, 0b000011),
394        (6, 0b000010),
395        (7, 0b0000011),
396        (7, 0b0000010),
397        (8, 0b00000011),
398        (8, 0b00000010),
399        (9, 0b000000011),
400        (9, 0b000000010),
401        (9, 0b000000001),
402    ],
403    &[
404        (3, 0b111),
405        (3, 0b110),
406        (3, 0b101),
407        (3, 0b100),
408        (3, 0b011),
409        (4, 0b0101),
410        (4, 0b0100),
411        (4, 0b0011),
412        (4, 0b0010),
413        (5, 0b00011),
414        (5, 0b00010),
415        (6, 0b000011),
416        (6, 0b000010),
417        (6, 0b000001),
418        (6, 0b000000),
419    ],
420    &[
421        (4, 0b0101),
422        (3, 0b111),
423        (3, 0b110),
424        (3, 0b101),
425        (4, 0b0100),
426        (4, 0b0011),
427        (3, 0b100),
428        (3, 0b011),
429        (4, 0b0010),
430        (5, 0b00011),
431        (5, 0b00010),
432        (6, 0b000001),
433        (5, 0b00001),
434        (6, 0b000000),
435    ],
436    &[
437        (5, 0b00011),
438        (3, 0b111),
439        (4, 0b0101),
440        (4, 0b0100),
441        (3, 0b110),
442        (3, 0b101),
443        (3, 0b100),
444        (4, 0b0011),
445        (3, 0b011),
446        (4, 0b0010),
447        (5, 0b00010),
448        (5, 0b00001),
449        (5, 0b00000),
450    ],
451    &[
452        (4, 0b0101),
453        (4, 0b0100),
454        (4, 0b0011),
455        (3, 0b111),
456        (3, 0b110),
457        (3, 0b101),
458        (3, 0b100),
459        (3, 0b011),
460        (4, 0b0010),
461        (5, 0b00001),
462        (4, 0b0001),
463        (5, 0b00000),
464    ],
465    &[
466        (6, 0b000001),
467        (5, 0b00001),
468        (3, 0b111),
469        (3, 0b110),
470        (3, 0b101),
471        (3, 0b100),
472        (3, 0b011),
473        (3, 0b010),
474        (4, 0b0001),
475        (3, 0b001),
476        (6, 0b000000),
477    ],
478    &[
479        (6, 0b000001),
480        (5, 0b00001),
481        (3, 0b101),
482        (3, 0b100),
483        (3, 0b011),
484        (2, 0b11),
485        (3, 0b010),
486        (4, 0b0001),
487        (3, 0b001),
488        (6, 0b000000),
489    ],
490    &[
491        (6, 0b000001),
492        (4, 0b0001),
493        (5, 0b00001),
494        (3, 0b011),
495        (2, 0b11),
496        (2, 0b10),
497        (3, 0b010),
498        (3, 0b001),
499        (6, 0b000000),
500    ],
501    &[
502        (6, 0b000001),
503        (6, 0b000000),
504        (4, 0b0001),
505        (2, 0b11),
506        (2, 0b10),
507        (3, 0b001),
508        (2, 0b01),
509        (5, 0b00001),
510    ],
511    &[
512        (5, 0b00001),
513        (5, 0b00000),
514        (3, 0b001),
515        (2, 0b11),
516        (2, 0b10),
517        (2, 0b01),
518        (4, 0b0001),
519    ],
520    &[
521        (4, 0b0000),
522        (4, 0b0001),
523        (3, 0b001),
524        (3, 0b010),
525        (1, 0b1),
526        (3, 0b011),
527    ],
528    &[(4, 0b0000), (4, 0b0001), (2, 0b01), (1, 0b1), (3, 0b001)],
529    &[(3, 0b000), (3, 0b001), (1, 0b1), (2, 0b01)],
530    &[(2, 0b00), (2, 0b01), (1, 0b1)],
531    &[(1, 0b0), (1, 0b1)],
532];
533
534// Table 9-9(a): total_zeros for chroma DC(maxNumCoeff=4)。行 = total_coeff(1..3)。
535const TOTAL_ZEROS_CHROMA: &[&[(u8, u16)]] = &[
536    &[(1, 0b1), (2, 0b01), (3, 0b001), (3, 0b000)],
537    &[(1, 0b1), (2, 0b01), (2, 0b00)],
538    &[(1, 0b1), (1, 0b0)],
539];
540
541fn read_total_zeros(r: &mut BitReader, total_coeff: u8, tables: &[&[(u8, u16)]]) -> u8 {
542    let row = tables[(total_coeff - 1) as usize];
543    let mut code: u16 = 0;
544    for len in 1..=9u8 {
545        code = (code << 1) | (r.read_bit() as u16);
546        for (tz, &(l, c)) in row.iter().enumerate() {
547            if l == len && c == code {
548                return tz as u8;
549            }
550        }
551    }
552    0
553}
554
555// Table 9-10: run_before。index = min(zerosLeft,7)-1、entry order = run_before 0,1,2,...
556const RUN_BEFORE: &[&[(u8, u16)]] = &[
557    &[(1, 0b1), (1, 0b0)],
558    &[(1, 0b1), (2, 0b01), (2, 0b00)],
559    &[(2, 0b11), (2, 0b10), (2, 0b01), (2, 0b00)],
560    &[(2, 0b11), (2, 0b10), (2, 0b01), (3, 0b001), (3, 0b000)],
561    &[
562        (2, 0b11),
563        (2, 0b10),
564        (3, 0b011),
565        (3, 0b010),
566        (3, 0b001),
567        (3, 0b000),
568    ],
569    &[
570        (2, 0b11),
571        (3, 0b000),
572        (3, 0b001),
573        (3, 0b011),
574        (3, 0b010),
575        (3, 0b101),
576        (3, 0b100),
577    ],
578    &[
579        (3, 0b111),
580        (3, 0b110),
581        (3, 0b101),
582        (3, 0b100),
583        (3, 0b011),
584        (3, 0b010),
585        (3, 0b001),
586        (4, 0b0001),
587        (5, 0b00001),
588        (6, 0b000001),
589        (7, 0b0000001),
590        (8, 0b00000001),
591        (9, 0b000000001),
592        (10, 0b0000000001),
593        (11, 0b00000000001),
594    ],
595];
596
597fn read_run_before(r: &mut BitReader, zeros_left: i32) -> i32 {
598    let idx = (zeros_left.min(7) - 1).max(0) as usize;
599    let row = RUN_BEFORE[idx];
600    let mut code: u16 = 0;
601    for len in 1..=11u8 {
602        code = (code << 1) | (r.read_bit() as u16);
603        for (run, &(l, c)) in row.iter().enumerate() {
604            if l == len && c == code {
605                return run as i32;
606            }
607        }
608    }
609    0
610}
611
612/// CAVLC で 1 残差ブロックを復号。スキャン順の係数配列と total_coeff を返す。
613/// n_c: 近傍係数数(-1=chroma DC, >=8=FLC)。tz_tables: total_zeros VLC 群。
614fn residual_block_cavlc(
615    r: &mut BitReader,
616    max_num_coeff: usize,
617    n_c: i32,
618    tz_tables: &[&[(u8, u16)]],
619) -> ([i32; 16], u8) {
620    let mut coeff = [0i32; 16];
621    let (total_coeff, trailing_ones) = match read_coeff_token_nc(r, n_c) {
622        Some(v) => v,
623        None => return (coeff, 0),
624    };
625    let ret_total = total_coeff;
626    if total_coeff == 0 {
627        return (coeff, 0);
628    }
629    let total_coeff = total_coeff as usize;
630    let trailing_ones = trailing_ones as usize;
631
632    // レベル復号(高周波から)
633    let mut level = [0i32; 16];
634    for i in 0..trailing_ones {
635        level[i] = if r.read_bit() == 1 { -1 } else { 1 };
636    }
637    let mut suffix_length: u32 = if total_coeff > 10 && trailing_ones < 3 {
638        1
639    } else {
640        0
641    };
642    for i in trailing_ones..total_coeff {
643        let mut level_prefix = 0u32;
644        while r.read_bit() == 0 {
645            level_prefix += 1;
646            if level_prefix > 31 {
647                break;
648            }
649        }
650        let mut suffix_size = suffix_length;
651        if level_prefix == 14 && suffix_length == 0 {
652            suffix_size = 4;
653        } else if level_prefix >= 15 {
654            suffix_size = level_prefix - 3;
655        }
656        let level_suffix = if suffix_size > 0 {
657            r.read_bits(suffix_size) as i32
658        } else {
659            0
660        };
661        let mut level_code = ((level_prefix.min(15) << suffix_length) as i32) + level_suffix;
662        if level_prefix >= 15 && suffix_length == 0 {
663            level_code += 15;
664        }
665        if level_prefix >= 16 {
666            level_code += (1 << (level_prefix - 3)) - 4096;
667        }
668        if i == trailing_ones && trailing_ones < 3 {
669            level_code += 2;
670        }
671        level[i] = if level_code % 2 == 0 {
672            (level_code + 2) >> 1
673        } else {
674            (-level_code - 1) >> 1
675        };
676        if suffix_length == 0 {
677            suffix_length = 1;
678        }
679        if level[i].unsigned_abs() > (3u32 << (suffix_length - 1)) && suffix_length < 6 {
680            suffix_length += 1;
681        }
682    }
683
684    // total_zeros
685    let total_zeros = if total_coeff < max_num_coeff {
686        read_total_zeros(r, total_coeff as u8, tz_tables) as i32
687    } else {
688        0
689    };
690
691    // run_before
692    let mut run = [0i32; 16];
693    let mut zeros_left = total_zeros;
694    for i in 0..total_coeff {
695        if i < total_coeff - 1 && zeros_left > 0 {
696            let rb = read_run_before(r, zeros_left);
697            run[i] = rb;
698            zeros_left -= rb;
699        } else if i == total_coeff - 1 {
700            run[i] = zeros_left;
701        } else {
702            run[i] = 0;
703        }
704    }
705
706    // スキャン順へ配置(高周波 level[0] を高 index に)
707    let mut coeff_ctr: i32 = -1;
708    for i in (0..total_coeff).rev() {
709        coeff_ctr += run[i] + 1;
710        if (coeff_ctr as usize) < 16 {
711            coeff[coeff_ctr as usize] = level[i];
712        }
713    }
714    (coeff, ret_total)
715}
716
717// ===================== 変換(DC 用 Hadamard) =====================
718
719/// I_16x16 輝度 DC の 4x4 逆 Hadamard 変換(in-place、raster)。丸め無し。
720fn inverse_hadamard_4x4(b: &mut [i32; 16]) {
721    for i in 0..4 {
722        let o = i * 4;
723        let (a0, a1, a2, a3) = (
724            b[o] + b[o + 2],
725            b[o] - b[o + 2],
726            b[o + 1] - b[o + 3],
727            b[o + 1] + b[o + 3],
728        );
729        b[o] = a0 + a3;
730        b[o + 1] = a1 + a2;
731        b[o + 2] = a1 - a2;
732        b[o + 3] = a0 - a3;
733    }
734    for j in 0..4 {
735        let (a0, a1, a2, a3) = (
736            b[j] + b[8 + j],
737            b[j] - b[8 + j],
738            b[4 + j] - b[12 + j],
739            b[4 + j] + b[12 + j],
740        );
741        b[j] = a0 + a3;
742        b[4 + j] = a1 + a2;
743        b[8 + j] = a1 - a2;
744        b[12 + j] = a0 - a3;
745    }
746}
747
748/// I_16x16 輝度 DC のスケーリング(8.5.10)。
749fn scale_luma_dc(f: &mut [i32; 16], qp: i32) {
750    let m = (qp % 6) as usize;
751    let ls = 16 * NORM_ADJUST_4X4[m][0];
752    let shift = qp / 6;
753    for v in f.iter_mut() {
754        *v = if qp >= 36 {
755            (*v * ls) << (shift - 6)
756        } else {
757            (*v * ls + (1 << (5 - shift))) >> (6 - shift)
758        };
759    }
760}
761
762// ===================== スライス/MB 復号 =====================
763
764pub struct DecodedYuv {
765    pub width: u32,
766    pub height: u32,
767    pub y: Vec<u8>,
768    pub u: Vec<u8>,
769    pub v: Vec<u8>,
770}
771
772/// YUV420 → RGB24(BT.601、各画素 [R,G,B])。描画経路用。
773pub fn yuv_to_rgb(yuv: &DecodedYuv) -> Vec<u8> {
774    let w = yuv.width as usize;
775    let h = yuv.height as usize;
776    let cw = (yuv.width / 2) as usize;
777    let mut rgb = alloc::vec![0u8; w * h * 3];
778    let mut oi = 0usize;
779    for y in 0..h {
780        let yrow = y * w;
781        let crow = (y >> 1) * cw;
782        for x in 0..w {
783            let yy = yuv.y[yrow + x] as i32;
784            let ci = crow + (x >> 1);
785            let cu = yuv.u[ci] as i32 - 128;
786            let cv = yuv.v[ci] as i32 - 128;
787            let r = yy + ((91881 * cv) >> 16);
788            let g = yy - ((22554 * cu + 46802 * cv) >> 16);
789            let b = yy + ((116130 * cu) >> 16);
790            rgb[oi] = r.clamp(0, 255) as u8;
791            rgb[oi + 1] = g.clamp(0, 255) as u8;
792            rgb[oi + 2] = b.clamp(0, 255) as u8;
793            oi += 3;
794        }
795    }
796    rgb
797}
798
799/// 参照ピクチャの動き情報(輝度 4x4 ブロック単位)。B の空間ダイレクトの co-located に使う。
800/// `refi` が L0/L1 とも -1 のブロックはイントラ。
801#[derive(Clone)]
802pub struct Motion {
803    blk_w: usize,
804    mv: [Vec<[i16; 2]>; 2],
805    refi: [Vec<i8>; 2],
806}
807
808/// DPB の 1 枚(短期参照フレーム)。
809pub struct RefPic {
810    pub yuv: DecodedYuv,
811    pub poc: i32,
812    frame_num: u32,
813    motion: Motion,
814}
815
816/// 参照リストの 1 要素。`id` は参照ピクチャの識別子(POC)で、デブロックの bS 判定で
817/// 「同じ参照ピクチャか」を refIdx ではなくピクチャそのもので比べるのに使う(8.7.2.1)。
818#[derive(Clone, Copy)]
819pub struct RefEntry<'a> {
820    yuv: &'a DecodedYuv,
821    id: i32,
822    /// PicNum(= FrameNumWrap、8.2.4.1)。参照リストの並べ替えで指す番号。
823    pic_num: i32,
824    motion: Option<&'a Motion>,
825}
826
827/// FrameNumWrap(= フレームの PicNum、8.2.4.1)。現在の frame_num より大きいものは前の周回。
828fn pic_num(frame_num: u32, cur_frame_num: u32, max_frame_num: i32) -> i32 {
829    if frame_num > cur_frame_num {
830        frame_num as i32 - max_frame_num
831    } else {
832        frame_num as i32
833    }
834}
835
836/// 参照リストの並べ替え(8.2.4.3.1、短期参照のみ)。`list` は初期リスト(num_active で切ったもの)、
837/// `all` は短期参照のすべて。指された PicNum のピクチャを refIdx の位置へ入れ、後ろの重複を除く。
838fn modify_ref_list<'a>(
839    list: &mut Vec<RefEntry<'a>>,
840    mods: &[(u32, u32)],
841    all: &[RefEntry<'a>],
842    cur_frame_num: u32,
843    max_frame_num: i32,
844    num_active: usize,
845) -> Result<(), &'static str> {
846    let cur = cur_frame_num as i32;
847    let mut pred = cur;
848    for (ref_idx, &(idc, abs_diff_minus1)) in mods.iter().enumerate() {
849        let d = abs_diff_minus1 as i32 + 1;
850        let mut no_wrap = if idc == 0 { pred - d } else { pred + d };
851        if no_wrap < 0 {
852            no_wrap += max_frame_num;
853        } else if no_wrap >= max_frame_num {
854            no_wrap -= max_frame_num;
855        }
856        pred = no_wrap;
857        let target = if no_wrap > cur { no_wrap - max_frame_num } else { no_wrap };
858        let pic = *all.iter().find(|e| e.pic_num == target).ok_or("ref_pic_list_modification: no such picture")?;
859        if let Some(pos) = list.iter().skip(ref_idx).position(|e| e.pic_num == target) {
860            list.remove(ref_idx + pos);
861        }
862        list.insert(ref_idx.min(list.len()), pic);
863        list.truncate(num_active);
864    }
865    Ok(())
866}
867
868/// スライスの種別ごとの初期参照リスト(並べ替え無し、8.2.4.2)。
869/// `p` は P の RefPicList0(復号の新しい順)、`b` は B の RefPicList0/1(POC 順)。
870struct RefLists<'a> {
871    p: Vec<RefEntry<'a>>,
872    b: [Vec<RefEntry<'a>>; 2],
873    /// 現在のピクチャの POC(暗黙の重み付き双予測の距離計算用)。
874    cur_poc: i32,
875}
876
877/// 短期参照フレームのスライディングウィンドウ(8.2.5.3)と POC の状態。**復号の新しい順**に持つ。
878/// IDR で空にする。長期参照・MMCO は未対応(スライスヘッダの解析で失敗を返す)。
879/// 【2026-10-17】B スライス対応で POC と動き情報を持つようにした。B の参照リストは POC の
880/// 大小で組み(8.2.4.2.3)、空間ダイレクトは RefPicList1[0] の co-located の動きを見る。
881#[derive(Default)]
882pub struct RefFrames {
883    frames: Vec<RefPic>,
884    poc: PocState,
885}
886
887impl RefFrames {
888    pub fn new() -> Self {
889        Self::default()
890    }
891
892    /// 現在のピクチャ(POC = `cur_poc`、frame_num = `cur_frame_num`)から見た初期参照リスト。
893    fn lists(&self, cur_poc: i32, cur_frame_num: u32, max_frame_num: i32) -> RefLists<'_> {
894        fn entry_of(f: &RefPic, cur_frame_num: u32, max_frame_num: i32) -> RefEntry<'_> {
895            let pn = pic_num(f.frame_num, cur_frame_num, max_frame_num);
896            RefEntry { yuv: &f.yuv, id: f.poc, pic_num: pn, motion: Some(&f.motion) }
897        }
898        let entry = |f| entry_of(f, cur_frame_num, max_frame_num);
899        let p: Vec<RefEntry> = self.frames.iter().map(entry).collect();
900        // L0: POC が小さいもの(近い順=降順)→ 大きいもの(昇順)。L1 はその逆(8.2.4.2.3)。
901        let mut before: Vec<&RefPic> = self.frames.iter().filter(|f| f.poc < cur_poc).collect();
902        let mut after: Vec<&RefPic> = self.frames.iter().filter(|f| f.poc > cur_poc).collect();
903        before.sort_by_key(|f| core::cmp::Reverse(f.poc));
904        after.sort_by_key(|f| f.poc);
905        let l0: Vec<RefEntry> = before.iter().chain(after.iter()).map(|f| entry(f)).collect();
906        let mut l1: Vec<RefEntry> = after.iter().chain(before.iter()).map(|f| entry(f)).collect();
907        // L1 が 2 枚以上で L0 と同じ並びなら、L1 の先頭 2 枚を入れ替える。
908        if l1.len() > 1 && l1.iter().zip(&l0).all(|(a, b)| a.id == b.id) {
909            l1.swap(0, 1);
910        }
911        RefLists { p, b: [l0, l1], cur_poc }
912    }
913
914    /// 1 フレーム(同じピクチャのスライス群)を復号して DPB を更新する。戻り値は (フレーム, POC)。
915    /// フレームは**復号順**に返るので、B を含むストリームの表示は POC 順に並べ替えること。
916    pub fn decode(
917        &mut self,
918        sps: &Sps,
919        pps: &Pps,
920        slices: &[&[u8]],
921        is_idr: bool,
922        nal_ref_idc: u8,
923    ) -> Result<(DecodedYuv, i32), &'static str> {
924        let first = slices.first().ok_or("no slices")?;
925        let h = parse_slice_header(&mut BitReader::new(first), sps, pps, is_idr, nal_ref_idc)?;
926        if is_idr {
927            self.frames.clear();
928        }
929        let poc = self.poc.compute(sps, &h, is_idr, nal_ref_idc);
930        let max_frame_num = 1i32 << sps.log2_max_frame_num;
931        let (yuv, motion) = {
932            let lists = self.lists(poc, h.frame_num, max_frame_num);
933            decode_picture_lists(sps, pps, slices, is_idr, nal_ref_idc, &lists)?
934        };
935        if nal_ref_idc != 0 {
936            // 参照ピクチャの記録(8.2.5)。MMCO 1 は PicNum = CurrPicNum - (diff + 1) の短期参照を外す。
937            for &diff in &h.mmco_unmark {
938                let target = h.frame_num as i32 - (diff as i32 + 1);
939                self.frames.retain(|f| pic_num(f.frame_num, h.frame_num, max_frame_num) != target);
940            }
941            let copy = DecodedYuv {
942                width: yuv.width,
943                height: yuv.height,
944                y: yuv.y.clone(),
945                u: yuv.u.clone(),
946                v: yuv.v.clone(),
947            };
948            // スライディングウィンドウ(MMCO の無いとき)。MMCO のときも DPB があふれないよう切る。
949            let max = sps.max_num_ref_frames.max(1) as usize;
950            if !h.adaptive_marking && self.frames.len() >= max {
951                self.frames.truncate(max - 1);
952            }
953            self.frames.insert(0, RefPic { yuv: copy, poc, frame_num: h.frame_num, motion });
954            self.frames.truncate(max);
955        }
956        Ok((yuv, poc))
957    }
958}
959
960/// RGB24 のフレーム(`fw`x`fh`)から表示領域(frame_cropping)を切り出す。
961/// 領域が不正なら切り出さずに返す。例: 640x360 は 640x368(MB 単位)を復号して下 8 行を落とす。
962fn crop_rgb(rgb: Vec<u8>, fw: u32, fh: u32, x: u32, y: u32, w: u32, h: u32) -> (u32, u32, Vec<u8>) {
963    if w == 0 || h == 0 || x + w > fw || y + h > fh || (w == fw && h == fh) {
964        return (fw, fh, rgb);
965    }
966    let mut out = Vec::with_capacity((w * h * 3) as usize);
967    for row in y..y + h {
968        let o = ((row * fw + x) * 3) as usize;
969        out.extend_from_slice(&rgb[o..o + (w * 3) as usize]);
970    }
971    (w, h, out)
972}
973
974/// MP4/AVCC 経路用のステートフルなビデオデコーダ。参照フレームを保持して P/B を復号し、
975/// B による並べ替えを POC 順に戻して返す。
976pub struct VideoDecoder {
977    sps: Option<Sps>,
978    pps: Option<Pps>,
979    length_size: usize,
980    refs: RefFrames,
981    /// 表示待ちのフレーム((IDR の通し番号, POC), フレーム)。
982    reorder: Vec<((u32, i32), DecodedYuv)>,
983    idr_count: u32,
984}
985
986impl Default for VideoDecoder {
987    fn default() -> Self {
988        Self::new()
989    }
990}
991
992impl VideoDecoder {
993    pub fn new() -> Self {
994        Self {
995            sps: None,
996            pps: None,
997            length_size: 4,
998            refs: RefFrames::new(),
999            reorder: Vec::new(),
1000            idr_count: 0,
1001        }
1002    }
1003
1004    /// avcC から SPS/PPS と NAL 長さフィールドサイズを取り込む。
1005    pub fn init_avcc(&mut self, avcc: &[u8]) {
1006        if let Some((ls, sps_list, pps_list)) = parse_avcc(avcc) {
1007            self.length_size = ls;
1008            if let Some(p) = sps_list.first() {
1009                if p.len() > 1 {
1010                    self.sps = parse_sps(&extract_rbsp(&p[1..])).ok();
1011                }
1012            }
1013            if let Some(p) = pps_list.first() {
1014                if p.len() > 1 {
1015                    self.pps = parse_pps(&extract_rbsp(&p[1..])).ok();
1016                }
1017            }
1018        }
1019    }
1020
1021    /// AVCC の 1 サンプル(= 1 アクセスユニット)を復号し、RGB フレームを返す。
1022    pub fn decode_sample_rgb(&mut self, sample: &[u8]) -> Option<(u32, u32, Vec<u8>)> {
1023        let nals = split_avcc(sample, self.length_size);
1024        // 1 サンプル = 1 アクセスユニット。中のスライスをまとめて 1 フレームとして組む。
1025        let mut slices: Vec<&[u8]> = Vec::new();
1026        let mut is_idr = false;
1027        let mut ref_idc = 0u8;
1028        for nu in &nals {
1029            match nu.unit_type {
1030                7 => self.sps = parse_sps(&nu.rbsp).ok(),
1031                8 => self.pps = parse_pps(&nu.rbsp).ok(),
1032                1 | 5 => {
1033                    slices.push(&nu.rbsp);
1034                    is_idr = nu.unit_type == 5;
1035                    ref_idc = nu.ref_idc;
1036                }
1037                _ => {}
1038            }
1039        }
1040        let (s, p) = match (self.sps.clone(), self.pps.clone()) {
1041            (Some(s), Some(p)) => (s, p),
1042            _ => return None,
1043        };
1044        if slices.is_empty() {
1045            return None;
1046        }
1047        let (frame, poc) = self.refs.decode(&s, &p, &slices, is_idr, ref_idc).ok()?;
1048        if is_idr {
1049            self.idr_count = self.idr_count.wrapping_add(1);
1050        }
1051        self.reorder.push(((self.idr_count, poc), frame));
1052        // 並べ替えの深さ: Baseline(B 無し)は 0。それ以外は参照枚数ぶん待つ(VUI の
1053        // max_num_reorder_frames は未解析なので控えめな上限で代用。表示がその枚数ぶん遅れる)。
1054        let depth = if s.profile_idc == 66 { 0 } else { s.max_num_ref_frames.clamp(1, 4) as usize };
1055        if self.reorder.len() <= depth {
1056            return None;
1057        }
1058        let (idx, _) = self.reorder.iter().enumerate().min_by_key(|(_, (k, _))| *k)?;
1059        let (_, frame) = self.reorder.remove(idx);
1060        let rgb = yuv_to_rgb(&frame);
1061        Some(crop_rgb(rgb, frame.width, frame.height, s.crop_x, s.crop_y, s.width, s.height))
1062    }
1063}
1064
1065/// デコーダ状態。YUV 平面に加え、4x4 ブロック単位の近傍情報グリッドを持つ。
1066struct DecState {
1067    yuv: DecodedYuv,
1068    blk_w: usize,             // 輝度 4x4 ブロック数(横)= width/4
1069    nnz_luma: Vec<u8>,        // 各輝度 4x4 ブロックの total_coeff(nC 計算用)
1070    mode_luma: Vec<i8>,       // 各輝度 4x4 ブロックの Intra4x4 モード(-1=非I4x4/不可用)
1071    decoded: Vec<bool>,       // 各輝度 4x4 ブロックが再構成済みか(top-right 可用性用)
1072    cblk_w: usize,            // クロマ 4x4 ブロック数(横)= (width/2)/4
1073    nnz_chroma: [Vec<u8>; 2], // Cb/Cr の各クロマ 4x4 ブロックの total_coeff
1074    mb_w: usize,              // MB 数(横)
1075    mb_qp: Vec<i32>,          // 各 MB の QPy(デブロッキング用)
1076    mv: [Vec<[i16; 2]>; 2],   // 各輝度 4x4 ブロックの動きベクトル(1/4 画素単位)[L0, L1]
1077    is_inter: Vec<bool>,      // 各輝度 4x4 ブロックがインター予測か(MV 予測・デブロック用)
1078    blk_h: usize,             // 輝度 4x4 ブロック数(縦)
1079    refi: [Vec<i8>; 2],       // 各輝度 4x4 ブロックの refIdxL0/L1(-1 = そのリストを使わない/イントラ)
1080    refp: [Vec<i32>; 2],      // 各輝度 4x4 ブロックの参照ピクチャ識別子(POC。デブロックの bS 判定用)
1081    num_ref: [u32; 2],        // このスライスの num_ref_idx_l0/l1_active
1082    mb_slice: Vec<u32>,       // 各 MB の所属スライス番号(0 = 未復号)。可用性の判定に使う
1083    cur_slice: u32,           // 復号中のスライス番号(1 始まり)
1084    mb_db: Vec<MbDeblock>,    // 各 MB のデブロック設定(所属スライスのヘッダ値)
1085    mb_t8: Vec<bool>,         // 各 MB が 8x8 変換か(transform_size_8x8_flag)
1086    wp: WpMode,               // このスライスの重み付き予測
1087    cur_poc: i32,             // 現在のピクチャの POC
1088}
1089
1090/// 重み付き予測のモード(8.4.2.3)。
1091enum WpMode {
1092    /// 既定(単方向はそのまま、双予測は平均)。
1093    Off,
1094    /// 明示(スライスヘッダの pred_weight_table)。
1095    Explicit(PredWeightTable),
1096    /// 暗黙(B の weighted_bipred_idc = 2。双予測だけ POC の距離で重み付け)。
1097    Implicit,
1098}
1099
1100/// 近傍 2 ブロックの nnz から nC を求める(-1 は不可用)。
1101fn combine_nc(na: i32, nb: i32) -> i32 {
1102    if na >= 0 && nb >= 0 {
1103        (na + nb + 1) >> 1
1104    } else if na >= 0 {
1105        na
1106    } else if nb >= 0 {
1107        nb
1108    } else {
1109        0
1110    }
1111}
1112
1113impl DecState {
1114    fn li(&self, bx4: usize, by4: usize) -> usize {
1115        by4 * self.blk_w + bx4
1116    }
1117
1118    /// 4x4 ブロック `li` を含む変換ブロックに 0 でない係数があるか(デブロックの bS = 2)。
1119    /// 8x8 変換の MB では 8x8 単位で見る(8.7.2.1)。
1120    fn has_coeffs(&self, li: usize) -> bool {
1121        let (bx, by) = (li % self.blk_w, li / self.blk_w);
1122        if self.mb_t8[(by / 4) * self.mb_w + bx / 4] {
1123            let (x0, y0) = (bx & !1, by & !1);
1124            [(0, 0), (1, 0), (0, 1), (1, 1)].iter().any(|&(dx, dy)| self.nnz_luma[self.li(x0 + dx, y0 + dy)] > 0)
1125        } else {
1126            self.nnz_luma[li] > 0
1127        }
1128    }
1129
1130    /// 輝度画素 (x, y) を含む MB が画面内にあり、復号中のスライスに属するか(6.4 の可用性)。
1131    /// 【2026-10-17】複数スライス対応。従来は画面内かどうかだけで判定しており、前のスライスの
1132    /// MB まで予測(イントラ・MV・nC)に使っていた。
1133    fn avail_px(&self, x: isize, y: isize) -> bool {
1134        if x < 0 || y < 0 || x >= self.yuv.width as isize || y >= self.yuv.height as isize {
1135            return false;
1136        }
1137        let mb = (y as usize / 16) * self.mb_w + x as usize / 16;
1138        self.mb_slice.get(mb).is_some_and(|&s| s == self.cur_slice)
1139    }
1140
1141    /// 輝度 4x4 ブロック (bx4,by4) が可用か(所属 MB が同じスライス)。
1142    fn avail_blk(&self, bx4: isize, by4: isize) -> bool {
1143        self.avail_px(bx4 * 4, by4 * 4)
1144    }
1145
1146    /// 輝度 4x4 ブロック (bx4,by4) の nC(左/上ブロックの nnz から)。
1147    fn luma_nc(&self, bx4: usize, by4: usize) -> i32 {
1148        let na = if self.avail_blk(bx4 as isize - 1, by4 as isize) {
1149            self.nnz_luma[self.li(bx4 - 1, by4)] as i32
1150        } else {
1151            -1
1152        };
1153        let nb = if self.avail_blk(bx4 as isize, by4 as isize - 1) {
1154            self.nnz_luma[self.li(bx4, by4 - 1)] as i32
1155        } else {
1156            -1
1157        };
1158        combine_nc(na, nb)
1159    }
1160
1161    /// Intra4x4 予測モードの予測値(左/上ブロックのモードの min)。
1162    fn pred_i4x4_mode(&self, bx4: usize, by4: usize) -> i32 {
1163        if !self.avail_blk(bx4 as isize - 1, by4 as isize) || !self.avail_blk(bx4 as isize, by4 as isize - 1) {
1164            return 2;
1165        } // どちらか不可用 -> DC
1166        let ma = self.mode_luma[self.li(bx4 - 1, by4)];
1167        let mb = self.mode_luma[self.li(bx4, by4 - 1)];
1168        let ma = if ma >= 0 { ma as i32 } else { 2 };
1169        let mb = if mb >= 0 { mb as i32 } else { 2 };
1170        ma.min(mb)
1171    }
1172
1173    /// 輝度 4x4 ブロックの近傍サンプルを収集し intra_4x4_predict で予測を返す。
1174    /// px,py はフレーム画素座標(ブロック左上)。
1175    fn predict_luma_4x4(&self, px: usize, py: usize, mode: u8) -> [i32; 16] {
1176        let w = self.yuv.width as usize;
1177        let top_avail = self.avail_px(px as isize, py as isize - 1);
1178        let left_avail = self.avail_px(px as isize - 1, py as isize);
1179        let bx4 = px / 4;
1180        let by4 = py / 4;
1181        let mut top = [0i32; 8];
1182        let mut left = [0i32; 4];
1183        let mut corner = 0i32;
1184        if top_avail {
1185            for x in 0..4 {
1186                top[x] = self.yuv.y[(py - 1) * w + px + x] as i32;
1187            }
1188            // top-right: 右上 4x4 ブロックが再構成済みかつフレーム内なら使用、無ければ top[3] を複製
1189            let tr_ok = by4 > 0
1190                && bx4 + 1 < self.blk_w
1191                && self.decoded[self.li(bx4 + 1, by4 - 1)]
1192                && self.avail_blk(bx4 as isize + 1, by4 as isize - 1);
1193            if tr_ok {
1194                for x in 0..4 {
1195                    top[4 + x] = self.yuv.y[(py - 1) * w + px + 4 + x] as i32;
1196                }
1197            } else {
1198                for x in 0..4 {
1199                    top[4 + x] = top[3];
1200                }
1201            }
1202        }
1203        if left_avail {
1204            for y in 0..4 {
1205                left[y] = self.yuv.y[(py + y) * w + px - 1] as i32;
1206            }
1207        }
1208        if top_avail && left_avail {
1209            corner = self.yuv.y[(py - 1) * w + px - 1] as i32;
1210        }
1211        intra_4x4_predict(mode, &top, corner, &left, top_avail, left_avail)
1212    }
1213}
1214
1215/// 単一スライス(I または P、IDR/非IDR、4:2:0、CAVLC)をデコードして YUV 平面を返す。
1216/// reference は P スライスの動き補償に使う直前のフレーム。
1217pub fn decode_slice(
1218    sps: &Sps,
1219    pps: &Pps,
1220    rbsp: &[u8],
1221    is_idr: bool,
1222    reference: Option<&DecodedYuv>,
1223) -> Result<DecodedYuv, &'static str> {
1224    let refs: Vec<&DecodedYuv> = reference.into_iter().collect();
1225    decode_slice_refs(sps, pps, rbsp, is_idr, 1, &refs)
1226}
1227
1228/// 1 スライス=1 フレームのときの簡易版(`decode_picture` に 1 スライスを渡す)。
1229pub fn decode_slice_refs(
1230    sps: &Sps,
1231    pps: &Pps,
1232    rbsp: &[u8],
1233    is_idr: bool,
1234    nal_ref_idc: u8,
1235    refs: &[&DecodedYuv],
1236) -> Result<DecodedYuv, &'static str> {
1237    decode_picture(sps, pps, &[rbsp], is_idr, nal_ref_idc, refs)
1238}
1239
1240/// スライスの `first_mb_in_slice`(先頭の ue(v))。0 なら新しいフレームの始まり。
1241pub fn slice_first_mb(rbsp: &[u8]) -> u32 {
1242    BitReader::new(rbsp).read_ue()
1243}
1244
1245/// MB ごとのデブロック設定(スライスヘッダの値)。
1246#[derive(Clone, Copy, Default)]
1247struct MbDeblock {
1248    disable_idc: u32,
1249    alpha_off: i32,
1250    beta_off: i32,
1251}
1252
1253/// 1 フレーム(1 つ以上のスライス)を復号する。`slices` は同じフレームのスライスの RBSP を
1254/// 並び順に渡す。`refs` は RefPicList0(先頭が refIdx 0)。並べ替え無しの P では短期参照を
1255/// PicNum の降順=**新しい順**に並べたもの(8.2.4.2.1)。呼び出し側が DPB を持つ(`RefFrames`)。
1256/// `nal_ref_idc` が 0 なら dec_ref_pic_marking は無い(7.3.3)。
1257/// 【2026-10-17】複数スライスに対応。従来は 1 スライス=1 フレーム前提で、2 枚目以降のスライスが
1258/// 別フレームとして組まれていた(`first_mb_in_slice` も読み捨て)。
1259pub fn decode_picture(
1260    sps: &Sps,
1261    pps: &Pps,
1262    slices: &[&[u8]],
1263    is_idr: bool,
1264    nal_ref_idc: u8,
1265    refs: &[&DecodedYuv],
1266) -> Result<DecodedYuv, &'static str> {
1267    // 識別子は並び順から作る(POC を持たない呼び出し側用。B は `RefFrames::decode` を使う)。
1268    let p: Vec<RefEntry> = refs
1269        .iter()
1270        .enumerate()
1271        .map(|(i, f)| RefEntry { yuv: f, id: -(i as i32) - 1, pic_num: -(i as i32) - 1, motion: None })
1272        .collect();
1273    let lists = RefLists { b: [p.clone(), Vec::new()], p, cur_poc: 0 };
1274    Ok(decode_picture_lists(sps, pps, slices, is_idr, nal_ref_idc, &lists)?.0)
1275}
1276
1277/// `decode_picture` の本体。復号したフレームと、その動き情報(次の B の co-located 用)を返す。
1278fn decode_picture_lists(
1279    sps: &Sps,
1280    pps: &Pps,
1281    slices: &[&[u8]],
1282    is_idr: bool,
1283    nal_ref_idc: u8,
1284    lists: &RefLists,
1285) -> Result<(DecodedYuv, Motion), &'static str> {
1286    // 不正/巨大 SPS による OOM・範囲外を防ぐ寸法サニティ(1080p=120x68 MB なので 300 で十分)。
1287    if sps.width_mbs == 0 || sps.height_mbs == 0 || sps.width_mbs > 300 || sps.height_mbs > 300 {
1288        return Err("invalid frame dimensions");
1289    }
1290    let width = sps.width_mbs * 16;
1291    let height = sps.height_mbs * 16;
1292    let cw = width / 2;
1293    let ch = height / 2;
1294    let yuv = DecodedYuv {
1295        width,
1296        height,
1297        y: alloc::vec![0u8; (width * height) as usize],
1298        u: alloc::vec![128u8; (cw * ch) as usize],
1299        v: alloc::vec![128u8; (cw * ch) as usize],
1300    };
1301    let blk_w = (width / 4) as usize;
1302    let blk_h = (height / 4) as usize;
1303    let cblk_w = (cw / 4) as usize;
1304    let cblk_h = (ch / 4) as usize;
1305    let mut st = DecState {
1306        yuv,
1307        blk_w,
1308        nnz_luma: alloc::vec![0u8; blk_w * blk_h],
1309        mode_luma: alloc::vec![-1i8; blk_w * blk_h],
1310        decoded: alloc::vec![false; blk_w * blk_h],
1311        cblk_w,
1312        nnz_chroma: [
1313            alloc::vec![0u8; cblk_w * cblk_h],
1314            alloc::vec![0u8; cblk_w * cblk_h],
1315        ],
1316        mb_w: sps.width_mbs as usize,
1317        mb_qp: alloc::vec![0i32; (sps.width_mbs * sps.height_mbs) as usize],
1318        mv: [alloc::vec![[0i16; 2]; blk_w * blk_h], alloc::vec![[0i16; 2]; blk_w * blk_h]],
1319        is_inter: alloc::vec![false; blk_w * blk_h],
1320        blk_h,
1321        refi: [alloc::vec![-1i8; blk_w * blk_h], alloc::vec![-1i8; blk_w * blk_h]],
1322        refp: [alloc::vec![0i32; blk_w * blk_h], alloc::vec![0i32; blk_w * blk_h]],
1323        num_ref: [1, 1],
1324        mb_slice: alloc::vec![0u32; (sps.width_mbs * sps.height_mbs) as usize],
1325        cur_slice: 0,
1326        mb_db: alloc::vec![MbDeblock::default(); (sps.width_mbs * sps.height_mbs) as usize],
1327        mb_t8: alloc::vec![false; (sps.width_mbs * sps.height_mbs) as usize],
1328        wp: WpMode::Off,
1329        cur_poc: lists.cur_poc,
1330    };
1331    if slices.is_empty() {
1332        return Err("no slices");
1333    }
1334    for (i, rbsp) in slices.iter().enumerate() {
1335        // スライスごとに番号を振り、隣のスライスの MB は予測に使わない(可用性は `mb_slice`)。
1336        st.cur_slice = i as u32 + 1;
1337        st.decoded.fill(false);
1338        decode_slice_into(&mut st, sps, pps, rbsp, is_idr, nal_ref_idc, lists)?;
1339    }
1340    // ---- デブロッキングフィルタ(全スライスを組み終えてから。設定は MB ごと)----
1341    deblock_frame(
1342        &mut st,
1343        sps.width_mbs as usize,
1344        sps.height_mbs as usize,
1345        pps.chroma_qp_offsets(),
1346    );
1347    let motion = Motion { blk_w: st.blk_w, mv: st.mv, refi: st.refi };
1348    Ok((st.yuv, motion))
1349}
1350
1351/// 1 スライスを `st` へ組み込む(`first_mb_in_slice` から `more_rbsp_data()` が尽きるまで)。
1352#[allow(clippy::too_many_arguments)]
1353fn decode_slice_into(
1354    st: &mut DecState,
1355    sps: &Sps,
1356    pps: &Pps,
1357    rbsp: &[u8],
1358    is_idr: bool,
1359    nal_ref_idc: u8,
1360    lists: &RefLists,
1361) -> Result<(), &'static str> {
1362    let width = st.yuv.width;
1363    let height = st.yuv.height;
1364
1365    let mut r = BitReader::new(rbsp);
1366
1367    // ---- スライスヘッダ(`h264_slice::parse_slice_header`)----
1368    let h = parse_slice_header(&mut r, sps, pps, is_idr, nal_ref_idc)?;
1369    let first_mb = h.first_mb as usize;
1370    let is_i = h.kind == SliceKind::I;
1371    // このスライスの参照リスト: 初期リストを num_ref_idx_active で切り、並べ替えを当てる。
1372    let max_frame_num = 1i32 << sps.log2_max_frame_num;
1373    let mut l0: Vec<RefEntry> = if h.kind == SliceKind::B { lists.b[0].clone() } else { lists.p.clone() };
1374    let mut l1: Vec<RefEntry> = lists.b[1].clone();
1375    for (list, l) in [&mut l0, &mut l1].into_iter().enumerate() {
1376        let n = h.num_ref_idx_active[list] as usize;
1377        l.truncate(n);
1378        if !h.list_mod[list].is_empty() {
1379            modify_ref_list(l, &h.list_mod[list], &lists.p, h.frame_num, max_frame_num, n)?;
1380        }
1381    }
1382    let refs: &[RefEntry] = &l0;
1383    let refs_l1: &[RefEntry] = &l1;
1384    if h.kind == SliceKind::B {
1385        if !h.direct_spatial_mv_pred {
1386            return Err("temporal direct not supported");
1387        }
1388        if refs.is_empty() || refs_l1.is_empty() {
1389            return Err("B slice without reference frames");
1390        }
1391        // 空間ダイレクトは RefPicList1[0] の動き情報を見る。
1392        if refs_l1[0].motion.is_none() {
1393            return Err("B slice needs co-located motion");
1394        }
1395    }
1396    if h.kind != SliceKind::I {
1397        // 参照フレームが存在し、解像度が一致していることを検証する(パニック防止)。
1398        if refs.is_empty() {
1399            return Err("P slice without reference frame");
1400        }
1401        if refs.iter().chain(refs_l1.iter()).any(|f| f.yuv.width != width || f.yuv.height != height) {
1402            return Err("reference dimension mismatch");
1403        }
1404        st.num_ref = h.num_ref_idx_active;
1405        if h.kind == SliceKind::P && st.num_ref[0] as usize > refs.len() {
1406            return Err("not enough reference frames");
1407        }
1408    }
1409    st.wp = match (&h.pwt, h.kind) {
1410        (Some(t), _) => WpMode::Explicit(t.clone()),
1411        (None, SliceKind::B) if pps.weighted_bipred_idc == 2 => WpMode::Implicit,
1412        _ => WpMode::Off,
1413    };
1414    let slice_qp = h.slice_qp;
1415    let disable_deblock_idc = h.disable_deblocking_filter_idc;
1416    let alpha_off = h.slice_alpha_c0_offset;
1417    let beta_off = h.slice_beta_offset;
1418
1419    // ---- マクロブロック ----
1420    let mb_w = sps.width_mbs;
1421    let mb_h = sps.height_mbs;
1422    let total_mb = (mb_w * mb_h) as usize;
1423    if first_mb >= total_mb {
1424        return Err("first_mb_in_slice out of range");
1425    }
1426    let mut qp = slice_qp;
1427    let db = MbDeblock { disable_idc: disable_deblock_idc, alpha_off, beta_off };
1428    let slice_id = st.cur_slice;
1429    // MB の復号を始める前に、所属スライスとデブロック設定を記録する。
1430    let begin_mb = |st: &mut DecState, mb_idx: usize| {
1431        if let Some(m) = st.mb_slice.get_mut(mb_idx) {
1432            *m = slice_id;
1433        }
1434        if let Some(m) = st.mb_db.get_mut(mb_idx) {
1435            *m = db;
1436        }
1437    };
1438
1439    if pps.entropy_coding_mode_flag != 0 {
1440        // ---- CABAC(9.3): MB ごとに(mb_skip_flag)→ MB → end_of_slice_flag ----
1441        let init = if is_i { None } else { Some(h.cabac_init_idc) };
1442        let engine = CabacEngine::new(rbsp, r.bit_position(), slice_qp, init);
1443        let mut cr = CabacReader::new(engine, st);
1444        let ctx = BCtx { l: [refs, refs_l1], direct_8x8: sps.direct_8x8_inference_flag };
1445        let mut mb_idx = first_mb;
1446        while mb_idx < total_mb {
1447            let mb_x = (mb_idx as u32 % mb_w) * 16;
1448            let mb_y = (mb_idx as u32 / mb_w) * 16;
1449            begin_mb(st, mb_idx);
1450            cr.start_mb(mb_idx);
1451            let res = if !is_i && cr.mb_skip(st, mb_x, mb_y, h.kind) {
1452                if h.kind == SliceKind::B {
1453                    cr.mark_direct(mb_x as usize, mb_y as usize, 16, 16);
1454                    let r = decode_b_direct_mb(st, mb_x, mb_y, &ctx);
1455                    st.mb_qp[mb_idx] = qp;
1456                    r
1457                } else {
1458                    decode_pskip(st, mb_x, mb_y, qp, refs);
1459                    Ok(())
1460                }
1461            } else {
1462                match h.kind {
1463                    SliceKind::I => decode_mb(&mut cr, st, mb_x, mb_y, &mut qp, pps),
1464                    SliceKind::P => decode_mb_p(&mut cr, st, mb_x, mb_y, &mut qp, pps, refs),
1465                    SliceKind::B => decode_mb_b(&mut cr, st, mb_x, mb_y, &mut qp, pps, &ctx),
1466                }
1467            };
1468            if let Err(e) = res {
1469                crate::println!("  H264 CABAC decode stopped at mb {} (of {}): {}", mb_idx, total_mb, e);
1470                return Err(e);
1471            }
1472            mb_idx += 1;
1473            if cr.e.terminate() == 1 {
1474                break;
1475            }
1476        }
1477    } else if is_i {
1478        let mut mb_idx = first_mb;
1479        while mb_idx < total_mb {
1480            let mb_x = (mb_idx as u32 % mb_w) * 16;
1481            let mb_y = (mb_idx as u32 / mb_w) * 16;
1482            begin_mb(st, mb_idx);
1483            if let Err(e) = decode_mb(&mut r, st, mb_x, mb_y, &mut qp, pps) {
1484                crate::println!(
1485                    "  H264 decode stopped at mb {} (of {}): {}",
1486                    mb_idx,
1487                    total_mb,
1488                    e
1489                );
1490                return Err(e);
1491            }
1492            mb_idx += 1;
1493            if !r.more_rbsp_data() {
1494                break;
1495            }
1496        }
1497    } else if h.kind == SliceKind::B {
1498        // B スライス: P と同じく mb_skip_run(B_Skip)→ 符号化 MB。
1499        let ctx = BCtx { l: [refs, refs_l1], direct_8x8: sps.direct_8x8_inference_flag };
1500        let mut mb_idx = first_mb;
1501        while mb_idx < total_mb {
1502            let skip_run = r.read_ue() as usize;
1503            for _ in 0..skip_run {
1504                if mb_idx >= total_mb {
1505                    break;
1506                }
1507                let mb_x = (mb_idx as u32 % mb_w) * 16;
1508                let mb_y = (mb_idx as u32 / mb_w) * 16;
1509                begin_mb(st, mb_idx);
1510                decode_b_direct_mb(st, mb_x, mb_y, &ctx)?;
1511                st.mb_qp[mb_idx] = qp;
1512                mb_idx += 1;
1513            }
1514            if mb_idx >= total_mb || !r.more_rbsp_data() {
1515                break;
1516            }
1517            let mb_x = (mb_idx as u32 % mb_w) * 16;
1518            let mb_y = (mb_idx as u32 / mb_w) * 16;
1519            begin_mb(st, mb_idx);
1520            if let Err(e) = decode_mb_b(&mut r, st, mb_x, mb_y, &mut qp, pps, &ctx) {
1521                crate::println!("  H264 B decode stopped at mb {} (of {}): {}", mb_idx, total_mb, e);
1522                return Err(e);
1523            }
1524            mb_idx += 1;
1525            if !r.more_rbsp_data() {
1526                break;
1527            }
1528        }
1529    } else {
1530        // P スライス: mb_skip_run で連続スキップ、その後 1 つの符号化 MB。
1531        // アンラップを安全な ok_or と ? 演算子に置き換え、参照フレーム不足時に早期エラー返却します。
1532        let mut mb_idx = first_mb;
1533        while mb_idx < total_mb {
1534            let skip_run = r.read_ue() as usize;
1535            for _ in 0..skip_run {
1536                if mb_idx >= total_mb {
1537                    break;
1538                }
1539                let mb_x = (mb_idx as u32 % mb_w) * 16;
1540                let mb_y = (mb_idx as u32 / mb_w) * 16;
1541                begin_mb(st, mb_idx);
1542                decode_pskip(st, mb_x, mb_y, qp, refs);
1543                mb_idx += 1;
1544            }
1545            // スキップの後にデータが無ければスライス終わり(7.3.4)。
1546            if mb_idx >= total_mb || !r.more_rbsp_data() {
1547                break;
1548            }
1549            let mb_x = (mb_idx as u32 % mb_w) * 16;
1550            let mb_y = (mb_idx as u32 / mb_w) * 16;
1551            begin_mb(st, mb_idx);
1552            if let Err(e) = decode_mb_p(&mut r, st, mb_x, mb_y, &mut qp, pps, refs) {
1553                crate::println!(
1554                    "  H264 P decode stopped at mb {} (of {}): {}",
1555                    mb_idx,
1556                    total_mb,
1557                    e
1558                );
1559                return Err(e);
1560            }
1561            mb_idx += 1;
1562            if !r.more_rbsp_data() {
1563                break;
1564            }
1565        }
1566    }
1567    Ok(())
1568
1569}
1570
1571// ===================== MB 層の構文要素の読み手 =====================
1572//
1573// 【2026-10-17】CABAC 対応のため、MB 層の読み取りをトレイトにまとめた。予測・再構成のコードは
1574// CAVLC(`BitReader`)と CABAC(`CabacReader`)で共通。CABAC は近傍の情報で文脈を選ぶので、
1575// 各メソッドは `DecState`(可用性・nnz・refIdx)と位置を受け取る。
1576
1577/// 残差ブロックの種類と位置(CAVLC は nC、CABAC は coded_block_flag の文脈に使う)。
1578#[derive(Clone, Copy)]
1579enum ResBlock {
1580    /// Intra16x16 の輝度 DC(MB 左上の 4x4 位置)。
1581    LumaDc { bx4: usize, by4: usize },
1582    /// Intra16x16 の輝度 AC(15 係数)。
1583    LumaAc { bx4: usize, by4: usize },
1584    /// 輝度 4x4(16 係数)。
1585    Luma4x4 { bx4: usize, by4: usize },
1586    /// クロマ DC(2x2、MB 左上の輝度画素位置)。
1587    ChromaDc { comp: usize, mb_x: u32, mb_y: u32 },
1588    /// クロマ AC(15 係数、クロマ 4x4 位置)。
1589    ChromaAc { comp: usize, cbx4: usize, cby4: usize },
1590}
1591
1592/// MB 層の構文要素の読み手。値の番号付けは CAVLC(表 7-11/7-13/7-14/7-17/7-18)に揃える。
1593#[allow(clippy::too_many_arguments)]
1594trait MbSyntax {
1595    fn mb_type(&mut self, st: &DecState, mb_x: u32, mb_y: u32, kind: SliceKind) -> Result<u32, &'static str>;
1596    fn sub_mb_type(&mut self, kind: SliceKind) -> Result<u32, &'static str>;
1597    /// prev_intra4x4_pred_mode_flag が 1 なら None、0 なら rem_intra4x4_pred_mode。
1598    fn intra4x4_rem(&mut self) -> Option<i32>;
1599    fn intra_chroma_mode(&mut self, st: &DecState, mb_x: u32, mb_y: u32) -> Result<u8, &'static str>;
1600    fn cbp(&mut self, st: &DecState, mb_x: u32, mb_y: u32, intra: bool) -> Result<u32, &'static str>;
1601    fn qp_delta(&mut self) -> Result<i32, &'static str>;
1602    /// パーティション (px, py, pw, ph) の ref_idx_lX。
1603    fn ref_idx(&mut self, st: &DecState, list: usize, px: usize, py: usize, pw: usize, ph: usize, num_ref: u32) -> Result<i8, &'static str>;
1604    /// パーティション (px, py, pw, ph) の mvd_lX。
1605    fn mvd(&mut self, st: &DecState, list: usize, px: usize, py: usize, pw: usize, ph: usize) -> Result<[i16; 2], &'static str>;
1606    /// 残差 1 ブロック(走査順の係数, 非ゼロ係数の数)。
1607    fn residual(&mut self, st: &DecState, blk: ResBlock) -> Result<([i32; 16], u8), &'static str>;
1608    /// I_PCM の 384 バイト(Y 256・Cb 64・Cr 64)。
1609    fn pcm(&mut self, out: &mut [u8; 384]);
1610    /// transform_size_8x8_flag。
1611    fn transform_8x8(&mut self, st: &DecState, mb_x: u32, mb_y: u32) -> bool;
1612    /// 輝度 8x8 の残差(左上の 4x4 位置)。(8x8 走査順の係数 64 個, 4 つの 4x4 ごとの係数の数)。
1613    fn residual8x8(&mut self, st: &DecState, bx4: usize, by4: usize) -> Result<([i32; 64], [u8; 4]), &'static str>;
1614    /// B_Skip / B_Direct の領域を記録する(CABAC の ref_idx の文脈用)。
1615    fn mark_direct(&mut self, _px: usize, _py: usize, _pw: usize, _ph: usize) {}
1616}
1617
1618impl MbSyntax for BitReader<'_> {
1619    fn mb_type(&mut self, _st: &DecState, _mb_x: u32, _mb_y: u32, _kind: SliceKind) -> Result<u32, &'static str> {
1620        Ok(self.read_ue())
1621    }
1622    fn sub_mb_type(&mut self, _kind: SliceKind) -> Result<u32, &'static str> {
1623        Ok(self.read_ue())
1624    }
1625    fn intra4x4_rem(&mut self) -> Option<i32> {
1626        if self.read_bit() == 1 {
1627            None
1628        } else {
1629            Some(self.read_bits(3) as i32)
1630        }
1631    }
1632    fn intra_chroma_mode(&mut self, _st: &DecState, _mb_x: u32, _mb_y: u32) -> Result<u8, &'static str> {
1633        let m = self.read_ue();
1634        if m > 3 {
1635            return Err("bad intra_chroma_pred_mode");
1636        }
1637        Ok(m as u8)
1638    }
1639    fn cbp(&mut self, _st: &DecState, _mb_x: u32, _mb_y: u32, intra: bool) -> Result<u32, &'static str> {
1640        let codenum = self.read_ue() as usize;
1641        let map = if intra { &CBP_INTRA_MAP } else { &CBP_INTER_MAP };
1642        Ok(*map.get(codenum).ok_or("cbp codeNum out of range")? as u32)
1643    }
1644    fn qp_delta(&mut self) -> Result<i32, &'static str> {
1645        Ok(self.read_se())
1646    }
1647    fn ref_idx(&mut self, _st: &DecState, _list: usize, _px: usize, _py: usize, _pw: usize, _ph: usize, num_ref: u32) -> Result<i8, &'static str> {
1648        read_ref_idx(self, num_ref)
1649    }
1650    fn mvd(&mut self, _st: &DecState, _list: usize, _px: usize, _py: usize, _pw: usize, _ph: usize) -> Result<[i16; 2], &'static str> {
1651        Ok([self.read_se() as i16, self.read_se() as i16])
1652    }
1653    fn residual(&mut self, st: &DecState, blk: ResBlock) -> Result<([i32; 16], u8), &'static str> {
1654        Ok(match blk {
1655            ResBlock::LumaDc { bx4, by4 } => residual_block_cavlc(self, 16, st.luma_nc(bx4, by4), TOTAL_ZEROS_16),
1656            ResBlock::LumaAc { bx4, by4 } => residual_block_cavlc(self, 15, st.luma_nc(bx4, by4), TOTAL_ZEROS_16),
1657            ResBlock::Luma4x4 { bx4, by4 } => residual_block_cavlc(self, 16, st.luma_nc(bx4, by4), TOTAL_ZEROS_16),
1658            ResBlock::ChromaDc { .. } => residual_block_cavlc(self, 4, -1, TOTAL_ZEROS_CHROMA),
1659            ResBlock::ChromaAc { comp, cbx4, cby4 } => {
1660                residual_block_cavlc(self, 15, chroma_nc(st, comp, cbx4, cby4), TOTAL_ZEROS_16)
1661            }
1662        })
1663    }
1664    fn pcm(&mut self, out: &mut [u8; 384]) {
1665        self.byte_align();
1666        for o in out.iter_mut() {
1667            *o = self.read_bits(8) as u8;
1668        }
1669    }
1670    fn transform_8x8(&mut self, _st: &DecState, _mb_x: u32, _mb_y: u32) -> bool {
1671        self.read_bit() == 1
1672    }
1673    /// CAVLC の 8x8 は 4x4 の residual_block を 4 回読み、係数を交互に並べる(7.3.5.3.2:
1674    /// level8x8[4 * k + i] = level4x4_i[k])。nC は 4x4 ごとで、同じ 8x8 の先に読んだ 4x4 の
1675    /// total_coeff も使う。
1676    fn residual8x8(&mut self, st: &DecState, bx4: usize, by4: usize) -> Result<([i32; 64], [u8; 4]), &'static str> {
1677        let mut coeff = [0i32; 64];
1678        let mut cnt = [0u8; 4];
1679        for i in 0..4 {
1680            let (x, y) = ((bx4 + i % 2) as isize, (by4 + i / 2) as isize);
1681            let nnz_at = |xx: isize, yy: isize, cnt: &[u8; 4]| -> i32 {
1682                if xx >= bx4 as isize && yy >= by4 as isize {
1683                    cnt[(yy as usize - by4) * 2 + (xx as usize - bx4)] as i32
1684                } else if st.avail_blk(xx, yy) {
1685                    st.nnz_luma[st.li(xx as usize, yy as usize)] as i32
1686                } else {
1687                    -1
1688                }
1689            };
1690            let nc = combine_nc(nnz_at(x - 1, y, &cnt), nnz_at(x, y - 1, &cnt));
1691            let (scan, tc) = residual_block_cavlc(self, 16, nc, TOTAL_ZEROS_16);
1692            cnt[i] = tc;
1693            for k in 0..16 {
1694                coeff[4 * k + i] = scan[k];
1695            }
1696        }
1697        Ok((coeff, cnt))
1698    }
1699}
1700
1701// MB の種類のフラグ(CABAC の文脈選択用)。
1702const MBF_INTRA: u8 = 1;
1703const MBF_I16: u8 = 2;
1704const MBF_PCM: u8 = 4;
1705const MBF_DIRECT16: u8 = 8;
1706const MBF_SKIP: u8 = 16;
1707
1708/// CABAC の MB 層の読み手(9.3.2 二値化・9.3.3.1.1 文脈選択)。近傍の情報は同じスライスの
1709/// MB だけを見る(可用性は `DecState::avail_px`)。スライスごとに作り直す。
1710struct CabacReader<'a> {
1711    e: CabacEngine<'a>,
1712    blk_w: usize,
1713    mb_w: usize,
1714    cur_mb: usize,
1715    cur_intra: bool,
1716    /// MB ごと: MBF_* フラグ・coded_block_pattern・DC の coded_block_flag(bit0 輝度, bit1 Cb, bit2 Cr)・
1717    /// intra_chroma_pred_mode。
1718    mb_flags: Vec<u8>,
1719    mb_cbp: Vec<u8>,
1720    mb_dc: Vec<u8>,
1721    mb_cpm: Vec<u8>,
1722    prev_dqp: i32,
1723    cur_dqp: i32,
1724    /// 4x4 ごと: |mvd|(成分ごと、70 で頭打ち)・refIdx > 0 か・ダイレクト予測か。
1725    mvd: [Vec<[u8; 2]>; 2],
1726    ref_gt0: [Vec<bool>; 2],
1727    direct: Vec<bool>,
1728}
1729
1730impl<'a> CabacReader<'a> {
1731    fn new(e: CabacEngine<'a>, st: &DecState) -> Self {
1732        let n_mb = st.mb_slice.len();
1733        let n_blk = st.blk_w * st.blk_h;
1734        Self {
1735            e,
1736            blk_w: st.blk_w,
1737            mb_w: st.mb_w,
1738            cur_mb: 0,
1739            cur_intra: false,
1740            mb_flags: alloc::vec![0; n_mb],
1741            mb_cbp: alloc::vec![0; n_mb],
1742            mb_dc: alloc::vec![0; n_mb],
1743            mb_cpm: alloc::vec![0; n_mb],
1744            prev_dqp: 0,
1745            cur_dqp: 0,
1746            mvd: [alloc::vec![[0; 2]; n_blk], alloc::vec![[0; 2]; n_blk]],
1747            ref_gt0: [alloc::vec![false; n_blk], alloc::vec![false; n_blk]],
1748            direct: alloc::vec![false; n_blk],
1749        }
1750    }
1751
1752    /// MB の復号を始める(mb_qp_delta の文脈は直前の MB の値)。
1753    fn start_mb(&mut self, mb_idx: usize) {
1754        self.cur_mb = mb_idx;
1755        self.prev_dqp = self.cur_dqp;
1756        self.cur_dqp = 0;
1757        self.cur_intra = false;
1758    }
1759
1760    /// 左(A)・上(B)の MB 番号(同じスライスで可用なときだけ)。
1761    fn nb_mbs(&self, st: &DecState, mb_x: u32, mb_y: u32) -> (Option<usize>, Option<usize>) {
1762        let a = st.avail_px(mb_x as isize - 1, mb_y as isize).then(|| self.cur_mb - 1);
1763        let b = st.avail_px(mb_x as isize, mb_y as isize - 1).then(|| self.cur_mb - self.mb_w);
1764        (a, b)
1765    }
1766
1767    /// mb_skip_flag(P: ctxIdx 11..13、B: 24..26)。真なら P_Skip / B_Skip として記録する。
1768    fn mb_skip(&mut self, st: &DecState, mb_x: u32, mb_y: u32, kind: SliceKind) -> bool {
1769        let (a, b) = self.nb_mbs(st, mb_x, mb_y);
1770        let cond = |m: Option<usize>| m.is_some_and(|m| self.mb_flags[m] & MBF_SKIP == 0) as usize;
1771        let base = if kind == SliceKind::B { 24 } else { 11 };
1772        let skip = self.e.decision(base + cond(a) + cond(b)) == 1;
1773        if skip {
1774            self.mb_flags[self.cur_mb] = MBF_SKIP | if kind == SliceKind::B { MBF_DIRECT16 } else { 0 };
1775            self.mb_cbp[self.cur_mb] = 0;
1776        }
1777        skip
1778    }
1779
1780    /// イントラの mb_type(表 9-36)。`base` は I スライスで 3、P で 17、B で 32。
1781    fn intra_mb_type(&mut self, base: usize, intra_slice: bool, inc: usize) -> u32 {
1782        let mut b = base;
1783        if intra_slice {
1784            if self.e.decision(base + inc) == 0 {
1785                return 0;
1786            }
1787            b += 2;
1788        } else if self.e.decision(base) == 0 {
1789            return 0;
1790        }
1791        if self.e.terminate() == 1 {
1792            return 25;
1793        }
1794        let is = intra_slice as usize;
1795        let mut t = 1 + 12 * self.e.decision(b + 1);
1796        if self.e.decision(b + 2) == 1 {
1797            t += 4 + 4 * self.e.decision(b + 2 + is);
1798        }
1799        t += 2 * self.e.decision(b + 3 + is);
1800        t += self.e.decision(b + 3 + 2 * is);
1801        t
1802    }
1803
1804    /// 1 つの mvd 成分(UEG3、uCoff = 9、符号付き)。`base` は x で 40、y で 47。
1805    fn mvd_comp(&mut self, base: usize, amvd: u32) -> Result<(i16, u8), &'static str> {
1806        let inc = if amvd < 3 { 0 } else if amvd > 32 { 2 } else { 1 };
1807        if self.e.decision(base + inc) == 0 {
1808            return Ok((0, 0));
1809        }
1810        let mut mvd: u32 = 1;
1811        let mut ctx = base + 3;
1812        while mvd < 9 && self.e.decision(ctx) == 1 {
1813            if mvd < 4 {
1814                ctx += 1;
1815            }
1816            mvd += 1;
1817        }
1818        if mvd >= 9 {
1819            let mut k = 3;
1820            while self.e.bypass() == 1 {
1821                mvd += 1 << k;
1822                k += 1;
1823                if k > 24 {
1824                    return Err("mvd overflow");
1825                }
1826            }
1827            while k > 0 {
1828                k -= 1;
1829                mvd += self.e.bypass() << k;
1830            }
1831        }
1832        let abs = mvd.min(70) as u8;
1833        let v = mvd.min(i16::MAX as u32) as i16;
1834        Ok((if self.e.bypass() == 1 { -v } else { v }, abs))
1835    }
1836
1837    /// 4x4 ブロック (bx4, by4) が同じスライスで可用か。
1838    fn blk_ok(st: &DecState, bx4: isize, by4: isize) -> bool {
1839        bx4 >= 0 && by4 >= 0 && st.avail_blk(bx4, by4)
1840    }
1841}
1842
1843impl MbSyntax for CabacReader<'_> {
1844    fn mb_type(&mut self, st: &DecState, mb_x: u32, mb_y: u32, kind: SliceKind) -> Result<u32, &'static str> {
1845        let (a, b) = self.nb_mbs(st, mb_x, mb_y);
1846        let t = match kind {
1847            SliceKind::I => {
1848                let c = |m: Option<usize>| m.is_some_and(|m| self.mb_flags[m] & (MBF_I16 | MBF_PCM) != 0) as usize;
1849                let inc = c(a) + c(b);
1850                self.intra_mb_type(3, true, inc)
1851            }
1852            SliceKind::P => {
1853                if self.e.decision(14) == 0 {
1854                    if self.e.decision(15) == 0 {
1855                        3 * self.e.decision(16)
1856                    } else {
1857                        2 - self.e.decision(17)
1858                    }
1859                } else {
1860                    5 + self.intra_mb_type(17, false, 0)
1861                }
1862            }
1863            SliceKind::B => {
1864                let c = |m: Option<usize>| m.is_some_and(|m| self.mb_flags[m] & MBF_DIRECT16 == 0) as usize;
1865                let inc = c(a) + c(b);
1866                if self.e.decision(27 + inc) == 0 {
1867                    0
1868                } else if self.e.decision(27 + 3) == 0 {
1869                    1 + self.e.decision(27 + 5)
1870                } else {
1871                    let mut bits = self.e.decision(27 + 4) << 3;
1872                    bits |= self.e.decision(27 + 5) << 2;
1873                    bits |= self.e.decision(27 + 5) << 1;
1874                    bits |= self.e.decision(27 + 5);
1875                    match bits {
1876                        0..=7 => bits + 3,
1877                        13 => 23 + self.intra_mb_type(32, false, 0),
1878                        14 => 11,
1879                        15 => 22,
1880                        _ => ((bits << 1) | self.e.decision(27 + 5)) - 4,
1881                    }
1882                }
1883            }
1884        };
1885        // 記録(イントラの種類・I16x16 の cbp・B_Direct_16x16)。
1886        let it = match kind {
1887            SliceKind::I => Some(t),
1888            SliceKind::P => t.checked_sub(5),
1889            SliceKind::B => t.checked_sub(23),
1890        };
1891        let m = self.cur_mb;
1892        self.mb_flags[m] = 0;
1893        if let Some(it) = it {
1894            self.cur_intra = true;
1895            self.mb_flags[m] = MBF_INTRA;
1896            if it == 25 {
1897                self.mb_flags[m] |= MBF_PCM;
1898                self.mb_cbp[m] = 0x2f;
1899                self.mb_dc[m] = 7;
1900            } else if it >= 1 {
1901                self.mb_flags[m] |= MBF_I16;
1902                let m1 = it - 1;
1903                self.mb_cbp[m] = ((if m1 >= 12 { 15 } else { 0 }) | (((m1 / 4) % 3) << 4)) as u8;
1904            }
1905        } else if kind == SliceKind::B && t == 0 {
1906            self.mb_flags[m] = MBF_DIRECT16;
1907        }
1908        Ok(t)
1909    }
1910
1911    fn sub_mb_type(&mut self, kind: SliceKind) -> Result<u32, &'static str> {
1912        let e = &mut self.e;
1913        Ok(if kind == SliceKind::B {
1914            if e.decision(36) == 0 {
1915                0
1916            } else if e.decision(37) == 0 {
1917                1 + e.decision(39)
1918            } else {
1919                let mut t = 3;
1920                if e.decision(38) == 1 {
1921                    if e.decision(39) == 1 {
1922                        return Ok(11 + e.decision(39));
1923                    }
1924                    t += 4;
1925                }
1926                t += 2 * e.decision(39);
1927                t + e.decision(39)
1928            }
1929        } else if e.decision(21) == 1 {
1930            0
1931        } else if e.decision(22) == 0 {
1932            1
1933        } else if e.decision(23) == 1 {
1934            2
1935        } else {
1936            3
1937        })
1938    }
1939
1940    fn intra4x4_rem(&mut self) -> Option<i32> {
1941        if self.e.decision(68) == 1 {
1942            return None;
1943        }
1944        let b0 = self.e.decision(69);
1945        let b1 = self.e.decision(69);
1946        let b2 = self.e.decision(69);
1947        Some((b0 | (b1 << 1) | (b2 << 2)) as i32)
1948    }
1949
1950    fn intra_chroma_mode(&mut self, st: &DecState, mb_x: u32, mb_y: u32) -> Result<u8, &'static str> {
1951        let (a, b) = self.nb_mbs(st, mb_x, mb_y);
1952        let c = |m: Option<usize>| m.is_some_and(|m| self.mb_cpm[m] != 0) as usize;
1953        let inc = c(a) + c(b);
1954        let mode = if self.e.decision(64 + inc) == 0 {
1955            0
1956        } else if self.e.decision(67) == 0 {
1957            1
1958        } else if self.e.decision(67) == 0 {
1959            2
1960        } else {
1961            3
1962        };
1963        self.mb_cpm[self.cur_mb] = mode;
1964        Ok(mode)
1965    }
1966
1967    fn cbp(&mut self, st: &DecState, mb_x: u32, mb_y: u32, _intra: bool) -> Result<u32, &'static str> {
1968        let (a, b) = self.nb_mbs(st, mb_x, mb_y);
1969        // 不可用の近傍は輝度ビットが立っている扱い(condTermFlag = 0)、クロマは 0。
1970        let ca = a.map_or(0x0f, |m| self.mb_cbp[m] as u32);
1971        let cb = b.map_or(0x0f, |m| self.mb_cbp[m] as u32);
1972        let nz = |v: u32, bit: u32| (v & bit == 0) as usize;
1973        let mut cbp = 0u32;
1974        cbp |= self.e.decision(73 + nz(ca, 2) + 2 * nz(cb, 4));
1975        cbp |= self.e.decision(73 + nz(cbp, 1) + 2 * nz(cb, 8)) << 1;
1976        cbp |= self.e.decision(73 + nz(ca, 8) + 2 * nz(cbp, 1)) << 2;
1977        cbp |= self.e.decision(73 + nz(cbp, 4) + 2 * nz(cbp, 2)) << 3;
1978        let (cha, chb) = ((ca >> 4) & 3, (cb >> 4) & 3);
1979        let inc = (cha > 0) as usize + 2 * (chb > 0) as usize;
1980        if self.e.decision(77 + inc) == 1 {
1981            let inc2 = 4 + (cha == 2) as usize + 2 * (chb == 2) as usize;
1982            cbp |= (1 + self.e.decision(77 + inc2)) << 4;
1983        }
1984        self.mb_cbp[self.cur_mb] = cbp as u8;
1985        Ok(cbp)
1986    }
1987
1988    fn qp_delta(&mut self) -> Result<i32, &'static str> {
1989        let mut v = 0i32;
1990        if self.e.decision(60 + (self.prev_dqp != 0) as usize) == 1 {
1991            v = 1;
1992            let mut ctx = 2;
1993            while self.e.decision(60 + ctx) == 1 {
1994                ctx = 3;
1995                v += 1;
1996                if v > 104 {
1997                    return Err("bad mb_qp_delta");
1998                }
1999            }
2000            v = if v & 1 == 1 { (v + 1) >> 1 } else { -((v + 1) >> 1) };
2001        }
2002        self.cur_dqp = v;
2003        Ok(v)
2004    }
2005
2006    fn ref_idx(&mut self, st: &DecState, list: usize, px: usize, py: usize, pw: usize, ph: usize, num_ref: u32) -> Result<i8, &'static str> {
2007        if num_ref <= 1 {
2008            return Ok(0);
2009        }
2010        let (bx, by) = ((px / 4) as isize, (py / 4) as isize);
2011        let cond = |x: isize, y: isize| -> usize {
2012            if !Self::blk_ok(st, x, y) {
2013                return 0;
2014            }
2015            let li = y as usize * self.blk_w + x as usize;
2016            (self.ref_gt0[list][li] && !self.direct[li]) as usize
2017        };
2018        let mut ctx = cond(bx - 1, by) + 2 * cond(bx, by - 1);
2019        let mut v = 0u32;
2020        while self.e.decision(54 + ctx) == 1 {
2021            v += 1;
2022            ctx = if ctx < 4 { 4 } else { 5 };
2023            if v >= 32 {
2024                return Err("ref_idx overflow");
2025            }
2026        }
2027        if v >= num_ref {
2028            return Err("ref_idx out of range");
2029        }
2030        for y in py / 4..(py + ph) / 4 {
2031            for x in px / 4..(px + pw) / 4 {
2032                self.ref_gt0[list][y * self.blk_w + x] = v > 0;
2033            }
2034        }
2035        Ok(v as i8)
2036    }
2037
2038    fn mvd(&mut self, st: &DecState, list: usize, px: usize, py: usize, pw: usize, ph: usize) -> Result<[i16; 2], &'static str> {
2039        let (bx, by) = ((px / 4) as isize, (py / 4) as isize);
2040        let get = |x: isize, y: isize| -> [u32; 2] {
2041            if !Self::blk_ok(st, x, y) {
2042                return [0, 0];
2043            }
2044            let m = self.mvd[list][y as usize * self.blk_w + x as usize];
2045            [m[0] as u32, m[1] as u32]
2046        };
2047        let (a, b) = (get(bx - 1, by), get(bx, by - 1));
2048        let (mx, ax) = self.mvd_comp(40, a[0] + b[0])?;
2049        let (my, ay) = self.mvd_comp(47, a[1] + b[1])?;
2050        for y in py / 4..(py + ph) / 4 {
2051            for x in px / 4..(px + pw) / 4 {
2052                self.mvd[list][y * self.blk_w + x] = [ax, ay];
2053            }
2054        }
2055        Ok([mx, my])
2056    }
2057
2058    fn residual(&mut self, st: &DecState, blk: ResBlock) -> Result<([i32; 16], u8), &'static str> {
2059        // (cat, 係数の数, coded_block_flag の文脈 inc)
2060        let intra = self.cur_intra as usize;
2061        let mb_cond = |rd: &Self, avail: bool, m: usize, bit: u8| -> usize {
2062            if !avail {
2063                intra
2064            } else if rd.mb_flags[m] & MBF_PCM != 0 {
2065                1
2066            } else {
2067                (rd.mb_dc[m] & bit != 0) as usize
2068            }
2069        };
2070        let blk_cond = |avail: bool, nnz: u8| -> usize { if !avail { intra } else { (nnz > 0) as usize } };
2071        let (cat, max, inc) = match blk {
2072            ResBlock::LumaDc { bx4, by4 } => {
2073                let (mx, my) = ((bx4 * 4) as u32, (by4 * 4) as u32);
2074                let (a, b) = self.nb_mbs(st, mx, my);
2075                let ia = mb_cond(self, a.is_some(), a.unwrap_or(0), 1);
2076                let ib = mb_cond(self, b.is_some(), b.unwrap_or(0), 1);
2077                (0usize, 16usize, ia + 2 * ib)
2078            }
2079            ResBlock::LumaAc { bx4, by4 } | ResBlock::Luma4x4 { bx4, by4 } => {
2080                let (x, y) = (bx4 as isize, by4 as isize);
2081                let ao = Self::blk_ok(st, x - 1, y);
2082                let bo = Self::blk_ok(st, x, y - 1);
2083                let ia = blk_cond(ao, if ao { st.nnz_luma[st.li(bx4 - 1, by4)] } else { 0 });
2084                let ib = blk_cond(bo, if bo { st.nnz_luma[st.li(bx4, by4 - 1)] } else { 0 });
2085                if matches!(blk, ResBlock::LumaAc { .. }) {
2086                    (1, 15, ia + 2 * ib)
2087                } else {
2088                    (2, 16, ia + 2 * ib)
2089                }
2090            }
2091            ResBlock::ChromaDc { comp, mb_x, mb_y } => {
2092                let (a, b) = self.nb_mbs(st, mb_x, mb_y);
2093                let bit = 2 << comp;
2094                let ia = mb_cond(self, a.is_some(), a.unwrap_or(0), bit);
2095                let ib = mb_cond(self, b.is_some(), b.unwrap_or(0), bit);
2096                (3, 4, ia + 2 * ib)
2097            }
2098            ResBlock::ChromaAc { comp, cbx4, cby4 } => {
2099                let ao = st.avail_px(cbx4 as isize * 8 - 1, cby4 as isize * 8);
2100                let bo = st.avail_px(cbx4 as isize * 8, cby4 as isize * 8 - 1);
2101                let ia = blk_cond(ao, if ao { st.nnz_chroma[comp][cby4 * st.cblk_w + cbx4 - 1] } else { 0 });
2102                let ib = blk_cond(bo, if bo { st.nnz_chroma[comp][(cby4 - 1) * st.cblk_w + cbx4] } else { 0 });
2103                (4, 15, ia + 2 * ib)
2104            }
2105        };
2106        let mut coeff = [0i32; 16];
2107        if self.e.decision(85 + cat * 4 + inc) == 0 {
2108            return Ok((coeff, 0));
2109        }
2110        match blk {
2111            ResBlock::LumaDc { .. } => self.mb_dc[self.cur_mb] |= 1,
2112            ResBlock::ChromaDc { comp, .. } => self.mb_dc[self.cur_mb] |= 2 << comp,
2113            _ => {}
2114        }
2115        // 有意係数マップ(9.3.3.1.3)。
2116        const SIG_OFF: [usize; 5] = [0, 15, 29, 44, 47];
2117        const ABS_OFF: [usize; 5] = [0, 10, 20, 30, 39];
2118        let sig = 105 + SIG_OFF[cat];
2119        let last = 166 + SIG_OFF[cat];
2120        let mut pos = [0usize; 16];
2121        let mut n = 0usize;
2122        let mut ended = false;
2123        for i in 0..max - 1 {
2124            let inc = if cat == 3 { i.min(2) } else { i };
2125            if self.e.decision(sig + inc) == 1 {
2126                pos[n] = i;
2127                n += 1;
2128                if self.e.decision(last + inc) == 1 {
2129                    ended = true;
2130                    break;
2131                }
2132            }
2133        }
2134        if !ended {
2135            pos[n] = max - 1;
2136            n += 1;
2137        }
2138        // 係数の大きさ(逆順、UEG0・uCoff = 14)と符号。
2139        let abs_base = 227 + ABS_OFF[cat];
2140        let (mut eq1, mut gt1) = (0usize, 0usize);
2141        for k in (0..n).rev() {
2142            let inc0 = if gt1 != 0 { 0 } else { (1 + eq1).min(4) };
2143            let mut abs: u32 = 1;
2144            if self.e.decision(abs_base + inc0) == 1 {
2145                let inc = 5 + gt1.min(4 - (cat == 3) as usize);
2146                abs = 2;
2147                while abs < 15 && self.e.decision(abs_base + inc) == 1 {
2148                    abs += 1;
2149                }
2150                if abs >= 15 {
2151                    let mut j = 0;
2152                    while self.e.bypass() == 1 && j < 23 {
2153                        j += 1;
2154                    }
2155                    let mut v: u32 = 1;
2156                    for _ in 0..j {
2157                        v = 2 * v + self.e.bypass();
2158                    }
2159                    abs = v + 14;
2160                }
2161            }
2162            if abs == 1 {
2163                eq1 += 1;
2164            } else {
2165                gt1 += 1;
2166            }
2167            let v = abs.min(i32::MAX as u32) as i32;
2168            coeff[pos[k]] = if self.e.bypass() == 1 { -v } else { v };
2169        }
2170        Ok((coeff, n as u8))
2171    }
2172
2173    fn pcm(&mut self, out: &mut [u8; 384]) {
2174        self.e.read_pcm(out);
2175    }
2176
2177    fn transform_8x8(&mut self, st: &DecState, mb_x: u32, mb_y: u32) -> bool {
2178        let (a, b) = self.nb_mbs(st, mb_x, mb_y);
2179        let c = |m: Option<usize>| m.is_some_and(|m| st.mb_t8[m]) as usize;
2180        self.e.decision(399 + c(a) + c(b)) == 1
2181    }
2182
2183    /// 輝度 8x8(cat 5)。4:2:0 では coded_block_flag は無く常に 1(7.4.5.3.3)。
2184    fn residual8x8(&mut self, _st: &DecState, _bx4: usize, _by4: usize) -> Result<([i32; 64], [u8; 4]), &'static str> {
2185        let mut coeff = [0i32; 64];
2186        let mut pos = [0usize; 64];
2187        let mut n = 0usize;
2188        let mut ended = false;
2189        for i in 0..63 {
2190            if self.e.decision(402 + SIG_CTX_8X8_FRAME[i] as usize) == 1 {
2191                pos[n] = i;
2192                n += 1;
2193                if self.e.decision(417 + LAST_CTX_8X8[i] as usize) == 1 {
2194                    ended = true;
2195                    break;
2196                }
2197            }
2198        }
2199        if !ended {
2200            pos[n] = 63;
2201            n += 1;
2202        }
2203        let (mut eq1, mut gt1) = (0usize, 0usize);
2204        for k in (0..n).rev() {
2205            let inc0 = if gt1 != 0 { 0 } else { (1 + eq1).min(4) };
2206            let mut abs: u32 = 1;
2207            if self.e.decision(426 + inc0) == 1 {
2208                let inc = 5 + gt1.min(4);
2209                abs = 2;
2210                while abs < 15 && self.e.decision(426 + inc) == 1 {
2211                    abs += 1;
2212                }
2213                if abs >= 15 {
2214                    let mut j = 0;
2215                    while self.e.bypass() == 1 && j < 23 {
2216                        j += 1;
2217                    }
2218                    let mut v: u32 = 1;
2219                    for _ in 0..j {
2220                        v = 2 * v + self.e.bypass();
2221                    }
2222                    abs = v + 14;
2223                }
2224            }
2225            if abs == 1 {
2226                eq1 += 1;
2227            } else {
2228                gt1 += 1;
2229            }
2230            let v = abs.min(i32::MAX as u32) as i32;
2231            coeff[pos[k]] = if self.e.bypass() == 1 { -v } else { v };
2232        }
2233        Ok((coeff, [n as u8; 4]))
2234    }
2235
2236    fn mark_direct(&mut self, px: usize, py: usize, pw: usize, ph: usize) {
2237        for y in py / 4..(py + ph) / 4 {
2238            for x in px / 4..(px + pw) / 4 {
2239                self.direct[y * self.blk_w + x] = true;
2240            }
2241        }
2242    }
2243}
2244
2245#[allow(clippy::too_many_arguments)]
2246fn decode_mb<R: MbSyntax>(
2247    r: &mut R,
2248    st: &mut DecState,
2249    mb_x: u32,
2250    mb_y: u32,
2251    qp: &mut i32,
2252    pps: &Pps,
2253) -> Result<(), &'static str> {
2254    let mb_type = r.mb_type(st, mb_x, mb_y, SliceKind::I)?;
2255    if mb_type == 25 {
2256        return decode_mb_ipcm(r, st, mb_x, mb_y);
2257    }
2258    if mb_type == 0 {
2259        decode_mb_i4x4(r, st, mb_x, mb_y, qp, pps)
2260    } else if (1..=24).contains(&mb_type) {
2261        decode_mb_i16x16(r, st, mb_x, mb_y, qp, mb_type, pps)
2262    } else {
2263        Err("unsupported mb_type (non-I)")
2264    }
2265}
2266
2267/// I_PCM マクロブロック: 生の画素値をそのまま格納(予測・変換なし)。
2268fn decode_mb_ipcm<R: MbSyntax>(
2269    r: &mut R,
2270    st: &mut DecState,
2271    mb_x: u32,
2272    mb_y: u32,
2273) -> Result<(), &'static str> {
2274    let mut buf = [0u8; 384];
2275    r.pcm(&mut buf);
2276    let w = st.yuv.width as usize;
2277    let cw = (st.yuv.width / 2) as usize;
2278    let (mx, my) = (mb_x as usize, mb_y as usize);
2279    for by in 0..16 {
2280        for bx in 0..16 {
2281            st.yuv.y[(my + by) * w + mx + bx] = buf[by * 16 + bx];
2282        }
2283    }
2284    let (cmx, cmy) = (mx / 2, my / 2);
2285    for by in 0..8 {
2286        for bx in 0..8 {
2287            st.yuv.u[(cmy + by) * cw + cmx + bx] = buf[256 + by * 8 + bx];
2288            st.yuv.v[(cmy + by) * cw + cmx + bx] = buf[320 + by * 8 + bx];
2289        }
2290    }
2291    // 近傍情報: I_PCM ブロックは nC 計算上 totalCoeff=16 扱い、モードは非I4x4(-1)。
2292    let bx0 = mx / 4;
2293    let by0 = my / 4;
2294    for dy in 0..4 {
2295        for dx in 0..4 {
2296            let li = st.li(bx0 + dx, by0 + dy);
2297            st.nnz_luma[li] = 16;
2298            st.mode_luma[li] = -1;
2299            st.decoded[li] = true;
2300        }
2301    }
2302    let cbx0 = cmx / 4;
2303    let cby0 = cmy / 4;
2304    for comp in 0..2 {
2305        for dy in 0..2 {
2306            for dx in 0..2 {
2307                st.nnz_chroma[comp][(cby0 + dy) * st.cblk_w + cbx0 + dx] = 16;
2308            }
2309        }
2310    }
2311    // I_PCM の QPy はデブロッキング上 0 とみなす(8.7.2)。
2312    st.mb_qp[(my / 16) * st.mb_w + (mx / 16)] = 0;
2313    Ok(())
2314}
2315
2316fn median3(a: i16, b: i16, c: i16) -> i16 {
2317    a + b + c - a.min(b).min(c) - a.max(b).max(c)
2318}
2319
2320/// 16x16 パーティションの明示的 MV 予測(8.4.1.3、単一参照 refIdx=0 前提)。
2321/// 重要: 「利用可能(デコード済み・画面内)」と「一致(インターで refIdx=0)」を区別する。
2322/// C(右上)の D 代用は C が *利用不可* のときのみ(イントラで利用可能なら代用しない)。
2323fn mv_predict_16x16(st: &DecState, mb_x: u32, mb_y: u32) -> [i16; 2] {
2324    let bx0 = (mb_x / 4) as i32;
2325    let by0 = (mb_y / 4) as i32;
2326    // (利用可能, 一致, MV)。一致 = インターで refIdx=0。イントラは利用可能だが不一致(mv=0)。
2327    let getn = |bx: i32, by: i32| -> (bool, bool, [i16; 2]) {
2328        if bx >= 0 && by >= 0 && (bx as usize) < st.blk_w && (by as usize) < st.blk_h {
2329            let li = (by as usize) * st.blk_w + bx as usize;
2330            if st.decoded[li] {
2331                if st.is_inter[li] {
2332                    return (true, true, st.mv[0][li]);
2333                }
2334                return (true, false, [0, 0]); // イントラ: 利用可能・不一致
2335            }
2336        }
2337        (false, false, [0, 0])
2338    };
2339    let (_a_av, a_m, mva) = getn(bx0 - 1, by0); // 左
2340    let (_b_av, b_m, mvb) = getn(bx0, by0 - 1); // 上
2341    let (c_av, c_m0, mvc0) = getn(bx0 + 4, by0 - 1); // 右上
2342    let (mut c_m, mut mvc) = (c_m0, mvc0);
2343    if !c_av {
2344        // C が *利用不可* のときのみ D(左上)で代用。
2345        let (_d_av, d_m, mvd) = getn(bx0 - 1, by0 - 1);
2346        c_m = d_m;
2347        mvc = mvd;
2348    }
2349    // mbB と mbC が(画面位置として)不可用で mbA 可用なら mvp=mvA。
2350    let b_mb_avail = mb_y > 0;
2351    let c_mb_avail = mb_y > 0 && (bx0 + 4 < st.blk_w as i32 || mb_x > 0);
2352    if !b_mb_avail && !c_mb_avail && mb_x > 0 {
2353        return mva;
2354    }
2355    let matches = a_m as i32 + b_m as i32 + c_m as i32;
2356    if matches == 1 {
2357        if a_m {
2358            return mva;
2359        }
2360        if b_m {
2361            return mvb;
2362        }
2363        return mvc;
2364    }
2365    [
2366        median3(mva[0], mvb[0], mvc[0]),
2367        median3(mva[1], mvb[1], mvc[1]),
2368    ]
2369}
2370
2371/// P_Skip の動きベクトル導出(8.4.1.1)。
2372fn predict_pskip_mv(st: &DecState, mb_x: u32, mb_y: u32) -> [i16; 2] {
2373    let bx0 = (mb_x / 4) as usize;
2374    let by0 = (mb_y / 4) as usize;
2375    let a_avail = st.avail_px(mb_x as isize - 1, mb_y as isize);
2376    let b_avail = st.avail_px(mb_x as isize, mb_y as isize - 1);
2377    if !a_avail || !b_avail {
2378        return [0, 0];
2379    }
2380    let a_li = by0 * st.blk_w + bx0 - 1;
2381    let b_li = (by0 - 1) * st.blk_w + bx0;
2382    // 「refIdxLXA が 0 かつ mvLXA が (0,0)」(B も同様)なら 0(8.4.1.1)。refIdx は単一参照で
2383    // 0 前提だが、**イントラの隣は refIdx = -1** なので該当しない。
2384    // 【2026-10-17】従来は MV だけを見ており、イントラの隣(MV は 0 と記録)でも 0 にしていた。
2385    // 実ストリーム(x264 Baseline)で 1 画素ずれ、以降の参照で広がる元になっていた。
2386    let zero_inter = |li: usize| st.is_inter[li] && st.refi[0][li] == 0 && st.mv[0][li] == [0, 0];
2387    if zero_inter(a_li) || zero_inter(b_li) {
2388        return [0, 0];
2389    }
2390    let _ = (bx0, by0);
2391    mv_predict(st, mb_x as usize, mb_y as usize, 16, MvDir::None, 0)
2392}
2393
2394/// 6タップ補間のコア(8.4.2.2.1)。get(dx,dy) でサンプル取得(クランプ有無は呼び出し側)。
2395fn interp_luma_core<F: Fn(isize, isize) -> i32>(get: F, fx: i32, fy: i32) -> i32 {
2396    let tap =
2397        |a: i32, b: i32, c: i32, d: i32, e: i32, f: i32| a - 5 * b + 20 * c + 20 * d - 5 * e + f;
2398    if fx == 0 && fy == 0 {
2399        return get(0, 0);
2400    }
2401    // 水平半画素(行 dy), 垂直半画素(列 dx)
2402    let hh = |dy: isize| -> i32 {
2403        clip1(
2404            (tap(
2405                get(-2, dy),
2406                get(-1, dy),
2407                get(0, dy),
2408                get(1, dy),
2409                get(2, dy),
2410                get(3, dy),
2411            ) + 16)
2412                >> 5,
2413        )
2414    };
2415    let vh = |dx: isize| -> i32 {
2416        clip1(
2417            (tap(
2418                get(dx, -2),
2419                get(dx, -1),
2420                get(dx, 0),
2421                get(dx, 1),
2422                get(dx, 2),
2423                get(dx, 3),
2424            ) + 16)
2425                >> 5,
2426        )
2427    };
2428    let jj = || -> i32 {
2429        let vi = |dx: isize| {
2430            tap(
2431                get(dx, -2),
2432                get(dx, -1),
2433                get(dx, 0),
2434                get(dx, 1),
2435                get(dx, 2),
2436                get(dx, 3),
2437            )
2438        };
2439        clip1((tap(vi(-2), vi(-1), vi(0), vi(1), vi(2), vi(3)) + 512) >> 10)
2440    };
2441    match (fx, fy) {
2442        (1, 0) => (get(0, 0) + hh(0) + 1) >> 1,
2443        (2, 0) => hh(0),
2444        (3, 0) => (get(1, 0) + hh(0) + 1) >> 1,
2445        (0, 1) => (get(0, 0) + vh(0) + 1) >> 1,
2446        (0, 2) => vh(0),
2447        (0, 3) => (get(0, 1) + vh(0) + 1) >> 1,
2448        (2, 2) => jj(),
2449        (1, 1) => (hh(0) + vh(0) + 1) >> 1,
2450        (3, 1) => (hh(0) + vh(1) + 1) >> 1,
2451        (1, 3) => (vh(0) + hh(1) + 1) >> 1,
2452        (3, 3) => (vh(1) + hh(1) + 1) >> 1,
2453        (2, 1) => (hh(0) + jj() + 1) >> 1,
2454        (1, 2) => (vh(0) + jj() + 1) >> 1,
2455        (3, 2) => (jj() + vh(1) + 1) >> 1,
2456        (2, 3) => (jj() + hh(1) + 1) >> 1,
2457        _ => get(0, 0),
2458    }
2459}
2460
2461/// 端のクランプ付きで 1 サンプル補間(フレーム境界をまたぐパーティション用)。
2462fn interp_luma_sample(refr: &DecodedYuv, x: isize, y: isize, fx: i32, fy: i32) -> i32 {
2463    let w = refr.width as isize;
2464    let h = refr.height as isize;
2465    interp_luma_core(
2466        |dx, dy| {
2467            let sx = (x + dx).clamp(0, w - 1);
2468            let sy = (y + dy).clamp(0, h - 1);
2469            refr.y[(sy * w + sx) as usize] as i32
2470        },
2471        fx,
2472        fy,
2473    )
2474}
2475
2476/// 輝度パーティション pw×ph @ (px,py) の動き補償(サブ画素対応)。
2477fn mc_part_luma(
2478    st: &mut DecState,
2479    px: usize,
2480    py: usize,
2481    pw: usize,
2482    ph: usize,
2483    mv: [i16; 2],
2484    refr: &DecodedYuv,
2485) {
2486    let w = st.yuv.width as usize;
2487    let h = st.yuv.height as usize;
2488    let fx = (mv[0] & 3) as i32;
2489    let fy = (mv[1] & 3) as i32;
2490    let ix = (mv[0] >> 2) as isize;
2491    let iy = (mv[1] >> 2) as isize;
2492    // 高速パス: 整数 MV かつフットプリントが完全に範囲内なら行コピー(クランプ・補間不要)。
2493    if fx == 0 && fy == 0 {
2494        let sx0 = px as isize + ix;
2495        let sy0 = py as isize + iy;
2496        if sx0 >= 0
2497            && sy0 >= 0
2498            && sx0 + pw as isize <= w as isize
2499            && sy0 + ph as isize <= h as isize
2500        {
2501            let (sx0, sy0) = (sx0 as usize, sy0 as usize);
2502            for y in 0..ph {
2503                let s = (sy0 + y) * w + sx0;
2504                let d = (py + y) * w + px;
2505                st.yuv.y[d..d + pw].copy_from_slice(&refr.y[s..s + pw]);
2506            }
2507            return;
2508        }
2509    }
2510    // 高速パス: フットプリント + 6タップのハロー(±2/+3)が範囲内ならクランプ無しで補間。
2511    let wi = w as isize;
2512    let hi = h as isize;
2513    let halo_ok = px as isize + ix - 2 >= 0
2514        && py as isize + iy - 2 >= 0
2515        && px as isize + ix + pw as isize + 2 < wi
2516        && py as isize + iy + ph as isize + 2 < hi;
2517    if halo_ok {
2518        // separable 高速パス: 中央(j)を使う位置は垂直6タップ中間値を行ごとに 1 回だけ計算し
2519        // 水平方向で再利用する(per-pixel の 7×6タップ → 約 2×6タップ/画素)。
2520        let needs_j = matches!((fx, fy), (2, 2) | (2, 1) | (2, 3) | (1, 2) | (3, 2));
2521        if needs_j {
2522            let base_x = px as isize + ix;
2523            let base_y = py as isize + iy;
2524            let tap = |a: i32, b: i32, c: i32, d: i32, e: i32, f: i32| {
2525                a - 5 * b + 20 * c + 20 * d - 5 * e + f
2526            };
2527            let g = |rx: isize, ry: isize| refr.y[(ry * wi + rx) as usize] as i32;
2528            let mut vrow = [0i32; 21]; // pw≤16, +5 列分の垂直中間値(未丸め)
2529            for y in 0..ph {
2530                let ry = base_y + y as isize;
2531                for c in 0..pw + 5 {
2532                    let rx = base_x + c as isize - 2;
2533                    vrow[c] = tap(
2534                        g(rx, ry - 2),
2535                        g(rx, ry - 1),
2536                        g(rx, ry),
2537                        g(rx, ry + 1),
2538                        g(rx, ry + 2),
2539                        g(rx, ry + 3),
2540                    );
2541                }
2542                for x in 0..pw {
2543                    let j = clip1(
2544                        (tap(
2545                            vrow[x],
2546                            vrow[x + 1],
2547                            vrow[x + 2],
2548                            vrow[x + 3],
2549                            vrow[x + 4],
2550                            vrow[x + 5],
2551                        ) + 512)
2552                            >> 10,
2553                    );
2554                    let rx = base_x + x as isize;
2555                    let val = match (fx, fy) {
2556                        (2, 2) => j,
2557                        (2, 1) => {
2558                            let b = clip1(
2559                                (tap(
2560                                    g(rx - 2, ry),
2561                                    g(rx - 1, ry),
2562                                    g(rx, ry),
2563                                    g(rx + 1, ry),
2564                                    g(rx + 2, ry),
2565                                    g(rx + 3, ry),
2566                                ) + 16)
2567                                    >> 5,
2568                            );
2569                            (b + j + 1) >> 1
2570                        }
2571                        (2, 3) => {
2572                            let s = clip1(
2573                                (tap(
2574                                    g(rx - 2, ry + 1),
2575                                    g(rx - 1, ry + 1),
2576                                    g(rx, ry + 1),
2577                                    g(rx + 1, ry + 1),
2578                                    g(rx + 2, ry + 1),
2579                                    g(rx + 3, ry + 1),
2580                                ) + 16)
2581                                    >> 5,
2582                            );
2583                            (j + s + 1) >> 1
2584                        }
2585                        (1, 2) => {
2586                            let hp = clip1(
2587                                (tap(
2588                                    g(rx, ry - 2),
2589                                    g(rx, ry - 1),
2590                                    g(rx, ry),
2591                                    g(rx, ry + 1),
2592                                    g(rx, ry + 2),
2593                                    g(rx, ry + 3),
2594                                ) + 16)
2595                                    >> 5,
2596                            );
2597                            (hp + j + 1) >> 1
2598                        }
2599                        (3, 2) => {
2600                            let m = clip1(
2601                                (tap(
2602                                    g(rx + 1, ry - 2),
2603                                    g(rx + 1, ry - 1),
2604                                    g(rx + 1, ry),
2605                                    g(rx + 1, ry + 1),
2606                                    g(rx + 1, ry + 2),
2607                                    g(rx + 1, ry + 3),
2608                                ) + 16)
2609                                    >> 5,
2610                            );
2611                            (j + m + 1) >> 1
2612                        }
2613                        _ => j,
2614                    };
2615                    st.yuv.y[(py + y) * w + px + x] = val as u8;
2616                }
2617            }
2618            return;
2619        }
2620        for y in 0..ph {
2621            for x in 0..pw {
2622                let bx = px as isize + x as isize + ix;
2623                let by = py as isize + y as isize + iy;
2624                let v = interp_luma_core(
2625                    |dx, dy| refr.y[((by + dy) * wi + bx + dx) as usize] as i32,
2626                    fx,
2627                    fy,
2628                );
2629                st.yuv.y[(py + y) * w + px + x] = v as u8;
2630            }
2631        }
2632        return;
2633    }
2634    // フレーム境界をまたぐ低速パス。needs_j 位置(jj() が絡む)は per-pixel だと
2635    // 縦6タップ×横6タップ=36タップに膨らむため、halo_ok 分岐と同じ「行ごとに縦の中間値を
2636    // 1回だけ計算し横方向で使い回す」separable 手法をクランプ付きで適用する。
2637    let needs_j = matches!((fx, fy), (2, 2) | (2, 1) | (2, 3) | (1, 2) | (3, 2));
2638    if needs_j {
2639        let base_x = px as isize + ix;
2640        let base_y = py as isize + iy;
2641        let tap =
2642            |a: i32, b: i32, c: i32, d: i32, e: i32, f: i32| a - 5 * b + 20 * c + 20 * d - 5 * e + f;
2643        let g = |rx: isize, ry: isize| {
2644            let cx = rx.clamp(0, wi - 1);
2645            let cy = ry.clamp(0, hi - 1);
2646            refr.y[(cy * wi + cx) as usize] as i32
2647        };
2648        let mut vrow = [0i32; 21]; // pw≤16, +5 列分の垂直中間値(未丸め)
2649        for y in 0..ph {
2650            let ry = base_y + y as isize;
2651            for c in 0..pw + 5 {
2652                let rx = base_x + c as isize - 2;
2653                vrow[c] = tap(
2654                    g(rx, ry - 2),
2655                    g(rx, ry - 1),
2656                    g(rx, ry),
2657                    g(rx, ry + 1),
2658                    g(rx, ry + 2),
2659                    g(rx, ry + 3),
2660                );
2661            }
2662            for x in 0..pw {
2663                let j = clip1(
2664                    (tap(
2665                        vrow[x],
2666                        vrow[x + 1],
2667                        vrow[x + 2],
2668                        vrow[x + 3],
2669                        vrow[x + 4],
2670                        vrow[x + 5],
2671                    ) + 512)
2672                        >> 10,
2673                );
2674                let rx = base_x + x as isize;
2675                let val = match (fx, fy) {
2676                    (2, 2) => j,
2677                    (2, 1) => {
2678                        let b = clip1(
2679                            (tap(
2680                                g(rx - 2, ry),
2681                                g(rx - 1, ry),
2682                                g(rx, ry),
2683                                g(rx + 1, ry),
2684                                g(rx + 2, ry),
2685                                g(rx + 3, ry),
2686                            ) + 16)
2687                                >> 5,
2688                        );
2689                        (b + j + 1) >> 1
2690                    }
2691                    (2, 3) => {
2692                        let s = clip1(
2693                            (tap(
2694                                g(rx - 2, ry + 1),
2695                                g(rx - 1, ry + 1),
2696                                g(rx, ry + 1),
2697                                g(rx + 1, ry + 1),
2698                                g(rx + 2, ry + 1),
2699                                g(rx + 3, ry + 1),
2700                            ) + 16)
2701                                >> 5,
2702                        );
2703                        (j + s + 1) >> 1
2704                    }
2705                    (1, 2) => {
2706                        let hp = clip1(
2707                            (tap(
2708                                g(rx, ry - 2),
2709                                g(rx, ry - 1),
2710                                g(rx, ry),
2711                                g(rx, ry + 1),
2712                                g(rx, ry + 2),
2713                                g(rx, ry + 3),
2714                            ) + 16)
2715                                >> 5,
2716                        );
2717                        (hp + j + 1) >> 1
2718                    }
2719                    (3, 2) => {
2720                        let m = clip1(
2721                            (tap(
2722                                g(rx + 1, ry - 2),
2723                                g(rx + 1, ry - 1),
2724                                g(rx + 1, ry),
2725                                g(rx + 1, ry + 1),
2726                                g(rx + 1, ry + 2),
2727                                g(rx + 1, ry + 3),
2728                            ) + 16)
2729                                >> 5,
2730                        );
2731                        (j + m + 1) >> 1
2732                    }
2733                    _ => j,
2734                };
2735                st.yuv.y[(py + y) * w + px + x] = val as u8;
2736            }
2737        }
2738        return;
2739    }
2740    for y in 0..ph {
2741        for x in 0..pw {
2742            let bx = px as isize + x as isize + ix;
2743            let by = py as isize + y as isize + iy;
2744            st.yuv.y[(py + y) * w + px + x] = interp_luma_sample(refr, bx, by, fx, fy) as u8;
2745        }
2746    }
2747}
2748
2749/// クロマのサブ画素補間(8.4.2.2.2、双線形 1/8 画素)。
2750fn interp_chroma_sample(
2751    plane: &[u8],
2752    cw: isize,
2753    ch: isize,
2754    x: isize,
2755    y: isize,
2756    fx: i32,
2757    fy: i32,
2758) -> i32 {
2759    let get = |dx: isize, dy: isize| -> i32 {
2760        let sx = (x + dx).clamp(0, cw - 1);
2761        let sy = (y + dy).clamp(0, ch - 1);
2762        plane[(sy * cw + sx) as usize] as i32
2763    };
2764    let a = get(0, 0);
2765    let b = get(1, 0);
2766    let c = get(0, 1);
2767    let d = get(1, 1);
2768    ((8 - fx) * (8 - fy) * a + fx * (8 - fy) * b + (8 - fx) * fy * c + fx * fy * d + 32) >> 6
2769}
2770
2771/// クロマパーティション(輝度 pw×ph @ (px,py) に対応)の動き補償(サブ画素 1/8)。
2772fn mc_part_chroma(
2773    st: &mut DecState,
2774    px: usize,
2775    py: usize,
2776    pw: usize,
2777    ph: usize,
2778    mv: [i16; 2],
2779    refr: &DecodedYuv,
2780) {
2781    let cw = (st.yuv.width / 2) as isize;
2782    let chh = (st.yuv.height / 2) as isize;
2783    let cx = px / 2;
2784    let cy = py / 2;
2785    let cpw = pw / 2;
2786    let cph = ph / 2;
2787    let fx = (mv[0] & 7) as i32;
2788    let fy = (mv[1] & 7) as i32;
2789    let ix = (mv[0] >> 3) as isize;
2790    let iy = (mv[1] >> 3) as isize;
2791    let cwz = cw as usize;
2792    let integer = fx == 0 && fy == 0;
2793    let sx0 = cx as isize + ix;
2794    let sy0 = cy as isize + iy;
2795    let in_bounds = sx0 >= 0 && sy0 >= 0 && sx0 + cpw as isize <= cw && sy0 + cph as isize <= chh;
2796    for comp in 0..2 {
2797        let (dst, src) = if comp == 0 {
2798            (&mut st.yuv.u, &refr.u)
2799        } else {
2800            (&mut st.yuv.v, &refr.v)
2801        };
2802        if integer && in_bounds {
2803            let (sx0, sy0) = (sx0 as usize, sy0 as usize);
2804            for y in 0..cph {
2805                let s = (sy0 + y) * cwz + sx0;
2806                let d = (cy + y) * cwz + cx;
2807                dst[d..d + cpw].copy_from_slice(&src[s..s + cpw]);
2808            }
2809            continue;
2810        }
2811        for y in 0..cph {
2812            for x in 0..cpw {
2813                let bx = cx as isize + x as isize + ix;
2814                let by = cy as isize + y as isize + iy;
2815                dst[(cy + y) * cwz + cx + x] =
2816                    interp_chroma_sample(src, cw, chh, bx, by, fx, fy) as u8;
2817            }
2818        }
2819    }
2820}
2821
2822/// パーティションの MC(輝度 + クロマ)。
2823fn mc_part(
2824    st: &mut DecState,
2825    px: usize,
2826    py: usize,
2827    pw: usize,
2828    ph: usize,
2829    mv: [i16; 2],
2830    refr: &DecodedYuv,
2831) {
2832    mc_part_luma(st, px, py, pw, ph, mv, refr);
2833    mc_part_chroma(st, px, py, pw, ph, mv, refr);
2834}
2835
2836/// L0 だけのパーティション(P)の MV をグリッドへ格納する。`id` は参照ピクチャの識別子。
2837#[allow(clippy::too_many_arguments)]
2838fn store_part_mv(st: &mut DecState, px: usize, py: usize, pw: usize, ph: usize, mv: [i16; 2], ref_idx: i8, id: i32) {
2839    store_part(st, px, py, pw, ph, [mv, [0, 0]], [ref_idx, -1], [id, 0]);
2840}
2841
2842/// パーティションの MV・refIdx(L0/L1)をグリッドへ格納(後続パーティション/MB の予測・
2843/// デブロック・次のピクチャの co-located 用)。使わないリストは refIdx = -1・MV = 0。
2844#[allow(clippy::too_many_arguments)]
2845fn store_part(
2846    st: &mut DecState,
2847    px: usize,
2848    py: usize,
2849    pw: usize,
2850    ph: usize,
2851    mv: [[i16; 2]; 2],
2852    ref_idx: [i8; 2],
2853    id: [i32; 2],
2854) {
2855    let bx0 = px / 4;
2856    let by0 = py / 4;
2857    for dy in 0..ph / 4 {
2858        for dx in 0..pw / 4 {
2859            let li = (by0 + dy) * st.blk_w + bx0 + dx;
2860            for l in 0..2 {
2861                st.mv[l][li] = mv[l];
2862                st.refi[l][li] = ref_idx[l];
2863                st.refp[l][li] = id[l];
2864            }
2865            st.is_inter[li] = true;
2866            st.mode_luma[li] = -1;
2867            st.nnz_luma[li] = 0;
2868            st.decoded[li] = true;
2869        }
2870    }
2871}
2872
2873/// MV 予測の方向ヒント(16x8/8x16 の特例)。
2874#[derive(Clone, Copy, PartialEq)]
2875enum MvDir {
2876    None,
2877    A,
2878    B,
2879    C,
2880}
2881
2882/// パーティション (px,py,pw) の近傍 A/B/C を返す: ((一致,mv) x3)。
2883/// 一致 = インターで refIdx=0。C は利用不可なら D(左上)で代用。
2884/// 動きベクトル予測の近傍(8.4.1.3.2)。可用か・refIdxL0(イントラ/不可用は -1)・MV。
2885#[derive(Clone, Copy)]
2886struct MvNeighbor {
2887    avail: bool,
2888    ref_idx: i8,
2889    mv: [i16; 2],
2890}
2891
2892fn part_neighbors(st: &DecState, px: usize, py: usize, pw: usize, list: usize) -> (MvNeighbor, MvNeighbor, MvNeighbor) {
2893    let bx0 = (px / 4) as i32;
2894    let by0 = (py / 4) as i32;
2895    let getn = |bx: i32, by: i32| -> MvNeighbor {
2896        if bx >= 0 && by >= 0 && (bx as usize) < st.blk_w && (by as usize) < st.blk_h && st.avail_blk(bx as isize, by as isize) {
2897            let li = (by as usize) * st.blk_w + bx as usize;
2898            if st.decoded[li] {
2899                if st.is_inter[li] {
2900                    // そのリストを使わないブロック(refIdx = -1)の MV は 0 として扱う(8.4.1.3.2)。
2901                    let ri = st.refi[list][li];
2902                    let mv = if ri >= 0 { st.mv[list][li] } else { [0, 0] };
2903                    return MvNeighbor { avail: true, ref_idx: ri, mv };
2904                }
2905                return MvNeighbor { avail: true, ref_idx: -1, mv: [0, 0] };
2906            }
2907        }
2908        MvNeighbor { avail: false, ref_idx: -1, mv: [0, 0] }
2909    };
2910    let a = getn(bx0 - 1, by0);
2911    let b = getn(bx0, by0 - 1);
2912    let mut c = getn(bx0 + (pw / 4) as i32, by0 - 1);
2913    if !c.avail {
2914        // C が不可用(画面外・未復号)のときだけ D(左上)で代用。
2915        c = getn(bx0 - 1, by0 - 1);
2916    }
2917    (a, b, c)
2918}
2919
2920/// パーティションの MV 予測(8.4.1.3、単一参照 refIdx=0、方向ヒント対応)。
2921/// `ref_idx` はこのパーティションの refIdxL0。
2922/// 【2026-10-17】複数参照に対応。従来は「インターの隣=一致」とみなしており(単一参照前提)、
2923/// 「B・C が不可用で A 可用なら A」も画面上の位置で近似していた。8.4.1.3 どおり refIdx の一致と
2924/// 実際の可用性(画面外・未復号)で判定する。
2925fn mv_predict(st: &DecState, px: usize, py: usize, pw: usize, dir: MvDir, ref_idx: i8) -> [i16; 2] {
2926    mv_predict_l(st, px, py, pw, dir, ref_idx, 0)
2927}
2928
2929/// `mv_predict` のリスト指定版(B の L1 用)。
2930#[allow(clippy::too_many_arguments)]
2931fn mv_predict_l(st: &DecState, px: usize, py: usize, pw: usize, dir: MvDir, ref_idx: i8, list: usize) -> [i16; 2] {
2932    let (a, mut b, mut c) = part_neighbors(st, px, py, pw, list);
2933    match dir {
2934        MvDir::A if a.ref_idx == ref_idx => return a.mv,
2935        MvDir::B if b.ref_idx == ref_idx => return b.mv,
2936        MvDir::C if c.ref_idx == ref_idx => return c.mv,
2937        _ => {}
2938    }
2939    // 8.4.1.3.1: B と C がともに不可用で A が可用なら、B・C を A で置き換える。
2940    if !b.avail && !c.avail && a.avail {
2941        b = a;
2942        c = a;
2943    }
2944    let hits = [a, b, c].iter().filter(|n| n.ref_idx == ref_idx).count();
2945    if hits == 1 {
2946        for n in [a, b, c] {
2947            if n.ref_idx == ref_idx {
2948                return n.mv;
2949            }
2950        }
2951    }
2952    [median3(a.mv[0], b.mv[0], c.mv[0]), median3(a.mv[1], b.mv[1], c.mv[1])]
2953}
2954
2955/// インター MB(P_Skip 含む)の近傍グリッドを更新。
2956fn set_mb_inter_grids(st: &mut DecState, mb_x: u32, mb_y: u32, mv: [i16; 2], qp: i32) {
2957    let bx0 = (mb_x / 4) as usize;
2958    let by0 = (mb_y / 4) as usize;
2959    for dy in 0..4 {
2960        for dx in 0..4 {
2961            let li = (by0 + dy) * st.blk_w + bx0 + dx;
2962            st.mv[0][li] = mv;
2963            st.nnz_luma[li] = 0;
2964            st.mode_luma[li] = -1;
2965            st.is_inter[li] = true;
2966            st.decoded[li] = true;
2967        }
2968    }
2969    let cbx0 = (mb_x / 8) as usize;
2970    let cby0 = (mb_y / 8) as usize;
2971    for comp in 0..2 {
2972        for dy in 0..2 {
2973            for dx in 0..2 {
2974                st.nnz_chroma[comp][(cby0 + dy) * st.cblk_w + cbx0 + dx] = 0;
2975            }
2976        }
2977    }
2978    st.mb_qp[(mb_y / 16) as usize * st.mb_w + (mb_x / 16) as usize] = qp;
2979}
2980
2981/// P_Skip マクロブロックのデコード。
2982fn decode_pskip(st: &mut DecState, mb_x: u32, mb_y: u32, qp: i32, refs: &[RefEntry]) {
2983    let mv = predict_pskip_mv(st, mb_x, mb_y);
2984    let (px, py) = (mb_x as usize, mb_y as usize);
2985    // P_Skip は refIdxL0 = 0(RefPicList0 の先頭)。
2986    let id = mc_pred(st, px, py, 16, 16, [mv, [0, 0]], [0, -1], [refs, &[]]).map_or(0, |ids| ids[0]);
2987    store_part_mv(st, px, py, 16, 16, mv, 0, id);
2988    st.mb_qp[(mb_y / 16) as usize * st.mb_w + (mb_x / 16) as usize] = qp;
2989}
2990
2991/// インター MB の輝度残差を MC 予測へ加算(4x4 ブロックごと)。
2992fn add_inter_luma_residual<R: MbSyntax>(
2993    r: &mut R,
2994    st: &mut DecState,
2995    mb_x: u32,
2996    mb_y: u32,
2997    cbp_luma: u32,
2998    qp: i32,
2999    t8: bool,
3000) -> Result<(), &'static str> {
3001    let w = st.yuv.width as usize;
3002    let bx0 = (mb_x / 4) as usize;
3003    if t8 {
3004        let by0 = (mb_y / 4) as usize;
3005        for i in 0..4 {
3006            let (bx4, by4) = (bx0 + (i % 2) * 2, by0 + (i / 2) * 2);
3007            let mut cnt = [0u8; 4];
3008            if cbp_luma & (1 << i) != 0 {
3009                let (scan, c) = r.residual8x8(st, bx4, by4)?;
3010                cnt = c;
3011                let res = residual_8x8_to_pixels(&scan, qp);
3012                let (px, py) = (bx4 * 4, by4 * 4);
3013                for y in 0..8 {
3014                    for x in 0..8 {
3015                        let o = (py + y) * w + px + x;
3016                        st.yuv.y[o] = clip1(st.yuv.y[o] as i32 + res[y * 8 + x]) as u8;
3017                    }
3018                }
3019            }
3020            for (k, (dx, dy)) in [(0, 0), (1, 0), (0, 1), (1, 1)].into_iter().enumerate() {
3021                let li = st.li(bx4 + dx, by4 + dy);
3022                st.nnz_luma[li] = cnt[k];
3023            }
3024        }
3025        return Ok(());
3026    }
3027    let by0 = (mb_y / 4) as usize;
3028    for blk in 0..16 {
3029        let (bx, by) = LUMA_4X4_BLOCK_XY[blk];
3030        let bx4 = bx0 + bx / 4;
3031        let by4 = by0 + by / 4;
3032        let li = by4 * st.blk_w + bx4;
3033        if cbp_luma & (1 << (blk / 4)) != 0 {
3034            let (scan, tc) = r.residual(st, ResBlock::Luma4x4 { bx4, by4 })?;
3035            st.nnz_luma[li] = tc;
3036            let mut block = [0i32; 16];
3037            for k in 0..16 {
3038                block[ZIGZAG_4X4[k]] = scan[k];
3039            }
3040            dequant_4x4(&mut block, qp);
3041            inverse_transform_4x4(&mut block);
3042            for yy in 0..4 {
3043                for xx in 0..4 {
3044                    let px = mb_x as usize + bx + xx;
3045                    let py = mb_y as usize + by + yy;
3046                    let cur = st.yuv.y[py * w + px] as i32;
3047                    st.yuv.y[py * w + px] = clip1(cur + block[yy * 4 + xx]) as u8;
3048                }
3049            }
3050        } else {
3051            st.nnz_luma[li] = 0;
3052        }
3053    }
3054    Ok(())
3055}
3056
3057/// インター MB のクロマ残差を MC 予測へ加算(DC 2x2 Hadamard + AC)。
3058fn add_inter_chroma_residual<R: MbSyntax>(
3059    r: &mut R,
3060    st: &mut DecState,
3061    mb_x: u32,
3062    mb_y: u32,
3063    cbp_chroma: u32,
3064    qp_y: i32,
3065    chroma_qp_off: [i32; 2],
3066) -> Result<(), &'static str> {
3067    let cw = (st.yuv.width / 2) as usize;
3068    let cmb_x = (mb_x / 2) as usize;
3069    let cmb_y = (mb_y / 2) as usize;
3070    let cbx0 = cmb_x / 4;
3071    let cby0 = cmb_y / 4;
3072    let qpcs = [chroma_qp(qp_y, chroma_qp_off[0]), chroma_qp(qp_y, chroma_qp_off[1])];
3073    let mut dc = [[0i32; 4]; 2];
3074    if cbp_chroma != 0 {
3075        for comp in 0..2 {
3076            let (scan, _) = r.residual(st, ResBlock::ChromaDc { comp, mb_x, mb_y })?;
3077            let f = inverse_hadamard_2x2([scan[0], scan[1], scan[2], scan[3]]);
3078            for i in 0..4 {
3079                dc[comp][i] = scale_chroma_dc(f[i], qpcs[comp]);
3080            }
3081        }
3082    }
3083    let mut ac = [[[0i32; 16]; 4]; 2];
3084    if cbp_chroma == 2 {
3085        for comp in 0..2 {
3086            for blk in 0..4 {
3087                let bx = (blk % 2) * 4;
3088                let by = (blk / 2) * 4;
3089                let cbx4 = cbx0 + bx / 4;
3090                let cby4 = cby0 + by / 4;
3091                let (scan, tc) = r.residual(st, ResBlock::ChromaAc { comp, cbx4, cby4 })?;
3092                for k in 0..15 {
3093                    ac[comp][blk][ZIGZAG_4X4[k + 1]] = scan[k];
3094                }
3095                st.nnz_chroma[comp][cby4 * st.cblk_w + cbx4] = tc;
3096            }
3097        }
3098    }
3099    if cbp_chroma == 0 {
3100        return Ok(());
3101    }
3102    for comp in 0..2 {
3103        let plane: &mut Vec<u8> = if comp == 0 {
3104            &mut st.yuv.u
3105        } else {
3106            &mut st.yuv.v
3107        };
3108        for blk in 0..4 {
3109            let bx = (blk % 2) * 4;
3110            let by = (blk / 2) * 4;
3111            let mut block = ac[comp][blk];
3112            dequant_4x4(&mut block, qpcs[comp]);
3113            block[0] = dc[comp][blk];
3114            inverse_transform_4x4(&mut block);
3115            for yy in 0..4 {
3116                for xx in 0..4 {
3117                    let px = cmb_x + bx + xx;
3118                    let py = cmb_y + by + yy;
3119                    let cur = plane[py * cw + px] as i32;
3120                    plane[py * cw + px] = clip1(cur + block[yy * 4 + xx]) as u8;
3121                }
3122            }
3123        }
3124    }
3125    Ok(())
3126}
3127
3128/// P スライスの符号化 MB。intra MB(mb_type>=5)は intra デコーダへ委譲。
3129/// P_8x8 / P_8x8ref0: 4 つの 8x8 サブ MB、各 sub_mb_type でさらに分割。
3130/// `ref_idx_l0`(te(v)、7.4.5.1)。値域が 0..=1 のときだけ 1 ビットの反転、それ以外は ue(v)。
3131/// 参照が 1 枚なら読まずに 0。
3132fn read_ref_idx(r: &mut BitReader, num_ref: u32) -> Result<i8, &'static str> {
3133    if num_ref <= 1 {
3134        return Ok(0);
3135    }
3136    let v = if num_ref == 2 { 1 - r.read_bit() } else { r.read_ue() };
3137    if v >= num_ref {
3138        return Err("ref_idx out of range");
3139    }
3140    Ok(v as i8)
3141}
3142
3143fn decode_p8x8<R: MbSyntax>(
3144    r: &mut R,
3145    st: &mut DecState,
3146    px: usize,
3147    py: usize,
3148    refs: &[RefEntry],
3149    ref0_only: bool,
3150) -> Result<bool, &'static str> {
3151    let mut sub_types = [0u32; 4];
3152    for s in sub_types.iter_mut() {
3153        *s = r.sub_mb_type(SliceKind::P)?;
3154        if *s > 3 {
3155            return Err("bad sub_mb_type");
3156        }
3157    }
3158    // ref_idx は 4 つの 8x8 ぶんを先に読む(P_8x8ref0 は全部 0)。
3159    let mut ref_idx = [0i8; 4];
3160    if !ref0_only {
3161        for (i, ri) in ref_idx.iter_mut().enumerate() {
3162            *ri = r.ref_idx(st, 0, px + (i % 2) * 8, py + (i / 2) * 8, 8, 8, st.num_ref[0])?;
3163        }
3164    }
3165    for (i, &stype) in sub_types.iter().enumerate() {
3166        let ri = ref_idx[i];
3167        let refr: RefEntry = *refs.get(ri as usize).ok_or("missing reference frame")?;
3168        let sx = px + (i % 2) * 8;
3169        let sy = py + (i / 2) * 8;
3170        let parts: &[(usize, usize, usize, usize)] = match stype {
3171            0 => &[(0, 0, 8, 8)],
3172            1 => &[(0, 0, 8, 4), (0, 4, 8, 4)],
3173            2 => &[(0, 0, 4, 8), (4, 0, 4, 8)],
3174            _ => &[(0, 0, 4, 4), (4, 0, 4, 4), (0, 4, 4, 4), (4, 4, 4, 4)],
3175        };
3176        for &(ox, oy, pw, ph) in parts {
3177            let ppx = sx + ox;
3178            let ppy = sy + oy;
3179            let [mvd_x, mvd_y] = r.mvd(st, 0, ppx, ppy, pw, ph)?;
3180            let mvp = mv_predict(st, ppx, ppy, pw, MvDir::None, ri);
3181            let mv = [mvp[0].wrapping_add(mvd_x), mvp[1].wrapping_add(mvd_y)];
3182            mc_pred(st, ppx, ppy, pw, ph, [mv, [0, 0]], [ri, -1], [refs, &[]])?;
3183            store_part_mv(st, ppx, ppy, pw, ph, mv, ri, refr.id);
3184        }
3185    }
3186    // 8x8 変換は全サブパーティションが 8x8 のときだけ。
3187    Ok(sub_types.iter().all(|&t| t == 0))
3188}
3189
3190/// インター: mb_type 0=16x16, 1=16x8, 2=8x16, 3/4=P_8x8。
3191#[allow(clippy::too_many_arguments)]
3192fn decode_mb_p<R: MbSyntax>(
3193    r: &mut R,
3194    st: &mut DecState,
3195    mb_x: u32,
3196    mb_y: u32,
3197    qp: &mut i32,
3198    pps: &Pps,
3199    refs: &[RefEntry],
3200) -> Result<(), &'static str> {
3201    let mb_type = r.mb_type(st, mb_x, mb_y, SliceKind::P)?;
3202    if mb_type >= 5 {
3203        let it = mb_type - 5;
3204        if it == 25 {
3205            return decode_mb_ipcm(r, st, mb_x, mb_y);
3206        }
3207        if it == 0 {
3208            return decode_mb_i4x4(r, st, mb_x, mb_y, qp, pps);
3209        }
3210        if (1..=24).contains(&it) {
3211            return decode_mb_i16x16(r, st, mb_x, mb_y, qp, it, pps);
3212        }
3213        return Err("bad intra mb_type in P slice");
3214    }
3215    let px = mb_x as usize;
3216    let py = mb_y as usize;
3217    // 1 パーティション分の mvd を読み、MV 予測 + MC + 格納を行う。
3218    // mb_pred() の並びは「全パーティションの ref_idx_l0 → 全パーティションの mvd_l0」(7.3.5.1)。
3219    let do_part = |r: &mut R,
3220                   st: &mut DecState,
3221                   ppx: usize,
3222                   ppy: usize,
3223                   pw: usize,
3224                   ph: usize,
3225                   dir: MvDir,
3226                   ri: i8|
3227     -> Result<(), &'static str> {
3228        let refr: RefEntry = *refs.get(ri as usize).ok_or("missing reference frame")?;
3229        let [mvd_x, mvd_y] = r.mvd(st, 0, ppx, ppy, pw, ph)?;
3230        let mvp = mv_predict(st, ppx, ppy, pw, dir, ri);
3231        let mv = [mvp[0].wrapping_add(mvd_x), mvp[1].wrapping_add(mvd_y)];
3232        mc_pred(st, ppx, ppy, pw, ph, [mv, [0, 0]], [ri, -1], [refs, &[]])?;
3233        store_part_mv(st, ppx, ppy, pw, ph, mv, ri, refr.id);
3234        Ok(())
3235    };
3236    let mut t8_ok = true;
3237    match mb_type {
3238        0 => {
3239            // P_L0_16x16
3240            let r0 = r.ref_idx(st, 0, px, py, 16, 16, st.num_ref[0])?;
3241            do_part(r, st, px, py, 16, 16, MvDir::None, r0)?;
3242        }
3243        1 => {
3244            // P_L0_L0_16x8(上→下)。上=Prefer B, 下=Prefer A。
3245            let r0 = r.ref_idx(st, 0, px, py, 16, 8, st.num_ref[0])?;
3246            let r1 = r.ref_idx(st, 0, px, py + 8, 16, 8, st.num_ref[0])?;
3247            do_part(r, st, px, py, 16, 8, MvDir::B, r0)?;
3248            do_part(r, st, px, py + 8, 16, 8, MvDir::A, r1)?;
3249        }
3250        2 => {
3251            // P_L0_L0_8x16(左→右)。左=Prefer A, 右=Prefer C。
3252            let r0 = r.ref_idx(st, 0, px, py, 8, 16, st.num_ref[0])?;
3253            let r1 = r.ref_idx(st, 0, px + 8, py, 8, 16, st.num_ref[0])?;
3254            do_part(r, st, px, py, 8, 16, MvDir::A, r0)?;
3255            do_part(r, st, px + 8, py, 8, 16, MvDir::C, r1)?;
3256        }
3257        3 | 4 => {
3258            // P_8x8 / P_8x8ref0(ref0 は ref_idx を読まず全部 0)
3259            t8_ok = decode_p8x8(r, st, px, py, refs, mb_type == 4)?;
3260        }
3261        _ => return Err("unknown P mb_type"),
3262    }
3263    decode_inter_residual(r, st, mb_x, mb_y, qp, pps, t8_ok)
3264}
3265
3266/// インター MB(P/B の符号化 MB)の coded_block_pattern・mb_qp_delta・残差。
3267fn decode_inter_residual<R: MbSyntax>(
3268    r: &mut R,
3269    st: &mut DecState,
3270    mb_x: u32,
3271    mb_y: u32,
3272    qp: &mut i32,
3273    pps: &Pps,
3274    t8_allowed: bool,
3275) -> Result<(), &'static str> {
3276    let cbp = r.cbp(st, mb_x, mb_y, false)?;
3277    let cbp_luma = cbp & 0xf;
3278    let cbp_chroma = cbp >> 4;
3279    // transform_size_8x8_flag(7.3.5): 8x8 未満のパーティションや、8x8 推論の無いダイレクトでは無い。
3280    let t8 = t8_allowed && cbp_luma != 0 && pps.transform_8x8_mode_flag && r.transform_8x8(st, mb_x, mb_y);
3281    st.mb_t8[(mb_y / 16) as usize * st.mb_w + (mb_x / 16) as usize] = t8;
3282    let cur_qp = if cbp != 0 {
3283        let d = r.qp_delta()?;
3284        if !(-26..=25).contains(&d) {
3285            return Err("mb_qp_delta out of range");
3286        }
3287        *qp = (*qp + d + 52) % 52;
3288        *qp
3289    } else {
3290        *qp
3291    };
3292    st.mb_qp[(mb_y / 16) as usize * st.mb_w + (mb_x / 16) as usize] = cur_qp;
3293
3294    add_inter_luma_residual(r, st, mb_x, mb_y, cbp_luma, cur_qp, t8)?;
3295    add_inter_chroma_residual(
3296        r,
3297        st,
3298        mb_x,
3299        mb_y,
3300        cbp_chroma,
3301        cur_qp,
3302        pps.chroma_qp_offsets(),
3303    )
3304}
3305
3306// ===================== B スライス =====================
3307//
3308// 【2026-10-17】B スライス(CAVLC、空間ダイレクト、既定の双予測平均)。時間ダイレクト・
3309// 重み付き予測(明示/暗黙)は未対応で、スライスヘッダの段階で失敗を返す。
3310
3311/// B の MB が使う参照リストと、空間ダイレクトの co-located の取り方。
3312struct BCtx<'a> {
3313    l: [&'a [RefEntry<'a>]; 2],
3314    direct_8x8: bool,
3315}
3316
3317/// パーティションの予測リスト(ビット 0 = L0、ビット 1 = L1、3 = 双予測)。
3318const PRED_L0: u8 = 1;
3319const PRED_L1: u8 = 2;
3320const PRED_BI: u8 = 3;
3321
3322/// B_x_y_16x8 / 8x16(mb_type 4..=21)の 2 パーティションの予測リスト(表 7-14、2 つずつ同じ組)。
3323const B_PART_PAIRS: [(u8, u8); 9] = [
3324    (PRED_L0, PRED_L0),
3325    (PRED_L1, PRED_L1),
3326    (PRED_L0, PRED_L1),
3327    (PRED_L1, PRED_L0),
3328    (PRED_L0, PRED_BI),
3329    (PRED_L1, PRED_BI),
3330    (PRED_BI, PRED_L0),
3331    (PRED_BI, PRED_L1),
3332    (PRED_BI, PRED_BI),
3333];
3334
3335/// B_8x8 の sub_mb_type(表 7-18)→ (予測リスト, 幅, 高さ)。0 は B_Direct_8x8。
3336const B_SUB_MB: [(u8, usize, usize); 13] = [
3337    (0, 8, 8),
3338    (PRED_L0, 8, 8),
3339    (PRED_L1, 8, 8),
3340    (PRED_BI, 8, 8),
3341    (PRED_L0, 8, 4),
3342    (PRED_L0, 4, 8),
3343    (PRED_L1, 8, 4),
3344    (PRED_L1, 4, 8),
3345    (PRED_BI, 8, 4),
3346    (PRED_BI, 4, 8),
3347    (PRED_L0, 4, 4),
3348    (PRED_L1, 4, 4),
3349    (PRED_BI, 4, 4),
3350];
3351
3352/// 幅×高さのサブパーティションの左上オフセット(8x8 の中)。
3353fn sub_part_offsets(w: usize, h: usize) -> &'static [(usize, usize)] {
3354    match (w, h) {
3355        (8, 8) => &[(0, 0)],
3356        (8, 4) => &[(0, 0), (0, 4)],
3357        (4, 8) => &[(0, 0), (4, 0)],
3358        _ => &[(0, 0), (4, 0), (0, 4), (4, 4)],
3359    }
3360}
3361
3362/// 予測リストに応じた動き補償(8.4.2)。双予測は L0・L1 の予測の平均 (a + b + 1) >> 1
3363/// (重み無しの既定、8.4.2.3.1)。戻り値は各リストの参照ピクチャ識別子(使わないリストは 0)。
3364#[allow(clippy::too_many_arguments)]
3365fn mc_pred(
3366    st: &mut DecState,
3367    px: usize,
3368    py: usize,
3369    pw: usize,
3370    ph: usize,
3371    mv: [[i16; 2]; 2],
3372    ref_idx: [i8; 2],
3373    l: [&[RefEntry]; 2],
3374) -> Result<[i32; 2], &'static str> {
3375    let mut ent: [Option<RefEntry>; 2] = [None, None];
3376    for list in 0..2 {
3377        if ref_idx[list] >= 0 {
3378            ent[list] = Some(*l[list].get(ref_idx[list] as usize).ok_or("missing reference frame")?);
3379        }
3380    }
3381    match (ent[0], ent[1]) {
3382        (Some(a), None) => {
3383            mc_part(st, px, py, pw, ph, mv[0], a.yuv);
3384            weight_single(st, px, py, pw, ph, 0, ref_idx[0]);
3385        }
3386        (None, Some(b)) => {
3387            mc_part(st, px, py, pw, ph, mv[1], b.yuv);
3388            weight_single(st, px, py, pw, ph, 1, ref_idx[1]);
3389        }
3390        (Some(a), Some(b)) => {
3391            let w = st.yuv.width as usize;
3392            let cw = w / 2;
3393            let (cx, cy, cpw, cph) = (px / 2, py / 2, pw / 2, ph / 2);
3394            mc_part(st, px, py, pw, ph, mv[0], a.yuv);
3395            // L0 の予測を退避(輝度は最大 16x16、クロマは 8x8)。
3396            let mut sy = [0u8; 256];
3397            let mut su = [0u8; 64];
3398            let mut sv = [0u8; 64];
3399            for y in 0..ph {
3400                sy[y * pw..(y + 1) * pw].copy_from_slice(&st.yuv.y[(py + y) * w + px..(py + y) * w + px + pw]);
3401            }
3402            for y in 0..cph {
3403                let o = (cy + y) * cw + cx;
3404                su[y * cpw..(y + 1) * cpw].copy_from_slice(&st.yuv.u[o..o + cpw]);
3405                sv[y * cpw..(y + 1) * cpw].copy_from_slice(&st.yuv.v[o..o + cpw]);
3406            }
3407            mc_part(st, px, py, pw, ph, mv[1], b.yuv);
3408            // 成分ごとの (logWD, w0, w1, (o0 + o1 + 1) >> 1)。None は既定の平均。
3409            let wts: Option<[(i32, i32, i32, i32); 3]> = match &st.wp {
3410                WpMode::Off => None,
3411                WpMode::Explicit(t) => {
3412                    let e0 = t.w[0].get(ref_idx[0] as usize).ok_or("missing weight entry")?;
3413                    let e1 = t.w[1].get(ref_idx[1] as usize).ok_or("missing weight entry")?;
3414                    let lw = [t.luma_log2_denom as i32, t.chroma_log2_denom as i32, t.chroma_log2_denom as i32];
3415                    Some(core::array::from_fn(|c| (lw[c], e0[c].0, e1[c].0, (e0[c].1 + e1[c].1 + 1) >> 1)))
3416                }
3417                WpMode::Implicit => {
3418                    let (w0, w1) = implicit_weights(st.cur_poc, a.id, b.id);
3419                    Some([(5, w0, w1, 0); 3])
3420                }
3421            };
3422            let comb = |c: usize, p0: u8, p1: u8| -> u8 {
3423                match wts {
3424                    None => ((p0 as u16 + p1 as u16 + 1) >> 1) as u8,
3425                    Some(wt) => {
3426                        let (lw, w0, w1, o) = wt[c];
3427                        let v = ((p0 as i32 * w0 + p1 as i32 * w1 + (1 << lw)) >> (lw + 1)) + o;
3428                        v.clamp(0, 255) as u8
3429                    }
3430                }
3431            };
3432            for y in 0..ph {
3433                for x in 0..pw {
3434                    let o = (py + y) * w + px + x;
3435                    st.yuv.y[o] = comb(0, sy[y * pw + x], st.yuv.y[o]);
3436                }
3437            }
3438            for y in 0..cph {
3439                for x in 0..cpw {
3440                    let o = (cy + y) * cw + cx + x;
3441                    st.yuv.u[o] = comb(1, su[y * cpw + x], st.yuv.u[o]);
3442                    st.yuv.v[o] = comb(2, sv[y * cpw + x], st.yuv.v[o]);
3443                }
3444            }
3445        }
3446        (None, None) => return Err("partition without prediction list"),
3447    }
3448    Ok([ent[0].map_or(0, |e| e.id), ent[1].map_or(0, |e| e.id)])
3449}
3450
3451/// 単方向予測の明示的な重み付け(8.4.2.3.2)。明示モード以外は何もしない(暗黙モードの
3452/// 単方向は既定)。予測済みの領域をその場で置き換える。
3453#[allow(clippy::too_many_arguments)]
3454fn weight_single(st: &mut DecState, px: usize, py: usize, pw: usize, ph: usize, list: usize, ref_idx: i8) {
3455    let WpMode::Explicit(t) = &st.wp else { return };
3456    let Some(e) = t.w[list].get(ref_idx as usize).copied() else { return };
3457    let lw = [t.luma_log2_denom as i32, t.chroma_log2_denom as i32, t.chroma_log2_denom as i32];
3458    let f = |c: usize, p: u8| -> u8 {
3459        let (w, o) = e[c];
3460        let v = if lw[c] >= 1 {
3461            ((p as i32 * w + (1 << (lw[c] - 1))) >> lw[c]) + o
3462        } else {
3463            p as i32 * w + o
3464        };
3465        v.clamp(0, 255) as u8
3466    };
3467    let w = st.yuv.width as usize;
3468    let cw = w / 2;
3469    for y in 0..ph {
3470        for x in 0..pw {
3471            let o = (py + y) * w + px + x;
3472            st.yuv.y[o] = f(0, st.yuv.y[o]);
3473        }
3474    }
3475    for y in 0..ph / 2 {
3476        for x in 0..pw / 2 {
3477            let o = (py / 2 + y) * cw + px / 2 + x;
3478            st.yuv.u[o] = f(1, st.yuv.u[o]);
3479            st.yuv.v[o] = f(2, st.yuv.v[o]);
3480        }
3481    }
3482}
3483
3484/// 暗黙の重み付き双予測の重み (w0, w1)(8.4.2.3.1、logWD = 5)。POC の距離の比から求め、
3485/// 範囲外・同じ距離なら等分(32, 32)。
3486fn implicit_weights(cur_poc: i32, poc0: i32, poc1: i32) -> (i32, i32) {
3487    let td = (poc1 - poc0).clamp(-128, 127);
3488    if td == 0 {
3489        return (32, 32);
3490    }
3491    let tb = (cur_poc - poc0).clamp(-128, 127);
3492    let tx = (16384 + (td / 2).abs()) / td;
3493    let dsf = ((tb * tx + 32) >> 6).clamp(-1024, 1023);
3494    let w1 = dsf >> 2;
3495    if !(-64..=128).contains(&w1) {
3496        return (32, 32);
3497    }
3498    (64 - w1, w1)
3499}
3500
3501/// 空間ダイレクトの MB 単位の導出結果(8.4.1.2.2)。
3502struct SpatialDirect {
3503    ref_idx: [i8; 2],
3504    mvp: [[i16; 2]; 2],
3505    /// 近傍がどちらのリストも使っていない(directZeroPredictionFlag)。
3506    zero: bool,
3507}
3508
3509/// MinPositive(x, y)(8.4.1.2.2)。
3510fn min_positive(x: i8, y: i8) -> i8 {
3511    if x >= 0 && y >= 0 {
3512        x.min(y)
3513    } else {
3514        x.max(y)
3515    }
3516}
3517
3518/// 空間ダイレクトの refIdx と MV 予測を MB の近傍 A/B/C(16x16 として)から求める。
3519/// MB 内のパーティションを 1 つも格納する前に呼ぶこと。
3520fn spatial_direct(st: &DecState, mb_x: u32, mb_y: u32) -> SpatialDirect {
3521    let (px, py) = (mb_x as usize, mb_y as usize);
3522    let mut ref_idx = [-1i8; 2];
3523    for (list, ri) in ref_idx.iter_mut().enumerate() {
3524        let (a, b, c) = part_neighbors(st, px, py, 16, list);
3525        *ri = min_positive(a.ref_idx, min_positive(b.ref_idx, c.ref_idx));
3526    }
3527    if ref_idx[0] < 0 && ref_idx[1] < 0 {
3528        return SpatialDirect { ref_idx: [0, 0], mvp: [[0, 0]; 2], zero: true };
3529    }
3530    let mut mvp = [[0i16; 2]; 2];
3531    for list in 0..2 {
3532        if ref_idx[list] >= 0 {
3533            mvp[list] = mv_predict_l(st, px, py, 16, MvDir::None, ref_idx[list], list);
3534        }
3535    }
3536    SpatialDirect { ref_idx, mvp, zero: false }
3537}
3538
3539/// co-located(RefPicList1[0] の同じ位置)の 4x4 ブロックが「ほぼ静止」か(colZeroFlag、8.4.1.2.2)。
3540/// `(bx, by)` は MB 内の 4x4 位置。direct_8x8_inference では 8x8 の外側の角を使う(8.4.1.2.1)。
3541fn col_zero(col: &Motion, mb_x: u32, mb_y: u32, bx: usize, by: usize, direct_8x8: bool) -> bool {
3542    let (cx, cy) = if direct_8x8 {
3543        (if bx < 2 { 0 } else { 3 }, if by < 2 { 0 } else { 3 })
3544    } else {
3545        (bx, by)
3546    };
3547    let li = (mb_y as usize / 4 + cy) * col.blk_w + mb_x as usize / 4 + cx;
3548    let (ri, mv) = if col.refi[0].get(li).is_some_and(|&r| r >= 0) {
3549        (col.refi[0][li], col.mv[0][li])
3550    } else if col.refi[1].get(li).is_some_and(|&r| r >= 0) {
3551        (col.refi[1][li], col.mv[1][li])
3552    } else {
3553        return false; // イントラ
3554    };
3555    ri == 0 && (-1..=1).contains(&mv[0]) && (-1..=1).contains(&mv[1])
3556}
3557
3558/// 空間ダイレクトの 8x8(`i` 番目)を予測・格納する。direct_8x8_inference が無ければ 4x4 ごと。
3559fn direct_8x8(st: &mut DecState, d: &SpatialDirect, mb_x: u32, mb_y: u32, i: usize, ctx: &BCtx) -> Result<(), &'static str> {
3560    let col = ctx.l[1].first().and_then(|e| e.motion).ok_or("B slice needs co-located motion")?;
3561    let (sx, sy) = ((i % 2) * 8, (i / 2) * 8);
3562    let (bs, offs): (usize, &[(usize, usize)]) = if ctx.direct_8x8 {
3563        (8, &[(0, 0)])
3564    } else {
3565        (4, &[(0, 0), (4, 0), (0, 4), (4, 4)])
3566    };
3567    for &(ox, oy) in offs {
3568        let (bx, by) = ((sx + ox) / 4, (sy + oy) / 4);
3569        let mut mv = [[0i16; 2]; 2];
3570        if !d.zero {
3571            let cz = col_zero(col, mb_x, mb_y, bx, by, ctx.direct_8x8);
3572            for list in 0..2 {
3573                if d.ref_idx[list] >= 0 && !(d.ref_idx[list] == 0 && cz) {
3574                    mv[list] = d.mvp[list];
3575                }
3576            }
3577        }
3578        let (px, py) = (mb_x as usize + sx + ox, mb_y as usize + sy + oy);
3579        let ids = mc_pred(st, px, py, bs, bs, mv, d.ref_idx, ctx.l)?;
3580        store_part(st, px, py, bs, bs, mv, d.ref_idx, ids);
3581    }
3582    Ok(())
3583}
3584
3585/// B_Skip / B_Direct_16x16 の予測(残差は呼び出し側)。
3586fn decode_b_direct_mb(st: &mut DecState, mb_x: u32, mb_y: u32, ctx: &BCtx) -> Result<(), &'static str> {
3587    let d = spatial_direct(st, mb_x, mb_y);
3588    for i in 0..4 {
3589        direct_8x8(st, &d, mb_x, mb_y, i, ctx)?;
3590    }
3591    Ok(())
3592}
3593
3594/// B スライスの符号化 MB(mb_type: 0 = B_Direct_16x16、1..=21 = 16x16/16x8/8x16、22 = B_8x8、
3595/// 23 以上 = イントラ)。mb_pred の並びは ref_idx_l0 → ref_idx_l1 → mvd_l0 → mvd_l1(7.3.5.1)。
3596#[allow(clippy::too_many_arguments)]
3597fn decode_mb_b<R: MbSyntax>(
3598    r: &mut R,
3599    st: &mut DecState,
3600    mb_x: u32,
3601    mb_y: u32,
3602    qp: &mut i32,
3603    pps: &Pps,
3604    ctx: &BCtx,
3605) -> Result<(), &'static str> {
3606    let mb_type = r.mb_type(st, mb_x, mb_y, SliceKind::B)?;
3607    if mb_type >= 23 {
3608        let it = mb_type - 23;
3609        if it == 25 {
3610            return decode_mb_ipcm(r, st, mb_x, mb_y);
3611        }
3612        if it == 0 {
3613            return decode_mb_i4x4(r, st, mb_x, mb_y, qp, pps);
3614        }
3615        if (1..=24).contains(&it) {
3616            return decode_mb_i16x16(r, st, mb_x, mb_y, qp, it, pps);
3617        }
3618        return Err("bad intra mb_type in B slice");
3619    }
3620    let (px, py) = (mb_x as usize, mb_y as usize);
3621    let mut t8_ok = true;
3622    match mb_type {
3623        0 => {
3624            r.mark_direct(px, py, 16, 16);
3625            decode_b_direct_mb(st, mb_x, mb_y, ctx)?;
3626            t8_ok = ctx.direct_8x8;
3627        }
3628        22 => t8_ok = decode_b8x8(r, st, mb_x, mb_y, ctx)?,
3629        _ => {
3630            // (x, y, w, h, 予測リスト, MV 予測の方向)
3631            let mut parts = [(0usize, 0usize, 16usize, 16usize, 0u8, MvDir::None); 2];
3632            let n = if mb_type <= 3 {
3633                parts[0].4 = mb_type as u8; // 1 = L0, 2 = L1, 3 = Bi
3634                1
3635            } else {
3636                let k = (mb_type - 4) as usize;
3637                let (m0, m1) = B_PART_PAIRS[k / 2];
3638                if k.is_multiple_of(2) {
3639                    parts = [(0, 0, 16, 8, m0, MvDir::B), (0, 8, 16, 8, m1, MvDir::A)];
3640                } else {
3641                    parts = [(0, 0, 8, 16, m0, MvDir::A), (8, 0, 8, 16, m1, MvDir::C)];
3642                }
3643                2
3644            };
3645            let mut ri = [[-1i8; 2]; 2];
3646            let mut mvd = [[[0i16; 2]; 2]; 2];
3647            for list in 0..2 {
3648                for i in 0..n {
3649                    if parts[i].4 & (1 << list) != 0 {
3650                        let (ox, oy, pw, ph, _, _) = parts[i];
3651                        ri[i][list] = r.ref_idx(st, list, px + ox, py + oy, pw, ph, st.num_ref[list])?;
3652                    }
3653                }
3654            }
3655            for list in 0..2 {
3656                for i in 0..n {
3657                    if parts[i].4 & (1 << list) != 0 {
3658                        let (ox, oy, pw, ph, _, _) = parts[i];
3659                        mvd[i][list] = r.mvd(st, list, px + ox, py + oy, pw, ph)?;
3660                    }
3661                }
3662            }
3663            for i in 0..n {
3664                let (ox, oy, pw, ph, _, dir) = parts[i];
3665                let (ppx, ppy) = (px + ox, py + oy);
3666                let mut mv = [[0i16; 2]; 2];
3667                for list in 0..2 {
3668                    if ri[i][list] >= 0 {
3669                        let p = mv_predict_l(st, ppx, ppy, pw, dir, ri[i][list], list);
3670                        mv[list] = [p[0].wrapping_add(mvd[i][list][0]), p[1].wrapping_add(mvd[i][list][1])];
3671                    }
3672                }
3673                let ids = mc_pred(st, ppx, ppy, pw, ph, mv, ri[i], ctx.l)?;
3674                store_part(st, ppx, ppy, pw, ph, mv, ri[i], ids);
3675            }
3676        }
3677    }
3678    decode_inter_residual(r, st, mb_x, mb_y, qp, pps, t8_ok)
3679}
3680
3681/// B_8x8: 4 つの sub_mb_type(表 7-18)。B_Direct_8x8 の 8x8 は空間ダイレクト。
3682fn decode_b8x8<R: MbSyntax>(r: &mut R, st: &mut DecState, mb_x: u32, mb_y: u32, ctx: &BCtx) -> Result<bool, &'static str> {
3683    let mut sub = [0usize; 4];
3684    for s in sub.iter_mut() {
3685        *s = r.sub_mb_type(SliceKind::B)? as usize;
3686        if *s >= B_SUB_MB.len() {
3687            return Err("bad B sub_mb_type");
3688        }
3689    }
3690    let (mx, my) = (mb_x as usize, mb_y as usize);
3691    for (i, &t) in sub.iter().enumerate() {
3692        if t == 0 {
3693            r.mark_direct(mx + (i % 2) * 8, my + (i / 2) * 8, 8, 8);
3694        }
3695    }
3696    let mut ri = [[-1i8; 2]; 4];
3697    for list in 0..2 {
3698        for i in 0..4 {
3699            if B_SUB_MB[sub[i]].0 & (1 << list) != 0 {
3700                ri[i][list] = r.ref_idx(st, list, mx + (i % 2) * 8, my + (i / 2) * 8, 8, 8, st.num_ref[list])?;
3701            }
3702        }
3703    }
3704    let mut mvd = [[[[0i16; 2]; 4]; 2]; 4];
3705    for list in 0..2 {
3706        for i in 0..4 {
3707            let (mode, w, h) = B_SUB_MB[sub[i]];
3708            if mode & (1 << list) != 0 {
3709                for (k, &(ox, oy)) in sub_part_offsets(w, h).iter().enumerate() {
3710                    let (ppx, ppy) = (mx + (i % 2) * 8 + ox, my + (i / 2) * 8 + oy);
3711                    mvd[i][list][k] = r.mvd(st, list, ppx, ppy, w, h)?;
3712                }
3713            }
3714        }
3715    }
3716    // ダイレクトの導出は MB の近傍だけを見るので、MB 内を格納する前に 1 度だけ求める。
3717    let direct = if sub.contains(&0) { Some(spatial_direct(st, mb_x, mb_y)) } else { None };
3718    for i in 0..4 {
3719        if sub[i] == 0 {
3720            if let Some(d) = &direct {
3721                direct_8x8(st, d, mb_x, mb_y, i, ctx)?;
3722            }
3723            continue;
3724        }
3725        let (_, w, h) = B_SUB_MB[sub[i]];
3726        let sx = mb_x as usize + (i % 2) * 8;
3727        let sy = mb_y as usize + (i / 2) * 8;
3728        for (k, &(ox, oy)) in sub_part_offsets(w, h).iter().enumerate() {
3729            let (ppx, ppy) = (sx + ox, sy + oy);
3730            let mut mv = [[0i16; 2]; 2];
3731            for list in 0..2 {
3732                if ri[i][list] >= 0 {
3733                    let p = mv_predict_l(st, ppx, ppy, w, MvDir::None, ri[i][list], list);
3734                    mv[list] = [p[0].wrapping_add(mvd[i][list][k][0]), p[1].wrapping_add(mvd[i][list][k][1])];
3735                }
3736            }
3737            let ids = mc_pred(st, ppx, ppy, w, h, mv, ri[i], ctx.l)?;
3738            store_part(st, ppx, ppy, w, h, mv, ri[i], ids);
3739        }
3740    }
3741    // 8x8 変換は全サブ MB が 8x8(ダイレクトは 8x8 推論のとき)のときだけ。
3742    Ok(sub.iter().all(|&t| (1..=3).contains(&t) || (t == 0 && ctx.direct_8x8)))
3743}
3744
3745// ===================== 8x8 変換(High) =====================
3746//
3747// 【2026-10-17】High プロファイル(avc1.64xxxx)のため新設。スケーリング行列は平坦(16)のみ。
3748
3749/// 8x8 のジグザグ走査(フレーム): 走査位置 → ラスタ位置。
3750const ZIGZAG_8X8: [usize; 64] = [
3751    0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14,
3752    21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53,
3753    60, 61, 54, 47, 55, 62, 63,
3754];
3755
3756/// CABAC の 8x8(cat 5、フレーム)の significant_coeff_flag の ctxIdxInc(表 9-43)。
3757const SIG_CTX_8X8_FRAME: [u8; 63] = [
3758    0, 1, 2, 3, 4, 5, 5, 4, 4, 3, 3, 4, 4, 4, 5, 5, 4, 4, 4, 4, 3, 3, 6, 7, 7, 7, 8, 9, 10, 9, 8, 7, 7, 6, 11, 12,
3759    13, 11, 6, 7, 8, 9, 14, 10, 9, 8, 6, 11, 12, 13, 11, 6, 9, 14, 10, 9, 11, 12, 13, 11, 14, 10, 12,
3760];
3761
3762/// CABAC の 8x8 の last_significant_coeff_flag の ctxIdxInc(表 9-43)。
3763const LAST_CTX_8X8: [u8; 63] = [
3764    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3,
3765    3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8,
3766];
3767
3768/// normAdjust8x8 の 6 つの値(8.5.9、qP % 6 ごと)。
3769const NORM_ADJUST_8X8: [[i32; 6]; 6] = [
3770    [20, 18, 32, 19, 25, 24],
3771    [22, 19, 35, 21, 28, 26],
3772    [26, 23, 42, 24, 33, 31],
3773    [28, 25, 45, 26, 35, 33],
3774    [32, 28, 51, 30, 40, 38],
3775    [36, 32, 58, 34, 46, 43],
3776];
3777
3778/// 8x8 の係数位置 (i, j) が normAdjust8x8 のどの値を使うか。
3779fn norm8_class(i: usize, j: usize) -> usize {
3780    match (i & 3, j & 3) {
3781        (0, 0) => 0,
3782        (1 | 3, 1 | 3) => 1,
3783        (2, 2) => 2,
3784        (0, 1 | 3) | (1 | 3, 0) => 3,
3785        (0, 2) | (2, 0) => 4,
3786        _ => 5,
3787    }
3788}
3789
3790/// 8x8 の逆量子化(8.5.13.1、平坦な weightScale = 16)。`b` はラスタ順。
3791fn dequant_8x8(b: &mut [i32; 64], qp: i32) {
3792    let m = (qp % 6) as usize;
3793    let sh = qp / 6;
3794    for (k, c) in b.iter_mut().enumerate() {
3795        if *c == 0 {
3796            continue;
3797        }
3798        let ls = 16 * NORM_ADJUST_8X8[m][norm8_class(k / 8, k % 8)];
3799        *c = if sh >= 6 { (*c * ls) << (sh - 6) } else { (*c * ls + (1 << (5 - sh))) >> (6 - sh) };
3800    }
3801}
3802
3803/// 8x8 の 1 次元逆変換(8.5.13.2)。
3804fn idct8_1d(d: [i32; 8]) -> [i32; 8] {
3805    let a0 = d[0] + d[4];
3806    let a4 = d[0] - d[4];
3807    let a2 = (d[2] >> 1) - d[6];
3808    let a6 = d[2] + (d[6] >> 1);
3809    let b0 = a0 + a6;
3810    let b2 = a4 + a2;
3811    let b4 = a4 - a2;
3812    let b6 = a0 - a6;
3813    let a1 = -d[3] + d[5] - d[7] - (d[7] >> 1);
3814    let a3 = d[1] + d[7] - d[3] - (d[3] >> 1);
3815    let a5 = -d[1] + d[7] + d[5] + (d[5] >> 1);
3816    let a7 = d[3] + d[5] + d[1] + (d[1] >> 1);
3817    let b1 = a1 + (a7 >> 2);
3818    let b7 = a7 - (a1 >> 2);
3819    let b3 = a3 + (a5 >> 2);
3820    let b5 = (a3 >> 2) - a5;
3821    [b0 + b7, b2 + b5, b4 + b3, b6 + b1, b6 - b1, b4 - b3, b2 - b5, b0 - b7]
3822}
3823
3824/// 8x8 の逆変換(行 → 列、最後に (x + 32) >> 6)。`b` はラスタ順で、残差に置き換える。
3825fn inverse_transform_8x8(b: &mut [i32; 64]) {
3826    for r in 0..8 {
3827        let row: [i32; 8] = core::array::from_fn(|c| b[r * 8 + c]);
3828        let o = idct8_1d(row);
3829        b[r * 8..r * 8 + 8].copy_from_slice(&o);
3830    }
3831    for c in 0..8 {
3832        let col: [i32; 8] = core::array::from_fn(|r| b[r * 8 + c]);
3833        let o = idct8_1d(col);
3834        for r in 0..8 {
3835            b[r * 8 + c] = (o[r] + 32) >> 6;
3836        }
3837    }
3838}
3839
3840/// 8x8 の係数(走査順)→ 逆量子化・逆変換済みの残差(ラスタ順)。
3841fn residual_8x8_to_pixels(scan: &[i32; 64], qp: i32) -> [i32; 64] {
3842    let mut b = [0i32; 64];
3843    for k in 0..64 {
3844        b[ZIGZAG_8X8[k]] = scan[k];
3845    }
3846    dequant_8x8(&mut b, qp);
3847    inverse_transform_8x8(&mut b);
3848    b
3849}
3850
3851impl DecState {
3852    /// Intra8x8 予測(8.3.2.2)。参照画素を [1, 2, 1] で平滑化してから 9 モードで予測する。
3853    fn predict_luma_8x8(&self, px: usize, py: usize, mode: u8) -> [i32; 64] {
3854        let w = self.yuv.width as usize;
3855        let (bx4, by4) = (px / 4, py / 4);
3856        let top = self.avail_px(px as isize, py as isize - 1);
3857        let left = self.avail_px(px as isize - 1, py as isize);
3858        let tl = self.avail_px(px as isize - 1, py as isize - 1);
3859        let tr = top
3860            && px + 8 < w
3861            && by4 > 0
3862            && self.avail_blk(bx4 as isize + 2, by4 as isize - 1)
3863            && self.decoded[self.li(bx4 + 2, by4 - 1)];
3864        let pix = |x: isize, y: isize| self.yuv.y[(py as isize + y) as usize * w + (px as isize + x) as usize] as i32;
3865        let mut t = [0i32; 16];
3866        let mut l = [0i32; 8];
3867        if top {
3868            let mut tp = [0i32; 16];
3869            for (x, v) in tp.iter_mut().enumerate() {
3870                *v = if x < 8 || tr { pix(x as isize, -1) } else { pix(7, -1) };
3871            }
3872            t[0] = ((if tl { pix(-1, -1) } else { tp[0] }) + 2 * tp[0] + tp[1] + 2) >> 2;
3873            for x in 1..15 {
3874                t[x] = (tp[x - 1] + 2 * tp[x] + tp[x + 1] + 2) >> 2;
3875            }
3876            t[15] = (tp[14] + 3 * tp[15] + 2) >> 2;
3877        }
3878        if left {
3879            let lp: [i32; 8] = core::array::from_fn(|y| pix(-1, y as isize));
3880            l[0] = ((if tl { pix(-1, -1) } else { lp[0] }) + 2 * lp[0] + lp[1] + 2) >> 2;
3881            for y in 1..7 {
3882                l[y] = (lp[y - 1] + 2 * lp[y] + lp[y + 1] + 2) >> 2;
3883            }
3884            l[7] = (lp[6] + 3 * lp[7] + 2) >> 2;
3885        }
3886        let mut q = 0;
3887        if tl {
3888            let c = pix(-1, -1);
3889            q = match (top, left) {
3890                (true, true) => (pix(0, -1) + 2 * c + pix(-1, 0) + 2) >> 2,
3891                (true, false) => (3 * c + pix(0, -1) + 2) >> 2,
3892                (false, true) => (3 * c + pix(-1, 0) + 2) >> 2,
3893                _ => c,
3894            };
3895        }
3896        // 添字 -1 は左上の角 Q。
3897        let tt = |i: i32| if i < 0 { q } else { t[i as usize] };
3898        let ll = |i: i32| if i < 0 { q } else { l[i as usize] };
3899        let mut pred = [0i32; 64];
3900        for y in 0..8i32 {
3901            for x in 0..8i32 {
3902                let v = match mode {
3903                    0 => tt(x),
3904                    1 => ll(y),
3905                    2 => match (top, left) {
3906                        (true, true) => (t[..8].iter().sum::<i32>() + l.iter().sum::<i32>() + 8) >> 4,
3907                        (false, true) => (l.iter().sum::<i32>() + 4) >> 3,
3908                        (true, false) => (t[..8].iter().sum::<i32>() + 4) >> 3,
3909                        _ => 128,
3910                    },
3911                    3 => {
3912                        if x == 7 && y == 7 {
3913                            (tt(14) + 3 * tt(15) + 2) >> 2
3914                        } else {
3915                            (tt(x + y) + 2 * tt(x + y + 1) + tt(x + y + 2) + 2) >> 2
3916                        }
3917                    }
3918                    4 => {
3919                        if x > y {
3920                            (tt(x - y - 2) + 2 * tt(x - y - 1) + tt(x - y) + 2) >> 2
3921                        } else if x < y {
3922                            (ll(y - x - 2) + 2 * ll(y - x - 1) + ll(y - x) + 2) >> 2
3923                        } else {
3924                            (tt(0) + 2 * q + ll(0) + 2) >> 2
3925                        }
3926                    }
3927                    5 => {
3928                        let z = 2 * x - y;
3929                        if z >= 0 && z % 2 == 0 {
3930                            (tt(x - (y >> 1) - 1) + tt(x - (y >> 1)) + 1) >> 1
3931                        } else if z >= 0 {
3932                            (tt(x - (y >> 1) - 2) + 2 * tt(x - (y >> 1) - 1) + tt(x - (y >> 1)) + 2) >> 2
3933                        } else if z == -1 {
3934                            (ll(0) + 2 * q + tt(0) + 2) >> 2
3935                        } else {
3936                            (ll(y - 2 * x - 1) + 2 * ll(y - 2 * x - 2) + ll(y - 2 * x - 3) + 2) >> 2
3937                        }
3938                    }
3939                    6 => {
3940                        let z = 2 * y - x;
3941                        if z >= 0 && z % 2 == 0 {
3942                            (ll(y - (x >> 1) - 1) + ll(y - (x >> 1)) + 1) >> 1
3943                        } else if z >= 0 {
3944                            (ll(y - (x >> 1) - 2) + 2 * ll(y - (x >> 1) - 1) + ll(y - (x >> 1)) + 2) >> 2
3945                        } else if z == -1 {
3946                            (ll(0) + 2 * q + tt(0) + 2) >> 2
3947                        } else {
3948                            (tt(x - 2 * y - 1) + 2 * tt(x - 2 * y - 2) + tt(x - 2 * y - 3) + 2) >> 2
3949                        }
3950                    }
3951                    7 => {
3952                        if y % 2 == 0 {
3953                            (tt(x + (y >> 1)) + tt(x + (y >> 1) + 1) + 1) >> 1
3954                        } else {
3955                            (tt(x + (y >> 1)) + 2 * tt(x + (y >> 1) + 1) + tt(x + (y >> 1) + 2) + 2) >> 2
3956                        }
3957                    }
3958                    _ => {
3959                        let z = x + 2 * y;
3960                        if z > 13 {
3961                            ll(7)
3962                        } else if z == 13 {
3963                            (ll(6) + 3 * ll(7) + 2) >> 2
3964                        } else if z % 2 == 0 {
3965                            (ll(y + (x >> 1)) + ll(y + (x >> 1) + 1) + 1) >> 1
3966                        } else {
3967                            (ll(y + (x >> 1)) + 2 * ll(y + (x >> 1) + 1) + ll(y + (x >> 1) + 2) + 2) >> 2
3968                        }
3969                    }
3970                };
3971                pred[(y * 8 + x) as usize] = v;
3972            }
3973        }
3974        pred
3975    }
3976}
3977
3978/// 輝度 4x4 ブロックの再構成(予測 + 残差)を行い、グリッドを更新する。
3979fn reconstruct_luma_4x4(st: &mut DecState, px: usize, py: usize, mode: u8, residual: &[i32; 16]) {
3980    let pred = st.predict_luma_4x4(px, py, mode);
3981    let w = st.yuv.width as usize;
3982    for yy in 0..4 {
3983        for xx in 0..4 {
3984            let v = clip1(pred[yy * 4 + xx] + residual[yy * 4 + xx]);
3985            st.yuv.y[(py + yy) * w + px + xx] = v as u8;
3986        }
3987    }
3988    let di = st.li(px / 4, py / 4);
3989    st.decoded[di] = true;
3990}
3991
3992fn decode_mb_i16x16<R: MbSyntax>(
3993    r: &mut R,
3994    st: &mut DecState,
3995    mb_x: u32,
3996    mb_y: u32,
3997    qp: &mut i32,
3998    mb_type: u32,
3999    pps: &Pps,
4000) -> Result<(), &'static str> {
4001    let m1 = mb_type - 1;
4002    let i16_pred = (m1 % 4) as u8;
4003    let cbp_chroma = (m1 / 4) % 3;
4004    let cbp_luma = if m1 / 12 != 0 { 15 } else { 0 };
4005
4006    let intra_chroma_pred_mode = r.intra_chroma_mode(st, mb_x, mb_y)?;
4007    let mb_qp_delta = r.qp_delta()?;
4008    if !(-26..=25).contains(&mb_qp_delta) {
4009        return Err("mb_qp_delta out of range");
4010    }
4011    *qp = (*qp + mb_qp_delta + 52) % 52;
4012    let cur_qp = *qp;
4013    st.mb_qp[(mb_y / 16) as usize * st.mb_w + (mb_x / 16) as usize] = cur_qp;
4014
4015    // 輝度 DC(16 係数, Hadamard)。nC は近傍 4x4 ブロックの nnz から。
4016    let bx0 = (mb_x / 4) as usize;
4017    let by0 = (mb_y / 4) as usize;
4018    let (dc_scan, _) = r.residual(st, ResBlock::LumaDc { bx4: bx0, by4: by0 })?;
4019    let mut dc_block = [0i32; 16];
4020    for k in 0..16 {
4021        dc_block[ZIGZAG_4X4[k]] = dc_scan[k];
4022    }
4023    inverse_hadamard_4x4(&mut dc_block);
4024    scale_luma_dc(&mut dc_block, cur_qp);
4025
4026    // 輝度 AC(cbp_luma のとき各 4x4)。
4027    let mut luma_ac = [[0i32; 16]; 16];
4028    for blk in 0..16 {
4029        let (bx, by) = LUMA_4X4_BLOCK_XY[blk];
4030        let bx4 = bx0 + bx / 4;
4031        let by4 = by0 + by / 4;
4032        let li = st.li(bx4, by4);
4033        if cbp_luma != 0 {
4034            let (ac_scan, tc) = r.residual(st, ResBlock::LumaAc { bx4, by4 })?;
4035            for k in 0..15 {
4036                luma_ac[blk][ZIGZAG_4X4[k + 1]] = ac_scan[k];
4037            }
4038            st.nnz_luma[li] = tc;
4039        } else {
4040            st.nnz_luma[li] = 0;
4041        }
4042    }
4043
4044    // 輝度予測(I_16x16、MB 全体)。
4045    let w = st.yuv.width as usize;
4046    let mut top = [0i32; 16];
4047    let mut left = [0i32; 16];
4048    let top_avail = st.avail_px(mb_x as isize, mb_y as isize - 1);
4049    let left_avail = st.avail_px(mb_x as isize - 1, mb_y as isize);
4050    if top_avail {
4051        for x in 0..16 {
4052            top[x] = st.yuv.y[(mb_y as usize - 1) * w + mb_x as usize + x] as i32;
4053        }
4054    }
4055    if left_avail {
4056        for y in 0..16 {
4057            left[y] = st.yuv.y[(mb_y as usize + y) * w + mb_x as usize - 1] as i32;
4058        }
4059    }
4060    let corner = if top_avail && left_avail {
4061        st.yuv.y[(mb_y as usize - 1) * w + mb_x as usize - 1] as i32
4062    } else {
4063        0
4064    };
4065    let pred = intra_16x16_predict(i16_pred, &top, corner, &left, top_avail, left_avail);
4066
4067    // 輝度再構成。
4068    for blk in 0..16 {
4069        let (bx, by) = LUMA_4X4_BLOCK_XY[blk];
4070        let dc_idx = (by / 4) * 4 + (bx / 4);
4071        let mut block = luma_ac[blk];
4072        dequant_4x4(&mut block, cur_qp);
4073        block[0] = dc_block[dc_idx];
4074        inverse_transform_4x4(&mut block);
4075        for yy in 0..4 {
4076            for xx in 0..4 {
4077                let px = mb_x as usize + bx + xx;
4078                let py = mb_y as usize + by + yy;
4079                let p = pred[(by + yy) * 16 + (bx + xx)];
4080                st.yuv.y[py * w + px] = clip1(p + block[yy * 4 + xx]) as u8;
4081            }
4082        }
4083        let di = st.li((mb_x as usize + bx) / 4, (mb_y as usize + by) / 4);
4084        st.decoded[di] = true;
4085    }
4086
4087    decode_chroma(
4088        r,
4089        st,
4090        mb_x,
4091        mb_y,
4092        cbp_chroma,
4093        intra_chroma_pred_mode,
4094        cur_qp,
4095        pps.chroma_qp_offsets(),
4096    )
4097}
4098
4099fn decode_mb_i4x4<R: MbSyntax>(
4100    r: &mut R,
4101    st: &mut DecState,
4102    mb_x: u32,
4103    mb_y: u32,
4104    qp: &mut i32,
4105    pps: &Pps,
4106) -> Result<(), &'static str> {
4107    let bx0 = (mb_x / 4) as usize;
4108    let by0 = (mb_y / 4) as usize;
4109    if pps.transform_8x8_mode_flag && r.transform_8x8(st, mb_x, mb_y) {
4110        return decode_mb_i8x8(r, st, mb_x, mb_y, qp, pps);
4111    }
4112
4113    // 16 個の Intra4x4 予測モードを読む。
4114    let mut modes = [0u8; 16];
4115    for blk in 0..16 {
4116        let (bx, by) = LUMA_4X4_BLOCK_XY[blk];
4117        let bx4 = bx0 + bx / 4;
4118        let by4 = by0 + by / 4;
4119        let pred_mode = st.pred_i4x4_mode(bx4, by4);
4120        let mode = match r.intra4x4_rem() {
4121            None => pred_mode,
4122            Some(rem) if rem < pred_mode => rem,
4123            Some(rem) => rem + 1,
4124        };
4125        modes[blk] = mode as u8;
4126        let li = st.li(bx4, by4);
4127        st.mode_luma[li] = mode as i8;
4128    }
4129
4130    let intra_chroma_pred_mode = r.intra_chroma_mode(st, mb_x, mb_y)?;
4131    let cbp = r.cbp(st, mb_x, mb_y, true)?;
4132    let cbp_luma = cbp & 0xf;
4133    let cbp_chroma = cbp >> 4;
4134
4135    let cur_qp = if cbp != 0 {
4136        let mb_qp_delta = r.qp_delta()?;
4137        if !(-26..=25).contains(&mb_qp_delta) {
4138            return Err("mb_qp_delta out of range");
4139        }
4140        *qp = (*qp + mb_qp_delta + 52) % 52;
4141        *qp
4142    } else {
4143        *qp
4144    };
4145    st.mb_qp[(mb_y / 16) as usize * st.mb_w + (mb_x / 16) as usize] = cur_qp;
4146
4147    // ブロックごとに残差を読み、予測 + 再構成。
4148    for blk in 0..16 {
4149        let (bx, by) = LUMA_4X4_BLOCK_XY[blk];
4150        let bx4 = bx0 + bx / 4;
4151        let by4 = by0 + by / 4;
4152        let mut block = [0i32; 16];
4153        let li = st.li(bx4, by4);
4154        if cbp_luma & (1 << (blk / 4)) != 0 {
4155            let (scan, tc) = r.residual(st, ResBlock::Luma4x4 { bx4, by4 })?;
4156            for k in 0..16 {
4157                block[ZIGZAG_4X4[k]] = scan[k];
4158            }
4159            st.nnz_luma[li] = tc;
4160        } else {
4161            st.nnz_luma[li] = 0;
4162        }
4163        dequant_4x4(&mut block, cur_qp);
4164        inverse_transform_4x4(&mut block);
4165        reconstruct_luma_4x4(
4166            st,
4167            mb_x as usize + bx,
4168            mb_y as usize + by,
4169            modes[blk],
4170            &block,
4171        );
4172    }
4173
4174    decode_chroma(
4175        r,
4176        st,
4177        mb_x,
4178        mb_y,
4179        cbp_chroma,
4180        intra_chroma_pred_mode,
4181        cur_qp,
4182        pps.chroma_qp_offsets(),
4183    )
4184}
4185
4186/// I_NxN で transform_size_8x8_flag = 1(Intra8x8)。4 つの 8x8 の予測モード → クロマ予測モード
4187/// → cbp → mb_qp_delta → 8x8 ごとの残差(7.3.5)。
4188fn decode_mb_i8x8<R: MbSyntax>(
4189    r: &mut R,
4190    st: &mut DecState,
4191    mb_x: u32,
4192    mb_y: u32,
4193    qp: &mut i32,
4194    pps: &Pps,
4195) -> Result<(), &'static str> {
4196    let bx0 = (mb_x / 4) as usize;
4197    let by0 = (mb_y / 4) as usize;
4198    st.mb_t8[(mb_y / 16) as usize * st.mb_w + (mb_x / 16) as usize] = true;
4199    let mut modes = [0u8; 4];
4200    for (i, m) in modes.iter_mut().enumerate() {
4201        let (bx4, by4) = (bx0 + (i % 2) * 2, by0 + (i / 2) * 2);
4202        // 予測モードの予測は 8x8 の左上 4x4 の左・上(4x4 の MB でも 8x8 の MB でも同じ位置関係)。
4203        let pred_mode = st.pred_i4x4_mode(bx4, by4);
4204        let mode = match r.intra4x4_rem() {
4205            None => pred_mode,
4206            Some(rem) if rem < pred_mode => rem,
4207            Some(rem) => rem + 1,
4208        };
4209        *m = mode as u8;
4210        for (dx, dy) in [(0, 0), (1, 0), (0, 1), (1, 1)] {
4211            let li = st.li(bx4 + dx, by4 + dy);
4212            st.mode_luma[li] = mode as i8;
4213        }
4214    }
4215    let intra_chroma_pred_mode = r.intra_chroma_mode(st, mb_x, mb_y)?;
4216    let cbp = r.cbp(st, mb_x, mb_y, true)?;
4217    let cur_qp = if cbp != 0 {
4218        let d = r.qp_delta()?;
4219        if !(-26..=25).contains(&d) {
4220            return Err("mb_qp_delta out of range");
4221        }
4222        *qp = (*qp + d + 52) % 52;
4223        *qp
4224    } else {
4225        *qp
4226    };
4227    st.mb_qp[(mb_y / 16) as usize * st.mb_w + (mb_x / 16) as usize] = cur_qp;
4228    let w = st.yuv.width as usize;
4229    for i in 0..4 {
4230        let (bx4, by4) = (bx0 + (i % 2) * 2, by0 + (i / 2) * 2);
4231        let (px, py) = (bx4 * 4, by4 * 4);
4232        let mut res = [0i32; 64];
4233        let mut cnt = [0u8; 4];
4234        if cbp & (1 << i) != 0 {
4235            let (scan, c) = r.residual8x8(st, bx4, by4)?;
4236            res = residual_8x8_to_pixels(&scan, cur_qp);
4237            cnt = c;
4238        }
4239        let pred = st.predict_luma_8x8(px, py, modes[i]);
4240        for y in 0..8 {
4241            for x in 0..8 {
4242                st.yuv.y[(py + y) * w + px + x] = clip1(pred[y * 8 + x] + res[y * 8 + x]) as u8;
4243            }
4244        }
4245        for (k, (dx, dy)) in [(0, 0), (1, 0), (0, 1), (1, 1)].into_iter().enumerate() {
4246            let li = st.li(bx4 + dx, by4 + dy);
4247            st.nnz_luma[li] = cnt[k];
4248            st.decoded[li] = true;
4249        }
4250    }
4251    decode_chroma(r, st, mb_x, mb_y, cbp >> 4, intra_chroma_pred_mode, cur_qp, pps.chroma_qp_offsets())
4252}
4253
4254/// 輝度 QP -> クロマ QP(Table 8-15、qPi>=30 の写像)。
4255fn chroma_qp(qp_y: i32, offset: i32) -> i32 {
4256    let qpi = (qp_y + offset).clamp(0, 51);
4257    if qpi < 30 {
4258        qpi
4259    } else {
4260        const MAP: [i32; 22] = [
4261            29, 30, 31, 32, 32, 33, 34, 34, 35, 35, 36, 36, 37, 37, 38, 38, 39, 39, 39, 39, 39, 39,
4262        ];
4263        MAP[(qpi - 30) as usize]
4264    }
4265}
4266
4267/// 2x2 クロマ DC の逆 Hadamard 変換(raster 2x2、c[0..3])。
4268fn inverse_hadamard_2x2(c: [i32; 4]) -> [i32; 4] {
4269    [
4270        c[0] + c[1] + c[2] + c[3],
4271        c[0] - c[1] + c[2] - c[3],
4272        c[0] + c[1] - c[2] - c[3],
4273        c[0] - c[1] - c[2] + c[3],
4274    ]
4275}
4276
4277/// クロマ DC のスケーリング(8.5.11.1): dcC = ((f * LevelScale(qPc%6,0,0)) << (qPc/6)) >> 5。
4278fn scale_chroma_dc(f: i32, qpc: i32) -> i32 {
4279    let ls = 16 * NORM_ADJUST_4X4[(qpc % 6) as usize][0];
4280    ((f * ls) << (qpc / 6)) >> 5
4281}
4282
4283/// クロマ 4x4 ブロックの nC(クロマプレーン内の左/上 nnz から)。
4284fn chroma_nc(st: &DecState, comp: usize, cbx4: usize, cby4: usize) -> i32 {
4285    let na = if st.avail_px(cbx4 as isize * 8 - 1, cby4 as isize * 8) {
4286        st.nnz_chroma[comp][cby4 * st.cblk_w + cbx4 - 1] as i32
4287    } else {
4288        -1
4289    };
4290    let nb = if st.avail_px(cbx4 as isize * 8, cby4 as isize * 8 - 1) {
4291        st.nnz_chroma[comp][(cby4 - 1) * st.cblk_w + cbx4] as i32
4292    } else {
4293        -1
4294    };
4295    combine_nc(na, nb)
4296}
4297
4298/// クロマ復号(4:2:0)。予測 + 残差(DC: 2x2 Hadamard、AC: cbp_chroma==2 のとき)。
4299fn decode_chroma<R: MbSyntax>(
4300    r: &mut R,
4301    st: &mut DecState,
4302    mb_x: u32,
4303    mb_y: u32,
4304    cbp_chroma: u32,
4305    chroma_pred_mode: u8,
4306    qp_y: i32,
4307    chroma_qp_offset: [i32; 2],
4308) -> Result<(), &'static str> {
4309    let cw = (st.yuv.width / 2) as usize;
4310    let cmb_x = (mb_x / 2) as usize;
4311    let cmb_y = (mb_y / 2) as usize;
4312    let cbx0 = cmb_x / 4;
4313    let cby0 = cmb_y / 4;
4314    let qpcs = [chroma_qp(qp_y, chroma_qp_offset[0]), chroma_qp(qp_y, chroma_qp_offset[1])];
4315
4316    // 各成分の DC(2x2)と AC(4 ブロック)を読む。
4317    // 残差は raster 順: 成分0(Cb) DC, 成分1(Cr) DC, 次に AC(cbp_chroma==2)。
4318    let mut dc = [[0i32; 4]; 2];
4319    if cbp_chroma != 0 {
4320        for comp in 0..2 {
4321            let (scan, _) = r.residual(st, ResBlock::ChromaDc { comp, mb_x, mb_y })?;
4322            // chroma DC スキャン順は raster(2x2)そのまま
4323            let raw = [scan[0], scan[1], scan[2], scan[3]];
4324            let f = inverse_hadamard_2x2(raw);
4325            for i in 0..4 {
4326                dc[comp][i] = scale_chroma_dc(f[i], qpcs[comp]);
4327            }
4328        }
4329    }
4330    let mut ac = [[[0i32; 16]; 4]; 2]; // [comp][block][coeff(raster)]
4331    if cbp_chroma == 2 {
4332        for comp in 0..2 {
4333            for blk in 0..4 {
4334                let bx = (blk % 2) * 4;
4335                let by = (blk / 2) * 4;
4336                let cbx4 = cbx0 + bx / 4;
4337                let cby4 = cby0 + by / 4;
4338                let (scan, tc) = r.residual(st, ResBlock::ChromaAc { comp, cbx4, cby4 })?;
4339                for k in 0..15 {
4340                    ac[comp][blk][ZIGZAG_4X4[k + 1]] = scan[k];
4341                }
4342                st.nnz_chroma[comp][cby4 * st.cblk_w + cbx4] = tc;
4343            }
4344        }
4345    }
4346
4347    // 予測 + 再構成(成分ごと)。
4348    let ca_t = st.avail_px(cmb_x as isize * 2, cmb_y as isize * 2 - 1);
4349    let ca_l = st.avail_px(cmb_x as isize * 2 - 1, cmb_y as isize * 2);
4350    for comp in 0..2 {
4351        let plane: &mut Vec<u8> = if comp == 0 {
4352            &mut st.yuv.u
4353        } else {
4354            &mut st.yuv.v
4355        };
4356        let mut ct = [0i32; 8];
4357        let mut cl = [0i32; 8];
4358        if ca_t {
4359            for x in 0..8 {
4360                ct[x] = plane[(cmb_y - 1) * cw + cmb_x + x] as i32;
4361            }
4362        }
4363        if ca_l {
4364            for y in 0..8 {
4365                cl[y] = plane[(cmb_y + y) * cw + cmb_x - 1] as i32;
4366            }
4367        }
4368        let corner = if ca_t && ca_l {
4369            plane[(cmb_y - 1) * cw + cmb_x - 1] as i32
4370        } else {
4371            0
4372        };
4373        let cpred = intra_chroma_predict(chroma_pred_mode, &ct, corner, &cl, ca_t, ca_l);
4374
4375        for blk in 0..4 {
4376            let bx = (blk % 2) * 4;
4377            let by = (blk / 2) * 4;
4378            let mut block = ac[comp][blk];
4379            dequant_4x4(&mut block, qpcs[comp]);
4380            block[0] = dc[comp][blk]; // DC は別経路
4381            inverse_transform_4x4(&mut block);
4382            for yy in 0..4 {
4383                for xx in 0..4 {
4384                    let px = cmb_x + bx + xx;
4385                    let py = cmb_y + by + yy;
4386                    let p = cpred[(by + yy) * 8 + (bx + xx)];
4387                    plane[py * cw + px] = clip1(p + block[yy * 4 + xx]) as u8;
4388                }
4389            }
4390        }
4391    }
4392    Ok(())
4393}
4394
4395// ===================== デブロッキングフィルタ =====================
4396
4397fn iclip(v: i32, lo: i32, hi: i32) -> i32 {
4398    v.clamp(lo, hi)
4399}
4400
4401/// indexA/indexB から (alpha, beta, tc0) を引く。
4402fn deblock_thresh(qpav: i32, alpha_off: i32, beta_off: i32, bs: u8) -> (i32, i32, i32) {
4403    let ia = iclip(qpav + alpha_off, 0, 51) as usize;
4404    let ib = iclip(qpav + beta_off, 0, 51) as usize;
4405    // bS=4 は強フィルタで tc0 を使わない(TC0_TABLE は bS=1..3 のみ)。
4406    let tc0 = if (1..=3).contains(&bs) {
4407        TC0_TABLE[ia][(bs - 1) as usize] as i32
4408    } else {
4409        0
4410    };
4411    (ALPHA_TABLE[ia] as i32, BETA_TABLE[ib] as i32, tc0)
4412}
4413
4414/// 輝度 1 エッジ(長さ len)をフィルタ。vert: 垂直エッジ。(ex,ey)=q 側左上(フレーム座標)。
4415#[allow(clippy::too_many_arguments)]
4416fn deblock_luma_edge(
4417    plane: &mut [u8],
4418    w: usize,
4419    vert: bool,
4420    ex: usize,
4421    ey: usize,
4422    len: usize,
4423    bs: u8,
4424    alpha: i32,
4425    beta: i32,
4426    tc0: i32,
4427) {
4428    if bs == 0 || alpha == 0 {
4429        return;
4430    }
4431    let across: isize = if vert { 1 } else { w as isize };
4432    for i in 0..len {
4433        let p0i: isize = if vert {
4434            ((ey + i) * w + (ex - 1)) as isize
4435        } else {
4436            ((ey - 1) * w + (ex + i)) as isize
4437        };
4438        let idx = |o: isize| -> usize { (p0i + o * across) as usize };
4439        let p0 = plane[idx(0)] as i32;
4440        let p1 = plane[idx(-1)] as i32;
4441        let p2 = plane[idx(-2)] as i32;
4442        let p3 = plane[idx(-3)] as i32;
4443        let q0 = plane[idx(1)] as i32;
4444        let q1 = plane[idx(2)] as i32;
4445        let q2 = plane[idx(3)] as i32;
4446        if (p0 - q0).abs() >= alpha || (p1 - p0).abs() >= beta || (q1 - q0).abs() >= beta {
4447            continue;
4448        }
4449        let ap = (p2 - p0).abs();
4450        let aq = (q2 - q0).abs();
4451        if bs < 4 {
4452            let mut tc = tc0;
4453            if ap < beta {
4454                tc += 1;
4455            }
4456            if aq < beta {
4457                tc += 1;
4458            }
4459            let delta = iclip(((q0 - p0) * 4 + (p1 - q1) + 4) >> 3, -tc, tc);
4460            plane[idx(0)] = clip1(p0 + delta) as u8;
4461            plane[idx(1)] = clip1(q0 - delta) as u8;
4462            if ap < beta {
4463                let d = iclip((p2 + ((p0 + q0 + 1) >> 1) - 2 * p1) >> 1, -tc0, tc0);
4464                plane[idx(-1)] = clip1(p1 + d) as u8;
4465            }
4466            if aq < beta {
4467                let d = iclip((q2 + ((p0 + q0 + 1) >> 1) - 2 * q1) >> 1, -tc0, tc0);
4468                plane[idx(2)] = clip1(q1 + d) as u8;
4469            }
4470        } else {
4471            let strong = (p0 - q0).abs() < (alpha >> 2) + 2;
4472            if ap < beta && strong {
4473                plane[idx(0)] = clip1((p2 + 2 * p1 + 2 * p0 + 2 * q0 + q1 + 4) >> 3) as u8;
4474                plane[idx(-1)] = clip1((p2 + p1 + p0 + q0 + 2) >> 2) as u8;
4475                plane[idx(-2)] = clip1((2 * p3 + 3 * p2 + p1 + p0 + q0 + 4) >> 3) as u8;
4476            } else {
4477                plane[idx(0)] = clip1((2 * p1 + p0 + q1 + 2) >> 2) as u8;
4478            }
4479            if aq < beta && strong {
4480                let q3 = plane[idx(4)] as i32;
4481                plane[idx(1)] = clip1((q2 + 2 * q1 + 2 * q0 + 2 * p0 + p1 + 4) >> 3) as u8;
4482                plane[idx(2)] = clip1((q2 + q1 + q0 + p0 + 2) >> 2) as u8;
4483                plane[idx(3)] = clip1((2 * q3 + 3 * q2 + q1 + q0 + p0 + 4) >> 3) as u8;
4484            } else {
4485                plane[idx(1)] = clip1((2 * q1 + q0 + p1 + 2) >> 2) as u8;
4486            }
4487        }
4488    }
4489}
4490
4491/// クロマ 1 エッジ(4:2:0、p0/q0 のみ修正)。
4492#[allow(clippy::too_many_arguments)]
4493fn deblock_chroma_edge(
4494    plane: &mut [u8],
4495    cw: usize,
4496    vert: bool,
4497    ex: usize,
4498    ey: usize,
4499    len: usize,
4500    bs: u8,
4501    alpha: i32,
4502    beta: i32,
4503    tc0: i32,
4504) {
4505    if bs == 0 || alpha == 0 {
4506        return;
4507    }
4508    let across: isize = if vert { 1 } else { cw as isize };
4509    for i in 0..len {
4510        let p0i: isize = if vert {
4511            ((ey + i) * cw + (ex - 1)) as isize
4512        } else {
4513            ((ey - 1) * cw + (ex + i)) as isize
4514        };
4515        let idx = |o: isize| -> usize { (p0i + o * across) as usize };
4516        let p0 = plane[idx(0)] as i32;
4517        let p1 = plane[idx(-1)] as i32;
4518        let q0 = plane[idx(1)] as i32;
4519        let q1 = plane[idx(2)] as i32;
4520        if (p0 - q0).abs() >= alpha || (p1 - p0).abs() >= beta || (q1 - q0).abs() >= beta {
4521            continue;
4522        }
4523        if bs < 4 {
4524            let tc = tc0 + 1;
4525            let delta = iclip(((q0 - p0) * 4 + (p1 - q1) + 4) >> 3, -tc, tc);
4526            plane[idx(0)] = clip1(p0 + delta) as u8;
4527            plane[idx(1)] = clip1(q0 - delta) as u8;
4528        } else {
4529            plane[idx(0)] = clip1((2 * p1 + p0 + q1 + 2) >> 2) as u8;
4530            plane[idx(1)] = clip1((2 * q1 + q0 + p1 + 2) >> 2) as u8;
4531        }
4532    }
4533}
4534
4535/// フレーム全体にデブロッキングを適用(I スライス: MB 境界 bS=4、内部 bS=3)。
4536/// 2 つの 4x4 ブロック間の境界強度(8.7.2.1)。I/P 両対応。
4537/// p_li/q_li: 輝度 4x4 ブロックの線形 index。is_mb_edge: MB 境界エッジか。
4538fn boundary_strength(st: &DecState, p_li: usize, q_li: usize, is_mb_edge: bool) -> u8 {
4539    let p_intra = !st.is_inter[p_li];
4540    let q_intra = !st.is_inter[q_li];
4541    if p_intra || q_intra {
4542        return if is_mb_edge { 4 } else { 3 };
4543    }
4544    if st.has_coeffs(p_li) || st.has_coeffs(q_li) {
4545        return 2;
4546    }
4547    // 参照ピクチャ(refIdx ではなくピクチャそのもの)や MV の本数が違えば 1。同じなら、
4548    // 同じ参照ピクチャどうしの MV の差が 4(1 画素)以上で 1(8.7.2.1)。
4549    let far = |m: [i16; 2], n: [i16; 2]| (m[0] - n[0]).abs() >= 4 || (m[1] - n[1]).abs() >= 4;
4550    let used = |li: usize| [st.refi[0][li] >= 0, st.refi[1][li] >= 0];
4551    let (pu, qu) = (used(p_li), used(q_li));
4552    let pn = pu.iter().filter(|&&u| u).count();
4553    let qn = qu.iter().filter(|&&u| u).count();
4554    if pn != qn {
4555        return 1;
4556    }
4557    if pn == 1 {
4558        let pl = if pu[0] { 0 } else { 1 };
4559        let ql = if qu[0] { 0 } else { 1 };
4560        if st.refp[pl][p_li] != st.refp[ql][q_li] {
4561            return 1;
4562        }
4563        return far(st.mv[pl][p_li], st.mv[ql][q_li]) as u8;
4564    }
4565    let (p0, p1) = (st.refp[0][p_li], st.refp[1][p_li]);
4566    let (q0, q1) = (st.refp[0][q_li], st.refp[1][q_li]);
4567    if !((p0 == q0 && p1 == q1) || (p0 == q1 && p1 == q0)) {
4568        return 1;
4569    }
4570    let (pm0, pm1) = (st.mv[0][p_li], st.mv[1][p_li]);
4571    let (qm0, qm1) = (st.mv[0][q_li], st.mv[1][q_li]);
4572    let straight = far(pm0, qm0) || far(pm1, qm1);
4573    let crossed = far(pm0, qm1) || far(pm1, qm0);
4574    let bs = if p0 != p1 {
4575        // 2 枚の別々の参照ピクチャ: 同じピクチャどうしを比べる。
4576        if p0 == q0 { straight } else { crossed }
4577    } else {
4578        // 同じピクチャを 2 回: どちらの組み合わせでも離れているときだけ 1。
4579        straight && crossed
4580    };
4581    bs as u8
4582}
4583
4584fn deblock_frame(st: &mut DecState, mb_w: usize, mb_h: usize, chroma_qp_off: [i32; 2]) {
4585    let w = st.yuv.width as usize;
4586    let cw = (st.yuv.width / 2) as usize;
4587    let blk_w = st.blk_w;
4588    for mby in 0..mb_h {
4589        for mbx in 0..mb_w {
4590            // デブロック設定はこの MB の所属スライスのもの(8.7)。idc=1 はこの MB を飛ばし、
4591            // idc=2 は別スライスとの境界(左・上の MB エッジ)を飛ばす。
4592            let mb_idx = mby * mb_w + mbx;
4593            let MbDeblock { disable_idc, alpha_off, beta_off } = st.mb_db[mb_idx];
4594            if disable_idc == 1 {
4595                continue;
4596            }
4597            let same_slice = |other: usize| disable_idc != 2 || st.mb_slice[other] == st.mb_slice[mb_idx];
4598            let filter_left = mbx > 0 && same_slice(mb_idx - 1);
4599            let filter_top = mby > 0 && same_slice(mb_idx - mb_w);
4600            let qp = st.mb_qp[mby * mb_w + mbx];
4601            let qp_left = if mbx > 0 {
4602                st.mb_qp[mby * mb_w + mbx - 1]
4603            } else {
4604                qp
4605            };
4606            let qp_top = if mby > 0 {
4607                st.mb_qp[(mby - 1) * mb_w + mbx]
4608            } else {
4609                qp
4610            };
4611            let qbx = mbx * 4; // この MB の左上 4x4 ブロック座標
4612            let qby = mby * 4;
4613
4614            // 8x8 変換の MB は輝度の 4x4 の内側エッジ(4, 12)を飛ばす(8.7)。
4615            let t8 = st.mb_t8[mb_idx];
4616            // 輝度 垂直エッジ(xe=0,4,8,12): 4 行セグメントごとに bS。
4617            for k in 0..4usize {
4618                if t8 && k % 2 == 1 {
4619                    continue;
4620                }
4621                let xe = k * 4;
4622                if xe == 0 && !filter_left {
4623                    continue;
4624                }
4625                let is_mb_edge = xe == 0;
4626                let qp2 = if is_mb_edge { qp_left } else { qp };
4627                let qpav = (qp + qp2 + 1) >> 1;
4628                for seg in 0..4usize {
4629                    let qx = qbx + k;
4630                    let qy = qby + seg;
4631                    let q_li = qy * blk_w + qx;
4632                    let p_li = qy * blk_w + qx - 1;
4633                    let bs = boundary_strength(st, p_li, q_li, is_mb_edge);
4634                    if bs == 0 {
4635                        continue;
4636                    }
4637                    let (a, b, t) = deblock_thresh(qpav, alpha_off, beta_off, bs);
4638                    deblock_luma_edge(
4639                        &mut st.yuv.y,
4640                        w,
4641                        true,
4642                        mbx * 16 + xe,
4643                        mby * 16 + seg * 4,
4644                        4,
4645                        bs,
4646                        a,
4647                        b,
4648                        t,
4649                    );
4650                }
4651            }
4652            // 輝度 水平エッジ(ye=0,4,8,12)
4653            for k in 0..4usize {
4654                if t8 && k % 2 == 1 {
4655                    continue;
4656                }
4657                let ye = k * 4;
4658                if ye == 0 && !filter_top {
4659                    continue;
4660                }
4661                let is_mb_edge = ye == 0;
4662                let qp2 = if is_mb_edge { qp_top } else { qp };
4663                let qpav = (qp + qp2 + 1) >> 1;
4664                for seg in 0..4usize {
4665                    let qx = qbx + seg;
4666                    let qy = qby + k;
4667                    let q_li = qy * blk_w + qx;
4668                    let p_li = (qy - 1) * blk_w + qx;
4669                    let bs = boundary_strength(st, p_li, q_li, is_mb_edge);
4670                    if bs == 0 {
4671                        continue;
4672                    }
4673                    let (a, b, t) = deblock_thresh(qpav, alpha_off, beta_off, bs);
4674                    deblock_luma_edge(
4675                        &mut st.yuv.y,
4676                        w,
4677                        false,
4678                        mbx * 16 + seg * 4,
4679                        mby * 16 + ye,
4680                        4,
4681                        bs,
4682                        a,
4683                        b,
4684                        t,
4685                    );
4686                }
4687            }
4688            // クロマ: 輝度 x=0,8(垂直)/ y=0,8(水平)の bS を流用。クロマ 2 行/列セグメント。
4689            for comp in 0..2usize {
4690                // Cb と Cr で QP オフセットが違いうる(High の second_chroma_qp_index_offset)。
4691                let qpc = chroma_qp(qp, chroma_qp_off[comp]);
4692                let qpc_left = chroma_qp(qp_left, chroma_qp_off[comp]);
4693                let qpc_top = chroma_qp(qp_top, chroma_qp_off[comp]);
4694                // 垂直エッジ cxe=0,4(輝度 luma_xe=0,8)
4695                for ck in 0..2usize {
4696                    let cxe = ck * 4;
4697                    let luma_k = ck * 2; // 輝度ブロック列オフセット(0 or 2)
4698                    if cxe == 0 && !filter_left {
4699                        continue;
4700                    }
4701                    let is_mb_edge = cxe == 0;
4702                    let qpav = (qpc + if is_mb_edge { qpc_left } else { qpc } + 1) >> 1;
4703                    for seg in 0..4usize {
4704                        let qx = qbx + luma_k;
4705                        let qy = qby + seg;
4706                        let q_li = qy * blk_w + qx;
4707                        let p_li = qy * blk_w + qx - 1;
4708                        let bs = boundary_strength(st, p_li, q_li, is_mb_edge);
4709                        if bs == 0 {
4710                            continue;
4711                        }
4712                        let (a, b, t) = deblock_thresh(qpav, alpha_off, beta_off, bs);
4713                        let plane = if comp == 0 {
4714                            &mut st.yuv.u
4715                        } else {
4716                            &mut st.yuv.v
4717                        };
4718                        deblock_chroma_edge(
4719                            plane,
4720                            cw,
4721                            true,
4722                            mbx * 8 + cxe,
4723                            mby * 8 + seg * 2,
4724                            2,
4725                            bs,
4726                            a,
4727                            b,
4728                            t,
4729                        );
4730                    }
4731                }
4732                // 水平エッジ cye=0,4(輝度 luma_ye=0,8)
4733                for ck in 0..2usize {
4734                    let cye = ck * 4;
4735                    let luma_k = ck * 2;
4736                    if cye == 0 && !filter_top {
4737                        continue;
4738                    }
4739                    let is_mb_edge = cye == 0;
4740                    let qpav = ((qpc + if is_mb_edge { qpc_top } else { qpc }) + 1) >> 1;
4741                    for seg in 0..4usize {
4742                        let qx = qbx + seg;
4743                        let qy = qby + luma_k;
4744                        let q_li = qy * blk_w + qx;
4745                        let p_li = (qy - 1) * blk_w + qx;
4746                        let bs = boundary_strength(st, p_li, q_li, is_mb_edge);
4747                        if bs == 0 {
4748                            continue;
4749                        }
4750                        let (a, b, t) = deblock_thresh(qpav, alpha_off, beta_off, bs);
4751                        let plane = if comp == 0 {
4752                            &mut st.yuv.u
4753                        } else {
4754                            &mut st.yuv.v
4755                        };
4756                        deblock_chroma_edge(
4757                            plane,
4758                            cw,
4759                            false,
4760                            mbx * 8 + seg * 2,
4761                            mby * 8 + cye,
4762                            2,
4763                            bs,
4764                            a,
4765                            b,
4766                            t,
4767                        );
4768                    }
4769                }
4770            }
4771        }
4772    }
4773}
4774
4775// ===================== テーブル健全性チェック =====================
4776
4777/// (len, code) 群が prefix-free か検査。衝突する組を見つけたら false。
4778fn is_prefix_free(entries: &[(u8, u16)], label: &str) -> bool {
4779    let mut ok = true;
4780    for i in 0..entries.len() {
4781        for j in 0..entries.len() {
4782            if i == j {
4783                continue;
4784            }
4785            let (la, ca) = entries[i];
4786            let (lb, cb) = entries[j];
4787            if la <= lb && (cb >> (lb - la)) == ca {
4788                crate::println!(
4789                    "  PREFIX-CONFLICT in {}: ({},{:b}) is prefix of ({},{:b})",
4790                    label,
4791                    la,
4792                    ca,
4793                    lb,
4794                    cb
4795                );
4796                ok = false;
4797            }
4798        }
4799    }
4800    ok
4801}
4802
4803/// coeff_token テーブルに有効な (tc, t1) 組が漏れなく 1 回ずつあるか検査。
4804/// max_tc までの全組(tc=0:(0,0)、tc>=1: t1=0..min(tc,3))を期待。
4805fn check_token_complete(t: &[TokEntry], max_tc: u8, label: &str) -> bool {
4806    let mut ok = true;
4807    for tc in 0..=max_tc {
4808        let max_t1 = if tc == 0 { 0 } else { tc.min(3) };
4809        for t1 in 0..=max_t1 {
4810            let count = t.iter().filter(|&&(_, _, c, o)| c == tc && o == t1).count();
4811            if count != 1 {
4812                crate::println!(
4813                    "  TOKEN {} ({},{}) appears {} times (want 1)",
4814                    label,
4815                    tc,
4816                    t1,
4817                    count
4818                );
4819                ok = false;
4820            }
4821        }
4822    }
4823    ok
4824}
4825
4826/// 全 CAVLC VLC テーブルの prefix-free 性 + coeff_token の完全性を検査。
4827pub fn tables_selftest() {
4828    let mut ok = true;
4829    let toks: [(&str, &[TokEntry]); 4] = [
4830        ("coeff_token_0", COEFF_TOKEN_0),
4831        ("coeff_token_2", COEFF_TOKEN_2),
4832        ("coeff_token_4", COEFF_TOKEN_4),
4833        ("coeff_token_cdc", COEFF_TOKEN_CHROMA_DC),
4834    ];
4835    for (name, t) in toks {
4836        let e: Vec<(u8, u16)> = t.iter().map(|&(l, c, _, _)| (l, c)).collect();
4837        if !is_prefix_free(&e, name) {
4838            ok = false;
4839        }
4840    }
4841    if !check_token_complete(COEFF_TOKEN_0, 16, "tok0") {
4842        ok = false;
4843    }
4844    if !check_token_complete(COEFF_TOKEN_2, 16, "tok2") {
4845        ok = false;
4846    }
4847    if !check_token_complete(COEFF_TOKEN_4, 16, "tok4") {
4848        ok = false;
4849    }
4850    if !check_token_complete(COEFF_TOKEN_CHROMA_DC, 4, "tokCDC") {
4851        ok = false;
4852    }
4853    // total_zeros の行 tc は total_zeros=0..(16-tc) の 17-tc エントリを持つはず。
4854    for (i, row) in TOTAL_ZEROS_16.iter().enumerate() {
4855        let tc = i + 1;
4856        if row.len() != 17 - tc {
4857            crate::println!(
4858                "  TZ16 row tc={} has {} entries (want {})",
4859                tc,
4860                row.len(),
4861                17 - tc
4862            );
4863            ok = false;
4864        }
4865    }
4866    for (i, row) in TOTAL_ZEROS_16.iter().enumerate() {
4867        if !is_prefix_free(row, "tz16") {
4868            crate::println!("   (tz16 row tc={})", i + 1);
4869            ok = false;
4870        }
4871    }
4872    for row in TOTAL_ZEROS_CHROMA.iter() {
4873        if !is_prefix_free(row, "tzC") {
4874            ok = false;
4875        }
4876    }
4877    for row in RUN_BEFORE.iter() {
4878        if !is_prefix_free(row, "run_before") {
4879            ok = false;
4880        }
4881    }
4882    // 正常時は無音(バッファ節約)。コンフリクト時のみ出す。
4883    if !ok {
4884        crate::println!("H264_TABLES: CONFLICTS FOUND");
4885    }
4886}
4887
4888// ===================== ゴールデンテスト =====================
4889
4890/// ゴールデン素材 1 本をデコードし、Y 面の一致画素数を返す。
4891fn run_golden(
4892    clip: &[u8],
4893    ref_yuv: &[u8],
4894) -> Result<(usize, usize, usize, usize, usize), &'static str> {
4895    let nals = split_annexb(clip);
4896    let mut sps: Option<Sps> = None;
4897    let mut pps: Option<Pps> = None;
4898    let mut reference: Option<DecodedYuv> = None;
4899    for nu in &nals {
4900        match nu.unit_type {
4901            7 => {
4902                sps = Some(parse_sps(&nu.rbsp)?);
4903            }
4904            8 => {
4905                pps = Some(parse_pps(&nu.rbsp)?);
4906            }
4907            1 | 5 => {
4908                let s = sps.as_ref().ok_or("no SPS")?;
4909                let p = pps.as_ref().ok_or("no PPS")?;
4910                let frame = decode_slice(s, p, &nu.rbsp, nu.unit_type == 5, reference.as_ref())?;
4911                reference = Some(frame);
4912            }
4913            _ => {}
4914        }
4915    }
4916    let yuv = reference.ok_or("no slice decoded")?;
4917    let yn = (yuv.width * yuv.height) as usize;
4918    let cn = yuv.u.len();
4919    // ref_yuv が複数フレームのとき、デコード結果は最後のフレーム。最後のフレームと比較。
4920    let fsize = yn + 2 * cn;
4921    let nframes = ref_yuv.len() / fsize;
4922    let off = (nframes.saturating_sub(1)) * fsize;
4923    let mut ym = 0;
4924    for i in 0..yn {
4925        if yuv.y[i] == ref_yuv[off + i] {
4926            ym += 1;
4927        }
4928    }
4929    let mut um = 0;
4930    for i in 0..cn {
4931        if yuv.u[i] == ref_yuv[off + yn + i] {
4932            um += 1;
4933        }
4934    }
4935    let mut vm = 0;
4936    for i in 0..cn {
4937        if yuv.v[i] == ref_yuv[off + yn + cn + i] {
4938            vm += 1;
4939        }
4940    }
4941    Ok((ym, um, vm, yn, cn))
4942}
4943
4944/// 不一致を 16x16 MB 単位で集計してデバッグ出力(複数フレーム対応、最後のフレームを比較)。
4945fn run_golden_debug(clip: &[u8], ref_yuv: &[u8]) {
4946    let nals = split_annexb(clip);
4947    let mut sps: Option<Sps> = None;
4948    let mut pps: Option<Pps> = None;
4949    let mut reference: Option<DecodedYuv> = None;
4950    for nu in &nals {
4951        match nu.unit_type {
4952            7 => sps = parse_sps(&nu.rbsp).ok(),
4953            8 => pps = parse_pps(&nu.rbsp).ok(),
4954            1 | 5 => {
4955                if let (Some(s), Some(p)) = (sps.as_ref(), pps.as_ref()) {
4956                    if let Ok(f) =
4957                        decode_slice(s, p, &nu.rbsp, nu.unit_type == 5, reference.as_ref())
4958                    {
4959                        reference = Some(f);
4960                    }
4961                }
4962            }
4963            _ => {}
4964        }
4965    }
4966    if let Some(yuv) = reference {
4967        let w = yuv.width as usize;
4968        let h = yuv.height as usize;
4969        let fsize = w * h + 2 * yuv.u.len();
4970        let off = (ref_yuv.len() / fsize).saturating_sub(1) * fsize;
4971        let mut shown = 0;
4972        'outer: for mby in (0..h).step_by(16) {
4973            for mbx in (0..w).step_by(16) {
4974                let mut bad = 0;
4975                let mut first = (0u8, 0u8, 0usize, 0usize);
4976                for yy in 0..16 {
4977                    for xx in 0..16 {
4978                        let i = (mby + yy) * w + mbx + xx;
4979                        if yuv.y[i] != ref_yuv[off + i] {
4980                            if bad == 0 {
4981                                first = (yuv.y[i], ref_yuv[off + i], mbx + xx, mby + yy);
4982                            }
4983                            bad += 1;
4984                        }
4985                    }
4986                }
4987                if bad > 0 {
4988                    crate::println!(
4989                        "  MB({},{}) bad={}/256 first@({},{}) got={} exp={}",
4990                        mbx,
4991                        mby,
4992                        bad,
4993                        first.2,
4994                        first.3,
4995                        first.0,
4996                        first.1
4997                    );
4998                    shown += 1;
4999                    if shown >= 8 {
5000                        break 'outer;
5001                    }
5002                }
5003            }
5004        }
5005    }
5006}
5007
5008/// 起動時に呼ぶゴールデン検証。結果をログ出力する。
5009pub fn golden_test() -> bool {
5010    use super::h264_golden::*;
5011    tables_selftest();
5012    let mut pass = 0usize;
5013    let mut total = 0usize;
5014    for (name, clip, refy) in [
5015        ("flat", FLAT_H264.as_slice(), FLAT_YUV.as_slice()),
5016        ("clip", CLIP_H264.as_slice(), CLIP_YUV.as_slice()),
5017        ("big", BIG_H264.as_slice(), BIG_YUV.as_slice()),
5018        ("dbclip", DBCLIP_H264.as_slice(), DBCLIP_YUV.as_slice()),
5019        ("dbbig", DBBIG_H264.as_slice(), DBBIG_YUV.as_slice()),
5020        ("pskip", PSKIP_H264.as_slice(), PSKIP_YUV.as_slice()),
5021        ("pint16", PINT16_H264.as_slice(), PINT16_YUV.as_slice()),
5022        ("psub", PSUB_H264.as_slice(), PSUB_YUV.as_slice()),
5023        ("ppart", PPART_H264.as_slice(), PPART_YUV.as_slice()),
5024        ("dbp", DBP_H264.as_slice(), DBP_YUV.as_slice()),
5025    ] {
5026        match run_golden(clip, refy) {
5027            Ok((ym, um, vm, yn, cn)) => {
5028                total += 1;
5029                if ym == yn && um == cn && vm == cn {
5030                    pass += 1;
5031                } else {
5032                    // 不一致のケースだけ詳細を出す(バッファを食わないため正常時は無音)。
5033                    crate::println!(
5034                        "H264_GOLDEN {}: MISMATCH Y {}/{} U {}/{} V {}/{}",
5035                        name,
5036                        ym,
5037                        yn,
5038                        um,
5039                        cn,
5040                        vm,
5041                        cn
5042                    );
5043                    if ym != yn {
5044                        run_golden_debug(clip, refy);
5045                    }
5046                }
5047            }
5048            Err(e) => {
5049                total += 1;
5050                crate::println!("H264_GOLDEN {}: ERROR {}", name, e);
5051            }
5052        }
5053    }
5054    // テスト詳細はサイレントモードで実行されるため、mismatch時以外は出力されない
5055    crate::info!("[H264] Golden test pass: {}/{}", pass, total);
5056    pass == total
5057}