summaryrefslogtreecommitdiffstats
path: root/third_party/rust/cubeb/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/cubeb/src
parentInitial commit. (diff)
downloadfirefox-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.rs9
-rw-r--r--third_party/rust/cubeb/src/frame.rs25
-rw-r--r--third_party/rust/cubeb/src/lib.rs38
-rw-r--r--third_party/rust/cubeb/src/sample.rs24
-rw-r--r--third_party/rust/cubeb/src/stream.rs378
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(&params)
+/// .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(&params)
+/// .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");
+}