2019-05-26 14:12:16 +00:00
|
|
|
// Copyright (C) 2019 Guillaume Desmottes <guillaume.desmottes@collabora.com>
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
|
// except according to those terms.
|
2022-01-15 19:18:47 +00:00
|
|
|
//
|
2022-03-14 08:22:53 +00:00
|
|
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
2019-05-26 14:12:16 +00:00
|
|
|
|
2020-11-22 17:21:45 +00:00
|
|
|
use gst::format::Bytes;
|
2021-06-03 18:20:54 +00:00
|
|
|
use gst::glib;
|
2019-05-26 14:12:16 +00:00
|
|
|
use gst::subclass::prelude::*;
|
|
|
|
use gst_base::prelude::*;
|
|
|
|
use gst_base::subclass::prelude::*;
|
2020-11-22 17:21:45 +00:00
|
|
|
use once_cell::sync::Lazy;
|
2019-05-26 14:12:16 +00:00
|
|
|
|
2019-06-05 05:19:27 +00:00
|
|
|
use crate::constants::{
|
|
|
|
CDG_COMMAND, CDG_HEIGHT, CDG_MASK, CDG_PACKET_PERIOD, CDG_PACKET_SIZE, CDG_WIDTH,
|
|
|
|
};
|
2019-05-26 14:12:16 +00:00
|
|
|
|
|
|
|
const CDG_CMD_MEMORY_PRESET: u8 = 1;
|
2019-11-03 15:54:54 +00:00
|
|
|
const CDG_CMD_MEMORY_LOAD_COLOR_TABLE_1: u8 = 30;
|
|
|
|
const CDG_CMD_MEMORY_LOAD_COLOR_TABLE_2: u8 = 31;
|
2019-05-26 14:12:16 +00:00
|
|
|
|
2021-03-07 16:22:24 +00:00
|
|
|
#[derive(Default)]
|
2020-11-15 13:50:12 +00:00
|
|
|
pub struct CdgParse;
|
2019-10-31 22:34:21 +00:00
|
|
|
|
2020-11-22 17:21:45 +00:00
|
|
|
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
|
|
|
gst::DebugCategory::new(
|
2019-10-31 22:34:21 +00:00
|
|
|
"cdgparse",
|
|
|
|
gst::DebugColorFlags::empty(),
|
|
|
|
Some("CDG parser"),
|
2020-11-22 17:21:45 +00:00
|
|
|
)
|
|
|
|
});
|
2019-05-26 14:12:16 +00:00
|
|
|
|
2021-03-07 16:22:24 +00:00
|
|
|
#[glib::object_subclass]
|
2019-05-26 14:12:16 +00:00
|
|
|
impl ObjectSubclass for CdgParse {
|
|
|
|
const NAME: &'static str = "CdgParse";
|
2020-11-15 13:50:12 +00:00
|
|
|
type Type = super::CdgParse;
|
2019-05-26 14:12:16 +00:00
|
|
|
type ParentType = gst_base::BaseParse;
|
|
|
|
}
|
|
|
|
|
2020-07-26 15:33:14 +00:00
|
|
|
impl ObjectImpl for CdgParse {}
|
2019-05-26 14:12:16 +00:00
|
|
|
|
2021-10-23 08:57:31 +00:00
|
|
|
impl GstObjectImpl for CdgParse {}
|
|
|
|
|
2021-01-21 18:21:29 +00:00
|
|
|
impl ElementImpl for CdgParse {
|
|
|
|
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
|
|
|
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
|
|
|
gst::subclass::ElementMetadata::new(
|
|
|
|
"CDG parser",
|
|
|
|
"Codec/Parser/Video",
|
|
|
|
"CDG parser",
|
|
|
|
"Guillaume Desmottes <guillaume.desmottes@collabora.com>",
|
|
|
|
)
|
|
|
|
});
|
|
|
|
|
|
|
|
Some(&*ELEMENT_METADATA)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn pad_templates() -> &'static [gst::PadTemplate] {
|
|
|
|
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
2021-11-06 07:34:10 +00:00
|
|
|
let sink_caps = gst::Caps::builder("video/x-cdg").build();
|
2021-01-21 18:21:29 +00:00
|
|
|
let sink_pad_template = gst::PadTemplate::new(
|
|
|
|
"sink",
|
|
|
|
gst::PadDirection::Sink,
|
|
|
|
gst::PadPresence::Always,
|
|
|
|
&sink_caps,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
2021-11-06 07:34:10 +00:00
|
|
|
let src_caps = gst::Caps::builder("video/x-cdg")
|
|
|
|
.field("width", CDG_WIDTH as i32)
|
|
|
|
.field("height", CDG_HEIGHT as i32)
|
|
|
|
.field("framerate", gst::Fraction::new(0, 1))
|
|
|
|
.field("parsed", true)
|
|
|
|
.build();
|
2021-01-21 18:21:29 +00:00
|
|
|
let src_pad_template = gst::PadTemplate::new(
|
|
|
|
"src",
|
|
|
|
gst::PadDirection::Src,
|
|
|
|
gst::PadPresence::Always,
|
|
|
|
&src_caps,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
vec![src_pad_template, sink_pad_template]
|
|
|
|
});
|
|
|
|
|
|
|
|
PAD_TEMPLATES.as_ref()
|
|
|
|
}
|
|
|
|
}
|
2019-05-26 14:12:16 +00:00
|
|
|
|
2019-06-07 10:55:55 +00:00
|
|
|
fn bytes_to_time(bytes: Bytes) -> gst::ClockTime {
|
2022-10-10 11:55:32 +00:00
|
|
|
let nb = *bytes / CDG_PACKET_SIZE as u64;
|
2021-06-04 17:06:24 +00:00
|
|
|
gst::ClockTime::from_nseconds(
|
|
|
|
nb.mul_div_round(*gst::ClockTime::SECOND, CDG_PACKET_PERIOD)
|
|
|
|
.unwrap(),
|
|
|
|
)
|
2019-05-26 14:12:16 +00:00
|
|
|
}
|
|
|
|
|
2019-06-07 10:36:41 +00:00
|
|
|
fn time_to_bytes(time: gst::ClockTime) -> Bytes {
|
2022-10-10 11:55:32 +00:00
|
|
|
time.nseconds()
|
|
|
|
.mul_div_round(
|
|
|
|
CDG_PACKET_PERIOD * CDG_PACKET_SIZE as u64,
|
|
|
|
*gst::ClockTime::SECOND,
|
|
|
|
)
|
|
|
|
.unwrap()
|
|
|
|
* Bytes::ONE
|
2019-05-26 14:12:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl BaseParseImpl for CdgParse {
|
2022-10-09 13:06:59 +00:00
|
|
|
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
|
|
|
self.instance().set_min_frame_size(CDG_PACKET_SIZE as u32);
|
2019-05-26 14:12:16 +00:00
|
|
|
|
|
|
|
/* Set duration */
|
2020-06-24 22:33:57 +00:00
|
|
|
let mut query = gst::query::Duration::new(gst::Format::Bytes);
|
2022-10-09 13:06:59 +00:00
|
|
|
if self.instance().src_pad().query(&mut query) {
|
2021-04-12 12:49:54 +00:00
|
|
|
let size = query.result();
|
2021-06-04 17:06:24 +00:00
|
|
|
let bytes: Option<Bytes> = size.try_into().unwrap();
|
2022-10-09 13:06:59 +00:00
|
|
|
self.instance().set_duration(bytes.map(bytes_to_time), 0);
|
2019-05-26 14:12:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_frame(
|
|
|
|
&self,
|
2019-12-18 16:55:17 +00:00
|
|
|
mut frame: gst_base::BaseParseFrame,
|
2019-05-26 14:12:16 +00:00
|
|
|
) -> Result<(gst::FlowSuccess, u32), gst::FlowError> {
|
2022-10-09 13:06:59 +00:00
|
|
|
if self.instance().src_pad().current_caps().is_none() {
|
2019-05-26 14:12:16 +00:00
|
|
|
// Set src pad caps
|
2021-11-06 07:34:10 +00:00
|
|
|
let src_caps = gst::Caps::builder("video/x-cdg")
|
|
|
|
.field("width", CDG_WIDTH as i32)
|
|
|
|
.field("height", CDG_HEIGHT as i32)
|
|
|
|
.field("framerate", gst::Fraction::new(0, 1))
|
|
|
|
.field("parsed", true)
|
|
|
|
.build();
|
2019-05-26 14:12:16 +00:00
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
self.instance()
|
|
|
|
.src_pad()
|
|
|
|
.push_event(gst::event::Caps::new(&src_caps));
|
2019-05-26 14:12:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Scan for CDG instruction
|
2021-04-12 12:49:54 +00:00
|
|
|
let input = frame.buffer().unwrap();
|
2019-05-26 14:12:16 +00:00
|
|
|
let skip = {
|
2019-12-18 05:50:10 +00:00
|
|
|
let map = input.map_readable().map_err(|_| {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::element_imp_error!(
|
|
|
|
self,
|
2019-05-26 14:12:16 +00:00
|
|
|
gst::CoreError::Failed,
|
|
|
|
["Failed to map input buffer readable"]
|
|
|
|
);
|
|
|
|
gst::FlowError::Error
|
|
|
|
})?;
|
|
|
|
let data = map.as_slice();
|
|
|
|
|
|
|
|
data.iter()
|
|
|
|
.enumerate()
|
|
|
|
.find(|(_, byte)| (*byte & CDG_MASK == CDG_COMMAND))
|
|
|
|
.map(|(i, _)| i)
|
2021-04-12 12:49:54 +00:00
|
|
|
.unwrap_or_else(|| input.size()) // skip the whole buffer
|
2019-05-26 14:12:16 +00:00
|
|
|
as u32
|
|
|
|
};
|
|
|
|
|
|
|
|
if skip != 0 {
|
|
|
|
// Skip to the start of the CDG packet
|
|
|
|
return Ok((gst::FlowSuccess::Ok, skip));
|
|
|
|
}
|
|
|
|
|
2019-11-03 15:54:54 +00:00
|
|
|
let (keyframe, header) = {
|
2019-12-18 05:50:10 +00:00
|
|
|
let map = input.map_readable().map_err(|_| {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::element_imp_error!(
|
|
|
|
self,
|
2019-05-26 14:12:16 +00:00
|
|
|
gst::CoreError::Failed,
|
|
|
|
["Failed to map input buffer readable"]
|
|
|
|
);
|
|
|
|
gst::FlowError::Error
|
|
|
|
})?;
|
|
|
|
let data = map.as_slice();
|
|
|
|
|
2019-11-03 15:54:54 +00:00
|
|
|
match data[1] & CDG_MASK {
|
|
|
|
// consider memory preset as keyframe as it clears the screen
|
|
|
|
CDG_CMD_MEMORY_PRESET => (true, false),
|
|
|
|
// mark palette commands as headers
|
|
|
|
CDG_CMD_MEMORY_LOAD_COLOR_TABLE_1 | CDG_CMD_MEMORY_LOAD_COLOR_TABLE_2 => {
|
|
|
|
(false, true)
|
|
|
|
}
|
|
|
|
_ => (false, false),
|
|
|
|
}
|
2019-05-26 14:12:16 +00:00
|
|
|
};
|
|
|
|
|
2022-10-10 11:55:32 +00:00
|
|
|
let pts = bytes_to_time(frame.offset() * Bytes::ONE);
|
2021-04-12 12:49:54 +00:00
|
|
|
let buffer = frame.buffer_mut().unwrap();
|
2019-05-26 14:12:16 +00:00
|
|
|
buffer.set_pts(pts);
|
|
|
|
|
|
|
|
if !keyframe {
|
|
|
|
buffer.set_flags(gst::BufferFlags::DELTA_UNIT);
|
|
|
|
}
|
2019-11-03 15:54:54 +00:00
|
|
|
if header {
|
|
|
|
buffer.set_flags(gst::BufferFlags::HEADER);
|
|
|
|
}
|
2019-05-26 14:12:16 +00:00
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::debug!(CAT, imp: self, "Found frame pts={}", pts);
|
2019-05-26 14:12:16 +00:00
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
self.instance()
|
|
|
|
.finish_frame(frame, CDG_PACKET_SIZE as u32)?;
|
2019-05-26 14:12:16 +00:00
|
|
|
|
|
|
|
Ok((gst::FlowSuccess::Ok, skip))
|
|
|
|
}
|
|
|
|
|
2022-07-04 16:04:11 +00:00
|
|
|
fn convert(
|
2019-05-26 14:12:16 +00:00
|
|
|
&self,
|
2022-07-04 16:04:11 +00:00
|
|
|
src_val: impl FormattedValue,
|
2019-05-26 14:12:16 +00:00
|
|
|
dest_format: gst::Format,
|
|
|
|
) -> Option<gst::GenericFormattedValue> {
|
|
|
|
let src_val = src_val.into();
|
|
|
|
|
|
|
|
match (src_val, dest_format) {
|
2019-06-07 10:55:55 +00:00
|
|
|
(gst::GenericFormattedValue::Bytes(bytes), gst::Format::Time) => {
|
2021-06-04 17:06:24 +00:00
|
|
|
Some(bytes.map(bytes_to_time).into())
|
2019-06-07 10:55:55 +00:00
|
|
|
}
|
2019-05-26 14:12:16 +00:00
|
|
|
(gst::GenericFormattedValue::Time(time), gst::Format::Bytes) => {
|
2021-06-04 17:06:24 +00:00
|
|
|
Some(time.map(time_to_bytes).into())
|
2019-05-26 14:12:16 +00:00
|
|
|
}
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|