From e11b12df7c64e4752ad2e431e54bff0c3a8284fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 20 Oct 2020 23:55:20 +0300 Subject: [PATCH] gstreamer: Provide class metadata, pad templates and basetransform configuration via trait methods This is closer to how this works in Python and also how properties and signals work now in the glib bindings. class_init() only has to be implemented for more special uses now. --- examples/src/bin/rtsp-server-subclass.rs | 4 + examples/src/bin/subclass.rs | 145 +++++++------- gstreamer-base/src/subclass/base_transform.rs | 69 +++---- gstreamer-base/src/subclass/mod.rs | 4 +- gstreamer/src/subclass/device_provider.rs | 65 ++++--- gstreamer/src/subclass/element.rs | 183 +++++++++++------- gstreamer/src/subclass/mod.rs | 9 +- gstreamer/src/subclass/pad.rs | 1 + 8 files changed, 254 insertions(+), 226 deletions(-) diff --git a/examples/src/bin/rtsp-server-subclass.rs b/examples/src/bin/rtsp-server-subclass.rs index c60a7b785..2de3c3e0c 100644 --- a/examples/src/bin/rtsp-server-subclass.rs +++ b/examples/src/bin/rtsp-server-subclass.rs @@ -97,6 +97,7 @@ mod media_factory { const NAME: &'static str = "RsRTSPMediaFactory"; type Type = super::Factory; type ParentType = gst_rtsp_server::RTSPMediaFactory; + type Interfaces = (); type Instance = gst::subclass::ElementInstanceStruct; type Class = subclass::simple::ClassStruct; @@ -190,6 +191,7 @@ mod media { const NAME: &'static str = "RsRTSPMedia"; type Type = super::Media; type ParentType = gst_rtsp_server::RTSPMedia; + type Interfaces = (); type Instance = gst::subclass::ElementInstanceStruct; type Class = subclass::simple::ClassStruct; @@ -258,6 +260,7 @@ mod server { const NAME: &'static str = "RsRTSPServer"; type Type = super::Server; type ParentType = gst_rtsp_server::RTSPServer; + type Interfaces = (); type Instance = gst::subclass::ElementInstanceStruct; type Class = subclass::simple::ClassStruct; @@ -334,6 +337,7 @@ mod client { const NAME: &'static str = "RsRTSPClient"; type Type = super::Client; type ParentType = gst_rtsp_server::RTSPClient; + type Interfaces = (); type Instance = gst::subclass::ElementInstanceStruct; type Class = subclass::simple::ClassStruct; diff --git a/examples/src/bin/subclass.rs b/examples/src/bin/subclass.rs index 6401e367c..54fc7472b 100644 --- a/examples/src/bin/subclass.rs +++ b/examples/src/bin/subclass.rs @@ -59,6 +59,7 @@ mod fir_filter { const NAME: &'static str = "RsFirFilter"; type Type = super::FirFilter; type ParentType = gst_base::BaseTransform; + type Interfaces = (); type Instance = gst::subclass::ElementInstanceStruct; type Class = subclass::simple::ClassStruct; @@ -73,89 +74,85 @@ mod fir_filter { history: Mutex::new(VecDeque::new()), } } - - // 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 only handle F32 mono audio. - 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( - "FIR Filter", - "Filter/Effect/Audio", - "A FIR audio filter", - "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 both of pads we can only handle F32 mono at any sample rate. - let caps = gst::Caps::new_simple( - "audio/x-raw", - &[ - ("format", &gst_audio::AUDIO_FORMAT_F32.to_str()), - ("rate", &gst::IntRange::::new(1, i32::MAX)), - ("channels", &1i32), - ("layout", &"interleaved"), - ], - ); - - // 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); - - // 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); - - // Configure basetransform so that we are always 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). - // - // The way how our processing is implemented, in-place transformation - // is simpler. - klass.configure( - gst_base::subclass::BaseTransformMode::AlwaysInPlace, - false, - false, - ); - } } // Implementation of glib::Object virtual methods impl ObjectImpl for FirFilter {} // Implementation of gst::Element virtual methods - impl ElementImpl for FirFilter {} + impl ElementImpl for FirFilter { + // 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. + fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { + static ELEMENT_METADATA: Lazy = Lazy::new(|| { + gst::subclass::ElementMetadata::new( + "FIR Filter", + "Filter/Effect/Audio", + "A FIR audio filter", + "Sebastian Dröge ", + ) + }); + + Some(&*ELEMENT_METADATA) + } + + fn pad_templates() -> &'static [gst::PadTemplate] { + static PAD_TEMPLATES: Lazy> = Lazy::new(|| { + // Create 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 both of pads we can only handle F32 mono at any sample rate. + let caps = gst::Caps::new_simple( + "audio/x-raw", + &[ + ("format", &gst_audio::AUDIO_FORMAT_F32.to_str()), + ("rate", &gst::IntRange::::new(1, i32::MAX)), + ("channels", &1i32), + ("layout", &"interleaved"), + ], + ); + + vec![ + // The src pad template must be named "src" for basetransform + // and specific a pad that is always there + gst::PadTemplate::new( + "src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &caps, + ) + .unwrap(), + // The sink pad template must be named "sink" for basetransform + // and specific a pad that is always there + gst::PadTemplate::new( + "sink", + gst::PadDirection::Sink, + gst::PadPresence::Always, + &caps, + ) + .unwrap(), + ] + }); + + PAD_TEMPLATES.as_ref() + } + } // Implementation of gst_base::BaseTransform virtual methods impl BaseTransformImpl for FirFilter { + // Configure basetransform so that we are always 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). + // + // The way how our processing is implemented, in-place transformation + // is simpler. + const MODE: gst_base::subclass::BaseTransformMode = + gst_base::subclass::BaseTransformMode::AlwaysInPlace; + const PASSTHROUGH_ON_SAME_CAPS: bool = false; + const TRANSFORM_IP_ON_PASSTHROUGH: bool = false; + // 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. diff --git a/gstreamer-base/src/subclass/base_transform.rs b/gstreamer-base/src/subclass/base_transform.rs index 7cb1c14c5..f7461e439 100644 --- a/gstreamer-base/src/subclass/base_transform.rs +++ b/gstreamer-base/src/subclass/base_transform.rs @@ -13,7 +13,18 @@ use std::ptr; use crate::BaseTransform; +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum BaseTransformMode { + AlwaysInPlace, + NeverInPlace, + Both, +} + pub trait BaseTransformImpl: BaseTransformImplExt + ElementImpl { + const MODE: BaseTransformMode; + const PASSTHROUGH_ON_SAME_CAPS: bool; + const TRANSFORM_IP_ON_PASSTHROUGH: bool; + fn start(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> { self.parent_start(element) } @@ -823,13 +834,6 @@ impl BaseTransformImplExt for T { } } -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum BaseTransformMode { - AlwaysInPlace, - NeverInPlace, - Both, -} - unsafe impl IsSubclassable for BaseTransform where ::Instance: PanicPoison, @@ -854,50 +858,27 @@ where klass.before_transform = Some(base_transform_before_transform::); klass.submit_input_buffer = Some(base_transform_submit_input_buffer::); klass.generate_output = Some(base_transform_generate_output::); - } -} -pub unsafe trait BaseTransformClassSubclassExt: Sized + 'static { - fn configure( - &mut self, - mode: BaseTransformMode, - passthrough_on_same_caps: bool, - transform_ip_on_passthrough: bool, - ) where - Self: ClassStruct, - ::Instance: PanicPoison, - { - unsafe { - let klass = &mut *(self as *mut Self as *mut ffi::GstBaseTransformClass); + klass.passthrough_on_same_caps = T::PASSTHROUGH_ON_SAME_CAPS.to_glib(); + klass.transform_ip_on_passthrough = T::TRANSFORM_IP_ON_PASSTHROUGH.to_glib(); - klass.passthrough_on_same_caps = passthrough_on_same_caps.to_glib(); - klass.transform_ip_on_passthrough = transform_ip_on_passthrough.to_glib(); - - match mode { - BaseTransformMode::AlwaysInPlace => { - klass.transform = None; - klass.transform_ip = Some(base_transform_transform_ip::); - } - BaseTransformMode::NeverInPlace => { - klass.transform = Some(base_transform_transform::); - klass.transform_ip = None; - } - BaseTransformMode::Both => { - klass.transform = Some(base_transform_transform::); - klass.transform_ip = Some(base_transform_transform_ip::); - } + match T::MODE { + BaseTransformMode::AlwaysInPlace => { + klass.transform = None; + klass.transform_ip = Some(base_transform_transform_ip::); + } + BaseTransformMode::NeverInPlace => { + klass.transform = Some(base_transform_transform::); + klass.transform_ip = None; + } + BaseTransformMode::Both => { + klass.transform = Some(base_transform_transform::); + klass.transform_ip = Some(base_transform_transform_ip::); } } } } -unsafe impl BaseTransformClassSubclassExt for T -where - T::Type: BaseTransformImpl, - ::Instance: PanicPoison, -{ -} - #[derive(Debug)] pub enum GenerateOutputSuccess { Buffer(gst::Buffer), diff --git a/gstreamer-base/src/subclass/mod.rs b/gstreamer-base/src/subclass/mod.rs index 636710cd3..7cb2f6e47 100644 --- a/gstreamer-base/src/subclass/mod.rs +++ b/gstreamer-base/src/subclass/mod.rs @@ -34,8 +34,6 @@ pub mod prelude { pub use super::base_parse::{BaseParseImpl, BaseParseImplExt}; pub use super::base_sink::{BaseSinkImpl, BaseSinkImplExt}; pub use super::base_src::{BaseSrcImpl, BaseSrcImplExt}; - pub use super::base_transform::{ - BaseTransformClassSubclassExt, BaseTransformImpl, BaseTransformImplExt, - }; + pub use super::base_transform::{BaseTransformImpl, BaseTransformImplExt}; pub use super::push_src::{PushSrcImpl, PushSrcImplExt}; } diff --git a/gstreamer/src/subclass/device_provider.rs b/gstreamer/src/subclass/device_provider.rs index 69f55c2f9..8253fed28 100644 --- a/gstreamer/src/subclass/device_provider.rs +++ b/gstreamer/src/subclass/device_provider.rs @@ -8,7 +8,20 @@ use crate::Device; use crate::DeviceProvider; use crate::LoggableError; +#[derive(Debug, Clone)] +pub struct DeviceProviderMetadata { + long_name: String, + classification: String, + description: String, + author: String, + additional: Vec<(String, String)>, +} + pub trait DeviceProviderImpl: DeviceProviderImplExt + ObjectImpl + Send + Sync { + fn metadata() -> Option<&'static DeviceProviderMetadata> { + None + } + fn probe(&self, device_provider: &Self::Type) -> Vec { self.parent_probe(device_provider) } @@ -78,38 +91,6 @@ impl DeviceProviderImplExt for T { } } -pub unsafe trait DeviceProviderClassSubclassExt: Sized + 'static { - fn set_metadata( - &mut self, - long_name: &str, - classification: &str, - description: &str, - author: &str, - ) { - unsafe { - ffi::gst_device_provider_class_set_metadata( - self as *mut Self as *mut ffi::GstDeviceProviderClass, - long_name.to_glib_none().0, - classification.to_glib_none().0, - description.to_glib_none().0, - author.to_glib_none().0, - ); - } - } - - fn add_metadata(&mut self, key: &str, value: &str) { - unsafe { - ffi::gst_device_provider_class_add_metadata( - self as *mut Self as *mut ffi::GstDeviceProviderClass, - key.to_glib_none().0, - value.to_glib_none().0, - ); - } - } -} - -unsafe impl DeviceProviderClassSubclassExt for glib::Class {} - unsafe impl IsSubclassable for DeviceProvider { fn override_vfuncs(klass: &mut glib::Class) { >::override_vfuncs(klass); @@ -117,6 +98,26 @@ unsafe impl IsSubclassable for DeviceProvider { klass.probe = Some(device_provider_probe::); klass.start = Some(device_provider_start::); klass.stop = Some(device_provider_stop::); + + unsafe { + if let Some(metadata) = T::metadata() { + ffi::gst_device_provider_class_set_metadata( + klass, + metadata.long_name.to_glib_none().0, + metadata.classification.to_glib_none().0, + metadata.description.to_glib_none().0, + metadata.author.to_glib_none().0, + ); + + for (key, value) in &metadata.additional { + ffi::gst_device_provider_class_add_metadata( + klass, + key.to_glib_none().0, + value.to_glib_none().0, + ); + } + } + } } } diff --git a/gstreamer/src/subclass/element.rs b/gstreamer/src/subclass/element.rs index c292705d6..067a338d7 100644 --- a/gstreamer/src/subclass/element.rs +++ b/gstreamer/src/subclass/element.rs @@ -14,7 +14,56 @@ use crate::StateChangeError; use crate::StateChangeReturn; use crate::StateChangeSuccess; +#[derive(Debug, Clone)] +pub struct ElementMetadata { + long_name: String, + classification: String, + description: String, + author: String, + additional: Vec<(String, String)>, +} + +impl ElementMetadata { + pub fn new(long_name: &str, classification: &str, description: &str, author: &str) -> Self { + Self { + long_name: long_name.into(), + classification: classification.into(), + description: description.into(), + author: author.into(), + additional: vec![], + } + } + + pub fn with_additional( + long_name: &str, + classification: &str, + description: &str, + author: &str, + additional: &[(&str, &str)], + ) -> Self { + Self { + long_name: long_name.into(), + classification: classification.into(), + description: description.into(), + author: author.into(), + additional: additional + .iter() + .copied() + .map(|(key, value)| (key.into(), value.into())) + .collect(), + } + } +} + pub trait ElementImpl: ElementImplExt + ObjectImpl + Send + Sync { + fn metadata() -> Option<&'static ElementMetadata> { + None + } + + fn pad_templates() -> &'static [PadTemplate] { + &[] + } + fn change_state( &self, element: &Self::Type, @@ -302,53 +351,13 @@ where } } -pub unsafe trait ElementClassSubclassExt: Sized + 'static { - fn add_pad_template(&mut self, pad_template: PadTemplate) { - unsafe { - ffi::gst_element_class_add_pad_template( - self as *mut Self as *mut ffi::GstElementClass, - pad_template.to_glib_none().0, - ); - } - } - - fn set_metadata( - &mut self, - long_name: &str, - classification: &str, - description: &str, - author: &str, - ) { - unsafe { - ffi::gst_element_class_set_metadata( - self as *mut Self as *mut ffi::GstElementClass, - long_name.to_glib_none().0, - classification.to_glib_none().0, - description.to_glib_none().0, - author.to_glib_none().0, - ); - } - } - - fn add_metadata(&mut self, key: &str, value: &str) { - unsafe { - ffi::gst_element_class_add_metadata( - self as *mut Self as *mut ffi::GstElementClass, - key.to_glib_none().0, - value.to_glib_none().0, - ); - } - } -} - -unsafe impl ElementClassSubclassExt for glib::Class {} - unsafe impl IsSubclassable for Element where ::Instance: PanicPoison, { fn override_vfuncs(klass: &mut glib::Class) { >::override_vfuncs(klass); + let klass = klass.as_mut(); klass.change_state = Some(element_change_state::); klass.request_new_pad = Some(element_request_new_pad::); @@ -359,6 +368,30 @@ where klass.set_clock = Some(element_set_clock::); klass.provide_clock = Some(element_provide_clock::); klass.post_message = Some(element_post_message::); + + unsafe { + for pad_template in T::pad_templates() { + ffi::gst_element_class_add_pad_template(klass, pad_template.to_glib_none().0); + } + + if let Some(metadata) = T::metadata() { + ffi::gst_element_class_set_metadata( + klass, + metadata.long_name.to_glib_none().0, + metadata.classification.to_glib_none().0, + metadata.description.to_glib_none().0, + metadata.author.to_glib_none().0, + ); + + for (key, value) in &metadata.additional { + ffi::gst_element_class_add_metadata( + klass, + key.to_glib_none().0, + value.to_glib_none().0, + ); + } + } + } } } @@ -623,6 +656,7 @@ mod tests { const NAME: &'static str = "TestElement"; type Type = super::TestElement; type ParentType = Element; + type Interfaces = (); type Instance = crate::subclass::ElementInstanceStruct; type Class = subclass::simple::ClassStruct; @@ -679,34 +713,6 @@ mod tests { sinkpad, } } - - fn class_init(klass: &mut Self::Class) { - klass.set_metadata( - "Test Element", - "Generic", - "Does nothing", - "Sebastian Dröge ", - ); - - let caps = crate::Caps::new_any(); - let src_pad_template = crate::PadTemplate::new( - "src", - crate::PadDirection::Src, - crate::PadPresence::Always, - &caps, - ) - .unwrap(); - klass.add_pad_template(src_pad_template); - - let sink_pad_template = crate::PadTemplate::new( - "sink", - crate::PadDirection::Sink, - crate::PadPresence::Always, - &caps, - ) - .unwrap(); - klass.add_pad_template(sink_pad_template); - } } impl ObjectImpl for TestElement { @@ -719,6 +725,45 @@ mod tests { } impl ElementImpl for TestElement { + fn metadata() -> Option<&'static ElementMetadata> { + use once_cell::sync::Lazy; + static ELEMENT_METADATA: Lazy = Lazy::new(|| { + ElementMetadata::new( + "Test Element", + "Generic", + "Does nothing", + "Sebastian Dröge ", + ) + }); + + Some(&*ELEMENT_METADATA) + } + + fn pad_templates() -> &'static [PadTemplate] { + use once_cell::sync::Lazy; + static PAD_TEMPLATES: Lazy> = Lazy::new(|| { + let caps = crate::Caps::new_any(); + vec![ + PadTemplate::new( + "src", + crate::PadDirection::Src, + crate::PadPresence::Always, + &caps, + ) + .unwrap(), + PadTemplate::new( + "sink", + crate::PadDirection::Sink, + crate::PadPresence::Always, + &caps, + ) + .unwrap(), + ] + }); + + PAD_TEMPLATES.as_ref() + } + fn change_state( &self, element: &Self::Type, diff --git a/gstreamer/src/subclass/mod.rs b/gstreamer/src/subclass/mod.rs index 7c3d8729c..7631cca97 100644 --- a/gstreamer/src/subclass/mod.rs +++ b/gstreamer/src/subclass/mod.rs @@ -32,6 +32,9 @@ mod preset; mod tag_setter; mod uri_handler; +pub use self::device_provider::DeviceProviderMetadata; +pub use self::element::ElementMetadata; + pub use self::error::FlowError; pub use self::plugin::{MAJOR_VERSION, MINOR_VERSION}; @@ -43,10 +46,8 @@ pub mod prelude { pub use super::child_proxy::ChildProxyImpl; pub use super::clock::{ClockImpl, ClockImplExt}; pub use super::device::{DeviceImpl, DeviceImplExt}; - pub use super::device_provider::{ - DeviceProviderClassSubclassExt, DeviceProviderImpl, DeviceProviderImplExt, - }; - pub use super::element::{ElementClassSubclassExt, ElementImpl, ElementImplExt}; + pub use super::device_provider::{DeviceProviderImpl, DeviceProviderImplExt}; + pub use super::element::{ElementImpl, ElementImplExt}; pub use super::ghost_pad::GhostPadImpl; pub use super::pad::{PadImpl, PadImplExt}; pub use super::pipeline::PipelineImpl; diff --git a/gstreamer/src/subclass/pad.rs b/gstreamer/src/subclass/pad.rs index 00f66b56a..7da9b09d8 100644 --- a/gstreamer/src/subclass/pad.rs +++ b/gstreamer/src/subclass/pad.rs @@ -104,6 +104,7 @@ mod tests { const NAME: &'static str = "TestPad"; type Type = super::TestPad; type ParentType = Pad; + type Interfaces = (); type Instance = subclass::simple::InstanceStruct; type Class = subclass::simple::ClassStruct;