Skip to main content

h264_reader/nal/slice/
macroblock.rs

1//! Macroblock type definitions for H.264 baseline profile.
2
3use std::fmt;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum MbPartPredMode {
7    Intra4x4,
8    Intra8x8,
9    Intra16x16,
10    PredL0,
11    Direct,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum MbTypeError {
16    InvalidIMbType(u32),
17    InvalidPMbType(u32),
18    InvalidPSubMbType(u32),
19    InvalidCodedBlockPatternCodeNum(u32),
20}
21impl std::error::Error for MbTypeError {}
22impl fmt::Display for MbTypeError {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        match self {
25            MbTypeError::InvalidIMbType(v) => write!(f, "invalid I mb_type value {}", v),
26            MbTypeError::InvalidPMbType(v) => write!(f, "invalid P mb_type value {}", v),
27            MbTypeError::InvalidPSubMbType(v) => write!(f, "invalid P sub_mb_type value {}", v),
28            MbTypeError::InvalidCodedBlockPatternCodeNum(v) => {
29                write!(f, "invalid coded_block_pattern code_num {}", v)
30            }
31        }
32    }
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub enum IMbTypeInfo {
37    INxN,
38    I16x16 {
39        intra16x16_pred_mode: u8,
40        coded_block_pattern_chroma: u8,
41        coded_block_pattern_luma: u8,
42    },
43    IPCM,
44}
45
46impl IMbTypeInfo {
47    pub fn pred_mode(&self) -> MbPartPredMode {
48        match self {
49            IMbTypeInfo::INxN => MbPartPredMode::Intra4x4,
50            IMbTypeInfo::I16x16 { .. } => MbPartPredMode::Intra16x16,
51            IMbTypeInfo::IPCM => MbPartPredMode::Intra4x4,
52        }
53    }
54
55    pub fn num_parts(&self) -> u8 {
56        1
57    }
58
59    pub fn part_width(&self) -> u8 {
60        16
61    }
62
63    pub fn part_height(&self) -> u8 {
64        16
65    }
66}
67
68pub fn i_mb_type_info(mb_type: u32) -> Result<IMbTypeInfo, MbTypeError> {
69    match mb_type {
70        0 => Ok(IMbTypeInfo::INxN),
71        1..=24 => {
72            let val = mb_type - 1;
73            let (coded_block_pattern_luma, idx) = if val < 12 { (0, val) } else { (15, val - 12) };
74            let intra16x16_pred_mode = (idx % 4) as u8;
75            let coded_block_pattern_chroma = (idx / 4) as u8;
76            Ok(IMbTypeInfo::I16x16 {
77                intra16x16_pred_mode,
78                coded_block_pattern_chroma,
79                coded_block_pattern_luma,
80            })
81        }
82        25 => Ok(IMbTypeInfo::IPCM),
83        _ => Err(MbTypeError::InvalidIMbType(mb_type)),
84    }
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub enum PMbTypeInfo {
89    P {
90        num_parts: u8,
91        pred_mode: MbPartPredMode,
92        part_width: u8,
93        part_height: u8,
94        ref_idx_forced_zero: bool,
95    },
96    I(IMbTypeInfo),
97}
98
99impl PMbTypeInfo {
100    pub fn num_parts(&self) -> u8 {
101        match self {
102            PMbTypeInfo::P { num_parts, .. } => *num_parts,
103            PMbTypeInfo::I(info) => info.num_parts(),
104        }
105    }
106
107    pub fn part_width(&self) -> u8 {
108        match self {
109            PMbTypeInfo::P { part_width, .. } => *part_width,
110            PMbTypeInfo::I(info) => info.part_width(),
111        }
112    }
113
114    pub fn part_height(&self) -> u8 {
115        match self {
116            PMbTypeInfo::P { part_height, .. } => *part_height,
117            PMbTypeInfo::I(info) => info.part_height(),
118        }
119    }
120
121    pub fn pred_mode(&self) -> MbPartPredMode {
122        match self {
123            PMbTypeInfo::P { pred_mode, .. } => *pred_mode,
124            PMbTypeInfo::I(info) => info.pred_mode(),
125        }
126    }
127}
128
129pub fn p_mb_type_info(mb_type: u32) -> Result<PMbTypeInfo, MbTypeError> {
130    match mb_type {
131        0 => Ok(PMbTypeInfo::P {
132            num_parts: 1,
133            pred_mode: MbPartPredMode::PredL0,
134            part_width: 16,
135            part_height: 16,
136            ref_idx_forced_zero: false,
137        }),
138        1 => Ok(PMbTypeInfo::P {
139            num_parts: 2,
140            pred_mode: MbPartPredMode::PredL0,
141            part_width: 16,
142            part_height: 8,
143            ref_idx_forced_zero: false,
144        }),
145        2 => Ok(PMbTypeInfo::P {
146            num_parts: 2,
147            pred_mode: MbPartPredMode::PredL0,
148            part_width: 8,
149            part_height: 16,
150            ref_idx_forced_zero: false,
151        }),
152        3 => Ok(PMbTypeInfo::P {
153            num_parts: 4,
154            pred_mode: MbPartPredMode::PredL0,
155            part_width: 8,
156            part_height: 8,
157            ref_idx_forced_zero: false,
158        }),
159        4 => Ok(PMbTypeInfo::P {
160            num_parts: 4,
161            pred_mode: MbPartPredMode::PredL0,
162            part_width: 8,
163            part_height: 8,
164            ref_idx_forced_zero: true,
165        }),
166        5..=30 => {
167            let i_type =
168                i_mb_type_info(mb_type - 5).map_err(|_| MbTypeError::InvalidPMbType(mb_type))?;
169            Ok(PMbTypeInfo::I(i_type))
170        }
171        _ => Err(MbTypeError::InvalidPMbType(mb_type)),
172    }
173}
174
175#[derive(Debug, Clone, PartialEq, Eq)]
176pub struct SubMbTypeInfo {
177    pub num_sub_parts: u8,
178    pub sub_part_width: u8,
179    pub sub_part_height: u8,
180    pub pred_mode: MbPartPredMode,
181}
182
183pub fn p_sub_mb_type_info(sub_mb_type: u32) -> Result<SubMbTypeInfo, MbTypeError> {
184    match sub_mb_type {
185        0 => Ok(SubMbTypeInfo {
186            num_sub_parts: 1,
187            sub_part_width: 8,
188            sub_part_height: 8,
189            pred_mode: MbPartPredMode::PredL0,
190        }),
191        1 => Ok(SubMbTypeInfo {
192            num_sub_parts: 2,
193            sub_part_width: 8,
194            sub_part_height: 4,
195            pred_mode: MbPartPredMode::PredL0,
196        }),
197        2 => Ok(SubMbTypeInfo {
198            num_sub_parts: 2,
199            sub_part_width: 4,
200            sub_part_height: 8,
201            pred_mode: MbPartPredMode::PredL0,
202        }),
203        3 => Ok(SubMbTypeInfo {
204            num_sub_parts: 4,
205            sub_part_width: 4,
206            sub_part_height: 4,
207            pred_mode: MbPartPredMode::PredL0,
208        }),
209        _ => Err(MbTypeError::InvalidPSubMbType(sub_mb_type)),
210    }
211}
212
213const CBP_INTRA_MAP: [u8; 48] = [
214    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,
215    35, 37, 42, 44, 1, 2, 4, 8, 17, 18, 20, 24, 6, 9, 22, 25, 32, 33, 34, 36, 40, 38, 41,
216];
217
218const CBP_INTER_MAP: [u8; 48] = [
219    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,
220    36, 40, 39, 43, 45, 46, 17, 18, 20, 24, 19, 21, 26, 28, 23, 27, 29, 30, 22, 25, 38, 41,
221];
222
223/// Convert a exp-golomb `code_num` into `(coded_block_pattern_luma, coded_block_pattern_chroma)`
224/// for 4:2:0 chroma format (Table 9-4).
225///
226/// - `code_num`: the decoded me(v) value, must be in range 0..=47
227/// - `is_intra`: `true` for Intra macroblocks, `false` for Inter macroblocks
228///
229/// Returns `None` if `code_num` is out of range.
230pub fn coded_block_pattern_from_me(code_num: u32, is_intra: bool) -> Option<(u8, u8)> {
231    if code_num > 47 {
232        return None;
233    }
234    let table = if is_intra {
235        &CBP_INTRA_MAP
236    } else {
237        &CBP_INTER_MAP
238    };
239    let cbp = table[code_num as usize];
240    let coded_block_pattern_luma = cbp % 16;
241    let coded_block_pattern_chroma = cbp / 16;
242    Some((coded_block_pattern_luma, coded_block_pattern_chroma))
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn i_mb_type_0_is_inxn() {
251        let info = i_mb_type_info(0).unwrap();
252        assert_eq!(info, IMbTypeInfo::INxN);
253        assert_eq!(info.pred_mode(), MbPartPredMode::Intra4x4);
254    }
255
256    #[test]
257    fn i_mb_type_1_is_i16x16_0_0_0() {
258        let info = i_mb_type_info(1).unwrap();
259        assert_eq!(
260            info,
261            IMbTypeInfo::I16x16 {
262                intra16x16_pred_mode: 0,
263                coded_block_pattern_chroma: 0,
264                coded_block_pattern_luma: 0,
265            }
266        );
267        assert_eq!(info.pred_mode(), MbPartPredMode::Intra16x16);
268    }
269
270    #[test]
271    fn i_mb_type_2_is_i16x16_1_0_0() {
272        let info = i_mb_type_info(2).unwrap();
273        assert_eq!(
274            info,
275            IMbTypeInfo::I16x16 {
276                intra16x16_pred_mode: 1,
277                coded_block_pattern_chroma: 0,
278                coded_block_pattern_luma: 0,
279            }
280        );
281    }
282
283    #[test]
284    fn i_mb_type_5_is_i16x16_0_1_0() {
285        let info = i_mb_type_info(5).unwrap();
286        assert_eq!(
287            info,
288            IMbTypeInfo::I16x16 {
289                intra16x16_pred_mode: 0,
290                coded_block_pattern_chroma: 1,
291                coded_block_pattern_luma: 0,
292            }
293        );
294    }
295
296    #[test]
297    fn i_mb_type_13_is_i16x16_0_0_1() {
298        let info = i_mb_type_info(13).unwrap();
299        assert_eq!(
300            info,
301            IMbTypeInfo::I16x16 {
302                intra16x16_pred_mode: 0,
303                coded_block_pattern_chroma: 0,
304                coded_block_pattern_luma: 15,
305            }
306        );
307    }
308
309    #[test]
310    fn i_mb_type_24_is_i16x16_3_2_1() {
311        let info = i_mb_type_info(24).unwrap();
312        assert_eq!(
313            info,
314            IMbTypeInfo::I16x16 {
315                intra16x16_pred_mode: 3,
316                coded_block_pattern_chroma: 2,
317                coded_block_pattern_luma: 15,
318            }
319        );
320    }
321
322    #[test]
323    fn i_mb_type_25_is_ipcm() {
324        let info = i_mb_type_info(25).unwrap();
325        assert_eq!(info, IMbTypeInfo::IPCM);
326    }
327
328    #[test]
329    fn i_mb_type_26_is_invalid() {
330        assert_eq!(i_mb_type_info(26), Err(MbTypeError::InvalidIMbType(26)));
331    }
332
333    #[test]
334    fn p_mb_type_0_is_p_l0_16x16() {
335        let info = p_mb_type_info(0).unwrap();
336        assert_eq!(
337            info,
338            PMbTypeInfo::P {
339                num_parts: 1,
340                pred_mode: MbPartPredMode::PredL0,
341                part_width: 16,
342                part_height: 16,
343                ref_idx_forced_zero: false,
344            }
345        );
346        assert_eq!(info.num_parts(), 1);
347    }
348
349    #[test]
350    fn p_mb_type_1_is_p_l0_l0_16x8() {
351        let info = p_mb_type_info(1).unwrap();
352        assert_eq!(info.num_parts(), 2);
353        assert_eq!(info.part_width(), 16);
354        assert_eq!(info.part_height(), 8);
355    }
356
357    #[test]
358    fn p_mb_type_2_is_p_l0_l0_8x16() {
359        let info = p_mb_type_info(2).unwrap();
360        assert_eq!(info.num_parts(), 2);
361        assert_eq!(info.part_width(), 8);
362        assert_eq!(info.part_height(), 16);
363    }
364
365    #[test]
366    fn p_mb_type_3_is_p_8x8() {
367        let info = p_mb_type_info(3).unwrap();
368        assert_eq!(info.num_parts(), 4);
369        assert_eq!(info.part_width(), 8);
370        assert_eq!(info.part_height(), 8);
371    }
372
373    #[test]
374    fn p_mb_type_4_is_p_8x8ref0() {
375        let info = p_mb_type_info(4).unwrap();
376        match info {
377            PMbTypeInfo::P {
378                ref_idx_forced_zero,
379                ..
380            } => assert!(ref_idx_forced_zero),
381            _ => panic!("expected P type"),
382        }
383    }
384
385    #[test]
386    fn p_mb_type_5_maps_to_inxn() {
387        let info = p_mb_type_info(5).unwrap();
388        assert_eq!(info, PMbTypeInfo::I(IMbTypeInfo::INxN));
389    }
390
391    #[test]
392    fn p_mb_type_6_maps_to_i16x16_0_0_0() {
393        let info = p_mb_type_info(6).unwrap();
394        assert_eq!(
395            info,
396            PMbTypeInfo::I(IMbTypeInfo::I16x16 {
397                intra16x16_pred_mode: 0,
398                coded_block_pattern_chroma: 0,
399                coded_block_pattern_luma: 0,
400            })
401        );
402    }
403
404    #[test]
405    fn p_mb_type_30_maps_to_ipcm() {
406        let info = p_mb_type_info(30).unwrap();
407        assert_eq!(info, PMbTypeInfo::I(IMbTypeInfo::IPCM));
408    }
409
410    #[test]
411    fn p_mb_type_31_is_invalid() {
412        assert_eq!(p_mb_type_info(31), Err(MbTypeError::InvalidPMbType(31)));
413    }
414
415    #[test]
416    fn p_sub_mb_type_0_is_8x8() {
417        let info = p_sub_mb_type_info(0).unwrap();
418        assert_eq!(
419            info,
420            SubMbTypeInfo {
421                num_sub_parts: 1,
422                sub_part_width: 8,
423                sub_part_height: 8,
424                pred_mode: MbPartPredMode::PredL0,
425            }
426        );
427    }
428
429    #[test]
430    fn p_sub_mb_type_1_is_8x4() {
431        let info = p_sub_mb_type_info(1).unwrap();
432        assert_eq!(info.num_sub_parts, 2);
433        assert_eq!(info.sub_part_width, 8);
434        assert_eq!(info.sub_part_height, 4);
435    }
436
437    #[test]
438    fn p_sub_mb_type_2_is_4x8() {
439        let info = p_sub_mb_type_info(2).unwrap();
440        assert_eq!(info.num_sub_parts, 2);
441        assert_eq!(info.sub_part_width, 4);
442        assert_eq!(info.sub_part_height, 8);
443    }
444
445    #[test]
446    fn p_sub_mb_type_3_is_4x4() {
447        let info = p_sub_mb_type_info(3).unwrap();
448        assert_eq!(info.num_sub_parts, 4);
449        assert_eq!(info.sub_part_width, 4);
450        assert_eq!(info.sub_part_height, 4);
451    }
452
453    #[test]
454    fn p_sub_mb_type_4_is_invalid() {
455        assert_eq!(
456            p_sub_mb_type_info(4),
457            Err(MbTypeError::InvalidPSubMbType(4))
458        );
459    }
460
461    #[test]
462    fn cbp_intra_code_num_0_gives_15_2() {
463        let (luma, chroma) = coded_block_pattern_from_me(0, true).unwrap();
464        assert_eq!(luma, 15);
465        assert_eq!(chroma, 2);
466    }
467
468    #[test]
469    fn cbp_inter_code_num_0_gives_0_0() {
470        let (luma, chroma) = coded_block_pattern_from_me(0, false).unwrap();
471        assert_eq!(luma, 0);
472        assert_eq!(chroma, 0);
473    }
474
475    #[test]
476    fn cbp_intra_code_num_3_gives_0_0() {
477        let (luma, chroma) = coded_block_pattern_from_me(3, true).unwrap();
478        assert_eq!(luma, 0);
479        assert_eq!(chroma, 0);
480    }
481
482    #[test]
483    fn cbp_inter_code_num_1_gives_0_1() {
484        let (luma, chroma) = coded_block_pattern_from_me(1, false).unwrap();
485        assert_eq!(luma, 0);
486        assert_eq!(chroma, 1);
487    }
488
489    #[test]
490    fn cbp_inter_code_num_11_gives_15_0() {
491        let (luma, chroma) = coded_block_pattern_from_me(11, false).unwrap();
492        assert_eq!(luma, 15);
493        assert_eq!(chroma, 0);
494    }
495
496    #[test]
497    fn cbp_code_num_47_is_valid() {
498        assert!(coded_block_pattern_from_me(47, true).is_some());
499        assert!(coded_block_pattern_from_me(47, false).is_some());
500    }
501
502    #[test]
503    fn cbp_code_num_48_is_invalid() {
504        assert!(coded_block_pattern_from_me(48, true).is_none());
505        assert!(coded_block_pattern_from_me(48, false).is_none());
506    }
507
508    #[test]
509    fn all_i_mb_types_valid() {
510        for mb_type in 0..=25 {
511            assert!(
512                i_mb_type_info(mb_type).is_ok(),
513                "mb_type {} should be valid",
514                mb_type
515            );
516        }
517    }
518
519    #[test]
520    fn all_i16x16_pred_modes_in_range() {
521        for mb_type in 1..=24 {
522            if let IMbTypeInfo::I16x16 {
523                intra16x16_pred_mode,
524                coded_block_pattern_chroma,
525                ..
526            } = i_mb_type_info(mb_type).unwrap()
527            {
528                assert!(
529                    intra16x16_pred_mode <= 3,
530                    "pred_mode out of range for mb_type {}",
531                    mb_type
532                );
533                assert!(
534                    coded_block_pattern_chroma <= 2,
535                    "cbp_chroma out of range for mb_type {}",
536                    mb_type
537                );
538            }
539        }
540    }
541}