From 78d7cbd7dd32fb24e23d3c4bcf3607644c0e5c0a Mon Sep 17 00:00:00 2001 From: Mathieu Duponchelle Date: Tue, 26 Oct 2021 01:11:35 +0200 Subject: [PATCH] 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. --- video/closedcaption/src/cea608overlay/imp.rs | 64 +++++++++++++++++++- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/video/closedcaption/src/cea608overlay/imp.rs b/video/closedcaption/src/cea608overlay/imp.rs index bd337652..d69d4a5a 100644 --- a/video/closedcaption/src/cea608overlay/imp.rs +++ b/video/closedcaption/src/cea608overlay/imp.rs @@ -45,6 +45,7 @@ const DEFAULT_BLACK_BACKGROUND: bool = false; struct Settings { field: i32, black_background: bool, + timeout: Option, } impl Default for Settings { @@ -52,6 +53,7 @@ impl Default for Settings { Settings { field: DEFAULT_FIELD, black_background: DEFAULT_BLACK_BACKGROUND, + timeout: gst::ClockTime::NONE, } } } @@ -64,6 +66,7 @@ struct State { left_alignment: i32, attach: bool, selected_field: Option, + last_cc_pts: Option, } impl Default for State { @@ -76,6 +79,7 @@ impl Default for State { left_alignment: 0, attach: false, selected_field: None, + last_cc_pts: gst::ClockTime::NONE, } } } @@ -331,6 +335,7 @@ impl Cea608Overlay { element: &super::Cea608Overlay, state: &mut State, data: &[u8], + pts: gst::ClockTime, ) { if data.len() % 3 != 0 { 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.reset_timeout(state, pts); } } else { break; @@ -385,6 +392,7 @@ impl Cea608Overlay { element: &super::Cea608Overlay, state: &mut State, data: &[u8], + pts: gst::ClockTime, ) { if data.len() % 3 != 0 { 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.reset_timeout(state, pts); } } } + fn reset_timeout(&self, state: &mut State, pts: gst::ClockTime) { + state.last_cc_pts = Some(pts); + } + fn sink_chain( &self, pad: &gst::Pad, @@ -429,6 +443,11 @@ impl Cea608Overlay { ) -> Result { 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(); if self.srcpad.check_reconfigure() { @@ -443,7 +462,7 @@ impl Cea608Overlay { if meta.caption_type() == gst_video::VideoCaptionType::Cea708Cdp { match extract_cdp(meta.data()) { Ok(data) => { - self.decode_cc_data(pad, element, &mut state, data); + self.decode_cc_data(pad, element, &mut state, data, pts); } Err(e) => { gst_warning!(CAT, "{}", &e.to_string()); @@ -451,9 +470,9 @@ impl Cea608Overlay { } } } 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 { - 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 { let data = meta.data(); assert!(data.len() % 2 == 0); @@ -476,6 +495,18 @@ impl Cea608Overlay { 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, 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"); 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!(), } } @@ -641,6 +691,14 @@ impl ObjectImpl for Cea608Overlay { let settings = self.settings.lock().unwrap(); 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!(), } }