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.
|
|
|
|
|
|
|
|
use glib::subclass::prelude::*;
|
2020-11-22 17:21:45 +00:00
|
|
|
use gst::format::Bytes;
|
2020-12-20 18:43:45 +00:00
|
|
|
use gst::gst_debug;
|
2019-05-26 14:12:16 +00:00
|
|
|
use gst::subclass::prelude::*;
|
|
|
|
use gst::SECOND_VAL;
|
|
|
|
use gst_base::prelude::*;
|
|
|
|
use gst_base::subclass::prelude::*;
|
2020-11-22 17:21:45 +00:00
|
|
|
use once_cell::sync::Lazy;
|
2019-06-07 10:55:55 +00:00
|
|
|
use std::convert::TryInto;
|
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;
|
|
|
|
type Instance = gst::subclass::ElementInstanceStruct<Self>;
|
|
|
|
}
|
|
|
|
|
2020-07-26 15:33:14 +00:00
|
|
|
impl ObjectImpl for CdgParse {}
|
2019-05-26 14:12:16 +00:00
|
|
|
|
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(|| {
|
|
|
|
let sink_caps = gst::Caps::new_simple("video/x-cdg", &[]);
|
|
|
|
let sink_pad_template = gst::PadTemplate::new(
|
|
|
|
"sink",
|
|
|
|
gst::PadDirection::Sink,
|
|
|
|
gst::PadPresence::Always,
|
|
|
|
&sink_caps,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let src_caps = gst::Caps::new_simple(
|
|
|
|
"video/x-cdg",
|
|
|
|
&[
|
|
|
|
("width", &(CDG_WIDTH as i32)),
|
|
|
|
("height", &(CDG_HEIGHT as i32)),
|
|
|
|
("framerate", &gst::Fraction::new(0, 1)),
|
|
|
|
("parsed", &true),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
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 {
|
|
|
|
match bytes {
|
|
|
|
Bytes(Some(bytes)) => {
|
|
|
|
let nb = bytes / CDG_PACKET_SIZE as u64;
|
|
|
|
gst::ClockTime(nb.mul_div_round(SECOND_VAL, CDG_PACKET_PERIOD))
|
|
|
|
}
|
|
|
|
Bytes(None) => gst::ClockTime::none(),
|
|
|
|
}
|
2019-05-26 14:12:16 +00:00
|
|
|
}
|
|
|
|
|
2019-06-07 10:36:41 +00:00
|
|
|
fn time_to_bytes(time: gst::ClockTime) -> Bytes {
|
2019-05-26 14:12:16 +00:00
|
|
|
match time.nseconds() {
|
|
|
|
Some(time) => {
|
|
|
|
let bytes = time.mul_div_round(CDG_PACKET_PERIOD * CDG_PACKET_SIZE as u64, SECOND_VAL);
|
2019-06-07 10:36:41 +00:00
|
|
|
Bytes(bytes)
|
2019-05-26 14:12:16 +00:00
|
|
|
}
|
2019-06-07 10:36:41 +00:00
|
|
|
None => Bytes(None),
|
2019-05-26 14:12:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BaseParseImpl for CdgParse {
|
2020-11-15 13:50:12 +00:00
|
|
|
fn start(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> {
|
2019-05-26 14:12:16 +00:00
|
|
|
element.set_min_frame_size(CDG_PACKET_SIZE as u32);
|
|
|
|
|
|
|
|
/* Set duration */
|
2020-06-24 22:33:57 +00:00
|
|
|
let mut query = gst::query::Duration::new(gst::Format::Bytes);
|
2019-05-26 14:12:16 +00:00
|
|
|
let pad = element.get_src_pad();
|
|
|
|
if pad.query(&mut query) {
|
2019-06-07 10:55:55 +00:00
|
|
|
let size = query.get_result();
|
|
|
|
let duration = bytes_to_time(size.try_into().unwrap());
|
2019-05-26 14:12:16 +00:00
|
|
|
element.set_duration(duration, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_frame(
|
|
|
|
&self,
|
2020-11-15 13:50:12 +00:00
|
|
|
element: &Self::Type,
|
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> {
|
|
|
|
let pad = element.get_src_pad();
|
|
|
|
if pad.get_current_caps().is_none() {
|
|
|
|
// Set src pad caps
|
|
|
|
let src_caps = gst::Caps::new_simple(
|
|
|
|
"video/x-cdg",
|
|
|
|
&[
|
|
|
|
("width", &(CDG_WIDTH as i32)),
|
|
|
|
("height", &(CDG_HEIGHT as i32)),
|
|
|
|
("framerate", &gst::Fraction::new(0, 1)),
|
|
|
|
("parsed", &true),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
|
2020-06-24 22:33:57 +00:00
|
|
|
pad.push_event(gst::event::Caps::new(&src_caps));
|
2019-05-26 14:12:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Scan for CDG instruction
|
|
|
|
let input = frame.get_buffer().unwrap();
|
|
|
|
let skip = {
|
2019-12-18 05:50:10 +00:00
|
|
|
let map = input.map_readable().map_err(|_| {
|
2020-12-20 18:43:45 +00:00
|
|
|
gst::element_error!(
|
2019-05-26 14:12:16 +00:00
|
|
|
element,
|
|
|
|
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)
|
2019-09-07 07:42:07 +00:00
|
|
|
.unwrap_or_else(|| input.get_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(|_| {
|
2020-12-20 18:43:45 +00:00
|
|
|
gst::element_error!(
|
2019-05-26 14:12:16 +00:00
|
|
|
element,
|
|
|
|
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
|
|
|
};
|
|
|
|
|
2019-06-07 10:55:55 +00:00
|
|
|
let pts = bytes_to_time(Bytes(Some(frame.get_offset())));
|
2019-12-18 16:55:17 +00:00
|
|
|
let buffer = frame.get_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
|
|
|
|
2019-10-31 22:34:21 +00:00
|
|
|
gst_debug!(CAT, obj: element, "Found frame pts={}", pts);
|
2019-05-26 14:12:16 +00:00
|
|
|
|
|
|
|
element.finish_frame(frame, CDG_PACKET_SIZE as u32)?;
|
|
|
|
|
|
|
|
Ok((gst::FlowSuccess::Ok, skip))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn convert<V: Into<gst::GenericFormattedValue>>(
|
|
|
|
&self,
|
2020-11-15 13:50:12 +00:00
|
|
|
_element: &Self::Type,
|
2019-05-26 14:12:16 +00:00
|
|
|
src_val: V,
|
|
|
|
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) => {
|
|
|
|
Some(bytes_to_time(bytes).into())
|
|
|
|
}
|
2019-05-26 14:12:16 +00:00
|
|
|
(gst::GenericFormattedValue::Time(time), gst::Format::Bytes) => {
|
2019-06-07 10:36:41 +00:00
|
|
|
Some(time_to_bytes(time).into())
|
2019-05-26 14:12:16 +00:00
|
|
|
}
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|