mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer-rs.git
synced 2024-11-25 11:01:10 +00:00
gst-video: bindings for VideoVBIEncoder & VideoVBIParser
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1319>
This commit is contained in:
parent
b158ca83f9
commit
abdd4df415
5 changed files with 948 additions and 0 deletions
|
@ -22,6 +22,7 @@ gst = { package = "gstreamer", path = "../gstreamer" }
|
||||||
gst-base = { package = "gstreamer-base", path = "../gstreamer-base" }
|
gst-base = { package = "gstreamer-base", path = "../gstreamer-base" }
|
||||||
futures-channel = "0.3"
|
futures-channel = "0.3"
|
||||||
serde = { version = "1.0", optional = true, features = ["derive"] }
|
serde = { version = "1.0", optional = true, features = ["derive"] }
|
||||||
|
thiserror = "1.0.49"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
itertools = "0.11"
|
itertools = "0.11"
|
||||||
|
|
|
@ -133,6 +133,27 @@ mod video_aggregator_convert_pad;
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
|
||||||
mod video_aggregator_pad;
|
mod video_aggregator_pad;
|
||||||
|
|
||||||
|
#[cfg(feature = "v1_16")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
|
||||||
|
mod video_vbi;
|
||||||
|
#[cfg(feature = "v1_16")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
|
||||||
|
pub use video_vbi::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "v1_16")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
|
||||||
|
mod video_vbi_encoder;
|
||||||
|
#[cfg(feature = "v1_16")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
|
||||||
|
pub use video_vbi_encoder::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "v1_16")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
|
||||||
|
mod video_vbi_parser;
|
||||||
|
#[cfg(feature = "v1_16")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
|
||||||
|
pub use video_vbi_parser::*;
|
||||||
|
|
||||||
pub const VIDEO_ENCODER_FLOW_NEED_DATA: gst::FlowSuccess = gst::FlowSuccess::CustomSuccess;
|
pub const VIDEO_ENCODER_FLOW_NEED_DATA: gst::FlowSuccess = gst::FlowSuccess::CustomSuccess;
|
||||||
pub const VIDEO_DECODER_FLOW_NEED_DATA: gst::FlowSuccess = gst::FlowSuccess::CustomSuccess;
|
pub const VIDEO_DECODER_FLOW_NEED_DATA: gst::FlowSuccess = gst::FlowSuccess::CustomSuccess;
|
||||||
|
|
||||||
|
|
35
gstreamer-video/src/video_vbi.rs
Normal file
35
gstreamer-video/src/video_vbi.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
use crate::VideoFormat;
|
||||||
|
pub(super) const VBI_HD_MIN_PIXEL_WIDTH: u32 = 1280;
|
||||||
|
|
||||||
|
// rustdoc-stripper-ignore-next
|
||||||
|
/// Video Vertical Blanking Interval related Errors.
|
||||||
|
#[derive(thiserror::Error, Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum VideoVBIError {
|
||||||
|
#[error("Format and/or pixel_width is not supported")]
|
||||||
|
Unsupported,
|
||||||
|
|
||||||
|
#[error("Not enough space left in the current line")]
|
||||||
|
NotEnoughSpace,
|
||||||
|
|
||||||
|
#[error("Not enough data left in the current line")]
|
||||||
|
NotEnoughData,
|
||||||
|
|
||||||
|
#[error("Insufficient line buffer length {found}. Expected: {expected}")]
|
||||||
|
InsufficientLineBufLen { found: usize, expected: usize },
|
||||||
|
}
|
||||||
|
|
||||||
|
// rustdoc-stripper-ignore-next
|
||||||
|
/// Returns the buffer length needed to store the line.
|
||||||
|
pub(super) fn line_buffer_len(format: VideoFormat, width: u32) -> usize {
|
||||||
|
skip_assert_initialized!();
|
||||||
|
// Taken from gst-plugins-base/gst-libs/gst/video/video-info.c:fill_planes
|
||||||
|
match format {
|
||||||
|
VideoFormat::V210 => ((width as usize + 47) / 48) * 128,
|
||||||
|
VideoFormat::Uyvy => {
|
||||||
|
// round up width to the next multiple of 4
|
||||||
|
// FIXME: {integer}::next_multiple_of was stabilised in rustc 1.73.0
|
||||||
|
((width as usize * 2) + 3) & !3
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
543
gstreamer-video/src/video_vbi_encoder.rs
Normal file
543
gstreamer-video/src/video_vbi_encoder.rs
Normal file
|
@ -0,0 +1,543 @@
|
||||||
|
// Take a look at the license at the top of the repository in the LICENSE file.
|
||||||
|
|
||||||
|
use crate::VideoFormat;
|
||||||
|
use glib::translate::*;
|
||||||
|
|
||||||
|
use crate::video_vbi::line_buffer_len;
|
||||||
|
use crate::{VideoAncillaryDID, VideoAncillaryDID16, VideoVBIError, VBI_HD_MIN_PIXEL_WIDTH};
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
struct VideoVBIEncoderInner(Boxed<ffi::GstVideoVBIEncoder>);
|
||||||
|
|
||||||
|
match fn {
|
||||||
|
copy => |ptr| ffi::gst_video_vbi_encoder_copy(ptr),
|
||||||
|
free => |ptr| ffi::gst_video_vbi_encoder_free(ptr),
|
||||||
|
type_ => || ffi::gst_video_vbi_encoder_get_type(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct VideoVBIEncoder {
|
||||||
|
inner: VideoVBIEncoderInner,
|
||||||
|
format: VideoFormat,
|
||||||
|
pixel_width: u32,
|
||||||
|
line_buffer_len: usize,
|
||||||
|
anc_len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for VideoVBIEncoder {}
|
||||||
|
unsafe impl Sync for VideoVBIEncoder {}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum VideoAFDDescriptionMode {
|
||||||
|
Composite,
|
||||||
|
Component,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VideoAFDDescriptionMode {
|
||||||
|
pub fn is_composite(&self) -> bool {
|
||||||
|
matches!(self, VideoAFDDescriptionMode::Composite)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_component(&self) -> bool {
|
||||||
|
matches!(self, VideoAFDDescriptionMode::Component)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VideoVBIEncoder {
|
||||||
|
#[doc(alias = "gst_video_vbi_encoder_new")]
|
||||||
|
fn try_new(format: VideoFormat, pixel_width: u32) -> Result<VideoVBIEncoder, VideoVBIError> {
|
||||||
|
skip_assert_initialized!();
|
||||||
|
let res: Option<VideoVBIEncoderInner> = unsafe {
|
||||||
|
from_glib_full(ffi::gst_video_vbi_encoder_new(
|
||||||
|
format.into_glib(),
|
||||||
|
pixel_width,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(VideoVBIEncoder {
|
||||||
|
inner: res.ok_or(VideoVBIError::Unsupported)?,
|
||||||
|
format,
|
||||||
|
pixel_width,
|
||||||
|
line_buffer_len: line_buffer_len(format, pixel_width),
|
||||||
|
anc_len: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// rustdoc-stripper-ignore-next
|
||||||
|
/// Adds the provided ancillary data as a DID and block number AFD.
|
||||||
|
pub fn add_did_ancillary(
|
||||||
|
&mut self,
|
||||||
|
adf_mode: VideoAFDDescriptionMode,
|
||||||
|
did: VideoAncillaryDID,
|
||||||
|
block_number: u8,
|
||||||
|
data: &[u8],
|
||||||
|
) -> Result<(), VideoVBIError> {
|
||||||
|
self.add_ancillary(adf_mode, did.into_glib() as u8, block_number, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// rustdoc-stripper-ignore-next
|
||||||
|
/// Adds the provided ancillary data as a DID16 (DID & SDID) AFD.
|
||||||
|
pub fn add_did16_ancillary(
|
||||||
|
&mut self,
|
||||||
|
adf_mode: VideoAFDDescriptionMode,
|
||||||
|
did16: VideoAncillaryDID16,
|
||||||
|
data: &[u8],
|
||||||
|
) -> Result<(), VideoVBIError> {
|
||||||
|
let did16 = did16.into_glib();
|
||||||
|
|
||||||
|
self.add_ancillary(
|
||||||
|
adf_mode,
|
||||||
|
((did16 & 0xff00) >> 8) as u8,
|
||||||
|
(did16 & 0xff) as u8,
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(alias = "gst_video_vbi_encoder_add_ancillary")]
|
||||||
|
pub fn add_ancillary(
|
||||||
|
&mut self,
|
||||||
|
adf_mode: VideoAFDDescriptionMode,
|
||||||
|
did: u8,
|
||||||
|
sdid_block_number: u8,
|
||||||
|
data: &[u8],
|
||||||
|
) -> Result<(), VideoVBIError> {
|
||||||
|
let data_count = data.len() as _;
|
||||||
|
let res: bool = unsafe {
|
||||||
|
from_glib(ffi::gst_video_vbi_encoder_add_ancillary(
|
||||||
|
self.inner.to_glib_none_mut().0,
|
||||||
|
adf_mode.is_composite().into_glib(),
|
||||||
|
did,
|
||||||
|
sdid_block_number,
|
||||||
|
data.to_glib_none().0,
|
||||||
|
data_count,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
if !res {
|
||||||
|
return Err(VideoVBIError::NotEnoughSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
// AFD: 1 byte (+2 if component)
|
||||||
|
// DID + SDID_block_number + Data Count: 3 bytes
|
||||||
|
// DATA: data_count bytes
|
||||||
|
// Checksum: 1 byte
|
||||||
|
let mut len = 1 + 3 + (data_count as usize) + 1;
|
||||||
|
if adf_mode.is_component() {
|
||||||
|
len += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches!(self.format, VideoFormat::V210) {
|
||||||
|
// 10bits payload on 16bits for now: will be packed when writing the line
|
||||||
|
len *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.anc_len += len;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// rustdoc-stripper-ignore-next
|
||||||
|
/// Returns the buffer length needed to store the line.
|
||||||
|
pub fn line_buffer_len(&self) -> usize {
|
||||||
|
self.line_buffer_len
|
||||||
|
}
|
||||||
|
|
||||||
|
// rustdoc-stripper-ignore-next
|
||||||
|
/// Writes the ancillaries encoded for VBI to the provided buffer.
|
||||||
|
///
|
||||||
|
/// Use [`Self::line_buffer_len`] to get the expected buffer length.
|
||||||
|
///
|
||||||
|
/// Resets the internal state, so this [`VideoVBIEncoder`] can be reused for
|
||||||
|
/// subsequent VBI encodings.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// - `Ok` with the written length in bytes in the line buffer containing the encoded
|
||||||
|
/// ancilliaries previously added using [`VideoVBIEncoder::add_ancillary`],
|
||||||
|
/// [`VideoVBIEncoder::add_did_ancillary`] or [`VideoVBIEncoder::add_did16_ancillary`].
|
||||||
|
/// - `Err` if the ancillary could not be added.
|
||||||
|
#[doc(alias = "gst_video_vbi_encoder_write_line")]
|
||||||
|
pub fn write_line(&mut self, data: &mut [u8]) -> Result<usize, VideoVBIError> {
|
||||||
|
if data.len() < self.line_buffer_len {
|
||||||
|
return Err(VideoVBIError::InsufficientLineBufLen {
|
||||||
|
found: data.len(),
|
||||||
|
expected: self.line_buffer_len,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let dest = data.as_mut_ptr();
|
||||||
|
ffi::gst_video_vbi_encoder_write_line(self.inner.to_glib_none_mut().0, dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut anc_len = std::mem::take(&mut self.anc_len);
|
||||||
|
match self.format {
|
||||||
|
VideoFormat::V210 => {
|
||||||
|
// Anc data consists in 10bits stored in 16bits word
|
||||||
|
let word_count = anc_len / 2;
|
||||||
|
|
||||||
|
if self.pixel_width < VBI_HD_MIN_PIXEL_WIDTH {
|
||||||
|
// SD: Packs 12x 10bits data in 4x 32bits word
|
||||||
|
anc_len =
|
||||||
|
4 * 4 * ((word_count / 12) + if word_count % 12 == 0 { 0 } else { 1 });
|
||||||
|
} else {
|
||||||
|
// HD: Packs 3x 10bits data in 1x 32bits word interleaving UV and Y components
|
||||||
|
// (where Y starts at buffer offset 0 and UV starts at buffer offset pixel_width)
|
||||||
|
// so we get 6 (uv,y) pairs every 4x 32bits word in the resulting line
|
||||||
|
// FIXME: {integer}::div_ceil was stabilised in rustc 1.73.0
|
||||||
|
let pair_count = usize::min(word_count, self.pixel_width as usize);
|
||||||
|
anc_len = 4 * 4 * ((pair_count / 6) + if pair_count % 6 == 0 { 0 } else { 1 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VideoFormat::Uyvy => {
|
||||||
|
// Anc data stored as bytes
|
||||||
|
|
||||||
|
if self.pixel_width < VBI_HD_MIN_PIXEL_WIDTH {
|
||||||
|
// SD: Stores 4x bytes in 4x bytes let's keep 32 bits alignment
|
||||||
|
anc_len = 4 * ((anc_len / 4) + if anc_len % 4 == 0 { 0 } else { 1 });
|
||||||
|
} else {
|
||||||
|
// HD: Stores 4x bytes in 4x bytes interleaving UV and Y components
|
||||||
|
// (where Y starts at buffer offset 0 and UV starts at buffer offset pixel_width)
|
||||||
|
// so we get 2 (uv,y) pairs every 4x bytes in the resulting line
|
||||||
|
// let's keep 32 bits alignment
|
||||||
|
// FIXME: {integer}::div_ceil was stabilised in rustc 1.73.0
|
||||||
|
let pair_count = usize::min(anc_len, self.pixel_width as usize);
|
||||||
|
anc_len = 4 * ((pair_count / 2) + if pair_count % 2 == 0 { 0 } else { 1 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(anc_len < self.line_buffer_len);
|
||||||
|
|
||||||
|
Ok(anc_len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a crate::VideoInfo> for VideoVBIEncoder {
|
||||||
|
type Error = VideoVBIError;
|
||||||
|
|
||||||
|
fn try_from(info: &'a crate::VideoInfo) -> Result<VideoVBIEncoder, VideoVBIError> {
|
||||||
|
skip_assert_initialized!();
|
||||||
|
VideoVBIEncoder::try_new(info.format(), info.width())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cea608_component() {
|
||||||
|
let mut encoder =
|
||||||
|
VideoVBIEncoder::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
|
||||||
|
encoder
|
||||||
|
.add_did16_ancillary(
|
||||||
|
VideoAFDDescriptionMode::Component,
|
||||||
|
VideoAncillaryDID16::S334Eia608,
|
||||||
|
&[0x80, 0x94, 0x2c],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut buf = vec![0; encoder.line_buffer_len()];
|
||||||
|
let anc_len = encoder.write_line(buf.as_mut_slice()).unwrap();
|
||||||
|
assert_eq!(32, anc_len);
|
||||||
|
assert_eq!(
|
||||||
|
buf[0..anc_len],
|
||||||
|
[
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0xff, 0x03, 0xf0, 0x3f, 0x00, 0x84, 0x05, 0x00, 0x02, 0x01,
|
||||||
|
0x30, 0x20, 0x00, 0x00, 0x06, 0x00, 0x94, 0x01, 0xc0, 0x12, 0x00, 0x98, 0x0a, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cea608_component_sd() {
|
||||||
|
let mut encoder = VideoVBIEncoder::try_new(VideoFormat::V210, 768).unwrap();
|
||||||
|
encoder
|
||||||
|
.add_did16_ancillary(
|
||||||
|
VideoAFDDescriptionMode::Component,
|
||||||
|
VideoAncillaryDID16::S334Eia608,
|
||||||
|
&[0x80, 0x94, 0x2c],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut buf = vec![0; encoder.line_buffer_len()];
|
||||||
|
let anc_len = encoder.write_line(buf.as_mut_slice()).unwrap();
|
||||||
|
assert_eq!(16, anc_len);
|
||||||
|
assert_eq!(
|
||||||
|
buf[0..anc_len],
|
||||||
|
[
|
||||||
|
0x00, 0xfc, 0xff, 0x3f, 0x61, 0x09, 0x34, 0x20, 0x80, 0x51, 0xc6, 0x12, 0xa6, 0x02,
|
||||||
|
0x00, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cea608_composite() {
|
||||||
|
let mut encoder =
|
||||||
|
VideoVBIEncoder::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
|
||||||
|
encoder
|
||||||
|
.add_did16_ancillary(
|
||||||
|
VideoAFDDescriptionMode::Composite,
|
||||||
|
VideoAncillaryDID16::S334Eia608,
|
||||||
|
&[0x15, 0x94, 0x2c],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut buf = vec![0; encoder.line_buffer_len()];
|
||||||
|
let anc_len = encoder.write_line(buf.as_mut_slice()).unwrap();
|
||||||
|
assert_eq!(32, anc_len);
|
||||||
|
assert_eq!(
|
||||||
|
buf[0..anc_len],
|
||||||
|
[
|
||||||
|
0x00, 0xf0, 0x0f, 0x00, 0x61, 0x01, 0x20, 0x10, 0x00, 0x0c, 0x08, 0x00, 0x15, 0x01,
|
||||||
|
0x40, 0x19, 0x00, 0xb0, 0x04, 0x00, 0x3b, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cea608_composite_sd() {
|
||||||
|
let mut encoder = VideoVBIEncoder::try_new(VideoFormat::V210, 768).unwrap();
|
||||||
|
encoder
|
||||||
|
.add_did16_ancillary(
|
||||||
|
VideoAFDDescriptionMode::Composite,
|
||||||
|
VideoAncillaryDID16::S334Eia608,
|
||||||
|
&[0x15, 0x94, 0x2c],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut buf = vec![0; encoder.line_buffer_len()];
|
||||||
|
let anc_len = encoder.write_line(buf.as_mut_slice()).unwrap();
|
||||||
|
assert_eq!(16, anc_len);
|
||||||
|
assert_eq!(
|
||||||
|
buf[0..anc_len],
|
||||||
|
[
|
||||||
|
0xfc, 0x87, 0x25, 0x10, 0x03, 0x56, 0x44, 0x19, 0x2c, 0xed, 0x08, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cea608_component_uyvy() {
|
||||||
|
let mut encoder =
|
||||||
|
VideoVBIEncoder::try_new(VideoFormat::Uyvy, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
|
||||||
|
encoder
|
||||||
|
.add_did16_ancillary(
|
||||||
|
VideoAFDDescriptionMode::Component,
|
||||||
|
VideoAncillaryDID16::S334Eia608,
|
||||||
|
&[0x80, 0x94, 0x2c],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut buf = vec![0; encoder.line_buffer_len()];
|
||||||
|
let anc_len = encoder.write_line(buf.as_mut_slice()).unwrap();
|
||||||
|
assert_eq!(20, anc_len);
|
||||||
|
assert_eq!(
|
||||||
|
buf[0..anc_len],
|
||||||
|
[
|
||||||
|
0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x61, 0x00, 0x02, 0x00, 0x03, 0x00, 0x80,
|
||||||
|
0x00, 0x94, 0x00, 0x2c, 0x00, 0xa6
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cea608_component_sd_uyvy() {
|
||||||
|
let mut encoder = VideoVBIEncoder::try_new(VideoFormat::Uyvy, 768).unwrap();
|
||||||
|
encoder
|
||||||
|
.add_did16_ancillary(
|
||||||
|
VideoAFDDescriptionMode::Component,
|
||||||
|
VideoAncillaryDID16::S334Eia608,
|
||||||
|
&[0x80, 0x94, 0x2c],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut buf = vec![0; encoder.line_buffer_len()];
|
||||||
|
let anc_len = encoder.write_line(buf.as_mut_slice()).unwrap();
|
||||||
|
assert_eq!(12, anc_len);
|
||||||
|
assert_eq!(
|
||||||
|
buf[0..anc_len],
|
||||||
|
[0x00, 0xff, 0xff, 0x61, 0x02, 0x03, 0x80, 0x94, 0x2c, 0xa6, 0x00, 0x00]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cea608_composite_uyvy() {
|
||||||
|
let mut encoder =
|
||||||
|
VideoVBIEncoder::try_new(VideoFormat::Uyvy, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
|
||||||
|
encoder
|
||||||
|
.add_did16_ancillary(
|
||||||
|
VideoAFDDescriptionMode::Composite,
|
||||||
|
VideoAncillaryDID16::S334Eia608,
|
||||||
|
&[0x15, 0x94, 0x2c],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut buf = vec![0; encoder.line_buffer_len()];
|
||||||
|
let anc_len = encoder.write_line(buf.as_mut_slice()).unwrap();
|
||||||
|
assert_eq!(16, anc_len);
|
||||||
|
assert_eq!(
|
||||||
|
buf[0..anc_len],
|
||||||
|
[
|
||||||
|
0x00, 0xfc, 0x00, 0x61, 0x00, 0x02, 0x00, 0x03, 0x00, 0x15, 0x00, 0x94, 0x00, 0x2c,
|
||||||
|
0x00, 0x3b
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cea608_composite_sd_uyvy() {
|
||||||
|
let mut encoder = VideoVBIEncoder::try_new(VideoFormat::Uyvy, 768).unwrap();
|
||||||
|
encoder
|
||||||
|
.add_did16_ancillary(
|
||||||
|
VideoAFDDescriptionMode::Composite,
|
||||||
|
VideoAncillaryDID16::S334Eia608,
|
||||||
|
&[0x15, 0x94, 0x2c],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut buf = vec![0; encoder.line_buffer_len()];
|
||||||
|
let anc_len = encoder.write_line(buf.as_mut_slice()).unwrap();
|
||||||
|
assert_eq!(8, anc_len);
|
||||||
|
assert_eq!(
|
||||||
|
buf[0..anc_len],
|
||||||
|
[0xfc, 0x61, 0x02, 0x03, 0x15, 0x94, 0x2c, 0x3b]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn insufficient_line_buf_len() {
|
||||||
|
let mut encoder =
|
||||||
|
VideoVBIEncoder::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
|
||||||
|
encoder
|
||||||
|
.add_did16_ancillary(
|
||||||
|
VideoAFDDescriptionMode::Component,
|
||||||
|
VideoAncillaryDID16::S334Eia608,
|
||||||
|
&[0x80, 0x94, 0x2c],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut buf = vec![0; 10];
|
||||||
|
assert_eq!(
|
||||||
|
encoder.write_line(buf.as_mut_slice()).unwrap_err(),
|
||||||
|
VideoVBIError::InsufficientLineBufLen {
|
||||||
|
found: 10,
|
||||||
|
expected: encoder.line_buffer_len()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cea708_component() {
|
||||||
|
let mut encoder =
|
||||||
|
VideoVBIEncoder::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
|
||||||
|
encoder
|
||||||
|
.add_did16_ancillary(
|
||||||
|
VideoAFDDescriptionMode::Component,
|
||||||
|
VideoAncillaryDID16::S334Eia708,
|
||||||
|
&[
|
||||||
|
0x96, 0x69, 0x55, 0x3f, 0x43, 0x00, 0x00, 0x72, 0xf8, 0xfc, 0x94, 0x2c, 0xf9,
|
||||||
|
0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00,
|
||||||
|
0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||||
|
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
|
||||||
|
0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00,
|
||||||
|
0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||||
|
0xfa, 0x00, 0x00, 0x74, 0x00, 0x00, 0x1b,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut buf = vec![0; encoder.line_buffer_len()];
|
||||||
|
let anc_len = encoder.write_line(buf.as_mut_slice()).unwrap();
|
||||||
|
assert_eq!(256, anc_len);
|
||||||
|
assert_eq!(
|
||||||
|
buf[0..anc_len],
|
||||||
|
[
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0xff, 0x03, 0xf0, 0x3f, 0x00, 0x84, 0x05, 0x00, 0x01, 0x01,
|
||||||
|
0x50, 0x25, 0x00, 0x58, 0x0a, 0x00, 0x69, 0x02, 0x50, 0x25, 0x00, 0xfc, 0x08, 0x00,
|
||||||
|
0x43, 0x01, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0x72, 0x02, 0x80, 0x1f, 0x00, 0xf0,
|
||||||
|
0x0b, 0x00, 0x94, 0x01, 0xc0, 0x12, 0x00, 0xe4, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20,
|
||||||
|
0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02,
|
||||||
|
0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00,
|
||||||
|
0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8,
|
||||||
|
0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20,
|
||||||
|
0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02,
|
||||||
|
0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00,
|
||||||
|
0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8,
|
||||||
|
0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20,
|
||||||
|
0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02,
|
||||||
|
0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00,
|
||||||
|
0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8,
|
||||||
|
0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20,
|
||||||
|
0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xd0, 0x09, 0x00, 0x00, 0x02,
|
||||||
|
0x00, 0x20, 0x00, 0x6c, 0x08, 0x00, 0xb7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cea608_and_cea708_component() {
|
||||||
|
let mut encoder =
|
||||||
|
VideoVBIEncoder::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
|
||||||
|
encoder
|
||||||
|
.add_did16_ancillary(
|
||||||
|
VideoAFDDescriptionMode::Component,
|
||||||
|
VideoAncillaryDID16::S334Eia608,
|
||||||
|
&[0x80, 0x94, 0x2c],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
encoder
|
||||||
|
.add_did16_ancillary(
|
||||||
|
VideoAFDDescriptionMode::Component,
|
||||||
|
VideoAncillaryDID16::S334Eia708,
|
||||||
|
&[
|
||||||
|
0x96, 0x69, 0x55, 0x3f, 0x43, 0x00, 0x00, 0x72, 0xf8, 0xfc, 0x94, 0x2c, 0xf9,
|
||||||
|
0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00,
|
||||||
|
0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||||
|
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
|
||||||
|
0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00,
|
||||||
|
0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||||
|
0xfa, 0x00, 0x00, 0x74, 0x00, 0x00, 0x1b,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut buf = vec![0; encoder.line_buffer_len()];
|
||||||
|
let anc_len = encoder.write_line(buf.as_mut_slice()).unwrap();
|
||||||
|
assert_eq!(272, anc_len);
|
||||||
|
assert_eq!(
|
||||||
|
buf[0..anc_len],
|
||||||
|
[
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0xff, 0x03, 0xf0, 0x3f, 0x00, 0x84, 0x05, 0x00, 0x02, 0x01,
|
||||||
|
0x30, 0x20, 0x00, 0x00, 0x06, 0x00, 0x94, 0x01, 0xc0, 0x12, 0x00, 0x98, 0x0a, 0x00,
|
||||||
|
0x00, 0x00, 0xf0, 0x3f, 0x00, 0xfc, 0x0f, 0x00, 0x61, 0x01, 0x10, 0x10, 0x00, 0x54,
|
||||||
|
0x09, 0x00, 0x96, 0x02, 0x90, 0x26, 0x00, 0x54, 0x09, 0x00, 0x3f, 0x02, 0x30, 0x14,
|
||||||
|
0x00, 0x00, 0x08, 0x00, 0x00, 0x02, 0x20, 0x27, 0x00, 0xe0, 0x07, 0x00, 0xfc, 0x02,
|
||||||
|
0x40, 0x19, 0x00, 0xb0, 0x04, 0x00, 0xf9, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00,
|
||||||
|
0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00,
|
||||||
|
0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20,
|
||||||
|
0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02,
|
||||||
|
0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00,
|
||||||
|
0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00,
|
||||||
|
0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20,
|
||||||
|
0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02,
|
||||||
|
0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00,
|
||||||
|
0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00,
|
||||||
|
0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20,
|
||||||
|
0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02,
|
||||||
|
0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00,
|
||||||
|
0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0x74, 0x02, 0x00, 0x20, 0x00, 0x00,
|
||||||
|
0x08, 0x00, 0x1b, 0x02, 0x70, 0x2b
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
348
gstreamer-video/src/video_vbi_parser.rs
Normal file
348
gstreamer-video/src/video_vbi_parser.rs
Normal file
|
@ -0,0 +1,348 @@
|
||||||
|
// Take a look at the license at the top of the repository in the LICENSE file.
|
||||||
|
|
||||||
|
use crate::VideoFormat;
|
||||||
|
use glib::translate::*;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::video_vbi::line_buffer_len;
|
||||||
|
use crate::{VideoAncillaryDID, VideoAncillaryDID16, VideoVBIError};
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
#[doc(alias = "GstVideoAncillary")]
|
||||||
|
pub struct VideoAncillary(BoxedInline<ffi::GstVideoAncillary>);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VideoAncillary {
|
||||||
|
pub fn did_u8(&self) -> u8 {
|
||||||
|
self.inner.DID
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn did(&self) -> VideoAncillaryDID {
|
||||||
|
unsafe { VideoAncillaryDID::from_glib(self.inner.DID as ffi::GstVideoAncillaryDID) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sdid_block_number(&self) -> u8 {
|
||||||
|
self.inner.SDID_block_number
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn did16(&self) -> VideoAncillaryDID16 {
|
||||||
|
unsafe {
|
||||||
|
VideoAncillaryDID16::from_glib(
|
||||||
|
(((self.inner.DID as u16) << 8) + self.inner.SDID_block_number as u16)
|
||||||
|
as ffi::GstVideoAncillaryDID16,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.inner.data_count as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.inner.data_count == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn data(&self) -> &[u8] {
|
||||||
|
&self.inner.data[0..(self.inner.data_count as usize)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for VideoAncillary {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("VideoAncillary")
|
||||||
|
.field("did", &self.did())
|
||||||
|
.field("sdid_block_number", &self.sdid_block_number())
|
||||||
|
.field("did16", &self.did16())
|
||||||
|
.field("data_count", &self.inner.data_count)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
struct VideoVBIParserInner(Boxed<ffi::GstVideoVBIParser>);
|
||||||
|
|
||||||
|
match fn {
|
||||||
|
copy => |ptr| ffi::gst_video_vbi_parser_copy(ptr),
|
||||||
|
free => |ptr| ffi::gst_video_vbi_parser_free(ptr),
|
||||||
|
type_ => || ffi::gst_video_vbi_parser_get_type(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct VideoVBIParser {
|
||||||
|
inner: VideoVBIParserInner,
|
||||||
|
line_buffer_len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VideoVBIParser {
|
||||||
|
#[doc(alias = "gst_video_vbi_parser_new")]
|
||||||
|
pub fn try_new(format: VideoFormat, pixel_width: u32) -> Result<VideoVBIParser, VideoVBIError> {
|
||||||
|
skip_assert_initialized!();
|
||||||
|
let res: Option<VideoVBIParserInner> = unsafe {
|
||||||
|
from_glib_full(ffi::gst_video_vbi_parser_new(
|
||||||
|
format.into_glib(),
|
||||||
|
pixel_width,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(VideoVBIParser {
|
||||||
|
inner: res.ok_or(VideoVBIError::Unsupported)?,
|
||||||
|
line_buffer_len: line_buffer_len(format, pixel_width),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// rustdoc-stripper-ignore-next
|
||||||
|
/// Returns the buffer length needed to store the line.
|
||||||
|
pub fn line_buffer_len(&self) -> usize {
|
||||||
|
self.line_buffer_len
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(alias = "gst_video_vbi_parser_add_line")]
|
||||||
|
pub fn add_line(&mut self, data: &[u8]) -> Result<(), VideoVBIError> {
|
||||||
|
if data.len() < self.line_buffer_len {
|
||||||
|
return Err(VideoVBIError::InsufficientLineBufLen {
|
||||||
|
found: data.len(),
|
||||||
|
expected: self.line_buffer_len,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
let data = data.as_ptr();
|
||||||
|
ffi::gst_video_vbi_parser_add_line(self.inner.to_glib_none_mut().0, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&mut self) -> AncillaryIter {
|
||||||
|
AncillaryIter { parser: self }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(alias = "gst_video_vbi_parser_get_ancillary")]
|
||||||
|
pub fn next_ancillary(&mut self) -> Option<Result<VideoAncillary, VideoVBIError>> {
|
||||||
|
unsafe {
|
||||||
|
let mut video_anc = std::mem::MaybeUninit::uninit();
|
||||||
|
let res = ffi::gst_video_vbi_parser_get_ancillary(
|
||||||
|
self.inner.to_glib_none_mut().0,
|
||||||
|
video_anc.as_mut_ptr(),
|
||||||
|
);
|
||||||
|
|
||||||
|
match res {
|
||||||
|
ffi::GST_VIDEO_VBI_PARSER_RESULT_OK => Some(Ok(VideoAncillary {
|
||||||
|
inner: video_anc.assume_init(),
|
||||||
|
})),
|
||||||
|
ffi::GST_VIDEO_VBI_PARSER_RESULT_DONE => None,
|
||||||
|
ffi::GST_VIDEO_VBI_PARSER_RESULT_ERROR => Some(Err(VideoVBIError::NotEnoughData)),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for VideoVBIParser {}
|
||||||
|
unsafe impl Sync for VideoVBIParser {}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a crate::VideoInfo> for VideoVBIParser {
|
||||||
|
type Error = VideoVBIError;
|
||||||
|
|
||||||
|
fn try_from(info: &'a crate::VideoInfo) -> Result<VideoVBIParser, VideoVBIError> {
|
||||||
|
skip_assert_initialized!();
|
||||||
|
VideoVBIParser::try_new(info.format(), info.width())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AncillaryIter<'a> {
|
||||||
|
parser: &'a mut VideoVBIParser,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for AncillaryIter<'a> {
|
||||||
|
type Item = Result<VideoAncillary, VideoVBIError>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.parser.next_ancillary()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::VBI_HD_MIN_PIXEL_WIDTH;
|
||||||
|
|
||||||
|
fn init_line_buf(parser: &VideoVBIParser, anc_buf: &[u8]) -> Vec<u8> {
|
||||||
|
skip_assert_initialized!();
|
||||||
|
let mut line_buf = vec![0; parser.line_buffer_len()];
|
||||||
|
line_buf[0..anc_buf.len()].copy_from_slice(anc_buf);
|
||||||
|
line_buf
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cea608_component() {
|
||||||
|
let mut parser =
|
||||||
|
VideoVBIParser::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
|
||||||
|
let line_buf = init_line_buf(
|
||||||
|
&parser,
|
||||||
|
&[
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0xff, 0x03, 0xf0, 0x3f, 0x00, 0x84, 0x05, 0x00, 0x02, 0x01,
|
||||||
|
0x30, 0x20, 0x00, 0x00, 0x06, 0x00, 0x94, 0x01, 0xc0, 0x12, 0x00, 0x98, 0x0a, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
parser.add_line(&line_buf).unwrap();
|
||||||
|
|
||||||
|
let video_anc = parser.next_ancillary().unwrap().unwrap();
|
||||||
|
assert_eq!(video_anc.did16(), VideoAncillaryDID16::S334Eia608);
|
||||||
|
assert_eq!(video_anc.data(), [0x80, 0x94, 0x2c]);
|
||||||
|
|
||||||
|
assert!(parser.next_ancillary().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cea608_composite() {
|
||||||
|
let mut parser =
|
||||||
|
VideoVBIParser::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
|
||||||
|
let line_buf = init_line_buf(
|
||||||
|
&parser,
|
||||||
|
&[
|
||||||
|
0x00, 0xf0, 0x0f, 0x00, 0x61, 0x01, 0x20, 0x10, 0x00, 0x0c, 0x08, 0x00, 0x15, 0x01,
|
||||||
|
0x40, 0x19, 0x00, 0xb0, 0x04, 0x00, 0x3b, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
parser.add_line(&line_buf).unwrap();
|
||||||
|
|
||||||
|
let video_anc = parser.next_ancillary().unwrap().unwrap();
|
||||||
|
assert_eq!(video_anc.did16(), VideoAncillaryDID16::S334Eia608);
|
||||||
|
assert_eq!(video_anc.data(), [0x15, 0x94, 0x2c]);
|
||||||
|
|
||||||
|
assert!(parser.next_ancillary().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cea608_can_not_parse() {
|
||||||
|
let mut parser =
|
||||||
|
VideoVBIParser::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
|
||||||
|
let line_buf = init_line_buf(&parser, &[0x00, 0xf0, 0x0f, 0x00, 0x61, 0x01, 0x20, 0x10]);
|
||||||
|
parser.add_line(&line_buf).unwrap();
|
||||||
|
|
||||||
|
assert!(parser.next_ancillary().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cea608_insufficient_line_buf_len() {
|
||||||
|
let mut parser =
|
||||||
|
VideoVBIParser::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
|
||||||
|
let line_buf = vec![0; 10];
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
parser.add_line(&line_buf).unwrap_err(),
|
||||||
|
VideoVBIError::InsufficientLineBufLen {
|
||||||
|
found: 10,
|
||||||
|
expected: parser.line_buffer_len()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cea708_component() {
|
||||||
|
let mut parser =
|
||||||
|
VideoVBIParser::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
|
||||||
|
let line_buf = init_line_buf(
|
||||||
|
&parser,
|
||||||
|
&[
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0xff, 0x03, 0xf0, 0x3f, 0x00, 0x84, 0x05, 0x00, 0x01, 0x01,
|
||||||
|
0x50, 0x25, 0x00, 0x58, 0x0a, 0x00, 0x69, 0x02, 0x50, 0x25, 0x00, 0xfc, 0x08, 0x00,
|
||||||
|
0x43, 0x01, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0x72, 0x02, 0x80, 0x1f, 0x00, 0xf0,
|
||||||
|
0x0b, 0x00, 0x94, 0x01, 0xc0, 0x12, 0x00, 0xe4, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20,
|
||||||
|
0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02,
|
||||||
|
0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00,
|
||||||
|
0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8,
|
||||||
|
0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20,
|
||||||
|
0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02,
|
||||||
|
0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00,
|
||||||
|
0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8,
|
||||||
|
0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20,
|
||||||
|
0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02,
|
||||||
|
0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00,
|
||||||
|
0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8,
|
||||||
|
0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20,
|
||||||
|
0x00, 0xe8, 0x0b, 0x00, 0x00, 0x02, 0x00, 0x20, 0x00, 0xd0, 0x09, 0x00, 0x00, 0x02,
|
||||||
|
0x00, 0x20, 0x00, 0x6c, 0x08, 0x00, 0xb7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
parser.add_line(&line_buf).unwrap();
|
||||||
|
|
||||||
|
let video_anc = parser.next_ancillary().unwrap().unwrap();
|
||||||
|
assert_eq!(video_anc.did16(), VideoAncillaryDID16::S334Eia708);
|
||||||
|
assert_eq!(
|
||||||
|
video_anc.data(),
|
||||||
|
[
|
||||||
|
0x96, 0x69, 0x55, 0x3f, 0x43, 0x00, 0x00, 0x72, 0xf8, 0xfc, 0x94, 0x2c, 0xf9, 0x00,
|
||||||
|
0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
|
||||||
|
0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||||
|
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00,
|
||||||
|
0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
|
||||||
|
0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x74, 0x00, 0x00,
|
||||||
|
0x1b,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(parser.next_ancillary().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cea608_and_cea708_component() {
|
||||||
|
let mut parser =
|
||||||
|
VideoVBIParser::try_new(VideoFormat::V210, VBI_HD_MIN_PIXEL_WIDTH).unwrap();
|
||||||
|
let mut line_buf = vec![0; parser.line_buffer_len()];
|
||||||
|
let anc_buf = [
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0xff, 0x03, 0xf0, 0x3f, 0x00, 0x84, 0x05, 0x00, 0x02, 0x01,
|
||||||
|
0x30, 0x20, 0x00, 0x00, 0x06, 0x00, 0x94, 0x01, 0xc0, 0x12, 0x00, 0x98, 0x0a, 0x00,
|
||||||
|
0x00, 0x00, 0xf0, 0x3f, 0x00, 0xfc, 0x0f, 0x00, 0x61, 0x01, 0x10, 0x10, 0x00, 0x54,
|
||||||
|
0x09, 0x00, 0x96, 0x02, 0x90, 0x26, 0x00, 0x54, 0x09, 0x00, 0x3f, 0x02, 0x30, 0x14,
|
||||||
|
0x00, 0x00, 0x08, 0x00, 0x00, 0x02, 0x20, 0x27, 0x00, 0xe0, 0x07, 0x00, 0xfc, 0x02,
|
||||||
|
0x40, 0x19, 0x00, 0xb0, 0x04, 0x00, 0xf9, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00,
|
||||||
|
0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00,
|
||||||
|
0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20,
|
||||||
|
0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02,
|
||||||
|
0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00,
|
||||||
|
0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00,
|
||||||
|
0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20,
|
||||||
|
0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02,
|
||||||
|
0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00,
|
||||||
|
0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00,
|
||||||
|
0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20,
|
||||||
|
0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02,
|
||||||
|
0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00,
|
||||||
|
0xfa, 0x02, 0x00, 0x20, 0x00, 0x00, 0x08, 0x00, 0x74, 0x02, 0x00, 0x20, 0x00, 0x00,
|
||||||
|
0x08, 0x00, 0x1b, 0x02, 0x70, 0x2b,
|
||||||
|
];
|
||||||
|
line_buf[0..anc_buf.len()].copy_from_slice(&anc_buf);
|
||||||
|
parser.add_line(&line_buf).unwrap();
|
||||||
|
|
||||||
|
let mut anc_iter = parser.iter();
|
||||||
|
|
||||||
|
let video_anc = anc_iter.next().unwrap().unwrap();
|
||||||
|
assert_eq!(video_anc.did16(), VideoAncillaryDID16::S334Eia608);
|
||||||
|
assert_eq!(video_anc.data(), [0x80, 0x94, 0x2c]);
|
||||||
|
|
||||||
|
let video_anc = anc_iter.next().unwrap().unwrap();
|
||||||
|
assert_eq!(video_anc.did16(), VideoAncillaryDID16::S334Eia708);
|
||||||
|
assert_eq!(
|
||||||
|
video_anc.data(),
|
||||||
|
[
|
||||||
|
0x96, 0x69, 0x55, 0x3f, 0x43, 0x00, 0x00, 0x72, 0xf8, 0xfc, 0x94, 0x2c, 0xf9, 0x00,
|
||||||
|
0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
|
||||||
|
0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||||
|
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00,
|
||||||
|
0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
|
||||||
|
0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x74, 0x00, 0x00,
|
||||||
|
0x1b,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(anc_iter.next().is_none());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue