Skip to main content

atmos/os_lib/
h264.rs

1// h264.rs - no_std H.264 / AVC (MPEG-4 Part 10) Video Decoder for AtmOS
2//
3// 段階実装中の「本物」デコーダの基盤レイヤ(ビットストリーム〜変換・予測の純関数)。
4// スライス/マクロブロック層・CAVLC 残差・再構成は h264_decode.rs にある。
5// 完成済みレイヤ(h264::selftest / h264_decode::golden_test で検証):
6//   [x] BitReader(MSB-first)+ Exp-Golomb ue(v)/se(v)
7//   [x] RBSP 抽出(emulation-prevention 0x03 除去)
8//   [x] NAL 分割(Annex-B スタートコード / AVCC 長さ前置の両対応)
9//   [x] SPS / PPS パース(実解像度 + フレームクロップ。avcC 抽出)
10//   [x] 逆量子化 + 4x4 整数 IDCT、イントラ予測(輝度 4x4/16x16・クロマ 8x8)
11// 互換 API の decode_nal / decode_slice は段階移行用(pixel 再構成は h264_decode.rs 側)。
12// 未実装(次段): I_4x4 全体経路 / nC≥2 の coeff_token / インター予測 / 動き補償 / デブロッキング。
13#![allow(dead_code, clippy::needless_range_loop)]
14
15use alloc::string::String;
16use alloc::vec::Vec;
17
18// ===================== ビットストリーム読み取り =====================
19
20/// MSB-first のビットリーダ。H.264 の RBSP を読むための最小実装。
21pub struct BitReader<'a> {
22    data: &'a [u8],
23    byte_pos: usize,
24    bit_pos: u8, // 0..=7、現在バイト内の次に読むビット(MSB=0)
25}
26
27impl<'a> BitReader<'a> {
28    pub fn new(data: &'a [u8]) -> Self {
29        Self {
30            data,
31            byte_pos: 0,
32            bit_pos: 0,
33        }
34    }
35
36    /// 残ビットが無い場合に true。
37    pub fn is_eof(&self) -> bool {
38        self.byte_pos >= self.data.len()
39    }
40
41    /// `more_rbsp_data()`(7.2): 末尾の rbsp_stop_one_bit より前に未読のビットが残っているか。
42    /// 停止ビットは「最後の 0 でないバイトの最下位の 1」(後ろの 0 バイトは cabac_zero_word 等)。
43    /// 複数スライスのフレームで、スライスの終わりを知るのに使う(2026-10-17)。
44    pub fn more_rbsp_data(&self) -> bool {
45        let Some(last) = self.data.iter().rposition(|&b| b != 0) else {
46            return false;
47        };
48        let tz = self.data.get(last).map_or(0, |b| b.trailing_zeros() as usize);
49        let stop_bit = last * 8 + (7 - tz);
50        self.byte_pos * 8 + (self.bit_pos as usize) < stop_bit
51    }
52
53    /// 1 ビット読む。EOF 後は 0 を返す(呼び出し側で長さ管理する前提)。
54    pub fn read_bit(&mut self) -> u32 {
55        if self.byte_pos >= self.data.len() {
56            return 0;
57        }
58        let byte = self.data[self.byte_pos];
59        let bit = (byte >> (7 - self.bit_pos)) & 1;
60        self.bit_pos += 1;
61        if self.bit_pos == 8 {
62            self.bit_pos = 0;
63            self.byte_pos += 1;
64        }
65        bit as u32
66    }
67
68    /// 先頭からのビット位置(CABAC の slice_data の開始位置を渡すのに使う)。
69    pub fn bit_position(&self) -> usize {
70        self.byte_pos * 8 + self.bit_pos as usize
71    }
72
73    /// バイト境界まで進める(I_PCM の pcm_alignment_zero_bits 用)。
74    pub fn byte_align(&mut self) {
75        if self.bit_pos != 0 {
76            self.bit_pos = 0;
77            self.byte_pos += 1;
78        }
79    }
80
81    /// n ビット読み(n <= 32)、無符号整数として返す。
82    pub fn read_bits(&mut self, n: u32) -> u32 {
83        let mut v = 0u32;
84        // 不正データ由来の巨大ビット数によるハングを防ぐ(u32 に収まる範囲に制限)。
85        for _ in 0..n.min(32) {
86            v = (v << 1) | self.read_bit();
87        }
88        v
89    }
90
91    /// 符号なし Exp-Golomb ue(v)。
92    /// 仕様: 先行ゼロ数 leadingZeroBits を数え、`2^lzb - 1 + read_bits(lzb)`。
93    pub fn read_ue(&mut self) -> u32 {
94        let mut leading_zeros = 0u32;
95        while !self.is_eof() && self.read_bit() == 0 {
96            leading_zeros += 1;
97            if leading_zeros > 31 {
98                break;
99            } // 異常データの暴走防止
100        }
101        if leading_zeros == 0 {
102            return 0;
103        }
104        let suffix = self.read_bits(leading_zeros);
105        (1u32 << leading_zeros) - 1 + suffix
106    }
107
108    /// 符号付き Exp-Golomb se(v)。ue を ±へマップ: 0->0, 1->1, 2->-1, 3->2, 4->-2 ...
109    pub fn read_se(&mut self) -> i32 {
110        let k = self.read_ue();
111        if k == 0 {
112            0
113        } else if k & 1 == 1 {
114            (k.div_ceil(2)) as i32
115        } else {
116            -((k / 2) as i32)
117        }
118    }
119}
120
121/// RBSP 抽出: NAL ペイロードから emulation_prevention_three_byte(0x03) を除去する。
122/// 0x00 0x00 0x03 のシーケンスで 0x03 を落とす(0x000000/0x000001 の誤検出回避用バイト)。
123pub fn extract_rbsp(payload: &[u8]) -> Vec<u8> {
124    let mut out = Vec::with_capacity(payload.len());
125    let mut zeros = 0usize;
126    let mut i = 0;
127    while i < payload.len() {
128        let b = payload[i];
129        if zeros >= 2 && b == 0x03 {
130            // 0x03 を除去。直後のバイトは通常通り処理させるため zeros をリセット。
131            zeros = 0;
132            i += 1;
133            continue;
134        }
135        out.push(b);
136        if b == 0x00 {
137            zeros += 1;
138        } else {
139            zeros = 0;
140        }
141        i += 1;
142    }
143    out
144}
145
146// ===================== NAL 分割 =====================
147
148#[derive(Clone)]
149pub struct NalUnit {
150    pub ref_idc: u8,
151    pub unit_type: u8,
152    pub rbsp: Vec<u8>, // ヘッダ1バイトを除いた RBSP(脱エミュレーション済み)
153}
154
155impl NalUnit {
156    fn from_ebsp(ebsp: &[u8]) -> Option<NalUnit> {
157        if ebsp.is_empty() {
158            return None;
159        }
160        let header = ebsp[0];
161        if (header >> 7) & 1 != 0 {
162            return None;
163        } // forbidden_zero_bit
164        Some(NalUnit {
165            ref_idc: (header >> 5) & 3,
166            unit_type: header & 0x1F,
167            rbsp: extract_rbsp(&ebsp[1..]),
168        })
169    }
170}
171
172/// Annex-B(00 00 01 / 00 00 00 01 区切り)のバイト列を NAL 群に分割。
173pub fn split_annexb(data: &[u8]) -> Vec<NalUnit> {
174    let mut nals = Vec::new();
175    let mut i = 0;
176    let n = data.len();
177    // 最初のスタートコードを探す
178    let mut start = None;
179    while i + 3 <= n {
180        if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
181            start = Some(i + 3);
182            i += 3;
183            break;
184        }
185        i += 1;
186    }
187    let mut s = match start {
188        Some(s) => s,
189        None => return nals,
190    };
191    while i + 3 <= n {
192        if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
193            // 直前に 0x00 があれば 4 バイトスタートコード。末尾の余分な 0 を含めない。
194            let mut end = i;
195            if end > s && data[end - 1] == 0 {
196                end -= 1;
197            }
198            if let Some(nu) = NalUnit::from_ebsp(&data[s..end]) {
199                nals.push(nu);
200            }
201            s = i + 3;
202            i += 3;
203        } else {
204            i += 1;
205        }
206    }
207    if s < n {
208        if let Some(nu) = NalUnit::from_ebsp(&data[s..n]) {
209            nals.push(nu);
210        }
211    }
212    nals
213}
214
215/// AVCC(長さ前置)の1サンプルを NAL 群に分割。length_size は 1/2/4。
216pub fn split_avcc(data: &[u8], length_size: usize) -> Vec<NalUnit> {
217    let mut nals = Vec::new();
218    let mut i = 0;
219    while i + length_size <= data.len() {
220        let mut len = 0usize;
221        for _ in 0..length_size {
222            len = (len << 8) | data[i] as usize;
223            i += 1;
224        }
225        if len == 0 || i + len > data.len() {
226            break;
227        }
228        if let Some(nu) = NalUnit::from_ebsp(&data[i..i + len]) {
229            nals.push(nu);
230        }
231        i += len;
232    }
233    nals
234}
235
236// ===================== パラメータセット =====================
237
238#[derive(Default, Clone)]
239pub struct Sps {
240    pub profile_idc: u8,
241    pub level_idc: u8,
242    pub seq_parameter_set_id: u32,
243    pub log2_max_frame_num: u32,
244    pub pic_order_cnt_type: u32,
245    pub log2_max_pic_order_cnt_lsb: u32,
246    pub width: u32,
247    pub height: u32,
248    pub frame_mbs_only_flag: u32,
249    /// 横マクロブロック数 = width/16(クロップ前の MB 数)。
250    pub width_mbs: u32,
251    pub height_mbs: u32,
252    /// 参照フレームの最大数(DPB のスライディングウィンドウの幅)。
253    pub max_num_ref_frames: u32,
254    /// B の空間ダイレクトで、8x8 ごとに角の 4x4 の co-located を使う(8.4.1.2.1)。
255    pub direct_8x8_inference_flag: bool,
256    /// 表示領域(frame_cropping)の左上(画素)。幅・高さは `width`/`height`。
257    pub crop_x: u32,
258    pub crop_y: u32,
259}
260
261/// SPS RBSP をパースして解像度等を得る(baseline/main で必要な範囲)。
262pub fn parse_sps(rbsp: &[u8]) -> Result<Sps, &'static str> {
263    let mut r = BitReader::new(rbsp);
264    let mut sps = Sps {
265        profile_idc: r.read_bits(8) as u8,
266        ..Default::default()
267    };
268    let _constraint_flags = r.read_bits(8); // constraint_set flags + reserved
269    sps.level_idc = r.read_bits(8) as u8;
270    sps.seq_parameter_set_id = r.read_ue();
271
272    // high profile 系のクロマ情報(baseline/main では無いが安全のため分岐)
273    if matches!(
274        sps.profile_idc,
275        100 | 110 | 122 | 244 | 44 | 83 | 86 | 118 | 128 | 138 | 139 | 134 | 135
276    ) {
277        // デコーダは 4:2:0・8 ビット・平坦なスケーリング行列だけ扱う。それ以外は黙って誤復号
278        // せずに失敗させる(2026-10-17。従来は読み捨てていた。スケーリング行列は存在フラグだけ
279        // 立って中身が無い場合も既定の非平坦行列=フォールバック規則 A になるので未対応扱い)。
280        let chroma_format_idc = r.read_ue();
281        if chroma_format_idc != 1 {
282            return Err("chroma format other than 4:2:0 not supported");
283        }
284        if r.read_ue() != 0 || r.read_ue() != 0 {
285            return Err("bit depth other than 8 not supported");
286        }
287        if r.read_bit() == 1 {
288            return Err("qpprime_y_zero_transform_bypass not supported");
289        }
290        if r.read_bit() == 1 {
291            return Err("SPS scaling matrix not supported");
292        }
293    }
294
295    sps.log2_max_frame_num = r.read_ue() + 4;
296    sps.pic_order_cnt_type = r.read_ue();
297    if sps.pic_order_cnt_type == 0 {
298        sps.log2_max_pic_order_cnt_lsb = r.read_ue() + 4;
299    } else if sps.pic_order_cnt_type == 1 {
300        let _delta_pic_order_always_zero_flag = r.read_bit();
301        let _offset_for_non_ref_pic = r.read_se();
302        let _offset_for_top_to_bottom_field = r.read_se();
303        // 不正データ由来の巨大値によるハングを防ぐ(規格上限 255)。
304        let num_ref_frames_in_pic_order_cnt_cycle = r.read_ue().min(256);
305        for _ in 0..num_ref_frames_in_pic_order_cnt_cycle {
306            let _ = r.read_se();
307        }
308    }
309    sps.max_num_ref_frames = r.read_ue().min(16);
310    let _gaps_in_frame_num_value_allowed_flag = r.read_bit();
311
312    let pic_width_in_mbs_minus1 = r.read_ue();
313    let pic_height_in_map_units_minus1 = r.read_ue();
314    sps.frame_mbs_only_flag = r.read_bit();
315    if sps.frame_mbs_only_flag == 0 {
316        let _mb_adaptive_frame_field_flag = r.read_bit();
317    }
318    sps.direct_8x8_inference_flag = r.read_bit() == 1;
319
320    let mut crop_left = 0u32;
321    let mut crop_right = 0u32;
322    let mut crop_top = 0u32;
323    let mut crop_bottom = 0u32;
324    let frame_cropping_flag = r.read_bit();
325    if frame_cropping_flag == 1 {
326        crop_left = r.read_ue();
327        crop_right = r.read_ue();
328        crop_top = r.read_ue();
329        crop_bottom = r.read_ue();
330    }
331
332    let width_mbs = pic_width_in_mbs_minus1 + 1;
333    let height_map_units = pic_height_in_map_units_minus1 + 1;
334    sps.width_mbs = width_mbs;
335    sps.height_mbs = height_map_units * (2 - sps.frame_mbs_only_flag);
336    let raw_width = width_mbs * 16;
337    let raw_height = height_map_units * 16 * (2 - sps.frame_mbs_only_flag);
338
339    // クロマ 4:2:0 を前提に crop 単位は水平2/垂直(2*(2-frame_mbs_only)) 画素。
340    let crop_unit_x = 2u32;
341    let crop_unit_y = 2u32 * (2 - sps.frame_mbs_only_flag);
342    sps.width = raw_width.saturating_sub((crop_left + crop_right) * crop_unit_x);
343    sps.height = raw_height.saturating_sub((crop_top + crop_bottom) * crop_unit_y);
344    sps.crop_x = crop_left * crop_unit_x;
345    sps.crop_y = crop_top * crop_unit_y;
346    if sps.width == 0 || sps.height == 0 {
347        return Err("SPS produced zero dimensions");
348    }
349    Ok(sps)
350}
351
352#[derive(Default, Clone)]
353pub struct Pps {
354    pub pic_parameter_set_id: u32,
355    pub seq_parameter_set_id: u32,
356    pub entropy_coding_mode_flag: u32, // 0=CAVLC, 1=CABAC
357    pub bottom_field_pic_order_in_frame_present_flag: u32, // = pic_order_present_flag
358    pub pic_init_qp: i32,              // 26 + pic_init_qp_minus26
359    pub deblocking_filter_control_present_flag: u32,
360    pub constrained_intra_pred_flag: u32,
361    pub redundant_pic_cnt_present_flag: u32,
362    pub chroma_qp_index_offset: i32,
363    /// `num_ref_idx_l0_default_active_minus1 + 1`(スライスヘッダで上書きされうる)。
364    pub num_ref_idx_l0_default_active: u32,
365    /// 重み付き予測(P)。1 ならスライスヘッダに pred_weight_table がある。
366    pub weighted_pred_flag: u32,
367    /// `num_ref_idx_l1_default_active_minus1 + 1`(B)。
368    pub num_ref_idx_l1_default_active: u32,
369    /// 重み付き双予測(B)。0=無し、1=明示、2=暗黙。
370    pub weighted_bipred_idc: u32,
371    /// 8x8 変換を使ってよいか(High。MB ごとに transform_size_8x8_flag がある)。
372    pub transform_8x8_mode_flag: bool,
373    /// Cr の QP オフセット(High。無ければ `chroma_qp_index_offset` と同じ)。
374    pub second_chroma_qp_index_offset: i32,
375}
376
377impl Pps {
378    /// [Cb, Cr] の QP オフセット。
379    pub fn chroma_qp_offsets(&self) -> [i32; 2] {
380        [self.chroma_qp_index_offset, self.second_chroma_qp_index_offset]
381    }
382}
383
384pub fn parse_pps(rbsp: &[u8]) -> Result<Pps, &'static str> {
385    let mut r = BitReader::new(rbsp);
386    let mut pps = Pps {
387        pic_parameter_set_id: r.read_ue(),
388        seq_parameter_set_id: r.read_ue(),
389        entropy_coding_mode_flag: r.read_bit(),
390        ..Default::default()
391    };
392    pps.bottom_field_pic_order_in_frame_present_flag = r.read_bit();
393    let num_slice_groups_minus1 = r.read_ue();
394    if num_slice_groups_minus1 > 0 {
395        // FMO(スライスグループ)は未対応。ベースライン単一グループのみ。
396        return Err("slice groups not supported");
397    }
398    pps.num_ref_idx_l0_default_active = r.read_ue().min(31) + 1;
399    pps.num_ref_idx_l1_default_active = r.read_ue().min(31) + 1;
400    pps.weighted_pred_flag = r.read_bit();
401    pps.weighted_bipred_idc = r.read_bits(2);
402    let pic_init_qp_minus26 = r.read_se();
403    pps.pic_init_qp = 26 + pic_init_qp_minus26;
404    let _pic_init_qs_minus26 = r.read_se();
405    pps.chroma_qp_index_offset = r.read_se();
406    pps.deblocking_filter_control_present_flag = r.read_bit();
407    pps.constrained_intra_pred_flag = r.read_bit();
408    pps.redundant_pic_cnt_present_flag = r.read_bit();
409    // High 以上の拡張(7.3.2.2)。スケーリング行列は未対応なので明示的に失敗させる。
410    pps.second_chroma_qp_index_offset = pps.chroma_qp_index_offset;
411    if r.more_rbsp_data() {
412        pps.transform_8x8_mode_flag = r.read_bit() == 1;
413        if r.read_bit() == 1 {
414            return Err("PPS scaling matrix not supported");
415        }
416        pps.second_chroma_qp_index_offset = r.read_se();
417    }
418    Ok(pps)
419}
420
421/// avcC(AVCDecoderConfigurationRecord)から SPS/PPS と length_size を抽出。
422/// 返り値: (length_size, sps_nal_payloads, pps_nal_payloads)。各 payload は NAL ヘッダ込み。
423pub fn parse_avcc(avcc: &[u8]) -> Option<(usize, Vec<Vec<u8>>, Vec<Vec<u8>>)> {
424    if avcc.len() < 7 || avcc[0] != 1 {
425        return None;
426    }
427    let length_size = (avcc[4] & 0x03) as usize + 1;
428    let num_sps = (avcc[5] & 0x1F) as usize;
429    let mut i = 6;
430    let mut sps_list = Vec::new();
431    for _ in 0..num_sps {
432        if i + 2 > avcc.len() {
433            return None;
434        }
435        let len = ((avcc[i] as usize) << 8) | avcc[i + 1] as usize;
436        i += 2;
437        if i + len > avcc.len() {
438            return None;
439        }
440        sps_list.push(avcc[i..i + len].to_vec());
441        i += len;
442    }
443    if i >= avcc.len() {
444        return None;
445    }
446    let num_pps = avcc[i] as usize;
447    i += 1;
448    let mut pps_list = Vec::new();
449    for _ in 0..num_pps {
450        if i + 2 > avcc.len() {
451            return None;
452        }
453        let len = ((avcc[i] as usize) << 8) | avcc[i + 1] as usize;
454        i += 2;
455        if i + len > avcc.len() {
456            return None;
457        }
458        pps_list.push(avcc[i..i + len].to_vec());
459        i += len;
460    }
461    Some((length_size, sps_list, pps_list))
462}
463
464// ===================== デコーダ本体 =====================
465
466pub struct DecodedVideoFrame {
467    pub width: u32,
468    pub height: u32,
469    pub rgb_data: Vec<u8>,
470}
471
472pub struct H264Decoder {
473    sps: Option<Sps>,
474    pps: Option<Pps>,
475    width: u32,
476    height: u32,
477}
478
479impl Default for H264Decoder {
480    fn default() -> Self {
481        Self::new()
482    }
483}
484
485impl H264Decoder {
486    pub fn new() -> Self {
487        Self {
488            sps: None,
489            pps: None,
490            width: 0,
491            height: 0,
492        }
493    }
494
495    /// avcC 由来の SPS/PPS を事前登録する(MP4 経路用)。
496    pub fn set_parameter_sets(&mut self, sps_payloads: &[Vec<u8>], pps_payloads: &[Vec<u8>]) {
497        for p in sps_payloads {
498            if let Some(nu) = NalUnit::from_ebsp(p) {
499                if nu.unit_type == 7 {
500                    if let Ok(s) = parse_sps(&nu.rbsp) {
501                        self.width = s.width;
502                        self.height = s.height;
503                        self.sps = Some(s);
504                    }
505                }
506            }
507        }
508        for p in pps_payloads {
509            if let Some(nu) = NalUnit::from_ebsp(p) {
510                if nu.unit_type == 8 {
511                    if let Ok(pp) = parse_pps(&nu.rbsp) {
512                        self.pps = Some(pp);
513                    }
514                }
515            }
516        }
517    }
518
519    /// 1つの NAL(Annex-B でも AVCC 単体でも、先頭にスタートコードがあれば剥がす)を処理。
520    /// 互換性のため従来シグネチャを維持。
521    pub fn decode_nal(&mut self, nal_data: &[u8]) -> Result<DecodedVideoFrame, &'static str> {
522        // Annex-B スタートコードを剥がして単一 NAL とみなす。
523        let nals = if has_start_code(nal_data) {
524            split_annexb(nal_data)
525        } else {
526            // 先頭がヘッダバイトの裸 NAL とみなす
527            match NalUnit::from_ebsp(nal_data) {
528                Some(nu) => alloc::vec![nu],
529                None => return Err("invalid NAL"),
530            }
531        };
532
533        let mut produced: Option<DecodedVideoFrame> = None;
534        for nu in &nals {
535            match nu.unit_type {
536                7 => {
537                    let s = parse_sps(&nu.rbsp)?;
538                    self.width = s.width;
539                    self.height = s.height;
540                    self.sps = Some(s);
541                }
542                8 => {
543                    self.pps = Some(parse_pps(&nu.rbsp)?);
544                }
545                1 | 5 => {
546                    produced = Some(self.decode_slice(&nu.rbsp, nu.unit_type == 5)?);
547                }
548                _ => {}
549            }
550        }
551        produced.ok_or("no picture in NAL (parameter set only)")
552    }
553
554    /// AVCC(長さ前置)の1サンプル=アクセスユニットを処理し、含まれる picture を返す。
555    /// MP4 経路ではこちらを使う(SPS/PPS は事前に set_parameter_sets 済みが前提だが、
556    /// in-band の SPS/PPS が来た場合もここで取り込む)。
557    pub fn decode_access_unit_avcc(
558        &mut self,
559        sample: &[u8],
560        length_size: usize,
561    ) -> Option<DecodedVideoFrame> {
562        let nals = split_avcc(sample, length_size);
563        let mut produced = None;
564        for nu in &nals {
565            match nu.unit_type {
566                7 => {
567                    if let Ok(s) = parse_sps(&nu.rbsp) {
568                        self.width = s.width;
569                        self.height = s.height;
570                        self.sps = Some(s);
571                    }
572                }
573                8 => {
574                    if let Ok(pp) = parse_pps(&nu.rbsp) {
575                        self.pps = Some(pp);
576                    }
577                }
578                1 | 5 => {
579                    if let Ok(f) = self.decode_slice(&nu.rbsp, nu.unit_type == 5) {
580                        produced = Some(f);
581                    }
582                }
583                _ => {}
584            }
585        }
586        produced
587    }
588
589    fn decode_slice(
590        &mut self,
591        _rbsp: &[u8],
592        _is_idr: bool,
593    ) -> Result<DecodedVideoFrame, &'static str> {
594        // 解像度が未確定ならデフォルト(SPS 未受信時のフォールバック)。
595        let w = if self.width == 0 { 320 } else { self.width };
596        let h = if self.height == 0 { 180 } else { self.height };
597
598        // 次段で CAVLC 残差 / イントラ予測 / IDCT を実装する。
599        // 現状は実解像度のニュートラルなグレーフレームを返す(偽色生成は撤去済み)。
600        let rgb_data = alloc::vec![0x20u8; (w * h * 3) as usize];
601        Ok(DecodedVideoFrame {
602            width: w,
603            height: h,
604            rgb_data,
605        })
606    }
607}
608
609fn has_start_code(d: &[u8]) -> bool {
610    d.len() >= 3
611        && d[0] == 0
612        && d[1] == 0
613        && (d[2] == 1 || (d.len() >= 4 && d[2] == 0 && d[3] == 1))
614}
615
616// ===================== 残差: 逆量子化 + 4x4 逆整数変換 =====================
617//
618// H.264 baseline の残差復元の数学コア。スケーリングリスト無し(flat、weightScale=16)。
619// 仕様 8.5.12(逆量子化)+ 8.5.12.2(4x4 逆変換)に基づく。
620
621/// normAdjust4x4(m, class)。class: 0=偶偶, 1=奇奇, 2=その他。
622pub(crate) const NORM_ADJUST_4X4: [[i32; 3]; 6] = [
623    [10, 16, 13],
624    [11, 18, 14],
625    [13, 20, 16],
626    [14, 23, 18],
627    [16, 25, 20],
628    [18, 29, 23],
629];
630
631#[allow(clippy::manual_is_multiple_of)]
632fn pos_class_4x4(i: usize, j: usize) -> usize {
633    if i % 2 == 0 && j % 2 == 0 {
634        0
635    } else if i % 2 == 1 && j % 2 == 1 {
636        1
637    } else {
638        2
639    }
640}
641
642/// 4x4 残差ブロックの逆量子化(in-place、raster 順)。qp は 0..=51。
643/// LevelScale = weightScale(=16) * normAdjust4x4(qp%6, class)。
644pub fn dequant_4x4(block: &mut [i32; 16], qp: i32) {
645    let m = (qp % 6) as usize;
646    let shift = qp / 6;
647    for i in 0..4 {
648        for j in 0..4 {
649            let idx = i * 4 + j;
650            let level_scale = 16 * NORM_ADJUST_4X4[m][pos_class_4x4(i, j)];
651            let c = block[idx];
652            block[idx] = if qp >= 24 {
653                (c * level_scale) << (shift - 4)
654            } else {
655                (c * level_scale + (1 << (3 - shift))) >> (4 - shift)
656            };
657        }
658    }
659}
660
661/// 4x4 逆整数変換(in-place、raster 順)。最後に (x + 32) >> 6 で正規化。
662pub fn inverse_transform_4x4(block: &mut [i32; 16]) {
663    // 水平(行)変換
664    for i in 0..4 {
665        let o = i * 4;
666        let (d0, d1, d2, d3) = (block[o], block[o + 1], block[o + 2], block[o + 3]);
667        let e0 = d0 + d2;
668        let e1 = d0 - d2;
669        let e2 = (d1 >> 1) - d3;
670        let e3 = d1 + (d3 >> 1);
671        block[o] = e0 + e3;
672        block[o + 1] = e1 + e2;
673        block[o + 2] = e1 - e2;
674        block[o + 3] = e0 - e3;
675    }
676    // 垂直(列)変換 + 正規化
677    for j in 0..4 {
678        let (d0, d1, d2, d3) = (block[j], block[4 + j], block[8 + j], block[12 + j]);
679        let e0 = d0 + d2;
680        let e1 = d0 - d2;
681        let e2 = (d1 >> 1) - d3;
682        let e3 = d1 + (d3 >> 1);
683        block[j] = (e0 + e3 + 32) >> 6;
684        block[4 + j] = (e1 + e2 + 32) >> 6;
685        block[8 + j] = (e1 - e2 + 32) >> 6;
686        block[12 + j] = (e0 - e3 + 32) >> 6;
687    }
688}
689
690// ===================== イントラ予測(輝度) =====================
691//
692// 仕様 8.3.1(Intra_4x4)/ 8.3.3(Intra_16x16)。近傍は再構成済み輝度サンプル。
693// 返り値は raster 順(idx = y*W + x)の予測サンプル。
694
695fn clip1(v: i32) -> i32 {
696    v.clamp(0, 255)
697}
698
699/// Intra_4x4 予測(9 モード)。
700/// top: p[x,-1] x=0..7(上 + 右上), corner: p[-1,-1], left: p[-1,y] y=0..3。
701/// DC モードのみ可用性で式が変わるため top_avail/left_avail を渡す。
702pub fn intra_4x4_predict(
703    mode: u8,
704    top: &[i32; 8],
705    corner: i32,
706    left: &[i32; 4],
707    top_avail: bool,
708    left_avail: bool,
709) -> [i32; 16] {
710    let mut pred = [0i32; 16];
711    // top をインデックス -1(corner)込みで参照
712    let pt = |i: i32| -> i32 {
713        if i < 0 {
714            corner
715        } else {
716            top[i as usize]
717        }
718    };
719    let pl = |i: i32| -> i32 {
720        if i < 0 {
721            corner
722        } else {
723            left[i as usize]
724        }
725    };
726    let put = |x: usize, y: usize, v: i32, p: &mut [i32; 16]| {
727        p[y * 4 + x] = v;
728    };
729
730    match mode {
731        0 => {
732            // Vertical
733            for y in 0..4 {
734                for x in 0..4 {
735                    put(x, y, top[x], &mut pred);
736                }
737            }
738        }
739        1 => {
740            // Horizontal
741            for y in 0..4 {
742                for x in 0..4 {
743                    put(x, y, left[y], &mut pred);
744                }
745            }
746        }
747        2 => {
748            // DC
749            let dc = match (top_avail, left_avail) {
750                (true, true) => {
751                    (top[0] + top[1] + top[2] + top[3] + left[0] + left[1] + left[2] + left[3] + 4)
752                        >> 3
753                }
754                (true, false) => (top[0] + top[1] + top[2] + top[3] + 2) >> 2,
755                (false, true) => (left[0] + left[1] + left[2] + left[3] + 2) >> 2,
756                (false, false) => 128,
757            };
758            for v in pred.iter_mut() {
759                *v = dc;
760            }
761        }
762        3 => {
763            // Diagonal Down-Left
764            for y in 0..4i32 {
765                for x in 0..4i32 {
766                    let v = if x == 3 && y == 3 {
767                        (pt(6) + 3 * pt(7) + 2) >> 2
768                    } else {
769                        (pt(x + y) + 2 * pt(x + y + 1) + pt(x + y + 2) + 2) >> 2
770                    };
771                    put(x as usize, y as usize, v, &mut pred);
772                }
773            }
774        }
775        4 => {
776            // Diagonal Down-Right
777            for y in 0..4i32 {
778                for x in 0..4i32 {
779                    let v = if x > y {
780                        (pt(x - y - 2) + 2 * pt(x - y - 1) + pt(x - y) + 2) >> 2
781                    } else if x < y {
782                        (pl(y - x - 2) + 2 * pl(y - x - 1) + pl(y - x) + 2) >> 2
783                    } else {
784                        (pt(0) + 2 * corner + pl(0) + 2) >> 2
785                    };
786                    put(x as usize, y as usize, v, &mut pred);
787                }
788            }
789        }
790        5 => {
791            // Vertical-Right
792            for y in 0..4i32 {
793                for x in 0..4i32 {
794                    let z = 2 * x - y;
795                    let v = if z >= 0 && z % 2 == 0 {
796                        (pt(x - (y >> 1) - 1) + pt(x - (y >> 1)) + 1) >> 1
797                    } else if z >= 0 {
798                        (pt(x - (y >> 1) - 2) + 2 * pt(x - (y >> 1) - 1) + pt(x - (y >> 1)) + 2)
799                            >> 2
800                    } else if z == -1 {
801                        (pl(0) + 2 * corner + pt(0) + 2) >> 2
802                    } else {
803                        (pl(y - 1) + 2 * pl(y - 2) + pl(y - 3) + 2) >> 2
804                    };
805                    put(x as usize, y as usize, v, &mut pred);
806                }
807            }
808        }
809        6 => {
810            // Horizontal-Down
811            for y in 0..4i32 {
812                for x in 0..4i32 {
813                    let z = 2 * y - x;
814                    let v = if z >= 0 && z % 2 == 0 {
815                        (pl(y - (x >> 1) - 1) + pl(y - (x >> 1)) + 1) >> 1
816                    } else if z >= 0 {
817                        (pl(y - (x >> 1) - 2) + 2 * pl(y - (x >> 1) - 1) + pl(y - (x >> 1)) + 2)
818                            >> 2
819                    } else if z == -1 {
820                        (pl(0) + 2 * corner + pt(0) + 2) >> 2
821                    } else {
822                        (pt(x - 1) + 2 * pt(x - 2) + pt(x - 3) + 2) >> 2
823                    };
824                    put(x as usize, y as usize, v, &mut pred);
825                }
826            }
827        }
828        7 => {
829            // Vertical-Left
830            for y in 0..4i32 {
831                for x in 0..4i32 {
832                    let v = if y % 2 == 0 {
833                        (pt(x + (y >> 1)) + pt(x + (y >> 1) + 1) + 1) >> 1
834                    } else {
835                        (pt(x + (y >> 1)) + 2 * pt(x + (y >> 1) + 1) + pt(x + (y >> 1) + 2) + 2)
836                            >> 2
837                    };
838                    put(x as usize, y as usize, v, &mut pred);
839                }
840            }
841        }
842        8 => {
843            // Horizontal-Up
844            for y in 0..4i32 {
845                for x in 0..4i32 {
846                    let z = x + 2 * y;
847                    let v = if z < 5 && z % 2 == 0 {
848                        (pl(y + (x >> 1)) + pl(y + (x >> 1) + 1) + 1) >> 1
849                    } else if z < 5 {
850                        (pl(y + (x >> 1)) + 2 * pl(y + (x >> 1) + 1) + pl(y + (x >> 1) + 2) + 2)
851                            >> 2
852                    } else if z == 5 {
853                        (pl(2) + 3 * pl(3) + 2) >> 2
854                    } else {
855                        pl(3)
856                    };
857                    put(x as usize, y as usize, v, &mut pred);
858                }
859            }
860        }
861        _ => {}
862    }
863    pred
864}
865
866/// Intra_16x16 予測(4 モード: 0=Vertical, 1=Horizontal, 2=DC, 3=Plane)。
867/// top: p[x,-1] x=0..15, corner: p[-1,-1], left: p[-1,y] y=0..15。
868pub fn intra_16x16_predict(
869    mode: u8,
870    top: &[i32; 16],
871    corner: i32,
872    left: &[i32; 16],
873    top_avail: bool,
874    left_avail: bool,
875) -> [i32; 256] {
876    let mut pred = [0i32; 256];
877    match mode {
878        0 => {
879            for y in 0..16 {
880                for x in 0..16 {
881                    pred[y * 16 + x] = top[x];
882                }
883            }
884        }
885        1 => {
886            for y in 0..16 {
887                for x in 0..16 {
888                    pred[y * 16 + x] = left[y];
889                }
890            }
891        }
892        2 => {
893            let st: i32 = top.iter().sum();
894            let sl: i32 = left.iter().sum();
895            let dc = match (top_avail, left_avail) {
896                (true, true) => (st + sl + 16) >> 5,
897                (true, false) => (st + 8) >> 4,
898                (false, true) => (sl + 8) >> 4,
899                (false, false) => 128,
900            };
901            for v in pred.iter_mut() {
902                *v = dc;
903            }
904        }
905        3 => {
906            // Plane
907            let mut h = 0i32;
908            let mut v = 0i32;
909            for i in 0..8i32 {
910                // H: x'=0..7, p[8+x',-1] - p[6-x',-1]; corner for index -1
911                let pt = |k: i32| -> i32 {
912                    if k < 0 {
913                        corner
914                    } else {
915                        top[k as usize]
916                    }
917                };
918                let pl = |k: i32| -> i32 {
919                    if k < 0 {
920                        corner
921                    } else {
922                        left[k as usize]
923                    }
924                };
925                h += (i + 1) * (pt(8 + i) - pt(6 - i));
926                v += (i + 1) * (pl(8 + i) - pl(6 - i));
927            }
928            let b = (5 * h + 32) >> 6;
929            let c = (5 * v + 32) >> 6;
930            let a = 16 * (top[15] + left[15]);
931            for y in 0..16i32 {
932                for x in 0..16i32 {
933                    let val = (a + b * (x - 7) + c * (y - 7) + 16) >> 5;
934                    pred[(y * 16 + x) as usize] = clip1(val);
935                }
936            }
937        }
938        _ => {}
939    }
940    pred
941}
942
943/// Intra chroma 8x8 予測(4:2:0、4 モード: 0=DC, 1=Horizontal, 2=Vertical, 3=Plane)。
944/// 仕様 8.3.4。top: p[x,-1] x=0..7, corner: p[-1,-1], left: p[-1,y] y=0..7。
945pub fn intra_chroma_predict(
946    mode: u8,
947    top: &[i32; 8],
948    corner: i32,
949    left: &[i32; 8],
950    top_avail: bool,
951    left_avail: bool,
952) -> [i32; 64] {
953    let mut pred = [0i32; 64];
954    match mode {
955        0 => {
956            // DC(4 つの 4x4 ブロックごとに規則が異なる)
957            let t0: i32 = top[0..4].iter().sum();
958            let t1: i32 = top[4..8].iter().sum();
959            let l0: i32 = left[0..4].iter().sum();
960            let l1: i32 = left[4..8].iter().sum();
961            // 各 4x4 ブロックの DC
962            let dc00 = match (top_avail, left_avail) {
963                (true, true) => (t0 + l0 + 4) >> 3,
964                (true, false) => (t0 + 2) >> 2,
965                (false, true) => (l0 + 2) >> 2,
966                (false, false) => 128,
967            };
968            let dc10 = if top_avail {
969                (t1 + 2) >> 2
970            } else if left_avail {
971                (l0 + 2) >> 2
972            } else {
973                128
974            };
975            let dc01 = if left_avail {
976                (l1 + 2) >> 2
977            } else if top_avail {
978                (t0 + 2) >> 2
979            } else {
980                128
981            };
982            let dc11 = match (top_avail, left_avail) {
983                (true, true) => (t1 + l1 + 4) >> 3,
984                (true, false) => (t1 + 2) >> 2,
985                (false, true) => (l1 + 2) >> 2,
986                (false, false) => 128,
987            };
988            for y in 0..8 {
989                for x in 0..8 {
990                    let dc = match (x < 4, y < 4) {
991                        (true, true) => dc00,
992                        (false, true) => dc10,
993                        (true, false) => dc01,
994                        (false, false) => dc11,
995                    };
996                    pred[y * 8 + x] = dc;
997                }
998            }
999        }
1000        1 => {
1001            for y in 0..8 {
1002                for x in 0..8 {
1003                    pred[y * 8 + x] = left[y];
1004                }
1005            }
1006        }
1007        2 => {
1008            for y in 0..8 {
1009                for x in 0..8 {
1010                    pred[y * 8 + x] = top[x];
1011                }
1012            }
1013        }
1014        3 => {
1015            // Plane
1016            let pt = |k: i32| -> i32 {
1017                if k < 0 {
1018                    corner
1019                } else {
1020                    top[k as usize]
1021                }
1022            };
1023            let pl = |k: i32| -> i32 {
1024                if k < 0 {
1025                    corner
1026                } else {
1027                    left[k as usize]
1028                }
1029            };
1030            let mut h = 0i32;
1031            let mut v = 0i32;
1032            for i in 0..4i32 {
1033                h += (i + 1) * (pt(4 + i) - pt(2 - i));
1034                v += (i + 1) * (pl(4 + i) - pl(2 - i));
1035            }
1036            let b = (17 * h + 16) >> 5;
1037            let c = (17 * v + 16) >> 5;
1038            let a = 16 * (top[7] + left[7]);
1039            for y in 0..8i32 {
1040                for x in 0..8i32 {
1041                    pred[(y * 8 + x) as usize] = clip1((a + b * (x - 3) + c * (y - 3) + 16) >> 5);
1042                }
1043            }
1044        }
1045        _ => {}
1046    }
1047    pred
1048}
1049
1050// ===================== 自己テスト =====================
1051
1052/// 既知解ベクタによる検証(起動時に実行)。
1053pub fn selftest() -> (usize, usize) {
1054    let mut passed = 0usize;
1055    let mut total = 0usize;
1056    let mut check = |cond: bool, label: &str| {
1057        total += 1;
1058        if cond {
1059            passed += 1;
1060        } else {
1061            crate::println!("H264_SELFTEST FAIL: {}", label);
1062        }
1063    };
1064
1065    // read_bits: 0b1011_0010 = 0xB2
1066    {
1067        let data = [0xB2u8];
1068        let mut r = BitReader::new(&data);
1069        check(r.read_bits(4) == 0b1011, "read_bits hi nibble");
1070        check(r.read_bits(4) == 0b0010, "read_bits lo nibble");
1071    }
1072
1073    // Exp-Golomb ue(v) の既知マッピング:
1074    //  "1"=0, "010"=1, "011"=2, "00100"=3, "00101"=4, "00110"=5, "00111"=6
1075    // ビット列 1 010 011 00100 を 1 バイト目に詰める: 1 0 1 0 0 1 1 0 = 0xA6
1076    //  続き 0100... → 次バイト 0100_0000 = 0x40
1077    {
1078        let data = [0xA6u8, 0x40u8];
1079        let mut r = BitReader::new(&data);
1080        check(r.read_ue() == 0, "ue=0");
1081        check(r.read_ue() == 1, "ue=1");
1082        check(r.read_ue() == 2, "ue=2");
1083        check(r.read_ue() == 3, "ue=3");
1084    }
1085
1086    // se(v): ue 0->0,1->1,2->-1,3->2,4->-2
1087    // ue 列 0,1,2,3,4 を se で読む。上と同じ先頭 + "00101"(=4)。
1088    // 1 010 011 00100 00101 → byte0=0xA6, byte1=0b0100_0010_1.. = 0x42 さらに 1 ビット
1089    {
1090        let data = [0xA6u8, 0x42u8, 0x80u8];
1091        let mut r = BitReader::new(&data);
1092        check(r.read_se() == 0, "se=0");
1093        check(r.read_se() == 1, "se=1");
1094        check(r.read_se() == -1, "se=-1");
1095        check(r.read_se() == 2, "se=2");
1096        check(r.read_se() == -2, "se=-2");
1097    }
1098
1099    // RBSP de-emulation: 00 00 03 01 -> 00 00 01
1100    {
1101        let inb = [0x00u8, 0x00, 0x03, 0x01, 0x00, 0x00, 0x03, 0x03];
1102        let out = extract_rbsp(&inb);
1103        check(
1104            out == alloc::vec![0x00, 0x00, 0x01, 0x00, 0x00, 0x03],
1105            "rbsp de-emulation",
1106        );
1107    }
1108
1109    // Annex-B 分割: 2 つの NAL(type 7 と 5)
1110    {
1111        let stream = [
1112            0x00, 0x00, 0x00, 0x01, 0x67, 0xAA, 0xBB, // SPS(7)
1113            0x00, 0x00, 0x01, 0x65, 0xCC, // IDR slice(5)
1114        ];
1115        let nals = split_annexb(&stream);
1116        check(nals.len() == 2, "annexb count");
1117        check(
1118            nals.first().map(|n| n.unit_type == 7).unwrap_or(false),
1119            "annexb nal0 type7",
1120        );
1121        check(
1122            nals.get(1).map(|n| n.unit_type == 5).unwrap_or(false),
1123            "annexb nal1 type5",
1124        );
1125    }
1126
1127    // AVCC 分割: [len=3][67 AA BB][len=2][65 CC]
1128    {
1129        let stream = [0, 0, 0, 3, 0x67, 0xAA, 0xBB, 0, 0, 0, 2, 0x65, 0xCC];
1130        let nals = split_avcc(&stream, 4);
1131        check(nals.len() == 2, "avcc count");
1132        check(
1133            nals.get(1)
1134                .map(|n| n.unit_type == 5 && n.rbsp == alloc::vec![0xCC])
1135                .unwrap_or(false),
1136            "avcc nal1",
1137        );
1138    }
1139
1140    // SPS パース(実 bitstream): baseline 160x144。
1141    // このバイト列は手計算でエンコードした検証済みベクタ:
1142    //   profile=66, level=30, sps_id=0, log2_max_frame_num=4, poc_type=0,
1143    //   max_num_ref=1, pic_width_in_mbs_minus1=9(=>160), height_map_units_minus1=8,
1144    //   frame_mbs_only=1, no cropping。
1145    {
1146        let sps = [0x42u8, 0x00, 0x1e, 0xF4, 0x14, 0x26, 0x20];
1147        match parse_sps(&sps) {
1148            Ok(s) => {
1149                check(s.profile_idc == 66, "sps profile baseline");
1150                check(s.frame_mbs_only_flag == 1, "sps frame_mbs_only");
1151                check(s.width == 160 && s.height == 144, "sps 160x144");
1152            }
1153            Err(_) => check(false, "sps parse ok"),
1154        }
1155    }
1156
1157    // 4x4 逆変換: 純 DC 入力は平坦ブロック (X+32)>>6 を生成。
1158    {
1159        let mut b = [0i32; 16];
1160        b[0] = 64;
1161        inverse_transform_4x4(&mut b);
1162        check(b.iter().all(|&v| v == 1), "idct pure DC=64 -> 1");
1163
1164        let mut b2 = [0i32; 16];
1165        b2[0] = 4096;
1166        inverse_transform_4x4(&mut b2);
1167        check(b2.iter().all(|&v| v == 64), "idct pure DC=4096 -> 64");
1168    }
1169
1170    // 4x4 逆変換: AC 係数 d(0,1)=64 の手計算結果(各行 [1,1,0,-1])。
1171    {
1172        let mut b = [0i32; 16];
1173        b[1] = 64;
1174        inverse_transform_4x4(&mut b);
1175        let expected = [1, 1, 0, -1, 1, 1, 0, -1, 1, 1, 0, -1, 1, 1, 0, -1];
1176        check(b == expected, "idct AC d(0,1)=64");
1177    }
1178
1179    // 逆量子化 + 逆変換の連鎖: c(0,0)=1, QP=24 -> dequant 160 -> 平坦 3。
1180    {
1181        let mut b = [0i32; 16];
1182        b[0] = 1;
1183        dequant_4x4(&mut b, 24);
1184        check(b[0] == 160, "dequant DC c=1 qp=24 -> 160");
1185        inverse_transform_4x4(&mut b);
1186        check(b.iter().all(|&v| v == 3), "dequant+idct -> flat 3");
1187    }
1188
1189    // 逆量子化の QP+6 倍化特性(ステップサイズが2倍)。
1190    {
1191        let mut a = [0i32; 16];
1192        a[5] = 1; // (1,1) class1
1193        let mut c = [0i32; 16];
1194        c[5] = 1;
1195        dequant_4x4(&mut a, 24);
1196        dequant_4x4(&mut c, 30);
1197        check(c[5] == a[5] * 2, "dequant qp+6 doubles");
1198    }
1199
1200    // イントラ 4x4: 定数近傍不変条件 — 全近傍が c なら全モードの全画素が c。
1201    // 重み付き平均の係数和とインデックス範囲を全 9 モードで検証する強力な不変条件。
1202    {
1203        let c = 100i32;
1204        let top = [c; 8];
1205        let left = [c; 4];
1206        let mut all_ok = true;
1207        for mode in 0..9u8 {
1208            let p = intra_4x4_predict(mode, &top, c, &left, true, true);
1209            if !p.iter().all(|&v| v == c) {
1210                all_ok = false;
1211                crate::println!("  intra4x4 mode {} broke constant", mode);
1212            }
1213        }
1214        check(all_ok, "intra4x4 constant invariant all modes");
1215    }
1216
1217    // イントラ 4x4 Vertical: 上ランプ [10,20,30,40] が各列にコピーされる。
1218    {
1219        let top = [10, 20, 30, 40, 50, 60, 70, 80];
1220        let left = [0; 4];
1221        let p = intra_4x4_predict(0, &top, 0, &left, true, true);
1222        check(
1223            p[0] == 10 && p[1] == 20 && p[2] == 30 && p[3] == 40 && p[12] == 10 && p[15] == 40,
1224            "intra4x4 vertical ramp",
1225        );
1226    }
1227
1228    // イントラ 4x4 Horizontal: 左ランプ [10,20,30,40] が各行にコピーされる。
1229    {
1230        let top = [0; 8];
1231        let left = [10, 20, 30, 40];
1232        let p = intra_4x4_predict(1, &top, 0, &left, true, true);
1233        check(
1234            p[0] == 10 && p[1] == 10 && p[4] == 20 && p[8] == 30 && p[12] == 40 && p[15] == 40,
1235            "intra4x4 horizontal ramp",
1236        );
1237    }
1238
1239    // イントラ 4x4 DC: top=[8,8,8,8], left=[16,16,16,16] -> (32+64+4)>>3 = 12。
1240    {
1241        let top = [8, 8, 8, 8, 8, 8, 8, 8];
1242        let left = [16, 16, 16, 16];
1243        let p = intra_4x4_predict(2, &top, 0, &left, true, true);
1244        check(p.iter().all(|&v| v == 12), "intra4x4 DC both avail");
1245    }
1246
1247    // イントラ 16x16: 定数近傍不変条件(全 4 モード、Plane 含む)。
1248    {
1249        let c = 77i32;
1250        let top = [c; 16];
1251        let left = [c; 16];
1252        let mut all_ok = true;
1253        for mode in 0..4u8 {
1254            let p = intra_16x16_predict(mode, &top, c, &left, true, true);
1255            if !p.iter().all(|&v| v == c) {
1256                all_ok = false;
1257                crate::println!("  intra16x16 mode {} broke constant", mode);
1258            }
1259        }
1260        check(all_ok, "intra16x16 constant invariant all modes");
1261    }
1262
1263    // イントラ 16x16 DC: top 全 40, left 全 80 -> (640+1280+16)>>5 = 60。
1264    {
1265        let top = [40; 16];
1266        let left = [80; 16];
1267        let p = intra_16x16_predict(2, &top, 0, &left, true, true);
1268        check(p.iter().all(|&v| v == 60), "intra16x16 DC value");
1269    }
1270
1271    // 実 x264 が出力した SPS(16x16, baseline)の EBSP を脱エミュレーション→パース。
1272    // 合成ベクタだけでなく実エンコーダ出力で SPS パーサを検証する。
1273    {
1274        let ebsp = [
1275            0x42u8, 0xc0, 0x0a, 0xdd, 0xec, 0x04, 0x40, 0x00, 0x00, 0x03, 0x00, 0x40, 0x00, 0x00,
1276            0x03, 0x00, 0x83, 0xc4, 0x89, 0xe0,
1277        ];
1278        let rbsp = extract_rbsp(&ebsp);
1279        match parse_sps(&rbsp) {
1280            Ok(s) => {
1281                check(s.profile_idc == 66, "real x264 SPS profile=66");
1282                check(s.width == 16 && s.height == 16, "real x264 SPS 16x16");
1283            }
1284            Err(_) => check(false, "real x264 SPS parse ok"),
1285        }
1286    }
1287
1288    // クロマ 8x8: 定数近傍不変条件(全 4 モード、Plane 含む)。
1289    {
1290        let c = 55i32;
1291        let top = [c; 8];
1292        let left = [c; 8];
1293        let mut all_ok = true;
1294        for mode in 0..4u8 {
1295            let p = intra_chroma_predict(mode, &top, c, &left, true, true);
1296            if !p.iter().all(|&v| v == c) {
1297                all_ok = false;
1298                crate::println!("  chroma mode {} broke constant", mode);
1299            }
1300        }
1301        check(all_ok, "chroma constant invariant all modes");
1302    }
1303
1304    // クロマ DC: 4 ブロックで別々の DC を計算する規則の検証。
1305    // top=[8,8,8,8, 40,40,40,40], left=[16,16,16,16, 60,60,60,60], 両可用。
1306    // t0=32,t1=160,l0=64,l1=240。
1307    // dc00=(32+64+4)>>3=12, dc10=(160+2)>>2=40, dc01=(240+2)>>2=60, dc11=(160+240+4)>>3=50。
1308    {
1309        let top = [8, 8, 8, 8, 40, 40, 40, 40];
1310        let left = [16, 16, 16, 16, 60, 60, 60, 60];
1311        let p = intra_chroma_predict(0, &top, 0, &left, true, true);
1312        check(p[0] == 12, "chroma DC block(0,0)=12");
1313        check(p[4] == 40, "chroma DC block(4,0)=40");
1314        check(p[32] == 60, "chroma DC block(0,4)=60");
1315        check(p[36] == 50, "chroma DC block(4,4)=50");
1316    }
1317
1318    (passed, total)
1319}
1320
1321/// デバッグ用の文字列表現。
1322pub fn describe_sps(s: &Sps) -> String {
1323    alloc::format!(
1324        "SPS profile={} level={} {}x{} mbs_only={}",
1325        s.profile_idc,
1326        s.level_idc,
1327        s.width,
1328        s.height,
1329        s.frame_mbs_only_flag
1330    )
1331}