From 80341fd90eba44321cf67b5c6329e849c42e93b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 5 Oct 2017 18:07:52 +0300 Subject: [PATCH] Add audiofx plugin with audioecho element --- Cargo.toml | 8 +- gst-plugin-audiofx/Cargo.toml | 20 ++ gst-plugin-audiofx/src/audioecho.rs | 386 ++++++++++++++++++++++++++++ gst-plugin-audiofx/src/lib.rs | 38 +++ 4 files changed, 451 insertions(+), 1 deletion(-) create mode 100644 gst-plugin-audiofx/Cargo.toml create mode 100644 gst-plugin-audiofx/src/audioecho.rs create mode 100644 gst-plugin-audiofx/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index c985b7a1..6e93fa24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,12 @@ [workspace] -members = ["gst-plugin", "gst-plugin-file", "gst-plugin-http", "gst-plugin-flv"] +members = [ + "gst-plugin", + "gst-plugin-file", + "gst-plugin-http", + "gst-plugin-flv", + "gst-plugin-audiofx", +] [profile.release] lto = true diff --git a/gst-plugin-audiofx/Cargo.toml b/gst-plugin-audiofx/Cargo.toml new file mode 100644 index 00000000..62322c94 --- /dev/null +++ b/gst-plugin-audiofx/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "gst-plugin-audiofx" +version = "0.1.0" +authors = ["Sebastian Dröge "] +repository = "https://github.com/sdroege/rsplugin" +license = "MIT/Apache-2.0" + +[dependencies] +gst-plugin = { path="../gst-plugin" } +glib = { git = "https://github.com/gtk-rs/glib" } +gstreamer = { git = "https://github.com/sdroege/gstreamer-rs", features = ["v1_10"] } +gstreamer-base = { git = "https://github.com/sdroege/gstreamer-rs", features = ["v1_10"] } +gstreamer-audio = { git = "https://github.com/sdroege/gstreamer-rs", features = ["v1_10"] } +byte-slice-cast = "0.1" +num-traits = "0.1" + +[lib] +name = "gstrsaudiofx" +crate-type = ["cdylib"] +path = "src/lib.rs" diff --git a/gst-plugin-audiofx/src/audioecho.rs b/gst-plugin-audiofx/src/audioecho.rs new file mode 100644 index 00000000..77ac2217 --- /dev/null +++ b/gst-plugin-audiofx/src/audioecho.rs @@ -0,0 +1,386 @@ +// Copyright (C) 2017 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; +use gst; +use gst::prelude::*; +use gst_base; +use gst_base::prelude::*; +use gst_audio; +use gst_audio::prelude::*; + +use gst_plugin::object::*; +use gst_plugin::element::*; +use gst_plugin::base_transform::*; + +use std::{cmp, iter, i32, u64}; +use std::sync::Mutex; + +use byte_slice_cast::*; + +use num_traits::float::Float; +use num_traits::cast::{FromPrimitive, ToPrimitive}; + +const DEFAULT_MAX_DELAY: u64 = 1 * gst::SECOND; +const DEFAULT_DELAY: u64 = 500 * gst::MSECOND; +const DEFAULT_INTENSITY: f64 = 0.5; +const DEFAULT_FEEDBACK: f64 = 0.0; + +#[derive(Debug, Clone, Copy)] +struct Settings { + pub max_delay: u64, + pub delay: u64, + pub intensity: f64, + pub feedback: f64, +} + +impl Default for Settings { + fn default() -> Self { + Settings { + max_delay: DEFAULT_MAX_DELAY, + delay: DEFAULT_DELAY, + intensity: DEFAULT_INTENSITY, + feedback: DEFAULT_FEEDBACK, + } + } +} + +struct State { + info: gst_audio::AudioInfo, + buffer: RingBuffer, +} + +struct AudioEcho { + cat: gst::DebugCategory, + settings: Mutex, + state: Mutex>, +} + +static PROPERTIES: [Property; 4] = [ + Property::UInt64( + "max-delay", + "Maximum Delay", + "Maximum delay of the echo in nanoseconds (can't be changed in PLAYING or PAUSED state)", + (0, u64::MAX), + DEFAULT_MAX_DELAY, + PropertyMutability::ReadWrite, + ), + Property::UInt64( + "delay", + "Delay", + "Delay of the echo in nanoseconds", + (0, u64::MAX), + DEFAULT_DELAY, + PropertyMutability::ReadWrite, + ), + Property::Double( + "intensity", + "Intensity", + "Intensity of the echo", + (0.0, 1.0), + DEFAULT_INTENSITY, + PropertyMutability::ReadWrite, + ), + Property::Double( + "feedback", + "Feedback", + "Amount of feedback", + (0.0, 1.0), + DEFAULT_FEEDBACK, + PropertyMutability::ReadWrite, + ), +]; + +impl AudioEcho { + fn new(_transform: &RsBaseTransform) -> Self { + Self { + cat: gst::DebugCategory::new( + "rsaudiofx", + gst::DebugColorFlags::empty(), + "Rust audiofx effect", + ), + settings: Mutex::new(Default::default()), + state: Mutex::new(None), + } + } + + fn class_init(klass: &mut RsBaseTransformClass) { + klass.set_metadata( + "Audio echo", + "Filter/Effect/Audio", + "Adds an echo or reverb effect to an audio stream", + "Sebastian Dröge ", + ); + + let caps = gst::Caps::new_simple( + "audio/x-raw", + &[ + ( + "format", + &gst::List::new(&[ + &gst_audio::AUDIO_FORMAT_F32.to_string(), + &gst_audio::AUDIO_FORMAT_F64.to_string(), + ]), + ), + ("rate", &gst::IntRange::::new(0, i32::MAX)), + ("channels", &gst::IntRange::::new(0, i32::MAX)), + ("layout", &"interleaved"), + ], + ); + let src_pad_template = gst::PadTemplate::new( + "src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &caps, + ); + klass.add_pad_template(src_pad_template); + + let sink_pad_template = gst::PadTemplate::new( + "sink", + gst::PadDirection::Sink, + gst::PadPresence::Always, + &caps, + ); + klass.add_pad_template(sink_pad_template); + + klass.install_properties(&PROPERTIES); + + klass.configure(BaseTransformMode::AlwaysInPlace, false, false); + } + + fn init(element: &RsBaseTransform) -> Box> { + let imp = Self::new(element); + Box::new(imp) + } + + fn process(data: &mut [F], state: &mut State, settings: &Settings) { + let delay_frames = (settings.delay as usize) * (state.info.channels() as usize) * (state.info.rate() as usize) / + (gst::SECOND as usize); + + for (i, (o, e)) in data.iter_mut().zip(state.buffer.iter(delay_frames)) { + let inp = (*i).to_f64().unwrap(); + let out = inp + settings.intensity * e; + *o = inp + settings.feedback * e; + *i = FromPrimitive::from_f64(out).unwrap(); + } + } +} + +impl ObjectImpl for AudioEcho { + fn set_property(&self, _obj: &glib::Object, id: u32, value: &glib::Value) { + let prop = &PROPERTIES[id as usize]; + + match *prop { + Property::UInt64("max-delay", ..) => { + let mut settings = self.settings.lock().unwrap(); + settings.max_delay = value.get().unwrap(); + } + Property::UInt64("delay", ..) => { + let mut settings = self.settings.lock().unwrap(); + settings.delay = value.get().unwrap(); + } + Property::Double("intensity", ..) => { + let mut settings = self.settings.lock().unwrap(); + settings.intensity = value.get().unwrap(); + } + Property::Double("feedback", ..) => { + let mut settings = self.settings.lock().unwrap(); + settings.feedback = value.get().unwrap(); + } + _ => unimplemented!(), + } + } + + fn get_property(&self, _obj: &glib::Object, id: u32) -> Result { + let prop = &PROPERTIES[id as usize]; + + match *prop { + Property::UInt64("max-delay", ..) => { + let settings = self.settings.lock().unwrap(); + if self.state.lock().unwrap().is_none() { + Ok(settings.max_delay.to_value()) + } else { + Err(()) + } + } + Property::UInt64("delay", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.delay.to_value()) + } + Property::Double("intensity", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.intensity.to_value()) + } + Property::Double("feedback", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.feedback.to_value()) + } + _ => unimplemented!(), + } + } +} + +impl ElementImpl for AudioEcho {} + +impl BaseTransformImpl for AudioEcho { + + fn transform_ip(&self, _element: &RsBaseTransform, buf: &gst::Buffer) -> gst::FlowReturn { + let mut settings = *self.settings.lock().unwrap(); + settings.delay = cmp::min(settings.max_delay, settings.delay); + + let mut state_guard = self.state.lock().unwrap(); + let state = match *state_guard { + None => return gst::FlowReturn::NotNegotiated, + Some(ref mut state) => state, + }; + + let mut map = match buf.get_mut().unwrap().map_writable() { + None => return gst::FlowReturn::Error, + Some(map) => map, + }; + + match state.info.format() { + gst_audio::AUDIO_FORMAT_F64 => { + let mut data = map.as_mut_slice().as_mut_slice_of::().unwrap(); + Self::process(data, state, &settings); + }, + gst_audio::AUDIO_FORMAT_F32 => { + let mut data = map.as_mut_slice().as_mut_slice_of::().unwrap(); + Self::process(data, state, &settings); + }, + _ => return gst::FlowReturn::NotNegotiated, + } + + gst::FlowReturn::Ok + } + + fn set_caps( + &self, + _element: &RsBaseTransform, + incaps: &gst::Caps, + outcaps: &gst::Caps, + ) -> bool { + if incaps != outcaps { + return false; + } + + let info = match gst_audio::AudioInfo::from_caps(incaps) { + None => return false, + Some(info) => info, + }; + + let max_delay = self.settings.lock().unwrap().max_delay; + let size = max_delay * (info.rate() as u64) / gst::SECOND; + let buffer_size = size * (info.channels() as u64); + + *self.state.lock().unwrap() = Some(State { + info: info, + buffer: RingBuffer::new(buffer_size as usize), + }); + + true + } + + fn stop(&self, _element: &RsBaseTransform) -> bool { + // Drop state + let _ = self.state.lock().unwrap().take(); + + true + } +} + +struct AudioEchoStatic; + +impl ImplTypeStatic for AudioEchoStatic { + fn get_name(&self) -> &str { + "AudioEcho" + } + + fn new(&self, element: &RsBaseTransform) -> Box> { + AudioEcho::init(element) + } + + fn class_init(&self, klass: &mut RsBaseTransformClass) { + AudioEcho::class_init(klass); + } +} + +pub fn register(plugin: &gst::Plugin) { + let audioecho_static = AudioEchoStatic; + let type_ = register_type(audioecho_static); + gst::Element::register(plugin, "rsaudioecho", 0, type_); +} + +struct RingBuffer { + buffer: Vec, + pos: usize, + size: usize, +} + +impl RingBuffer { + fn new(size: usize) -> Self { + let mut buffer = Vec::with_capacity(size as usize); + buffer.extend(iter::repeat(0.0).take(size as usize)); + + Self { + buffer: buffer, + pos: 0, + size: size, + } + } + + fn iter(&mut self, delay: usize) -> RingBufferIter { + RingBufferIter::new(self, delay) + } +} + +struct RingBufferIter<'a> { + buffer: &'a mut RingBuffer, + ptr: *mut f64, + read_pos: usize, + write_pos: usize, +} + +impl<'a> RingBufferIter<'a> { + fn new(buffer: &'a mut RingBuffer, delay: usize) -> RingBufferIter<'a> { + assert!(buffer.size >= delay); + assert_ne!(buffer.size, 0); + + let ptr = buffer.buffer.as_mut_ptr(); + + let read_pos = (buffer.size - delay + buffer.pos) % buffer.size; + let write_pos = buffer.pos % buffer.size; + + RingBufferIter { + buffer: buffer, + ptr: ptr, + read_pos: read_pos, + write_pos: write_pos, + } + } +} + +impl<'a> Iterator for RingBufferIter<'a> { + type Item = (&'a mut f64, f64); + + fn next(&mut self) -> Option { + unsafe { + let res = (&mut *self.ptr.offset(self.write_pos as isize), *self.ptr.offset(self.read_pos as isize)); + self.write_pos = (self.write_pos + 1) % self.buffer.size; + self.read_pos = (self.read_pos + 1) % self.buffer.size; + + Some(res) + } + } +} + +impl<'a> Drop for RingBufferIter<'a> { + fn drop(&mut self) { + self.buffer.pos = self.write_pos; + } +} diff --git a/gst-plugin-audiofx/src/lib.rs b/gst-plugin-audiofx/src/lib.rs new file mode 100644 index 00000000..e33d1179 --- /dev/null +++ b/gst-plugin-audiofx/src/lib.rs @@ -0,0 +1,38 @@ +// Copyright (C) 2017 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. + +#![crate_type = "cdylib"] + +extern crate byte_slice_cast; +extern crate glib; +#[macro_use] +extern crate gst_plugin; +#[macro_use] +extern crate gstreamer as gst; +extern crate gstreamer_audio as gst_audio; +extern crate gstreamer_base as gst_base; +extern crate num_traits; + +mod audioecho; + +fn plugin_init(plugin: &gst::Plugin) -> bool { + audioecho::register(plugin); + true +} + +plugin_define!( + b"rsaudiofx\0", + b"Rust AudioFx Plugin\0", + plugin_init, + b"1.0\0", + b"MIT/X11\0", + b"rsaudiofx\0", + b"rsaudiofx\0", + b"https://github.com/sdroege/rsplugin\0", + b"2016-12-08\0" +);