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