diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/cubeb | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/cubeb')
-rw-r--r-- | third_party/rust/cubeb/.cargo-checksum.json | 1 | ||||
-rw-r--r-- | third_party/rust/cubeb/Cargo.lock | 55 | ||||
-rw-r--r-- | third_party/rust/cubeb/Cargo.toml | 30 | ||||
-rw-r--r-- | third_party/rust/cubeb/LICENSE | 13 | ||||
-rw-r--r-- | third_party/rust/cubeb/README.md | 26 | ||||
-rw-r--r-- | third_party/rust/cubeb/examples/common/mod.rs | 31 | ||||
-rw-r--r-- | third_party/rust/cubeb/examples/devices.rs | 130 | ||||
-rw-r--r-- | third_party/rust/cubeb/examples/tone.rs | 60 | ||||
-rw-r--r-- | third_party/rust/cubeb/src/context.rs | 8 | ||||
-rw-r--r-- | third_party/rust/cubeb/src/frame.rs | 40 | ||||
-rw-r--r-- | third_party/rust/cubeb/src/lib.rs | 37 | ||||
-rw-r--r-- | third_party/rust/cubeb/src/log.rs | 59 | ||||
-rw-r--r-- | third_party/rust/cubeb/src/sample.rs | 24 | ||||
-rw-r--r-- | third_party/rust/cubeb/src/stream.rs | 271 |
14 files changed, 785 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..3084616660 --- /dev/null +++ b/third_party/rust/cubeb/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.lock":"5c6ff9f4925b4505337025ef175c6abeafd31a6d7f1035513bc1dad95bc980b4","Cargo.toml":"f98dcb39dea45c8a9b12a742dd8cd5e8385ee7da4c61cbd1411b8b840fba8dde","LICENSE":"8c044baa5d883274736eeece0b955249076c2697b826e576fce59496235b2cf5","README.md":"408c573ec240927cf5b9c036098e94e374ec41f71991415422586f450586b214","examples/common/mod.rs":"a5e1b79fc2b4addff1e442879ba3dbcb1cf5973e76b9a62d97dd0042597480db","examples/devices.rs":"ff5dcd588e7036165c4b4c20ec355d036e0ae90cf88b3b0f5cd86621fe2ce61d","examples/tone.rs":"8f5f9851b6d99f6f16c597fcb9312e3ef81769cbfb89341d2ea2522ca2e2214e","src/context.rs":"72507f5338a2f520fef9e2eface0638afba4c0d9e87fde92a0aaade643ba1335","src/frame.rs":"ed1e8f4576022d0c23106bb115125e5a2967b0375a10d0c54bbe99f04a70cc3f","src/lib.rs":"98e9280890551ac9305f2f808e315b6aa6bcd5781b8e96a078787ded0ef91e2a","src/log.rs":"704faeb31934dad6bc6d02e01caa85118754209bd559d30d03fcfa5cb8c1603c","src/sample.rs":"e23be3b691052001916f920ce9c1a0051bd097e39c9d34cbcb80ab8120265f45","src/stream.rs":"b3babf86252cd19cfbc98ffbc8f48bb033284f89db9cbdc46836611893356eff"},"package":"289952682b57343f3d852161d60f2a34a07c5fc39c113f155ab8aa3f471c917b"}
\ 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..e36ebaa457 --- /dev/null +++ b/third_party/rust/cubeb/Cargo.lock @@ -0,0 +1,55 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "cc" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c" + +[[package]] +name = "cmake" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e56268c17a6248366d66d4a47a3381369d068cce8409bb1716ed77ea32163bb" +dependencies = [ + "cc", +] + +[[package]] +name = "cubeb" +version = "0.8.0" +dependencies = [ + "cubeb-core", +] + +[[package]] +name = "cubeb-core" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6197f805b94171473c3fa3059f688f00a2df1ee76259f9ea641401b58917df3" +dependencies = [ + "bitflags", + "cubeb-sys", +] + +[[package]] +name = "cubeb-sys" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0f751f2aee031f589bcfd812b99cfbf923aa1612d34c9757608cf540f74ad9" +dependencies = [ + "cmake", + "pkg-config", +] + +[[package]] +name = "pkg-config" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33" diff --git a/third_party/rust/cubeb/Cargo.toml b/third_party/rust/cubeb/Cargo.toml new file mode 100644 index 0000000000..4dfce47ced --- /dev/null +++ b/third_party/rust/cubeb/Cargo.toml @@ -0,0 +1,30 @@ +# 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 believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +name = "cubeb" +version = "0.8.0" +authors = ["Dan Glastonbury <dglastonbury@mozilla.com>"] +description = "Bindings to libcubeb for interacting with system audio from rust.\n" +homepage = "https://github.com/djg/cubeb-rs" +readme = "README.md" +keywords = ["cubeb"] +categories = ["api-bindings"] +license = "ISC" +repository = "https://github.com/djg/cubeb-rs" +[dependencies.cubeb-core] +version = "0.8.0" + +[features] +gecko-in-tree = ["cubeb-core/gecko-in-tree"] +[badges.circle-ci] +repository = "djg/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..46034111b0 --- /dev/null +++ b/third_party/rust/cubeb/examples/common/mod.rs @@ -0,0 +1,31 @@ +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(¶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"); + + 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..c45876a71f --- /dev/null +++ b/third_party/rust/cubeb/src/context.rs @@ -0,0 +1,8 @@ +use {Context, Result}; +use std::ffi::CString; + +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..70b7d6a231 --- /dev/null +++ b/third_party/rust/cubeb/src/frame.rs @@ -0,0 +1,40 @@ +//! Frame utilities + +use std::{mem, slice}; + +/// 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> {} + +pub unsafe fn frame_from_bytes<F: Frame>(bytes: &[u8]) -> &[F] { + debug_assert_eq!(bytes.len() % mem::size_of::<F>(), 0); + slice::from_raw_parts( + bytes.as_ptr() as *const F, + bytes.len() / mem::size_of::<F>(), + ) +} + +pub unsafe fn frame_from_bytes_mut<F: Frame>(bytes: &mut [u8]) -> &mut [F] { + debug_assert!(bytes.len() % mem::size_of::<F>() == 0); + slice::from_raw_parts_mut(bytes.as_ptr() as *mut F, bytes.len() / mem::size_of::<F>()) +} diff --git a/third_party/rust/cubeb/src/lib.rs b/third_party/rust/cubeb/src/lib.rs new file mode 100644 index 0000000000..f828d1945b --- /dev/null +++ b/third_party/rust/cubeb/src/lib.rs @@ -0,0 +1,37 @@ +//! # 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. + +// 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; +#[macro_use] +mod log; +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/log.rs b/third_party/rust/cubeb/src/log.rs new file mode 100644 index 0000000000..eb0170d947 --- /dev/null +++ b/third_party/rust/cubeb/src/log.rs @@ -0,0 +1,59 @@ +// Copyright © 2017-2018 Mozilla Foundation +// +// This program is made available under an ISC-style license. See the +// accompanying file LICENSE for details. + +#[macro_export] +macro_rules! cubeb_log_internal { + ($level: expr, $msg: expr) => { + #[allow(unused_unsafe)] + unsafe { + if $level <= $crate::ffi::g_cubeb_log_level.into() { + cubeb_log_internal!(__INTERNAL__ $msg); + } + } + }; + ($level: expr, $fmt: expr, $($arg: expr),+) => { + #[allow(unused_unsafe)] + unsafe { + if $level <= $crate::ffi::g_cubeb_log_level.into() { + cubeb_log_internal!(__INTERNAL__ format!($fmt, $($arg),*)); + } + } + }; + (__INTERNAL__ $msg: expr) => { + if let Some(log_callback) = $crate::ffi::g_cubeb_log_callback { + let cstr = ::std::ffi::CString::new(format!("{}:{}: {}\n", file!(), line!(), $msg)).unwrap(); + log_callback(cstr.as_ptr()); + } + } +} + +#[macro_export] +macro_rules! cubeb_logv { + ($msg: expr) => (cubeb_log_internal!($crate::LogLevel::Verbose, $msg)); + ($fmt: expr, $($arg: expr),+) => (cubeb_log_internal!($crate::LogLevel::Verbose, $fmt, $($arg),*)); +} + +#[macro_export] +macro_rules! cubeb_log { + ($msg: expr) => (cubeb_log_internal!($crate::LogLevel::Normal, $msg)); + ($fmt: expr, $($arg: expr),+) => (cubeb_log_internal!($crate::LogLevel::Normal, $fmt, $($arg),*)); +} + +#[cfg(test)] +mod tests { + #[test] + fn test_normal_logging() { + cubeb_log!("This is log at normal level"); + cubeb_log!("{} Formatted log", 1); + cubeb_log!("{} Formatted {} log {}", 1, 2, 3); + } + + #[test] + fn test_verbose_logging() { + cubeb_logv!("This is a log at verbose level"); + cubeb_logv!("{} Formatted log", 1); + cubeb_logv!("{} Formatted {} log {}", 1, 2, 3); + } +} diff --git a/third_party/rust/cubeb/src/sample.rs b/third_party/rust/cubeb/src/sample.rs new file mode 100644 index 0000000000..041de8448c --- /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..38ea704f5a --- /dev/null +++ b/third_party/rust/cubeb/src/stream.rs @@ -0,0 +1,271 @@ +// Copyright © 2017-2018 Mozilla Foundation +// +// This program is made available under an ISC-style license. See the +// accompanying file LICENSE for details. + +//! Stream Functions +//! +//! # 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(); +//! } +//! ``` + +use {ContextRef, DeviceId, Error, Result, State, StreamParamsRef}; +use cubeb_core; +use ffi; +use std::{ops, panic, ptr}; +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}; + +pub type DataCallback<F> = dyn FnMut(&[F], &mut [F]) -> isize + Send + Sync + 'static; +pub type StateCallback = dyn FnMut(State) + Send + Sync + 'static; +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>>, +} + +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 + } +} + +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> { + StreamBuilder { + name: None, + input: None, + output: None, + latency: None, + data_cb: None, + state_cb: None, + device_changed_cb: None, + } + } + + 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 + } + 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 + } + + pub fn name<T: Into<Vec<u8>>>(&mut self, name: T) -> &mut Self { + self.name = Some(CString::new(name).unwrap()); + self + } + + pub fn default_input(&mut self, params: &'a StreamParamsRef) -> &mut Self { + self.input = Some((ptr::null(), params)); + self + } + + pub fn input(&mut self, device: DeviceId, params: &'a StreamParamsRef) -> &mut Self { + self.input = Some((device, params)); + self + } + + pub fn default_output(&mut self, params: &'a StreamParamsRef) -> &mut Self { + self.output = Some((ptr::null(), params)); + self + } + + pub fn output(&mut self, device: DeviceId, params: &'a StreamParamsRef) -> &mut Self { + self.output = Some((device, params)); + self + } + + pub fn latency(&mut self, latency: u32) -> &mut Self { + self.latency = Some(latency); + self + } + + 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 + } + + 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_ref().and_then(|n| Some(n.as_c_str())); + 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)) + } +} + +// 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"); +} |