Skip to main content

dvb_si/descriptors/
hevc_video.rs

1//! HEVC Video Descriptor — ISO/IEC 13818-1 §2.6.95 (tag 0x38).
2//!
3//! Describes the profile, tier, level, and constraint flags for an
4//! HEVC/H.265 video elementary stream. The `copied_44bits` field is a
5//! 44-bit non-byte-aligned region stored as a u64 masked to 44 bits.
6//! The temporal_id block is conditional on `temporal_layer_subset_flag`.
7
8use super::descriptor_body;
9use super::hdr_wcg_idc::HdrWcgIdc;
10use crate::error::{Error, Result};
11use dvb_common::{Parse, Serialize};
12
13/// Descriptor tag for HEVC_video_descriptor.
14pub const TAG: u8 = 0x38;
15const HEADER_LEN: usize = 2;
16const COPIED_44_MASK: u64 = (1 << 44) - 1;
17/// Fixed body length without temporal_id sub-block.
18const FIXED_BODY_LEN: u8 = 12;
19/// Temporal sub-block length when present.
20const TEMPORAL_SUB_LEN: u8 = 2;
21
22/// Temporal layer subset sub-block — present when `temporal_layer_subset_flag` is true.
23#[derive(Debug, Clone, PartialEq, Eq)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize))]
25pub struct HevcTemporalSub {
26    /// Minimum temporal_id value.
27    pub temporal_id_min: u8,
28    /// Maximum temporal_id value.
29    pub temporal_id_max: u8,
30}
31
32/// HEVC Video Descriptor.
33#[derive(Debug, Clone, PartialEq, Eq)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize))]
35#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
36pub struct HevcVideoDescriptor {
37    /// Profile space (2 bits).
38    pub profile_space: u8,
39    /// Tier flag.
40    pub tier_flag: bool,
41    /// Profile IDC (5 bits).
42    pub profile_idc: u8,
43    /// Profile compatibility indication (32 bits).
44    pub profile_compatibility_indication: u32,
45    /// Progressive source flag.
46    pub progressive_source_flag: bool,
47    /// Interlaced source flag.
48    pub interlaced_source_flag: bool,
49    /// Non-packed constraint flag.
50    pub non_packed_constraint_flag: bool,
51    /// Frame only constraint flag.
52    pub frame_only_constraint_flag: bool,
53    /// Copied 44 bits — stored as u64, masked to 44 bits.
54    pub copied_44bits: u64,
55    /// Level IDC.
56    pub level_idc: u8,
57    /// Temporal layer subset flag.
58    pub temporal_layer_subset_flag: bool,
59    /// HEVC still present flag.
60    pub hevc_still_present_flag: bool,
61    /// HEVC 24hr picture present flag.
62    pub hevc_24hr_picture_present_flag: bool,
63    /// Sub-pic HRD params not present flag.
64    pub sub_pic_hrd_params_not_present_flag: bool,
65    /// HDR/WCG indication (Table 2-114).
66    pub hdr_wcg_idc: HdrWcgIdc,
67    /// Temporal layer sub-block, when present.
68    pub temporal_sub: Option<HevcTemporalSub>,
69}
70
71impl<'a> Parse<'a> for HevcVideoDescriptor {
72    type Error = crate::error::Error;
73
74    fn parse(bytes: &'a [u8]) -> Result<Self> {
75        let body = descriptor_body(
76            bytes,
77            TAG,
78            "HevcVideoDescriptor",
79            "unexpected tag for HEVC_video_descriptor",
80        )?;
81        if body.len() < (FIXED_BODY_LEN as usize) {
82            return Err(Error::InvalidDescriptor {
83                tag: TAG,
84                reason: "HEVC_video_descriptor too short",
85            });
86        }
87
88        let b0 = body[0]; // profile_space(2)|tier_flag(1)|profile_idc(5)
89        let profile_space = b0 >> 6;
90        let tier_flag = (b0 & 0x20) != 0;
91        let profile_idc = b0 & 0x1F;
92
93        // profile_compatibility_indication: bytes 1..5 (32 bits big-endian)
94        let profile_compatibility_indication =
95            u32::from_be_bytes([body[1], body[2], body[3], body[4]]);
96
97        let b5 = body[5]; // progressive_source_flag..frame_only_constraint_flag | copied_44bits hi 4 bits
98        let progressive_source_flag = (b5 & 0x80) != 0;
99        let interlaced_source_flag = (b5 & 0x40) != 0;
100        let non_packed_constraint_flag = (b5 & 0x20) != 0;
101        let frame_only_constraint_flag = (b5 & 0x10) != 0;
102
103        // copied_44bits: 4 bits from b5 (low nibble) + 5 full bytes (body[6..11]) = 44 bits
104        let copied_44bits_hi: u64 = (b5 as u64 & 0x0F) << 40;
105        let copied_44bits_lo: u64 = ((body[6] as u64) << 32)
106            | ((body[7] as u64) << 24)
107            | ((body[8] as u64) << 16)
108            | ((body[9] as u64) << 8)
109            | (body[10] as u64);
110        let copied_44bits = (copied_44bits_hi | copied_44bits_lo) & COPIED_44_MASK;
111
112        let b11 = body[11]; // level_idc
113
114        let temporal_layer_subset_flag;
115        let hevc_still_present_flag;
116        let hevc_24hr_picture_present_flag;
117        let sub_pic_hrd_params_not_present_flag;
118        let hdr_wcg_idc;
119        let temporal_sub;
120
121        if body.len() > (FIXED_BODY_LEN as usize) {
122            // byte 12: tls(7)|still(6)|24hr(5)|sub_pic(4)|reserved(3:2)|HDR_WCG_idc(1:0)
123            let b12 = body[12];
124            temporal_layer_subset_flag = (b12 & 0x80) != 0;
125            hevc_still_present_flag = (b12 & 0x40) != 0;
126            hevc_24hr_picture_present_flag = (b12 & 0x20) != 0;
127            sub_pic_hrd_params_not_present_flag = (b12 & 0x10) != 0;
128            hdr_wcg_idc = HdrWcgIdc::from_u8(b12 & 0x03);
129
130            if temporal_layer_subset_flag {
131                if body.len() < (FIXED_BODY_LEN + TEMPORAL_SUB_LEN) as usize {
132                    return Err(Error::InvalidDescriptor {
133                        tag: TAG,
134                        reason: "HEVC_video_descriptor too short for temporal sub-block",
135                    });
136                }
137                let b13 = body[13];
138                let b14 = body[14];
139                temporal_sub = Some(HevcTemporalSub {
140                    temporal_id_min: b13 >> 5,
141                    temporal_id_max: b14 >> 5,
142                });
143            } else {
144                temporal_sub = None;
145            }
146        } else {
147            // No extra bytes beyond fixed — default flags
148            temporal_layer_subset_flag = false;
149            hevc_still_present_flag = false;
150            hevc_24hr_picture_present_flag = false;
151            sub_pic_hrd_params_not_present_flag = false;
152            hdr_wcg_idc = HdrWcgIdc::NoIndication;
153            temporal_sub = None;
154        }
155
156        Ok(Self {
157            profile_space,
158            tier_flag,
159            profile_idc,
160            profile_compatibility_indication,
161            progressive_source_flag,
162            interlaced_source_flag,
163            non_packed_constraint_flag,
164            frame_only_constraint_flag,
165            copied_44bits,
166            level_idc: b11,
167            temporal_layer_subset_flag,
168            hevc_still_present_flag,
169            hevc_24hr_picture_present_flag,
170            sub_pic_hrd_params_not_present_flag,
171            hdr_wcg_idc,
172            temporal_sub,
173        })
174    }
175}
176
177impl Serialize for HevcVideoDescriptor {
178    type Error = crate::error::Error;
179
180    fn serialized_len(&self) -> usize {
181        let extra = if self.temporal_layer_subset_flag {
182            TEMPORAL_SUB_LEN
183        } else {
184            0
185        };
186        HEADER_LEN + (FIXED_BODY_LEN as usize) + 1 + (extra as usize)
187    }
188
189    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
190        let len = self.serialized_len();
191        if buf.len() < len {
192            return Err(Error::OutputBufferTooSmall {
193                need: len,
194                have: buf.len(),
195            });
196        }
197        let body_len = (len - HEADER_LEN) as u8;
198        buf[0] = TAG;
199        buf[1] = body_len;
200
201        // byte 0: profile_space(2)|tier_flag(1)|profile_idc(5)
202        buf[HEADER_LEN] =
203            (self.profile_space << 6) | ((self.tier_flag as u8) << 5) | (self.profile_idc & 0x1F);
204
205        // bytes 1..5: profile_compatibility_indication (32 bits big-endian)
206        buf[HEADER_LEN + 1..HEADER_LEN + 5]
207            .copy_from_slice(&self.profile_compatibility_indication.to_be_bytes());
208
209        // byte 5: flags(4) | copied_44bits hi 4 bits
210        let copied = self.copied_44bits & COPIED_44_MASK;
211        buf[HEADER_LEN + 5] = ((self.progressive_source_flag as u8) << 7)
212            | ((self.interlaced_source_flag as u8) << 6)
213            | ((self.non_packed_constraint_flag as u8) << 5)
214            | ((self.frame_only_constraint_flag as u8) << 4)
215            | (((copied >> 40) & 0x0F) as u8);
216
217        // bytes 6..11: copied_44bits lo 40 bits
218        buf[HEADER_LEN + 6] = ((copied >> 32) & 0xFF) as u8;
219        buf[HEADER_LEN + 7] = ((copied >> 24) & 0xFF) as u8;
220        buf[HEADER_LEN + 8] = ((copied >> 16) & 0xFF) as u8;
221        buf[HEADER_LEN + 9] = ((copied >> 8) & 0xFF) as u8;
222        buf[HEADER_LEN + 10] = (copied & 0xFF) as u8;
223
224        // byte 11: level_idc
225        buf[HEADER_LEN + 11] = self.level_idc;
226
227        // byte 12: tls(7)|still(6)|24hr(5)|sub_pic(4)|reserved(3:2)|HDR_WCG_idc(1:0)
228        buf[HEADER_LEN + 12] = ((self.temporal_layer_subset_flag as u8) << 7)
229            | ((self.hevc_still_present_flag as u8) << 6)
230            | ((self.hevc_24hr_picture_present_flag as u8) << 5)
231            | ((self.sub_pic_hrd_params_not_present_flag as u8) << 4)
232            | (self.hdr_wcg_idc.to_u8() & 0x03);
233
234        if self.temporal_layer_subset_flag {
235            if let Some(ref ts) = self.temporal_sub {
236                buf[HEADER_LEN + 13] = ts.temporal_id_min << 5;
237                buf[HEADER_LEN + 14] = ts.temporal_id_max << 5;
238            }
239        }
240
241        Ok(len)
242    }
243}
244
245impl<'a> crate::traits::DescriptorDef<'a> for HevcVideoDescriptor {
246    const TAG: u8 = TAG;
247    const NAME: &'static str = "HEVC_VIDEO";
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253
254    #[test]
255    fn round_trip_no_temporal() {
256        let orig = HevcVideoDescriptor {
257            profile_space: 1,
258            tier_flag: true,
259            profile_idc: 2,
260            profile_compatibility_indication: 0xDEADBEEF,
261            progressive_source_flag: true,
262            interlaced_source_flag: false,
263            non_packed_constraint_flag: true,
264            frame_only_constraint_flag: false,
265            copied_44bits: 0x123456789AB & COPIED_44_MASK,
266            level_idc: 0x99,
267            temporal_layer_subset_flag: false,
268            hevc_still_present_flag: true,
269            hevc_24hr_picture_present_flag: false,
270            sub_pic_hrd_params_not_present_flag: true,
271            hdr_wcg_idc: HdrWcgIdc::HdrAndWcg,
272            temporal_sub: None,
273        };
274        let mut buf = vec![0u8; orig.serialized_len()];
275        orig.serialize_into(&mut buf).unwrap();
276        let reparsed = HevcVideoDescriptor::parse(&buf).unwrap();
277        assert_eq!(orig, reparsed);
278    }
279
280    #[test]
281    fn hdr_wcg_idc_is_low_two_bits_of_byte12() {
282        // byte12 = tls(7)|still(6)|24hr(5)|sub_pic(4)|reserved(3:2)|HDR_WCG_idc(1:0).
283        // Set the reserved bits [3:2] = '11' and HDR_WCG_idc [1:0] = '10' (=2, HdrAndWcg).
284        // A parser that read the wrong bits ([3:2]) would decode 3 (NoIndication).
285        let body12 = 0b0000_1110u8; // reserved=0b11, hdr_wcg_idc=0b10
286        let buf = [
287            TAG, 13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, body12,
288        ];
289        let d = HevcVideoDescriptor::parse(&buf).unwrap();
290        assert_eq!(d.hdr_wcg_idc, HdrWcgIdc::HdrAndWcg);
291        // Reserved bits must not leak into any field and must serialize back as zero.
292        let mut out = vec![0u8; d.serialized_len()];
293        d.serialize_into(&mut out).unwrap();
294        // body byte 12 sits at buffer index HEADER_LEN + 12 = 14.
295        assert_eq!(out[14] & 0x03, 0b10, "HDR_WCG_idc must occupy bits [1:0]");
296        assert_eq!(
297            out[14] & 0x0C,
298            0,
299            "reserved bits [3:2] must serialize as zero"
300        );
301    }
302
303    #[test]
304    fn round_trip_with_temporal() {
305        let orig = HevcVideoDescriptor {
306            profile_space: 3,
307            tier_flag: false,
308            profile_idc: 0x0A,
309            profile_compatibility_indication: 0xCAFEBABE,
310            progressive_source_flag: false,
311            interlaced_source_flag: true,
312            non_packed_constraint_flag: false,
313            frame_only_constraint_flag: true,
314            copied_44bits: 0xABCDEF01234 & COPIED_44_MASK,
315            level_idc: 0x5A,
316            temporal_layer_subset_flag: true,
317            hevc_still_present_flag: false,
318            hevc_24hr_picture_present_flag: true,
319            sub_pic_hrd_params_not_present_flag: false,
320            hdr_wcg_idc: HdrWcgIdc::Sdr,
321            temporal_sub: Some(HevcTemporalSub {
322                temporal_id_min: 5,
323                temporal_id_max: 7,
324            }),
325        };
326        let mut buf = vec![0u8; orig.serialized_len()];
327        orig.serialize_into(&mut buf).unwrap();
328        let reparsed = HevcVideoDescriptor::parse(&buf).unwrap();
329        assert_eq!(orig, reparsed);
330    }
331
332    #[test]
333    fn round_trip_nonzero_copied_44bits() {
334        // deliberately set all 44 bits
335        let orig = HevcVideoDescriptor {
336            profile_space: 0,
337            tier_flag: false,
338            profile_idc: 1,
339            profile_compatibility_indication: 0,
340            progressive_source_flag: false,
341            interlaced_source_flag: false,
342            non_packed_constraint_flag: false,
343            frame_only_constraint_flag: false,
344            copied_44bits: COPIED_44_MASK, // all 44 bits set
345            level_idc: 0x3C,
346            temporal_layer_subset_flag: false,
347            hevc_still_present_flag: false,
348            hevc_24hr_picture_present_flag: false,
349            sub_pic_hrd_params_not_present_flag: false,
350            hdr_wcg_idc: HdrWcgIdc::NoIndication,
351            temporal_sub: None,
352        };
353        let mut buf = vec![0u8; orig.serialized_len()];
354        orig.serialize_into(&mut buf).unwrap();
355        let reparsed = HevcVideoDescriptor::parse(&buf).unwrap();
356        assert_eq!(orig, reparsed);
357        assert_eq!(reparsed.copied_44bits, COPIED_44_MASK);
358    }
359
360    #[test]
361    fn parse_rejects_wrong_tag() {
362        let buf = [
363            0x02, 13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
364        ];
365        let err = HevcVideoDescriptor::parse(&buf).unwrap_err();
366        assert!(matches!(err, Error::InvalidDescriptor { tag: 0x02, .. }));
367    }
368
369    #[test]
370    fn parse_rejects_too_short() {
371        // present-but-short body (2 bytes < FIXED_BODY_LEN) → descriptor's own check.
372        let err = HevcVideoDescriptor::parse(&[TAG, 2, 0x00, 0x00]).unwrap_err();
373        assert!(matches!(err, Error::InvalidDescriptor { tag: TAG, .. }));
374    }
375}