Skip to main content

rff_format/
avc.rs

1//! H.264 bitstream helpers shared by demuxers (FFmpeg's `h264_mp4toannexb`
2//! bitstream filter plus a minimal SPS reader).
3//!
4//! rff's packet contract for H.264 is **Annex-B**: every NAL prefixed with a
5//! `00 00 00 01` start code, SPS/PPS repeated before each keyframe, and
6//! `Stream::extradata` left empty. Containers that store AVCC (length-prefixed
7//! NALs + an `avcC` config record) — MP4 and Matroska — use [`parse_avcc`] +
8//! [`avcc_to_annexb`] to normalise, so consumers see one format regardless of
9//! the container. [`sps_dimensions`] recovers coded width/height for containers
10//! whose headers carry none (MPEG-TS).
11
12/// Per-track H.264 config for AVCC→Annex-B conversion, parsed from an `avcC`
13/// (AVCDecoderConfigurationRecord) box.
14pub struct AvcConfig {
15    /// Bytes in each NAL length prefix (1, 2 or 4).
16    pub nal_len: usize,
17    /// The SPS + PPS NALs, already start-code prefixed, ready to prepend to
18    /// keyframe packets.
19    pub headers_annexb: Vec<u8>,
20}
21
22/// Parse an `avcC` box into the NAL length size and Annex-B SPS/PPS headers.
23pub fn parse_avcc(avcc: &[u8]) -> Option<AvcConfig> {
24    if avcc.len() < 6 {
25        return None;
26    }
27    let nal_len = (avcc[4] & 0x03) as usize + 1;
28    let mut headers = Vec::new();
29    let mut i = 5;
30    let num_sps = avcc[i] & 0x1F;
31    i += 1;
32    for _ in 0..num_sps {
33        let len = be16(avcc, i)? as usize;
34        i += 2;
35        let nal = avcc.get(i..i + len)?;
36        headers.extend_from_slice(&[0, 0, 0, 1]);
37        headers.extend_from_slice(nal);
38        i += len;
39    }
40    let num_pps = *avcc.get(i)?;
41    i += 1;
42    for _ in 0..num_pps {
43        let len = be16(avcc, i)? as usize;
44        i += 2;
45        let nal = avcc.get(i..i + len)?;
46        headers.extend_from_slice(&[0, 0, 0, 1]);
47        headers.extend_from_slice(nal);
48        i += len;
49    }
50    Some(AvcConfig {
51        nal_len,
52        headers_annexb: headers,
53    })
54}
55
56/// Convert one AVCC sample (a series of `nal_len`-byte length + NAL) to Annex-B
57/// (each NAL prefixed with a `00 00 00 01` start code), appending to `out`.
58pub fn avcc_to_annexb(sample: &[u8], nal_len: usize, out: &mut Vec<u8>) {
59    let mut i = 0;
60    while i + nal_len <= sample.len() {
61        let mut len = 0usize;
62        for _ in 0..nal_len {
63            len = (len << 8) | sample[i] as usize;
64            i += 1;
65        }
66        if i + len > sample.len() {
67            break;
68        }
69        out.extend_from_slice(&[0, 0, 0, 1]);
70        out.extend_from_slice(&sample[i..i + len]);
71        i += len;
72    }
73}
74
75/// Find the first SPS NAL (type 7) in an Annex-B stream. Returns the NAL
76/// including its header byte, without the start code.
77pub fn find_sps_annexb(data: &[u8]) -> Option<&[u8]> {
78    let mut i = 0;
79    let mut nal_start: Option<usize> = None;
80    while i + 3 <= data.len() {
81        if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
82            if let Some(s) = nal_start {
83                // End of the previous NAL (trim the 0 of a 4-byte start code).
84                let end = if i > 0 && data[i - 1] == 0 { i - 1 } else { i };
85                if data[s] & 0x1F == 7 {
86                    return Some(&data[s..end]);
87                }
88            }
89            nal_start = Some(i + 3);
90            i += 3;
91        } else {
92            i += 1;
93        }
94    }
95    let s = nal_start?;
96    (*data.get(s)? & 0x1F == 7).then(|| &data[s..])
97}
98
99/// Decode coded width/height from an SPS NAL (header byte included), applying
100/// the frame cropping rectangle. Handles High-profile chroma/bit-depth fields
101/// and scaling matrices. Returns `None` on any malformed or truncated field.
102pub fn sps_dimensions(nal: &[u8]) -> Option<(u32, u32)> {
103    if nal.first()? & 0x1F != 7 {
104        return None;
105    }
106    // RBSP: strip emulation-prevention bytes (00 00 03 → 00 00).
107    let mut rbsp = Vec::with_capacity(nal.len());
108    let mut zeros = 0u32;
109    for &b in &nal[1..] {
110        if zeros >= 2 && b == 3 {
111            zeros = 0;
112            continue;
113        }
114        zeros = if b == 0 { zeros + 1 } else { 0 };
115        rbsp.push(b);
116    }
117
118    let mut r = Bits::new(&rbsp);
119    let profile_idc = r.bits(8)?;
120    r.bits(16)?; // constraint flags + reserved + level_idc
121    r.ue()?; // seq_parameter_set_id
122
123    let mut chroma_format_idc = 1; // 4:2:0 unless the profile spells it out
124    let mut separate_colour_plane = false;
125    if matches!(
126        profile_idc,
127        100 | 110 | 122 | 244 | 44 | 83 | 86 | 118 | 128 | 138 | 139 | 134 | 135
128    ) {
129        chroma_format_idc = r.ue()?;
130        if chroma_format_idc == 3 {
131            separate_colour_plane = r.bit()? == 1;
132        }
133        r.ue()?; // bit_depth_luma_minus8
134        r.ue()?; // bit_depth_chroma_minus8
135        r.bit()?; // qpprime_y_zero_transform_bypass_flag
136        if r.bit()? == 1 {
137            // seq_scaling_matrix_present: skip each present scaling list.
138            let lists = if chroma_format_idc != 3 { 8 } else { 12 };
139            for i in 0..lists {
140                if r.bit()? == 1 {
141                    skip_scaling_list(&mut r, if i < 6 { 16 } else { 64 })?;
142                }
143            }
144        }
145    }
146
147    r.ue()?; // log2_max_frame_num_minus4
148    match r.ue()? {
149        0 => {
150            r.ue()?; // log2_max_pic_order_cnt_lsb_minus4
151        }
152        1 => {
153            r.bit()?; // delta_pic_order_always_zero_flag
154            r.se()?; // offset_for_non_ref_pic
155            r.se()?; // offset_for_top_to_bottom_field
156            let n = r.ue()?;
157            for _ in 0..n {
158                r.se()?; // offset_for_ref_frame
159            }
160        }
161        _ => {}
162    }
163    r.ue()?; // max_num_ref_frames
164    r.bit()?; // gaps_in_frame_num_value_allowed_flag
165
166    let width_mbs = r.ue()? + 1;
167    let height_map_units = r.ue()? + 1;
168    let frame_mbs_only = r.bit()?;
169    if frame_mbs_only == 0 {
170        r.bit()?; // mb_adaptive_frame_field_flag
171    }
172    r.bit()?; // direct_8x8_inference_flag
173
174    let mut width = width_mbs.checked_mul(16)?;
175    let mut height = (2 - frame_mbs_only)
176        .checked_mul(height_map_units)?
177        .checked_mul(16)?;
178    if r.bit()? == 1 {
179        // frame_cropping: units depend on the chroma sampling (Table 6-1).
180        let (left, right, top, bottom) = (r.ue()?, r.ue()?, r.ue()?, r.ue()?);
181        let chroma_array_type = if separate_colour_plane { 0 } else { chroma_format_idc };
182        let (sub_w, sub_h) = match chroma_array_type {
183            1 => (2, 2), // 4:2:0
184            2 => (2, 1), // 4:2:2
185            _ => (1, 1), // monochrome / 4:4:4
186        };
187        let crop_y = sub_h * (2 - frame_mbs_only);
188        width = width.checked_sub((left + right).checked_mul(sub_w)?)?;
189        height = height.checked_sub((top + bottom).checked_mul(crop_y)?)?;
190    }
191    (width > 0 && height > 0).then_some((width, height))
192}
193
194/// Split an Annex-B bitstream into NAL units (start codes removed). The
195/// mux-side inverse of [`avcc_to_annexb`], shared by every container that
196/// stores AVCC (MP4, Matroska).
197pub fn split_annexb(data: &[u8]) -> Vec<&[u8]> {
198    let mut nals = Vec::new();
199    let mut i = 0;
200    let mut nal_start: Option<usize> = None;
201    while i + 3 <= data.len() {
202        if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
203            if let Some(s) = nal_start {
204                // A leading zero before this start code belongs to a 4-byte code.
205                let end = if i > s && data[i - 1] == 0 { i - 1 } else { i };
206                if end > s {
207                    nals.push(&data[s..end]);
208                }
209            }
210            i += 3;
211            nal_start = Some(i);
212        } else {
213            i += 1;
214        }
215    }
216    if let Some(s) = nal_start {
217        if s < data.len() {
218            nals.push(&data[s..]);
219        }
220    }
221    nals
222}
223
224/// Build an AVCDecoderConfigurationRecord (the `avcC` payload, no box header)
225/// from the SPS and PPS NALs. MP4 wraps it in an `avcC` box; Matroska stores it
226/// raw as CodecPrivate.
227pub fn build_avcc_record(sps: &[u8], pps: &[u8]) -> Vec<u8> {
228    let mut b = Vec::new();
229    b.push(1); // configurationVersion
230    b.push(*sps.get(1).unwrap_or(&0x42)); // AVCProfileIndication
231    b.push(*sps.get(2).unwrap_or(&0)); // profile_compatibility
232    b.push(*sps.get(3).unwrap_or(&30)); // AVCLevelIndication
233    b.push(0xFF); // 6 bits reserved + lengthSizeMinusOne = 3 (4-byte lengths)
234    b.push(0xE1); // 3 bits reserved + numOfSPS = 1
235    b.extend_from_slice(&(sps.len() as u16).to_be_bytes());
236    b.extend_from_slice(sps);
237    b.push(1); // numOfPPS
238    b.extend_from_slice(&(pps.len() as u16).to_be_bytes());
239    b.extend_from_slice(pps);
240    b
241}
242
243/// Consume a `scaling_list()` of `size` entries (we only need the bits gone).
244fn skip_scaling_list(r: &mut Bits, size: u32) -> Option<()> {
245    let mut next = 8i32;
246    for _ in 0..size {
247        if next != 0 {
248            next = (next + r.se()? + 256) % 256;
249        }
250        if next == 0 {
251            break; // remaining entries repeat the last — no more bits follow
252        }
253    }
254    Some(())
255}
256
257/// MSB-first bit reader over an RBSP.
258struct Bits<'a> {
259    d: &'a [u8],
260    pos: usize, // in bits
261}
262
263impl<'a> Bits<'a> {
264    fn new(d: &'a [u8]) -> Bits<'a> {
265        Bits { d, pos: 0 }
266    }
267
268    fn bit(&mut self) -> Option<u32> {
269        let b = *self.d.get(self.pos / 8)?;
270        let v = (b >> (7 - self.pos % 8)) & 1;
271        self.pos += 1;
272        Some(v as u32)
273    }
274
275    fn bits(&mut self, n: u32) -> Option<u32> {
276        let mut v = 0;
277        for _ in 0..n {
278            v = (v << 1) | self.bit()?;
279        }
280        Some(v)
281    }
282
283    /// Unsigned Exp-Golomb (`ue(v)`).
284    fn ue(&mut self) -> Option<u32> {
285        let mut zeros = 0;
286        while self.bit()? == 0 {
287            zeros += 1;
288            if zeros > 31 {
289                return None;
290            }
291        }
292        Some((1u32 << zeros) - 1 + if zeros > 0 { self.bits(zeros)? } else { 0 })
293    }
294
295    /// Signed Exp-Golomb (`se(v)`).
296    fn se(&mut self) -> Option<i32> {
297        let k = self.ue()? as i64;
298        Some(if k % 2 == 1 { (k + 1) / 2 } else { -(k / 2) } as i32)
299    }
300}
301
302fn be16(d: &[u8], i: usize) -> Option<u16> {
303    Some(((*d.get(i)? as u16) << 8) | *d.get(i + 1)? as u16)
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309
310    #[test]
311    fn avcc_roundtrip_to_annexb() {
312        // avcC: ver 1, profile/compat/level, nal_len=4, 1 SPS (3 bytes), 1 PPS (2 bytes).
313        let avcc = [
314            1, 100, 0, 31, 0xFF, 0xE1, 0, 3, 0x67, 0xAA, 0xBB, 1, 0, 2, 0x68, 0xCC,
315        ];
316        let cfg = parse_avcc(&avcc).unwrap();
317        assert_eq!(cfg.nal_len, 4);
318        assert_eq!(
319            cfg.headers_annexb,
320            &[0, 0, 0, 1, 0x67, 0xAA, 0xBB, 0, 0, 0, 1, 0x68, 0xCC]
321        );
322
323        let sample = [0, 0, 0, 2, 0x41, 0x9A, 0, 0, 0, 1, 0x06];
324        let mut out = Vec::new();
325        avcc_to_annexb(&sample, 4, &mut out);
326        assert_eq!(out, &[0, 0, 0, 1, 0x41, 0x9A, 0, 0, 0, 1, 0x06]);
327    }
328
329    #[test]
330    fn finds_sps_between_start_codes() {
331        let data = [0, 0, 0, 1, 0x09, 0xF0, 0, 0, 0, 1, 0x67, 0xAA, 0, 0, 1, 0x68];
332        assert_eq!(find_sps_annexb(&data), Some(&[0x67u8, 0xAA][..]));
333        // SPS as the last NAL.
334        let data = [0, 0, 1, 0x67, 0xAA, 0xBB];
335        assert_eq!(find_sps_annexb(&data), Some(&[0x67u8, 0xAA, 0xBB][..]));
336        assert_eq!(find_sps_annexb(&[0, 0, 1, 0x68, 0x00]), None);
337    }
338
339    #[test]
340    fn sps_dimensions_x264_640x360() {
341        // x264 High-profile SPS for 640x360 (360 = 368 coded − 8 crop).
342        let sps = [
343            0x67, 0x64, 0x00, 0x1E, 0xAC, 0xD9, 0x40, 0xA0, 0x2F, 0xF9, 0x70, 0x11, 0x00, 0x00,
344            0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x30, 0x0F, 0x16, 0x2D, 0x96,
345        ];
346        assert_eq!(sps_dimensions(&sps), Some((640, 360)));
347    }
348
349    #[test]
350    fn sps_dimensions_baseline_176x144() {
351        // Baseline QCIF SPS (no cropping, no High-profile fields).
352        let sps = [0x67, 0x42, 0xC0, 0x0D, 0x8C, 0x8D, 0x41, 0x62, 0x64, 0x0A, 0x60, 0x80];
353        assert_eq!(sps_dimensions(&sps), Some((176, 144)));
354    }
355}