cea608overlay: expose timeout property

C.9 Automatic Caption Erasure (Preferred)

[...]

Some manufacturers have suggested building automatic timeout into their
decoders. They propose that if no data are received for the selected caption
channel within a given time, the decoder should automatically erase the
caption. Such erasure may supersede the intentions of the caption service
providers and institute one maximum display time for all captioning services.

If such a timeout is deemed necessary, however, the time limit should be no less
than 16 seconds, an amount of time said by caption service providers to be longer
than their most enduring caption. It is preferred, when automatic caption erasure
is used in a decoder, that only displayed memory be erased, since some caption
service providers may, contrary to recommended practice (see Section B.8.3), send
pop-on style caption data to non-displayed memory more than 16 seconds before
sending the EOC command which causes the caption to display.
This commit is contained in:
Mathieu Duponchelle 2021-10-26 01:11:35 +02:00
parent 44b4a4eb7e
commit 78d7cbd7dd

View file

@ -45,6 +45,7 @@ const DEFAULT_BLACK_BACKGROUND: bool = false;
struct Settings { struct Settings {
field: i32, field: i32,
black_background: bool, black_background: bool,
timeout: Option<gst::ClockTime>,
} }
impl Default for Settings { impl Default for Settings {
@ -52,6 +53,7 @@ impl Default for Settings {
Settings { Settings {
field: DEFAULT_FIELD, field: DEFAULT_FIELD,
black_background: DEFAULT_BLACK_BACKGROUND, black_background: DEFAULT_BLACK_BACKGROUND,
timeout: gst::ClockTime::NONE,
} }
} }
} }
@ -64,6 +66,7 @@ struct State {
left_alignment: i32, left_alignment: i32,
attach: bool, attach: bool,
selected_field: Option<u8>, selected_field: Option<u8>,
last_cc_pts: Option<gst::ClockTime>,
} }
impl Default for State { impl Default for State {
@ -76,6 +79,7 @@ impl Default for State {
left_alignment: 0, left_alignment: 0,
attach: false, attach: false,
selected_field: None, selected_field: None,
last_cc_pts: gst::ClockTime::NONE,
} }
} }
} }
@ -331,6 +335,7 @@ impl Cea608Overlay {
element: &super::Cea608Overlay, element: &super::Cea608Overlay,
state: &mut State, state: &mut State,
data: &[u8], data: &[u8],
pts: gst::ClockTime,
) { ) {
if data.len() % 3 != 0 { if data.len() % 3 != 0 {
gst_warning!(CAT, "cc_data length is not a multiple of 3, truncating"); gst_warning!(CAT, "cc_data length is not a multiple of 3, truncating");
@ -371,6 +376,8 @@ impl Cea608Overlay {
self.overlay_text(element, &text, state); self.overlay_text(element, &text, state);
} }
self.reset_timeout(state, pts);
} }
} else { } else {
break; break;
@ -385,6 +392,7 @@ impl Cea608Overlay {
element: &super::Cea608Overlay, element: &super::Cea608Overlay,
state: &mut State, state: &mut State,
data: &[u8], data: &[u8],
pts: gst::ClockTime,
) { ) {
if data.len() % 3 != 0 { if data.len() % 3 != 0 {
gst_warning!(CAT, "cc_data length is not a multiple of 3, truncating"); gst_warning!(CAT, "cc_data length is not a multiple of 3, truncating");
@ -417,10 +425,16 @@ impl Cea608Overlay {
self.overlay_text(element, &text, state); self.overlay_text(element, &text, state);
} }
self.reset_timeout(state, pts);
} }
} }
} }
fn reset_timeout(&self, state: &mut State, pts: gst::ClockTime) {
state.last_cc_pts = Some(pts);
}
fn sink_chain( fn sink_chain(
&self, &self,
pad: &gst::Pad, pad: &gst::Pad,
@ -429,6 +443,11 @@ impl Cea608Overlay {
) -> Result<gst::FlowSuccess, gst::FlowError> { ) -> Result<gst::FlowSuccess, gst::FlowError> {
gst_log!(CAT, obj: pad, "Handling buffer {:?}", buffer); gst_log!(CAT, obj: pad, "Handling buffer {:?}", buffer);
let pts = buffer.pts().ok_or_else(|| {
gst_error!(CAT, obj: pad, "Require timestamped buffers");
gst::FlowError::Error
})?;
let mut state = self.state.lock().unwrap(); let mut state = self.state.lock().unwrap();
if self.srcpad.check_reconfigure() { if self.srcpad.check_reconfigure() {
@ -443,7 +462,7 @@ impl Cea608Overlay {
if meta.caption_type() == gst_video::VideoCaptionType::Cea708Cdp { if meta.caption_type() == gst_video::VideoCaptionType::Cea708Cdp {
match extract_cdp(meta.data()) { match extract_cdp(meta.data()) {
Ok(data) => { Ok(data) => {
self.decode_cc_data(pad, element, &mut state, data); self.decode_cc_data(pad, element, &mut state, data, pts);
} }
Err(e) => { Err(e) => {
gst_warning!(CAT, "{}", &e.to_string()); gst_warning!(CAT, "{}", &e.to_string());
@ -451,9 +470,9 @@ impl Cea608Overlay {
} }
} }
} else if meta.caption_type() == gst_video::VideoCaptionType::Cea708Raw { } else if meta.caption_type() == gst_video::VideoCaptionType::Cea708Raw {
self.decode_cc_data(pad, element, &mut state, meta.data()); self.decode_cc_data(pad, element, &mut state, meta.data(), pts);
} else if meta.caption_type() == gst_video::VideoCaptionType::Cea608S3341a { } else if meta.caption_type() == gst_video::VideoCaptionType::Cea608S3341a {
self.decode_s334_1a(pad, element, &mut state, meta.data()); self.decode_s334_1a(pad, element, &mut state, meta.data(), pts);
} else if meta.caption_type() == gst_video::VideoCaptionType::Cea608Raw { } else if meta.caption_type() == gst_video::VideoCaptionType::Cea608Raw {
let data = meta.data(); let data = meta.data();
assert!(data.len() % 2 == 0); assert!(data.len() % 2 == 0);
@ -476,6 +495,18 @@ impl Cea608Overlay {
self.overlay_text(element, &text, &mut state); self.overlay_text(element, &text, &mut state);
} }
self.reset_timeout(&mut state, pts);
}
}
}
if let Some(timeout) = self.settings.lock().unwrap().timeout {
if let Some(interval) = pts.opt_saturating_sub(state.last_cc_pts) {
if interval > timeout {
gst_info!(CAT, obj: element, "Reached timeout, clearing overlay");
state.composition.take();
state.last_cc_pts.take();
} }
} }
} }
@ -596,6 +627,15 @@ impl ObjectImpl for Cea608Overlay {
DEFAULT_BLACK_BACKGROUND, DEFAULT_BLACK_BACKGROUND,
glib::ParamFlags::READWRITE | gst::PARAM_FLAG_MUTABLE_PLAYING, glib::ParamFlags::READWRITE | gst::PARAM_FLAG_MUTABLE_PLAYING,
), ),
glib::ParamSpec::new_uint64(
"timeout",
"Timeout",
"Duration after which to erase overlay when no cc data has arrived for the selected field",
gst::ClockTime::from_seconds(16).nseconds(),
u64::MAX,
u64::MAX,
glib::ParamFlags::READWRITE | gst::PARAM_FLAG_MUTABLE_PLAYING,
),
] ]
}); });
@ -627,6 +667,16 @@ impl ObjectImpl for Cea608Overlay {
settings.black_background = value.get().expect("type checked upstream"); settings.black_background = value.get().expect("type checked upstream");
let _ = state.layout.take(); let _ = state.layout.take();
} }
"timeout" => {
let mut settings = self.settings.lock().unwrap();
let timeout = value.get().expect("type checked upstream");
settings.timeout = match timeout {
u64::MAX => gst::ClockTime::NONE,
_ => Some(gst::ClockTime::from_nseconds(timeout)),
};
}
_ => unimplemented!(), _ => unimplemented!(),
} }
} }
@ -641,6 +691,14 @@ impl ObjectImpl for Cea608Overlay {
let settings = self.settings.lock().unwrap(); let settings = self.settings.lock().unwrap();
settings.black_background.to_value() settings.black_background.to_value()
} }
"timeout" => {
let settings = self.settings.lock().unwrap();
if let Some(timeout) = settings.timeout {
timeout.nseconds().to_value()
} else {
u64::MAX.to_value()
}
}
_ => unimplemented!(), _ => unimplemented!(),
} }
} }