mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-05-10 20:32:43 +00:00
4ad101b53b
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>
495 lines
16 KiB
Rust
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)
|
|
}
|
|
}
|