Skip to main content

arcly_stream/codec/
nal.rs

1//! Shared Annex-B NAL utilities for the NAL-based codecs (H.264, H.265, VVC).
2//!
3//! All three frame their bitstreams identically — `00 00 01` / `00 00 00 01`
4//! start codes with `00 00 03` emulation prevention — and differ only in NAL
5//! header layout and parameter-set field order. This module is the single
6//! canonical implementation of start-code scanning and RBSP de-emulation.
7
8/// Iterate the NAL units of an Annex-B bytestream, yielding each NAL payload
9/// (including its NAL header) **without** the start code.
10pub fn iter_nals(data: &[u8]) -> NalIter<'_> {
11    NalIter { data, pos: 0 }
12}
13
14/// Iterator over Annex-B NAL units. See [`iter_nals`].
15pub struct NalIter<'a> {
16    data: &'a [u8],
17    pos: usize,
18}
19
20impl<'a> Iterator for NalIter<'a> {
21    type Item = &'a [u8];
22
23    fn next(&mut self) -> Option<&'a [u8]> {
24        let start = next_start_code(self.data, self.pos)?;
25        let nal_begin = start.0 + start.1;
26        let nal_end = match next_start_code(self.data, nal_begin) {
27            Some((off, _)) => off,
28            None => self.data.len(),
29        };
30        self.pos = nal_end;
31        if nal_begin >= nal_end {
32            return self.next();
33        }
34        Some(&self.data[nal_begin..nal_end])
35    }
36}
37
38/// Find the next start code at/after `from`, returning `(offset, len)` where
39/// `len` is 3 (`00 00 01`) or 4 (`00 00 00 01`).
40pub fn next_start_code(data: &[u8], from: usize) -> Option<(usize, usize)> {
41    let mut i = from;
42    while i + 3 <= data.len() {
43        if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
44            if i > from && data[i - 1] == 0 {
45                return Some((i - 1, 4));
46            }
47            return Some((i, 3));
48        }
49        i += 1;
50    }
51    None
52}
53
54/// Strip emulation-prevention bytes (`00 00 03` → `00 00`) from an RBSP.
55pub fn unescape_rbsp(nal: &[u8]) -> Vec<u8> {
56    let mut out = Vec::with_capacity(nal.len());
57    let mut zeros = 0;
58    for &b in nal {
59        if zeros >= 2 && b == 0x03 {
60            zeros = 0;
61            continue;
62        }
63        if b == 0 {
64            zeros += 1;
65        } else {
66            zeros = 0;
67        }
68        out.push(b);
69    }
70    out
71}
72
73/// Apply HEVC/VVC conformance-window cropping to coded luma dimensions,
74/// returning the displayed `(width, height)`. `chroma_format_idc` selects the
75/// crop unit (4:2:0 → 2×2, 4:2:2 → 2×1, 4:4:4 / mono → 1×1).
76pub fn conformance_dims(
77    width_luma: u32,
78    height_luma: u32,
79    chroma_format_idc: u32,
80    crop_left: u32,
81    crop_right: u32,
82    crop_top: u32,
83    crop_bottom: u32,
84) -> (u32, u32) {
85    let (sub_w, sub_h) = match chroma_format_idc {
86        1 => (2, 2), // 4:2:0
87        2 => (2, 1), // 4:2:2
88        _ => (1, 1), // 4:4:4 or monochrome
89    };
90    let width = width_luma.saturating_sub((crop_left + crop_right) * sub_w);
91    let height = height_luma.saturating_sub((crop_top + crop_bottom) * sub_h);
92    (width, height)
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn iterates_three_and_four_byte_start_codes() {
101        let data = [0, 0, 0, 1, 9, 0xF0, 0, 0, 1, 7, 0x42];
102        let nals: Vec<&[u8]> = iter_nals(&data).collect();
103        assert_eq!(nals, vec![&[9u8, 0xF0][..], &[7u8, 0x42][..]]);
104    }
105
106    #[test]
107    fn unescape_removes_emulation_bytes() {
108        assert_eq!(unescape_rbsp(&[0, 0, 3, 1]), vec![0, 0, 1]);
109        assert_eq!(unescape_rbsp(&[0, 0, 3, 0, 0, 3, 2]), vec![0, 0, 0, 0, 2]);
110    }
111
112    #[test]
113    fn conformance_crop_420() {
114        // 1920x1088 coded, crop 4 rows off the bottom in 4:2:0 → 1920x1080.
115        assert_eq!(conformance_dims(1920, 1088, 1, 0, 0, 0, 4), (1920, 1080));
116    }
117}