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#[derive(Debug)]
166pub struct AncillaryIter<'a> {
167    parser: &'a mut VideoVBIParser,
168}
169
170impl Iterator for AncillaryIter<'_> {
171    type Item = Result<VideoAncillary, VideoVBIError>;
172
173    fn next(&mut self) -> Option<Self::Item> {
174        self.parser.next_ancillary()
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181    use crate::VBI_HD_MIN_PIXEL_WIDTH;
182
183    fn init_line_buf(parser: &VideoVBIParser, anc_buf: &[u8]) -> Vec<u8> {
184        skip_assert_initialized!();
185        let mut line_buf = vec![0; parser.line_buffer_len()];
186        line_buf[0..anc_buf.len()].copy_from_slice(anc_buf);
187        line_buf
188    }
189
190    #[test]
191    fn cea608_component() {
192        let mut parser =
193            VideoVBIParser::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
194        let line_buf = init_line_buf(
195            &parser,
196            &[
197                0x00, 0x00, 0x00, 0x00, 0xff, 0x03, 0xf0, 0x3f, 0x00, 0x84, 0x05, 0x00, 0x02, 0x01,
198                0x30, 0x20, 0x00, 0x00, 0x06, 0x00, 0x94, 0x01, 0xc0, 0x12, 0x00, 0x98, 0x0a, 0x00,
199                0x00, 0x00, 0x00, 0x00,
200            ],
201        );
202        parser.add_line(&line_buf).unwrap();
203
204        let video_anc = parser.next_ancillary().unwrap().unwrap();
205        assert_eq!(video_anc.did16(), VideoAncillaryDID16::S334Eia608);
206        assert_eq!(video_anc.data(), [0x80, 0x94, 0x2c]);
207
208        assert!(parser.next_ancillary().is_none());
209    }
210
211    #[test]
212    fn cea608_composite() {
213        let mut parser =
214            VideoVBIParser::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
215        let line_buf = init_line_buf(
216            &parser,
217            &[
218                0x00, 0xf0, 0x0f, 0x00, 0x61, 0x01, 0x20, 0x10, 0x00, 0x0c, 0x08, 0x00, 0x15, 0x01,
219                0x40, 0x19, 0x00, 0xb0, 0x04, 0x00, 0x3b, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
220                0x00, 0x00, 0x00, 0x00,
221            ],
222        );
223        parser.add_line(&line_buf).unwrap();
224
225        let video_anc = parser.next_ancillary().unwrap().unwrap();
226        assert_eq!(video_anc.did16(), VideoAncillaryDID16::S334Eia608);
227        assert_eq!(video_anc.data(), [0x15, 0x94, 0x2c]);
228
229        assert!(parser.next_ancillary().is_none());
230    }
231
232    #[test]
233    fn cea608_can_not_parse() {
234        let mut parser =
235            VideoVBIParser::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
236        let line_buf = init_line_buf(&parser, &[0x00, 0xf0, 0x0f, 0x00, 0x61, 0x01, 0x20, 0x10]);
237        parser.add_line(&line_buf).unwrap();
238
239        assert!(parser.next_ancillary().is_none());
240    }
241
242    #[test]
243    fn cea608_insufficient_line_buf_len() {
244        let mut parser =
245            VideoVBIParser::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
246        let line_buf = vec![0; 10];
247
248        assert_eq!(
249            parser.add_line(&line_buf).unwrap_err(),
250            VideoVBIError::InsufficientLineBufLen {
251                found: 10,
252                expected: parser.line_buffer_len()
253            },
254        );
255    }
256
257    #[test]
258    fn cea708_component() {
259        let mut parser =
260            VideoVBIParser::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
261        let line_buf = init_line_buf(
262            &parser,
263            &[
264                0x00, 0x00, 0x00, 0x00, 0xff, 0x03, 0xf0, 0x3f, 0x00, 0x84, 0x05, 0x00, 0x01, 0x01,
265                0x50, 0x25, 0x00, 0x58, 0x0a, 0x00, 0x69, 0x02, 0x50, 0x25, 0x00, 0xfc, 0x08, 0x00,
266                0x43, 0x01, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0x72, 0x02, 0x80, 0x1f, 0x00, 0xf0,
267                0x0b, 0x00, 0x94, 0x01, 0xc0, 0x12, 0x00, 0xe4, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20,
268                0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02,
269                0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00,
270                0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8,
271                0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20,
272                0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02,
273                0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00,
274                0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8,
275                0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20,
276                0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02,
277                0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00,
278                0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8,
279                0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20,
280                0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xd0, 0x09, 0x00, 0x00, 0x02,
281                0x00, 0x20, 0x00, 0x6c, 0x08, 0x00, 0xb7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
282                0x00, 0x00, 0x00, 0x00,
283            ],
284        );
285        parser.add_line(&line_buf).unwrap();
286
287        let video_anc = parser.next_ancillary().unwrap().unwrap();
288        assert_eq!(video_anc.did16(), VideoAncillaryDID16::S334Eia708);
289        assert_eq!(
290            video_anc.data(),
291            [
292                0x96, 0x69, 0x55, 0x3f, 0x43, 0x00, 0x00, 0x72, 0xf8, 0xfc, 0x94, 0x2c, 0xf9, 0x00,
293                0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
294                0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
295                0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00,
296                0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
297                0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x74, 0x00, 0x00,
298                0x1b,
299            ]
300        );
301
302        assert!(parser.next_ancillary().is_none());
303    }
304
305    #[test]
306    fn cea608_and_cea708_component() {
307        let mut parser =
308            VideoVBIParser::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
309        let mut line_buf = vec![0; parser.line_buffer_len()];
310        let anc_buf = [
311            0x00, 0x00, 0x00, 0x00, 0xff, 0x03, 0xf0, 0x3f, 0x00, 0x84, 0x05, 0x00, 0x02, 0x01,
312            0x30, 0x20, 0x00, 0x00, 0x06, 0x00, 0x94, 0x01, 0xc0, 0x12, 0x00, 0x98, 0x0a, 0x00,
313            0x00, 0x00, 0xf0, 0x3f, 0x00, 0xfc, 0x0f, 0x00, 0x61, 0x01, 0x10, 0x10, 0x00, 0x54,
314            0x09, 0x00, 0x96, 0x02, 0x90, 0x26, 0x00, 0x54, 0x09, 0x00, 0x3f, 0x02, 0x30, 0x14,
315            0x00, 0x00, 0x08, 0x00, 0x00, 0x02, 0x20, 0x27, 0x00, 0xe0, 0x07, 0x00, 0xfc, 0x02,
316            0x40, 0x19, 0x00, 0xb0, 0x04, 0x00, 0xf9, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00,
317            0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00,
318            0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20,
319            0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02,
320            0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00,
321            0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00,
322            0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20,
323            0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02,
324            0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00,
325            0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00,
326            0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20,
327            0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02,
328            0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00,
329            0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0x74, 0x02, 0x00, 0x20, 0x00, 0x00,
330            0x08, 0x00, 0x1b, 0x02, 0x70, 0x2b,
331        ];
332        line_buf[0..anc_buf.len()].copy_from_slice(&anc_buf);
333        parser.add_line(&line_buf).unwrap();
334
335        let mut anc_iter = parser.iter();
336
337        let video_anc = anc_iter.next().unwrap().unwrap();
338        assert_eq!(video_anc.did16(), VideoAncillaryDID16::S334Eia608);
339        assert_eq!(video_anc.data(), [0x80, 0x94, 0x2c]);
340
341        let video_anc = anc_iter.next().unwrap().unwrap();
342        assert_eq!(video_anc.did16(), VideoAncillaryDID16::S334Eia708);
343        assert_eq!(
344            video_anc.data(),
345            [
346                0x96, 0x69, 0x55, 0x3f, 0x43, 0x00, 0x00, 0x72, 0xf8, 0xfc, 0x94, 0x2c, 0xf9, 0x00,
347                0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
348                0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
349                0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00,
350                0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
351                0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x74, 0x00, 0x00,
352                0x1b,
353            ]
354        );
355
356        assert!(anc_iter.next().is_none());
357    }
358}