From 61639146051d8290575e36bba2eafa99eb04711f Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Tue, 30 Aug 2022 16:13:45 +0200 Subject: [PATCH] pbutils: Add bindings for the AudioVisualizer base class --- gstreamer-pbutils/Cargo.toml | 2 + gstreamer-pbutils/Gir.toml | 2 + gstreamer-pbutils/src/audio_visualizer.rs | 72 ++++++ .../src/auto/audio_visualizer.rs | 123 ++++++++++ gstreamer-pbutils/src/auto/enums.rs | 102 ++++++++ gstreamer-pbutils/src/auto/mod.rs | 5 + gstreamer-pbutils/src/lib.rs | 5 + .../src/subclass/audio_visualizer.rs | 226 ++++++++++++++++++ gstreamer-pbutils/src/subclass/mod.rs | 8 + 9 files changed, 545 insertions(+) create mode 100644 gstreamer-pbutils/src/audio_visualizer.rs create mode 100644 gstreamer-pbutils/src/auto/audio_visualizer.rs create mode 100644 gstreamer-pbutils/src/subclass/audio_visualizer.rs create mode 100644 gstreamer-pbutils/src/subclass/mod.rs diff --git a/gstreamer-pbutils/Cargo.toml b/gstreamer-pbutils/Cargo.toml index 5306448cc..e50888790 100644 --- a/gstreamer-pbutils/Cargo.toml +++ b/gstreamer-pbutils/Cargo.toml @@ -19,6 +19,8 @@ libc = "0.2" ffi = { package = "gstreamer-pbutils-sys", path = "sys" } glib = { git = "https://github.com/gtk-rs/gtk-rs-core" } gst = { package = "gstreamer", path = "../gstreamer" } +gst-video = { package = "gstreamer-video", path = "../gstreamer-video" } +gst-audio = { package = "gstreamer-audio", path = "../gstreamer-audio" } thiserror = "1.0" serde = { version = "1.0", optional = true } diff --git a/gstreamer-pbutils/Gir.toml b/gstreamer-pbutils/Gir.toml index d456aba30..317030292 100644 --- a/gstreamer-pbutils/Gir.toml +++ b/gstreamer-pbutils/Gir.toml @@ -16,6 +16,8 @@ external_libraries = [ ] generate = [ + "GstPbutils.AudioVisualizer", + "GstPbutils.AudioVisualizerShader", "GstPbutils.DiscovererResult", "GstPbutils.PbUtilsCapsDescriptionFlags", ] diff --git a/gstreamer-pbutils/src/audio_visualizer.rs b/gstreamer-pbutils/src/audio_visualizer.rs new file mode 100644 index 000000000..7d1ba4c47 --- /dev/null +++ b/gstreamer-pbutils/src/audio_visualizer.rs @@ -0,0 +1,72 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::auto::AudioVisualizer; +use crate::subclass::AudioVisualizerSetupToken; +use glib::object::IsA; +use gst::prelude::*; + +pub trait AudioVisualizerExtManual: 'static { + // rustdoc-stripper-ignore-next + /// Returns the number of samples per frame required before calling the render method + fn req_spf(&self) -> u32; + + // rustdoc-stripper-ignore-next + /// Modify the request of samples per frame required to be present in buffer before calling + /// the render method + fn set_req_spf(&self, spf: u32, token: &AudioVisualizerSetupToken); + + fn audio_info(&self) -> gst_audio::AudioInfo; + + fn video_info(&self) -> gst_video::VideoInfo; +} + +impl + ElementExt> AudioVisualizerExtManual for O { + fn req_spf(&self) -> u32 { + let sinkpad = self.static_pad("sink").expect("sink pad presence"); + let _stream_lock = sinkpad.stream_lock(); + + let ptr = self.as_ptr() as *mut ffi::GstAudioVisualizer; + unsafe { (*ptr).req_spf } + } + + fn set_req_spf(&self, spf: u32, token: &AudioVisualizerSetupToken) { + assert_eq!( + self.as_ptr() as *mut ffi::GstAudioVisualizer, + token.0.as_ptr() as *mut ffi::GstAudioVisualizer + ); + + let sinkpad = self.static_pad("sink").expect("sink pad presence"); + let _stream_lock = sinkpad.stream_lock(); + + let mut ptr = self.as_ptr() as *mut ffi::GstAudioVisualizer; + unsafe { + (*ptr).req_spf = spf; + } + } + + fn audio_info(&self) -> gst_audio::AudioInfo { + let sinkpad = self.static_pad("sink").expect("sink pad presence"); + let _stream_lock = sinkpad.stream_lock(); + + let ptr = self.as_ptr() as *mut ffi::GstAudioVisualizer; + unsafe { + let info = &(*ptr).ainfo; + glib::translate::from_glib_none(glib::translate::mut_override( + info as *const gst_audio::ffi::GstAudioInfo, + )) + } + } + + fn video_info(&self) -> gst_video::VideoInfo { + let srcpad = self.static_pad("src").expect("src pad presence"); + let _stream_lock = srcpad.stream_lock(); + + let ptr = self.as_ptr() as *mut ffi::GstAudioVisualizer; + unsafe { + let info = &(*ptr).vinfo; + glib::translate::from_glib_none(glib::translate::mut_override( + info as *const gst_video::ffi::GstVideoInfo, + )) + } + } +} diff --git a/gstreamer-pbutils/src/auto/audio_visualizer.rs b/gstreamer-pbutils/src/auto/audio_visualizer.rs new file mode 100644 index 000000000..72e0ddf50 --- /dev/null +++ b/gstreamer-pbutils/src/auto/audio_visualizer.rs @@ -0,0 +1,123 @@ +// This file was generated by gir (https://github.com/gtk-rs/gir) +// from gir-files (https://github.com/gtk-rs/gir-files) +// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git) +// DO NOT EDIT + +use crate::AudioVisualizerShader; +use glib::object::Cast; +use glib::object::IsA; +use glib::signal::connect_raw; +use glib::signal::SignalHandlerId; +use glib::translate::*; +use glib::StaticType; +use glib::ToValue; +use std::boxed::Box as Box_; +use std::mem::transmute; + +glib::wrapper! { + #[doc(alias = "GstAudioVisualizer")] + pub struct AudioVisualizer(Object) @extends gst::Element, gst::Object; + + match fn { + type_ => || ffi::gst_audio_visualizer_get_type(), + } +} + +impl AudioVisualizer { + pub const NONE: Option<&'static AudioVisualizer> = None; +} + +unsafe impl Send for AudioVisualizer {} +unsafe impl Sync for AudioVisualizer {} + +pub trait AudioVisualizerExt: 'static { + #[doc(alias = "shade-amount")] + fn shade_amount(&self) -> u32; + + #[doc(alias = "shade-amount")] + fn set_shade_amount(&self, shade_amount: u32); + + fn shader(&self) -> AudioVisualizerShader; + + fn set_shader(&self, shader: AudioVisualizerShader); + + #[doc(alias = "shade-amount")] + fn connect_shade_amount_notify( + &self, + f: F, + ) -> SignalHandlerId; + + #[doc(alias = "shader")] + fn connect_shader_notify(&self, f: F) -> SignalHandlerId; +} + +impl> AudioVisualizerExt for O { + fn shade_amount(&self) -> u32 { + glib::ObjectExt::property(self.as_ref(), "shade-amount") + } + + fn set_shade_amount(&self, shade_amount: u32) { + glib::ObjectExt::set_property(self.as_ref(), "shade-amount", &shade_amount) + } + + fn shader(&self) -> AudioVisualizerShader { + glib::ObjectExt::property(self.as_ref(), "shader") + } + + fn set_shader(&self, shader: AudioVisualizerShader) { + glib::ObjectExt::set_property(self.as_ref(), "shader", &shader) + } + + fn connect_shade_amount_notify( + &self, + f: F, + ) -> SignalHandlerId { + unsafe extern "C" fn notify_shade_amount_trampoline< + P: IsA, + F: Fn(&P) + Send + Sync + 'static, + >( + this: *mut ffi::GstAudioVisualizer, + _param_spec: glib::ffi::gpointer, + f: glib::ffi::gpointer, + ) { + let f: &F = &*(f as *const F); + f(AudioVisualizer::from_glib_borrow(this).unsafe_cast_ref()) + } + unsafe { + let f: Box_ = Box_::new(f); + connect_raw( + self.as_ptr() as *mut _, + b"notify::shade-amount\0".as_ptr() as *const _, + Some(transmute::<_, unsafe extern "C" fn()>( + notify_shade_amount_trampoline:: as *const (), + )), + Box_::into_raw(f), + ) + } + } + + fn connect_shader_notify(&self, f: F) -> SignalHandlerId { + unsafe extern "C" fn notify_shader_trampoline< + P: IsA, + F: Fn(&P) + Send + Sync + 'static, + >( + this: *mut ffi::GstAudioVisualizer, + _param_spec: glib::ffi::gpointer, + f: glib::ffi::gpointer, + ) { + let f: &F = &*(f as *const F); + f(AudioVisualizer::from_glib_borrow(this).unsafe_cast_ref()) + } + unsafe { + let f: Box_ = Box_::new(f); + connect_raw( + self.as_ptr() as *mut _, + b"notify::shader\0".as_ptr() as *const _, + Some(transmute::<_, unsafe extern "C" fn()>( + notify_shader_trampoline:: as *const (), + )), + Box_::into_raw(f), + ) + } + } +} diff --git a/gstreamer-pbutils/src/auto/enums.rs b/gstreamer-pbutils/src/auto/enums.rs index 94c3ad16e..c8508dd6c 100644 --- a/gstreamer-pbutils/src/auto/enums.rs +++ b/gstreamer-pbutils/src/auto/enums.rs @@ -9,6 +9,108 @@ use glib::value::ToValue; use glib::StaticType; use glib::Type; +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] +#[non_exhaustive] +#[doc(alias = "GstAudioVisualizerShader")] +pub enum AudioVisualizerShader { + #[doc(alias = "GST_AUDIO_VISUALIZER_SHADER_NONE")] + None, + #[doc(alias = "GST_AUDIO_VISUALIZER_SHADER_FADE")] + Fade, + #[doc(alias = "GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_UP")] + FadeAndMoveUp, + #[doc(alias = "GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_DOWN")] + FadeAndMoveDown, + #[doc(alias = "GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_LEFT")] + FadeAndMoveLeft, + #[doc(alias = "GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_RIGHT")] + FadeAndMoveRight, + #[doc(alias = "GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_HORIZ_OUT")] + FadeAndMoveHorizOut, + #[doc(alias = "GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_HORIZ_IN")] + FadeAndMoveHorizIn, + #[doc(alias = "GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_VERT_OUT")] + FadeAndMoveVertOut, + #[doc(alias = "GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_VERT_IN")] + FadeAndMoveVertIn, + #[doc(hidden)] + __Unknown(i32), +} + +#[doc(hidden)] +impl IntoGlib for AudioVisualizerShader { + type GlibType = ffi::GstAudioVisualizerShader; + + fn into_glib(self) -> ffi::GstAudioVisualizerShader { + match self { + Self::None => ffi::GST_AUDIO_VISUALIZER_SHADER_NONE, + Self::Fade => ffi::GST_AUDIO_VISUALIZER_SHADER_FADE, + Self::FadeAndMoveUp => ffi::GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_UP, + Self::FadeAndMoveDown => ffi::GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_DOWN, + Self::FadeAndMoveLeft => ffi::GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_LEFT, + Self::FadeAndMoveRight => ffi::GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_RIGHT, + Self::FadeAndMoveHorizOut => ffi::GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_HORIZ_OUT, + Self::FadeAndMoveHorizIn => ffi::GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_HORIZ_IN, + Self::FadeAndMoveVertOut => ffi::GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_VERT_OUT, + Self::FadeAndMoveVertIn => ffi::GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_VERT_IN, + Self::__Unknown(value) => value, + } + } +} + +#[doc(hidden)] +impl FromGlib for AudioVisualizerShader { + unsafe fn from_glib(value: ffi::GstAudioVisualizerShader) -> Self { + skip_assert_initialized!(); + match value { + ffi::GST_AUDIO_VISUALIZER_SHADER_NONE => Self::None, + ffi::GST_AUDIO_VISUALIZER_SHADER_FADE => Self::Fade, + ffi::GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_UP => Self::FadeAndMoveUp, + ffi::GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_DOWN => Self::FadeAndMoveDown, + ffi::GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_LEFT => Self::FadeAndMoveLeft, + ffi::GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_RIGHT => Self::FadeAndMoveRight, + ffi::GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_HORIZ_OUT => Self::FadeAndMoveHorizOut, + ffi::GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_HORIZ_IN => Self::FadeAndMoveHorizIn, + ffi::GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_VERT_OUT => Self::FadeAndMoveVertOut, + ffi::GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_VERT_IN => Self::FadeAndMoveVertIn, + value => Self::__Unknown(value), + } + } +} + +impl StaticType for AudioVisualizerShader { + fn static_type() -> Type { + unsafe { from_glib(ffi::gst_audio_visualizer_shader_get_type()) } + } +} + +impl glib::value::ValueType for AudioVisualizerShader { + type Type = Self; +} + +unsafe impl<'a> FromValue<'a> for AudioVisualizerShader { + type Checker = glib::value::GenericValueTypeChecker; + + unsafe fn from_value(value: &'a glib::Value) -> Self { + skip_assert_initialized!(); + from_glib(glib::gobject_ffi::g_value_get_enum(value.to_glib_none().0)) + } +} + +impl ToValue for AudioVisualizerShader { + fn to_value(&self) -> glib::Value { + let mut value = glib::Value::for_value_type::(); + unsafe { + glib::gobject_ffi::g_value_set_enum(value.to_glib_none_mut().0, self.into_glib()); + } + value + } + + fn value_type(&self) -> glib::Type { + Self::static_type() + } +} + #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] #[non_exhaustive] #[doc(alias = "GstDiscovererResult")] diff --git a/gstreamer-pbutils/src/auto/mod.rs b/gstreamer-pbutils/src/auto/mod.rs index be665b1d9..7b51e46d4 100644 --- a/gstreamer-pbutils/src/auto/mod.rs +++ b/gstreamer-pbutils/src/auto/mod.rs @@ -3,6 +3,9 @@ // from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git) // DO NOT EDIT +mod audio_visualizer; +pub use self::audio_visualizer::AudioVisualizer; + mod discoverer; pub use self::discoverer::Discoverer; @@ -40,6 +43,7 @@ mod encoding_video_profile; pub use self::encoding_video_profile::EncodingVideoProfile; mod enums; +pub use self::enums::AudioVisualizerShader; pub use self::enums::DiscovererResult; mod flags; @@ -52,6 +56,7 @@ pub mod functions; #[doc(hidden)] pub mod traits { + pub use super::audio_visualizer::AudioVisualizerExt; pub use super::discoverer_stream_info::DiscovererStreamInfoExt; pub use super::encoding_profile::EncodingProfileExt; } diff --git a/gstreamer-pbutils/src/lib.rs b/gstreamer-pbutils/src/lib.rs index 503d323d8..4294611cb 100644 --- a/gstreamer-pbutils/src/lib.rs +++ b/gstreamer-pbutils/src/lib.rs @@ -57,12 +57,17 @@ pub mod encoding_profile; pub mod functions; pub use crate::functions::*; +pub mod subclass; + +pub mod audio_visualizer; + // Re-export all the traits in a prelude module, so that applications // can always "use gst_pbutils::prelude::*" without getting conflicts pub mod prelude { #[doc(hidden)] pub use gst::prelude::*; + pub use crate::audio_visualizer::*; pub use crate::auto::traits::*; pub use crate::encoding_profile::{ EncodingProfileBuilder, EncodingProfileHasRestrictionGetter, diff --git a/gstreamer-pbutils/src/subclass/audio_visualizer.rs b/gstreamer-pbutils/src/subclass/audio_visualizer.rs new file mode 100644 index 000000000..6f258becc --- /dev/null +++ b/gstreamer-pbutils/src/subclass/audio_visualizer.rs @@ -0,0 +1,226 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use glib::prelude::*; +use glib::translate::*; +use gst::subclass::prelude::*; +use gst::{result_from_gboolean, LoggableError, CAT_RUST}; + +use crate::AudioVisualizer; + +pub struct AudioVisualizerSetupToken<'a>(pub(crate) &'a AudioVisualizer); + +pub trait AudioVisualizerImpl: AudioVisualizerImplExt + ElementImpl { + fn setup( + &self, + element: &Self::Type, + token: &AudioVisualizerSetupToken, + ) -> Result<(), LoggableError> { + self.parent_setup(element, token) + } + + fn render( + &self, + element: &Self::Type, + audio_buffer: &gst::BufferRef, + video_frame: &mut gst_video::VideoFrameRef<&mut gst::BufferRef>, + ) -> Result<(), LoggableError> { + self.parent_render(element, audio_buffer, video_frame) + } + + fn decide_allocation( + &self, + element: &Self::Type, + query: &mut gst::query::Allocation, + ) -> Result<(), gst::LoggableError> { + self.parent_decide_allocation(element, query) + } +} + +pub trait AudioVisualizerImplExt: ObjectSubclass { + fn parent_setup( + &self, + element: &Self::Type, + token: &AudioVisualizerSetupToken, + ) -> Result<(), LoggableError>; + + fn parent_render( + &self, + element: &Self::Type, + audio_buffer: &gst::BufferRef, + video_frame: &mut gst_video::VideoFrameRef<&mut gst::BufferRef>, + ) -> Result<(), LoggableError>; + + fn parent_decide_allocation( + &self, + element: &Self::Type, + query: &mut gst::query::Allocation, + ) -> Result<(), gst::LoggableError>; +} + +impl AudioVisualizerImplExt for T { + fn parent_setup( + &self, + element: &Self::Type, + token: &AudioVisualizerSetupToken, + ) -> Result<(), LoggableError> { + assert_eq!( + element.as_ptr() as *mut ffi::GstAudioVisualizer, + token.0.as_ptr() as *mut ffi::GstAudioVisualizer + ); + + unsafe { + let data = Self::type_data(); + let parent_class = data.as_ref().parent_class() as *mut ffi::GstAudioVisualizerClass; + (*parent_class) + .setup + .map(|f| { + result_from_gboolean!( + f(element + .unsafe_cast_ref::() + .to_glib_none() + .0,), + CAT_RUST, + "Parent function `setup` failed", + ) + }) + .unwrap_or(Ok(())) + } + } + + fn parent_render( + &self, + element: &Self::Type, + audio_buffer: &gst::BufferRef, + video_frame: &mut gst_video::VideoFrameRef<&mut gst::BufferRef>, + ) -> Result<(), LoggableError> { + unsafe { + let data = Self::type_data(); + let parent_class = data.as_ref().parent_class() as *mut ffi::GstAudioVisualizerClass; + (*parent_class) + .render + .map(|f| { + result_from_gboolean!( + f( + element + .unsafe_cast_ref::() + .to_glib_none() + .0, + audio_buffer.as_mut_ptr(), + video_frame.as_mut_ptr(), + ), + CAT_RUST, + "Parent function `render` failed", + ) + }) + .unwrap_or(Ok(())) + } + } + + fn parent_decide_allocation( + &self, + element: &Self::Type, + query: &mut gst::query::Allocation, + ) -> Result<(), gst::LoggableError> { + unsafe { + let data = Self::type_data(); + let parent_class = data.as_ref().parent_class() as *mut ffi::GstAudioVisualizerClass; + (*parent_class) + .decide_allocation + .map(|f| { + gst::result_from_gboolean!( + f( + element + .unsafe_cast_ref::() + .to_glib_none() + .0, + query.as_mut_ptr(), + ), + gst::CAT_RUST, + "Parent function `decide_allocation` failed", + ) + }) + .unwrap_or(Ok(())) + } + } +} + +unsafe impl IsSubclassable for AudioVisualizer { + fn class_init(klass: &mut glib::Class) { + Self::parent_class_init::(klass); + let klass = klass.as_mut(); + klass.setup = Some(audio_visualizer_setup::); + klass.render = Some(audio_visualizer_render::); + klass.decide_allocation = Some(audio_visualizer_decide_allocation::); + } +} + +unsafe extern "C" fn audio_visualizer_setup( + ptr: *mut ffi::GstAudioVisualizer, +) -> gst::ffi::GstFlowReturn { + let instance = &*(ptr as *mut T::Instance); + let imp = instance.imp(); + let wrap: Borrowed = from_glib_borrow(ptr); + + gst::panic_to_error!(&wrap, imp.panicked(), false, { + let token = AudioVisualizerSetupToken(&*wrap); + + match imp.setup(wrap.unsafe_cast_ref(), &token) { + Ok(()) => true, + Err(err) => { + err.log_with_object(&*wrap); + false + } + } + }) + .into_glib() +} + +unsafe extern "C" fn audio_visualizer_render( + ptr: *mut ffi::GstAudioVisualizer, + audio_buffer: *mut gst::ffi::GstBuffer, + video_frame: *mut gst_video::ffi::GstVideoFrame, +) -> gst::ffi::GstFlowReturn { + let instance = &*(ptr as *mut T::Instance); + let imp = instance.imp(); + let wrap: Borrowed = from_glib_borrow(ptr); + let buffer = gst::BufferRef::from_ptr(audio_buffer); + + gst::panic_to_error!(&wrap, imp.panicked(), false, { + match imp.render( + wrap.unsafe_cast_ref(), + buffer, + &mut gst_video::VideoFrameRef::from_glib_borrow_mut(video_frame), + ) { + Ok(()) => true, + Err(err) => { + err.log_with_object(&*wrap); + false + } + } + }) + .into_glib() +} + +unsafe extern "C" fn audio_visualizer_decide_allocation( + ptr: *mut ffi::GstAudioVisualizer, + query: *mut gst::ffi::GstQuery, +) -> gst::ffi::GstFlowReturn { + let instance = &*(ptr as *mut T::Instance); + let imp = instance.imp(); + let wrap: Borrowed = from_glib_borrow(ptr); + let query = match gst::QueryRef::from_mut_ptr(query).view_mut() { + gst::QueryViewMut::Allocation(allocation) => allocation, + _ => unreachable!(), + }; + + gst::panic_to_error!(&wrap, imp.panicked(), false, { + match imp.decide_allocation(wrap.unsafe_cast_ref(), query) { + Ok(()) => true, + Err(err) => { + err.log_with_object(&*wrap); + false + } + } + }) + .into_glib() +} diff --git a/gstreamer-pbutils/src/subclass/mod.rs b/gstreamer-pbutils/src/subclass/mod.rs new file mode 100644 index 000000000..bd5fa9ce1 --- /dev/null +++ b/gstreamer-pbutils/src/subclass/mod.rs @@ -0,0 +1,8 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +mod audio_visualizer; +pub use audio_visualizer::AudioVisualizerSetupToken; + +pub mod prelude { + pub use super::audio_visualizer::{AudioVisualizerImpl, AudioVisualizerImplExt}; +}