From 4829e31191213eda72e2eecdcbf16f7168ba81a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 15 Nov 2020 10:08:32 +0200 Subject: tutorial: Update for subclassing API changes --- tutorial/src/identity.rs | 273 ------------- tutorial/src/identity/imp.rs | 262 +++++++++++++ tutorial/src/identity/mod.rs | 33 ++ tutorial/src/progressbin.rs | 270 ------------- tutorial/src/progressbin/imp.rs | 242 ++++++++++++ tutorial/src/progressbin/mod.rs | 52 +++ tutorial/src/rgb2gray.rs | 576 --------------------------- tutorial/src/rgb2gray/imp.rs | 566 +++++++++++++++++++++++++++ tutorial/src/rgb2gray/mod.rs | 33 ++ tutorial/src/sinesrc.rs | 844 ---------------------------------------- tutorial/src/sinesrc/imp.rs | 827 +++++++++++++++++++++++++++++++++++++++ tutorial/src/sinesrc/mod.rs | 33 ++ tutorial/tutorial-1.md | 91 +++-- tutorial/tutorial-2.md | 73 ++-- 14 files changed, 2157 insertions(+), 2018 deletions(-) delete mode 100644 tutorial/src/identity.rs create mode 100644 tutorial/src/identity/imp.rs create mode 100644 tutorial/src/identity/mod.rs delete mode 100644 tutorial/src/progressbin.rs create mode 100644 tutorial/src/progressbin/imp.rs create mode 100644 tutorial/src/progressbin/mod.rs delete mode 100644 tutorial/src/rgb2gray.rs create mode 100644 tutorial/src/rgb2gray/imp.rs create mode 100644 tutorial/src/rgb2gray/mod.rs delete mode 100644 tutorial/src/sinesrc.rs create mode 100644 tutorial/src/sinesrc/imp.rs create mode 100644 tutorial/src/sinesrc/mod.rs (limited to 'tutorial') diff --git a/tutorial/src/identity.rs b/tutorial/src/identity.rs deleted file mode 100644 index e77b44fdd..000000000 --- a/tutorial/src/identity.rs +++ /dev/null @@ -1,273 +0,0 @@ -// Copyright (C) 2018 Sebastian Dröge -// -// 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. - -use glib::prelude::*; -use glib::subclass; -use glib::subclass::prelude::*; -use gst::prelude::*; -use gst::subclass::prelude::*; - -use once_cell::sync::Lazy; - -// Struct containing all the element data -struct Identity { - srcpad: gst::Pad, - sinkpad: gst::Pad, -} - -static CAT: Lazy = Lazy::new(|| { - gst::DebugCategory::new( - "rsidentity", - gst::DebugColorFlags::empty(), - Some("Identity Element"), - ) -}); - -impl Identity { - // Called whenever a new buffer is passed to our sink pad. Here buffers should be processed and - // whenever some output buffer is available have to push it out of the source pad. - // Here we just pass through all buffers directly - // - // See the documentation of gst::Buffer and gst::BufferRef to see what can be done with - // buffers. - fn sink_chain( - &self, - pad: &gst::Pad, - _element: &gst::Element, - buffer: gst::Buffer, - ) -> Result { - gst_log!(CAT, obj: pad, "Handling buffer {:?}", buffer); - self.srcpad.push(buffer) - } - - // Called whenever an event arrives on the sink pad. It has to be handled accordingly and in - // most cases has to be either passed to Pad::event_default() on this pad for default handling, - // or Pad::push_event() on all pads with the opposite direction for direct forwarding. - // Here we just pass through all events directly to the source pad. - // - // See the documentation of gst::Event and gst::EventRef to see what can be done with - // events, and especially the gst::EventView type for inspecting events. - fn sink_event(&self, pad: &gst::Pad, _element: &gst::Element, event: gst::Event) -> bool { - gst_log!(CAT, obj: pad, "Handling event {:?}", event); - self.srcpad.push_event(event) - } - - // Called whenever a query is sent to the sink pad. It has to be answered if the element can - // handle it, potentially by forwarding the query first to the peer pads of the pads with the - // opposite direction, or false has to be returned. Default handling can be achieved with - // Pad::query_default() on this pad and forwarding with Pad::peer_query() on the pads with the - // opposite direction. - // Here we just forward all queries directly to the source pad's peers. - // - // See the documentation of gst::Query and gst::QueryRef to see what can be done with - // queries, and especially the gst::QueryView type for inspecting and modifying queries. - fn sink_query( - &self, - pad: &gst::Pad, - _element: &gst::Element, - query: &mut gst::QueryRef, - ) -> bool { - gst_log!(CAT, obj: pad, "Handling query {:?}", query); - self.srcpad.peer_query(query) - } - - // Called whenever an event arrives on the source pad. It has to be handled accordingly and in - // most cases has to be either passed to Pad::event_default() on the same pad for default - // handling, or Pad::push_event() on all pads with the opposite direction for direct - // forwarding. - // Here we just pass through all events directly to the sink pad. - // - // See the documentation of gst::Event and gst::EventRef to see what can be done with - // events, and especially the gst::EventView type for inspecting events. - fn src_event(&self, pad: &gst::Pad, _element: &gst::Element, event: gst::Event) -> bool { - gst_log!(CAT, obj: pad, "Handling event {:?}", event); - self.sinkpad.push_event(event) - } - - // Called whenever a query is sent to the source pad. It has to be answered if the element can - // handle it, potentially by forwarding the query first to the peer pads of the pads with the - // opposite direction, or false has to be returned. Default handling can be achieved with - // Pad::query_default() on this pad and forwarding with Pad::peer_query() on the pads with the - // opposite direction. - // Here we just forward all queries directly to the sink pad's peers. - // - // See the documentation of gst::Query and gst::QueryRef to see what can be done with - // queries, and especially the gst::QueryView type for inspecting and modifying queries. - fn src_query( - &self, - pad: &gst::Pad, - _element: &gst::Element, - query: &mut gst::QueryRef, - ) -> bool { - gst_log!(CAT, obj: pad, "Handling query {:?}", query); - self.sinkpad.peer_query(query) - } -} - -// This trait registers our type with the GObject object system and -// provides the entry points for creating a new instance and setting -// up the class data -impl ObjectSubclass for Identity { - const NAME: &'static str = "RsIdentity"; - type ParentType = gst::Element; - type Instance = gst::subclass::ElementInstanceStruct; - type Class = subclass::simple::ClassStruct; - - // This macro provides some boilerplate. - glib_object_subclass!(); - - // Called when a new instance is to be created. We need to return an instance - // of our struct here and also get the class struct passed in case it's needed - fn with_class(klass: &subclass::simple::ClassStruct) -> Self { - // Create our two pads from the templates that were registered with - // the class and set all the functions on them. - // - // Each function is wrapped in catch_panic_pad_function(), which will - // - Catch panics from the pad functions and instead of aborting the process - // it will simply convert them into an error message and poison the element - // instance - // - Extract our Identity struct from the object instance and pass it to us - // - // Details about what each function is good for is next to each function definition - let templ = klass.get_pad_template("sink").unwrap(); - let sinkpad = gst::Pad::builder_with_template(&templ, Some("sink")) - .chain_function(|pad, parent, buffer| { - Identity::catch_panic_pad_function( - parent, - || Err(gst::FlowError::Error), - |identity, element| identity.sink_chain(pad, element, buffer), - ) - }) - .event_function(|pad, parent, event| { - Identity::catch_panic_pad_function( - parent, - || false, - |identity, element| identity.sink_event(pad, element, event), - ) - }) - .query_function(|pad, parent, query| { - Identity::catch_panic_pad_function( - parent, - || false, - |identity, element| identity.sink_query(pad, element, query), - ) - }) - .build(); - - let templ = klass.get_pad_template("src").unwrap(); - let srcpad = gst::Pad::builder_with_template(&templ, Some("src")) - .event_function(|pad, parent, event| { - Identity::catch_panic_pad_function( - parent, - || false, - |identity, element| identity.src_event(pad, element, event), - ) - }) - .query_function(|pad, parent, query| { - Identity::catch_panic_pad_function( - parent, - || false, - |identity, element| identity.src_query(pad, element, query), - ) - }) - .build(); - - // Return an instance of our struct and also include our debug category here. - // The debug category will be used later whenever we need to put something - // into the debug logs - Self { srcpad, sinkpad } - } - - // Called exactly once when registering the type. Used for - // setting up metadata for all instances, e.g. the name and - // classification and the pad templates with their caps. - // - // Actual instances can create pads based on those pad templates - // with a subset of the caps given here. - fn class_init(klass: &mut subclass::simple::ClassStruct) { - // Set the element specific metadata. This information is what - // is visible from gst-inspect-1.0 and can also be programatically - // retrieved from the gst::Registry after initial registration - // without having to load the plugin in memory. - klass.set_metadata( - "Identity", - "Generic", - "Does nothing with the data", - "Sebastian Dröge ", - ); - - // Create and add pad templates for our sink and source pad. These - // are later used for actually creating the pads and beforehand - // already provide information to GStreamer about all possible - // pads that could exist for this type. - - // Our element can accept any possible caps on both pads - let caps = gst::Caps::new_any(); - let src_pad_template = gst::PadTemplate::new( - "src", - gst::PadDirection::Src, - gst::PadPresence::Always, - &caps, - ) - .unwrap(); - klass.add_pad_template(src_pad_template); - - let sink_pad_template = gst::PadTemplate::new( - "sink", - gst::PadDirection::Sink, - gst::PadPresence::Always, - &caps, - ) - .unwrap(); - klass.add_pad_template(sink_pad_template); - } -} - -// Implementation of glib::Object virtual methods -impl ObjectImpl for Identity { - // Called right after construction of a new instance - fn constructed(&self, obj: &glib::Object) { - // Call the parent class' ::constructed() implementation first - self.parent_constructed(obj); - - // Here we actually add the pads we created in Identity::new() to the - // element so that GStreamer is aware of their existence. - let element = obj.downcast_ref::().unwrap(); - element.add_pad(&self.sinkpad).unwrap(); - element.add_pad(&self.srcpad).unwrap(); - } -} - -// Implementation of gst::Element virtual methods -impl ElementImpl for Identity { - // Called whenever the state of the element should be changed. This allows for - // starting up the element, allocating/deallocating resources or shutting down - // the element again. - fn change_state( - &self, - element: &gst::Element, - transition: gst::StateChange, - ) -> Result { - gst_trace!(CAT, obj: element, "Changing state {:?}", transition); - - // Call the parent class' implementation of ::change_state() - self.parent_change_state(element, transition) - } -} - -// Registers the type for our element, and then registers in GStreamer under -// the name "rsidentity" for being able to instantiate it via e.g. -// gst::ElementFactory::make(). -pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { - gst::Element::register( - Some(plugin), - "rsidentity", - gst::Rank::None, - Identity::get_type(), - ) -} diff --git a/tutorial/src/identity/imp.rs b/tutorial/src/identity/imp.rs new file mode 100644 index 000000000..a02a18388 --- /dev/null +++ b/tutorial/src/identity/imp.rs @@ -0,0 +1,262 @@ +// Copyright (C) 2018 Sebastian Dröge +// +// 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. + +use glib::subclass; +use glib::subclass::prelude::*; +use gst::prelude::*; +use gst::subclass::prelude::*; + +use once_cell::sync::Lazy; + +// This module contains the private implementation details of our element + +static CAT: Lazy = Lazy::new(|| { + gst::DebugCategory::new( + "rsidentity", + gst::DebugColorFlags::empty(), + Some("Identity Element"), + ) +}); + +// Struct containing all the element data +pub struct Identity { + srcpad: gst::Pad, + sinkpad: gst::Pad, +} + +impl Identity { + // Called whenever a new buffer is passed to our sink pad. Here buffers should be processed and + // whenever some output buffer is available have to push it out of the source pad. + // Here we just pass through all buffers directly + // + // See the documentation of gst::Buffer and gst::BufferRef to see what can be done with + // buffers. + fn sink_chain( + &self, + pad: &gst::Pad, + _element: &super::Identity, + buffer: gst::Buffer, + ) -> Result { + gst_log!(CAT, obj: pad, "Handling buffer {:?}", buffer); + self.srcpad.push(buffer) + } + + // Called whenever an event arrives on the sink pad. It has to be handled accordingly and in + // most cases has to be either passed to Pad::event_default() on this pad for default handling, + // or Pad::push_event() on all pads with the opposite direction for direct forwarding. + // Here we just pass through all events directly to the source pad. + // + // See the documentation of gst::Event and gst::EventRef to see what can be done with + // events, and especially the gst::EventView type for inspecting events. + fn sink_event(&self, pad: &gst::Pad, _element: &super::Identity, event: gst::Event) -> bool { + gst_log!(CAT, obj: pad, "Handling event {:?}", event); + self.srcpad.push_event(event) + } + + // Called whenever a query is sent to the sink pad. It has to be answered if the element can + // handle it, potentially by forwarding the query first to the peer pads of the pads with the + // opposite direction, or false has to be returned. Default handling can be achieved with + // Pad::query_default() on this pad and forwarding with Pad::peer_query() on the pads with the + // opposite direction. + // Here we just forward all queries directly to the source pad's peers. + // + // See the documentation of gst::Query and gst::QueryRef to see what can be done with + // queries, and especially the gst::QueryView type for inspecting and modifying queries. + fn sink_query( + &self, + pad: &gst::Pad, + _element: &super::Identity, + query: &mut gst::QueryRef, + ) -> bool { + gst_log!(CAT, obj: pad, "Handling query {:?}", query); + self.srcpad.peer_query(query) + } + + // Called whenever an event arrives on the source pad. It has to be handled accordingly and in + // most cases has to be either passed to Pad::event_default() on the same pad for default + // handling, or Pad::push_event() on all pads with the opposite direction for direct + // forwarding. + // Here we just pass through all events directly to the sink pad. + // + // See the documentation of gst::Event and gst::EventRef to see what can be done with + // events, and especially the gst::EventView type for inspecting events. + fn src_event(&self, pad: &gst::Pad, _element: &super::Identity, event: gst::Event) -> bool { + gst_log!(CAT, obj: pad, "Handling event {:?}", event); + self.sinkpad.push_event(event) + } + + // Called whenever a query is sent to the source pad. It has to be answered if the element can + // handle it, potentially by forwarding the query first to the peer pads of the pads with the + // opposite direction, or false has to be returned. Default handling can be achieved with + // Pad::query_default() on this pad and forwarding with Pad::peer_query() on the pads with the + // opposite direction. + // Here we just forward all queries directly to the sink pad's peers. + // + // See the documentation of gst::Query and gst::QueryRef to see what can be done with + // queries, and especially the gst::QueryView type for inspecting and modifying queries. + fn src_query( + &self, + pad: &gst::Pad, + _element: &super::Identity, + query: &mut gst::QueryRef, + ) -> bool { + gst_log!(CAT, obj: pad, "Handling query {:?}", query); + self.sinkpad.peer_query(query) + } +} + +// This trait registers our type with the GObject object system and +// provides the entry points for creating a new instance and setting +// up the class data +impl ObjectSubclass for Identity { + const NAME: &'static str = "RsIdentity"; + type Type = super::Identity; + type ParentType = gst::Element; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = subclass::simple::ClassStruct; + + // This macro provides some boilerplate. + glib_object_subclass!(); + + // Called when a new instance is to be created. We need to return an instance + // of our struct here and also get the class struct passed in case it's needed + fn with_class(klass: &Self::Class) -> Self { + // Create our two pads from the templates that were registered with + // the class and set all the functions on them. + // + // Each function is wrapped in catch_panic_pad_function(), which will + // - Catch panics from the pad functions and instead of aborting the process + // it will simply convert them into an error message and poison the element + // instance + // - Extract our Identity struct from the object instance and pass it to us + // + // Details about what each function is good for is next to each function definition + let templ = klass.get_pad_template("sink").unwrap(); + let sinkpad = gst::Pad::builder_with_template(&templ, Some("sink")) + .chain_function(|pad, parent, buffer| { + Identity::catch_panic_pad_function( + parent, + || Err(gst::FlowError::Error), + |identity, element| identity.sink_chain(pad, element, buffer), + ) + }) + .event_function(|pad, parent, event| { + Identity::catch_panic_pad_function( + parent, + || false, + |identity, element| identity.sink_event(pad, element, event), + ) + }) + .query_function(|pad, parent, query| { + Identity::catch_panic_pad_function( + parent, + || false, + |identity, element| identity.sink_query(pad, element, query), + ) + }) + .build(); + + let templ = klass.get_pad_template("src").unwrap(); + let srcpad = gst::Pad::builder_with_template(&templ, Some("src")) + .event_function(|pad, parent, event| { + Identity::catch_panic_pad_function( + parent, + || false, + |identity, element| identity.src_event(pad, element, event), + ) + }) + .query_function(|pad, parent, query| { + Identity::catch_panic_pad_function( + parent, + || false, + |identity, element| identity.src_query(pad, element, query), + ) + }) + .build(); + + // Return an instance of our struct and also include our debug category here. + // The debug category will be used later whenever we need to put something + // into the debug logs + Self { srcpad, sinkpad } + } + + // Called exactly once when registering the type. Used for + // setting up metadata for all instances, e.g. the name and + // classification and the pad templates with their caps. + // + // Actual instances can create pads based on those pad templates + // with a subset of the caps given here. + fn class_init(klass: &mut Self::Class) { + // Set the element specific metadata. This information is what + // is visible from gst-inspect-1.0 and can also be programatically + // retrieved from the gst::Registry after initial registration + // without having to load the plugin in memory. + klass.set_metadata( + "Identity", + "Generic", + "Does nothing with the data", + "Sebastian Dröge ", + ); + + // Create and add pad templates for our sink and source pad. These + // are later used for actually creating the pads and beforehand + // already provide information to GStreamer about all possible + // pads that could exist for this type. + + // Our element can accept any possible caps on both pads + let caps = gst::Caps::new_any(); + let src_pad_template = gst::PadTemplate::new( + "src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &caps, + ) + .unwrap(); + klass.add_pad_template(src_pad_template); + + let sink_pad_template = gst::PadTemplate::new( + "sink", + gst::PadDirection::Sink, + gst::PadPresence::Always, + &caps, + ) + .unwrap(); + klass.add_pad_template(sink_pad_template); + } +} + +// Implementation of glib::Object virtual methods +impl ObjectImpl for Identity { + // Called right after construction of a new instance + fn constructed(&self, obj: &Self::Type) { + // Call the parent class' ::constructed() implementation first + self.parent_constructed(obj); + + // Here we actually add the pads we created in Identity::new() to the + // element so that GStreamer is aware of their existence. + obj.add_pad(&self.sinkpad).unwrap(); + obj.add_pad(&self.srcpad).unwrap(); + } +} + +// Implementation of gst::Element virtual methods +impl ElementImpl for Identity { + // Called whenever the state of the element should be changed. This allows for + // starting up the element, allocating/deallocating resources or shutting down + // the element again. + fn change_state( + &self, + element: &Self::Type, + transition: gst::StateChange, + ) -> Result { + gst_trace!(CAT, obj: element, "Changing state {:?}", transition); + + // Call the parent class' implementation of ::change_state() + self.parent_change_state(element, transition) + } +} diff --git a/tutorial/src/identity/mod.rs b/tutorial/src/identity/mod.rs new file mode 100644 index 000000000..027d52a52 --- /dev/null +++ b/tutorial/src/identity/mod.rs @@ -0,0 +1,33 @@ +// Copyright (C) 2020 Sebastian Dröge +// +// 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. + +use glib::prelude::*; + +mod imp; + +// The public Rust wrapper type for our element +glib_wrapper! { + pub struct Identity(ObjectSubclass) @extends gst::Element, gst::Object; +} + +// GStreamer elements need to be thread-safe. For the private implementation this is automatically +// enforced but for the public wrapper type we need to specify this manually. +unsafe impl Send for Identity {} +unsafe impl Sync for Identity {} + +// Registers the type for our element, and then registers in GStreamer under +// the name "rsidentity" for being able to instantiate it via e.g. +// gst::ElementFactory::make(). +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "rsidentity", + gst::Rank::None, + Identity::static_type(), + ) +} diff --git a/tutorial/src/progressbin.rs b/tutorial/src/progressbin.rs deleted file mode 100644 index 35a9e650d..000000000 --- a/tutorial/src/progressbin.rs +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright (C) 2019 Sebastian Dröge -// -// 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. - -use glib::prelude::*; -use glib::subclass; -use glib::subclass::prelude::*; -use gst::prelude::*; -use gst::subclass::prelude::*; -use std::sync::Mutex; - -use once_cell::sync::Lazy; - -// This enum may be used to control what type of output the progressbin should produce. -// It also serves the secondary purpose of illustrating how to add enum-type properties -// to a plugin written in rust. -#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, GEnum)] -#[repr(u32)] -#[genum(type_name = "GstProgressBinOutput")] -pub(crate) enum ProgressBinOutput { - #[genum( - name = "Println: Outputs the progress using a println! macro.", - nick = "println" - )] - Println = 0, - #[genum( - name = "Debug Category: Outputs the progress as info logs under the element's debug category.", - nick = "debug-category" - )] - DebugCategory = 1, -} - -const DEFAULT_OUTPUT_TYPE: ProgressBinOutput = ProgressBinOutput::Println; - -static CAT: Lazy = Lazy::new(|| { - gst::DebugCategory::new( - "progressbin", - gst::DebugColorFlags::empty(), - Some("Rust Progress Reporter"), - ) -}); - -// Struct containing all the element data -struct ProgressBin { - progress: gst::Element, - srcpad: gst::GhostPad, - sinkpad: gst::GhostPad, - // We put the output_type property behind a mutex, as we want - // change it in the set_property function, which can be called - // from any thread. - output_type: Mutex, -} - -// Metadata for the element's properties -static PROPERTIES: [subclass::Property; 1] = [subclass::Property("output", |name| { - glib::ParamSpec::enum_( - name, - "Output", - "Defines the output type of the progressbin", - ProgressBinOutput::static_type(), - DEFAULT_OUTPUT_TYPE as i32, - glib::ParamFlags::READWRITE, - ) -})]; - -// This trait registers our type with the GObject object system and -// provides the entry points for creating a new instance and setting -// up the class data -impl ObjectSubclass for ProgressBin { - const NAME: &'static str = "RsProgressBin"; - type ParentType = gst::Bin; - type Instance = gst::subclass::ElementInstanceStruct; - type Class = subclass::simple::ClassStruct; - - // This macro provides some boilerplate. - glib_object_subclass!(); - - // Called when a new instance is to be created. We need to return an instance - // of our struct here and also get the class struct passed in case it's needed - fn with_class(klass: &subclass::simple::ClassStruct) -> Self { - // Create our two ghostpads from the templates that were registered with - // the class. We don't provide a target for them yet because we can only - // do so after the progressreport element was added to the bin. - // - // We do that and adding the pads inside glib::Object::constructed() later. - let templ = klass.get_pad_template("sink").unwrap(); - let sinkpad = gst::GhostPad::from_template(&templ, Some("sink")); - let templ = klass.get_pad_template("src").unwrap(); - let srcpad = gst::GhostPad::from_template(&templ, Some("src")); - - // Create the progressreport element. - let progress = gst::ElementFactory::make("progressreport", Some("progress")).unwrap(); - // Don't let progressreport print to stdout itself - progress.set_property("silent", &true).unwrap(); - - // Return an instance of our struct - Self { - progress, - srcpad, - sinkpad, - output_type: Mutex::new(ProgressBinOutput::Println), - } - } - - // Called exactly once when registering the type. Used for - // setting up metadata for all instances, e.g. the name and - // classification and the pad templates with their caps. - // - // Actual instances can create pads based on those pad templates - // with a subset of the caps given here. - fn class_init(klass: &mut subclass::simple::ClassStruct) { - // Set the element specific metadata. This information is what - // is visible from gst-inspect-1.0 and can also be programatically - // retrieved from the gst::Registry after initial registration - // without having to load the plugin in memory. - klass.set_metadata( - "ProgressBin", - "Generic", - "Prints progress information to stdout", - "Sebastian Dröge ", - ); - - // Create and add pad templates for our sink and source pad. These - // are later used for actually creating the pads and beforehand - // already provide information to GStreamer about all possible - // pads that could exist for this type. - - // Our element can accept any possible caps on both pads - let caps = gst::Caps::new_any(); - let src_pad_template = gst::PadTemplate::new( - "src", - gst::PadDirection::Src, - gst::PadPresence::Always, - &caps, - ) - .unwrap(); - klass.add_pad_template(src_pad_template); - - let sink_pad_template = gst::PadTemplate::new( - "sink", - gst::PadDirection::Sink, - gst::PadPresence::Always, - &caps, - ) - .unwrap(); - klass.add_pad_template(sink_pad_template); - - // Install all our properties - klass.install_properties(&PROPERTIES); - } -} - -// Implementation of glib::Object virtual methods -impl ObjectImpl for ProgressBin { - // Called whenever a value of a property is changed. It can be called - // at any time from any thread. - fn set_property(&self, obj: &glib::Object, id: usize, value: &glib::Value) { - let prop = &PROPERTIES[id]; - let element = obj.downcast_ref::().unwrap(); - - match *prop { - subclass::Property("output", ..) => { - let mut output_type = self.output_type.lock().unwrap(); - let new_output_type = value - .get_some::() - .expect("type checked upstream"); - gst_info!( - CAT, - obj: element, - "Changing output from {:?} to {:?}", - output_type, - new_output_type - ); - *output_type = new_output_type; - } - _ => unimplemented!(), - } - } - - // Called whenever a value of a property is read. It can be called - // at any time from any thread. - fn get_property(&self, _obj: &glib::Object, id: usize) -> Result { - let prop = &PROPERTIES[id]; - - match *prop { - subclass::Property("output", ..) => { - let output_type = self.output_type.lock().unwrap(); - Ok(output_type.to_value()) - } - _ => unimplemented!(), - } - } - - // Called right after construction of a new instance - fn constructed(&self, obj: &glib::Object) { - // Call the parent class' ::constructed() implementation first - self.parent_constructed(obj); - - // Here we actually add the pads we created in ProgressBin::new() to the - // element so that GStreamer is aware of their existence. - let bin = obj.downcast_ref::().unwrap(); - - // Add the progressreport element to the bin. - bin.add(&self.progress).unwrap(); - - // Then set the ghost pad targets to the corresponding pads of the progressreport element. - self.sinkpad - .set_target(Some(&self.progress.get_static_pad("sink").unwrap())) - .unwrap(); - self.srcpad - .set_target(Some(&self.progress.get_static_pad("src").unwrap())) - .unwrap(); - - // And finally add the two ghostpads to the bin. - bin.add_pad(&self.sinkpad).unwrap(); - bin.add_pad(&self.srcpad).unwrap(); - } -} - -// Implementation of gst::Element virtual methods -impl ElementImpl for ProgressBin {} - -// Implementation of gst::Bin virtual methods -impl BinImpl for ProgressBin { - fn handle_message(&self, bin: &gst::Bin, msg: gst::Message) { - use gst::MessageView; - - match msg.view() { - // If this is the progressreport message, we print the status - // to stdout. Otherwise we pass through to the default message - // handling of the parent class, i.e. forwarding to the parent - // bins and the application. - MessageView::Element(ref msg) - if msg.get_src().as_ref() == Some(self.progress.upcast_ref()) - && msg - .get_structure() - .map(|s| s.get_name() == "progress") - .unwrap_or(false) => - { - let s = msg.get_structure().unwrap(); - if let Ok(percent) = s.get_some::("percent-double") { - let output_type = self.output_type.lock().unwrap(); - match *output_type { - ProgressBinOutput::Println => println!("progress: {:5.1}%", percent), - ProgressBinOutput::DebugCategory => { - gst_info!(CAT, "progress: {:5.1}%", percent); - } - }; - } - } - _ => self.parent_handle_message(bin, msg), - } - } -} - -// Registers the type for our element, and then registers in GStreamer under -// the name "rsprogressbin" for being able to instantiate it via e.g. -// gst::ElementFactory::make(). -pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { - gst::Element::register( - Some(plugin), - "rsprogressbin", - gst::Rank::None, - ProgressBin::get_type(), - ) -} diff --git a/tutorial/src/progressbin/imp.rs b/tutorial/src/progressbin/imp.rs new file mode 100644 index 000000000..3198d2711 --- /dev/null +++ b/tutorial/src/progressbin/imp.rs @@ -0,0 +1,242 @@ +// Copyright (C) 2019 Sebastian Dröge +// +// 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. + +use glib::prelude::*; +use glib::subclass; +use glib::subclass::prelude::*; +use gst::prelude::*; +use gst::subclass::prelude::*; +use std::sync::Mutex; + +use once_cell::sync::Lazy; + +use super::ProgressBinOutput; + +// This module contains the private implementation details of our element + +const DEFAULT_OUTPUT_TYPE: ProgressBinOutput = ProgressBinOutput::Println; + +static CAT: Lazy = Lazy::new(|| { + gst::DebugCategory::new( + "progressbin", + gst::DebugColorFlags::empty(), + Some("Rust Progress Reporter"), + ) +}); + +// Struct containing all the element data +pub struct ProgressBin { + progress: gst::Element, + srcpad: gst::GhostPad, + sinkpad: gst::GhostPad, + // We put the output_type property behind a mutex, as we want + // change it in the set_property function, which can be called + // from any thread. + output_type: Mutex, +} + +// Metadata for the element's properties +static PROPERTIES: [subclass::Property; 1] = [subclass::Property("output", |name| { + glib::ParamSpec::enum_( + name, + "Output", + "Defines the output type of the progressbin", + ProgressBinOutput::static_type(), + DEFAULT_OUTPUT_TYPE as i32, + glib::ParamFlags::READWRITE, + ) +})]; + +// This trait registers our type with the GObject object system and +// provides the entry points for creating a new instance and setting +// up the class data +impl ObjectSubclass for ProgressBin { + const NAME: &'static str = "RsProgressBin"; + type Type = super::ProgressBin; + type ParentType = gst::Bin; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = subclass::simple::ClassStruct; + + // This macro provides some boilerplate. + glib_object_subclass!(); + + // Called when a new instance is to be created. We need to return an instance + // of our struct here and also get the class struct passed in case it's needed + fn with_class(klass: &Self::Class) -> Self { + // Create our two ghostpads from the templates that were registered with + // the class. We don't provide a target for them yet because we can only + // do so after the progressreport element was added to the bin. + // + // We do that and adding the pads inside glib::Object::constructed() later. + let templ = klass.get_pad_template("sink").unwrap(); + let sinkpad = gst::GhostPad::from_template(&templ, Some("sink")); + let templ = klass.get_pad_template("src").unwrap(); + let srcpad = gst::GhostPad::from_template(&templ, Some("src")); + + // Create the progressreport element. + let progress = gst::ElementFactory::make("progressreport", Some("progress")).unwrap(); + // Don't let progressreport print to stdout itself + progress.set_property("silent", &true).unwrap(); + + // Return an instance of our struct + Self { + progress, + srcpad, + sinkpad, + output_type: Mutex::new(ProgressBinOutput::Println), + } + } + + // Called exactly once when registering the type. Used for + // setting up metadata for all instances, e.g. the name and + // classification and the pad templates with their caps. + // + // Actual instances can create pads based on those pad templates + // with a subset of the caps given here. + fn class_init(klass: &mut Self::Class) { + // Set the element specific metadata. This information is what + // is visible from gst-inspect-1.0 and can also be programatically + // retrieved from the gst::Registry after initial registration + // without having to load the plugin in memory. + klass.set_metadata( + "ProgressBin", + "Generic", + "Prints progress information to stdout", + "Sebastian Dröge ", + ); + + // Create and add pad templates for our sink and source pad. These + // are later used for actually creating the pads and beforehand + // already provide information to GStreamer about all possible + // pads that could exist for this type. + + // Our element can accept any possible caps on both pads + let caps = gst::Caps::new_any(); + let src_pad_template = gst::PadTemplate::new( + "src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &caps, + ) + .unwrap(); + klass.add_pad_template(src_pad_template); + + let sink_pad_template = gst::PadTemplate::new( + "sink", + gst::PadDirection::Sink, + gst::PadPresence::Always, + &caps, + ) + .unwrap(); + klass.add_pad_template(sink_pad_template); + + // Install all our properties + klass.install_properties(&PROPERTIES); + } +} + +// Implementation of glib::Object virtual methods +impl ObjectImpl for ProgressBin { + // Called whenever a value of a property is changed. It can be called + // at any time from any thread. + fn set_property(&self, obj: &Self::Type, id: usize, value: &glib::Value) { + let prop = &PROPERTIES[id]; + + match *prop { + subclass::Property("output", ..) => { + let mut output_type = self.output_type.lock().unwrap(); + let new_output_type = value + .get_some::() + .expect("type checked upstream"); + gst_info!( + CAT, + obj: obj, + "Changing output from {:?} to {:?}", + output_type, + new_output_type + ); + *output_type = new_output_type; + } + _ => unimplemented!(), + } + } + + // Called whenever a value of a property is read. It can be called + // at any time from any thread. + fn get_property(&self, _obj: &Self::Type, id: usize) -> Result { + let prop = &PROPERTIES[id]; + + match *prop { + subclass::Property("output", ..) => { + let output_type = self.output_type.lock().unwrap(); + Ok(output_type.to_value()) + } + _ => unimplemented!(), + } + } + + // Called right after construction of a new instance + fn constructed(&self, obj: &Self::Type) { + // Call the parent class' ::constructed() implementation first + self.parent_constructed(obj); + + // Here we actually add the pads we created in ProgressBin::new() to the + // element so that GStreamer is aware of their existence. + + // Add the progressreport element to the bin. + obj.add(&self.progress).unwrap(); + + // Then set the ghost pad targets to the corresponding pads of the progressreport element. + self.sinkpad + .set_target(Some(&self.progress.get_static_pad("sink").unwrap())) + .unwrap(); + self.srcpad + .set_target(Some(&self.progress.get_static_pad("src").unwrap())) + .unwrap(); + + // And finally add the two ghostpads to the bin. + obj.add_pad(&self.sinkpad).unwrap(); + obj.add_pad(&self.srcpad).unwrap(); + } +} + +// Implementation of gst::Element virtual methods +impl ElementImpl for ProgressBin {} + +// Implementation of gst::Bin virtual methods +impl BinImpl for ProgressBin { + fn handle_message(&self, bin: &Self::Type, msg: gst::Message) { + use gst::MessageView; + + match msg.view() { + // If this is the progressreport message, we print the status + // to stdout. Otherwise we pass through to the default message + // handling of the parent class, i.e. forwarding to the parent + // bins and the application. + MessageView::Element(ref msg) + if msg.get_src().as_ref() == Some(self.progress.upcast_ref()) + && msg + .get_structure() + .map(|s| s.get_name() == "progress") + .unwrap_or(false) => + { + let s = msg.get_structure().unwrap(); + if let Ok(percent) = s.get_some::("percent-double") { + let output_type = self.output_type.lock().unwrap(); + match *output_type { + ProgressBinOutput::Println => println!("progress: {:5.1}%", percent), + ProgressBinOutput::DebugCategory => { + gst_info!(CAT, "progress: {:5.1}%", percent); + } + }; + } + } + _ => self.parent_handle_message(bin, msg), + } + } +} diff --git a/tutorial/src/progressbin/mod.rs b/tutorial/src/progressbin/mod.rs new file mode 100644 index 000000000..4c1a41309 --- /dev/null +++ b/tutorial/src/progressbin/mod.rs @@ -0,0 +1,52 @@ +// Copyright (C) 2020 Sebastian Dröge +// +// 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. + +use glib::prelude::*; + +mod imp; + +// This enum may be used to control what type of output the progressbin should produce. +// It also serves the secondary purpose of illustrating how to add enum-type properties +// to a plugin written in rust. +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, GEnum)] +#[repr(u32)] +#[genum(type_name = "GstProgressBinOutput")] +pub enum ProgressBinOutput { + #[genum( + name = "Println: Outputs the progress using a println! macro.", + nick = "println" + )] + Println = 0, + #[genum( + name = "Debug Category: Outputs the progress as info logs under the element's debug category.", + nick = "debug-category" + )] + DebugCategory = 1, +} + +// The public Rust wrapper type for our element +glib_wrapper! { + pub struct ProgressBin(ObjectSubclass) @extends gst::Bin, gst::Element, gst::Object; +} + +// GStreamer elements need to be thread-safe. For the private implementation this is automatically +// enforced but for the public wrapper type we need to specify this manually. +unsafe impl Send for ProgressBin {} +unsafe impl Sync for ProgressBin {} + +// Registers the type for our element, and then registers in GStreamer under +// the name "rsprogressbin" for being able to instantiate it via e.g. +// gst::ElementFactory::make(). +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "rsprogressbin", + gst::Rank::None, + ProgressBin::static_type(), + ) +} diff --git a/tutorial/src/rgb2gray.rs b/tutorial/src/rgb2gray.rs deleted file mode 100644 index af47ad352..000000000 --- a/tutorial/src/rgb2gray.rs +++ /dev/null @@ -1,576 +0,0 @@ -// Copyright (C) 2017,2018 Sebastian Dröge -// -// 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. - -use glib::subclass; -use glib::subclass::prelude::*; -use gst::prelude::*; -use gst::subclass::prelude::*; -use gst_base::subclass::prelude::*; - -use std::i32; -use std::sync::Mutex; - -use once_cell::sync::Lazy; - -// Default values of properties -const DEFAULT_INVERT: bool = false; -const DEFAULT_SHIFT: u32 = 0; - -// Property value storage -#[derive(Debug, Clone, Copy)] -struct Settings { - invert: bool, - shift: u32, -} - -impl Default for Settings { - fn default() -> Self { - Settings { - invert: DEFAULT_INVERT, - shift: DEFAULT_SHIFT, - } - } -} - -// Metadata for the properties -static PROPERTIES: [subclass::Property; 2] = [ - subclass::Property("invert", |name| { - glib::ParamSpec::boolean( - name, - "Invert", - "Invert grayscale output", - DEFAULT_INVERT, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("shift", |name| { - glib::ParamSpec::uint( - name, - "Shift", - "Shift grayscale output (wrapping around)", - 0, - 255, - DEFAULT_SHIFT, - glib::ParamFlags::READWRITE, - ) - }), -]; - -// Stream-specific state, i.e. video format configuration -struct State { - in_info: gst_video::VideoInfo, - out_info: gst_video::VideoInfo, -} - -// Struct containing all the element data -struct Rgb2Gray { - settings: Mutex, - state: Mutex>, -} - -static CAT: Lazy = Lazy::new(|| { - gst::DebugCategory::new( - "rsrgb2gray", - gst::DebugColorFlags::empty(), - Some("Rust RGB-GRAY converter"), - ) -}); - -impl Rgb2Gray { - // Converts one pixel of BGRx to a grayscale value, shifting and/or - // inverting it as configured - #[inline] - fn bgrx_to_gray(in_p: &[u8], shift: u8, invert: bool) -> u8 { - // See https://en.wikipedia.org/wiki/YUV#SDTV_with_BT.601 - const R_Y: u32 = 19595; // 0.299 * 65536 - const G_Y: u32 = 38470; // 0.587 * 65536 - const B_Y: u32 = 7471; // 0.114 * 65536 - - assert_eq!(in_p.len(), 4); - - let b = u32::from(in_p[0]); - let g = u32::from(in_p[1]); - let r = u32::from(in_p[2]); - - let gray = ((r * R_Y) + (g * G_Y) + (b * B_Y)) / 65536; - let gray = (gray as u8).wrapping_add(shift); - - if invert { - 255 - gray - } else { - gray - } - } -} - -// This trait registers our type with the GObject object system and -// provides the entry points for creating a new instance and setting -// up the class data -impl ObjectSubclass for Rgb2Gray { - const NAME: &'static str = "RsRgb2Gray"; - type ParentType = gst_base::BaseTransform; - type Instance = gst::subclass::ElementInstanceStruct; - type Class = subclass::simple::ClassStruct; - - // This macro provides some boilerplate - glib_object_subclass!(); - - // Called when a new instance is to be created. We need to return an instance - // of our struct here. - fn new() -> Self { - Self { - settings: Mutex::new(Default::default()), - state: Mutex::new(None), - } - } - - // Called exactly once when registering the type. Used for - // setting up metadata for all instances, e.g. the name and - // classification and the pad templates with their caps. - // - // Actual instances can create pads based on those pad templates - // with a subset of the caps given here. In case of basetransform, - // a "src" and "sink" pad template are required here and the base class - // will automatically instantiate pads for them. - // - // Our element here can convert BGRx to BGRx or GRAY8, both being grayscale. - fn class_init(klass: &mut subclass::simple::ClassStruct) { - // Set the element specific metadata. This information is what - // is visible from gst-inspect-1.0 and can also be programatically - // retrieved from the gst::Registry after initial registration - // without having to load the plugin in memory. - klass.set_metadata( - "RGB-GRAY Converter", - "Filter/Effect/Converter/Video", - "Converts RGB to GRAY or grayscale RGB", - "Sebastian Dröge ", - ); - - // Create and add pad templates for our sink and source pad. These - // are later used for actually creating the pads and beforehand - // already provide information to GStreamer about all possible - // pads that could exist for this type. - - // On the src pad, we can produce BGRx and GRAY8 of any - // width/height and with any framerate - let caps = gst::Caps::new_simple( - "video/x-raw", - &[ - ( - "format", - &gst::List::new(&[ - &gst_video::VideoFormat::Bgrx.to_str(), - &gst_video::VideoFormat::Gray8.to_str(), - ]), - ), - ("width", &gst::IntRange::::new(0, i32::MAX)), - ("height", &gst::IntRange::::new(0, i32::MAX)), - ( - "framerate", - &gst::FractionRange::new( - gst::Fraction::new(0, 1), - gst::Fraction::new(i32::MAX, 1), - ), - ), - ], - ); - // The src pad template must be named "src" for basetransform - // and specific a pad that is always there - let src_pad_template = gst::PadTemplate::new( - "src", - gst::PadDirection::Src, - gst::PadPresence::Always, - &caps, - ) - .unwrap(); - klass.add_pad_template(src_pad_template); - - // On the sink pad, we can accept BGRx of any - // width/height and with any framerate - let caps = gst::Caps::new_simple( - "video/x-raw", - &[ - ("format", &gst_video::VideoFormat::Bgrx.to_str()), - ("width", &gst::IntRange::::new(0, i32::MAX)), - ("height", &gst::IntRange::::new(0, i32::MAX)), - ( - "framerate", - &gst::FractionRange::new( - gst::Fraction::new(0, 1), - gst::Fraction::new(i32::MAX, 1), - ), - ), - ], - ); - // The sink pad template must be named "sink" for basetransform - // and specific a pad that is always there - let sink_pad_template = gst::PadTemplate::new( - "sink", - gst::PadDirection::Sink, - gst::PadPresence::Always, - &caps, - ) - .unwrap(); - klass.add_pad_template(sink_pad_template); - - // Install all our properties - klass.install_properties(&PROPERTIES); - - // Configure basetransform so that we are never running in-place, - // don't passthrough on same caps and also never call transform_ip - // in passthrough mode (which does not matter for us here). - // - // We could work in-place for BGRx->BGRx but don't do here for simplicity - // for now. - klass.configure( - gst_base::subclass::BaseTransformMode::NeverInPlace, - false, - false, - ); - } -} - -// Implementation of glib::Object virtual methods -impl ObjectImpl for Rgb2Gray { - // Called whenever a value of a property is changed. It can be called - // at any time from any thread. - fn set_property(&self, obj: &glib::Object, id: usize, value: &glib::Value) { - let prop = &PROPERTIES[id]; - let element = obj.downcast_ref::().unwrap(); - - match *prop { - subclass::Property("invert", ..) => { - let mut settings = self.settings.lock().unwrap(); - let invert = value.get_some().expect("type checked upstream"); - gst_info!( - CAT, - obj: element, - "Changing invert from {} to {}", - settings.invert, - invert - ); - settings.invert = invert; - } - subclass::Property("shift", ..) => { - let mut settings = self.settings.lock().unwrap(); - let shift = value.get_some().expect("type checked upstream"); - gst_info!( - CAT, - obj: element, - "Changing shift from {} to {}", - settings.shift, - shift - ); - settings.shift = shift; - } - _ => unimplemented!(), - } - } - - // Called whenever a value of a property is read. It can be called - // at any time from any thread. - fn get_property(&self, _obj: &glib::Object, id: usize) -> Result { - let prop = &PROPERTIES[id]; - - match *prop { - subclass::Property("invert", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.invert.to_value()) - } - subclass::Property("shift", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.shift.to_value()) - } - _ => unimplemented!(), - } - } -} - -// Implementation of gst::Element virtual methods -impl ElementImpl for Rgb2Gray {} - -// Implementation of gst_base::BaseTransform virtual methods -impl BaseTransformImpl for Rgb2Gray { - // Called for converting caps from one pad to another to account for any - // changes in the media format this element is performing. - // - // In our case that means that: - fn transform_caps( - &self, - element: &gst_base::BaseTransform, - direction: gst::PadDirection, - caps: &gst::Caps, - filter: Option<&gst::Caps>, - ) -> Option { - let other_caps = if direction == gst::PadDirection::Src { - // For src to sink, no matter if we get asked for BGRx or GRAY8 caps, we can only - // accept corresponding BGRx caps on the sinkpad. We will only ever get BGRx and GRAY8 - // caps here as input. - let mut caps = caps.clone(); - - for s in caps.make_mut().iter_mut() { - s.set("format", &gst_video::VideoFormat::Bgrx.to_str()); - } - - caps - } else { - // For the sink to src case, we will only get BGRx caps and for each of them we could - // output the same caps or the same caps as GRAY8. We prefer GRAY8 (put it first), and - // at a later point the caps negotiation mechanism of GStreamer will decide on which - // one to actually produce. - let mut gray_caps = gst::Caps::new_empty(); - - { - let gray_caps = gray_caps.get_mut().unwrap(); - - for s in caps.iter() { - let mut s_gray = s.to_owned(); - s_gray.set("format", &gst_video::VideoFormat::Gray8.to_str()); - gray_caps.append_structure(s_gray); - } - gray_caps.append(caps.clone()); - } - - gray_caps - }; - - gst_debug!( - CAT, - obj: element, - "Transformed caps from {} to {} in direction {:?}", - caps, - other_caps, - direction - ); - - // In the end we need to filter the caps through an optional filter caps to get rid of any - // unwanted caps. - if let Some(filter) = filter { - Some(filter.intersect_with_mode(&other_caps, gst::CapsIntersectMode::First)) - } else { - Some(other_caps) - } - } - - // Returns the size of one processing unit (i.e. a frame in our case) corresponding - // to the given caps. This is used for allocating a big enough output buffer and - // sanity checking the input buffer size, among other things. - fn get_unit_size(&self, _element: &gst_base::BaseTransform, caps: &gst::Caps) -> Option { - gst_video::VideoInfo::from_caps(caps) - .map(|info| info.size()) - .ok() - } - - // Called whenever the input/output caps are changing, i.e. in the very beginning before data - // flow happens and whenever the situation in the pipeline is changing. All buffers after this - // call have the caps given here. - // - // We simply remember the resulting VideoInfo from the caps to be able to use this for knowing - // the width, stride, etc when transforming buffers - fn set_caps( - &self, - element: &gst_base::BaseTransform, - incaps: &gst::Caps, - outcaps: &gst::Caps, - ) -> Result<(), gst::LoggableError> { - let in_info = match gst_video::VideoInfo::from_caps(incaps) { - Err(_) => return Err(gst_loggable_error!(CAT, "Failed to parse input caps")), - Ok(info) => info, - }; - let out_info = match gst_video::VideoInfo::from_caps(outcaps) { - Err(_) => return Err(gst_loggable_error!(CAT, "Failed to parse output caps")), - Ok(info) => info, - }; - - gst_debug!( - CAT, - obj: element, - "Configured for caps {} to {}", - incaps, - outcaps - ); - - *self.state.lock().unwrap() = Some(State { in_info, out_info }); - - Ok(()) - } - - // Called when shutting down the element so we can release all stream-related state - // There's also start(), which is called whenever starting the element again - fn stop(&self, element: &gst_base::BaseTransform) -> Result<(), gst::ErrorMessage> { - // Drop state - let _ = self.state.lock().unwrap().take(); - - gst_info!(CAT, obj: element, "Stopped"); - - Ok(()) - } - - // Does the actual transformation of the input buffer to the output buffer - fn transform( - &self, - element: &gst_base::BaseTransform, - inbuf: &gst::Buffer, - outbuf: &mut gst::BufferRef, - ) -> Result { - // Keep a local copy of the values of all our properties at this very moment. This - // ensures that the mutex is never locked for long and the application wouldn't - // have to block until this function returns when getting/setting property values - let settings = *self.settings.lock().unwrap(); - - // Get a locked reference to our state, i.e. the input and output VideoInfo - let mut state_guard = self.state.lock().unwrap(); - let state = state_guard.as_mut().ok_or_else(|| { - gst_element_error!(element, gst::CoreError::Negotiation, ["Have no state yet"]); - gst::FlowError::NotNegotiated - })?; - - // Map the input buffer as a VideoFrameRef. This is similar to directly mapping - // the buffer with inbuf.map_readable() but in addition extracts various video - // specific metadata and sets up a convenient data structure that directly gives - // pointers to the different planes and has all the information about the raw - // video frame, like width, height, stride, video format, etc. - // - // This fails if the buffer can't be read or is invalid in relation to the video - // info that is passed here - let in_frame = - gst_video::VideoFrameRef::from_buffer_ref_readable(inbuf.as_ref(), &state.in_info) - .map_err(|_| { - gst_element_error!( - element, - gst::CoreError::Failed, - ["Failed to map input buffer readable"] - ); - gst::FlowError::Error - })?; - - // And now map the output buffer writable, so we can fill it. - let mut out_frame = - gst_video::VideoFrameRef::from_buffer_ref_writable(outbuf, &state.out_info).map_err( - |_| { - gst_element_error!( - element, - gst::CoreError::Failed, - ["Failed to map output buffer writable"] - ); - gst::FlowError::Error - }, - )?; - - // Keep the various metadata we need for working with the video frames in - // local variables. This saves some typing below. - let width = in_frame.width() as usize; - let in_stride = in_frame.plane_stride()[0] as usize; - let in_data = in_frame.plane_data(0).unwrap(); - let out_stride = out_frame.plane_stride()[0] as usize; - let out_format = out_frame.format(); - let out_data = out_frame.plane_data_mut(0).unwrap(); - - // First check the output format. Our input format is always BGRx but the output might - // be BGRx or GRAY8. Based on what it is we need to do processing slightly differently. - if out_format == gst_video::VideoFormat::Bgrx { - // Some assertions about our assumptions how the data looks like. This is only there - // to give some further information to the compiler, in case these can be used for - // better optimizations of the resulting code. - // - // If any of the assertions were not true, the code below would fail cleanly. - assert_eq!(in_data.len() % 4, 0); - assert_eq!(out_data.len() % 4, 0); - assert_eq!(out_data.len() / out_stride, in_data.len() / in_stride); - - let in_line_bytes = width * 4; - let out_line_bytes = width * 4; - - assert!(in_line_bytes <= in_stride); - assert!(out_line_bytes <= out_stride); - - // Iterate over each line of the input and output frame, mutable for the output frame. - // Each input line has in_stride bytes, each output line out_stride. We use the - // chunks_exact/chunks_exact_mut iterators here for getting a chunks of that many bytes per - // iteration and zip them together to have access to both at the same time. - for (in_line, out_line) in in_data - .chunks_exact(in_stride) - .zip(out_data.chunks_exact_mut(out_stride)) - { - // Next iterate the same way over each actual pixel in each line. Every pixel is 4 - // bytes in the input and output, so we again use the chunks_exact/chunks_exact_mut iterators - // to give us each pixel individually and zip them together. - // - // Note that we take a sub-slice of the whole lines: each line can contain an - // arbitrary amount of padding at the end (e.g. for alignment purposes) and we - // don't want to process that padding. - for (in_p, out_p) in in_line[..in_line_bytes] - .chunks_exact(4) - .zip(out_line[..out_line_bytes].chunks_exact_mut(4)) - { - assert_eq!(out_p.len(), 4); - - // Use our above-defined function to convert a BGRx pixel with the settings to - // a grayscale value. Then store the same value in the red/green/blue component - // of the pixel. - let gray = Rgb2Gray::bgrx_to_gray(in_p, settings.shift as u8, settings.invert); - out_p[0] = gray; - out_p[1] = gray; - out_p[2] = gray; - } - } - } else if out_format == gst_video::VideoFormat::Gray8 { - assert_eq!(in_data.len() % 4, 0); - assert_eq!(out_data.len() / out_stride, in_data.len() / in_stride); - - let in_line_bytes = width * 4; - let out_line_bytes = width; - - assert!(in_line_bytes <= in_stride); - assert!(out_line_bytes <= out_stride); - - // Iterate over each line of the input and output frame, mutable for the output frame. - // Each input line has in_stride bytes, each output line out_stride. We use the - // chunks_exact/chunks_exact_mut iterators here for getting a chunks of that many bytes per - // iteration and zip them together to have access to both at the same time. - for (in_line, out_line) in in_data - .chunks_exact(in_stride) - .zip(out_data.chunks_exact_mut(out_stride)) - { - // Next iterate the same way over each actual pixel in each line. Every pixel is 4 - // bytes in the input and 1 byte in the output, so we again use the - // chunks_exact/chunks_exact_mut iterators to give us each pixel individually and zip them - // together. - // - // Note that we take a sub-slice of the whole lines: each line can contain an - // arbitrary amount of padding at the end (e.g. for alignment purposes) and we - // don't want to process that padding. - for (in_p, out_p) in in_line[..in_line_bytes] - .chunks_exact(4) - .zip(out_line[..out_line_bytes].iter_mut()) - { - // Use our above-defined function to convert a BGRx pixel with the settings to - // a grayscale value. Then store the value in the grayscale output directly. - let gray = Rgb2Gray::bgrx_to_gray(in_p, settings.shift as u8, settings.invert); - *out_p = gray; - } - } - } else { - unimplemented!(); - } - - Ok(gst::FlowSuccess::Ok) - } -} - -// Registers the type for our element, and then registers in GStreamer under -// the name "rsrgb2gray" for being able to instantiate it via e.g. -// gst::ElementFactory::make(). -pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { - gst::Element::register( - Some(plugin), - "rsrgb2gray", - gst::Rank::None, - Rgb2Gray::get_type(), - ) -} diff --git a/tutorial/src/rgb2gray/imp.rs b/tutorial/src/rgb2gray/imp.rs new file mode 100644 index 000000000..4dbacbdd4 --- /dev/null +++ b/tutorial/src/rgb2gray/imp.rs @@ -0,0 +1,566 @@ +// Copyright (C) 2017,2018 Sebastian Dröge +// +// 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. + +use glib::subclass; +use glib::subclass::prelude::*; +use gst::prelude::*; +use gst::subclass::prelude::*; +use gst_base::subclass::prelude::*; + +use std::i32; +use std::sync::Mutex; + +use once_cell::sync::Lazy; + +// This module contains the private implementation details of our element +// +static CAT: Lazy = Lazy::new(|| { + gst::DebugCategory::new( + "rsrgb2gray", + gst::DebugColorFlags::empty(), + Some("Rust RGB-GRAY converter"), + ) +}); + +// Default values of properties +const DEFAULT_INVERT: bool = false; +const DEFAULT_SHIFT: u32 = 0; + +// Property value storage +#[derive(Debug, Clone, Copy)] +struct Settings { + invert: bool, + shift: u32, +} + +impl Default for Settings { + fn default() -> Self { + Settings { + invert: DEFAULT_INVERT, + shift: DEFAULT_SHIFT, + } + } +} + +// Metadata for the properties +static PROPERTIES: [subclass::Property; 2] = [ + subclass::Property("invert", |name| { + glib::ParamSpec::boolean( + name, + "Invert", + "Invert grayscale output", + DEFAULT_INVERT, + glib::ParamFlags::READWRITE, + ) + }), + subclass::Property("shift", |name| { + glib::ParamSpec::uint( + name, + "Shift", + "Shift grayscale output (wrapping around)", + 0, + 255, + DEFAULT_SHIFT, + glib::ParamFlags::READWRITE, + ) + }), +]; + +// Stream-specific state, i.e. video format configuration +struct State { + in_info: gst_video::VideoInfo, + out_info: gst_video::VideoInfo, +} + +// Struct containing all the element data +pub struct Rgb2Gray { + settings: Mutex, + state: Mutex>, +} + +impl Rgb2Gray { + // Converts one pixel of BGRx to a grayscale value, shifting and/or + // inverting it as configured + #[inline] + fn bgrx_to_gray(in_p: &[u8], shift: u8, invert: bool) -> u8 { + // See https://en.wikipedia.org/wiki/YUV#SDTV_with_BT.601 + const R_Y: u32 = 19595; // 0.299 * 65536 + const G_Y: u32 = 38470; // 0.587 * 65536 + const B_Y: u32 = 7471; // 0.114 * 65536 + + assert_eq!(in_p.len(), 4); + + let b = u32::from(in_p[0]); + let g = u32::from(in_p[1]); + let r = u32::from(in_p[2]); + + let gray = ((r * R_Y) + (g * G_Y) + (b * B_Y)) / 65536; + let gray = (gray as u8).wrapping_add(shift); + + if invert { + 255 - gray + } else { + gray + } + } +} + +// This trait registers our type with the GObject object system and +// provides the entry points for creating a new instance and setting +// up the class data +impl ObjectSubclass for Rgb2Gray { + const NAME: &'static str = "RsRgb2Gray"; + type Type = super::Rgb2Gray; + type ParentType = gst_base::BaseTransform; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = subclass::simple::ClassStruct; + + // This macro provides some boilerplate + glib_object_subclass!(); + + // Called when a new instance is to be created. We need to return an instance + // of our struct here. + fn new() -> Self { + Self { + settings: Mutex::new(Default::default()), + state: Mutex::new(None), + } + } + + // Called exactly once when registering the type. Used for + // setting up metadata for all instances, e.g. the name and + // classification and the pad templates with their caps. + // + // Actual instances can create pads based on those pad templates + // with a subset of the caps given here. In case of basetransform, + // a "src" and "sink" pad template are required here and the base class + // will automatically instantiate pads for them. + // + // Our element here can convert BGRx to BGRx or GRAY8, both being grayscale. + fn class_init(klass: &mut Self::Class) { + // Set the element specific metadata. This information is what + // is visible from gst-inspect-1.0 and can also be programatically + // retrieved from the gst::Registry after initial registration + // without having to load the plugin in memory. + klass.set_metadata( + "RGB-GRAY Converter", + "Filter/Effect/Converter/Video", + "Converts RGB to GRAY or grayscale RGB", + "Sebastian Dröge ", + ); + + // Create and add pad templates for our sink and source pad. These + // are later used for actually creating the pads and beforehand + // already provide information to GStreamer about all possible + // pads that could exist for this type. + + // On the src pad, we can produce BGRx and GRAY8 of any + // width/height and with any framerate + let caps = gst::Caps::new_simple( + "video/x-raw", + &[ + ( + "format", + &gst::List::new(&[ + &gst_video::VideoFormat::Bgrx.to_str(), + &gst_video::VideoFormat::Gray8.to_str(), + ]), + ), + ("width", &gst::IntRange::::new(0, i32::MAX)), + ("height", &gst::IntRange::::new(0, i32::MAX)), + ( + "framerate", + &gst::FractionRange::new( + gst::Fraction::new(0, 1), + gst::Fraction::new(i32::MAX, 1), + ), + ), + ], + ); + // The src pad template must be named "src" for basetransform + // and specific a pad that is always there + let src_pad_template = gst::PadTemplate::new( + "src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &caps, + ) + .unwrap(); + klass.add_pad_template(src_pad_template); + + // On the sink pad, we can accept BGRx of any + // width/height and with any framerate + let caps = gst::Caps::new_simple( + "video/x-raw", + &[ + ("format", &gst_video::VideoFormat::Bgrx.to_str()), + ("width", &gst::IntRange::::new(0, i32::MAX)), + ("height", &gst::IntRange::::new(0, i32::MAX)), + ( + "framerate", + &gst::FractionRange::new( + gst::Fraction::new(0, 1), + gst::Fraction::new(i32::MAX, 1), + ), + ), + ], + ); + // The sink pad template must be named "sink" for basetransform + // and specific a pad that is always there + let sink_pad_template = gst::PadTemplate::new( + "sink", + gst::PadDirection::Sink, + gst::PadPresence::Always, + &caps, + ) + .unwrap(); + klass.add_pad_template(sink_pad_template); + + // Install all our properties + klass.install_properties(&PROPERTIES); + + // Configure basetransform so that we are never running in-place, + // don't passthrough on same caps and also never call transform_ip + // in passthrough mode (which does not matter for us here). + // + // We could work in-place for BGRx->BGRx but don't do here for simplicity + // for now. + klass.configure( + gst_base::subclass::BaseTransformMode::NeverInPlace, + false, + false, + ); + } +} + +// Implementation of glib::Object virtual methods +impl ObjectImpl for Rgb2Gray { + // Called whenever a value of a property is changed. It can be called + // at any time from any thread. + fn set_property(&self, obj: &Self::Type, id: usize, value: &glib::Value) { + let prop = &PROPERTIES[id]; + + match *prop { + subclass::Property("invert", ..) => { + let mut settings = self.settings.lock().unwrap(); + let invert = value.get_some().expect("type checked upstream"); + gst_info!( + CAT, + obj: obj, + "Changing invert from {} to {}", + settings.invert, + invert + ); + settings.invert = invert; + } + subclass::Property("shift", ..) => { + let mut settings = self.settings.lock().unwrap(); + let shift = value.get_some().expect("type checked upstream"); + gst_info!( + CAT, + obj: obj, + "Changing shift from {} to {}", + settings.shift, + shift + ); + settings.shift = shift; + } + _ => unimplemented!(), + } + } + + // Called whenever a value of a property is read. It can be called + // at any time from any thread. + fn get_property(&self, _obj: &Self::Type, id: usize) -> Result { + let prop = &PROPERTIES[id]; + + match *prop { + subclass::Property("invert", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.invert.to_value()) + } + subclass::Property("shift", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.shift.to_value()) + } + _ => unimplemented!(), + } + } +} + +// Implementation of gst::Element virtual methods +impl ElementImpl for Rgb2Gray {} + +// Implementation of gst_base::BaseTransform virtual methods +impl BaseTransformImpl for Rgb2Gray { + // Called for converting caps from one pad to another to account for any + // changes in the media format this element is performing. + // + // In our case that means that: + fn transform_caps( + &self, + element: &Self::Type, + direction: gst::PadDirection, + caps: &gst::Caps, + filter: Option<&gst::Caps>, + ) -> Option { + let other_caps = if direction == gst::PadDirection::Src { + // For src to sink, no matter if we get asked for BGRx or GRAY8 caps, we can only + // accept corresponding BGRx caps on the sinkpad. We will only ever get BGRx and GRAY8 + // caps here as input. + let mut caps = caps.clone(); + + for s in caps.make_mut().iter_mut() { + s.set("format", &gst_video::VideoFormat::Bgrx.to_str()); + } + + caps + } else { + // For the sink to src case, we will only get BGRx caps and for each of them we could + // output the same caps or the same caps as GRAY8. We prefer GRAY8 (put it first), and + // at a later point the caps negotiation mechanism of GStreamer will decide on which + // one to actually produce. + let mut gray_caps = gst::Caps::new_empty(); + + { + let gray_caps = gray_caps.get_mut().unwrap(); + + for s in caps.iter() { + let mut s_gray = s.to_owned(); + s_gray.set("format", &gst_video::VideoFormat::Gray8.to_str()); + gray_caps.append_structure(s_gray); + } + gray_caps.append(caps.clone()); + } + + gray_caps + }; + + gst_debug!( + CAT, + obj: element, + "Transformed caps from {} to {} in direction {:?}", + caps, + other_caps, + direction + ); + + // In the end we need to filter the caps through an optional filter caps to get rid of any + // unwanted caps. + if let Some(filter) = filter { + Some(filter.intersect_with_mode(&other_caps, gst::CapsIntersectMode::First)) + } else { + Some(other_caps) + } + } + + // Returns the size of one processing unit (i.e. a frame in our case) corresponding + // to the given caps. This is used for allocating a big enough output buffer and + // sanity checking the input buffer size, among other things. + fn get_unit_size(&self, _element: &Self::Type, caps: &gst::Caps) -> Option { + gst_video::VideoInfo::from_caps(caps) + .map(|info| info.size()) + .ok() + } + + // Called whenever the input/output caps are changing, i.e. in the very beginning before data + // flow happens and whenever the situation in the pipeline is changing. All buffers after this + // call have the caps given here. + // + // We simply remember the resulting VideoInfo from the caps to be able to use this for knowing + // the width, stride, etc when transforming buffers + fn set_caps( + &self, + element: &Self::Type, + incaps: &gst::Caps, + outcaps: &gst::Caps, + ) -> Result<(), gst::LoggableError> { + let in_info = match gst_video::VideoInfo::from_caps(incaps) { + Err(_) => return Err(gst_loggable_error!(CAT, "Failed to parse input caps")), + Ok(info) => info, + }; + let out_info = match gst_video::VideoInfo::from_caps(outcaps) { + Err(_) => return Err(gst_loggable_error!(CAT, "Failed to parse output caps")), + Ok(info) => info, + }; + + gst_debug!( + CAT, + obj: element, + "Configured for caps {} to {}", + incaps, + outcaps + ); + + *self.state.lock().unwrap() = Some(State { in_info, out_info }); + + Ok(()) + } + + // Called when shutting down the element so we can release all stream-related state + // There's also start(), which is called whenever starting the element again + fn stop(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> { + // Drop state + let _ = self.state.lock().unwrap().take(); + + gst_info!(CAT, obj: element, "Stopped"); + + Ok(()) + } + + // Does the actual transformation of the input buffer to the output buffer + fn transform( + &self, + element: &Self::Type, + inbuf: &gst::Buffer, + outbuf: &mut gst::BufferRef, + ) -> Result { + // Keep a local copy of the values of all our properties at this very moment. This + // ensures that the mutex is never locked for long and the application wouldn't + // have to block until this function returns when getting/setting property values + let settings = *self.settings.lock().unwrap(); + + // Get a locked reference to our state, i.e. the input and output VideoInfo + let mut state_guard = self.state.lock().unwrap(); + let state = state_guard.as_mut().ok_or_else(|| { + gst_element_error!(element, gst::CoreError::Negotiation, ["Have no state yet"]); + gst::FlowError::NotNegotiated + })?; + + // Map the input buffer as a VideoFrameRef. This is similar to directly mapping + // the buffer with inbuf.map_readable() but in addition extracts various video + // specific metadata and sets up a convenient data structure that directly gives + // pointers to the different planes and has all the information about the raw + // video frame, like width, height, stride, video format, etc. + // + // This fails if the buffer can't be read or is invalid in relation to the video + // info that is passed here + let in_frame = + gst_video::VideoFrameRef::from_buffer_ref_readable(inbuf.as_ref(), &state.in_info) + .map_err(|_| { + gst_element_error!( + element, + gst::CoreError::Failed, + ["Failed to map input buffer readable"] + ); + gst::FlowError::Error + })?; + + // And now map the output buffer writable, so we can fill it. + let mut out_frame = + gst_video::VideoFrameRef::from_buffer_ref_writable(outbuf, &state.out_info).map_err( + |_| { + gst_element_error!( + element, + gst::CoreError::Failed, + ["Failed to map output buffer writable"] + ); + gst::FlowError::Error + }, + )?; + + // Keep the various metadata we need for working with the video frames in + // local variables. This saves some typing below. + let width = in_frame.width() as usize; + let in_stride = in_frame.plane_stride()[0] as usize; + let in_data = in_frame.plane_data(0).unwrap(); + let out_stride = out_frame.plane_stride()[0] as usize; + let out_format = out_frame.format(); + let out_data = out_frame.plane_data_mut(0).unwrap(); + + // First check the output format. Our input format is always BGRx but the output might + // be BGRx or GRAY8. Based on what it is we need to do processing slightly differently. + if out_format == gst_video::VideoFormat::Bgrx { + // Some assertions about our assumptions how the data looks like. This is only there + // to give some further information to the compiler, in case these can be used for + // better optimizations of the resulting code. + // + // If any of the assertions were not true, the code below would fail cleanly. + assert_eq!(in_data.len() % 4, 0); + assert_eq!(out_data.len() % 4, 0); + assert_eq!(out_data.len() / out_stride, in_data.len() / in_stride); + + let in_line_bytes = width * 4; + let out_line_bytes = width * 4; + + assert!(in_line_bytes <= in_stride); + assert!(out_line_bytes <= out_stride); + + // Iterate over each line of the input and output frame, mutable for the output frame. + // Each input line has in_stride bytes, each output line out_stride. We use the + // chunks_exact/chunks_exact_mut iterators here for getting a chunks of that many bytes per + // iteration and zip them together to have access to both at the same time. + for (in_line, out_line) in in_data + .chunks_exact(in_stride) + .zip(out_data.chunks_exact_mut(out_stride)) + { + // Next iterate the same way over each actual pixel in each line. Every pixel is 4 + // bytes in the input and output, so we again use the chunks_exact/chunks_exact_mut iterators + // to give us each pixel individually and zip them together. + // + // Note that we take a sub-slice of the whole lines: each line can contain an + // arbitrary amount of padding at the end (e.g. for alignment purposes) and we + // don't want to process that padding. + for (in_p, out_p) in in_line[..in_line_bytes] + .chunks_exact(4) + .zip(out_line[..out_line_bytes].chunks_exact_mut(4)) + { + assert_eq!(out_p.len(), 4); + + // Use our above-defined function to convert a BGRx pixel with the settings to + // a grayscale value. Then store the same value in the red/green/blue component + // of the pixel. + let gray = Rgb2Gray::bgrx_to_gray(in_p, settings.shift as u8, settings.invert); + out_p[0] = gray; + out_p[1] = gray; + out_p[2] = gray; + } + } + } else if out_format == gst_video::VideoFormat::Gray8 { + assert_eq!(in_data.len() % 4, 0); + assert_eq!(out_data.len() / out_stride, in_data.len() / in_stride); + + let in_line_bytes = width * 4; + let out_line_bytes = width; + + assert!(in_line_bytes <= in_stride); + assert!(out_line_bytes <= out_stride); + + // Iterate over each line of the input and output frame, mutable for the output frame. + // Each input line has in_stride bytes, each output line out_stride. We use the + // chunks_exact/chunks_exact_mut iterators here for getting a chunks of that many bytes per + // iteration and zip them together to have access to both at the same time. + for (in_line, out_line) in in_data + .chunks_exact(in_stride) + .zip(out_data.chunks_exact_mut(out_stride)) + { + // Next iterate the same way over each actual pixel in each line. Every pixel is 4 + // bytes in the input and 1 byte in the output, so we again use the + // chunks_exact/chunks_exact_mut iterators to give us each pixel individually and zip them + // together. + // + // Note that we take a sub-slice of the whole lines: each line can contain an + // arbitrary amount of padding at the end (e.g. for alignment purposes) and we + // don't want to process that padding. + for (in_p, out_p) in in_line[..in_line_bytes] + .chunks_exact(4) + .zip(out_line[..out_line_bytes].iter_mut()) + { + // Use our above-defined function to convert a BGRx pixel with the settings to + // a grayscale value. Then store the value in the grayscale output directly. + let gray = Rgb2Gray::bgrx_to_gray(in_p, settings.shift as u8, settings.invert); + *out_p = gray; + } + } + } else { + unimplemented!(); + } + + Ok(gst::FlowSuccess::Ok) + } +} diff --git a/tutorial/src/rgb2gray/mod.rs b/tutorial/src/rgb2gray/mod.rs new file mode 100644 index 000000000..b85d708e4 --- /dev/null +++ b/tutorial/src/rgb2gray/mod.rs @@ -0,0 +1,33 @@ +// Copyright (C) 2020 Sebastian Dröge +// +// 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. + +use glib::prelude::*; + +mod imp; + +// The public Rust wrapper type for our element +glib_wrapper! { + pub struct Rgb2Gray(ObjectSubclass) @extends gst_base::BaseTransform, gst::Element, gst::Object; +} + +// GStreamer elements need to be thread-safe. For the private implementation this is automatically +// enforced but for the public wrapper type we need to specify this manually. +unsafe impl Send for Rgb2Gray {} +unsafe impl Sync for Rgb2Gray {} + +// Registers the type for our element, and then registers in GStreamer under +// the name "rsrgb2gray" for being able to instantiate it via e.g. +// gst::ElementFactory::make(). +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "rsrgb2gray", + gst::Rank::None, + Rgb2Gray::static_type(), + ) +} diff --git a/tutorial/src/sinesrc.rs b/tutorial/src/sinesrc.rs deleted file mode 100644 index ff2e5aff5..000000000 --- a/tutorial/src/sinesrc.rs +++ /dev/null @@ -1,844 +0,0 @@ -// Copyright (C) 2018 Sebastian Dröge -// -// 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. - -use glib::subclass; -use glib::subclass::prelude::*; -use gst::prelude::*; -use gst::subclass::prelude::*; -use gst_base::prelude::*; -use gst_base::subclass::prelude::*; - -use byte_slice_cast::*; - -use std::ops::Rem; -use std::sync::Mutex; -use std::{i32, u32}; - -use num_traits::cast::NumCast; -use num_traits::float::Float; - -use once_cell::sync::Lazy; - -// Default values of properties -const DEFAULT_SAMPLES_PER_BUFFER: u32 = 1024; -const DEFAULT_FREQ: u32 = 440; -const DEFAULT_VOLUME: f64 = 0.8; -const DEFAULT_MUTE: bool = false; -const DEFAULT_IS_LIVE: bool = false; - -// Property value storage -#[derive(Debug, Clone, Copy)] -struct Settings { - samples_per_buffer: u32, - freq: u32, - volume: f64, - mute: bool, - is_live: bool, -} - -impl Default for Settings { - fn default() -> Self { - Settings { - samples_per_buffer: DEFAULT_SAMPLES_PER_BUFFER, - freq: DEFAULT_FREQ, - volume: DEFAULT_VOLUME, - mute: DEFAULT_MUTE, - is_live: DEFAULT_IS_LIVE, - } - } -} - -// Metadata for the properties -static PROPERTIES: [subclass::Property; 5] = [ - subclass::Property("samples-per-buffer", |name| { - glib::ParamSpec::uint( - name, - "Samples Per Buffer", - "Number of samples per output buffer", - 1, - u32::MAX, - DEFAULT_SAMPLES_PER_BUFFER, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("freq", |name| { - glib::ParamSpec::uint( - name, - "Frequency", - "Frequency", - 1, - u32::MAX, - DEFAULT_FREQ, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("volume", |name| { - glib::ParamSpec::double( - name, - "Volume", - "Output volume", - 0.0, - 10.0, - DEFAULT_VOLUME, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("mute", |name| { - glib::ParamSpec::boolean( - name, - "Mute", - "Mute", - DEFAULT_MUTE, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("is-live", |name| { - glib::ParamSpec::boolean( - name, - "Is Live", - "(Pseudo) live output", - DEFAULT_IS_LIVE, - glib::ParamFlags::READWRITE, - ) - }), -]; - -// Stream-specific state, i.e. audio format configuration -// and sample offset -struct State { - info: Option, - sample_offset: u64, - sample_stop: Option, - accumulator: f64, -} - -impl Default for State { - fn default() -> State { - State { - info: None, - sample_offset: 0, - sample_stop: None, - accumulator: 0.0, - } - } -} - -struct ClockWait { - clock_id: Option, - flushing: bool, -} - -// Struct containing all the element data -struct SineSrc { - settings: Mutex, - state: Mutex, - clock_wait: Mutex, -} - -static CAT: Lazy = Lazy::new(|| { - gst::DebugCategory::new( - "rssinesrc", - gst::DebugColorFlags::empty(), - Some("Rust Sine Wave Source"), - ) -}); - -impl SineSrc { - fn process( - data: &mut [u8], - accumulator_ref: &mut f64, - freq: u32, - rate: u32, - channels: u32, - vol: f64, - ) { - use std::f64::consts::PI; - - // Reinterpret our byte-slice as a slice containing elements of the type - // we're interested in. GStreamer requires for raw audio that the alignment - // of memory is correct, so this will never ever fail unless there is an - // actual bug elsewhere. - let data = data.as_mut_slice_of::().unwrap(); - - // Convert all our parameters to the target type for calculations - let vol: F = NumCast::from(vol).unwrap(); - let freq = freq as f64; - let rate = rate as f64; - let two_pi = 2.0 * PI; - - // We're carrying a accumulator with up to 2pi around instead of working - // on the sample offset. High sample offsets cause too much inaccuracy when - // converted to floating point numbers and then iterated over in 1-steps - let mut accumulator = *accumulator_ref; - let step = two_pi * freq / rate; - - for chunk in data.chunks_exact_mut(channels as usize) { - let value = vol * F::sin(NumCast::from(accumulator).unwrap()); - for sample in chunk { - *sample = value; - } - - accumulator += step; - if accumulator >= two_pi { - accumulator -= two_pi; - } - } - - *accumulator_ref = accumulator; - } -} - -// This trait registers our type with the GObject object system and -// provides the entry points for creating a new instance and setting -// up the class data -impl ObjectSubclass for SineSrc { - const NAME: &'static str = "RsSineSrc"; - type ParentType = gst_base::PushSrc; - type Instance = gst::subclass::ElementInstanceStruct; - type Class = subclass::simple::ClassStruct; - - // This macro provides some boilerplate. - glib_object_subclass!(); - - // Called when a new instance is to be created. We need to return an instance - // of our struct here. - fn new() -> Self { - Self { - settings: Mutex::new(Default::default()), - state: Mutex::new(Default::default()), - clock_wait: Mutex::new(ClockWait { - clock_id: None, - flushing: true, - }), - } - } - - // Called exactly once when registering the type. Used for - // setting up metadata for all instances, e.g. the name and - // classification and the pad templates with their caps. - // - // Actual instances can create pads based on those pad templates - // with a subset of the caps given here. In case of basesrc, - // a "src" and "sink" pad template are required here and the base class - // will automatically instantiate pads for them. - // - // Our element here can output f32 and f64 - fn class_init(klass: &mut subclass::simple::ClassStruct) { - // Set the element specific metadata. This information is what - // is visible from gst-inspect-1.0 and can also be programatically - // retrieved from the gst::Registry after initial registration - // without having to load the plugin in memory. - klass.set_metadata( - "Sine Wave Source", - "Source/Audio", - "Creates a sine wave", - "Sebastian Dröge ", - ); - - // Create and add pad templates for our sink and source pad. These - // are later used for actually creating the pads and beforehand - // already provide information to GStreamer about all possible - // pads that could exist for this type. - - // On the src pad, we can produce F32/F64 with any sample rate - // and any number of channels - let caps = gst::Caps::new_simple( - "audio/x-raw", - &[ - ( - "format", - &gst::List::new(&[ - &gst_audio::AUDIO_FORMAT_F32.to_str(), - &gst_audio::AUDIO_FORMAT_F64.to_str(), - ]), - ), - ("layout", &"interleaved"), - ("rate", &gst::IntRange::::new(1, i32::MAX)), - ("channels", &gst::IntRange::::new(1, i32::MAX)), - ], - ); - // The src pad template must be named "src" for basesrc - // and specific a pad that is always there - let src_pad_template = gst::PadTemplate::new( - "src", - gst::PadDirection::Src, - gst::PadPresence::Always, - &caps, - ) - .unwrap(); - klass.add_pad_template(src_pad_template); - - // Install all our properties - klass.install_properties(&PROPERTIES); - } -} - -// Implementation of glib::Object virtual methods -impl ObjectImpl for SineSrc { - // Called right after construction of a new instance - fn constructed(&self, obj: &glib::Object) { - // Call the parent class' ::constructed() implementation first - self.parent_constructed(obj); - - // Initialize live-ness and notify the base class that - // we'd like to operate in Time format - let basesrc = obj.downcast_ref::().unwrap(); - basesrc.set_live(DEFAULT_IS_LIVE); - basesrc.set_format(gst::Format::Time); - } - - // Called whenever a value of a property is changed. It can be called - // at any time from any thread. - fn set_property(&self, obj: &glib::Object, id: usize, value: &glib::Value) { - let prop = &PROPERTIES[id]; - let basesrc = obj.downcast_ref::().unwrap(); - - match *prop { - subclass::Property("samples-per-buffer", ..) => { - let mut settings = self.settings.lock().unwrap(); - let samples_per_buffer = value.get_some().expect("type checked upstream"); - gst_info!( - CAT, - obj: basesrc, - "Changing samples-per-buffer from {} to {}", - settings.samples_per_buffer, - samples_per_buffer - ); - settings.samples_per_buffer = samples_per_buffer; - drop(settings); - - let _ = basesrc.post_message(gst::message::Latency::builder().src(basesrc).build()); - } - subclass::Property("freq", ..) => { - let mut settings = self.settings.lock().unwrap(); - let freq = value.get_some().expect("type checked upstream"); - gst_info!( - CAT, - obj: basesrc, - "Changing freq from {} to {}", - settings.freq, - freq - ); - settings.freq = freq; - } - subclass::Property("volume", ..) => { - let mut settings = self.settings.lock().unwrap(); - let volume = value.get_some().expect("type checked upstream"); - gst_info!( - CAT, - obj: basesrc, - "Changing volume from {} to {}", - settings.volume, - volume - ); - settings.volume = volume; - } - subclass::Property("mute", ..) => { - let mut settings = self.settings.lock().unwrap(); - let mute = value.get_some().expect("type checked upstream"); - gst_info!( - CAT, - obj: basesrc, - "Changing mute from {} to {}", - settings.mute, - mute - ); - settings.mute = mute; - } - subclass::Property("is-live", ..) => { - let mut settings = self.settings.lock().unwrap(); - let is_live = value.get_some().expect("type checked upstream"); - gst_info!( - CAT, - obj: basesrc, - "Changing is-live from {} to {}", - settings.is_live, - is_live - ); - settings.is_live = is_live; - } - _ => unimplemented!(), - } - } - - // Called whenever a value of a property is read. It can be called - // at any time from any thread. - fn get_property(&self, _obj: &glib::Object, id: usize) -> Result { - let prop = &PROPERTIES[id]; - - match *prop { - subclass::Property("samples-per-buffer", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.samples_per_buffer.to_value()) - } - subclass::Property("freq", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.freq.to_value()) - } - subclass::Property("volume", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.volume.to_value()) - } - subclass::Property("mute", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.mute.to_value()) - } - subclass::Property("is-live", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.is_live.to_value()) - } - _ => unimplemented!(), - } - } -} - -// Implementation of gst::Element virtual methods -impl ElementImpl for SineSrc { - // Called whenever the state of the element should be changed. This allows for - // starting up the element, allocating/deallocating resources or shutting down - // the element again. - fn change_state( - &self, - element: &gst::Element, - transition: gst::StateChange, - ) -> Result { - let basesrc = element.downcast_ref::().unwrap(); - - // Configure live'ness once here just before starting the source - if let gst::StateChange::ReadyToPaused = transition { - basesrc.set_live(self.settings.lock().unwrap().is_live); - } - - // Call the parent class' implementation of ::change_state() - self.parent_change_state(element, transition) - } -} - -// Implementation of gst_base::BaseSrc virtual methods -impl BaseSrcImpl for SineSrc { - // Called whenever the input/output caps are changing, i.e. in the very beginning before data - // flow happens and whenever the situation in the pipeline is changing. All buffers after this - // call have the caps given here. - // - // We simply remember the resulting AudioInfo from the caps to be able to use this for knowing - // the sample rate, etc. when creating buffers - fn set_caps( - &self, - element: &gst_base::BaseSrc, - caps: &gst::Caps, - ) -> Result<(), gst::LoggableError> { - use std::f64::consts::PI; - - let info = gst_audio::AudioInfo::from_caps(caps).map_err(|_| { - gst_loggable_error!(CAT, "Failed to build `AudioInfo` from caps {}", caps) - })?; - - gst_debug!(CAT, obj: element, "Configuring for caps {}", caps); - - element.set_blocksize(info.bpf() * (*self.settings.lock().unwrap()).samples_per_buffer); - - let settings = *self.settings.lock().unwrap(); - let mut state = self.state.lock().unwrap(); - - // If we have no caps yet, any old sample_offset and sample_stop will be - // in nanoseconds - let old_rate = match state.info { - Some(ref info) => info.rate() as u64, - None => gst::SECOND_VAL, - }; - - // Update sample offset and accumulator based on the previous values and the - // sample rate change, if any - let old_sample_offset = state.sample_offset; - let sample_offset = old_sample_offset - .mul_div_floor(info.rate() as u64, old_rate) - .unwrap(); - - let old_sample_stop = state.sample_stop; - let sample_stop = - old_sample_stop.map(|v| v.mul_div_floor(info.rate() as u64, old_rate).unwrap()); - - let accumulator = - (sample_offset as f64).rem(2.0 * PI * (settings.freq as f64) / (info.rate() as f64)); - - *state = State { - info: Some(info), - sample_offset, - sample_stop, - accumulator, - }; - - drop(state); - - let _ = element.post_message(gst::message::Latency::builder().src(element).build()); - - Ok(()) - } - - // Called when starting, so we can initialize all stream-related state to its defaults - fn start(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { - // Reset state - *self.state.lock().unwrap() = Default::default(); - self.unlock_stop(element)?; - - gst_info!(CAT, obj: element, "Started"); - - Ok(()) - } - - // Called when shutting down the element so we can release all stream-related state - fn stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { - // Reset state - *self.state.lock().unwrap() = Default::default(); - self.unlock(element)?; - - gst_info!(CAT, obj: element, "Stopped"); - - Ok(()) - } - - fn query(&self, element: &gst_base::BaseSrc, query: &mut gst::QueryRef) -> bool { - use gst::QueryView; - - match query.view_mut() { - // In Live mode we will have a latency equal to the number of samples in each buffer. - // We can't output samples before they were produced, and the last sample of a buffer - // is produced that much after the beginning, leading to this latency calculation - QueryView::Latency(ref mut q) => { - let settings = *self.settings.lock().unwrap(); - let state = self.state.lock().unwrap(); - - if let Some(ref info) = state.info { - let latency = gst::SECOND - .mul_div_floor(settings.samples_per_buffer as u64, info.rate() as u64) - .unwrap(); - gst_debug!(CAT, obj: element, "Returning latency {}", latency); - q.set(settings.is_live, latency, gst::CLOCK_TIME_NONE); - true - } else { - false - } - } - _ => BaseSrcImplExt::parent_query(self, element, query), - } - } - - fn fixate(&self, element: &gst_base::BaseSrc, mut caps: gst::Caps) -> gst::Caps { - // Fixate the caps. BaseSrc will do some fixation for us, but - // as we allow any rate between 1 and MAX it would fixate to 1. 1Hz - // is generally not a useful sample rate. - // - // We fixate to the closest integer value to 48kHz that is possible - // here, and for good measure also decide that the closest value to 1 - // channel is good. - caps.truncate(); - { - let caps = caps.make_mut(); - let s = caps.get_mut_structure(0).unwrap(); - s.fixate_field_nearest_int("rate", 48_000); - s.fixate_field_nearest_int("channels", 1); - } - - // Let BaseSrc fixate anything else for us. We could've alternatively have - // called caps.fixate() here - self.parent_fixate(element, caps) - } - - fn is_seekable(&self, _element: &gst_base::BaseSrc) -> bool { - true - } - - fn do_seek(&self, element: &gst_base::BaseSrc, segment: &mut gst::Segment) -> bool { - // Handle seeking here. For Time and Default (sample offset) seeks we can - // do something and have to update our sample offset and accumulator accordingly. - // - // Also we should remember the stop time (so we can stop at that point), and if - // reverse playback is requested. These values will all be used during buffer creation - // and for calculating the timestamps, etc. - - if segment.get_rate() < 0.0 { - gst_error!(CAT, obj: element, "Reverse playback not supported"); - return false; - } - - let settings = *self.settings.lock().unwrap(); - let mut state = self.state.lock().unwrap(); - - // We store sample_offset and sample_stop in nanoseconds if we - // don't know any sample rate yet. It will be converted correctly - // once a sample rate is known. - let rate = match state.info { - None => gst::SECOND_VAL, - Some(ref info) => info.rate() as u64, - }; - - if let Some(segment) = segment.downcast_ref::() { - use std::f64::consts::PI; - - let sample_offset = segment - .get_start() - .unwrap() - .mul_div_floor(rate, gst::SECOND_VAL) - .unwrap(); - - let sample_stop = segment - .get_stop() - .map(|v| v.mul_div_floor(rate, gst::SECOND_VAL).unwrap()); - - let accumulator = - (sample_offset as f64).rem(2.0 * PI * (settings.freq as f64) / (rate as f64)); - - gst_debug!( - CAT, - obj: element, - "Seeked to {}-{:?} (accum: {}) for segment {:?}", - sample_offset, - sample_stop, - accumulator, - segment - ); - - *state = State { - info: state.info.clone(), - sample_offset, - sample_stop, - accumulator, - }; - - true - } else if let Some(segment) = segment.downcast_ref::() { - use std::f64::consts::PI; - - if state.info.is_none() { - gst_error!( - CAT, - obj: element, - "Can only seek in Default format if sample rate is known" - ); - return false; - } - - let sample_offset = segment.get_start().unwrap(); - let sample_stop = segment.get_stop().0; - - let accumulator = - (sample_offset as f64).rem(2.0 * PI * (settings.freq as f64) / (rate as f64)); - - gst_debug!( - CAT, - obj: element, - "Seeked to {}-{:?} (accum: {}) for segment {:?}", - sample_offset, - sample_stop, - accumulator, - segment - ); - - *state = State { - info: state.info.clone(), - sample_offset, - sample_stop, - accumulator, - }; - - true - } else { - gst_error!( - CAT, - obj: element, - "Can't seek in format {:?}", - segment.get_format() - ); - - false - } - } - - fn unlock(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { - // This should unblock the create() function ASAP, so we - // just unschedule the clock it here, if any. - gst_debug!(CAT, obj: element, "Unlocking"); - let mut clock_wait = self.clock_wait.lock().unwrap(); - if let Some(clock_id) = clock_wait.clock_id.take() { - clock_id.unschedule(); - } - clock_wait.flushing = true; - - Ok(()) - } - - fn unlock_stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { - // This signals that unlocking is done, so we can reset - // all values again. - gst_debug!(CAT, obj: element, "Unlock stop"); - let mut clock_wait = self.clock_wait.lock().unwrap(); - clock_wait.flushing = false; - - Ok(()) - } -} - -impl PushSrcImpl for SineSrc { - // Creates the audio buffers - fn create(&self, element: &gst_base::PushSrc) -> Result { - // Keep a local copy of the values of all our properties at this very moment. This - // ensures that the mutex is never locked for long and the application wouldn't - // have to block until this function returns when getting/setting property values - let settings = *self.settings.lock().unwrap(); - - // Get a locked reference to our state, i.e. the input and output AudioInfo - let mut state = self.state.lock().unwrap(); - let info = match state.info { - None => { - gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); - return Err(gst::FlowError::NotNegotiated); - } - Some(ref info) => info.clone(), - }; - - // If a stop position is set (from a seek), only produce samples up to that - // point but at most samples_per_buffer samples per buffer - let n_samples = if let Some(sample_stop) = state.sample_stop { - if sample_stop <= state.sample_offset { - gst_log!(CAT, obj: element, "At EOS"); - return Err(gst::FlowError::Eos); - } - - sample_stop - state.sample_offset - } else { - settings.samples_per_buffer as u64 - }; - - // Allocate a new buffer of the required size, update the metadata with the - // current timestamp and duration and then fill it according to the current - // caps - let mut buffer = - gst::Buffer::with_size((n_samples as usize) * (info.bpf() as usize)).unwrap(); - { - let buffer = buffer.get_mut().unwrap(); - - // Calculate the current timestamp (PTS) and the next one, - // and calculate the duration from the difference instead of - // simply the number of samples to prevent rounding errors - let pts = state - .sample_offset - .mul_div_floor(gst::SECOND_VAL, info.rate() as u64) - .unwrap() - .into(); - let next_pts: gst::ClockTime = (state.sample_offset + n_samples) - .mul_div_floor(gst::SECOND_VAL, info.rate() as u64) - .unwrap() - .into(); - buffer.set_pts(pts); - buffer.set_duration(next_pts - pts); - - // Map the buffer writable and create the actual samples - let mut map = buffer.map_writable().unwrap(); - let data = map.as_mut_slice(); - - if info.format() == gst_audio::AUDIO_FORMAT_F32 { - Self::process::( - data, - &mut state.accumulator, - settings.freq, - info.rate(), - info.channels(), - settings.volume, - ); - } else { - Self::process::( - data, - &mut state.accumulator, - settings.freq, - info.rate(), - info.channels(), - settings.volume, - ); - } - } - state.sample_offset += n_samples; - drop(state); - - // If we're live, we are waiting until the time of the last sample in our buffer has - // arrived. This is the very reason why we have to report that much latency. - // A real live-source would of course only allow us to have the data available after - // that latency, e.g. when capturing from a microphone, and no waiting from our side - // would be necessary.. - // - // Waiting happens based on the pipeline clock, which means that a real live source - // with its own clock would require various translations between the two clocks. - // This is out of scope for the tutorial though. - if element.is_live() { - let clock = match element.get_clock() { - None => return Ok(buffer), - Some(clock) => clock, - }; - - let segment = element - .get_segment() - .downcast::() - .unwrap(); - let base_time = element.get_base_time(); - let running_time = segment.to_running_time(buffer.get_pts() + buffer.get_duration()); - - // The last sample's clock time is the base time of the element plus the - // running time of the last sample - let wait_until = running_time + base_time; - if wait_until.is_none() { - return Ok(buffer); - } - - // Store the clock ID in our struct unless we're flushing anyway. - // This allows to asynchronously cancel the waiting from unlock() - // so that we immediately stop waiting on e.g. shutdown. - let mut clock_wait = self.clock_wait.lock().unwrap(); - if clock_wait.flushing { - gst_debug!(CAT, obj: element, "Flushing"); - return Err(gst::FlowError::Flushing); - } - - let id = clock.new_single_shot_id(wait_until); - clock_wait.clock_id = Some(id.clone()); - drop(clock_wait); - - gst_log!( - CAT, - obj: element, - "Waiting until {}, now {}", - wait_until, - clock.get_time() - ); - let (res, jitter) = id.wait(); - gst_log!(CAT, obj: element, "Waited res {:?} jitter {}", res, jitter); - self.clock_wait.lock().unwrap().clock_id.take(); - - // If the clock ID was unscheduled, unlock() was called - // and we should return Flushing immediately. - if res == Err(gst::ClockError::Unscheduled) { - gst_debug!(CAT, obj: element, "Flushing"); - return Err(gst::FlowError::Flushing); - } - } - - gst_debug!(CAT, obj: element, "Produced buffer {:?}", buffer); - - Ok(buffer) - } -} - -// Registers the type for our element, and then registers in GStreamer under -// the name "sinesrc" for being able to instantiate it via e.g. -// gst::ElementFactory::make(). -pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { - gst::Element::register( - Some(plugin), - "rssinesrc", - gst::Rank::None, - SineSrc::get_type(), - ) -} diff --git a/tutorial/src/sinesrc/imp.rs b/tutorial/src/sinesrc/imp.rs new file mode 100644 index 000000000..6f4b933fd --- /dev/null +++ b/tutorial/src/sinesrc/imp.rs @@ -0,0 +1,827 @@ +// Copyright (C) 2018 Sebastian Dröge +// +// 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. + +use glib::subclass; +use glib::subclass::prelude::*; +use gst::prelude::*; +use gst::subclass::prelude::*; +use gst_base::prelude::*; +use gst_base::subclass::prelude::*; + +use byte_slice_cast::*; + +use std::ops::Rem; +use std::sync::Mutex; +use std::{i32, u32}; + +use num_traits::cast::NumCast; +use num_traits::float::Float; + +use once_cell::sync::Lazy; + +// This module contains the private implementation details of our element + +static CAT: Lazy = Lazy::new(|| { + gst::DebugCategory::new( + "rssinesrc", + gst::DebugColorFlags::empty(), + Some("Rust Sine Wave Source"), + ) +}); + +// Default values of properties +const DEFAULT_SAMPLES_PER_BUFFER: u32 = 1024; +const DEFAULT_FREQ: u32 = 440; +const DEFAULT_VOLUME: f64 = 0.8; +const DEFAULT_MUTE: bool = false; +const DEFAULT_IS_LIVE: bool = false; + +// Property value storage +#[derive(Debug, Clone, Copy)] +struct Settings { + samples_per_buffer: u32, + freq: u32, + volume: f64, + mute: bool, + is_live: bool, +} + +impl Default for Settings { + fn default() -> Self { + Settings { + samples_per_buffer: DEFAULT_SAMPLES_PER_BUFFER, + freq: DEFAULT_FREQ, + volume: DEFAULT_VOLUME, + mute: DEFAULT_MUTE, + is_live: DEFAULT_IS_LIVE, + } + } +} + +// Metadata for the properties +static PROPERTIES: [subclass::Property; 5] = [ + subclass::Property("samples-per-buffer", |name| { + glib::ParamSpec::uint( + name, + "Samples Per Buffer", + "Number of samples per output buffer", + 1, + u32::MAX, + DEFAULT_SAMPLES_PER_BUFFER, + glib::ParamFlags::READWRITE, + ) + }), + subclass::Property("freq", |name| { + glib::ParamSpec::uint( + name, + "Frequency", + "Frequency", + 1, + u32::MAX, + DEFAULT_FREQ, + glib::ParamFlags::READWRITE, + ) + }), + subclass::Property("volume", |name| { + glib::ParamSpec::double( + name, + "Volume", + "Output volume", + 0.0, + 10.0, + DEFAULT_VOLUME, + glib::ParamFlags::READWRITE, + ) + }), + subclass::Property("mute", |name| { + glib::ParamSpec::boolean( + name, + "Mute", + "Mute", + DEFAULT_MUTE, + glib::ParamFlags::READWRITE, + ) + }), + subclass::Property("is-live", |name| { + glib::ParamSpec::boolean( + name, + "Is Live", + "(Pseudo) live output", + DEFAULT_IS_LIVE, + glib::ParamFlags::READWRITE, + ) + }), +]; + +// Stream-specific state, i.e. audio format configuration +// and sample offset +struct State { + info: Option, + sample_offset: u64, + sample_stop: Option, + accumulator: f64, +} + +impl Default for State { + fn default() -> State { + State { + info: None, + sample_offset: 0, + sample_stop: None, + accumulator: 0.0, + } + } +} + +struct ClockWait { + clock_id: Option, + flushing: bool, +} + +// Struct containing all the element data +pub struct SineSrc { + settings: Mutex, + state: Mutex, + clock_wait: Mutex, +} + +impl SineSrc { + fn process( + data: &mut [u8], + accumulator_ref: &mut f64, + freq: u32, + rate: u32, + channels: u32, + vol: f64, + ) { + use std::f64::consts::PI; + + // Reinterpret our byte-slice as a slice containing elements of the type + // we're interested in. GStreamer requires for raw audio that the alignment + // of memory is correct, so this will never ever fail unless there is an + // actual bug elsewhere. + let data = data.as_mut_slice_of::().unwrap(); + + // Convert all our parameters to the target type for calculations + let vol: F = NumCast::from(vol).unwrap(); + let freq = freq as f64; + let rate = rate as f64; + let two_pi = 2.0 * PI; + + // We're carrying a accumulator with up to 2pi around instead of working + // on the sample offset. High sample offsets cause too much inaccuracy when + // converted to floating point numbers and then iterated over in 1-steps + let mut accumulator = *accumulator_ref; + let step = two_pi * freq / rate; + + for chunk in data.chunks_exact_mut(channels as usize) { + let value = vol * F::sin(NumCast::from(accumulator).unwrap()); + for sample in chunk { + *sample = value; + } + + accumulator += step; + if accumulator >= two_pi { + accumulator -= two_pi; + } + } + + *accumulator_ref = accumulator; + } +} + +// This trait registers our type with the GObject object system and +// provides the entry points for creating a new instance and setting +// up the class data +impl ObjectSubclass for SineSrc { + const NAME: &'static str = "RsSineSrc"; + type Type = super::SineSrc; + type ParentType = gst_base::PushSrc; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = subclass::simple::ClassStruct; + + // This macro provides some boilerplate. + glib_object_subclass!(); + + // Called when a new instance is to be created. We need to return an instance + // of our struct here. + fn new() -> Self { + Self { + settings: Mutex::new(Default::default()), + state: Mutex::new(Default::default()), + clock_wait: Mutex::new(ClockWait { + clock_id: None, + flushing: true, + }), + } + } + + // Called exactly once when registering the type. Used for + // setting up metadata for all instances, e.g. the name and + // classification and the pad templates with their caps. + // + // Actual instances can create pads based on those pad templates + // with a subset of the caps given here. In case of basesrc, + // a "src" and "sink" pad template are required here and the base class + // will automatically instantiate pads for them. + // + // Our element here can output f32 and f64 + fn class_init(klass: &mut Self::Class) { + // Set the element specific metadata. This information is what + // is visible from gst-inspect-1.0 and can also be programatically + // retrieved from the gst::Registry after initial registration + // without having to load the plugin in memory. + klass.set_metadata( + "Sine Wave Source", + "Source/Audio", + "Creates a sine wave", + "Sebastian Dröge ", + ); + + // Create and add pad templates for our sink and source pad. These + // are later used for actually creating the pads and beforehand + // already provide information to GStreamer about all possible + // pads that could exist for this type. + + // On the src pad, we can produce F32/F64 with any sample rate + // and any number of channels + let caps = gst::Caps::new_simple( + "audio/x-raw", + &[ + ( + "format", + &gst::List::new(&[ + &gst_audio::AUDIO_FORMAT_F32.to_str(), + &gst_audio::AUDIO_FORMAT_F64.to_str(), + ]), + ), + ("layout", &"interleaved"), + ("rate", &gst::IntRange::::new(1, i32::MAX)), + ("channels", &gst::IntRange::::new(1, i32::MAX)), + ], + ); + // The src pad template must be named "src" for basesrc + // and specific a pad that is always there + let src_pad_template = gst::PadTemplate::new( + "src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &caps, + ) + .unwrap(); + klass.add_pad_template(src_pad_template); + + // Install all our properties + klass.install_properties(&PROPERTIES); + } +} + +// Implementation of glib::Object virtual methods +impl ObjectImpl for SineSrc { + // Called right after construction of a new instance + fn constructed(&self, obj: &Self::Type) { + // Call the parent class' ::constructed() implementation first + self.parent_constructed(obj); + + // Initialize live-ness and notify the base class that + // we'd like to operate in Time format + obj.set_live(DEFAULT_IS_LIVE); + obj.set_format(gst::Format::Time); + } + + // Called whenever a value of a property is changed. It can be called + // at any time from any thread. + fn set_property(&self, obj: &Self::Type, id: usize, value: &glib::Value) { + let prop = &PROPERTIES[id]; + + match *prop { + subclass::Property("samples-per-buffer", ..) => { + let mut settings = self.settings.lock().unwrap(); + let samples_per_buffer = value.get_some().expect("type checked upstream"); + gst_info!( + CAT, + obj: obj, + "Changing samples-per-buffer from {} to {}", + settings.samples_per_buffer, + samples_per_buffer + ); + settings.samples_per_buffer = samples_per_buffer; + drop(settings); + + let _ = obj.post_message(gst::message::Latency::builder().src(obj).build()); + } + subclass::Property("freq", ..) => { + let mut settings = self.settings.lock().unwrap(); + let freq = value.get_some().expect("type checked upstream"); + gst_info!( + CAT, + obj: obj, + "Changing freq from {} to {}", + settings.freq, + freq + ); + settings.freq = freq; + } + subclass::Property("volume", ..) => { + let mut settings = self.settings.lock().unwrap(); + let volume = value.get_some().expect("type checked upstream"); + gst_info!( + CAT, + obj: obj, + "Changing volume from {} to {}", + settings.volume, + volume + ); + settings.volume = volume; + } + subclass::Property("mute", ..) => { + let mut settings = self.settings.lock().unwrap(); + let mute = value.get_some().expect("type checked upstream"); + gst_info!( + CAT, + obj: obj, + "Changing mute from {} to {}", + settings.mute, + mute + ); + settings.mute = mute; + } + subclass::Property("is-live", ..) => { + let mut settings = self.settings.lock().unwrap(); + let is_live = value.get_some().expect("type checked upstream"); + gst_info!( + CAT, + obj: obj, + "Changing is-live from {} to {}", + settings.is_live, + is_live + ); + settings.is_live = is_live; + } + _ => unimplemented!(), + } + } + + // Called whenever a value of a property is read. It can be called + // at any time from any thread. + fn get_property(&self, _obj: &Self::Type, id: usize) -> Result { + let prop = &PROPERTIES[id]; + + match *prop { + subclass::Property("samples-per-buffer", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.samples_per_buffer.to_value()) + } + subclass::Property("freq", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.freq.to_value()) + } + subclass::Property("volume", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.volume.to_value()) + } + subclass::Property("mute", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.mute.to_value()) + } + subclass::Property("is-live", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.is_live.to_value()) + } + _ => unimplemented!(), + } + } +} + +// Implementation of gst::Element virtual methods +impl ElementImpl for SineSrc { + // Called whenever the state of the element should be changed. This allows for + // starting up the element, allocating/deallocating resources or shutting down + // the element again. + fn change_state( + &self, + element: &Self::Type, + transition: gst::StateChange, + ) -> Result { + // Configure live'ness once here just before starting the source + if let gst::StateChange::ReadyToPaused = transition { + element.set_live(self.settings.lock().unwrap().is_live); + } + + // Call the parent class' implementation of ::change_state() + self.parent_change_state(element, transition) + } +} + +// Implementation of gst_base::BaseSrc virtual methods +impl BaseSrcImpl for SineSrc { + // Called whenever the input/output caps are changing, i.e. in the very beginning before data + // flow happens and whenever the situation in the pipeline is changing. All buffers after this + // call have the caps given here. + // + // We simply remember the resulting AudioInfo from the caps to be able to use this for knowing + // the sample rate, etc. when creating buffers + fn set_caps(&self, element: &Self::Type, caps: &gst::Caps) -> Result<(), gst::LoggableError> { + use std::f64::consts::PI; + + let info = gst_audio::AudioInfo::from_caps(caps).map_err(|_| { + gst_loggable_error!(CAT, "Failed to build `AudioInfo` from caps {}", caps) + })?; + + gst_debug!(CAT, obj: element, "Configuring for caps {}", caps); + + element.set_blocksize(info.bpf() * (*self.settings.lock().unwrap()).samples_per_buffer); + + let settings = *self.settings.lock().unwrap(); + let mut state = self.state.lock().unwrap(); + + // If we have no caps yet, any old sample_offset and sample_stop will be + // in nanoseconds + let old_rate = match state.info { + Some(ref info) => info.rate() as u64, + None => gst::SECOND_VAL, + }; + + // Update sample offset and accumulator based on the previous values and the + // sample rate change, if any + let old_sample_offset = state.sample_offset; + let sample_offset = old_sample_offset + .mul_div_floor(info.rate() as u64, old_rate) + .unwrap(); + + let old_sample_stop = state.sample_stop; + let sample_stop = + old_sample_stop.map(|v| v.mul_div_floor(info.rate() as u64, old_rate).unwrap()); + + let accumulator = + (sample_offset as f64).rem(2.0 * PI * (settings.freq as f64) / (info.rate() as f64)); + + *state = State { + info: Some(info), + sample_offset, + sample_stop, + accumulator, + }; + + drop(state); + + let _ = element.post_message(gst::message::Latency::builder().src(element).build()); + + Ok(()) + } + + // Called when starting, so we can initialize all stream-related state to its defaults + fn start(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> { + // Reset state + *self.state.lock().unwrap() = Default::default(); + self.unlock_stop(element)?; + + gst_info!(CAT, obj: element, "Started"); + + Ok(()) + } + + // Called when shutting down the element so we can release all stream-related state + fn stop(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> { + // Reset state + *self.state.lock().unwrap() = Default::default(); + self.unlock(element)?; + + gst_info!(CAT, obj: element, "Stopped"); + + Ok(()) + } + + fn query(&self, element: &Self::Type, query: &mut gst::QueryRef) -> bool { + use gst::QueryView; + + match query.view_mut() { + // In Live mode we will have a latency equal to the number of samples in each buffer. + // We can't output samples before they were produced, and the last sample of a buffer + // is produced that much after the beginning, leading to this latency calculation + QueryView::Latency(ref mut q) => { + let settings = *self.settings.lock().unwrap(); + let state = self.state.lock().unwrap(); + + if let Some(ref info) = state.info { + let latency = gst::SECOND + .mul_div_floor(settings.samples_per_buffer as u64, info.rate() as u64) + .unwrap(); + gst_debug!(CAT, obj: element, "Returning latency {}", latency); + q.set(settings.is_live, latency, gst::CLOCK_TIME_NONE); + true + } else { + false + } + } + _ => BaseSrcImplExt::parent_query(self, element, query), + } + } + + fn fixate(&self, element: &Self::Type, mut caps: gst::Caps) -> gst::Caps { + // Fixate the caps. BaseSrc will do some fixation for us, but + // as we allow any rate between 1 and MAX it would fixate to 1. 1Hz + // is generally not a useful sample rate. + // + // We fixate to the closest integer value to 48kHz that is possible + // here, and for good measure also decide that the closest value to 1 + // channel is good. + caps.truncate(); + { + let caps = caps.make_mut(); + let s = caps.get_mut_structure(0).unwrap(); + s.fixate_field_nearest_int("rate", 48_000); + s.fixate_field_nearest_int("channels", 1); + } + + // Let BaseSrc fixate anything else for us. We could've alternatively have + // called caps.fixate() here + self.parent_fixate(element, caps) + } + + fn is_seekable(&self, _element: &Self::Type) -> bool { + true + } + + fn do_seek(&self, element: &Self::Type, segment: &mut gst::Segment) -> bool { + // Handle seeking here. For Time and Default (sample offset) seeks we can + // do something and have to update our sample offset and accumulator accordingly. + // + // Also we should remember the stop time (so we can stop at that point), and if + // reverse playback is requested. These values will all be used during buffer creation + // and for calculating the timestamps, etc. + + if segment.get_rate() < 0.0 { + gst_error!(CAT, obj: element, "Reverse playback not supported"); + return false; + } + + let settings = *self.settings.lock().unwrap(); + let mut state = self.state.lock().unwrap(); + + // We store sample_offset and sample_stop in nanoseconds if we + // don't know any sample rate yet. It will be converted correctly + // once a sample rate is known. + let rate = match state.info { + None => gst::SECOND_VAL, + Some(ref info) => info.rate() as u64, + }; + + if let Some(segment) = segment.downcast_ref::() { + use std::f64::consts::PI; + + let sample_offset = segment + .get_start() + .unwrap() + .mul_div_floor(rate, gst::SECOND_VAL) + .unwrap(); + + let sample_stop = segment + .get_stop() + .map(|v| v.mul_div_floor(rate, gst::SECOND_VAL).unwrap()); + + let accumulator = + (sample_offset as f64).rem(2.0 * PI * (settings.freq as f64) / (rate as f64)); + + gst_debug!( + CAT, + obj: element, + "Seeked to {}-{:?} (accum: {}) for segment {:?}", + sample_offset, + sample_stop, + accumulator, + segment + ); + + *state = State { + info: state.info.clone(), + sample_offset, + sample_stop, + accumulator, + }; + + true + } else if let Some(segment) = segment.downcast_ref::() { + use std::f64::consts::PI; + + if state.info.is_none() { + gst_error!( + CAT, + obj: element, + "Can only seek in Default format if sample rate is known" + ); + return false; + } + + let sample_offset = segment.get_start().unwrap(); + let sample_stop = segment.get_stop().0; + + let accumulator = + (sample_offset as f64).rem(2.0 * PI * (settings.freq as f64) / (rate as f64)); + + gst_debug!( + CAT, + obj: element, + "Seeked to {}-{:?} (accum: {}) for segment {:?}", + sample_offset, + sample_stop, + accumulator, + segment + ); + + *state = State { + info: state.info.clone(), + sample_offset, + sample_stop, + accumulator, + }; + + true + } else { + gst_error!( + CAT, + obj: element, + "Can't seek in format {:?}", + segment.get_format() + ); + + false + } + } + + fn unlock(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> { + // This should unblock the create() function ASAP, so we + // just unschedule the clock it here, if any. + gst_debug!(CAT, obj: element, "Unlocking"); + let mut clock_wait = self.clock_wait.lock().unwrap(); + if let Some(clock_id) = clock_wait.clock_id.take() { + clock_id.unschedule(); + } + clock_wait.flushing = true; + + Ok(()) + } + + fn unlock_stop(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> { + // This signals that unlocking is done, so we can reset + // all values again. + gst_debug!(CAT, obj: element, "Unlock stop"); + let mut clock_wait = self.clock_wait.lock().unwrap(); + clock_wait.flushing = false; + + Ok(()) + } +} + +impl PushSrcImpl for SineSrc { + // Creates the audio buffers + fn create(&self, element: &Self::Type) -> Result { + // Keep a local copy of the values of all our properties at this very moment. This + // ensures that the mutex is never locked for long and the application wouldn't + // have to block until this function returns when getting/setting property values + let settings = *self.settings.lock().unwrap(); + + // Get a locked reference to our state, i.e. the input and output AudioInfo + let mut state = self.state.lock().unwrap(); + let info = match state.info { + None => { + gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); + return Err(gst::FlowError::NotNegotiated); + } + Some(ref info) => info.clone(), + }; + + // If a stop position is set (from a seek), only produce samples up to that + // point but at most samples_per_buffer samples per buffer + let n_samples = if let Some(sample_stop) = state.sample_stop { + if sample_stop <= state.sample_offset { + gst_log!(CAT, obj: element, "At EOS"); + return Err(gst::FlowError::Eos); + } + + sample_stop - state.sample_offset + } else { + settings.samples_per_buffer as u64 + }; + + // Allocate a new buffer of the required size, update the metadata with the + // current timestamp and duration and then fill it according to the current + // caps + let mut buffer = + gst::Buffer::with_size((n_samples as usize) * (info.bpf() as usize)).unwrap(); + { + let buffer = buffer.get_mut().unwrap(); + + // Calculate the current timestamp (PTS) and the next one, + // and calculate the duration from the difference instead of + // simply the number of samples to prevent rounding errors + let pts = state + .sample_offset + .mul_div_floor(gst::SECOND_VAL, info.rate() as u64) + .unwrap() + .into(); + let next_pts: gst::ClockTime = (state.sample_offset + n_samples) + .mul_div_floor(gst::SECOND_VAL, info.rate() as u64) + .unwrap() + .into(); + buffer.set_pts(pts); + buffer.set_duration(next_pts - pts); + + // Map the buffer writable and create the actual samples + let mut map = buffer.map_writable().unwrap(); + let data = map.as_mut_slice(); + + if info.format() == gst_audio::AUDIO_FORMAT_F32 { + Self::process::( + data, + &mut state.accumulator, + settings.freq, + info.rate(), + info.channels(), + settings.volume, + ); + } else { + Self::process::( + data, + &mut state.accumulator, + settings.freq, + info.rate(), + info.channels(), + settings.volume, + ); + } + } + state.sample_offset += n_samples; + drop(state); + + // If we're live, we are waiting until the time of the last sample in our buffer has + // arrived. This is the very reason why we have to report that much latency. + // A real live-source would of course only allow us to have the data available after + // that latency, e.g. when capturing from a microphone, and no waiting from our side + // would be necessary.. + // + // Waiting happens based on the pipeline clock, which means that a real live source + // with its own clock would require various translations between the two clocks. + // This is out of scope for the tutorial though. + if element.is_live() { + let clock = match element.get_clock() { + None => return Ok(buffer), + Some(clock) => clock, + }; + + let segment = element + .get_segment() + .downcast::() + .unwrap(); + let base_time = element.get_base_time(); + let running_time = segment.to_running_time(buffer.get_pts() + buffer.get_duration()); + + // The last sample's clock time is the base time of the element plus the + // running time of the last sample + let wait_until = running_time + base_time; + if wait_until.is_none() { + return Ok(buffer); + } + + // Store the clock ID in our struct unless we're flushing anyway. + // This allows to asynchronously cancel the waiting from unlock() + // so that we immediately stop waiting on e.g. shutdown. + let mut clock_wait = self.clock_wait.lock().unwrap(); + if clock_wait.flushing { + gst_debug!(CAT, obj: element, "Flushing"); + return Err(gst::FlowError::Flushing); + } + + let id = clock.new_single_shot_id(wait_until); + clock_wait.clock_id = Some(id.clone()); + drop(clock_wait); + + gst_log!( + CAT, + obj: element, + "Waiting until {}, now {}", + wait_until, + clock.get_time() + ); + let (res, jitter) = id.wait(); + gst_log!(CAT, obj: element, "Waited res {:?} jitter {}", res, jitter); + self.clock_wait.lock().unwrap().clock_id.take(); + + // If the clock ID was unscheduled, unlock() was called + // and we should return Flushing immediately. + if res == Err(gst::ClockError::Unscheduled) { + gst_debug!(CAT, obj: element, "Flushing"); + return Err(gst::FlowError::Flushing); + } + } + + gst_debug!(CAT, obj: element, "Produced buffer {:?}", buffer); + + Ok(buffer) + } +} diff --git a/tutorial/src/sinesrc/mod.rs b/tutorial/src/sinesrc/mod.rs new file mode 100644 index 000000000..7baf92280 --- /dev/null +++ b/tutorial/src/sinesrc/mod.rs @@ -0,0 +1,33 @@ +// Copyright (C) 2020 Sebastian Dröge +// +// 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. + +use glib::prelude::*; + +mod imp; + +// The public Rust wrapper type for our element +glib_wrapper! { + pub struct SineSrc(ObjectSubclass) @extends gst_base::BaseSrc, gst::Element, gst::Object; +} + +// GStreamer elements need to be thread-safe. For the private implementation this is automatically +// enforced but for the public wrapper type we need to specify this manually. +unsafe impl Send for SineSrc {} +unsafe impl Sync for SineSrc {} + +// Registers the type for our element, and then registers in GStreamer under +// the name "sinesrc" for being able to instantiate it via e.g. +// gst::ElementFactory::make(). +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "rssinesrc", + gst::Rank::None, + SineSrc::static_type(), + ) +} diff --git a/tutorial/tutorial-1.md b/tutorial/tutorial-1.md index 3f0933e78..39ade8afb 100644 --- a/tutorial/tutorial-1.md +++ b/tutorial/tutorial-1.md @@ -153,7 +153,7 @@ fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { } ``` -With that our `src/lib.rs` is complete, and all following code is only in `src/rgb2gray.rs`. At the top of the new file we first need to add various `use-directives` to import various types and functions we’re going to use into the current module’s scope. +With that our `src/lib.rs` is complete, and all following code is only in `src/rgb2gray/imp.rs`. At the top of the new file we first need to add various `use-directives` to import various types and functions we’re going to use into the current module’s scope. ```rust use glib; @@ -178,12 +178,13 @@ GStreamer is based on the GLib object system ([GObject](https://developer.gnome. So, as a next step we need to register a new type for our RGB to Grayscale converter GStreamer element with the GObject type system, and then register that type with GStreamer to be able to create new instances of it. We do this with the following code ```rust -struct Rgb2Gray{} +pub struct Rgb2Gray{} impl Rgb2Gray{} impl ObjectSubclass for Rgb2Gray { const NAME: &'static str = "RsRgb2Gray"; + type Type = super::Rgb2Gray; type ParentType = gst_base::BaseTransform; type Instance = gst::subclass::ElementInstanceStruct; type Class = subclass::simple::ClassStruct; @@ -200,29 +201,50 @@ impl ObjectSubclass for Rgb2Gray { } } +This defines a struct `Rgb2Gray` which is empty for now and an empty implementation of the struct which will later be used. The `ObjectSubclass` trait is implemented on the struct `Rgb2Gray` for providing static information about the type to the type system. By implementing `ObjectSubclass` we allow registering our struct with the GObject object system. + +`ObjectSubclass` has an associated constant which contains the name of the type, some associated types, and functions for initializing/returning a new instance of our element (`new`) and for initializing the class metadata (`class_init`, more on that later). We simply let those functions proxy to associated functions on the `Rgb2Gray` struct that we’re going to define at a later time. + +We also add the following code is in `src/rgb2gray/mod.rs`: + +```rust +use glib::prelude::*; + +mod imp; + +// The public Rust wrapper type for our element +glib_wrapper! { + pub struct Rgb2Gray(ObjectSubclass) @extends gst_base::BaseTransform, gst::Element, gst::Object; +} + +// GStreamer elements need to be thread-safe. For the private implementation this is automatically +// enforced but for the public wrapper type we need to specify this manually. +unsafe impl Send for Rgb2Gray {} +unsafe impl Sync for Rgb2Gray {} + +// Registers the type for our element, and then registers in GStreamer under +// the name "rsrgb2gray" for being able to instantiate it via e.g. +// gst::ElementFactory::make(). pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { gst::Element::register( Some(plugin), "rsrgb2gray", gst::Rank::None, - Rgb2Gray::get_type(), + Rgb2Gray::static_type(), ) } - ``` -This defines a struct `Rgb2Gray` which is empty for now and an empty implementation of the struct which will later be used. The `ObjectSubclass` trait is implemented on the struct `Rgb2Gray` for providing static information about the type to the type system. By implementing `ObjectSubclass` we allow registering our struct with the GObject object system. - -`ObjectSubclass` has an associated constant which contains the name of the type, some associated types, and functions for initializing/returning a new instance of our element (`new`) and for initializing the class metadata (`class_init`, more on that later). We simply let those functions proxy to associated functions on the `Rgb2Gray` struct that we’re going to define at a later time. +This defines a Rust wrapper type for our element subclass. This is similar to `gst::Element` and others and provides the public interface to our element. -In addition, we also define a `register` function (the one that is already called from our `plugin_init` function). When `register` function is called it registers the element factory with GStreamer based on the type ID, to be able to create new instances of it with the name “rsrgb2gray” (e.g. when using [`gst::ElementFactory::make`](https://slomo.pages.freedesktop.org/rustdocs/gstreamer/gstreamer/struct.ElementFactory.html#method.make)). The `get_type` function will register the type with the GObject type system on the first call and the next time it's called (or on all the following calls) it will return the type ID. +In addition, we also define a `register` function (the one that is already called from our `plugin_init` function). When `register` function is called it registers the element factory with GStreamer based on the type ID, to be able to create new instances of it with the name “rsrgb2gray” (e.g. when using [`gst::ElementFactory::make`](https://slomo.pages.freedesktop.org/rustdocs/gstreamer/gstreamer/struct.ElementFactory.html#method.make)). The `static_type` function will register the type with the GObject type system on the first call and the next time it's called (or on all the following calls) it will return the type ID. ## Type Class & Instance Initialization As a next step we implement the `new` funtion and `class_init` functions. In the first version, this struct is empty for now but we will later use it to store all state of our element. ```rust -struct Rgb2Gray { +pub struct Rgb2Gray { } impl Rgb2Gray{} @@ -235,7 +257,7 @@ impl ObjectSubclass for Rgb2Gray { } } - fn class_init(klass: &mut subclass::simple::ClassStruct) { + fn class_init(klass: &mut Self::Class) { klass.set_metadata( "RGB-GRAY Converter", "Filter/Effect/Converter/Video", @@ -267,17 +289,18 @@ impl BaseTransformImpl for Rgb2Gray {} With all this defined, `gst-inspect-1.0` should be able to show some more information about our element already but will still complain that it’s not complete yet. -**Side note:** This is the basic code that should be in `rgb2gray.rs` to successfully build the plugin. You can fill up the code while going through the later part of the tutorial. +**Side note:** This is the basic code that should be in `src/rgb2gray/imp.rs` and `src/rgb2gray/mod.rs` to successfully build the plugin. You can fill up the code while going through the later part of the tutorial. ```rust -//all imports... +// all imports... -struct Rgb2Gray {} +pub struct Rgb2Gray {} impl Rgb2Gray {} impl ObjectSubclass for Rgb2Gray { const NAME: &'static str = "RsRgb2Gray"; + type Type = super::Rgb2Gray; type ParentType = gst_base::BaseTransform; type Instance = gst::subclass::ElementInstanceStruct; type Class = subclass::simple::ClassStruct; @@ -290,13 +313,26 @@ impl ObjectImpl for Rgb2Gray {} impl ElementImpl for Rgb2Gray {} impl BaseTransformImpl for Rgb2Gray {} +``` + +```rust +use glib::prelude::*; + +mod imp; + +glib_wrapper! { + pub struct Rgb2Gray(ObjectSubclass) @extends gst_base::BaseTransform, gst::Element, gst::Object; +} + +unsafe impl Send for Rgb2Gray {} +unsafe impl Sync for Rgb2Gray {} pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { gst::Element::register( Some(plugin), "rsrgb2gray", gst::Rank::None, - Rgb2Gray::get_type(), + Rgb2Gray::static_type(), ) } ``` @@ -401,7 +437,7 @@ struct State { out_info: gst_video::VideoInfo, } -struct Rgb2Gray { +pub struct Rgb2Gray { state: Mutex> } @@ -426,7 +462,7 @@ Whenever input/output caps are configured on our element, the `set_caps` virtual impl BaseTransformImpl for Rgb2Gray { fn set_caps( &self, - element: &gst_base::BaseTransform, + element: &Self::Type, incaps: &gst::Caps, outcaps: &gst::Caps, ) -> Result<(), gst::LoggableError> { @@ -452,7 +488,7 @@ impl BaseTransformImpl for Rgb2Gray { Ok(()) } - fn stop(&self, element: &gst_base::BaseTransform) -> Result<(), gst::ErrorMessage> { + fn stop(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> { // Drop state let _ = self.state.lock().unwrap().take(); @@ -469,7 +505,7 @@ Next we have to provide information to the `BaseTransform` base class about the ```rust impl BaseTransformImpl for Rgb2Gray { - fn get_unit_size(&self, _element: &gst_base::BaseTransform, caps: &gst::Caps) -> Option { + fn get_unit_size(&self, _element: &Self::Type, caps: &gst::Caps) -> Option { gst_video::VideoInfo::from_caps(caps).map(|info| info.size()) } } @@ -489,7 +525,7 @@ This has to be implemented in the `transform_caps` virtual method, and looks as impl BaseTransformImpl for Rgb2Gray { fn transform_caps( &self, - element: &gst_base::BaseTransform, + element: &Self::Type, direction: gst::PadDirection, caps: &gst::Caps, filter: Option<&gst::Caps>, @@ -579,7 +615,7 @@ Afterwards we have to actually call this function on every pixel. For this the t impl BaseTransformImpl for Rgb2Gray { fn transform( &self, - element: &gst_base::BaseTransform, + element: &Self::Type, inbuf: &gst::Buffer, outbuf: &mut gst::BufferRef, ) -> Result { @@ -752,7 +788,7 @@ static PROPERTIES: [subclass::Property; 2] = [ }), ]; -struct Rgb2Gray { +pub struct Rgb2Gray { settings: Mutex, state: Mutex>, } @@ -777,7 +813,7 @@ In the next step we have to make use of these: we need to tell the GObject type ```rust impl ObjectSubclass for Rgb2Gray { - fn class_init(klass: &mut subclass::simple::ClassStruct) { + fn class_init(klass: &mut Self::Class) { [...] klass.install_properties(&PROPERTIES); [...] @@ -787,9 +823,8 @@ impl ObjectSubclass for Rgb2Gray { impl ObjectImpl for Rgb2Gray { [...] - fn set_property(&self, obj: &glib::Object, id: usize, value: &glib::Value) { + fn set_property(&self, obj: &Self::Type, id: usize, value: &glib::Value) { let prop = &PROPERTIES[id]; - let element = obj.downcast_ref::().unwrap(); match *prop { subclass::Property("invert", ..) => { @@ -797,7 +832,7 @@ impl ObjectImpl for Rgb2Gray { let invert = value.get_some().expect("type checked upstream"); gst_info!( CAT, - obj: element, + obj: obj, "Changing invert from {} to {}", settings.invert, invert @@ -809,7 +844,7 @@ impl ObjectImpl for Rgb2Gray { let shift = value.get_some().expect("type checked upstream"); gst_info!( CAT, - obj: element, + obj: obj, "Changing shift from {} to {}", settings.shift, shift @@ -820,7 +855,7 @@ impl ObjectImpl for Rgb2Gray { } } - fn get_property(&self, _obj: &glib::Object, id: usize) -> Result { + fn get_property(&self, _obj: &Self::Type, id: usize) -> Result { let prop = &PROPERTIES[id]; match *prop { @@ -871,7 +906,7 @@ impl Rgb2Gray { impl BaseTransformImpl for Rgb2Gray { fn transform( &self, - element: &gst_base::BaseTransform, + element: &Self::Type, inbuf: &gst::Buffer, outbuf: &mut gst::BufferRef, ) -> Result { diff --git a/tutorial/tutorial-2.md b/tutorial/tutorial-2.md index aa6b512dd..9bc43ea6a 100644 --- a/tutorial/tutorial-2.md +++ b/tutorial/tutorial-2.md @@ -19,6 +19,8 @@ Our sine wave element is going to produce raw audio, with a number of channels a So let's get started with all the boilerplate. This time our element will be based on the [`PushSrc`](https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer-libs/html/GstPushSrc.html) base class instead of [`BaseTransform`](https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer-libs/html/GstBaseTransform.html). `PushSrc` is a subclass of the [`BaseSrc`](https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer-libs/html/GstBaseSrc.html) base class that only works in push mode, i.e. creates buffers as they arrive instead of allowing downstream elements to explicitly pull them. +In `src/sinesrc/imp.rs`: + ```rust use glib; use gst; @@ -134,7 +136,7 @@ impl Default for State { } // Struct containing all the element data -struct SineSrc { +pub struct SineSrc { settings: Mutex, state: Mutex, } @@ -149,6 +151,7 @@ static CAT: Lazy = Lazy::new(|| { impl ObjectSubclass for SineSrc { const NAME: &'static str = "RsSineSrc"; + type Type = super::SineSrc; type ParentType = gst_base::PushSrc; type Instance = gst::subclass::ElementInstanceStruct; type Class = subclass::simple::ClassStruct; @@ -179,7 +182,7 @@ impl ObjectSubclass for SineSrc { // will automatically instantiate pads for them. // // Our element here can output f32 and f64 - fn class_init(klass: &mut subclass::simple::ClassStruct) { + fn class_init(klass: &mut Self::Class) { // Set the element specific metadata. This information is what // is visible from gst-inspect-1.0 and can also be programatically // retrieved from the gst::Registry after initial registration @@ -231,22 +234,20 @@ impl ObjectSubclass for SineSrc { impl ObjectImpl for SineSrc { // Called right after construction of a new instance - fn constructed(&self, obj: &glib::Object) { + fn constructed(&self, obj: &Self::Type) { // Call the parent class' ::constructed() implementation first self.parent_constructed(obj); // Initialize live-ness and notify the base class that // we'd like to operate in Time format - let basesrc = obj.downcast_ref::().unwrap(); - basesrc.set_live(DEFAULT_IS_LIVE); - basesrc.set_format(gst::Format::Time); + obj.set_live(DEFAULT_IS_LIVE); + obj.set_format(gst::Format::Time); } // Called whenever a value of a property is changed. It can be called // at any time from any thread. - fn set_property(&self, obj: &glib::Object, id: u32, value: &glib::Value) { + fn set_property(&self, obj: &Self::Type, id: u32, value: &glib::Value) { let prop = &PROPERTIES[id as usize]; - let element = obj.clone().downcast::().unwrap(); match *prop { Property::UInt("samples-per-buffer", ..) => { @@ -254,7 +255,7 @@ impl ObjectImpl for SineSrc { let samples_per_buffer = value.get_some().expect("type checked upstream"); gst_info!( CAT, - obj: &element, + obj: obj, "Changing samples-per-buffer from {} to {}", settings.samples_per_buffer, samples_per_buffer @@ -263,14 +264,14 @@ impl ObjectImpl for SineSrc { drop(settings); let _ = - element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); + obj.post_message(&gst::Message::new_latency().src(Some(obj)).build()); } Property::UInt("freq", ..) => { let mut settings = self.settings.lock().unwrap(); let freq = value.get_some().expect("type checked upstream"); gst_info!( CAT, - obj: &element, + obj: obj, "Changing freq from {} to {}", settings.freq, freq @@ -282,7 +283,7 @@ impl ObjectImpl for SineSrc { let volume = value.get_some().expect("type checked upstream"); gst_info!( CAT, - obj: &element, + obj: obj, "Changing volume from {} to {}", settings.volume, volume @@ -294,7 +295,7 @@ impl ObjectImpl for SineSrc { let mute = value.get_some().expect("type checked upstream"); gst_info!( CAT, - obj: &element, + obj: obj, "Changing mute from {} to {}", settings.mute, mute @@ -306,7 +307,7 @@ impl ObjectImpl for SineSrc { let is_live = value.get_some().expect("type checked upstream"); gst_info!( CAT, - obj: &element, + obj: obj, "Changing is-live from {} to {}", settings.is_live, is_live @@ -319,7 +320,7 @@ impl ObjectImpl for SineSrc { // Called whenever a value of a property is read. It can be called // at any time from any thread. - fn get_property(&self, _obj: &glib::Object, id: u32) -> Result { + fn get_property(&self, _obj: &Self::Type, id: u32) -> Result { let prop = &PROPERTIES[id as usize]; match *prop { @@ -353,7 +354,7 @@ impl ElementImpl for SineSrc { } impl BaseSrcImpl for SineSrc { // Called when starting, so we can initialize all stream-related state to its defaults - fn start(&self, element: &BaseSrc) -> bool { + fn start(&self, element: &Self::Type) -> bool { // Reset state *self.state.lock().unwrap() = Default::default(); @@ -363,7 +364,7 @@ impl BaseSrcImpl for SineSrc { } // Called when shutting down the element so we can release all stream-related state - fn stop(&self, element: &BaseSrc) -> bool { + fn stop(&self, element: &Self::Type) -> bool { // Reset state *self.state.lock().unwrap() = Default::default(); @@ -372,6 +373,24 @@ impl BaseSrcImpl for SineSrc { true } } +``` + +In `src/sinesrc/mod.rs`: + +```rust +use glib::prelude::*; + +mod imp; + +// The public Rust wrapper type for our element +glib_wrapper! { + pub struct SineSrc(ObjectSubclass) @extends gst_base::BaseSrc, gst::Element, gst::Object; +} + +// GStreamer elements need to be thread-safe. For the private implementation this is automatically +// enforced but for the public wrapper type we need to specify this manually. +unsafe impl Send for SineSrc {} +unsafe impl Sync for SineSrc {} // Registers the type for our element, and then registers in GStreamer under // the name "rssinesrc" for being able to instantiate it via e.g. @@ -381,7 +400,7 @@ pub fn register(plugin: &gst::Plugin) { Some(plugin), "rssinesrc", gst::Rank::None, - SineSrc::get_type(), + SineSrc::static_type(), ) } ``` @@ -410,7 +429,7 @@ The first part that we have to implement, just like last time, is caps negotiati First of all, we need to get notified whenever the caps that our source is configured for are changing. This will happen once in the very beginning and then whenever the pipeline topology or state changes and new caps would be more optimal for the new situation. This notification happens via the `BaseTransform::set_caps` virtual method. ```rust - fn set_caps(&self, element: &BaseSrc, caps: &gst::Caps) -> Result<(), gst::LoggableError> { + fn set_caps(&self, element: &Self::Type, caps: &gst::Caps) -> Result<(), gst::LoggableError> { use std::f64::consts::PI; let info = gst_audio::AudioInfo::from_caps(caps).map_err(|_| { @@ -469,7 +488,7 @@ As a last step we post a new `LATENCY` message on the bus whenever the sample `BaseSrc` is by default already selecting possible caps for us, if there are multiple options. However these defaults might not be (and often are not) ideal and we should override the default behaviour slightly. This is done in the `BaseSrc::fixate` virtual method. ```rust - fn fixate(&self, element: &BaseSrc, mut caps: gst::Caps) -> gst::Caps { + fn fixate(&self, element: &Self::Type, mut caps: gst::Caps) -> gst::Caps { // Fixate the caps. BaseSrc will do some fixation for us, but // as we allow any rate between 1 and MAX it would fixate to 1. 1Hz // is generally not a useful sample rate. @@ -561,7 +580,7 @@ Now that this is done, we need to implement the `PushSrc::create` virtual meth ```rust fn create( &self, - element: &PushSrc, + element: &Self::Type, ) -> Result { // Keep a local copy of the values of all our properties at this very moment. This // ensures that the mutex is never locked for long and the application wouldn't @@ -742,7 +761,7 @@ Now we also have to tell the base class that we're running in live mode now. Thi impl ElementImpl for SineSrc { fn change_state( &self, - element: &BaseSrc, + element: &Self::Type, transition: gst::StateChange, ) -> gst::StateChangeReturn { // Configure live'ness once here just before starting the source @@ -763,7 +782,7 @@ And as a last step, we also need to notify downstream elements about our [laten This querying is done with the `LATENCY` query, which we will have to handle in the `BaseSrc::query()` function. ```rust - fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { + fn query(&self, element: &Self::Type, query: &mut gst::QueryRef) -> bool { use gst::QueryView; match query.view_mut() { @@ -819,7 +838,7 @@ struct SineSrc { [...] - fn unlock(&self, element: &BaseSrc) -> bool { + fn unlock(&self, element: &Self::Type) -> bool { // This should unblock the create() function ASAP, so we // just unschedule the clock it here, if any. gst_debug!(CAT, obj: element, "Unlocking"); @@ -838,7 +857,7 @@ We store the clock ID in our struct, together with a boolean to signal whether w Once everything is unlocked, we need to reset things again so that data flow can happen in the future. This is done in the `unlock_stop` virtual method. ```rust - fn unlock_stop(&self, element: &BaseSrc) -> bool { + fn unlock_stop(&self, element: &Self::Type) -> bool { // This signals that unlocking is done, so we can reset // all values again. gst_debug!(CAT, obj: element, "Unlock stop"); @@ -903,11 +922,11 @@ As a last feature we implement seeking on our source element. In our case that o Seeking is implemented in the `BaseSrc::do_seek` virtual method, and signalling whether we can actually seek in the `is_seekable` virtual method. ```rust - fn is_seekable(&self, _element: &BaseSrc) -> bool { + fn is_seekable(&self, _element: &Self::Type) -> bool { true } - fn do_seek(&self, element: &BaseSrc, segment: &mut gst::Segment) -> bool { + fn do_seek(&self, element: &Self::Type, segment: &mut gst::Segment) -> bool { // Handle seeking here. For Time and Default (sample offset) seeks we can // do something and have to update our sample offset and accumulator accordingly. // -- cgit v1.2.3