gst-plugins-rs/generic/threadshare/examples/standalone/src/imp.rs
Sebastian Dröge 4ad101b53b Use once_cell crate directly again
The glib crate does not depend on it anymore and also does not re-export
it anymore.

Also switch some usages of OnceCell to OnceLock from std.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1441>
2024-01-31 18:07:57 +02:00

495 lines
16 KiB
Rust

// Copyright (C) 2022 François Laignel <fengalin@free.fr>
//
// 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
// <https://mozilla.org/MPL/2.0/>.
//
// 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<gst::DebugCategory> = 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<u32>,
}
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<timer::Interval>,
is_main_elem: bool,
push_period: gst::ClockTime,
need_initial_events: bool,
num_buffers: Option<u32>,
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::<u32>(), rand::random::<u32>());
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::<gst::format::Time>::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<Settings>,
}
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<Vec<glib::ParamSpec>> = 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::<Option<String>>()
.unwrap()
.unwrap_or_else(|| DEFAULT_CONTEXT.into());
}
"context-wait" => {
settings.context_wait = Duration::from_millis(value.get::<u32>().unwrap().into());
}
"push-period" => {
let value: u64 = value.get::<u32>().unwrap().into();
settings.push_period = value.mseconds();
}
"main-elem" => {
settings.is_main_elem = value.get::<bool>().unwrap();
}
"num-buffers" => {
let value = value.get::<i32>().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<gst::subclass::ElementMetadata> = Lazy::new(|| {
gst::subclass::ElementMetadata::new(
"Thread-sharing standalone test source",
"Source/Test",
"Thread-sharing standalone test source",
"François Laignel <fengalin@free.fr>",
)
});
Some(&*ELEMENT_METADATA)
}
fn pad_templates() -> &'static [gst::PadTemplate] {
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = 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::StateChangeSuccess, gst::StateChangeError> {
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)
}
}