Skip to main content

rff_format/
hvc.rs

1//! HEVC bitstream helpers for containers: the `hvcC`
2//! (HEVCDecoderConfigurationRecord, ISO/IEC 14496-15 §8.3.3) parser and
3//! builder, plus a minimal SPS reader.
4//!
5//! rff's packet contract for HEVC is the same as for H.264: **Annex-B**, with
6//! parameter sets repeated before each keyframe and `Stream::extradata` left
7//! empty. `hvc1`/`hev1` (MP4) and `V_MPEGH/ISO/HEVC` (Matroska) store
8//! length-prefixed NALs plus an `hvcC` record, so demuxers normalise with
9//! [`parse_hvcc`] and [`crate::avc::avcc_to_annexb`] (the length-prefix
10//! conversion is codec-independent).
11
12use crate::avc::AvcConfig;
13
14/// Parse an `hvcC` record into the NAL length size and Annex-B VPS/SPS/PPS
15/// headers, in the same shape the AVCC path produces.
16pub fn parse_hvcc(hvcc: &[u8]) -> Option<AvcConfig> {
17    if hvcc.len() < 23 || hvcc[0] != 1 {
18        return None;
19    }
20    let nal_len = (hvcc[21] & 0x03) as usize + 1;
21    let num_arrays = hvcc[22] as usize;
22    let mut headers = Vec::new();
23    let mut i = 23;
24    for _ in 0..num_arrays {
25        let nal_type = *hvcc.get(i)? & 0x3f;
26        i += 1;
27        let count = be16(hvcc, i)? as usize;
28        i += 2;
29        for _ in 0..count {
30            let len = be16(hvcc, i)? as usize;
31            i += 2;
32            let nal = hvcc.get(i..i + len)?;
33            // Only the parameter sets are worth prepending (32 VPS, 33 SPS,
34            // 34 PPS); a record may also carry prefix SEI arrays.
35            if matches!(nal_type, 32 | 33 | 34) {
36                headers.extend_from_slice(&[0, 0, 0, 1]);
37                headers.extend_from_slice(nal);
38            }
39            i += len;
40        }
41    }
42    if headers.is_empty() {
43        return None;
44    }
45    Some(AvcConfig {
46        nal_len,
47        headers_annexb: headers,
48    })
49}
50
51/// The SPS fields an `hvcC` record has to repeat.
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct SpsInfo {
54    pub profile_space: u8,
55    pub tier_flag: bool,
56    pub profile_idc: u8,
57    pub profile_compatibility_flags: u32,
58    pub constraint_flags: [u8; 6],
59    pub level_idc: u8,
60    pub chroma_format_idc: u8,
61    pub bit_depth_luma: u8,
62    pub bit_depth_chroma: u8,
63    pub max_sub_layers: u8,
64    pub temporal_id_nesting: bool,
65    pub width: u32,
66    pub height: u32,
67}
68
69/// Reads the fields above out of an HEVC SPS NAL (with or without its two-byte
70/// NAL header, emulation prevention still in place).
71pub fn sps_info(nal: &[u8]) -> Option<SpsInfo> {
72    // Strip the NAL header when present (nal_unit_type 33).
73    let payload = if !nal.is_empty() && (nal[0] >> 1) & 0x3f == 33 {
74        nal.get(2..)?
75    } else {
76        nal
77    };
78    let rbsp = unescape(payload);
79    let mut r = Bits::new(&rbsp);
80    r.bits(4)?; // sps_video_parameter_set_id
81    let max_sub_layers_minus1 = r.bits(3)? as u8;
82    let temporal_id_nesting = r.bit()?;
83    // profile_tier_level(1, max_sub_layers_minus1)
84    let profile_space = r.bits(2)? as u8;
85    let tier_flag = r.bit()?;
86    let profile_idc = r.bits(5)? as u8;
87    let profile_compatibility_flags = r.bits(32)?;
88    let mut constraint_flags = [0u8; 6];
89    for c in constraint_flags.iter_mut() {
90        *c = r.bits(8)? as u8;
91    }
92    let level_idc = r.bits(8)? as u8;
93    let mut sub_profile = [false; 8];
94    let mut sub_level = [false; 8];
95    for i in 0..max_sub_layers_minus1 as usize {
96        sub_profile[i] = r.bit()?;
97        sub_level[i] = r.bit()?;
98    }
99    if max_sub_layers_minus1 > 0 {
100        for _ in max_sub_layers_minus1 as usize..8 {
101            r.bits(2)?;
102        }
103    }
104    for i in 0..max_sub_layers_minus1 as usize {
105        if sub_profile[i] {
106            r.bits(32)?;
107            r.bits(32)?;
108            r.bits(24)?;
109        }
110        if sub_level[i] {
111            r.bits(8)?;
112        }
113    }
114    r.ue()?; // sps_seq_parameter_set_id
115    let chroma_format_idc = r.ue()? as u8;
116    if chroma_format_idc == 3 {
117        r.bit()?; // separate_colour_plane_flag
118    }
119    let width = r.ue()?;
120    let height = r.ue()?;
121    let (mut cl, mut cr, mut ct, mut cb) = (0, 0, 0, 0);
122    if r.bit()? {
123        cl = r.ue()?;
124        cr = r.ue()?;
125        ct = r.ue()?;
126        cb = r.ue()?;
127    }
128    let bit_depth_luma = r.ue()? as u8 + 8;
129    let bit_depth_chroma = r.ue()? as u8 + 8;
130    let (sw, sh) = match chroma_format_idc {
131        1 => (2, 2),
132        2 => (2, 1),
133        _ => (1, 1),
134    };
135    Some(SpsInfo {
136        profile_space,
137        tier_flag,
138        profile_idc,
139        profile_compatibility_flags,
140        constraint_flags,
141        level_idc,
142        chroma_format_idc,
143        bit_depth_luma,
144        bit_depth_chroma,
145        max_sub_layers: max_sub_layers_minus1 + 1,
146        temporal_id_nesting,
147        width: width.saturating_sub(sw * (cl + cr)),
148        height: height.saturating_sub(sh * (ct + cb)),
149    })
150}
151
152/// Cropped picture size from an HEVC SPS NAL (the TS demuxer's need).
153pub fn sps_dimensions(nal: &[u8]) -> Option<(u32, u32)> {
154    sps_info(nal).map(|s| (s.width, s.height))
155}
156
157/// Build an `hvcC` record from Annex-B parameter sets (4-byte NAL lengths).
158pub fn build_hvcc_record(vps: &[u8], sps: &[u8], pps: &[u8]) -> Option<Vec<u8>> {
159    let info = sps_info(sps)?;
160    let mut out = Vec::with_capacity(64 + vps.len() + sps.len() + pps.len());
161    out.push(1); // configurationVersion
162    out.push((info.profile_space << 6) | ((info.tier_flag as u8) << 5) | info.profile_idc);
163    out.extend_from_slice(&info.profile_compatibility_flags.to_be_bytes());
164    out.extend_from_slice(&info.constraint_flags);
165    out.push(info.level_idc);
166    out.extend_from_slice(&[0xf0, 0x00]); // reserved + min_spatial_segmentation_idc = 0
167    out.push(0xfc); // reserved + parallelismType = 0
168    out.push(0xfc | info.chroma_format_idc);
169    out.push(0xf8 | (info.bit_depth_luma - 8));
170    out.push(0xf8 | (info.bit_depth_chroma - 8));
171    out.extend_from_slice(&[0, 0]); // avgFrameRate = 0 (unspecified)
172                                    // constantFrameRate=0, numTemporalLayers, temporalIdNested, lengthSizeMinusOne=3
173    out.push((info.max_sub_layers << 3) | ((info.temporal_id_nesting as u8) << 2) | 3);
174    let arrays: [(u8, &[u8]); 3] = [(32, vps), (33, sps), (34, pps)];
175    let present = arrays.iter().filter(|(_, n)| !n.is_empty()).count();
176    out.push(present as u8);
177    for (t, nal) in arrays {
178        if nal.is_empty() {
179            continue;
180        }
181        out.push(0x80 | t); // array_completeness = 1
182        out.extend_from_slice(&1u16.to_be_bytes());
183        out.extend_from_slice(&(nal.len() as u16).to_be_bytes());
184        out.extend_from_slice(nal);
185    }
186    Some(out)
187}
188
189/// Finds the first NAL of `nal_type` in an Annex-B stream (header included).
190pub fn find_nal_annexb(data: &[u8], nal_type: u8) -> Option<&[u8]> {
191    for nal in crate::avc::split_annexb(data) {
192        if !nal.is_empty() && (nal[0] >> 1) & 0x3f == nal_type {
193            return Some(nal);
194        }
195    }
196    None
197}
198
199fn be16(d: &[u8], i: usize) -> Option<u16> {
200    Some(u16::from_be_bytes([*d.get(i)?, *d.get(i + 1)?]))
201}
202
203fn unescape(ebsp: &[u8]) -> Vec<u8> {
204    let mut out = Vec::with_capacity(ebsp.len());
205    let mut zeros = 0;
206    for &b in ebsp {
207        if zeros >= 2 && b == 3 {
208            zeros = 0;
209            continue;
210        }
211        out.push(b);
212        zeros = if b == 0 { zeros + 1 } else { 0 };
213    }
214    out
215}
216
217struct Bits<'a> {
218    d: &'a [u8],
219    p: usize,
220}
221
222impl<'a> Bits<'a> {
223    fn new(d: &'a [u8]) -> Self {
224        Bits { d, p: 0 }
225    }
226    fn bit(&mut self) -> Option<bool> {
227        let b = *self.d.get(self.p / 8)?;
228        let v = (b >> (7 - self.p % 8)) & 1;
229        self.p += 1;
230        Some(v == 1)
231    }
232    fn bits(&mut self, n: u32) -> Option<u32> {
233        let mut v = 0u32;
234        for _ in 0..n {
235            v = (v << 1) | self.bit()? as u32;
236        }
237        Some(v)
238    }
239    fn ue(&mut self) -> Option<u32> {
240        let mut lz = 0;
241        while !self.bit()? {
242            lz += 1;
243            if lz > 31 {
244                return None;
245            }
246        }
247        if lz == 0 {
248            return Some(0);
249        }
250        Some((1u32 << lz) - 1 + self.bits(lz)?)
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257
258    /// The real VPS/SPS/PPS of the JCT-VC `IPRED_A_docomo_2` stream
259    /// (832x480 Main profile, level_idc 153), NAL headers included.
260    const VPS: &[u8] = &[
261        0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00,
262        0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x99, 0xf0, 0x24,
263    ];
264    const SPS: &[u8] = &[
265        0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00,
266        0x00, 0x03, 0x00, 0x99, 0xa0, 0x06, 0x82, 0x01, 0xe1, 0xfe, 0x5f, 0x92, 0x46, 0xd9, 0x6c,
267        0x80,
268    ];
269    const PPS: &[u8] = &[0x44, 0x01, 0xc1, 0x90, 0x95, 0x81, 0x12];
270
271    #[test]
272    fn reads_geometry_and_profile() {
273        let i = sps_info(SPS).expect("sps");
274        assert_eq!((i.width, i.height), (832, 480));
275        assert_eq!(i.level_idc, 153);
276        assert_eq!(i.max_sub_layers, 1);
277        assert_eq!(i.chroma_format_idc, 1);
278        assert_eq!(i.bit_depth_luma, 8);
279        assert_eq!(i.bit_depth_chroma, 8);
280        assert_eq!(i.profile_idc, 1);
281        assert_eq!(sps_dimensions(SPS), Some((832, 480)));
282        // and with the NAL header already stripped by the caller
283        assert_eq!(sps_dimensions(&SPS[2..]), Some((832, 480)));
284    }
285
286    #[test]
287    fn hvcc_roundtrip() {
288        let (vps, pps) = (VPS, PPS);
289        let rec = build_hvcc_record(vps, SPS, pps).expect("build");
290        assert_eq!(rec[0], 1);
291        assert_eq!(rec[21] & 3, 3, "4-byte NAL length");
292        assert_eq!(rec[22], 3, "three arrays");
293        let cfg = parse_hvcc(&rec).expect("parse");
294        assert_eq!(cfg.nal_len, 4);
295        // VPS, SPS and PPS each prefixed with a 4-byte start code.
296        let mut want = Vec::new();
297        for n in [vps, SPS, pps] {
298            want.extend_from_slice(&[0, 0, 0, 1]);
299            want.extend_from_slice(n);
300        }
301        assert_eq!(cfg.headers_annexb, want);
302    }
303
304    #[test]
305    fn rejects_junk() {
306        assert!(parse_hvcc(&[]).is_none());
307        assert!(parse_hvcc(&[0u8; 40]).is_none());
308        assert!(sps_info(&[0x42, 0x01]).is_none());
309    }
310}