// Copyright (C) 2022 François Laignel // // This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at // . // // SPDX-License-Identifier: MPL-2.0 use futures::future::BoxFuture; use futures::prelude::*; use gst::glib; use gst::prelude::*; use gst::subclass::prelude::*; use once_cell::sync::Lazy; use std::sync::Mutex; use std::time::Duration; use gstthreadshare::runtime::prelude::*; use gstthreadshare::runtime::{task, timer, Context, PadSrc, Task}; static CAT: Lazy = Lazy::new(|| { gst::DebugCategory::new( super::ELEMENT_NAME, gst::DebugColorFlags::empty(), Some("Thread-sharing standalone test src"), ) }); const DEFAULT_CONTEXT: &str = ""; const DEFAULT_CONTEXT_WAIT: Duration = Duration::from_millis(20); const DEFAULT_PUSH_PERIOD: gst::ClockTime = gst::ClockTime::from_mseconds(20); const DEFAULT_NUM_BUFFERS: i32 = 50 * 100; #[derive(Debug, Clone)] struct Settings { context: String, context_wait: Duration, push_period: gst::ClockTime, is_main_elem: bool, num_buffers: Option, } impl Default for Settings { fn default() -> Self { Settings { context: DEFAULT_CONTEXT.into(), context_wait: DEFAULT_CONTEXT_WAIT, push_period: DEFAULT_PUSH_PERIOD, is_main_elem: false, num_buffers: Some(DEFAULT_NUM_BUFFERS as u32), } } } #[derive(Clone, Debug)] struct TestSrcPadHandler; impl PadSrcHandler for TestSrcPadHandler { type ElementImpl = TestSrc; } #[derive(Debug)] struct SrcTask { elem: super::TestSrc, buffer_pool: gst::BufferPool, timer: Option, is_main_elem: bool, push_period: gst::ClockTime, need_initial_events: bool, num_buffers: Option, buffer_count: u32, } impl SrcTask { fn new(elem: super::TestSrc) -> Self { let buffer_pool = gst::BufferPool::new(); let mut pool_config = buffer_pool.config(); pool_config .as_mut() .set_params(Some(&gst::Caps::builder("foo/bar").build()), 10, 10, 10); buffer_pool.set_config(pool_config).unwrap(); SrcTask { elem, buffer_pool, timer: None, is_main_elem: false, push_period: gst::ClockTime::ZERO, need_initial_events: true, num_buffers: Some(DEFAULT_NUM_BUFFERS as u32), buffer_count: 0, } } } impl TaskImpl for SrcTask { type Item = (); fn prepare(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { let imp = self.elem.imp(); let settings = imp.settings.lock().unwrap(); self.is_main_elem = settings.is_main_elem; log_or_trace!(CAT, self.is_main_elem, imp: imp, "Preparing Task"); self.push_period = settings.push_period; self.num_buffers = settings.num_buffers; future::ok(()).boxed() } fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { async move { log_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Starting Task"); if self.need_initial_events { let imp = self.elem.imp(); debug_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Pushing initial events"); let stream_id = format!("{:08x}{:08x}", rand::random::(), rand::random::()); let stream_start_evt = gst::event::StreamStart::builder(&stream_id) .group_id(gst::GroupId::next()) .build(); imp.src_pad.push_event(stream_start_evt).await; imp.src_pad .push_event(gst::event::Caps::new( &gst::Caps::builder("foo/bar").build(), )) .await; let segment_evt = gst::event::Segment::new(&gst::FormattedSegment::::new()); imp.src_pad.push_event(segment_evt).await; self.need_initial_events = false; } self.timer = Some( timer::interval_delayed_by( // Delay first buffer push so as to let others start. Duration::from_secs(2), self.push_period.into(), ) .expect("push period must be greater than 0"), ); self.buffer_count = 0; self.buffer_pool.set_active(true).unwrap(); Ok(()) } .boxed() } fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { log_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Stopping Task"); self.buffer_pool.set_active(false).unwrap(); self.timer = None; self.need_initial_events = true; future::ok(()).boxed() } fn try_next(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { async move { log_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Awaiting timer"); self.timer.as_mut().unwrap().next().await; log_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Timer ticked"); Ok(()) } .boxed() } fn handle_item(&mut self, _: ()) -> BoxFuture<'_, Result<(), gst::FlowError>> { async move { let buffer = self .buffer_pool .acquire_buffer(None) .map(|mut buffer| { { let buffer = buffer.get_mut().unwrap(); let rtime = self.elem.current_running_time().unwrap(); buffer.set_dts(rtime); } buffer }) .map_err(|err| { gst::error!(CAT, obj: self.elem, "Failed to acquire buffer {err}"); err })?; debug_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Forwarding buffer"); self.elem.imp().src_pad.push(buffer).await?; log_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Successfully pushed buffer"); self.buffer_count += 1; if self.num_buffers.opt_eq(self.buffer_count) == Some(true) { return Err(gst::FlowError::Eos); } Ok(()) } .boxed() } fn handle_loop_error(&mut self, err: gst::FlowError) -> BoxFuture<'_, task::Trigger> { async move { match err { gst::FlowError::Eos => { debug_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Pushing EOS"); let imp = self.elem.imp(); if !imp.src_pad.push_event(gst::event::Eos::new()).await { gst::error!(CAT, imp: imp, "Error pushing EOS"); } task::Trigger::Stop } gst::FlowError::Flushing => { debug_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Flushing"); task::Trigger::FlushStart } err => { gst::error!(CAT, obj: self.elem, "Got error {err}"); gst::element_error!( &self.elem, gst::StreamError::Failed, ("Internal data stream error"), ["streaming stopped, reason {}", err] ); task::Trigger::Error } } } .boxed() } } #[derive(Debug)] pub struct TestSrc { src_pad: PadSrc, task: Task, settings: Mutex, } impl TestSrc { fn prepare(&self) -> Result<(), gst::ErrorMessage> { let is_main_elem = self.settings.lock().unwrap().is_main_elem; debug_or_trace!(CAT, is_main_elem, imp: self, "Preparing"); let settings = self.settings.lock().unwrap(); let ts_ctx = Context::acquire(&settings.context, settings.context_wait).map_err(|err| { gst::error_msg!( gst::ResourceError::OpenRead, ["Failed to acquire Context: {}", err] ) })?; drop(settings); self.task .prepare(SrcTask::new(self.obj().clone()), ts_ctx) .block_on()?; debug_or_trace!(CAT, is_main_elem, imp: self, "Prepared"); Ok(()) } fn unprepare(&self) { let is_main_elem = self.settings.lock().unwrap().is_main_elem; debug_or_trace!(CAT, is_main_elem, imp: self, "Unpreparing"); self.task.unprepare().block_on().unwrap(); debug_or_trace!(CAT, is_main_elem, imp: self, "Unprepared"); } fn stop(&self) -> Result<(), gst::ErrorMessage> { let is_main_elem = self.settings.lock().unwrap().is_main_elem; debug_or_trace!(CAT, is_main_elem, imp: self, "Stopping"); self.task.stop().block_on()?; debug_or_trace!(CAT, is_main_elem, imp: self, "Stopped"); Ok(()) } fn start(&self) -> Result<(), gst::ErrorMessage> { let is_main_elem = self.settings.lock().unwrap().is_main_elem; debug_or_trace!(CAT, is_main_elem, imp: self, "Starting"); self.task.start().block_on()?; debug_or_trace!(CAT, is_main_elem, imp: self, "Started"); Ok(()) } fn pause(&self) -> Result<(), gst::ErrorMessage> { let is_main_elem = self.settings.lock().unwrap().is_main_elem; debug_or_trace!(CAT, is_main_elem, imp: self, "Pausing"); self.task.pause().block_on()?; debug_or_trace!(CAT, is_main_elem, imp: self, "Paused"); Ok(()) } } #[glib::object_subclass] impl ObjectSubclass for TestSrc { const NAME: &'static str = "StandaloneTestSrc"; type Type = super::TestSrc; type ParentType = gst::Element; fn with_class(klass: &Self::Class) -> Self { Self { src_pad: PadSrc::new( gst::Pad::from_template(&klass.pad_template("src").unwrap()), TestSrcPadHandler, ), task: Task::default(), settings: Default::default(), } } } impl ObjectImpl for TestSrc { fn properties() -> &'static [glib::ParamSpec] { static PROPERTIES: Lazy> = Lazy::new(|| { vec![ glib::ParamSpecString::builder("context") .nick("Context") .blurb("Context name to share threads with") .default_value(Some(DEFAULT_CONTEXT)) .build(), glib::ParamSpecUInt::builder("context-wait") .nick("Context Wait") .blurb("Throttle poll loop to run at most once every this many ms") .maximum(1000) .default_value(DEFAULT_CONTEXT_WAIT.as_millis() as u32) .build(), glib::ParamSpecUInt::builder("push-period") .nick("Buffer Push Period") .blurb("Push a new buffer every this many ms") .default_value(DEFAULT_PUSH_PERIOD.mseconds() as u32) .build(), glib::ParamSpecBoolean::builder("main-elem") .nick("Main Element") .blurb("Declare this element as the main one") .write_only() .build(), glib::ParamSpecInt::builder("num-buffers") .nick("Num Buffers") .blurb("Number of buffers to output before sending EOS (-1 = unlimited)") .minimum(-1i32) .default_value(DEFAULT_NUM_BUFFERS) .build(), ] }); PROPERTIES.as_ref() } fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { let mut settings = self.settings.lock().unwrap(); match pspec.name() { "context" => { settings.context = value .get::>() .unwrap() .unwrap_or_else(|| DEFAULT_CONTEXT.into()); } "context-wait" => { settings.context_wait = Duration::from_millis(value.get::().unwrap().into()); } "push-period" => { let value: u64 = value.get::().unwrap().into(); settings.push_period = value.mseconds(); } "main-elem" => { settings.is_main_elem = value.get::().unwrap(); } "num-buffers" => { let value = value.get::().unwrap(); settings.num_buffers = if value > 0 { Some(value as u32) } else { None }; } _ => unimplemented!(), } } fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { let settings = self.settings.lock().unwrap(); match pspec.name() { "context" => settings.context.to_value(), "context-wait" => (settings.context_wait.as_millis() as u32).to_value(), "push-period" => (settings.push_period.mseconds() as u32).to_value(), "main-elem" => settings.is_main_elem.to_value(), "num-buffers" => settings .num_buffers .and_then(|val| val.try_into().ok()) .unwrap_or(-1i32) .to_value(), _ => unimplemented!(), } } fn constructed(&self) { self.parent_constructed(); let obj = self.obj(); obj.add_pad(self.src_pad.gst_pad()).unwrap(); obj.set_element_flags(gst::ElementFlags::SOURCE); } } impl GstObjectImpl for TestSrc {} impl ElementImpl for TestSrc { fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { static ELEMENT_METADATA: Lazy = Lazy::new(|| { gst::subclass::ElementMetadata::new( "Thread-sharing standalone test source", "Source/Test", "Thread-sharing standalone test source", "François Laignel ", ) }); Some(&*ELEMENT_METADATA) } fn pad_templates() -> &'static [gst::PadTemplate] { static PAD_TEMPLATES: Lazy> = Lazy::new(|| { let caps = gst::Caps::new_any(); let src_pad_template = gst::PadTemplate::new( "src", gst::PadDirection::Src, gst::PadPresence::Always, &caps, ) .unwrap(); vec![src_pad_template] }); PAD_TEMPLATES.as_ref() } fn change_state( &self, transition: gst::StateChange, ) -> Result { gst::trace!(CAT, imp: self, "Changing state {transition:?}"); match transition { gst::StateChange::NullToReady => { self.prepare().map_err(|err| { self.post_error_message(err); gst::StateChangeError })?; } gst::StateChange::PlayingToPaused => { self.pause().map_err(|_| gst::StateChangeError)?; } gst::StateChange::ReadyToNull => { self.unprepare(); } _ => (), } let mut success = self.parent_change_state(transition)?; match transition { gst::StateChange::ReadyToPaused => { success = gst::StateChangeSuccess::NoPreroll; } gst::StateChange::PausedToPlaying => { self.start().map_err(|_| gst::StateChangeError)?; } gst::StateChange::PlayingToPaused => { success = gst::StateChangeSuccess::NoPreroll; } gst::StateChange::PausedToReady => { self.stop().map_err(|_| gst::StateChangeError)?; } _ => (), } Ok(success) } }