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 {
182            0
183        } else {
184            chroma_format_idc
185        };
186        let (sub_w, sub_h) = match chroma_array_type {
187            1 => (2, 2), // 4:2:0
188            2 => (2, 1), // 4:2:2
189            _ => (1, 1), // monochrome / 4:4:4
190        };
191        let crop_y = sub_h * (2 - frame_mbs_only);
192        width = width.checked_sub((left + right).checked_mul(sub_w)?)?;
193        height = height.checked_sub((top + bottom).checked_mul(crop_y)?)?;
194    }
195    (width > 0 && height > 0).then_some((width, height))
196}
197
198/// Split an Annex-B bitstream into NAL units (start codes removed). The
199/// mux-side inverse of [`avcc_to_annexb`], shared by every container that
200/// stores AVCC (MP4, Matroska).
201pub fn split_annexb(data: &[u8]) -> Vec<&[u8]> {
202    let mut nals = Vec::new();
203    let mut i = 0;
204    let mut nal_start: Option<usize> = None;
205    while i + 3 <= data.len() {
206        if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
207            if let Some(s) = nal_start {
208                // A leading zero before this start code belongs to a 4-byte code.
209                let end = if i > s && data[i - 1] == 0 { i - 1 } else { i };
210                if end > s {
211                    nals.push(&data[s..end]);
212                }
213            }
214            i += 3;
215            nal_start = Some(i);
216        } else {
217            i += 1;
218        }
219    }
220    if let Some(s) = nal_start {
221        if s < data.len() {
222            nals.push(&data[s..]);
223        }
224    }
225    nals
226}
227
228/// Build an AVCDecoderConfigurationRecord (the `avcC` payload, no box header)
229/// from the SPS and PPS NALs. MP4 wraps it in an `avcC` box; Matroska stores it
230/// raw as CodecPrivate.
231pub fn build_avcc_record(sps: &[u8], pps: &[u8]) -> Vec<u8> {
232    let mut b = Vec::new();
233    b.push(1); // configurationVersion
234    b.push(*sps.get(1).unwrap_or(&0x42)); // AVCProfileIndication
235    b.push(*sps.get(2).unwrap_or(&0)); // profile_compatibility
236    b.push(*sps.get(3).unwrap_or(&30)); // AVCLevelIndication
237    b.push(0xFF); // 6 bits reserved + lengthSizeMinusOne = 3 (4-byte lengths)
238    b.push(0xE1); // 3 bits reserved + numOfSPS = 1
239    b.extend_from_slice(&(sps.len() as u16).to_be_bytes());
240    b.extend_from_slice(sps);
241    b.push(1); // numOfPPS
242    b.extend_from_slice(&(pps.len() as u16).to_be_bytes());
243    b.extend_from_slice(pps);
244    b
245}
246
247/// Consume a `scaling_list()` of `size` entries (we only need the bits gone).
248fn skip_scaling_list(r: &mut Bits, size: u32) -> Option<()> {
249    let mut next = 8i32;
250    for _ in 0..size {
251        if next != 0 {
252            next = (next + r.se()? + 256) % 256;
253        }
254        if next == 0 {
255            break; // remaining entries repeat the last — no more bits follow
256        }
257    }
258    Some(())
259}
260
261/// MSB-first bit reader over an RBSP.
262struct Bits<'a> {
263    d: &'a [u8],
264    pos: usize, // in bits
265}
266
267impl<'a> Bits<'a> {
268    fn new(d: &'a [u8]) -> Bits<'a> {
269        Bits { d, pos: 0 }
270    }
271
272    fn bit(&mut self) -> Option<u32> {
273        let b = *self.d.get(self.pos / 8)?;
274        let v = (b >> (7 - self.pos % 8)) & 1;
275        self.pos += 1;
276        Some(v as u32)
277    }
278
279    fn bits(&mut self, n: u32) -> Option<u32> {
280        let mut v = 0;
281        for _ in 0..n {
282            v = (v << 1) | self.bit()?;
283        }
284        Some(v)
285    }
286
287    /// Unsigned Exp-Golomb (`ue(v)`).
288    fn ue(&mut self) -> Option<u32> {
289        let mut zeros = 0;
290        while self.bit()? == 0 {
291            zeros += 1;
292            if zeros > 31 {
293                return None;
294            }
295        }
296        Some((1u32 << zeros) - 1 + if zeros > 0 { self.bits(zeros)? } else { 0 })
297    }
298
299    /// Signed Exp-Golomb (`se(v)`).
300    fn se(&mut self) -> Option<i32> {
301        let k = self.ue()? as i64;
302        Some(if k % 2 == 1 { (k + 1) / 2 } else { -(k / 2) } as i32)
303    }
304}
305
306fn be16(d: &[u8], i: usize) -> Option<u16> {
307    Some(((*d.get(i)? as u16) << 8) | *d.get(i + 1)? as u16)
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313
314    #[test]
315    fn avcc_roundtrip_to_annexb() {
316        // avcC: ver 1, profile/compat/level, nal_len=4, 1 SPS (3 bytes), 1 PPS (2 bytes).
317        let avcc = [
318            1, 100, 0, 31, 0xFF, 0xE1, 0, 3, 0x67, 0xAA, 0xBB, 1, 0, 2, 0x68, 0xCC,
319        ];
320        let cfg = parse_avcc(&avcc).unwrap();
321        assert_eq!(cfg.nal_len, 4);
322        assert_eq!(
323            cfg.headers_annexb,
324            &[0, 0, 0, 1, 0x67, 0xAA, 0xBB, 0, 0, 0, 1, 0x68, 0xCC]
325        );
326
327        let sample = [0, 0, 0, 2, 0x41, 0x9A, 0, 0, 0, 1, 0x06];
328        let mut out = Vec::new();
329        avcc_to_annexb(&sample, 4, &mut out);
330        assert_eq!(out, &[0, 0, 0, 1, 0x41, 0x9A, 0, 0, 0, 1, 0x06]);
331    }
332
333    #[test]
334    fn finds_sps_between_start_codes() {
335        let data = [
336            0, 0, 0, 1, 0x09, 0xF0, 0, 0, 0, 1, 0x67, 0xAA, 0, 0, 1, 0x68,
337        ];
338        assert_eq!(find_sps_annexb(&data), Some(&[0x67u8, 0xAA][..]));
339        // SPS as the last NAL.
340        let data = [0, 0, 1, 0x67, 0xAA, 0xBB];
341        assert_eq!(find_sps_annexb(&data), Some(&[0x67u8, 0xAA, 0xBB][..]));
342        assert_eq!(find_sps_annexb(&[0, 0, 1, 0x68, 0x00]), None);
343    }
344
345    #[test]
346    fn sps_dimensions_x264_640x360() {
347        // x264 High-profile SPS for 640x360 (360 = 368 coded − 8 crop).
348        let sps = [
349            0x67, 0x64, 0x00, 0x1E, 0xAC, 0xD9, 0x40, 0xA0, 0x2F, 0xF9, 0x70, 0x11, 0x00, 0x00,
350            0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x30, 0x0F, 0x16, 0x2D, 0x96,
351        ];
352        assert_eq!(sps_dimensions(&sps), Some((640, 360)));
353    }
354
355    #[test]
356    fn sps_dimensions_baseline_176x144() {
357        // Baseline QCIF SPS (no cropping, no High-profile fields).
358        let sps = [
359            0x67, 0x42, 0xC0, 0x0D, 0x8C, 0x8D, 0x41, 0x62, 0x64, 0x0A, 0x60, 0x80,
360        ];
361        assert_eq!(sps_dimensions(&sps), Some((176, 144)));
362    }
363}