gstreamer_video/
video_vbi_parser.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use crate::{ffi, VideoFormat};
4use glib::translate::*;
5
6use std::fmt;
7
8use crate::video_vbi::line_buffer_len;
9use crate::{VideoAncillaryDID, VideoAncillaryDID16, VideoVBIError};
10
11glib::wrapper! {
12    /// Video Ancillary data, according to SMPTE-291M specification.
13    ///
14    /// Note that the contents of the data are always stored as 8bit data (i.e. do not contain
15    /// the parity check bits).
16    #[doc(alias = "GstVideoAncillary")]
17    pub struct VideoAncillary(BoxedInline<ffi::GstVideoAncillary>);
18}
19
20impl VideoAncillary {
21    pub fn did_u8(&self) -> u8 {
22        self.inner.DID
23    }
24
25    pub fn did(&self) -> VideoAncillaryDID {
26        unsafe { VideoAncillaryDID::from_glib(self.inner.DID as ffi::GstVideoAncillaryDID) }
27    }
28
29    pub fn sdid_block_number(&self) -> u8 {
30        self.inner.SDID_block_number
31    }
32
33    pub fn did16(&self) -> VideoAncillaryDID16 {
34        unsafe {
35            VideoAncillaryDID16::from_glib(
36                (((self.inner.DID as u16) << 8) + self.inner.SDID_block_number as u16)
37                    as ffi::GstVideoAncillaryDID16,
38            )
39        }
40    }
41
42    pub fn len(&self) -> usize {
43        self.inner.data_count as usize
44    }
45
46    pub fn is_empty(&self) -> bool {
47        self.inner.data_count == 0
48    }
49
50    pub fn data(&self) -> &[u8] {
51        &self.inner.data[0..(self.inner.data_count as usize)]
52    }
53}
54
55impl fmt::Debug for VideoAncillary {
56    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
57        f.debug_struct("VideoAncillary")
58            .field("did", &self.did())
59            .field("sdid_block_number", &self.sdid_block_number())
60            .field("did16", &self.did16())
61            .field("data_count", &self.inner.data_count)
62            .finish()
63    }
64}
65
66glib::wrapper! {
67    #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
68    struct VideoVBIParserInner(Boxed<ffi::GstVideoVBIParser>);
69
70    match fn {
71        copy => |ptr| ffi::gst_video_vbi_parser_copy(ptr),
72        free => |ptr| ffi::gst_video_vbi_parser_free(ptr),
73        type_ => || ffi::gst_video_vbi_parser_get_type(),
74    }
75}
76
77/// A parser for detecting and extracting [`VideoAncillary`][crate::VideoAncillary] data from
78/// Vertical Blanking Interval lines of component signals.
79#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
80pub struct VideoVBIParser {
81    inner: VideoVBIParserInner,
82    line_buffer_len: usize,
83}
84
85impl VideoVBIParser {
86    #[doc(alias = "gst_video_vbi_parser_new")]
87    pub fn try_new(format: VideoFormat, pixel_width: u32) -> Result<VideoVBIParser, VideoVBIError> {
88        skip_assert_initialized!();
89        let res: Option<VideoVBIParserInner> = unsafe {
90            from_glib_full(ffi::gst_video_vbi_parser_new(
91                format.into_glib(),
92                pixel_width,
93            ))
94        };
95
96        Ok(VideoVBIParser {
97            inner: res.ok_or(VideoVBIError::Unsupported)?,
98            line_buffer_len: line_buffer_len(format, pixel_width),
99        })
100    }
101
102    // rustdoc-stripper-ignore-next
103    /// Returns the buffer length needed to store the line.
104    pub fn line_buffer_len(&self) -> usize {
105        self.line_buffer_len
106    }
107
108    /// Provide a new line of data to the `self`. Call [`ancillary()`][Self::ancillary()]
109    /// to get the Ancillary data that might be present on that line.
110    /// ## `data`
111    /// The line of data to parse
112    #[doc(alias = "gst_video_vbi_parser_add_line")]
113    pub fn add_line(&mut self, data: &[u8]) -> Result<(), VideoVBIError> {
114        if data.len() < self.line_buffer_len {
115            return Err(VideoVBIError::InsufficientLineBufLen {
116                found: data.len(),
117                expected: self.line_buffer_len,
118            });
119        }
120        unsafe {
121            let data = data.as_ptr();
122            ffi::gst_video_vbi_parser_add_line(self.inner.to_glib_none_mut().0, data);
123        }
124
125        Ok(())
126    }
127
128    pub fn iter(&mut self) -> AncillaryIter {
129        AncillaryIter { parser: self }
130    }
131
132    #[doc(alias = "gst_video_vbi_parser_get_ancillary")]
133    pub fn next_ancillary(&mut self) -> Option<Result<VideoAncillary, VideoVBIError>> {
134        unsafe {
135            let mut video_anc = std::mem::MaybeUninit::uninit();
136            let res = ffi::gst_video_vbi_parser_get_ancillary(
137                self.inner.to_glib_none_mut().0,
138                video_anc.as_mut_ptr(),
139            );
140
141            match res {
142                ffi::GST_VIDEO_VBI_PARSER_RESULT_OK => Some(Ok(VideoAncillary {
143                    inner: video_anc.assume_init(),
144                })),
145                ffi::GST_VIDEO_VBI_PARSER_RESULT_DONE => None,
146                ffi::GST_VIDEO_VBI_PARSER_RESULT_ERROR => Some(Err(VideoVBIError::NotEnoughData)),
147                _ => unreachable!(),
148            }
149        }
150    }
151}
152
153unsafe impl Send for VideoVBIParser {}
154unsafe impl Sync for VideoVBIParser {}
155
156impl<'a> TryFrom<&'a crate::VideoInfo> for VideoVBIParser {
157    type Error = VideoVBIError;
158
159    fn try_from(info: &'a crate::VideoInfo) -> Result<VideoVBIParser, VideoVBIError> {
160        skip_assert_initialized!();
161        VideoVBIParser::try_new(info.format(), info.width())
162    }
163}
164
165#[must_use = "iterators are lazy and do nothing unless consumed"]
166#[derive(Debug)]
167pub struct AncillaryIter<'a> {
168    parser: &'a mut VideoVBIParser,
169}
170
171impl Iterator for AncillaryIter<'_> {
172    type Item = Result<VideoAncillary, VideoVBIError>;
173
174    fn next(&mut self) -> Option<Self::Item> {
175        self.parser.next_ancillary()
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182    use crate::VBI_HD_MIN_PIXEL_WIDTH;
183
184    fn init_line_buf(parser: &VideoVBIParser, anc_buf: &[u8]) -> Vec<u8> {
185        skip_assert_initialized!();
186        let mut line_buf = vec![0; parser.line_buffer_len()];
187        line_buf[0..anc_buf.len()].copy_from_slice(anc_buf);
188        line_buf
189    }
190
191    #[test]
192    fn cea608_component() {
193        let mut parser =
194            VideoVBIParser::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
195        let line_buf = init_line_buf(
196            &parser,
197            &[
198                0x00, 0x00, 0x00, 0x00, 0xff, 0x03, 0xf0, 0x3f, 0x00, 0x84, 0x05, 0x00, 0x02, 0x01,
199                0x30, 0x20, 0x00, 0x00, 0x06, 0x00, 0x94, 0x01, 0xc0, 0x12, 0x00, 0x98, 0x0a, 0x00,
200                0x00, 0x00, 0x00, 0x00,
201            ],
202        );
203        parser.add_line(&line_buf).unwrap();
204
205        let video_anc = parser.next_ancillary().unwrap().unwrap();
206        assert_eq!(video_anc.did16(), VideoAncillaryDID16::S334Eia608);
207        assert_eq!(video_anc.data(), [0x80, 0x94, 0x2c]);
208
209        assert!(parser.next_ancillary().is_none());
210    }
211
212    #[test]
213    fn cea608_composite() {
214        let mut parser =
215            VideoVBIParser::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
216        let line_buf = init_line_buf(
217            &parser,
218            &[
219                0x00, 0xf0, 0x0f, 0x00, 0x61, 0x01, 0x20, 0x10, 0x00, 0x0c, 0x08, 0x00, 0x15, 0x01,
220                0x40, 0x19, 0x00, 0xb0, 0x04, 0x00, 0x3b, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
221                0x00, 0x00, 0x00, 0x00,
222            ],
223        );
224        parser.add_line(&line_buf).unwrap();
225
226        let video_anc = parser.next_ancillary().unwrap().unwrap();
227        assert_eq!(video_anc.did16(), VideoAncillaryDID16::S334Eia608);
228        assert_eq!(video_anc.data(), [0x15, 0x94, 0x2c]);
229
230        assert!(parser.next_ancillary().is_none());
231    }
232
233    #[test]
234    fn cea608_can_not_parse() {
235        let mut parser =
236            VideoVBIParser::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
237        let line_buf = init_line_buf(&parser, &[0x00, 0xf0, 0x0f, 0x00, 0x61, 0x01, 0x20, 0x10]);
238        parser.add_line(&line_buf).unwrap();
239
240        assert!(parser.next_ancillary().is_none());
241    }
242
243    #[test]
244    fn cea608_insufficient_line_buf_len() {
245        let mut parser =
246            VideoVBIParser::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
247        let line_buf = vec![0; 10];
248
249        assert_eq!(
250            parser.add_line(&line_buf).unwrap_err(),
251            VideoVBIError::InsufficientLineBufLen {
252                found: 10,
253                expected: parser.line_buffer_len()
254            },
255        );
256    }
257
258    #[test]
259    fn cea708_component() {
260        let mut parser =
261            VideoVBIParser::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
262        let line_buf = init_line_buf(
263            &parser,
264            &[
265                0x00, 0x00, 0x00, 0x00, 0xff, 0x03, 0xf0, 0x3f, 0x00, 0x84, 0x05, 0x00, 0x01, 0x01,
266                0x50, 0x25, 0x00, 0x58, 0x0a, 0x00, 0x69, 0x02, 0x50, 0x25, 0x00, 0xfc, 0x08, 0x00,
267                0x43, 0x01, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0x72, 0x02, 0x80, 0x1f, 0x00, 0xf0,
268                0x0b, 0x00, 0x94, 0x01, 0xc0, 0x12, 0x00, 0xe4, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20,
269                0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02,
270                0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00,
271                0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8,
272                0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20,
273                0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02,
274                0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00,
275                0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8,
276                0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20,
277                0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02,
278                0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00,
279                0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8,
280                0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20,
281                0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xd0, 0x09, 0x00, 0x00, 0x02,
282                0x00, 0x20, 0x00, 0x6c, 0x08, 0x00, 0xb7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
283                0x00, 0x00, 0x00, 0x00,
284            ],
285        );
286        parser.add_line(&line_buf).unwrap();
287
288        let video_anc = parser.next_ancillary().unwrap().unwrap();
289        assert_eq!(video_anc.did16(), VideoAncillaryDID16::S334Eia708);
290        assert_eq!(
291            video_anc.data(),
292            [
293                0x96, 0x69, 0x55, 0x3f, 0x43, 0x00, 0x00, 0x72, 0xf8, 0xfc, 0x94, 0x2c, 0xf9, 0x00,
294                0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
295                0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
296                0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00,
297                0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
298                0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x74, 0x00, 0x00,
299                0x1b,
300            ]
301        );
302
303        assert!(parser.next_ancillary().is_none());
304    }
305
306    #[test]
307    fn cea608_and_cea708_component() {
308        let mut parser =
309            VideoVBIParser::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
310        let mut line_buf = vec![0; parser.line_buffer_len()];
311        let anc_buf = [
312            0x00, 0x00, 0x00, 0x00, 0xff, 0x03, 0xf0, 0x3f, 0x00, 0x84, 0x05, 0x00, 0x02, 0x01,
313            0x30, 0x20, 0x00, 0x00, 0x06, 0x00, 0x94, 0x01, 0xc0, 0x12, 0x00, 0x98, 0x0a, 0x00,
314            0x00, 0x00, 0xf0, 0x3f, 0x00, 0xfc, 0x0f, 0x00, 0x61, 0x01, 0x10, 0x10, 0x00, 0x54,
315            0x09, 0x00, 0x96, 0x02, 0x90, 0x26, 0x00, 0x54, 0x09, 0x00, 0x3f, 0x02, 0x30, 0x14,
316            0x00, 0x00, 0x08, 0x00, 0x00, 0x02, 0x20, 0x27, 0x00, 0xe0, 0x07, 0x00, 0xfc, 0x02,
317            0x40, 0x19, 0x00, 0xb0, 0x04, 0x00, 0xf9, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00,
318            0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00,
319            0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20,
320            0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02,
321            0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00,
322            0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00,
323            0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20,
324            0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02,
325            0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00,
326            0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00,
327            0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20,
328            0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02,
329            0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00,
330            0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0x74, 0x02, 0x00, 0x20, 0x00, 0x00,
331            0x08, 0x00, 0x1b, 0x02, 0x70, 0x2b,
332        ];
333        line_buf[0..anc_buf.len()].copy_from_slice(&anc_buf);
334        parser.add_line(&line_buf).unwrap();
335
336        let mut anc_iter = parser.iter();
337
338        let video_anc = anc_iter.next().unwrap().unwrap();
339        assert_eq!(video_anc.did16(), VideoAncillaryDID16::S334Eia608);
340        assert_eq!(video_anc.data(), [0x80, 0x94, 0x2c]);
341
342        let video_anc = anc_iter.next().unwrap().unwrap();
343        assert_eq!(video_anc.did16(), VideoAncillaryDID16::S334Eia708);
344        assert_eq!(
345            video_anc.data(),
346            [
347                0x96, 0x69, 0x55, 0x3f, 0x43, 0x00, 0x00, 0x72, 0xf8, 0xfc, 0x94, 0x2c, 0xf9, 0x00,
348                0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
349                0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
350                0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00,
351                0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
352                0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x74, 0x00, 0x00,
353                0x1b,
354            ]
355        );
356
357        assert!(anc_iter.next().is_none());
358    }
359}