mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-11-26 13:31:00 +00:00
cea608overlay: support all VideoCaptionType meta
Also expose a field property, to allow selecting CC for a specific field when the information is available. Defaults to automatically picking the first field encountered. Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/474>
This commit is contained in:
parent
3c982a3de0
commit
cc402f769c
1 changed files with 194 additions and 2 deletions
|
@ -19,7 +19,7 @@ use glib::subclass;
|
||||||
use glib::subclass::prelude::*;
|
use glib::subclass::prelude::*;
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
use gst::subclass::prelude::*;
|
use gst::subclass::prelude::*;
|
||||||
use gst::{gst_error, gst_log, gst_trace};
|
use gst::{gst_error, gst_info, gst_log, gst_trace, gst_warning};
|
||||||
use gst_video::prelude::*;
|
use gst_video::prelude::*;
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
@ -29,6 +29,7 @@ use std::sync::Mutex;
|
||||||
use pango::prelude::*;
|
use pango::prelude::*;
|
||||||
|
|
||||||
use crate::caption_frame::{CaptionFrame, Status};
|
use crate::caption_frame::{CaptionFrame, Status};
|
||||||
|
use crate::ccutils::extract_cdp;
|
||||||
|
|
||||||
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||||
gst::DebugCategory::new(
|
gst::DebugCategory::new(
|
||||||
|
@ -38,6 +39,21 @@ static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const DEFAULT_FIELD: i32 = -1;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Settings {
|
||||||
|
field: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Settings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Settings {
|
||||||
|
field: DEFAULT_FIELD,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct State {
|
struct State {
|
||||||
video_info: Option<gst_video::VideoInfo>,
|
video_info: Option<gst_video::VideoInfo>,
|
||||||
layout: Option<pango::Layout>,
|
layout: Option<pango::Layout>,
|
||||||
|
@ -45,6 +61,7 @@ struct State {
|
||||||
composition: Option<gst_video::VideoOverlayComposition>,
|
composition: Option<gst_video::VideoOverlayComposition>,
|
||||||
left_alignment: i32,
|
left_alignment: i32,
|
||||||
attach: bool,
|
attach: bool,
|
||||||
|
selected_field: Option<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for State {
|
impl Default for State {
|
||||||
|
@ -56,6 +73,7 @@ impl Default for State {
|
||||||
composition: None,
|
composition: None,
|
||||||
left_alignment: 0,
|
left_alignment: 0,
|
||||||
attach: false,
|
attach: false,
|
||||||
|
selected_field: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,6 +84,7 @@ pub struct Cea608Overlay {
|
||||||
srcpad: gst::Pad,
|
srcpad: gst::Pad,
|
||||||
sinkpad: gst::Pad,
|
sinkpad: gst::Pad,
|
||||||
state: Mutex<State>,
|
state: Mutex<State>,
|
||||||
|
settings: Mutex<Settings>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cea608Overlay {
|
impl Cea608Overlay {
|
||||||
|
@ -297,6 +316,112 @@ impl Cea608Overlay {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decode_cc_data(
|
||||||
|
&self,
|
||||||
|
pad: &gst::Pad,
|
||||||
|
element: &super::Cea608Overlay,
|
||||||
|
state: &mut State,
|
||||||
|
data: &[u8],
|
||||||
|
) {
|
||||||
|
if data.len() % 3 != 0 {
|
||||||
|
gst_warning!(CAT, "cc_data length is not a multiple of 3, truncating");
|
||||||
|
}
|
||||||
|
|
||||||
|
for triple in data.chunks_exact(3) {
|
||||||
|
let cc_valid = (triple[0] & 0x04) == 0x04;
|
||||||
|
let cc_type = triple[0] & 0x03;
|
||||||
|
|
||||||
|
if cc_valid {
|
||||||
|
if cc_type == 0x00 || cc_type == 0x01 {
|
||||||
|
if state.selected_field.is_none() {
|
||||||
|
state.selected_field = Some(cc_type);
|
||||||
|
gst_info!(
|
||||||
|
CAT,
|
||||||
|
obj: element,
|
||||||
|
"Selected field {} automatically",
|
||||||
|
cc_type
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if Some(cc_type) == state.selected_field {
|
||||||
|
match state
|
||||||
|
.caption_frame
|
||||||
|
.decode((triple[1] as u16) << 8 | triple[2] as u16, 0.0)
|
||||||
|
{
|
||||||
|
Ok(Status::Ready) => {
|
||||||
|
let text = match state.caption_frame.to_text(true) {
|
||||||
|
Ok(text) => text,
|
||||||
|
Err(_) => {
|
||||||
|
gst_error!(
|
||||||
|
CAT,
|
||||||
|
obj: pad,
|
||||||
|
"Failed to convert caption frame to text"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.overlay_text(element, &text, state);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_s334_1a(
|
||||||
|
&self,
|
||||||
|
pad: &gst::Pad,
|
||||||
|
element: &super::Cea608Overlay,
|
||||||
|
state: &mut State,
|
||||||
|
data: &[u8],
|
||||||
|
) {
|
||||||
|
if data.len() % 3 != 0 {
|
||||||
|
gst_warning!(CAT, "cc_data length is not a multiple of 3, truncating");
|
||||||
|
}
|
||||||
|
|
||||||
|
for triple in data.chunks_exact(3) {
|
||||||
|
let cc_type = triple[0] & 0x01;
|
||||||
|
if state.selected_field.is_none() {
|
||||||
|
state.selected_field = Some(cc_type);
|
||||||
|
gst_info!(
|
||||||
|
CAT,
|
||||||
|
obj: element,
|
||||||
|
"Selected field {} automatically",
|
||||||
|
cc_type
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if Some(cc_type) == state.selected_field {
|
||||||
|
match state
|
||||||
|
.caption_frame
|
||||||
|
.decode((triple[1] as u16) << 8 | triple[2] as u16, 0.0)
|
||||||
|
{
|
||||||
|
Ok(Status::Ready) => {
|
||||||
|
let text = match state.caption_frame.to_text(true) {
|
||||||
|
Ok(text) => text,
|
||||||
|
Err(_) => {
|
||||||
|
gst_error!(
|
||||||
|
CAT,
|
||||||
|
obj: pad,
|
||||||
|
"Failed to convert caption frame to text"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.overlay_text(element, &text, state);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn sink_chain(
|
fn sink_chain(
|
||||||
&self,
|
&self,
|
||||||
pad: &gst::Pad,
|
pad: &gst::Pad,
|
||||||
|
@ -312,7 +437,21 @@ impl Cea608Overlay {
|
||||||
}
|
}
|
||||||
|
|
||||||
for meta in buffer.iter_meta::<gst_video::VideoCaptionMeta>() {
|
for meta in buffer.iter_meta::<gst_video::VideoCaptionMeta>() {
|
||||||
if meta.get_caption_type() == gst_video::VideoCaptionType::Cea608Raw {
|
if meta.get_caption_type() == gst_video::VideoCaptionType::Cea708Cdp {
|
||||||
|
match extract_cdp(meta.get_data()) {
|
||||||
|
Ok(data) => {
|
||||||
|
self.decode_cc_data(pad, element, &mut state, data);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
gst_warning!(CAT, "{}", &e.to_string());
|
||||||
|
gst::element_warning!(element, gst::StreamError::Decode, [&e.to_string()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if meta.get_caption_type() == gst_video::VideoCaptionType::Cea708Raw {
|
||||||
|
self.decode_cc_data(pad, element, &mut state, meta.get_data());
|
||||||
|
} else if meta.get_caption_type() == gst_video::VideoCaptionType::Cea608S3341a {
|
||||||
|
self.decode_s334_1a(pad, element, &mut state, meta.get_data());
|
||||||
|
} else if meta.get_caption_type() == gst_video::VideoCaptionType::Cea608Raw {
|
||||||
let data = meta.get_data();
|
let data = meta.get_data();
|
||||||
assert!(data.len() % 2 == 0);
|
assert!(data.len() % 2 == 0);
|
||||||
for i in 0..data.len() / 2 {
|
for i in 0..data.len() / 2 {
|
||||||
|
@ -436,11 +575,59 @@ impl ObjectSubclass for Cea608Overlay {
|
||||||
srcpad,
|
srcpad,
|
||||||
sinkpad,
|
sinkpad,
|
||||||
state: Mutex::new(State::default()),
|
state: Mutex::new(State::default()),
|
||||||
|
settings: Mutex::new(Settings::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectImpl for Cea608Overlay {
|
impl ObjectImpl for Cea608Overlay {
|
||||||
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||||
|
vec![glib::ParamSpec::int(
|
||||||
|
"field",
|
||||||
|
"Field",
|
||||||
|
"The field to render the caption for when available, (-1=automatic)",
|
||||||
|
-1,
|
||||||
|
1,
|
||||||
|
DEFAULT_FIELD,
|
||||||
|
glib::ParamFlags::READWRITE | gst::PARAM_FLAG_MUTABLE_PLAYING,
|
||||||
|
)]
|
||||||
|
});
|
||||||
|
|
||||||
|
PROPERTIES.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_property(
|
||||||
|
&self,
|
||||||
|
_obj: &Self::Type,
|
||||||
|
_id: usize,
|
||||||
|
value: &glib::Value,
|
||||||
|
pspec: &glib::ParamSpec,
|
||||||
|
) {
|
||||||
|
match pspec.get_name() {
|
||||||
|
"field" => {
|
||||||
|
let mut settings = self.settings.lock().unwrap();
|
||||||
|
let mut state = self.state.lock().unwrap();
|
||||||
|
|
||||||
|
settings.field = value.get_some().expect("type checked upstream");
|
||||||
|
state.selected_field = match settings.field {
|
||||||
|
-1 => None,
|
||||||
|
val => Some(val as u8),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||||
|
match pspec.get_name() {
|
||||||
|
"field" => {
|
||||||
|
let settings = self.settings.lock().unwrap();
|
||||||
|
settings.field.to_value()
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
fn constructed(&self, obj: &Self::Type) {
|
fn constructed(&self, obj: &Self::Type) {
|
||||||
self.parent_constructed(obj);
|
self.parent_constructed(obj);
|
||||||
|
|
||||||
|
@ -504,6 +691,11 @@ impl ElementImpl for Cea608Overlay {
|
||||||
// Reset the whole state
|
// Reset the whole state
|
||||||
let mut state = self.state.lock().unwrap();
|
let mut state = self.state.lock().unwrap();
|
||||||
*state = State::default();
|
*state = State::default();
|
||||||
|
let settings = self.settings.lock().unwrap();
|
||||||
|
state.selected_field = match settings.field {
|
||||||
|
-1 => None,
|
||||||
|
val => Some(val as u8),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue