forked from mirrors/gstreamer-rs
examples: Add an example that shows how to write subclasses with virtual methods
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1356>
This commit is contained in:
parent
84ca72a833
commit
0ec7b2608c
7 changed files with 516 additions and 0 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -470,6 +470,7 @@ name = "examples"
|
|||
version = "0.22.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"atomic_refcell",
|
||||
"byte-slice-cast",
|
||||
"cairo-rs",
|
||||
"cocoa",
|
||||
|
|
|
@ -40,6 +40,7 @@ pangocairo = { git = "https://github.com/gtk-rs/gtk-rs-core", optional = true }
|
|||
raw-window-handle = { version = "0.5", optional = true }
|
||||
uds = { version = "0.4", optional = true }
|
||||
winit = { version = "0.29", optional = true, default-features = false, features = ["rwh_05"] }
|
||||
atomic_refcell = "0.1"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows = { version = "0.52", features=["Win32_Graphics_Direct3D11",
|
||||
|
|
186
examples/src/bin/subclass_vfuncs/iirfilter/imp.rs
Normal file
186
examples/src/bin/subclass_vfuncs/iirfilter/imp.rs
Normal file
|
@ -0,0 +1,186 @@
|
|||
// In the imp submodule we include the actual implementation
|
||||
|
||||
use std::{collections::VecDeque, sync::Mutex};
|
||||
|
||||
use glib::{once_cell::sync::Lazy, prelude::*};
|
||||
use gst_audio::subclass::prelude::*;
|
||||
|
||||
use byte_slice_cast::*;
|
||||
|
||||
use atomic_refcell::AtomicRefCell;
|
||||
|
||||
// The debug category we use below for our filter
|
||||
pub static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||
gst::DebugCategory::new(
|
||||
"rsiirfilter",
|
||||
gst::DebugColorFlags::empty(),
|
||||
Some("Rust IIR Filter"),
|
||||
)
|
||||
});
|
||||
|
||||
#[derive(Default)]
|
||||
// This is the state of our filter
|
||||
struct State {
|
||||
a: Vec<f64>,
|
||||
b: Vec<f64>,
|
||||
x: VecDeque<f64>,
|
||||
y: VecDeque<f64>,
|
||||
}
|
||||
|
||||
// This is the private data of our filter
|
||||
#[derive(Default)]
|
||||
pub struct IirFilter {
|
||||
coeffs: Mutex<Option<(Vec<f64>, Vec<f64>)>>,
|
||||
state: AtomicRefCell<State>,
|
||||
}
|
||||
|
||||
// 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
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for IirFilter {
|
||||
const NAME: &'static str = "RsIirFilter";
|
||||
const ABSTRACT: bool = true;
|
||||
type Type = super::IirFilter;
|
||||
type ParentType = gst_audio::AudioFilter;
|
||||
type Class = super::Class;
|
||||
|
||||
fn class_init(class: &mut Self::Class) {
|
||||
class.set_rate = |obj, rate| obj.imp().set_rate_default(rate);
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation of glib::Object virtual methods
|
||||
impl ObjectImpl for IirFilter {}
|
||||
|
||||
impl GstObjectImpl for IirFilter {}
|
||||
|
||||
// Implementation of gst::Element virtual methods
|
||||
impl ElementImpl for IirFilter {}
|
||||
|
||||
// Implementation of gst_base::BaseTransform virtual methods
|
||||
impl BaseTransformImpl for IirFilter {
|
||||
// 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;
|
||||
|
||||
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
self.parent_start()?;
|
||||
|
||||
*self.state.borrow_mut() = State::default();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
self.parent_stop()?;
|
||||
|
||||
*self.state.borrow_mut() = State::default();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transform_ip(&self, buf: &mut gst::BufferRef) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
let mut state = self.state.borrow_mut();
|
||||
|
||||
// Update coefficients if new coefficients were set
|
||||
{
|
||||
let mut coeffs = self.coeffs.lock().unwrap();
|
||||
|
||||
if let Some((a, b)) = coeffs.take() {
|
||||
state.x.clear();
|
||||
state.y.clear();
|
||||
if !a.is_empty() {
|
||||
state.y.resize(a.len() - 1, 0.0);
|
||||
}
|
||||
if !b.is_empty() {
|
||||
state.x.resize(b.len() - 1, 0.0);
|
||||
}
|
||||
state.a = a;
|
||||
state.b = b;
|
||||
}
|
||||
}
|
||||
|
||||
if state.a.is_empty() | state.b.is_empty() {
|
||||
return Ok(gst::FlowSuccess::Ok);
|
||||
}
|
||||
|
||||
let mut map = buf.map_writable().map_err(|_| {
|
||||
gst::error!(CAT, imp: self, "Failed to map buffer writable");
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
|
||||
let samples = map.as_mut_slice_of::<f32>().unwrap();
|
||||
|
||||
assert!(state.b.len() - 1 == state.x.len());
|
||||
assert!(state.a.len() - 1 == state.y.len());
|
||||
|
||||
for sample in samples.iter_mut() {
|
||||
let mut val = state.b[0] * *sample as f64;
|
||||
|
||||
for (b, x) in Iterator::zip(state.b.iter().skip(1), state.x.iter()) {
|
||||
val += b * x;
|
||||
}
|
||||
|
||||
for (a, y) in Iterator::zip(state.a.iter().skip(1), state.y.iter()) {
|
||||
val -= a * y;
|
||||
}
|
||||
|
||||
val /= state.a[0];
|
||||
|
||||
let _ = state.x.pop_back().unwrap();
|
||||
state.x.push_front(*sample as f64);
|
||||
|
||||
let _ = state.y.pop_back().unwrap();
|
||||
state.y.push_front(val);
|
||||
|
||||
*sample = val as f32;
|
||||
}
|
||||
|
||||
Ok(gst::FlowSuccess::Ok)
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioFilterImpl for IirFilter {
|
||||
fn allowed_caps() -> &'static gst::Caps {
|
||||
static CAPS: Lazy<gst::Caps> = Lazy::new(|| {
|
||||
// On both of pads we can only handle F32 mono at any sample rate.
|
||||
gst_audio::AudioCapsBuilder::new_interleaved()
|
||||
.format(gst_audio::AUDIO_FORMAT_F32)
|
||||
.channels(1)
|
||||
.build()
|
||||
});
|
||||
|
||||
&CAPS
|
||||
}
|
||||
|
||||
fn setup(&self, info: &gst_audio::AudioInfo) -> Result<(), gst::LoggableError> {
|
||||
self.parent_setup(info)?;
|
||||
|
||||
gst::debug!(CAT, imp: self, "Rate changed to {}", info.rate());
|
||||
let obj = self.obj();
|
||||
(obj.class().as_ref().set_rate)(&obj, info.rate());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrappers for public methods and associated helper functions.
|
||||
impl IirFilter {
|
||||
pub(super) fn set_coeffs(&self, a: Vec<f64>, b: Vec<f64>) {
|
||||
gst::debug!(CAT, imp: self, "Setting coefficients a: {a:?}, b: {b:?}");
|
||||
*self.coeffs.lock().unwrap() = Some((a, b));
|
||||
}
|
||||
}
|
||||
|
||||
/// Default virtual method implementations.
|
||||
impl IirFilter {
|
||||
fn set_rate_default(&self, _rate: u32) {}
|
||||
}
|
82
examples/src/bin/subclass_vfuncs/iirfilter/mod.rs
Normal file
82
examples/src/bin/subclass_vfuncs/iirfilter/mod.rs
Normal file
|
@ -0,0 +1,82 @@
|
|||
use gst::{prelude::*, subclass::prelude::*};
|
||||
use gst_audio::subclass::prelude::*;
|
||||
|
||||
mod imp;
|
||||
|
||||
// This here defines the public interface of our element and implements
|
||||
// the corresponding traits so that it behaves like any other gst::Element
|
||||
//
|
||||
// GObject
|
||||
// ╰──GstObject
|
||||
// ╰──GstElement
|
||||
// ╰──GstBaseTransform
|
||||
// ╰──GstAudioFilter
|
||||
// ╰──IirFilter
|
||||
glib::wrapper! {
|
||||
pub struct IirFilter(ObjectSubclass<imp::IirFilter>) @extends gst_audio::AudioFilter, gst_base::BaseTransform, gst::Element, gst::Object;
|
||||
}
|
||||
|
||||
/// Trait containing extension methods for `IirFilter`.
|
||||
pub trait IirFilterExt: IsA<IirFilter> {
|
||||
// Sets the coefficients by getting access to the private struct and simply setting them
|
||||
fn set_coeffs(&self, a: Vec<f64>, b: Vec<f64>) {
|
||||
self.upcast_ref::<IirFilter>().imp().set_coeffs(a, b)
|
||||
}
|
||||
}
|
||||
|
||||
impl<O: IsA<IirFilter>> IirFilterExt for O {}
|
||||
|
||||
/// Trait to implement in `IirFilter` subclasses.
|
||||
pub trait IirFilterImpl: AudioFilterImpl {
|
||||
/// Called whenever the sample rate is changing.
|
||||
fn set_rate(&self, rate: u32) {
|
||||
self.parent_set_rate(rate);
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait containing extension methods for `IirFilterImpl`, specifically methods for chaining
|
||||
/// up to the parent implementation of virtual methods.
|
||||
pub trait IirFilterImplExt: IirFilterImpl {
|
||||
fn parent_set_rate(&self, rate: u32) {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.set_rate)(self.obj().unsafe_cast_ref(), rate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IirFilterImpl> IirFilterImplExt for T {}
|
||||
|
||||
/// Class struct for `IirFilter`.
|
||||
#[repr(C)]
|
||||
pub struct Class {
|
||||
parent: <<imp::IirFilter as ObjectSubclass>::ParentType as glib::ObjectType>::GlibClassType,
|
||||
|
||||
set_rate: fn(&IirFilter, rate: u32),
|
||||
}
|
||||
|
||||
unsafe impl ClassStruct for Class {
|
||||
type Type = imp::IirFilter;
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Class {
|
||||
type Target = glib::Class<<<Self as ClassStruct>::Type as ObjectSubclass>::ParentType>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { &*(&self.parent as *const _ as *const _) }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: IirFilterImpl> IsSubclassable<T> for IirFilter {
|
||||
fn class_init(class: &mut glib::Class<Self>) {
|
||||
Self::parent_class_init::<T>(class);
|
||||
|
||||
let class = class.as_mut();
|
||||
|
||||
class.set_rate = |obj, rate| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.set_rate(rate);
|
||||
};
|
||||
}
|
||||
}
|
165
examples/src/bin/subclass_vfuncs/lowpass/imp.rs
Normal file
165
examples/src/bin/subclass_vfuncs/lowpass/imp.rs
Normal file
|
@ -0,0 +1,165 @@
|
|||
// In the imp submodule we include the actual implementation
|
||||
|
||||
use std::sync::Mutex;
|
||||
|
||||
use glib::{once_cell::sync::Lazy, prelude::*};
|
||||
use gst::prelude::*;
|
||||
use gst_audio::subclass::prelude::*;
|
||||
|
||||
use crate::iirfilter::{IirFilterExt, IirFilterImpl};
|
||||
|
||||
// These are the property values of our filter
|
||||
pub struct Settings {
|
||||
cutoff: f32,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Settings { cutoff: 0.0 }
|
||||
}
|
||||
}
|
||||
|
||||
// This is the state of our filter
|
||||
#[derive(Default)]
|
||||
pub struct State {
|
||||
rate: Option<u32>,
|
||||
}
|
||||
|
||||
// This is the private data of our filter
|
||||
#[derive(Default)]
|
||||
pub struct Lowpass {
|
||||
settings: Mutex<Settings>,
|
||||
state: Mutex<State>,
|
||||
}
|
||||
|
||||
// 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
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for Lowpass {
|
||||
const NAME: &'static str = "RsLowpass";
|
||||
type Type = super::Lowpass;
|
||||
type ParentType = crate::iirfilter::IirFilter;
|
||||
}
|
||||
|
||||
// Implementation of glib::Object virtual methods
|
||||
impl ObjectImpl for Lowpass {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
vec![glib::ParamSpecFloat::builder("cutoff")
|
||||
.nick("Cutoff")
|
||||
.blurb("Cutoff frequency in Hz")
|
||||
.default_value(Settings::default().cutoff)
|
||||
.minimum(0.0)
|
||||
.mutable_playing()
|
||||
.build()]
|
||||
});
|
||||
|
||||
PROPERTIES.as_ref()
|
||||
}
|
||||
|
||||
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
match pspec.name() {
|
||||
"cutoff" => {
|
||||
self.settings.lock().unwrap().cutoff = value.get().unwrap();
|
||||
self.calculate_coeffs();
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
}
|
||||
|
||||
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
match pspec.name() {
|
||||
"cutoff" => self.settings.lock().unwrap().cutoff.to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GstObjectImpl for Lowpass {}
|
||||
|
||||
// Implementation of gst::Element virtual methods
|
||||
impl ElementImpl for Lowpass {
|
||||
// The element specific metadata. This information is what is visible from
|
||||
// gst-inspect-1.0 and can also be programmatically 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<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||
gst::subclass::ElementMetadata::new(
|
||||
"Lowpass Filter",
|
||||
"Filter/Effect/Audio",
|
||||
"A Lowpass audio filter",
|
||||
"Sebastian Dröge <sebastian@centricular.com>",
|
||||
)
|
||||
});
|
||||
|
||||
Some(&*ELEMENT_METADATA)
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation of gst_base::BaseTransform virtual methods
|
||||
impl BaseTransformImpl for Lowpass {
|
||||
const MODE: gst_base::subclass::BaseTransformMode =
|
||||
<<crate::iirfilter::IirFilter as glib::object::ObjectSubclassIs>::Subclass>::MODE;
|
||||
const PASSTHROUGH_ON_SAME_CAPS: bool =
|
||||
<<crate::iirfilter::IirFilter as glib::object::ObjectSubclassIs>::Subclass>::PASSTHROUGH_ON_SAME_CAPS;
|
||||
const TRANSFORM_IP_ON_PASSTHROUGH: bool =
|
||||
<<crate::iirfilter::IirFilter as glib::object::ObjectSubclassIs>::Subclass>::TRANSFORM_IP_ON_PASSTHROUGH;
|
||||
|
||||
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
self.parent_start()?;
|
||||
|
||||
*self.state.lock().unwrap() = State::default();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Implement of gst_audio::AudioFilter virtual methods
|
||||
impl AudioFilterImpl for Lowpass {}
|
||||
|
||||
// Implement of IirFilter virtual methods
|
||||
impl IirFilterImpl for Lowpass {
|
||||
fn set_rate(&self, rate: u32) {
|
||||
self.state.lock().unwrap().rate = Some(rate);
|
||||
self.calculate_coeffs();
|
||||
}
|
||||
}
|
||||
|
||||
impl Lowpass {
|
||||
fn calculate_coeffs(&self) {
|
||||
use std::f64;
|
||||
|
||||
let Some(rate) = self.state.lock().unwrap().rate else {
|
||||
return;
|
||||
};
|
||||
let cutoff = self.settings.lock().unwrap().cutoff;
|
||||
|
||||
// See Audio EQ Cookbook
|
||||
// https://www.w3.org/TR/audio-eq-cookbook
|
||||
let cutoff = cutoff as f64 / rate as f64;
|
||||
|
||||
let omega = 2.0 * f64::consts::PI * cutoff;
|
||||
let q = 1.0;
|
||||
|
||||
let alpha = f64::sin(omega) / (2.0 * q);
|
||||
|
||||
let mut b = vec![
|
||||
(1.0 - f64::cos(omega)) / 2.0,
|
||||
1.0 - f64::cos(omega),
|
||||
(1.0 - f64::cos(omega) / 2.0),
|
||||
];
|
||||
|
||||
let mut a = vec![1.0 + alpha, -2.0 * f64::cos(omega), 1.0 - alpha];
|
||||
|
||||
let a0 = a[0];
|
||||
for a in &mut a {
|
||||
*a /= a0;
|
||||
}
|
||||
for b in &mut b {
|
||||
*b /= a0;
|
||||
}
|
||||
|
||||
self.obj().set_coeffs(a, b);
|
||||
}
|
||||
}
|
15
examples/src/bin/subclass_vfuncs/lowpass/mod.rs
Normal file
15
examples/src/bin/subclass_vfuncs/lowpass/mod.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
mod imp;
|
||||
|
||||
// This here defines the public interface of our element and implements
|
||||
// the corresponding traits so that it behaves like any other gst::Element
|
||||
//
|
||||
// GObject
|
||||
// ╰──GstObject
|
||||
// ╰──GstElement
|
||||
// ╰──GstBaseTransform
|
||||
// ╰──GstAudioFilter
|
||||
// ╰──IirFilter
|
||||
// ╰──Lowpass
|
||||
glib::wrapper! {
|
||||
pub struct Lowpass(ObjectSubclass<imp::Lowpass>) @extends crate::iirfilter::IirFilter, gst_audio::AudioFilter, gst_base::BaseTransform, gst::Element, gst::Object;
|
||||
}
|
66
examples/src/bin/subclass_vfuncs/main.rs
Normal file
66
examples/src/bin/subclass_vfuncs/main.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
// This example implements a baseclass IirFilter, and a subclass Lowpass of that.
|
||||
//
|
||||
// The example shows how to provide and implement virtual methods, and how to provide non-virtual
|
||||
// methods on the base class.
|
||||
|
||||
use gst::prelude::*;
|
||||
|
||||
mod iirfilter;
|
||||
mod lowpass;
|
||||
|
||||
#[path = "../../examples-common.rs"]
|
||||
mod examples_common;
|
||||
|
||||
fn example_main() {
|
||||
gst::init().unwrap();
|
||||
|
||||
let pipeline = gst::Pipeline::new();
|
||||
let src = gst::ElementFactory::make("audiotestsrc")
|
||||
.property_from_str("wave", "white-noise")
|
||||
.build()
|
||||
.unwrap();
|
||||
let filter = glib::Object::builder::<lowpass::Lowpass>()
|
||||
.property("cutoff", 4000.0f32)
|
||||
.build();
|
||||
let conv = gst::ElementFactory::make("audioconvert").build().unwrap();
|
||||
let sink = gst::ElementFactory::make("autoaudiosink").build().unwrap();
|
||||
|
||||
pipeline
|
||||
.add_many([&src, filter.as_ref(), &conv, &sink])
|
||||
.unwrap();
|
||||
gst::Element::link_many([&src, filter.as_ref(), &conv, &sink]).unwrap();
|
||||
|
||||
let bus = pipeline.bus().unwrap();
|
||||
|
||||
pipeline
|
||||
.set_state(gst::State::Playing)
|
||||
.expect("Unable to set the pipeline to the `Playing` state");
|
||||
|
||||
for msg in bus.iter_timed(gst::ClockTime::NONE) {
|
||||
use gst::MessageView;
|
||||
|
||||
match msg.view() {
|
||||
MessageView::Eos(..) => break,
|
||||
MessageView::Error(err) => {
|
||||
println!(
|
||||
"Error from {:?}: {} ({:?})",
|
||||
err.src().map(|s| s.path_string()),
|
||||
err.error(),
|
||||
err.debug()
|
||||
);
|
||||
break;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
pipeline
|
||||
.set_state(gst::State::Null)
|
||||
.expect("Unable to set the pipeline to the `Null` state");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// tutorials_common::run is only required to set up the application environment on macOS
|
||||
// (but not necessary in normal Cocoa applications where this is set up automatically)
|
||||
examples_common::run(example_main);
|
||||
}
|
Loading…
Reference in a new issue