cea608tott: use our own CEA-608 frame handling instead of libcaption

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1517>
This commit is contained in:
Matthew Waters 2024-03-25 10:43:26 +11:00
parent d8fe1c64f1
commit 2575013faa
3 changed files with 80 additions and 37 deletions

View file

@ -6,15 +6,17 @@
// //
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use cea608_types::Cea608State;
use gst::glib; use gst::glib;
use gst::prelude::*; use gst::prelude::*;
use gst::subclass::prelude::*; use gst::subclass::prelude::*;
use crate::caption_frame::{CaptionFrame, Status};
use atomic_refcell::AtomicRefCell; use atomic_refcell::AtomicRefCell;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use crate::cea608utils::Cea608Frame;
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
enum Format { enum Format {
Srt, Srt,
@ -25,7 +27,8 @@ enum Format {
struct State { struct State {
format: Option<Format>, format: Option<Format>,
wrote_header: bool, wrote_header: bool,
caption_frame: CaptionFrame, state: Cea608State,
frame: Cea608Frame,
previous_text: Option<(gst::ClockTime, String)>, previous_text: Option<(gst::ClockTime, String)>,
index: u64, index: u64,
} }
@ -35,7 +38,8 @@ impl Default for State {
State { State {
format: None, format: None,
wrote_header: false, wrote_header: false,
caption_frame: CaptionFrame::default(), state: Cea608State::default(),
frame: Cea608Frame::new(),
previous_text: None, previous_text: None,
index: 1, index: 1,
} }
@ -79,8 +83,6 @@ impl Cea608ToTt {
gst::FlowError::Error gst::FlowError::Error
})?; })?;
let pts = (buffer_pts.nseconds() as f64) / 1_000_000_000.0;
let data = buffer.map_readable().map_err(|_| { let data = buffer.map_readable().map_err(|_| {
gst::error!(CAT, obj: pad, "Can't map buffer readable"); gst::error!(CAT, obj: pad, "Can't map buffer readable");
@ -93,39 +95,48 @@ impl Cea608ToTt {
return Ok(gst::FlowSuccess::Ok); return Ok(gst::FlowSuccess::Ok);
} }
let previous_text = match state let previous_text = {
.caption_frame match state.state.decode([data[0], data[1]]) {
.decode((data[0] as u16) << 8 | data[1] as u16, pts) Err(e) => {
{ gst::error!(CAT, obj: pad, "Failed to decode closed caption packet: {e:?}");
Ok(Status::Ok) => return Ok(gst::FlowSuccess::Ok), return Ok(gst::FlowSuccess::Ok);
Err(_) => { }
gst::error!(CAT, obj: pad, "Failed to decode closed caption packet"); Ok(Some(cea608)) => {
return Ok(gst::FlowSuccess::Ok); gst::trace!(CAT, obj: pad, "received {:x?} cea608: {cea608:?}", [data[0], data[1]]);
} if state.frame.push_code(cea608) {
Ok(Status::Clear) => { let text = state.frame.get_text();
gst::debug!(CAT, obj: pad, "Clearing previous closed caption packet"); gst::trace!(CAT, obj: pad, "generated text: {text}");
state.previous_text.take() if text.is_empty() {
} state.previous_text.take()
Ok(Status::Ready) => { } else if state.frame.mode() == Some(cea608_types::Mode::PaintOn)
gst::debug!(CAT, obj: pad, "Have new closed caption packet"); || matches!(
let text = match state.caption_frame.to_text(false) { cea608,
Ok(text) => text, cea608_types::Cea608::EraseDisplay(_)
Err(_) => { | cea608_types::Cea608::Backspace(_)
gst::error!(CAT, obj: pad, "Failed to convert caption frame to text"); | cea608_types::Cea608::EndOfCaption(_)
| cea608_types::Cea608::DeleteToEndOfRow(_)
)
{
// only in some specific circumstances do we want to actually change
// our generated text
state.previous_text.replace((buffer_pts, text))
} else {
return Ok(gst::FlowSuccess::Ok);
}
} else {
// no change, nothing to do
return Ok(gst::FlowSuccess::Ok); return Ok(gst::FlowSuccess::Ok);
} }
}; }
Ok(None) => {
state.previous_text.replace((buffer_pts, text)) return Ok(gst::FlowSuccess::Ok);
}
} }
}; };
let previous_text = match previous_text { let Some(previous_text) = previous_text else {
Some(previous_text) => previous_text, gst::debug!(CAT, obj: pad, "Have no previous text");
None => { return Ok(gst::FlowSuccess::Ok);
gst::debug!(CAT, obj: pad, "Have no previous text");
return Ok(gst::FlowSuccess::Ok);
}
}; };
let duration = buffer_pts.saturating_sub(previous_text.0); let duration = buffer_pts.saturating_sub(previous_text.0);
@ -317,7 +328,8 @@ impl Cea608ToTt {
} }
EventView::FlushStop(..) => { EventView::FlushStop(..) => {
let mut state = self.state.borrow_mut(); let mut state = self.state.borrow_mut();
state.caption_frame = CaptionFrame::default(); state.frame = Cea608Frame::new();
state.state = Cea608State::default();
state.previous_text = None; state.previous_text = None;
} }
EventView::Eos(..) => { EventView::Eos(..) => {

View file

@ -408,6 +408,37 @@ impl Cea608Frame {
pub fn iter(&self) -> impl Iterator<Item = &Cea608Line> { pub fn iter(&self) -> impl Iterator<Item = &Cea608Line> {
self.display_lines.iter() self.display_lines.iter()
} }
pub fn get_text(&self) -> String {
let mut text = String::new();
for (i, line) in self.iter().enumerate() {
let mut seen_non_space = false;
if i != 0 {
text.push('\r');
text.push('\n');
}
for (_i, c) in line.display_iter() {
match c {
Cea608Cell::Empty | Cea608Cell::MidRow(_) => {
if seen_non_space {
text.push(' ')
}
}
Cea608Cell::Char(c) => {
if *c != ' ' {
seen_non_space = true;
}
text.push(*c);
}
}
}
}
text
}
pub fn mode(&self) -> Option<cea608_types::Mode> {
self.mode
}
} }
#[derive(Debug)] #[derive(Debug)]

View file

@ -43,17 +43,17 @@ fn test_parse() {
( (
18_985_633_333.nseconds(), 18_985_633_333.nseconds(),
1_234_566_667.nseconds(), 1_234_566_667.nseconds(),
"Yes, Im supporting\r\nDonald Trump.", "Yes, I'm supporting\r\nDonald Trump.",
), ),
( (
20_220_200_000.nseconds(), 20_220_200_000.nseconds(),
2_168_833_333.nseconds(), 2_168_833_333.nseconds(),
"Im doing so as enthusiastically\r\nas I can,", "I'm doing so as enthusiastically\r\nas I can,",
), ),
( (
22_389_033_333.nseconds(), 22_389_033_333.nseconds(),
2_235_566_667.nseconds(), 2_235_566_667.nseconds(),
"even the fact I think\r\nhes a terrible human being.", "even the fact I think\r\nhe's a terrible human being.",
), ),
]; ];