diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/cubeb/src | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/cubeb/src')
-rw-r--r-- | third_party/rust/cubeb/src/context.rs | 9 | ||||
-rw-r--r-- | third_party/rust/cubeb/src/frame.rs | 25 | ||||
-rw-r--r-- | third_party/rust/cubeb/src/lib.rs | 38 | ||||
-rw-r--r-- | third_party/rust/cubeb/src/sample.rs | 24 | ||||
-rw-r--r-- | third_party/rust/cubeb/src/stream.rs | 378 |
5 files changed, 474 insertions, 0 deletions
diff --git a/third_party/rust/cubeb/src/context.rs b/third_party/rust/cubeb/src/context.rs new file mode 100644 index 0000000000..6b9d8fc03d --- /dev/null +++ b/third_party/rust/cubeb/src/context.rs @@ -0,0 +1,9 @@ +use std::ffi::CString; +use {Context, Result}; + +/// Initialize a new cubeb [`Context`] +pub fn init<T: Into<Vec<u8>>>(name: T) -> Result<Context> { + let name = CString::new(name)?; + + Context::init(Some(name.as_c_str()), None) +} diff --git a/third_party/rust/cubeb/src/frame.rs b/third_party/rust/cubeb/src/frame.rs new file mode 100644 index 0000000000..65fd2084f6 --- /dev/null +++ b/third_party/rust/cubeb/src/frame.rs @@ -0,0 +1,25 @@ +//! Frame utilities + +/// A `Frame` is a collection of samples which have a a specific +/// layout represented by `ChannelLayout` +pub trait Frame {} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +/// A monaural frame. +pub struct MonoFrame<T> { + /// Mono channel + pub m: T, +} + +impl<T> Frame for MonoFrame<T> {} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +/// A stereo frame. +pub struct StereoFrame<T> { + /// Left channel + pub l: T, + /// Right channel + pub r: T, +} + +impl<T> Frame for StereoFrame<T> {} diff --git a/third_party/rust/cubeb/src/lib.rs b/third_party/rust/cubeb/src/lib.rs new file mode 100644 index 0000000000..7e9764941b --- /dev/null +++ b/third_party/rust/cubeb/src/lib.rs @@ -0,0 +1,38 @@ +//! # libcubeb bindings for rust +//! +//! This library contains bindings to the [cubeb][1] C library which +//! is used to interact with system audio. The library itself is a +//! work in progress and is likely lacking documentation and test. +//! +//! [1]: https://github.com/kinetiknz/cubeb/ +//! +//! The cubeb-rs library exposes the user API of libcubeb. It doesn't +//! expose the internal interfaces, so isn't suitable for extending +//! libcubeb. See [cubeb-pulse-rs][2] for an example of extending +//! libcubeb via implementing a cubeb backend in rust. +//! +//! To get started, have a look at the [`StreamBuilder`] + +// Copyright © 2017-2018 Mozilla Foundation +// +// This program is made available under an ISC-style license. See the +// accompanying file LICENSE for details. + +extern crate cubeb_core; + +mod context; +mod frame; +mod sample; +mod stream; + +pub use context::*; +// Re-export cubeb_core types +pub use cubeb_core::{ + ffi, ChannelLayout, Context, ContextRef, Device, DeviceCollection, DeviceCollectionRef, + DeviceFormat, DeviceId, DeviceInfo, DeviceInfoRef, DeviceRef, DeviceState, DeviceType, Error, + ErrorCode, LogLevel, Result, SampleFormat, State, StreamParams, StreamParamsBuilder, + StreamParamsRef, StreamPrefs, StreamRef, +}; +pub use frame::*; +pub use sample::*; +pub use stream::*; diff --git a/third_party/rust/cubeb/src/sample.rs b/third_party/rust/cubeb/src/sample.rs new file mode 100644 index 0000000000..7d4dc7d475 --- /dev/null +++ b/third_party/rust/cubeb/src/sample.rs @@ -0,0 +1,24 @@ +// Copyright © 2017-2018 Mozilla Foundation +// +// This program is made available under an ISC-style license. See the +// accompanying file LICENSE for details. + +/// An extension trait which allows the implementation of converting +/// void* buffers from libcubeb-sys into rust slices of the appropriate +/// type. +pub trait Sample: Send + Copy { + /// Map f32 in range [-1,1] to sample type + fn from_float(_: f32) -> Self; +} + +impl Sample for i16 { + fn from_float(x: f32) -> i16 { + (x * f32::from(i16::max_value())) as i16 + } +} + +impl Sample for f32 { + fn from_float(x: f32) -> f32 { + x + } +} diff --git a/third_party/rust/cubeb/src/stream.rs b/third_party/rust/cubeb/src/stream.rs new file mode 100644 index 0000000000..33c37fd6cb --- /dev/null +++ b/third_party/rust/cubeb/src/stream.rs @@ -0,0 +1,378 @@ +// Copyright © 2017-2018 Mozilla Foundation +// +// This program is made available under an ISC-style license. See the +// accompanying file LICENSE for details. + +use cubeb_core; +use ffi; +use std::ffi::CString; +use std::marker::PhantomData; +use std::mem::ManuallyDrop; +use std::os::raw::{c_long, c_void}; +use std::slice::{from_raw_parts, from_raw_parts_mut}; +use std::{ops, panic, ptr}; +use {ContextRef, DeviceId, Error, Result, State, StreamParamsRef}; + +/// User supplied data callback. +/// +/// - Calling other cubeb functions from this callback is unsafe. +/// - The code in the callback should be non-blocking. +/// - Returning less than the number of frames this callback asks for or +/// provides puts the stream in drain mode. This callback will not be called +/// again, and the state callback will be called with CUBEB_STATE_DRAINED when +/// all the frames have been output. +/// +/// # Arguments +/// +/// - `input_buffer`: A slice containing the input data, zero-len if this is an output-only stream. +/// - `output_buffer`: A mutable slice to be filled with audio samples, zero-len if this is an input-only stream. +/// +/// # Return value +/// +/// If the stream has output, this is the number of frames written to the output buffer. In this +/// case, if this number is less than the length of the output buffer, then the stream will start to +/// drain. +/// +/// If the stream is input only, then returning the length of the input buffer indicates data has +/// been read. In this case, a value less than that will result in the stream being stopped. +pub type DataCallback<F> = dyn FnMut(&[F], &mut [F]) -> isize + Send + Sync + 'static; + +/// User supplied state callback. +/// +/// # Arguments +/// +/// `state`: The new state of the stream +pub type StateCallback = dyn FnMut(State) + Send + Sync + 'static; + +/// User supplied callback called when the underlying device changed. +pub type DeviceChangedCallback = dyn FnMut() + Send + Sync + 'static; + +pub struct StreamCallbacks<F> { + pub(crate) data: Box<DataCallback<F>>, + pub(crate) state: Box<StateCallback>, + pub(crate) device_changed: Option<Box<DeviceChangedCallback>>, +} + +/// Audio input/output stream +/// +/// # Example +/// ```no_run +/// extern crate cubeb; +/// use std::thread; +/// use std::time::Duration; +/// +/// type Frame = cubeb::MonoFrame<f32>; +/// +/// fn main() { +/// let ctx = cubeb::init("Cubeb tone example").unwrap(); +/// +/// let params = cubeb::StreamParamsBuilder::new() +/// .format(cubeb::SampleFormat::Float32LE) +/// .rate(44_100) +/// .channels(1) +/// .layout(cubeb::ChannelLayout::MONO) +/// .prefs(cubeb::StreamPrefs::NONE) +/// .take(); +/// +/// let phase_inc = 440.0 / 44_100.0; +/// let mut phase = 0.0; +/// let volume = 0.25; +/// +/// let mut builder = cubeb::StreamBuilder::<Frame>::new(); +/// builder +/// .name("Cubeb Square Wave") +/// .default_output(¶ms) +/// .latency(0x1000) +/// .data_callback(move |_, output| { +/// // Generate a square wave +/// for x in output.iter_mut() { +/// x.m = if phase < 0.5 { volume } else { -volume }; +/// phase = (phase + phase_inc) % 1.0; +/// } +/// +/// output.len() as isize +/// }) +/// .state_callback(|state| { +/// println!("stream {:?}", state); +/// }); +/// let stream = builder.init(&ctx).expect("Failed to create stream."); +/// +/// // Start playback +/// stream.start().unwrap(); +/// +/// // Play for 1/2 second +/// thread::sleep(Duration::from_millis(500)); +/// +/// // Shutdown +/// stream.stop().unwrap(); +/// } +/// ``` +pub struct Stream<F>(ManuallyDrop<cubeb_core::Stream>, PhantomData<*const F>); + +impl<F> Stream<F> { + fn new(s: cubeb_core::Stream) -> Stream<F> { + Stream(ManuallyDrop::new(s), PhantomData) + } +} + +impl<F> Drop for Stream<F> { + fn drop(&mut self) { + let user_ptr = self.user_ptr(); + unsafe { ManuallyDrop::drop(&mut self.0) }; + let _ = unsafe { Box::from_raw(user_ptr as *mut StreamCallbacks<F>) }; + } +} + +impl<F> ops::Deref for Stream<F> { + type Target = cubeb_core::Stream; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// Stream builder +/// +/// ```no_run +/// use cubeb::{Context, MonoFrame, Sample}; +/// use std::f32::consts::PI; +/// use std::thread; +/// use std::time::Duration; +/// +/// const SAMPLE_FREQUENCY: u32 = 48_000; +/// const STREAM_FORMAT: cubeb::SampleFormat = cubeb::SampleFormat::S16LE; +/// type Frame = MonoFrame<i16>; +/// +/// let ctx = Context::init(None, None).unwrap(); +/// +/// let params = cubeb::StreamParamsBuilder::new() +/// .format(STREAM_FORMAT) +/// .rate(SAMPLE_FREQUENCY) +/// .channels(1) +/// .layout(cubeb::ChannelLayout::MONO) +/// .take(); +/// +/// let mut position = 0u32; +/// +/// let mut builder = cubeb::StreamBuilder::<Frame>::new(); +/// builder +/// .name("Cubeb tone (mono)") +/// .default_output(¶ms) +/// .latency(0x1000) +/// .data_callback(move |_, output| { +/// // generate our test tone on the fly +/// for f in output.iter_mut() { +/// // North American dial tone +/// let t1 = (2.0 * PI * 350.0 * position as f32 / SAMPLE_FREQUENCY as f32).sin(); +/// let t2 = (2.0 * PI * 440.0 * position as f32 / SAMPLE_FREQUENCY as f32).sin(); +/// +/// f.m = i16::from_float(0.5 * (t1 + t2)); +/// +/// position += 1; +/// } +/// output.len() as isize +/// }) +/// .state_callback(|state| { +/// println!("stream {:?}", state); +/// }); +/// +/// let stream = builder.init(&ctx).expect("Failed to create cubeb stream"); +/// ``` +pub struct StreamBuilder<'a, F> { + name: Option<CString>, + input: Option<(DeviceId, &'a StreamParamsRef)>, + output: Option<(DeviceId, &'a StreamParamsRef)>, + latency: Option<u32>, + data_cb: Option<Box<DataCallback<F>>>, + state_cb: Option<Box<StateCallback>>, + device_changed_cb: Option<Box<DeviceChangedCallback>>, +} + +impl<'a, F> StreamBuilder<'a, F> { + pub fn new() -> StreamBuilder<'a, F> { + Default::default() + } + + /// User supplied data callback, see [`DataCallback`] + pub fn data_callback<D>(&mut self, cb: D) -> &mut Self + where + D: FnMut(&[F], &mut [F]) -> isize + Send + Sync + 'static, + { + self.data_cb = Some(Box::new(cb) as Box<DataCallback<F>>); + self + } + + /// User supplied state callback, see [`StateCallback`] + pub fn state_callback<S>(&mut self, cb: S) -> &mut Self + where + S: FnMut(State) + Send + Sync + 'static, + { + self.state_cb = Some(Box::new(cb) as Box<StateCallback>); + self + } + + /// A name for this stream. + pub fn name<T: Into<Vec<u8>>>(&mut self, name: T) -> &mut Self { + self.name = Some(CString::new(name).unwrap()); + self + } + + /// Use the default input device with `params` + /// + /// Optional if the stream is output only + pub fn default_input(&mut self, params: &'a StreamParamsRef) -> &mut Self { + self.input = Some((ptr::null(), params)); + self + } + + /// Use a specific input device with `params` + /// + /// Optional if the stream is output only + pub fn input(&mut self, device: DeviceId, params: &'a StreamParamsRef) -> &mut Self { + self.input = Some((device, params)); + self + } + + /// Use the default output device with `params` + /// + /// Optional if the stream is input only + pub fn default_output(&mut self, params: &'a StreamParamsRef) -> &mut Self { + self.output = Some((ptr::null(), params)); + self + } + + /// Use a specific output device with `params` + /// + /// Optional if the stream is input only + pub fn output(&mut self, device: DeviceId, params: &'a StreamParamsRef) -> &mut Self { + self.output = Some((device, params)); + self + } + + /// Stream latency in frames. + /// + /// Valid range is [1, 96000]. + pub fn latency(&mut self, latency: u32) -> &mut Self { + self.latency = Some(latency); + self + } + + /// User supplied callback called when the underlying device changed. + /// + /// See [`StateCallback`] + /// + /// Optional + pub fn device_changed_cb<CB>(&mut self, cb: CB) -> &mut Self + where + CB: FnMut() + Send + Sync + 'static, + { + self.device_changed_cb = Some(Box::new(cb) as Box<DeviceChangedCallback>); + self + } + + /// Build the stream + pub fn init(self, ctx: &ContextRef) -> Result<Stream<F>> { + if self.data_cb.is_none() || self.state_cb.is_none() { + return Err(Error::error()); + } + + let has_device_changed = self.device_changed_cb.is_some(); + let cbs = Box::into_raw(Box::new(StreamCallbacks { + data: self.data_cb.unwrap(), + state: self.state_cb.unwrap(), + device_changed: self.device_changed_cb, + })); + + let stream_name = self.name.as_deref(); + let (input_device, input_stream_params) = + self.input.map_or((ptr::null(), None), |x| (x.0, Some(x.1))); + let (output_device, output_stream_params) = self + .output + .map_or((ptr::null(), None), |x| (x.0, Some(x.1))); + let latency = self.latency.unwrap_or(1); + let data_callback: ffi::cubeb_data_callback = Some(data_cb_c::<F>); + let state_callback: ffi::cubeb_state_callback = Some(state_cb_c::<F>); + + let stream = unsafe { + ctx.stream_init( + stream_name, + input_device, + input_stream_params, + output_device, + output_stream_params, + latency, + data_callback, + state_callback, + cbs as *mut _, + )? + }; + if has_device_changed { + let device_changed_callback: ffi::cubeb_device_changed_callback = + Some(device_changed_cb_c::<F>); + stream.register_device_changed_callback(device_changed_callback)?; + } + Ok(Stream::new(stream)) + } +} + +impl<'a, F> Default for StreamBuilder<'a, F> { + fn default() -> Self { + StreamBuilder { + name: None, + input: None, + output: None, + latency: None, + data_cb: None, + state_cb: None, + device_changed_cb: None, + } + } +} + +// C callable callbacks +unsafe extern "C" fn data_cb_c<F>( + _: *mut ffi::cubeb_stream, + user_ptr: *mut c_void, + input_buffer: *const c_void, + output_buffer: *mut c_void, + nframes: c_long, +) -> c_long { + let ok = panic::catch_unwind(|| { + let cbs = &mut *(user_ptr as *mut StreamCallbacks<F>); + let input: &[F] = if input_buffer.is_null() { + &[] + } else { + from_raw_parts(input_buffer as *const _, nframes as usize) + }; + let output: &mut [F] = if output_buffer.is_null() { + &mut [] + } else { + from_raw_parts_mut(output_buffer as *mut _, nframes as usize) + }; + (cbs.data)(input, output) as c_long + }); + ok.unwrap_or(0) +} + +unsafe extern "C" fn state_cb_c<F>( + _: *mut ffi::cubeb_stream, + user_ptr: *mut c_void, + state: ffi::cubeb_state, +) { + let ok = panic::catch_unwind(|| { + let state = State::from(state); + let cbs = &mut *(user_ptr as *mut StreamCallbacks<F>); + (cbs.state)(state); + }); + ok.expect("State callback panicked"); +} + +unsafe extern "C" fn device_changed_cb_c<F>(user_ptr: *mut c_void) { + let ok = panic::catch_unwind(|| { + let cbs = &mut *(user_ptr as *mut StreamCallbacks<F>); + if let Some(ref mut device_changed) = cbs.device_changed { + device_changed(); + } + }); + ok.expect("Device changed callback panicked"); +} |