summaryrefslogtreecommitdiffstats
path: root/third_party/rust/cubeb
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/cubeb')
-rw-r--r--third_party/rust/cubeb/.cargo-checksum.json1
-rw-r--r--third_party/rust/cubeb/Cargo.lock57
-rw-r--r--third_party/rust/cubeb/Cargo.toml33
-rw-r--r--third_party/rust/cubeb/LICENSE13
-rw-r--r--third_party/rust/cubeb/README.md26
-rw-r--r--third_party/rust/cubeb/examples/common/mod.rs32
-rw-r--r--third_party/rust/cubeb/examples/devices.rs130
-rw-r--r--third_party/rust/cubeb/examples/tone.rs60
-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
13 files changed, 826 insertions, 0 deletions
diff --git a/third_party/rust/cubeb/.cargo-checksum.json b/third_party/rust/cubeb/.cargo-checksum.json
new file mode 100644
index 0000000000..f68cd93289
--- /dev/null
+++ b/third_party/rust/cubeb/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.lock":"d3b573eee37b0b0f709f168d4026071803c499f8c0d3efb830367d762c40754f","Cargo.toml":"8b0bd9b5b8f74492a893f3281dd14ca6ebd0f47ce3368c9927f16b76498fc340","LICENSE":"8c044baa5d883274736eeece0b955249076c2697b826e576fce59496235b2cf5","README.md":"408c573ec240927cf5b9c036098e94e374ec41f71991415422586f450586b214","examples/common/mod.rs":"3363405f3d21ad120617473a0288c337a7b15eb79f5bb1035cfd258bd9bedf2b","examples/devices.rs":"ff5dcd588e7036165c4b4c20ec355d036e0ae90cf88b3b0f5cd86621fe2ce61d","examples/tone.rs":"8f5f9851b6d99f6f16c597fcb9312e3ef81769cbfb89341d2ea2522ca2e2214e","src/context.rs":"176f0923c13245acdc3934f649608fea1c17ad254daa3396d8929f38374e932e","src/frame.rs":"649cedaa2e9e1c5d2c3146bd0d66fd28bb230e68142493a9ea7dbab4a15f5adc","src/lib.rs":"58c78cad9541ebaef0ea96c67bc1db7d25e68e4e3d2f9a90f371c473e25c181f","src/sample.rs":"0d4645cc1d6ac26f198f6f59a44e193422e5646ecef0de46224653bef79d1a10","src/stream.rs":"0f51eab71d08685afdd0e748b85513541b9a612da1cad329c8c897dc0f79607c"},"package":"6db57570f2617f0214c11721e8d2325816d9dc936c2c472661ac5d90a30fba98"} \ No newline at end of file
diff --git a/third_party/rust/cubeb/Cargo.lock b/third_party/rust/cubeb/Cargo.lock
new file mode 100644
index 0000000000..726259fde9
--- /dev/null
+++ b/third_party/rust/cubeb/Cargo.lock
@@ -0,0 +1,57 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "cc"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+
+[[package]]
+name = "cmake"
+version = "0.1.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "cubeb"
+version = "0.12.0"
+dependencies = [
+ "cubeb-core",
+]
+
+[[package]]
+name = "cubeb-core"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2380c03a7df0ea3744f6a210d6340f423935e53cbf2fd68ada84b5e808e46ac7"
+dependencies = [
+ "bitflags",
+ "cubeb-sys",
+]
+
+[[package]]
+name = "cubeb-sys"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c20c457d7b34dad6e0c1a9c759c96b4420b9e9917a572998b81835799a07e1d"
+dependencies = [
+ "cmake",
+ "pkg-config",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
diff --git a/third_party/rust/cubeb/Cargo.toml b/third_party/rust/cubeb/Cargo.toml
new file mode 100644
index 0000000000..d79451598c
--- /dev/null
+++ b/third_party/rust/cubeb/Cargo.toml
@@ -0,0 +1,33 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+name = "cubeb"
+version = "0.12.0"
+authors = ["Dan Glastonbury <dglastonbury@mozilla.com>"]
+description = """
+Bindings to libcubeb for interacting with system audio from rust.
+"""
+homepage = "https://github.com/mozilla/cubeb-rs"
+readme = "README.md"
+keywords = ["cubeb"]
+categories = ["api-bindings"]
+license = "ISC"
+repository = "https://github.com/mozilla/cubeb-rs"
+
+[dependencies.cubeb-core]
+version = "0.12.0"
+
+[features]
+gecko-in-tree = ["cubeb-core/gecko-in-tree"]
+
+[badges.circle-ci]
+repository = "mozilla/cubeb-rs"
diff --git a/third_party/rust/cubeb/LICENSE b/third_party/rust/cubeb/LICENSE
new file mode 100644
index 0000000000..ec9718cf02
--- /dev/null
+++ b/third_party/rust/cubeb/LICENSE
@@ -0,0 +1,13 @@
+Copyright © 2017 Mozilla Foundation
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/third_party/rust/cubeb/README.md b/third_party/rust/cubeb/README.md
new file mode 100644
index 0000000000..53f559c190
--- /dev/null
+++ b/third_party/rust/cubeb/README.md
@@ -0,0 +1,26 @@
+# cubeb-rs
+
+[![Build Status](https://travis-ci.org/djg/cubeb-rs.svg?branch=master)](https://travis-ci.org/djg/cubeb-rs)
+
+[Documentation](https://docs.rs/cubeb)
+
+cubeb bindings for Rust
+
+```toml
+[dependencies]
+cubeb = "0.1"
+```
+
+## Building cubeb-rs
+
+First, you'll need to install _CMake_. Afterwards, just run:
+
+```sh
+$ git clone https://github.com/djg/cubeb-rs
+$ cd cubeb-rs
+$ cargo build
+```
+
+# License
+
+`cubeb-rs` is distributed under an ISC-style license. See LICENSE for details.
diff --git a/third_party/rust/cubeb/examples/common/mod.rs b/third_party/rust/cubeb/examples/common/mod.rs
new file mode 100644
index 0000000000..a577a8fc06
--- /dev/null
+++ b/third_party/rust/cubeb/examples/common/mod.rs
@@ -0,0 +1,32 @@
+use cubeb::{Context, Result};
+use std::env;
+use std::ffi::CString;
+use std::io::{self, Write};
+
+pub fn init<T: Into<Vec<u8>>>(ctx_name: T) -> Result<Context> {
+ let backend = match env::var("CUBEB_BACKEND") {
+ Ok(s) => Some(s),
+ Err(_) => None,
+ };
+
+ let ctx_name = CString::new(ctx_name).unwrap();
+ let ctx = Context::init(Some(ctx_name.as_c_str()), None);
+ if let Ok(ref ctx) = ctx {
+ if let Some(ref backend) = backend {
+ let ctx_backend = ctx.backend_id();
+ if backend != ctx_backend {
+ let stderr = io::stderr();
+ let mut handle = stderr.lock();
+
+ writeln!(
+ handle,
+ "Requested backend `{}', got `{}'",
+ backend, ctx_backend
+ )
+ .unwrap();
+ }
+ }
+ }
+
+ ctx
+}
diff --git a/third_party/rust/cubeb/examples/devices.rs b/third_party/rust/cubeb/examples/devices.rs
new file mode 100644
index 0000000000..0f85762944
--- /dev/null
+++ b/third_party/rust/cubeb/examples/devices.rs
@@ -0,0 +1,130 @@
+// Copyright © 2011 Mozilla Foundation
+// Copyright © 2015 Haakon Sporsheim <haakon.sporsheim@telenordigital.com>
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+//! libcubeb enumerate device test/example.
+//! Prints out a list of input/output devices connected to the system.
+extern crate cubeb;
+
+mod common;
+
+use cubeb::{DeviceFormat, DeviceType};
+
+fn print_device_info(info: &cubeb::DeviceInfo) {
+ let devtype = if info.device_type().contains(DeviceType::INPUT) {
+ "input"
+ } else if info.device_type().contains(DeviceType::OUTPUT) {
+ "output"
+ } else {
+ "unknown?"
+ };
+
+ let devstate = match info.state() {
+ cubeb::DeviceState::Disabled => "disabled",
+ cubeb::DeviceState::Unplugged => "unplugged",
+ cubeb::DeviceState::Enabled => "enabled",
+ };
+
+ let devdeffmt = match info.default_format() {
+ DeviceFormat::S16LE => "S16LE",
+ DeviceFormat::S16BE => "S16BE",
+ DeviceFormat::F32LE => "F32LE",
+ DeviceFormat::F32BE => "F32BE",
+ _ => "unknown?",
+ };
+
+ let mut devfmts = "".to_string();
+ if info.format().contains(DeviceFormat::S16LE) {
+ devfmts = format!("{} S16LE", devfmts);
+ }
+ if info.format().contains(DeviceFormat::S16BE) {
+ devfmts = format!("{} S16BE", devfmts);
+ }
+ if info.format().contains(DeviceFormat::F32LE) {
+ devfmts = format!("{} F32LE", devfmts);
+ }
+ if info.format().contains(DeviceFormat::F32BE) {
+ devfmts = format!("{} F32BE", devfmts);
+ }
+
+ if let Some(device_id) = info.device_id() {
+ let preferred = if info.preferred().is_empty() {
+ ""
+ } else {
+ " (PREFERRED)"
+ };
+ println!("dev: \"{}\"{}", device_id, preferred);
+ }
+ if let Some(friendly_name) = info.friendly_name() {
+ println!("\tName: \"{}\"", friendly_name);
+ }
+ if let Some(group_id) = info.group_id() {
+ println!("\tGroup: \"{}\"", group_id);
+ }
+ if let Some(vendor_name) = info.vendor_name() {
+ println!("\tVendor: \"{}\"", vendor_name);
+ }
+ println!("\tType: {}", devtype);
+ println!("\tState: {}", devstate);
+ println!("\tCh: {}", info.max_channels());
+ println!(
+ "\tFormat: {} (0x{:x}) (default: {})",
+ &devfmts[1..],
+ info.format(),
+ devdeffmt
+ );
+ println!(
+ "\tRate: {} - {} (default: {})",
+ info.min_rate(),
+ info.max_rate(),
+ info.default_rate()
+ );
+ println!(
+ "\tLatency: lo {} frames, hi {} frames",
+ info.latency_lo(),
+ info.latency_hi()
+ );
+}
+
+fn main() {
+ let ctx = common::init("Cubeb audio test").expect("Failed to create cubeb context");
+
+ println!("Enumerating input devices for backend {}", ctx.backend_id());
+
+ let devices = match ctx.enumerate_devices(DeviceType::INPUT) {
+ Ok(devices) => devices,
+ Err(e) if e.code() == cubeb::ErrorCode::NotSupported => {
+ println!("Device enumeration not support for this backend.");
+ return;
+ }
+ Err(e) => {
+ println!("Error enumerating devices: {}", e);
+ return;
+ }
+ };
+
+ println!("Found {} input devices", devices.len());
+ for d in devices.iter() {
+ print_device_info(d);
+ }
+
+ println!(
+ "Enumerating output devices for backend {}",
+ ctx.backend_id()
+ );
+
+ let devices = match ctx.enumerate_devices(DeviceType::OUTPUT) {
+ Ok(devices) => devices,
+ Err(e) => {
+ println!("Error enumerating devices: {}", e);
+ return;
+ }
+ };
+
+ println!("Found {} output devices", devices.len());
+ for d in devices.iter() {
+ print_device_info(d);
+ }
+}
diff --git a/third_party/rust/cubeb/examples/tone.rs b/third_party/rust/cubeb/examples/tone.rs
new file mode 100644
index 0000000000..97e940bc94
--- /dev/null
+++ b/third_party/rust/cubeb/examples/tone.rs
@@ -0,0 +1,60 @@
+// Copyright © 2011 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+//! libcubeb api/function test. Plays a simple tone.
+extern crate cubeb;
+
+mod common;
+
+use cubeb::{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>;
+
+fn main() {
+ let ctx = common::init("Cubeb tone example").expect("Failed to create cubeb context");
+
+ 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");
+
+ stream.start().unwrap();
+ thread::sleep(Duration::from_millis(500));
+ stream.stop().unwrap();
+}
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");
+}