Skip to main content

dvb_si/descriptors/
video_stream.rs

1//! Video Stream Descriptor — ISO/IEC 13818-1 §2.6.2 (tag 0x02).
2//!
3//! Describes the elementary stream as a video stream, including frame rate
4//! and profile/level constraints.
5
6use super::descriptor_body;
7use crate::error::{Error, Result};
8use dvb_common::{Parse, Serialize};
9
10/// Descriptor tag for video_stream_descriptor.
11pub const TAG: u8 = 0x02;
12const HEADER_LEN: usize = 2;
13const BODY_LEN: u8 = 3;
14const BODY_MPEG1_LEN: u8 = 1;
15
16/// Frame rate code — ISO/IEC 13818-1 Table 2-47.
17///
18/// 4-bit code in the video_stream_descriptor. The "also includes" column
19/// (`multiple_frame_rate_flag = 1`) is handled by the descriptor-level
20/// `multiple_frame_rate_flag` field.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize))]
23#[non_exhaustive]
24pub enum FrameRateCode {
25    /// 0x0 — Forbidden.
26    Forbidden,
27    /// 0x1 — 23.976 Hz.
28    Frame23_976,
29    /// 0x2 — 24.0 Hz (includes 23.976).
30    Frame24_0,
31    /// 0x3 — 25.0 Hz.
32    Frame25_0,
33    /// 0x4 — 29.97 Hz (includes 23.976).
34    Frame29_97,
35    /// 0x5 — 30.0 Hz (includes 23.976, 24.0, 29.97).
36    Frame30_0,
37    /// 0x6 — 50.0 Hz (includes 25.0).
38    Frame50_0,
39    /// 0x7 — 59.94 Hz (includes 23.976, 29.97).
40    Frame59_94,
41    /// 0x8 — 60.0 Hz (includes 23.976, 24.0, 29.97, 30.0, 59.94).
42    Frame60_0,
43    /// 0x9–0xF — Reserved / unrecognised value, preserved verbatim
44    /// for byte-identical round-trip.
45    Reserved(u8),
46}
47
48impl FrameRateCode {
49    /// Construct from a raw byte; unknown values are preserved as `Reserved`
50    /// for byte-identical round-trip.
51    #[must_use]
52    pub fn from_u8(v: u8) -> Self {
53        match v {
54            0x0 => Self::Forbidden,
55            0x1 => Self::Frame23_976,
56            0x2 => Self::Frame24_0,
57            0x3 => Self::Frame25_0,
58            0x4 => Self::Frame29_97,
59            0x5 => Self::Frame30_0,
60            0x6 => Self::Frame50_0,
61            0x7 => Self::Frame59_94,
62            0x8 => Self::Frame60_0,
63            v => Self::Reserved(v),
64        }
65    }
66
67    /// Return the raw byte value.
68    #[must_use]
69    pub fn to_u8(self) -> u8 {
70        match self {
71            Self::Forbidden => 0x0,
72            Self::Frame23_976 => 0x1,
73            Self::Frame24_0 => 0x2,
74            Self::Frame25_0 => 0x3,
75            Self::Frame29_97 => 0x4,
76            Self::Frame30_0 => 0x5,
77            Self::Frame50_0 => 0x6,
78            Self::Frame59_94 => 0x7,
79            Self::Frame60_0 => 0x8,
80            Self::Reserved(v) => v,
81        }
82    }
83
84    /// Returns a human-readable spec name for this value.
85    #[must_use]
86    pub fn name(self) -> &'static str {
87        match self {
88            Self::Forbidden => "forbidden",
89            Self::Frame23_976 => "23.976",
90            Self::Frame24_0 => "24.0",
91            Self::Frame25_0 => "25.0",
92            Self::Frame29_97 => "29.97",
93            Self::Frame30_0 => "30.0",
94            Self::Frame50_0 => "50.0",
95            Self::Frame59_94 => "59.94",
96            Self::Frame60_0 => "60.0",
97            Self::Reserved(_) => "reserved",
98        }
99    }
100}
101dvb_common::impl_spec_display!(FrameRateCode, Reserved);
102
103/// Video Stream Descriptor.
104///
105/// If `mpeg_1_only_flag` is true, `profile_and_level_indication`,
106/// `chroma_format`, and `frame_rate_extension_flag` are absent (body
107/// is only 1 byte instead of 3).
108#[derive(Debug, Clone, PartialEq, Eq)]
109#[cfg_attr(feature = "serde", derive(serde::Serialize))]
110#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
111pub struct VideoStreamDescriptor {
112    /// 1 — more than one frame rate may be present (see Table 2-47 "also includes").
113    pub multiple_frame_rate_flag: bool,
114    /// Frame rate code (Table 2-47).
115    pub frame_rate_code: FrameRateCode,
116    /// 1 — stream constrained to MPEG-1 (no profile/level/chroma fields follow).
117    pub mpeg_1_only_flag: bool,
118    /// Constrained parameters flag.
119    pub constrained_parameter_flag: bool,
120    /// Still picture flag.
121    pub still_picture_flag: bool,
122    /// Profile and level indication — only when `mpeg_1_only_flag` is false.
123    pub profile_and_level_indication: Option<u8>,
124    /// Chroma format (H.262 §6.3.11) — only when `mpeg_1_only_flag` is false.
125    pub chroma_format: Option<u8>,
126    /// Frame rate extension flag — only when `mpeg_1_only_flag` is false.
127    pub frame_rate_extension_flag: Option<bool>,
128}
129
130impl<'a> Parse<'a> for VideoStreamDescriptor {
131    type Error = crate::error::Error;
132
133    fn parse(bytes: &'a [u8]) -> Result<Self> {
134        let body = descriptor_body(
135            bytes,
136            TAG,
137            "VideoStreamDescriptor",
138            "unexpected tag for video_stream_descriptor",
139        )?;
140        if body.is_empty() {
141            return Err(Error::InvalidDescriptor {
142                tag: TAG,
143                reason: "video_stream_descriptor length must be at least 1",
144            });
145        }
146        let b0 = body[0];
147        let multiple_frame_rate_flag = (b0 & 0x80) != 0;
148        let frame_rate_code = FrameRateCode::from_u8((b0 >> 3) & 0x0F);
149        let mpeg_1_only_flag = (b0 & 0x04) != 0;
150        let constrained_parameter_flag = (b0 & 0x02) != 0;
151        let still_picture_flag = (b0 & 0x01) != 0;
152
153        if mpeg_1_only_flag {
154            Ok(Self {
155                multiple_frame_rate_flag,
156                frame_rate_code,
157                mpeg_1_only_flag,
158                constrained_parameter_flag,
159                still_picture_flag,
160                profile_and_level_indication: None,
161                chroma_format: None,
162                frame_rate_extension_flag: None,
163            })
164        } else {
165            if body.len() < (BODY_LEN as usize) {
166                return Err(Error::InvalidDescriptor {
167                    tag: TAG,
168                    reason: "video_stream_descriptor too short for MPEG-2 fields",
169                });
170            }
171            let b1 = body[1];
172            let b2 = body[2];
173            let profile_and_level_indication = b1;
174            let chroma_format = (b2 >> 6) & 0x03;
175            let frame_rate_extension_flag = (b2 & 0x20) != 0;
176            Ok(Self {
177                multiple_frame_rate_flag,
178                frame_rate_code,
179                mpeg_1_only_flag,
180                constrained_parameter_flag,
181                still_picture_flag,
182                profile_and_level_indication: Some(profile_and_level_indication),
183                chroma_format: Some(chroma_format),
184                frame_rate_extension_flag: Some(frame_rate_extension_flag),
185            })
186        }
187    }
188}
189
190impl Serialize for VideoStreamDescriptor {
191    type Error = crate::error::Error;
192
193    fn serialized_len(&self) -> usize {
194        if self.mpeg_1_only_flag {
195            HEADER_LEN + (BODY_MPEG1_LEN as usize)
196        } else {
197            HEADER_LEN + (BODY_LEN as usize)
198        }
199    }
200
201    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
202        let len = self.serialized_len();
203        if buf.len() < len {
204            return Err(Error::OutputBufferTooSmall {
205                need: len,
206                have: buf.len(),
207            });
208        }
209        buf[0] = TAG;
210        buf[1] = (len - HEADER_LEN) as u8;
211        let b0 = ((self.multiple_frame_rate_flag as u8) << 7)
212            | (self.frame_rate_code.to_u8() << 3)
213            | ((self.mpeg_1_only_flag as u8) << 2)
214            | ((self.constrained_parameter_flag as u8) << 1)
215            | (self.still_picture_flag as u8);
216        buf[HEADER_LEN] = b0;
217        if !self.mpeg_1_only_flag {
218            buf[HEADER_LEN + 1] = self.profile_and_level_indication.unwrap_or(0);
219            let chroma = self.chroma_format.unwrap_or(0) & 0x03;
220            let fre = self.frame_rate_extension_flag.unwrap_or(false) as u8;
221            buf[HEADER_LEN + 2] = (chroma << 6) | (fre << 5);
222        }
223        Ok(len)
224    }
225}
226impl<'a> crate::traits::DescriptorDef<'a> for VideoStreamDescriptor {
227    const TAG: u8 = TAG;
228    const NAME: &'static str = "VIDEO_STREAM";
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    #[test]
236    fn parse_mpeg_2() {
237        let bytes = [
238            TAG,
239            3,           // tag + length
240            0b1010_0011, // multiple=1, frame_rate=4 (29.97), mpeg1=0, constrained=1, still=1
241            0xDE,        // profile_and_level_indication
242            0b1110_0000, // chroma_format=3, frame_rate_extension_flag=1, reserved=0
243        ];
244        let d = VideoStreamDescriptor::parse(&bytes).unwrap();
245        assert!(d.multiple_frame_rate_flag);
246        assert_eq!(d.frame_rate_code, FrameRateCode::Frame29_97);
247        assert!(!d.mpeg_1_only_flag);
248        assert!(d.constrained_parameter_flag);
249        assert!(d.still_picture_flag);
250        assert_eq!(d.profile_and_level_indication, Some(0xDE));
251        assert_eq!(d.chroma_format, Some(3));
252        assert_eq!(d.frame_rate_extension_flag, Some(true));
253    }
254
255    #[test]
256    fn parse_mpeg_1() {
257        let bytes = [
258            TAG,
259            1,
260            0b0001_1101, // multiple=0, frame_rate=3 (25.0), mpeg1=1, constrained=0, still=1
261        ];
262        let d = VideoStreamDescriptor::parse(&bytes).unwrap();
263        assert!(!d.multiple_frame_rate_flag);
264        assert_eq!(d.frame_rate_code, FrameRateCode::Frame25_0);
265        assert!(d.mpeg_1_only_flag);
266        assert!(!d.constrained_parameter_flag);
267        assert!(d.still_picture_flag);
268        assert!(d.profile_and_level_indication.is_none());
269        assert!(d.chroma_format.is_none());
270        assert!(d.frame_rate_extension_flag.is_none());
271    }
272
273    #[test]
274    fn serialize_round_trip_mpeg_2() {
275        let d = VideoStreamDescriptor {
276            multiple_frame_rate_flag: true,
277            frame_rate_code: FrameRateCode::Frame60_0,
278            mpeg_1_only_flag: false,
279            constrained_parameter_flag: false,
280            still_picture_flag: true,
281            profile_and_level_indication: Some(0xAB),
282            chroma_format: Some(1),
283            frame_rate_extension_flag: Some(false),
284        };
285        let mut buf = vec![0u8; d.serialized_len()];
286        d.serialize_into(&mut buf).unwrap();
287        let reparsed = VideoStreamDescriptor::parse(&buf).unwrap();
288        assert_eq!(d, reparsed);
289    }
290
291    #[test]
292    fn serialize_round_trip_mpeg_1() {
293        let d = VideoStreamDescriptor {
294            multiple_frame_rate_flag: false,
295            frame_rate_code: FrameRateCode::Frame23_976,
296            mpeg_1_only_flag: true,
297            constrained_parameter_flag: true,
298            still_picture_flag: false,
299            profile_and_level_indication: None,
300            chroma_format: None,
301            frame_rate_extension_flag: None,
302        };
303        let mut buf = vec![0u8; d.serialized_len()];
304        d.serialize_into(&mut buf).unwrap();
305        let reparsed = VideoStreamDescriptor::parse(&buf).unwrap();
306        assert_eq!(d, reparsed);
307    }
308
309    #[test]
310    fn parse_rejects_wrong_tag() {
311        let err = VideoStreamDescriptor::parse(&[0x03, 1, 0x00]).unwrap_err();
312        assert!(matches!(err, Error::InvalidDescriptor { tag: 0x03, .. }));
313    }
314
315    #[test]
316    fn parse_rejects_empty_body() {
317        let err = VideoStreamDescriptor::parse(&[TAG, 0]).unwrap_err();
318        assert!(matches!(err, Error::InvalidDescriptor { tag: TAG, .. }));
319    }
320
321    #[test]
322    fn frame_rate_code_round_trip() {
323        for v in 0u8..=0x0F {
324            assert_eq!(
325                FrameRateCode::from_u8(v).to_u8(),
326                v,
327                "round-trip failed for {v:#04x}"
328            );
329        }
330    }
331
332    #[test]
333    fn frame_rate_code_name() {
334        assert_eq!(FrameRateCode::Frame23_976.name(), "23.976");
335        assert_eq!(FrameRateCode::Frame25_0.name(), "25.0");
336        assert_eq!(FrameRateCode::Reserved(0xA).name(), "reserved");
337        assert_eq!(FrameRateCode::Forbidden.name(), "forbidden");
338    }
339
340    #[test]
341    fn serialize_rejects_small_buffer() {
342        let d = VideoStreamDescriptor {
343            multiple_frame_rate_flag: false,
344            frame_rate_code: FrameRateCode::Frame25_0,
345            mpeg_1_only_flag: true,
346            constrained_parameter_flag: false,
347            still_picture_flag: false,
348            profile_and_level_indication: None,
349            chroma_format: None,
350            frame_rate_extension_flag: None,
351        };
352        let mut tiny = vec![0u8; 2];
353        let err = d.serialize_into(&mut tiny).unwrap_err();
354        assert!(matches!(err, Error::OutputBufferTooSmall { .. }));
355    }
356}