// Copyright (C) 2019 Guillaume Desmottes // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. // // SPDX-License-Identifier: MIT OR Apache-2.0 use gst::format::Bytes; use gst::glib; use gst::subclass::prelude::*; use gst_base::prelude::*; use gst_base::subclass::prelude::*; use once_cell::sync::Lazy; use crate::constants::{ CDG_COMMAND, CDG_HEIGHT, CDG_MASK, CDG_PACKET_PERIOD, CDG_PACKET_SIZE, CDG_WIDTH, }; const CDG_CMD_MEMORY_PRESET: u8 = 1; const CDG_CMD_MEMORY_LOAD_COLOR_TABLE_1: u8 = 30; const CDG_CMD_MEMORY_LOAD_COLOR_TABLE_2: u8 = 31; #[derive(Default)] pub struct CdgParse; static CAT: Lazy = Lazy::new(|| { gst::DebugCategory::new( "cdgparse", gst::DebugColorFlags::empty(), Some("CDG parser"), ) }); #[glib::object_subclass] impl ObjectSubclass for CdgParse { const NAME: &'static str = "GstCdgParse"; type Type = super::CdgParse; type ParentType = gst_base::BaseParse; } impl ObjectImpl for CdgParse {} impl GstObjectImpl for CdgParse {} impl ElementImpl for CdgParse { fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { static ELEMENT_METADATA: Lazy = Lazy::new(|| { gst::subclass::ElementMetadata::new( "CDG parser", "Codec/Parser/Video", "CDG parser", "Guillaume Desmottes ", ) }); Some(&*ELEMENT_METADATA) } fn pad_templates() -> &'static [gst::PadTemplate] { static PAD_TEMPLATES: Lazy> = Lazy::new(|| { let sink_caps = gst::Caps::builder("video/x-cdg").build(); let sink_pad_template = gst::PadTemplate::new( "sink", gst::PadDirection::Sink, gst::PadPresence::Always, &sink_caps, ) .unwrap(); 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(); 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() } } fn bytes_to_time(bytes: Bytes) -> gst::ClockTime { let nb = bytes / CDG_PACKET_SIZE as u64; nb.mul_div_round(*gst::ClockTime::SECOND, CDG_PACKET_PERIOD) .unwrap() .nseconds() } fn time_to_bytes(time: gst::ClockTime) -> Bytes { time.nseconds() .mul_div_round( CDG_PACKET_PERIOD * CDG_PACKET_SIZE as u64, *gst::ClockTime::SECOND, ) .unwrap() .bytes() } impl BaseParseImpl for CdgParse { fn start(&self) -> Result<(), gst::ErrorMessage> { self.instance().set_min_frame_size(CDG_PACKET_SIZE as u32); /* Set duration */ let mut query = gst::query::Duration::new(gst::Format::Bytes); if self.instance().src_pad().query(&mut query) { let size = query.result(); let bytes: Option = size.try_into().unwrap(); self.instance().set_duration(bytes.map(bytes_to_time), 0); } Ok(()) } fn handle_frame( &self, mut frame: gst_base::BaseParseFrame, ) -> Result<(gst::FlowSuccess, u32), gst::FlowError> { if self.instance().src_pad().current_caps().is_none() { // Set src pad caps 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(); self.instance() .src_pad() .push_event(gst::event::Caps::new(&src_caps)); } // Scan for CDG instruction let input = frame.buffer().unwrap(); let skip = { let map = input.map_readable().map_err(|_| { gst::element_imp_error!( self, 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) .unwrap_or_else(|| input.size()) // skip the whole buffer as u32 }; if skip != 0 { // Skip to the start of the CDG packet return Ok((gst::FlowSuccess::Ok, skip)); } let (keyframe, header) = { let map = input.map_readable().map_err(|_| { gst::element_imp_error!( self, gst::CoreError::Failed, ["Failed to map input buffer readable"] ); gst::FlowError::Error })?; let data = map.as_slice(); 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), } }; let pts = bytes_to_time(frame.offset().bytes()); let buffer = frame.buffer_mut().unwrap(); buffer.set_pts(pts); if !keyframe { buffer.set_flags(gst::BufferFlags::DELTA_UNIT); } if header { buffer.set_flags(gst::BufferFlags::HEADER); } gst::debug!(CAT, imp: self, "Found frame pts={}", pts); self.instance() .finish_frame(frame, CDG_PACKET_SIZE as u32)?; Ok((gst::FlowSuccess::Ok, skip)) } fn convert( &self, src_val: impl FormattedValue, dest_format: gst::Format, ) -> Option { let src_val = src_val.into(); match (src_val, dest_format) { (gst::GenericFormattedValue::Bytes(bytes), gst::Format::Time) => { Some(bytes.map(bytes_to_time).into()) } (gst::GenericFormattedValue::Time(time), gst::Format::Bytes) => { Some(time.map(time_to_bytes).into()) } _ => None, } } }