├── cubeb-api ├── LICENSE ├── src │ ├── context.rs │ ├── frame.rs │ ├── sample.rs │ ├── lib.rs │ └── stream.rs ├── README.md ├── Cargo.toml └── examples │ ├── common │ └── mod.rs │ ├── tone.rs │ └── devices.rs ├── cubeb-core ├── LICENSE ├── src │ ├── util.rs │ ├── call.rs │ ├── log.c │ ├── lib.rs │ ├── format.rs │ ├── error.rs │ ├── log.rs │ ├── device_collection.rs │ ├── context.rs │ ├── channel.rs │ ├── device.rs │ ├── ffi_types.rs │ ├── builders.rs │ └── stream.rs ├── Cargo.toml └── build.rs ├── cubeb-sys ├── LICENSE ├── src │ ├── error.rs │ ├── lib.rs │ ├── callbacks.rs │ ├── format.rs │ ├── internal.rs │ ├── log.rs │ ├── mixer.rs │ ├── macros.rs │ ├── audio_dump.rs │ ├── resampler.rs │ ├── context.rs │ ├── channel.rs │ ├── stream.rs │ └── device.rs ├── Cargo.toml └── build.rs ├── cubeb-backend ├── LICENSE ├── src │ ├── lib.rs │ ├── traits.rs │ ├── ops.rs │ ├── log.rs │ └── capi.rs ├── Cargo.toml └── tests │ └── test_capi.rs ├── .gitignore ├── .cargo └── config.toml ├── .gitmodules ├── xtask ├── Cargo.toml └── src │ └── main.rs ├── systest ├── src │ └── main.rs ├── Cargo.toml └── build.rs ├── Cargo.toml ├── LICENSE ├── README.md └── .github └── workflows └── build.yml /cubeb-api/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /cubeb-core/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /cubeb-sys/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /cubeb-backend/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --package xtask --" 3 | 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "cubeb-sys/libcubeb"] 2 | path = cubeb-sys/libcubeb 3 | url = https://github.com/mozilla/cubeb 4 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.16.0" 4 | edition = "2021" 5 | publish = false 6 | release = false 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /systest/src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(bad_style, unused_macros)] 2 | 3 | extern crate cubeb_sys; 4 | 5 | use cubeb_sys::*; 6 | use std::os::raw::*; 7 | 8 | include!(concat!(env!("OUT_DIR"), "/all.rs")); 9 | -------------------------------------------------------------------------------- /cubeb-api/src/context.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | use {Context, Result}; 3 | 4 | /// Initialize a new cubeb [`Context`] 5 | pub fn init>>(name: T) -> Result { 6 | let name = CString::new(name)?; 7 | 8 | Context::init(Some(name.as_c_str()), None) 9 | } 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "cubeb-core", 5 | "cubeb-api", 6 | "cubeb-backend", 7 | "cubeb-sys", 8 | "systest", 9 | "xtask", 10 | ] 11 | exclude = [ 12 | "cubeb-sys/libcubeb/src/cubeb-coreaudio-rs", 13 | "cubeb-sys/libcubeb/src/cubeb-pulse-rs", 14 | ] 15 | -------------------------------------------------------------------------------- /cubeb-backend/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | extern crate cubeb_core; 7 | 8 | pub mod capi; 9 | #[macro_use] 10 | pub mod log; 11 | mod ops; 12 | mod traits; 13 | 14 | // Re-export cubeb_core types 15 | pub use cubeb_core::*; 16 | pub use ops::Ops; 17 | pub use traits::{ContextOps, StreamOps}; 18 | -------------------------------------------------------------------------------- /cubeb-core/src/util.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use std::ffi::CStr; 7 | use std::os::raw::c_char; 8 | 9 | pub unsafe fn opt_bytes<'a>(c: *const c_char) -> Option<&'a [u8]> { 10 | if c.is_null() { 11 | None 12 | } else { 13 | Some(CStr::from_ptr(c).to_bytes()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /systest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "systest" 3 | version = "0.1.0" 4 | authors = ["Dan Glastonbury "] 5 | build = "build.rs" 6 | edition = "2018" 7 | publish = false 8 | 9 | [package.metadata.release] 10 | release = false 11 | 12 | [features] 13 | gecko-in-tree = ["cubeb-sys/gecko-in-tree"] 14 | 15 | [dependencies] 16 | cubeb-sys = { path = "../cubeb-sys" } 17 | libc = "0.2" 18 | 19 | [build-dependencies] 20 | ctest = "0.4" 21 | -------------------------------------------------------------------------------- /cubeb-core/src/call.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | #![macro_use] 6 | 7 | use std::os::raw::c_int; 8 | use Error; 9 | 10 | pub fn cvt_r(ret: c_int) -> Result<(), Error> { 11 | Error::wrap(ret) 12 | } 13 | 14 | macro_rules! call { 15 | (ffi::$p:ident ($($e:expr),*)) => ({ 16 | ::call::cvt_r(ffi::$p($($e),*)) 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /cubeb-core/src/log.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /** The maximum size of a log message, after having been formatted. */ 5 | #define CUBEB_LOG_MESSAGE_MAX_SIZE 256 6 | 7 | void RUST_WRITE_FORMATTED_MSG(char* msg); 8 | 9 | void cubeb_write_log(char const * fmt, ...) { 10 | va_list args; 11 | va_start(args, fmt); 12 | char msg[CUBEB_LOG_MESSAGE_MAX_SIZE]; 13 | vsnprintf(msg, CUBEB_LOG_MESSAGE_MAX_SIZE, fmt, args); 14 | va_end(args); 15 | RUST_WRITE_FORMATTED_MSG(msg); 16 | } 17 | 18 | -------------------------------------------------------------------------------- /cubeb-sys/src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use std::os::raw::c_int; 7 | 8 | pub const CUBEB_OK: c_int = 0; 9 | pub const CUBEB_ERROR: c_int = -1; 10 | pub const CUBEB_ERROR_INVALID_FORMAT: c_int = -2; 11 | pub const CUBEB_ERROR_INVALID_PARAMETER: c_int = -3; 12 | pub const CUBEB_ERROR_NOT_SUPPORTED: c_int = -4; 13 | pub const CUBEB_ERROR_DEVICE_UNAVAILABLE: c_int = -5; 14 | -------------------------------------------------------------------------------- /cubeb-api/README.md: -------------------------------------------------------------------------------- 1 | # cubeb-rs 2 | 3 | [![Build Status](https://travis-ci.org/djg/cubeb-rs.svg?branch=master)](https://travis-ci.org/djg/cubeb-rs) 4 | 5 | [Documentation](https://docs.rs/cubeb) 6 | 7 | cubeb bindings for Rust 8 | 9 | ```toml 10 | [dependencies] 11 | cubeb = "0.1" 12 | ``` 13 | 14 | ## Building cubeb-rs 15 | 16 | First, you'll need to install _CMake_. Afterwards, just run: 17 | 18 | ```sh 19 | $ git clone https://github.com/djg/cubeb-rs 20 | $ cd cubeb-rs 21 | $ cargo build 22 | ``` 23 | 24 | # License 25 | 26 | `cubeb-rs` is distributed under an ISC-style license. See LICENSE for details. 27 | -------------------------------------------------------------------------------- /cubeb-api/src/frame.rs: -------------------------------------------------------------------------------- 1 | //! Frame utilities 2 | 3 | /// A `Frame` is a collection of samples which have a a specific 4 | /// layout represented by `ChannelLayout` 5 | pub trait Frame {} 6 | 7 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 8 | /// A monaural frame. 9 | pub struct MonoFrame { 10 | /// Mono channel 11 | pub m: T, 12 | } 13 | 14 | impl Frame for MonoFrame {} 15 | 16 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 17 | /// A stereo frame. 18 | pub struct StereoFrame { 19 | /// Left channel 20 | pub l: T, 21 | /// Right channel 22 | pub r: T, 23 | } 24 | 25 | impl Frame for StereoFrame {} 26 | -------------------------------------------------------------------------------- /cubeb-api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cubeb" 3 | version = "0.33.0" 4 | authors = ["Dan Glastonbury "] 5 | license = "ISC" 6 | readme = "README.md" 7 | keywords = ["cubeb"] 8 | repository = "https://github.com/mozilla/cubeb-rs" 9 | homepage = "https://github.com/mozilla/cubeb-rs" 10 | description = """ 11 | Bindings to libcubeb for interacting with system audio from rust. 12 | """ 13 | categories = ["api-bindings"] 14 | 15 | [badges] 16 | circle-ci = { repository = "mozilla/cubeb-rs" } 17 | 18 | [features] 19 | gecko-in-tree = ["cubeb-core/gecko-in-tree"] 20 | 21 | [dependencies] 22 | cubeb-core = { path = "../cubeb-core", version = "0.33.0" } 23 | -------------------------------------------------------------------------------- /cubeb-backend/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cubeb-backend" 3 | version = "0.33.0" 4 | authors = ["Dan Glastonbury "] 5 | license = "ISC" 6 | keywords = ["cubeb"] 7 | repository = "https://github.com/mozilla/cubeb-rs" 8 | homepage = "https://github.com/mozilla/cubeb-rs" 9 | description = """ 10 | Bindings to libcubeb internals to facilitate implementing cubeb backends in rust. 11 | """ 12 | categories = ["api-bindings"] 13 | 14 | [badges] 15 | circle-ci = { repository = "mozilla/cubeb-rs" } 16 | 17 | [features] 18 | gecko-in-tree = ["cubeb-core/gecko-in-tree"] 19 | 20 | [dependencies] 21 | cubeb-core = { path = "../cubeb-core", version = "0.33.0" } 22 | 23 | [dev-dependencies] 24 | regex = "1.11" 25 | -------------------------------------------------------------------------------- /cubeb-api/src/sample.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | /// An extension trait which allows the implementation of converting 7 | /// void* buffers from libcubeb-sys into rust slices of the appropriate 8 | /// type. 9 | pub trait Sample: Send + Copy { 10 | /// Map f32 in range [-1,1] to sample type 11 | fn from_float(_: f32) -> Self; 12 | } 13 | 14 | impl Sample for i16 { 15 | fn from_float(x: f32) -> i16 { 16 | (x * f32::from(i16::MAX)) as i16 17 | } 18 | } 19 | 20 | impl Sample for f32 { 21 | fn from_float(x: f32) -> f32 { 22 | x 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /cubeb-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | #![allow(non_camel_case_types)] 7 | 8 | #[macro_use] 9 | mod macros; 10 | 11 | mod audio_dump; 12 | mod callbacks; 13 | mod channel; 14 | mod context; 15 | mod device; 16 | mod error; 17 | mod format; 18 | mod log; 19 | mod mixer; 20 | mod resampler; 21 | mod stream; 22 | 23 | pub use audio_dump::*; 24 | pub use callbacks::*; 25 | pub use channel::*; 26 | pub use context::*; 27 | pub use device::*; 28 | pub use error::*; 29 | pub use format::*; 30 | pub use log::*; 31 | pub use mixer::*; 32 | pub use resampler::*; 33 | pub use stream::*; 34 | -------------------------------------------------------------------------------- /cubeb-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cubeb-core" 3 | version = "0.33.0" 4 | authors = ["Dan Glastonbury "] 5 | license = "ISC" 6 | keywords = ["cubeb"] 7 | repository = "https://github.com/mozilla/cubeb-rs" 8 | homepage = "https://github.com/mozilla/cubeb-rs" 9 | description = """ 10 | Common types and definitions for cubeb rust and C bindings. Not intended for direct use. 11 | """ 12 | categories = ["api-bindings"] 13 | 14 | [badges] 15 | circle-ci = { repository = "mozilla/cubeb-rs" } 16 | 17 | [features] 18 | gecko-in-tree = ["cubeb-sys/gecko-in-tree"] 19 | 20 | [dependencies] 21 | bitflags = "1.2.0" 22 | cubeb-sys = { path = "../cubeb-sys", version = "0.33" } 23 | 24 | [build-dependencies] 25 | cc = "1.1.30" 26 | -------------------------------------------------------------------------------- /cubeb-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cubeb-sys" 3 | version = "0.33.0" 4 | authors = ["Dan Glastonbury "] 5 | repository = "https://github.com/mozilla/cubeb-rs" 6 | license = "ISC" 7 | description = "Native bindings to the cubeb library" 8 | exclude = ["libcubeb/googletest"] 9 | 10 | links = "cubeb" 11 | build = "build.rs" 12 | 13 | [badges] 14 | circle-ci = { repository = "mozilla/cubeb-rs" } 15 | 16 | [features] 17 | gecko-in-tree = [] 18 | unittest-build = [] 19 | 20 | [build-dependencies] 21 | pkg-config = "0.3" 22 | cmake = "0.1.2" 23 | 24 | # Workaround, see build.rs and 25 | # https://github.com/rust-lang/cargo/issues/4789#issuecomment-2308131243 26 | [dev-dependencies] 27 | cubeb-sys = { path = ".", features = ["unittest-build"] } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2017 Mozilla Foundation 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /cubeb-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | #[macro_use] 7 | extern crate bitflags; 8 | extern crate cubeb_sys; 9 | 10 | #[macro_use] 11 | mod ffi_types; 12 | 13 | mod call; 14 | 15 | mod builders; 16 | mod channel; 17 | mod context; 18 | mod device; 19 | mod device_collection; 20 | mod error; 21 | mod format; 22 | mod log; 23 | mod stream; 24 | mod util; 25 | 26 | pub use builders::*; 27 | pub use channel::*; 28 | pub use context::*; 29 | pub use device::*; 30 | pub use device_collection::*; 31 | pub use error::*; 32 | pub use format::*; 33 | pub use log::*; 34 | pub use stream::*; 35 | 36 | pub mod ffi { 37 | pub use cubeb_sys::*; 38 | } 39 | -------------------------------------------------------------------------------- /cubeb-sys/src/callbacks.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use context::cubeb; 7 | use std::os::raw::{c_long, c_void}; 8 | use stream::{cubeb_state, cubeb_stream}; 9 | 10 | pub type cubeb_data_callback = Option< 11 | unsafe extern "C" fn( 12 | *mut cubeb_stream, 13 | *mut c_void, 14 | *const c_void, 15 | *mut c_void, 16 | c_long, 17 | ) -> c_long, 18 | >; 19 | pub type cubeb_state_callback = 20 | Option; 21 | pub type cubeb_device_changed_callback = Option; 22 | pub type cubeb_device_collection_changed_callback = 23 | Option; 24 | -------------------------------------------------------------------------------- /cubeb-sys/src/format.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | cubeb_enum! { 7 | pub enum cubeb_sample_format { 8 | CUBEB_SAMPLE_S16LE, 9 | CUBEB_SAMPLE_S16BE, 10 | CUBEB_SAMPLE_FLOAT32LE, 11 | CUBEB_SAMPLE_FLOAT32BE, 12 | } 13 | } 14 | 15 | #[cfg(target_endian = "big")] 16 | pub const CUBEB_SAMPLE_S16NE: cubeb_sample_format = CUBEB_SAMPLE_S16BE; 17 | #[cfg(target_endian = "big")] 18 | pub const CUBEB_SAMPLE_FLOAT32NE: cubeb_sample_format = CUBEB_SAMPLE_FLOAT32BE; 19 | #[cfg(target_endian = "little")] 20 | pub const CUBEB_SAMPLE_S16NE: cubeb_sample_format = CUBEB_SAMPLE_S16LE; 21 | #[cfg(target_endian = "little")] 22 | pub const CUBEB_SAMPLE_FLOAT32NE: cubeb_sample_format = CUBEB_SAMPLE_FLOAT32LE; 23 | -------------------------------------------------------------------------------- /cubeb-sys/src/internal.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use std::{fmt, mem}; 7 | use std::os::raw::{c_char, c_uint}; 8 | 9 | #[repr(C)] 10 | pub struct cubeb_layout_map { 11 | name: *const c_char, 12 | channels: c_uint, 13 | layout: cubeb_channel_layout, 14 | } 15 | 16 | impl Default for cubeb_layout_map { 17 | fn default() -> Self { unsafe { mem::zeroed() } } 18 | } 19 | 20 | // Explicit Debug impl to work around bug in ctest 21 | impl fmt::Debug for cubeb_layout_map { 22 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 23 | f.debug_struct("cubeb_layout_map") 24 | .field("name", &self.name) 25 | .field("channels", &self.channels) 26 | .field("layout", &self.layout) 27 | .finish() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cubeb-sys/src/log.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use std::os::raw::{c_char, c_int, c_void}; 7 | 8 | cubeb_enum! { 9 | pub enum cubeb_log_level { 10 | CUBEB_LOG_DISABLED = 0, 11 | CUBEB_LOG_NORMAL = 1, 12 | CUBEB_LOG_VERBOSE = 2, 13 | } 14 | } 15 | 16 | pub type cubeb_log_callback = Option; 17 | 18 | extern "C" { 19 | pub fn cubeb_set_log_callback( 20 | log_level: cubeb_log_level, 21 | log_callback: cubeb_log_callback, 22 | ) -> c_int; 23 | 24 | pub fn cubeb_log_get_callback() -> cubeb_log_callback; 25 | pub fn cubeb_log_get_level() -> cubeb_log_level; 26 | 27 | pub fn cubeb_async_log_reset_threads(_: c_void); 28 | pub fn cubeb_async_log(msg: *const c_char, ...); 29 | } 30 | -------------------------------------------------------------------------------- /cubeb-api/examples/common/mod.rs: -------------------------------------------------------------------------------- 1 | use cubeb::{Context, Result}; 2 | use std::env; 3 | use std::ffi::CString; 4 | use std::io::{self, Write}; 5 | 6 | pub fn init>>(ctx_name: T) -> Result { 7 | let backend = match env::var("CUBEB_BACKEND") { 8 | Ok(s) => Some(s), 9 | Err(_) => None, 10 | }; 11 | 12 | let ctx_name = CString::new(ctx_name).unwrap(); 13 | let ctx = Context::init(Some(ctx_name.as_c_str()), None); 14 | if let Ok(ref ctx) = ctx { 15 | if let Some(ref backend) = backend { 16 | let ctx_backend = ctx.backend_id(); 17 | if backend != ctx_backend { 18 | let stderr = io::stderr(); 19 | let mut handle = stderr.lock(); 20 | 21 | writeln!( 22 | handle, 23 | "Requested backend `{}', got `{}'", 24 | backend, ctx_backend 25 | ) 26 | .unwrap(); 27 | } 28 | } 29 | } 30 | 31 | ctx 32 | } 33 | -------------------------------------------------------------------------------- /cubeb-sys/src/mixer.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use channel::cubeb_channel_layout; 7 | use format::cubeb_sample_format; 8 | use std::os::raw::{c_int, c_uint, c_void}; 9 | 10 | pub enum cubeb_mixer {} 11 | 12 | extern "C" { 13 | pub fn cubeb_mixer_create( 14 | format: cubeb_sample_format, 15 | in_channels: u32, 16 | in_layout: cubeb_channel_layout, 17 | out_channels: u32, 18 | out_layout: cubeb_channel_layout, 19 | ) -> *mut cubeb_mixer; 20 | pub fn cubeb_mixer_destroy(mixer: *mut cubeb_mixer); 21 | pub fn cubeb_mixer_mix( 22 | mixer: *mut cubeb_mixer, 23 | frames: usize, 24 | input_buffer: *const c_void, 25 | input_buffer_length: usize, 26 | output_buffer: *mut c_void, 27 | output_buffer_length: usize, 28 | ) -> c_int; 29 | 30 | pub fn cubeb_channel_layout_nb_channels(channel_layout: cubeb_channel_layout) -> c_uint; 31 | } 32 | -------------------------------------------------------------------------------- /cubeb-sys/src/macros.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | macro_rules! cubeb_enum { 7 | (pub enum $name:ident { $($variants:tt)* }) => { 8 | #[cfg(target_env = "msvc")] 9 | pub type $name = i32; 10 | #[cfg(not(target_env = "msvc"))] 11 | pub type $name = u32; 12 | cubeb_enum!(gen, $name, 0, $($variants)*); 13 | }; 14 | (pub enum $name:ident: $t:ty { $($variants:tt)* }) => { 15 | pub type $name = $t; 16 | cubeb_enum!(gen, $name, 0, $($variants)*); 17 | }; 18 | (gen, $name:ident, $val:expr, $variant:ident, $($rest:tt)*) => { 19 | pub const $variant: $name = $val; 20 | cubeb_enum!(gen, $name, $val+1, $($rest)*); 21 | }; 22 | (gen, $name:ident, $val:expr, $variant:ident = $e:expr, $($rest:tt)*) => { 23 | pub const $variant: $name = $e; 24 | cubeb_enum!(gen, $name, $e+1, $($rest)*); 25 | }; 26 | (gen, $name:ident, $val:expr, ) => {} 27 | } 28 | -------------------------------------------------------------------------------- /cubeb-core/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | use std::path::Path; 4 | 5 | fn main() { 6 | let versioned = format!( 7 | "rust_write_formatted_msg_{}_{}_{}", 8 | env!("CARGO_PKG_VERSION_MAJOR"), 9 | env!("CARGO_PKG_VERSION_MINOR"), 10 | env!("CARGO_PKG_VERSION_PATCH") 11 | ); 12 | 13 | let mut cfg = cc::Build::new(); 14 | cfg.file("src/log.c"); 15 | cfg.define("RUST_WRITE_FORMATTED_MSG", Some(versioned.as_str())); 16 | cfg.compile("cubeb_log_wrap"); 17 | 18 | let out_dir = env::var_os("OUT_DIR").unwrap(); 19 | let dest_path = Path::new(&out_dir).join("log_wrap.rs"); 20 | fs::write( 21 | &dest_path, 22 | format!( 23 | r#" 24 | #[allow(clippy::missing_safety_doc)] 25 | #[no_mangle] 26 | pub unsafe extern "C" fn {}(s: *const c_char) {{ 27 | rust_write_formatted_msg(s); 28 | }} 29 | "#, 30 | versioned 31 | ), 32 | ) 33 | .unwrap(); 34 | 35 | println!("cargo::rerun-if-changed=src/log.c"); 36 | println!("cargo::rerun-if-changed=build.rs"); 37 | } 38 | -------------------------------------------------------------------------------- /cubeb-sys/src/audio_dump.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2023 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use std::os::raw::{c_char, c_int, c_void}; 7 | use stream::cubeb_stream_params; 8 | 9 | pub enum cubeb_audio_dump_stream {} 10 | pub enum cubeb_audio_dump_session {} 11 | pub type cubeb_audio_dump_stream_t = *mut cubeb_audio_dump_stream; 12 | pub type cubeb_audio_dump_session_t = *mut cubeb_audio_dump_session; 13 | 14 | extern "C" { 15 | pub fn cubeb_audio_dump_init(session: *mut cubeb_audio_dump_session_t) -> c_int; 16 | pub fn cubeb_audio_dump_shutdown(session: cubeb_audio_dump_session_t) -> c_int; 17 | pub fn cubeb_audio_dump_stream_init( 18 | session: cubeb_audio_dump_session_t, 19 | stream: *mut cubeb_audio_dump_stream_t, 20 | stream_params: cubeb_stream_params, 21 | name: *const c_char, 22 | ) -> c_int; 23 | pub fn cubeb_audio_dump_stream_shutdown( 24 | session: cubeb_audio_dump_session_t, 25 | stream: cubeb_audio_dump_stream_t, 26 | ) -> c_int; 27 | pub fn cubeb_audio_dump_start(session: cubeb_audio_dump_session_t) -> c_int; 28 | pub fn cubeb_audio_dump_stop(session: cubeb_audio_dump_session_t) -> c_int; 29 | pub fn cubeb_audio_dump_write( 30 | stream: cubeb_audio_dump_stream_t, 31 | audio_samples: *mut c_void, 32 | count: u32, 33 | ) -> c_int; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /cubeb-core/src/format.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use ffi; 7 | 8 | #[derive(PartialEq, Eq, Clone, Debug, Copy)] 9 | pub enum SampleFormat { 10 | S16LE, 11 | S16BE, 12 | Float32LE, 13 | Float32BE, 14 | // Maps to the platform native endian 15 | S16NE, 16 | Float32NE, 17 | } 18 | 19 | impl From for SampleFormat { 20 | fn from(x: ffi::cubeb_sample_format) -> SampleFormat { 21 | match x { 22 | ffi::CUBEB_SAMPLE_S16LE => SampleFormat::S16LE, 23 | ffi::CUBEB_SAMPLE_S16BE => SampleFormat::S16BE, 24 | ffi::CUBEB_SAMPLE_FLOAT32LE => SampleFormat::Float32LE, 25 | ffi::CUBEB_SAMPLE_FLOAT32BE => SampleFormat::Float32BE, 26 | // TODO: Implement TryFrom 27 | _ => SampleFormat::S16NE, 28 | } 29 | } 30 | } 31 | 32 | impl From for ffi::cubeb_sample_format { 33 | fn from(x: SampleFormat) -> Self { 34 | use SampleFormat::*; 35 | match x { 36 | S16LE => ffi::CUBEB_SAMPLE_S16LE, 37 | S16BE => ffi::CUBEB_SAMPLE_S16BE, 38 | Float32LE => ffi::CUBEB_SAMPLE_FLOAT32LE, 39 | Float32BE => ffi::CUBEB_SAMPLE_FLOAT32BE, 40 | S16NE => ffi::CUBEB_SAMPLE_S16NE, 41 | Float32NE => ffi::CUBEB_SAMPLE_FLOAT32NE, 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /cubeb-api/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # libcubeb bindings for rust 2 | //! 3 | //! This library contains bindings to the [cubeb][1] C library which 4 | //! is used to interact with system audio. The library itself is a 5 | //! work in progress and is likely lacking documentation and test. 6 | //! 7 | //! [1]: https://github.com/mozilla/cubeb/ 8 | //! 9 | //! The cubeb-rs library exposes the user API of libcubeb. It doesn't 10 | //! expose the internal interfaces, so isn't suitable for extending 11 | //! libcubeb. See [cubeb-pulse-rs][2] for an example of extending 12 | //! libcubeb via implementing a cubeb backend in rust. 13 | //! 14 | //! [2]: https://github.com/mozilla/cubeb-pulse-rs/ 15 | //! 16 | //! To get started, have a look at the [`StreamBuilder`] 17 | 18 | // Copyright © 2017-2018 Mozilla Foundation 19 | // 20 | // This program is made available under an ISC-style license. See the 21 | // accompanying file LICENSE for details. 22 | 23 | extern crate cubeb_core; 24 | 25 | mod context; 26 | mod frame; 27 | mod sample; 28 | mod stream; 29 | 30 | pub use context::*; 31 | // Re-export cubeb_core types 32 | pub use cubeb_core::{ 33 | ffi, ChannelLayout, Context, ContextRef, Device, DeviceCollection, DeviceCollectionRef, 34 | DeviceFormat, DeviceId, DeviceInfo, DeviceInfoRef, DeviceRef, DeviceState, DeviceType, Error, 35 | LogLevel, Result, SampleFormat, State, StreamParams, StreamParamsBuilder, StreamParamsRef, 36 | StreamPrefs, StreamRef, 37 | }; 38 | pub use frame::*; 39 | pub use sample::*; 40 | pub use stream::*; 41 | -------------------------------------------------------------------------------- /cubeb-sys/src/resampler.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use callbacks::cubeb_data_callback; 7 | use std::os::raw::{c_long, c_uint, c_void}; 8 | use stream::{cubeb_stream, cubeb_stream_params}; 9 | 10 | pub enum cubeb_resampler {} 11 | 12 | cubeb_enum! { 13 | pub enum cubeb_resampler_quality { 14 | CUBEB_RESAMPLER_QUALITY_VOIP, 15 | CUBEB_RESAMPLER_QUALITY_DEFAULT, 16 | CUBEB_RESAMPLER_QUALITY_DESKTOP, 17 | } 18 | } 19 | 20 | cubeb_enum! { 21 | pub enum cubeb_resampler_reclock { 22 | CUBEB_RESAMPLER_RECLOCK_NONE, 23 | CUBEB_RESAMPLER_RECLOCK_INPUT, 24 | } 25 | } 26 | 27 | extern "C" { 28 | pub fn cubeb_resampler_create( 29 | stream: *mut cubeb_stream, 30 | input_params: *mut cubeb_stream_params, 31 | output_params: *mut cubeb_stream_params, 32 | target_rate: c_uint, 33 | callback: cubeb_data_callback, 34 | user_ptr: *mut c_void, 35 | quality: cubeb_resampler_quality, 36 | reclock: cubeb_resampler_reclock, 37 | ) -> *mut cubeb_resampler; 38 | 39 | pub fn cubeb_resampler_fill( 40 | resampler: *mut cubeb_resampler, 41 | input_buffer: *mut c_void, 42 | input_frame_count: *mut c_long, 43 | output_buffer: *mut c_void, 44 | output_frames_needed: c_long, 45 | ) -> c_long; 46 | 47 | pub fn cubeb_resampler_destroy(resampler: *mut cubeb_resampler); 48 | pub fn cubeb_resampler_latency(resampler: *mut cubeb_resampler) -> c_long; 49 | } 50 | -------------------------------------------------------------------------------- /systest/build.rs: -------------------------------------------------------------------------------- 1 | extern crate ctest; 2 | 3 | use std::env; 4 | use std::path::PathBuf; 5 | 6 | fn main() { 7 | let root = PathBuf::from(env::var_os("DEP_CUBEB_ROOT").unwrap()); 8 | 9 | let target = env::var("TARGET").unwrap(); 10 | let windows = target.contains("windows"); 11 | let debug = env::var("DEBUG").unwrap().parse::().unwrap(); 12 | 13 | if windows && debug { 14 | println!("cargo:rustc-link-lib=msvcrtd"); 15 | } 16 | 17 | let mut cfg = ctest::TestGenerator::new(); 18 | 19 | if windows { 20 | // Ignore warnings when declaring CUBEB_LAYOUT_* enum. 21 | cfg.flag("/wd5287"); 22 | } 23 | 24 | // Include the header files where the C APIs are defined 25 | cfg.header("cubeb.h") 26 | .header("cubeb_mixer.h") 27 | .header("cubeb_resampler.h") 28 | .header("cubeb_log.h") 29 | .header("cubeb_audio_dump.h"); 30 | 31 | // Include the directory where the header files are defined 32 | cfg.include(root.join("include")) 33 | .include(root.join("include/cubeb")) 34 | .include("../cubeb-sys/libcubeb/src"); 35 | 36 | cfg.type_name(|s, _, _| s.to_string()) 37 | .field_name(|_, f| match f { 38 | "device_type" => "type".to_string(), 39 | _ => f.to_string(), 40 | }); 41 | 42 | // Don't perform `((type_t) -1) < 0)` checks for pointers because 43 | // they are unsigned and always >= 0. 44 | cfg.skip_signededness(|s| match s { 45 | s if s.ends_with("_callback") => true, 46 | "cubeb_devid" => true, 47 | _ => false, 48 | }); 49 | 50 | // Generate the tests, passing the path to the `*-sys` library as well as 51 | // the module to generate. 52 | cfg.generate("../cubeb-sys/src/lib.rs", "all.rs"); 53 | } 54 | -------------------------------------------------------------------------------- /cubeb-sys/src/context.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use callbacks::{cubeb_data_callback, cubeb_state_callback}; 7 | use device::cubeb_devid; 8 | use std::os::raw::{c_char, c_int, c_uint, c_void}; 9 | use stream::{cubeb_input_processing_params, cubeb_stream, cubeb_stream_params}; 10 | 11 | pub enum cubeb {} 12 | 13 | extern "C" { 14 | pub fn cubeb_init( 15 | context: *mut *mut cubeb, 16 | context_name: *const c_char, 17 | backend_name: *const c_char, 18 | ) -> c_int; 19 | pub fn cubeb_get_backend_id(context: *mut cubeb) -> *const c_char; 20 | pub fn cubeb_get_max_channel_count(context: *mut cubeb, max_channels: *mut c_uint) -> c_int; 21 | pub fn cubeb_get_min_latency( 22 | context: *mut cubeb, 23 | params: *mut cubeb_stream_params, 24 | latency_frames: *mut c_uint, 25 | ) -> c_int; 26 | pub fn cubeb_get_preferred_sample_rate(context: *mut cubeb, rate: *mut c_uint) -> c_int; 27 | pub fn cubeb_get_supported_input_processing_params( 28 | context: *mut cubeb, 29 | params: *mut cubeb_input_processing_params, 30 | ) -> c_int; 31 | pub fn cubeb_destroy(context: *mut cubeb); 32 | pub fn cubeb_stream_init( 33 | context: *mut cubeb, 34 | stream: *mut *mut cubeb_stream, 35 | stream_name: *const c_char, 36 | input_device: cubeb_devid, 37 | input_stream_params: *mut cubeb_stream_params, 38 | output_device: cubeb_devid, 39 | output_stream_params: *mut cubeb_stream_params, 40 | latency_frames: c_uint, 41 | data_callback: cubeb_data_callback, 42 | state_callback: cubeb_state_callback, 43 | user_ptr: *mut c_void, 44 | ) -> c_int; 45 | } 46 | -------------------------------------------------------------------------------- /cubeb-api/examples/tone.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2011 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | //! libcubeb api/function test. Plays a simple tone. 7 | extern crate cubeb; 8 | 9 | mod common; 10 | 11 | use cubeb::{MonoFrame, Sample}; 12 | use std::f32::consts::PI; 13 | use std::thread; 14 | use std::time::Duration; 15 | 16 | const SAMPLE_FREQUENCY: u32 = 48_000; 17 | const STREAM_FORMAT: cubeb::SampleFormat = cubeb::SampleFormat::S16LE; 18 | 19 | type Frame = MonoFrame; 20 | 21 | fn main() { 22 | let ctx = common::init("Cubeb tone example").expect("Failed to create cubeb context"); 23 | 24 | let params = cubeb::StreamParamsBuilder::new() 25 | .format(STREAM_FORMAT) 26 | .rate(SAMPLE_FREQUENCY) 27 | .channels(1) 28 | .layout(cubeb::ChannelLayout::MONO) 29 | .take(); 30 | 31 | let mut position = 0u32; 32 | 33 | let mut builder = cubeb::StreamBuilder::::new(); 34 | builder 35 | .name("Cubeb tone (mono)") 36 | .default_output(¶ms) 37 | .latency(0x1000) 38 | .data_callback(move |_, output| { 39 | // generate our test tone on the fly 40 | for f in output.iter_mut() { 41 | // North American dial tone 42 | let t1 = (2.0 * PI * 350.0 * position as f32 / SAMPLE_FREQUENCY as f32).sin(); 43 | let t2 = (2.0 * PI * 440.0 * position as f32 / SAMPLE_FREQUENCY as f32).sin(); 44 | 45 | f.m = i16::from_float(0.5 * (t1 + t2)); 46 | 47 | position += 1; 48 | } 49 | output.len() as isize 50 | }) 51 | .state_callback(|state| { 52 | println!("stream {:?}", state); 53 | }); 54 | 55 | let stream = builder.init(&ctx).expect("Failed to create cubeb stream"); 56 | 57 | stream.start().unwrap(); 58 | thread::sleep(Duration::from_millis(500)); 59 | stream.stop().unwrap(); 60 | } 61 | -------------------------------------------------------------------------------- /cubeb-backend/src/traits.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use cubeb_core::{ 7 | DeviceId, DeviceInfo, DeviceRef, DeviceType, InputProcessingParams, Result, Stream, 8 | StreamParams, StreamParamsRef, 9 | }; 10 | use ffi; 11 | use std::ffi::CStr; 12 | use std::os::raw::c_void; 13 | 14 | pub trait ContextOps { 15 | fn init(context_name: Option<&CStr>) -> Result>; 16 | fn backend_id(&mut self) -> &CStr; 17 | fn max_channel_count(&mut self) -> Result; 18 | fn min_latency(&mut self, params: StreamParams) -> Result; 19 | fn preferred_sample_rate(&mut self) -> Result; 20 | fn supported_input_processing_params(&mut self) -> Result; 21 | fn enumerate_devices(&mut self, devtype: DeviceType) -> Result>; 22 | fn device_collection_destroy(&mut self, collection: Box<[DeviceInfo]>) -> Result<()>; 23 | #[allow(clippy::too_many_arguments)] 24 | fn stream_init( 25 | &mut self, 26 | stream_name: Option<&CStr>, 27 | input_device: DeviceId, 28 | input_stream_params: Option<&StreamParamsRef>, 29 | output_device: DeviceId, 30 | output_stream_params: Option<&StreamParamsRef>, 31 | latency_frames: u32, 32 | data_callback: ffi::cubeb_data_callback, 33 | state_callback: ffi::cubeb_state_callback, 34 | user_ptr: *mut c_void, 35 | ) -> Result; 36 | fn register_device_collection_changed( 37 | &mut self, 38 | devtype: DeviceType, 39 | cb: ffi::cubeb_device_collection_changed_callback, 40 | user_ptr: *mut c_void, 41 | ) -> Result<()>; 42 | } 43 | 44 | pub trait StreamOps { 45 | fn start(&mut self) -> Result<()>; 46 | fn stop(&mut self) -> Result<()>; 47 | fn position(&mut self) -> Result; 48 | fn latency(&mut self) -> Result; 49 | fn input_latency(&mut self) -> Result; 50 | fn set_volume(&mut self, volume: f32) -> Result<()>; 51 | fn set_name(&mut self, name: &CStr) -> Result<()>; 52 | fn current_device(&mut self) -> Result<&DeviceRef>; 53 | fn set_input_mute(&mut self, mute: bool) -> Result<()>; 54 | fn set_input_processing_params(&mut self, params: InputProcessingParams) -> Result<()>; 55 | fn device_destroy(&mut self, device: &DeviceRef) -> Result<()>; 56 | fn register_device_changed_callback( 57 | &mut self, 58 | device_changed_callback: ffi::cubeb_device_changed_callback, 59 | ) -> Result<()>; 60 | } 61 | -------------------------------------------------------------------------------- /cubeb-core/src/error.rs: -------------------------------------------------------------------------------- 1 | use ffi; 2 | use std::ffi::NulError; 3 | use std::os::raw::c_int; 4 | use std::{error, fmt}; 5 | 6 | pub type Result = ::std::result::Result; 7 | 8 | /// An enumeration of possible errors that can happen when working with cubeb. 9 | #[derive(PartialEq, Eq, Clone, Debug, Copy)] 10 | pub enum Error { 11 | /// GenericError 12 | Error = ffi::CUBEB_ERROR as isize, 13 | /// Requested format is invalid 14 | InvalidFormat = ffi::CUBEB_ERROR_INVALID_FORMAT as isize, 15 | /// Requested parameter is invalid 16 | InvalidParameter = ffi::CUBEB_ERROR_INVALID_PARAMETER as isize, 17 | /// Requested operation is not supported 18 | NotSupported = ffi::CUBEB_ERROR_NOT_SUPPORTED as isize, 19 | /// Requested device is unavailable 20 | DeviceUnavailable = ffi::CUBEB_ERROR_DEVICE_UNAVAILABLE as isize, 21 | } 22 | 23 | impl Error { 24 | pub fn wrap(code: c_int) -> Result<()> { 25 | let inner = match code { 26 | ffi::CUBEB_OK => return Ok(()), 27 | ffi::CUBEB_ERROR_INVALID_FORMAT => Error::InvalidFormat, 28 | ffi::CUBEB_ERROR_INVALID_PARAMETER => Error::InvalidParameter, 29 | ffi::CUBEB_ERROR_NOT_SUPPORTED => Error::NotSupported, 30 | ffi::CUBEB_ERROR_DEVICE_UNAVAILABLE => Error::DeviceUnavailable, 31 | // Everything else is just the generic error 32 | _ => { 33 | debug_assert!(code == Error::Error as c_int); 34 | Error::Error 35 | } 36 | }; 37 | 38 | Err(inner) 39 | } 40 | } 41 | 42 | impl error::Error for Error { 43 | fn description(&self) -> &str { 44 | match self { 45 | Error::Error => "Error", 46 | Error::InvalidFormat => "Invalid format", 47 | Error::InvalidParameter => "Invalid parameter", 48 | Error::NotSupported => "Not supported", 49 | Error::DeviceUnavailable => "Device unavailable", 50 | } 51 | } 52 | } 53 | 54 | impl fmt::Display for Error { 55 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 56 | write!(f, "{self:?}") 57 | } 58 | } 59 | 60 | impl From for Error { 61 | fn from(_: NulError) -> Error { 62 | Error::Error 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::*; 69 | use ffi; 70 | 71 | #[test] 72 | fn test_from_raw() { 73 | macro_rules! test { 74 | ( $($raw:ident => $err:ident),* ) => {{ 75 | $( 76 | let e = Error::wrap(ffi::$raw); 77 | assert_eq!(e.unwrap_err() as c_int, ffi::$raw); 78 | )* 79 | }}; 80 | } 81 | test!(CUBEB_ERROR => Error, 82 | CUBEB_ERROR_INVALID_FORMAT => InvalidFormat, 83 | CUBEB_ERROR_INVALID_PARAMETER => InvalidParameter, 84 | CUBEB_ERROR_NOT_SUPPORTED => NotSupported, 85 | CUBEB_ERROR_DEVICE_UNAVAILABLE => DeviceUnavailable 86 | ); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /cubeb-core/src/log.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use std::ffi::{c_char, CStr}; 7 | use std::sync::RwLock; 8 | use {ffi, Error, Result}; 9 | 10 | /// Level (verbosity) of logging for a particular cubeb context. 11 | #[derive(PartialEq, Eq, Clone, Debug, Copy, PartialOrd, Ord)] 12 | pub enum LogLevel { 13 | /// Logging disabled 14 | Disabled, 15 | /// Logging lifetime operation (creation/destruction). 16 | Normal, 17 | /// Verbose logging of callbacks, can have performance implications. 18 | Verbose, 19 | } 20 | 21 | impl From for LogLevel { 22 | fn from(x: ffi::cubeb_log_level) -> Self { 23 | use LogLevel::*; 24 | match x { 25 | ffi::CUBEB_LOG_NORMAL => Normal, 26 | ffi::CUBEB_LOG_VERBOSE => Verbose, 27 | _ => Disabled, 28 | } 29 | } 30 | } 31 | 32 | impl From for ffi::cubeb_log_level { 33 | fn from(x: LogLevel) -> Self { 34 | use LogLevel::*; 35 | match x { 36 | Normal => ffi::CUBEB_LOG_NORMAL, 37 | Verbose => ffi::CUBEB_LOG_VERBOSE, 38 | Disabled => ffi::CUBEB_LOG_DISABLED, 39 | } 40 | } 41 | } 42 | 43 | pub fn log_enabled() -> bool { 44 | unsafe { ffi::cubeb_log_get_level() != LogLevel::Disabled as _ } 45 | } 46 | 47 | static LOG_CALLBACK: RwLock> = RwLock::new(None); 48 | 49 | extern "C" { 50 | fn cubeb_write_log(fmt: *const c_char, ...); 51 | } 52 | 53 | include!(concat!(env!("OUT_DIR"), "/log_wrap.rs")); 54 | 55 | /// # Safety 56 | /// 57 | /// |s| must be null, or a pointer to a valid, nul-terminated, array of chars. 58 | pub unsafe fn rust_write_formatted_msg(s: *const c_char) { 59 | if s.is_null() { 60 | // Do nothing if the pointer is null. 61 | return; 62 | } 63 | if let Ok(guard) = LOG_CALLBACK.read() { 64 | if let Some(f) = *guard { 65 | f(CStr::from_ptr(s)); 66 | } 67 | // Do nothing if there is no callback. 68 | } 69 | // Silently fail if lock cannot be acquired. 70 | } 71 | 72 | pub fn set_logging(level: LogLevel, f: Option) -> Result<()> { 73 | match LOG_CALLBACK.write() { 74 | Ok(mut guard) => { 75 | *guard = f; 76 | } 77 | Err(_) => return Err(Error::Error), 78 | } 79 | unsafe { 80 | call!(ffi::cubeb_set_log_callback( 81 | level.into(), 82 | if level == LogLevel::Disabled { 83 | None 84 | } else { 85 | Some(cubeb_write_log) 86 | } 87 | )) 88 | } 89 | } 90 | 91 | #[cfg(test)] 92 | mod tests { 93 | use super::*; 94 | 95 | #[test] 96 | fn test_logging_disabled_by_default() { 97 | assert!(!log_enabled()); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /cubeb-core/src/device_collection.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use ffi; 7 | use ffi_types; 8 | use std::{ops, slice}; 9 | use {ContextRef, DeviceInfo}; 10 | 11 | /// A collection of `DeviceInfo` used by libcubeb 12 | type CType = ffi::cubeb_device_collection; 13 | 14 | #[derive(Debug)] 15 | pub struct DeviceCollection<'ctx>(CType, &'ctx ContextRef); 16 | 17 | impl DeviceCollection<'_> { 18 | pub(crate) fn init_with_ctx(ctx: &ContextRef, coll: CType) -> DeviceCollection<'_> { 19 | DeviceCollection(coll, ctx) 20 | } 21 | 22 | pub fn as_ptr(&self) -> *mut CType { 23 | &self.0 as *const CType as *mut CType 24 | } 25 | } 26 | 27 | impl Drop for DeviceCollection<'_> { 28 | fn drop(&mut self) { 29 | unsafe { 30 | let _ = call!(ffi::cubeb_device_collection_destroy( 31 | self.1.as_ptr(), 32 | &mut self.0 33 | )); 34 | } 35 | } 36 | } 37 | 38 | impl ::std::ops::Deref for DeviceCollection<'_> { 39 | type Target = DeviceCollectionRef; 40 | 41 | #[inline] 42 | fn deref(&self) -> &DeviceCollectionRef { 43 | let ptr = &self.0 as *const CType as *mut CType; 44 | unsafe { DeviceCollectionRef::from_ptr(ptr) } 45 | } 46 | } 47 | 48 | impl ::std::convert::AsRef for DeviceCollection<'_> { 49 | #[inline] 50 | fn as_ref(&self) -> &DeviceCollectionRef { 51 | self 52 | } 53 | } 54 | 55 | pub struct DeviceCollectionRef(ffi_types::Opaque); 56 | 57 | impl DeviceCollectionRef { 58 | /// # Safety 59 | /// 60 | /// This function is unsafe because it dereferences the given `ptr` pointer. 61 | /// The caller should ensure that pointer is valid. 62 | #[inline] 63 | pub unsafe fn from_ptr<'a>(ptr: *mut CType) -> &'a Self { 64 | &*(ptr as *mut _) 65 | } 66 | 67 | /// # Safety 68 | /// 69 | /// This function is unsafe because it dereferences the given `ptr` pointer. 70 | /// The caller should ensure that pointer is valid. 71 | #[inline] 72 | pub unsafe fn from_ptr_mut<'a>(ptr: *mut CType) -> &'a mut Self { 73 | &mut *(ptr as *mut _) 74 | } 75 | 76 | #[inline] 77 | pub fn as_ptr(&self) -> *mut CType { 78 | self as *const _ as *mut _ 79 | } 80 | } 81 | 82 | impl ::std::fmt::Debug for DeviceCollectionRef { 83 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 84 | let ptr = self as *const DeviceCollectionRef as usize; 85 | f.debug_tuple(stringify!(DeviceCollectionRef)) 86 | .field(&ptr) 87 | .finish() 88 | } 89 | } 90 | 91 | impl ops::Deref for DeviceCollectionRef { 92 | type Target = [DeviceInfo]; 93 | fn deref(&self) -> &[DeviceInfo] { 94 | unsafe { 95 | let coll: &ffi::cubeb_device_collection = &*self.as_ptr(); 96 | slice::from_raw_parts(coll.device as *const DeviceInfo, coll.count) 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cubeb-rs [![ISC License](https://img.shields.io/crates/l/cubeb.svg)](https://github.com/djg/cubeb-rs/blob/master/LICENSE) 2 | 3 | A cross-platform audio library in Rust. 4 | 5 | ## Features 6 | 7 | Provides access to the following: 8 | 9 | - Multiple audio backends across multiple platforms. See 10 | [here](https://github.com/mozilla/cubeb/wiki/Backend-Support) for details. 11 | - Enumeration of available audio devices. 12 | - Opening input, output and duplex audio streams with control over latency, 13 | sample rate, channel layout, state transitions, data handling and more. 14 | 15 | ## Goals 16 | 17 | Currently, **cubeb-rs** is based on a set of bindings to the original [**cubeb** 18 | C++ library](https://github.com/mozilla/cubeb) most notable for its use as the 19 | audio backend within 20 | [Gecko](https://github.com/mozilla/gecko-dev/search?q=cubeb&unscoped_q=cubeb), 21 | Mozilla's browser engine. The long-term goal for **cubeb-rs** is to become 22 | independent of the C++ library and provide a pure-Rust implementation down to 23 | the level of the platform API eventually replacing the original within Gecko 24 | where possible. 25 | 26 | In order to achieve this goal **cubeb-rs** is structured in a manner that 27 | supports backend implementations in both pure-Rust and via bindings to the C++ 28 | implementation, allowing for progressive replacement. So far, pure-Rust 29 | implementations exist for: 30 | 31 | - CoreAudio https://github.com/mozilla/cubeb-coreaudio-rs 32 | - PulseAudio https://github.com/mozilla/cubeb-pulse-rs 33 | 34 | The plan is to consolidate all **cubeb**-related projects (including backend 35 | implementations) under a single repository 36 | [here](https://github.com/mozilla/cubeb) in the near future. 37 | 38 | While **cubeb** is primarily renown for its use within Gecko, contributions and 39 | use from projects outside of Gecko is very welcome. 40 | 41 | ## Crates 42 | 43 | The following crates are included within this repository: 44 | 45 | | Crate | Links | Description | 46 | | --- | --- | --- | 47 | | `cubeb` | [![crates.io](https://img.shields.io/crates/v/cubeb.svg)](https://crates.io/crates/cubeb) [![docs.rs](https://docs.rs/cubeb/badge.svg)](https://docs.rs/cubeb) | The top-level user API for **cubeb-rs**. See the `cubeb-api` subdirectory. Depends on `cubeb-core`. | 48 | | `cubeb-core` | [![crates.io](https://img.shields.io/crates/v/cubeb-core.svg)](https://crates.io/crates/cubeb-core) [![docs.rs](https://docs.rs/cubeb-core/badge.svg)](https://docs.rs/cubeb-core) | Common types and definitions for cubeb rust and C bindings. Not intended for direct use. Depends on `cubeb-sys`. | 49 | | `cubeb-sys` | [![crates.io](https://img.shields.io/crates/v/cubeb-sys.svg)](https://crates.io/crates/cubeb-sys) [![docs.rs](https://docs.rs/cubeb-sys/badge.svg)](https://docs.rs/cubeb-sys) | Native bindings to the cubeb C++ library. Requires `pkg-config` and `cmake` | 50 | | `cubeb-backend` | [![crates.io](https://img.shields.io/crates/v/cubeb-backend.svg)](https://crates.io/crates/cubeb-backend) [![docs.rs](https://docs.rs/cubeb-backend/badge.svg)](https://docs.rs/cubeb-backend) | Bindings to libcubeb internals to facilitate implementing cubeb backends in Rust. Depends on `cubeb-core`. | 51 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{ matrix.os }} 8 | continue-on-error: ${{ matrix.experimental }} 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | os: [ubuntu-24.04, windows-2025, macos-14] 13 | rust: [stable] 14 | experimental: [false] 15 | include: 16 | - os: ubuntu-24.04 17 | rust: nightly 18 | experimental: true 19 | - os: windows-2025 20 | rust: nightly 21 | experimental: true 22 | - os: macos-14 23 | rust: nightly 24 | experimental: true 25 | 26 | steps: 27 | - uses: actions/checkout@v2 28 | with: 29 | submodules: recursive 30 | 31 | - name: Install Rust 32 | run: rustup toolchain install ${{ matrix.rust }} --profile minimal --component rustfmt,clippy 33 | 34 | - name: Install Dependencies (Linux) 35 | if: ${{ matrix.os == 'ubuntu-24.04' }} 36 | run: sudo apt-get update && sudo apt-get install libpulse-dev pulseaudio 37 | 38 | - name: Check format 39 | shell: bash 40 | run: rustup run ${{ matrix.rust }} cargo fmt --all -- --check 41 | 42 | # Skip clippy checks for `systest` 43 | - name: Clippy 44 | shell: bash 45 | run: rustup run ${{ matrix.rust }} cargo clippy -p cubeb -p cubeb-backend -p cubeb-core -p cubeb-sys -- -D warnings 46 | 47 | - name: Build 48 | shell: bash 49 | run: rustup run ${{ matrix.rust }} cargo build --all 50 | 51 | - name: Start Sound Server (Linux) 52 | if: ${{ matrix.os == 'ubuntu-24.04' }} 53 | run: pulseaudio -D --start 54 | 55 | - name: Setup Audio 56 | if: ${{ matrix.os == 'macos-14' }} 57 | run: | 58 | brew install switchaudio-osx 59 | brew install blackhole-2ch 60 | sudo killall -9 coreaudiod 61 | SwitchAudioSource -s "BlackHole 2ch" -t input 62 | SwitchAudioSource -s "BlackHole 2ch" -t output 63 | 64 | - name: Grant microphone access 65 | if: ${{ matrix.os == 'macos-14' }} 66 | env: 67 | tcc_extra_columns: ${{ matrix.os == 'macos-14' && ',NULL,NULL,''UNUSED'',1687786159' || '' }} 68 | run: sqlite3 $HOME/Library/Application\ Support/com.apple.TCC/TCC.db "INSERT OR IGNORE INTO access VALUES ('kTCCServiceMicrophone','/usr/local/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159${{ env.tcc_extra_columns }});" 69 | 70 | - name: Test 71 | shell: bash 72 | run: rustup run ${{ matrix.rust }} cargo test --all 73 | 74 | - name: Run systest 75 | shell: bash 76 | run: rustup run ${{ matrix.rust }} cargo run -p systest 77 | 78 | build_arm_linux: 79 | name: Cross-compile for aarch64 linux 80 | env: 81 | CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc 82 | 83 | runs-on: ubuntu-24.04 84 | 85 | steps: 86 | - uses: actions/checkout@v2 87 | with: 88 | submodules: recursive 89 | 90 | - name: Install Rust 91 | shell: bash 92 | run: rustup target add aarch64-unknown-linux-gnu 93 | 94 | - name: Install crossbuild headers 95 | shell: bash 96 | run: sudo apt-get update && sudo apt-get install -y crossbuild-essential-arm64 97 | 98 | - name: Build cross compile aarch64 99 | shell: bash 100 | run: cargo build --workspace --exclude systest --target aarch64-unknown-linux-gnu 101 | -------------------------------------------------------------------------------- /cubeb-sys/src/channel.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | #[cfg(all(windows, not(target_env = "gnu")))] 7 | use std::os::raw::c_int as c_enum; 8 | #[cfg(any(not(windows), all(windows, target_env = "gnu")))] 9 | use std::os::raw::c_uint as c_enum; 10 | 11 | cubeb_enum! { 12 | pub enum cubeb_channel : c_enum { 13 | CHANNEL_UNKNOWN = 0, 14 | CHANNEL_FRONT_LEFT = 1 << 0, 15 | CHANNEL_FRONT_RIGHT = 1 << 1, 16 | CHANNEL_FRONT_CENTER = 1 << 2, 17 | CHANNEL_LOW_FREQUENCY = 1 << 3, 18 | CHANNEL_BACK_LEFT = 1 << 4, 19 | CHANNEL_BACK_RIGHT = 1 << 5, 20 | CHANNEL_FRONT_LEFT_OF_CENTER = 1 << 6, 21 | CHANNEL_FRONT_RIGHT_OF_CENTER = 1 << 7, 22 | CHANNEL_BACK_CENTER = 1 << 8, 23 | CHANNEL_SIDE_LEFT = 1 << 9, 24 | CHANNEL_SIDE_RIGHT = 1 << 10, 25 | CHANNEL_TOP_CENTER = 1 << 11, 26 | CHANNEL_TOP_FRONT_LEFT = 1 << 12, 27 | CHANNEL_TOP_FRONT_CENTER = 1 << 13, 28 | CHANNEL_TOP_FRONT_RIGHT = 1 << 14, 29 | CHANNEL_TOP_BACK_LEFT = 1 << 15, 30 | CHANNEL_TOP_BACK_CENTER = 1 << 16, 31 | CHANNEL_TOP_BACK_RIGHT = 1 << 17, 32 | } 33 | } 34 | 35 | cubeb_enum! { 36 | pub enum cubeb_channel_layout : c_enum { 37 | CUBEB_LAYOUT_UNDEFINED = 0, 38 | CUBEB_LAYOUT_MONO = CHANNEL_FRONT_CENTER, 39 | CUBEB_LAYOUT_MONO_LFE = CUBEB_LAYOUT_MONO | CHANNEL_LOW_FREQUENCY, 40 | CUBEB_LAYOUT_STEREO = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT, 41 | CUBEB_LAYOUT_STEREO_LFE = CUBEB_LAYOUT_STEREO | CHANNEL_LOW_FREQUENCY, 42 | CUBEB_LAYOUT_3F = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | 43 | CHANNEL_FRONT_CENTER, 44 | CUBEB_LAYOUT_3F_LFE = CUBEB_LAYOUT_3F | CHANNEL_LOW_FREQUENCY, 45 | CUBEB_LAYOUT_2F1 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | 46 | CHANNEL_BACK_CENTER, 47 | CUBEB_LAYOUT_2F1_LFE = CUBEB_LAYOUT_2F1 | CHANNEL_LOW_FREQUENCY, 48 | CUBEB_LAYOUT_3F1 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | 49 | CHANNEL_FRONT_CENTER | CHANNEL_BACK_CENTER, 50 | CUBEB_LAYOUT_3F1_LFE = CUBEB_LAYOUT_3F1 | CHANNEL_LOW_FREQUENCY, 51 | CUBEB_LAYOUT_2F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | 52 | CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT, 53 | CUBEB_LAYOUT_2F2_LFE = CUBEB_LAYOUT_2F2 | CHANNEL_LOW_FREQUENCY, 54 | CUBEB_LAYOUT_QUAD = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | 55 | CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT, 56 | CUBEB_LAYOUT_QUAD_LFE = CUBEB_LAYOUT_QUAD | CHANNEL_LOW_FREQUENCY, 57 | CUBEB_LAYOUT_3F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | 58 | CHANNEL_FRONT_CENTER | CHANNEL_SIDE_LEFT | 59 | CHANNEL_SIDE_RIGHT, 60 | CUBEB_LAYOUT_3F2_LFE = CUBEB_LAYOUT_3F2 | CHANNEL_LOW_FREQUENCY, 61 | CUBEB_LAYOUT_3F2_BACK = CUBEB_LAYOUT_QUAD | CHANNEL_FRONT_CENTER, 62 | CUBEB_LAYOUT_3F2_LFE_BACK = CUBEB_LAYOUT_3F2_BACK | CHANNEL_LOW_FREQUENCY, 63 | CUBEB_LAYOUT_3F3R_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | 64 | CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY | 65 | CHANNEL_BACK_CENTER | CHANNEL_SIDE_LEFT | 66 | CHANNEL_SIDE_RIGHT, 67 | CUBEB_LAYOUT_3F4_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | 68 | CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY | 69 | CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT | 70 | CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT, 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /cubeb-sys/src/stream.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use callbacks::cubeb_device_changed_callback; 7 | use channel::cubeb_channel_layout; 8 | use device::cubeb_device; 9 | use format::cubeb_sample_format; 10 | use std::os::raw::{c_char, c_float, c_int, c_uint, c_void}; 11 | use std::{fmt, mem}; 12 | 13 | cubeb_enum! { 14 | pub enum cubeb_input_processing_params { 15 | CUBEB_INPUT_PROCESSING_PARAM_NONE = 0x00, 16 | CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION = 0x01, 17 | CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION = 0x02, 18 | CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL = 0x04, 19 | CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION = 0x08, 20 | } 21 | } 22 | 23 | cubeb_enum! { 24 | pub enum cubeb_stream_prefs { 25 | CUBEB_STREAM_PREF_NONE = 0x00, 26 | CUBEB_STREAM_PREF_LOOPBACK = 0x01, 27 | CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING = 0x02, 28 | CUBEB_STREAM_PREF_VOICE = 0x04, 29 | } 30 | } 31 | 32 | cubeb_enum! { 33 | pub enum cubeb_state { 34 | CUBEB_STATE_STARTED, 35 | CUBEB_STATE_STOPPED, 36 | CUBEB_STATE_DRAINED, 37 | CUBEB_STATE_ERROR, 38 | } 39 | } 40 | 41 | pub enum cubeb_stream {} 42 | 43 | #[repr(C)] 44 | #[derive(Clone, Copy)] 45 | pub struct cubeb_stream_params { 46 | pub format: cubeb_sample_format, 47 | pub rate: c_uint, 48 | pub channels: c_uint, 49 | pub layout: cubeb_channel_layout, 50 | pub prefs: cubeb_stream_prefs, 51 | pub input_params: cubeb_input_processing_params, 52 | } 53 | 54 | impl Default for cubeb_stream_params { 55 | fn default() -> Self { 56 | unsafe { mem::zeroed() } 57 | } 58 | } 59 | 60 | // Explicit Debug impl to work around bug in ctest 61 | impl fmt::Debug for cubeb_stream_params { 62 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 63 | f.debug_struct("cubeb_stream_params") 64 | .field("format", &self.format) 65 | .field("rate", &self.rate) 66 | .field("channels", &self.channels) 67 | .field("layout", &self.layout) 68 | .field("prefs", &self.prefs) 69 | .field("input_params", &self.input_params) 70 | .finish() 71 | } 72 | } 73 | 74 | extern "C" { 75 | pub fn cubeb_stream_destroy(stream: *mut cubeb_stream); 76 | pub fn cubeb_stream_start(stream: *mut cubeb_stream) -> c_int; 77 | pub fn cubeb_stream_stop(stream: *mut cubeb_stream) -> c_int; 78 | pub fn cubeb_stream_get_position(stream: *mut cubeb_stream, position: *mut u64) -> c_int; 79 | pub fn cubeb_stream_get_latency(stream: *mut cubeb_stream, latency: *mut c_uint) -> c_int; 80 | pub fn cubeb_stream_get_input_latency(stream: *mut cubeb_stream, latency: *mut c_uint) 81 | -> c_int; 82 | pub fn cubeb_stream_set_volume(stream: *mut cubeb_stream, volume: c_float) -> c_int; 83 | pub fn cubeb_stream_set_name(stream: *mut cubeb_stream, name: *const c_char) -> c_int; 84 | pub fn cubeb_stream_get_current_device( 85 | stream: *mut cubeb_stream, 86 | device: *mut *mut cubeb_device, 87 | ) -> c_int; 88 | pub fn cubeb_stream_set_input_mute(stream: *mut cubeb_stream, mute: c_int) -> c_int; 89 | pub fn cubeb_stream_set_input_processing_params( 90 | stream: *mut cubeb_stream, 91 | params: cubeb_input_processing_params, 92 | ) -> c_int; 93 | pub fn cubeb_stream_device_destroy( 94 | stream: *mut cubeb_stream, 95 | devices: *mut cubeb_device, 96 | ) -> c_int; 97 | pub fn cubeb_stream_register_device_changed_callback( 98 | stream: *mut cubeb_stream, 99 | device_changed_callback: cubeb_device_changed_callback, 100 | ) -> c_int; 101 | pub fn cubeb_stream_user_ptr(stream: *mut cubeb_stream) -> *mut c_void; 102 | } 103 | -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env, fs, 3 | path::{Path, PathBuf}, 4 | process::Command, 5 | }; 6 | 7 | type DynError = Box; 8 | 9 | fn main() { 10 | if let Err(e) = try_main() { 11 | eprintln!("{e}"); 12 | std::process::exit(-1); 13 | } 14 | } 15 | 16 | fn try_main() -> Result<(), DynError> { 17 | let task = env::args().nth(1); 18 | let version = env::args().nth(2); 19 | match task.as_deref() { 20 | Some("release") => release(version.as_deref().unwrap_or("minor"))?, 21 | _ => print_help(), 22 | } 23 | Ok(()) 24 | } 25 | 26 | fn print_help() { 27 | eprintln!( 28 | "Tasks: 29 | 30 | release [VERSION] runs 'cargo release [VERSION]' after preparing the source directory 31 | [VERSION] can be 'major', 'minor', or 'patch'. If not specified, 'minor' is used. 32 | " 33 | ) 34 | } 35 | 36 | fn visit_dirs(dir: &Path, cb: &dyn Fn(&fs::DirEntry)) -> std::io::Result<()> { 37 | if dir.is_dir() { 38 | for entry in fs::read_dir(dir)? { 39 | let entry = entry?; 40 | let path = entry.path(); 41 | if path.is_dir() { 42 | visit_dirs(&path, cb)?; 43 | } else { 44 | cb(&entry); 45 | } 46 | } 47 | } 48 | Ok(()) 49 | } 50 | 51 | fn release(version: &str) -> Result<(), DynError> { 52 | let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); 53 | 54 | let status = Command::new(&cargo) 55 | .current_dir(project_root()) 56 | .args(["release", "--workspace", version, "-x", "--no-publish"]) 57 | .status()?; 58 | 59 | if !status.success() { 60 | Err("cargo release failed")?; 61 | } 62 | 63 | // For packaged build: rename libcubeb Cargo.toml files to Cargo.toml.in. 64 | visit_dirs(Path::new("cubeb-sys/libcubeb"), &|entry| { 65 | let path = entry.path(); 66 | if path 67 | .file_name() 68 | .unwrap() 69 | .to_str() 70 | .unwrap() 71 | .ends_with("Cargo.toml") 72 | { 73 | let new_path = path.with_file_name("Cargo.toml.in"); 74 | fs::rename(&path, &new_path).unwrap(); 75 | } 76 | }) 77 | .unwrap(); 78 | 79 | let status = Command::new(&cargo) 80 | .current_dir(project_root()) 81 | .args(["publish", "--package", "cubeb-sys", "--allow-dirty"]) 82 | .status()?; 83 | if !status.success() { 84 | Err("cargo publish failed")?; 85 | } 86 | 87 | // Rename libcubeb Cargo.toml.in files back to Cargo.toml. 88 | visit_dirs(Path::new("cubeb-sys/libcubeb"), &|entry| { 89 | let path = entry.path(); 90 | if path 91 | .file_name() 92 | .unwrap() 93 | .to_str() 94 | .unwrap() 95 | .ends_with("Cargo.toml.in") 96 | { 97 | let new_path = path.with_file_name("Cargo.toml"); 98 | fs::rename(&path, &new_path).unwrap(); 99 | } 100 | }) 101 | .unwrap(); 102 | 103 | let status = Command::new(&cargo) 104 | .current_dir(project_root()) 105 | .args(["publish", "--package", "cubeb-core"]) 106 | .status()?; 107 | if !status.success() { 108 | Err("cargo publish failed")?; 109 | } 110 | 111 | let status = Command::new(&cargo) 112 | .current_dir(project_root()) 113 | .args(["publish", "--package", "cubeb-backend"]) 114 | .status()?; 115 | if !status.success() { 116 | Err("cargo publish failed")?; 117 | } 118 | 119 | let status = Command::new(&cargo) 120 | .current_dir(project_root()) 121 | .args(["publish", "--package", "cubeb"]) 122 | .status()?; 123 | if !status.success() { 124 | Err("cargo publish failed")?; 125 | } 126 | 127 | Ok(()) 128 | } 129 | 130 | fn project_root() -> PathBuf { 131 | Path::new(&env!("CARGO_MANIFEST_DIR")) 132 | .ancestors() 133 | .nth(1) 134 | .unwrap() 135 | .to_path_buf() 136 | } 137 | -------------------------------------------------------------------------------- /cubeb-api/examples/devices.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2011 Mozilla Foundation 2 | // Copyright © 2015 Haakon Sporsheim 3 | // 4 | // This program is made available under an ISC-style license. See the 5 | // accompanying file LICENSE for details. 6 | 7 | //! libcubeb enumerate device test/example. 8 | //! Prints out a list of input/output devices connected to the system. 9 | extern crate cubeb; 10 | 11 | mod common; 12 | 13 | use cubeb::{DeviceFormat, DeviceType}; 14 | 15 | fn print_device_info(info: &cubeb::DeviceInfo) { 16 | let devtype = if info.device_type().contains(DeviceType::INPUT) { 17 | "input" 18 | } else if info.device_type().contains(DeviceType::OUTPUT) { 19 | "output" 20 | } else { 21 | "unknown?" 22 | }; 23 | 24 | let devstate = match info.state() { 25 | cubeb::DeviceState::Disabled => "disabled", 26 | cubeb::DeviceState::Unplugged => "unplugged", 27 | cubeb::DeviceState::Enabled => "enabled", 28 | }; 29 | 30 | let devdeffmt = match info.default_format() { 31 | DeviceFormat::S16LE => "S16LE", 32 | DeviceFormat::S16BE => "S16BE", 33 | DeviceFormat::F32LE => "F32LE", 34 | DeviceFormat::F32BE => "F32BE", 35 | _ => "unknown?", 36 | }; 37 | 38 | let mut devfmts = "".to_string(); 39 | if info.format().contains(DeviceFormat::S16LE) { 40 | devfmts = format!("{} S16LE", devfmts); 41 | } 42 | if info.format().contains(DeviceFormat::S16BE) { 43 | devfmts = format!("{} S16BE", devfmts); 44 | } 45 | if info.format().contains(DeviceFormat::F32LE) { 46 | devfmts = format!("{} F32LE", devfmts); 47 | } 48 | if info.format().contains(DeviceFormat::F32BE) { 49 | devfmts = format!("{} F32BE", devfmts); 50 | } 51 | 52 | if let Some(device_id) = info.device_id() { 53 | let preferred = if info.preferred().is_empty() { 54 | "" 55 | } else { 56 | " (PREFERRED)" 57 | }; 58 | println!("dev: \"{}\"{}", device_id, preferred); 59 | } 60 | if let Some(friendly_name) = info.friendly_name() { 61 | println!("\tName: \"{}\"", friendly_name); 62 | } 63 | if let Some(group_id) = info.group_id() { 64 | println!("\tGroup: \"{}\"", group_id); 65 | } 66 | if let Some(vendor_name) = info.vendor_name() { 67 | println!("\tVendor: \"{}\"", vendor_name); 68 | } 69 | println!("\tType: {}", devtype); 70 | println!("\tState: {}", devstate); 71 | println!("\tCh: {}", info.max_channels()); 72 | println!( 73 | "\tFormat: {} (0x{:x}) (default: {})", 74 | &devfmts[1..], 75 | info.format(), 76 | devdeffmt 77 | ); 78 | println!( 79 | "\tRate: {} - {} (default: {})", 80 | info.min_rate(), 81 | info.max_rate(), 82 | info.default_rate() 83 | ); 84 | println!( 85 | "\tLatency: lo {} frames, hi {} frames", 86 | info.latency_lo(), 87 | info.latency_hi() 88 | ); 89 | } 90 | 91 | fn main() { 92 | let ctx = common::init("Cubeb audio test").expect("Failed to create cubeb context"); 93 | 94 | println!("Enumerating input devices for backend {}", ctx.backend_id()); 95 | 96 | let devices = match ctx.enumerate_devices(DeviceType::INPUT) { 97 | Ok(devices) => devices, 98 | Err(cubeb::Error::NotSupported) => { 99 | println!("Device enumeration not support for this backend."); 100 | return; 101 | } 102 | Err(e) => { 103 | println!("Error enumerating devices: {}", e); 104 | return; 105 | } 106 | }; 107 | 108 | println!("Found {} input devices", devices.len()); 109 | for d in devices.iter() { 110 | print_device_info(d); 111 | } 112 | 113 | println!( 114 | "Enumerating output devices for backend {}", 115 | ctx.backend_id() 116 | ); 117 | 118 | let devices = match ctx.enumerate_devices(DeviceType::OUTPUT) { 119 | Ok(devices) => devices, 120 | Err(e) => { 121 | println!("Error enumerating devices: {}", e); 122 | return; 123 | } 124 | }; 125 | 126 | println!("Found {} output devices", devices.len()); 127 | for d in devices.iter() { 128 | print_device_info(d); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /cubeb-backend/src/ops.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | #![allow(non_camel_case_types)] 7 | 8 | use ffi; 9 | use std::os::raw::{c_char, c_float, c_int, c_uint, c_void}; 10 | 11 | #[repr(C)] 12 | pub struct Ops { 13 | pub init: Option< 14 | unsafe extern "C" fn(context: *mut *mut ffi::cubeb, context_name: *const c_char) -> c_int, 15 | >, 16 | pub get_backend_id: Option *const c_char>, 17 | pub get_max_channel_count: 18 | Option c_int>, 19 | pub get_min_latency: Option< 20 | unsafe extern "C" fn( 21 | context: *mut ffi::cubeb, 22 | params: ffi::cubeb_stream_params, 23 | latency_ms: *mut c_uint, 24 | ) -> c_int, 25 | >, 26 | pub get_preferred_sample_rate: 27 | Option c_int>, 28 | pub get_supported_input_processing_params: Option< 29 | unsafe extern "C" fn( 30 | c: *mut ffi::cubeb, 31 | params: *mut ffi::cubeb_input_processing_params, 32 | ) -> c_int, 33 | >, 34 | pub enumerate_devices: Option< 35 | unsafe extern "C" fn( 36 | context: *mut ffi::cubeb, 37 | devtype: ffi::cubeb_device_type, 38 | collection: *mut ffi::cubeb_device_collection, 39 | ) -> c_int, 40 | >, 41 | pub device_collection_destroy: Option< 42 | unsafe extern "C" fn( 43 | context: *mut ffi::cubeb, 44 | collection: *mut ffi::cubeb_device_collection, 45 | ) -> c_int, 46 | >, 47 | pub destroy: Option, 48 | #[allow(clippy::type_complexity)] 49 | pub stream_init: Option< 50 | unsafe extern "C" fn( 51 | context: *mut ffi::cubeb, 52 | stream: *mut *mut ffi::cubeb_stream, 53 | stream_name: *const c_char, 54 | input_device: ffi::cubeb_devid, 55 | input_stream_params: *mut ffi::cubeb_stream_params, 56 | output_device: ffi::cubeb_devid, 57 | output_stream_params: *mut ffi::cubeb_stream_params, 58 | latency: c_uint, 59 | data_callback: ffi::cubeb_data_callback, 60 | state_callback: ffi::cubeb_state_callback, 61 | user_ptr: *mut c_void, 62 | ) -> c_int, 63 | >, 64 | pub stream_destroy: Option, 65 | pub stream_start: Option c_int>, 66 | pub stream_stop: Option c_int>, 67 | pub stream_get_position: 68 | Option c_int>, 69 | pub stream_get_latency: 70 | Option c_int>, 71 | pub stream_get_input_latency: 72 | Option c_int>, 73 | pub stream_set_volume: 74 | Option c_int>, 75 | pub stream_set_name: 76 | Option c_int>, 77 | pub stream_get_current_device: Option< 78 | unsafe extern "C" fn( 79 | stream: *mut ffi::cubeb_stream, 80 | device: *mut *mut ffi::cubeb_device, 81 | ) -> c_int, 82 | >, 83 | pub stream_set_input_mute: 84 | Option c_int>, 85 | pub stream_set_input_processing_params: Option< 86 | unsafe extern "C" fn( 87 | stream: *mut ffi::cubeb_stream, 88 | params: ffi::cubeb_input_processing_params, 89 | ) -> c_int, 90 | >, 91 | pub stream_device_destroy: Option< 92 | unsafe extern "C" fn( 93 | stream: *mut ffi::cubeb_stream, 94 | device: *mut ffi::cubeb_device, 95 | ) -> c_int, 96 | >, 97 | pub stream_register_device_changed_callback: Option< 98 | unsafe extern "C" fn( 99 | stream: *mut ffi::cubeb_stream, 100 | device_changed_callback: ffi::cubeb_device_changed_callback, 101 | ) -> c_int, 102 | >, 103 | pub register_device_collection_changed: Option< 104 | unsafe extern "C" fn( 105 | context: *mut ffi::cubeb, 106 | devtype: ffi::cubeb_device_type, 107 | callback: ffi::cubeb_device_collection_changed_callback, 108 | user_ptr: *mut c_void, 109 | ) -> c_int, 110 | >, 111 | } 112 | -------------------------------------------------------------------------------- /cubeb-core/src/context.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use ffi; 7 | use std::ffi::CStr; 8 | use std::os::raw::c_void; 9 | use std::{ptr, str}; 10 | use util::opt_bytes; 11 | use { 12 | DeviceCollection, DeviceId, DeviceType, InputProcessingParams, Result, Stream, StreamParamsRef, 13 | }; 14 | 15 | macro_rules! as_ptr { 16 | ($e:expr) => { 17 | $e.map(|s| s.as_ptr()).unwrap_or(ptr::null_mut()) 18 | }; 19 | } 20 | 21 | ffi_type_heap! { 22 | type CType = ffi::cubeb; 23 | fn drop = ffi::cubeb_destroy; 24 | pub struct Context; 25 | pub struct ContextRef; 26 | } 27 | 28 | impl Context { 29 | pub fn init(context_name: Option<&CStr>, backend_name: Option<&CStr>) -> Result { 30 | let mut context: *mut ffi::cubeb = ptr::null_mut(); 31 | let context_name = as_ptr!(context_name); 32 | let backend_name = as_ptr!(backend_name); 33 | unsafe { 34 | call!(ffi::cubeb_init(&mut context, context_name, backend_name))?; 35 | Ok(Context::from_ptr(context)) 36 | } 37 | } 38 | } 39 | 40 | impl ContextRef { 41 | pub fn backend_id(&self) -> &str { 42 | str::from_utf8(self.backend_id_bytes()).unwrap() 43 | } 44 | 45 | pub fn backend_id_bytes(&self) -> &[u8] { 46 | unsafe { opt_bytes(ffi::cubeb_get_backend_id(self.as_ptr())).unwrap() } 47 | } 48 | 49 | pub fn max_channel_count(&self) -> Result { 50 | let mut channel_count = 0u32; 51 | unsafe { 52 | call!(ffi::cubeb_get_max_channel_count( 53 | self.as_ptr(), 54 | &mut channel_count 55 | ))?; 56 | } 57 | Ok(channel_count) 58 | } 59 | 60 | pub fn min_latency(&self, params: &StreamParamsRef) -> Result { 61 | let mut latency = 0u32; 62 | unsafe { 63 | call!(ffi::cubeb_get_min_latency( 64 | self.as_ptr(), 65 | params.as_ptr(), 66 | &mut latency 67 | ))?; 68 | } 69 | Ok(latency) 70 | } 71 | 72 | pub fn preferred_sample_rate(&self) -> Result { 73 | let mut rate = 0u32; 74 | unsafe { 75 | call!(ffi::cubeb_get_preferred_sample_rate( 76 | self.as_ptr(), 77 | &mut rate 78 | ))?; 79 | } 80 | Ok(rate) 81 | } 82 | 83 | pub fn supported_input_processing_params(&self) -> Result { 84 | let mut params = ffi::CUBEB_INPUT_PROCESSING_PARAM_NONE; 85 | unsafe { 86 | call!(ffi::cubeb_get_supported_input_processing_params( 87 | self.as_ptr(), 88 | &mut params 89 | ))?; 90 | }; 91 | Ok(InputProcessingParams::from_bits_truncate(params)) 92 | } 93 | 94 | /// # Safety 95 | /// 96 | /// This function is unsafe because it dereferences the given `data_callback`, `state_callback`, and `user_ptr` pointers. 97 | /// The caller should ensure those pointers are valid. 98 | #[allow(clippy::too_many_arguments)] 99 | pub unsafe fn stream_init( 100 | &self, 101 | stream_name: Option<&CStr>, 102 | input_device: DeviceId, 103 | input_stream_params: Option<&StreamParamsRef>, 104 | output_device: DeviceId, 105 | output_stream_params: Option<&StreamParamsRef>, 106 | latency_frames: u32, 107 | data_callback: ffi::cubeb_data_callback, 108 | state_callback: ffi::cubeb_state_callback, 109 | user_ptr: *mut c_void, 110 | ) -> Result { 111 | let mut stm: *mut ffi::cubeb_stream = ptr::null_mut(); 112 | 113 | let stream_name = as_ptr!(stream_name); 114 | let input_stream_params = as_ptr!(input_stream_params); 115 | let output_stream_params = as_ptr!(output_stream_params); 116 | 117 | call!(ffi::cubeb_stream_init( 118 | self.as_ptr(), 119 | &mut stm, 120 | stream_name, 121 | input_device, 122 | input_stream_params, 123 | output_device, 124 | output_stream_params, 125 | latency_frames, 126 | data_callback, 127 | state_callback, 128 | user_ptr 129 | ))?; 130 | Ok(Stream::from_ptr(stm)) 131 | } 132 | 133 | pub fn enumerate_devices(&self, devtype: DeviceType) -> Result> { 134 | let mut coll = ffi::cubeb_device_collection::default(); 135 | unsafe { 136 | call!(ffi::cubeb_enumerate_devices( 137 | self.as_ptr(), 138 | devtype.bits(), 139 | &mut coll 140 | ))?; 141 | } 142 | Ok(DeviceCollection::init_with_ctx(self, coll)) 143 | } 144 | 145 | /// # Safety 146 | /// 147 | /// This function is unsafe because it dereferences the given `callback` and `user_ptr` pointers. 148 | /// The caller should ensure those pointers are valid. 149 | pub unsafe fn register_device_collection_changed( 150 | &self, 151 | devtype: DeviceType, 152 | callback: ffi::cubeb_device_collection_changed_callback, 153 | user_ptr: *mut c_void, 154 | ) -> Result<()> { 155 | call!(ffi::cubeb_register_device_collection_changed( 156 | self.as_ptr(), 157 | devtype.bits(), 158 | callback, 159 | user_ptr 160 | ))?; 161 | 162 | Ok(()) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /cubeb-core/src/channel.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use ffi; 7 | 8 | bitflags! { 9 | /// Some common layout definitions 10 | pub struct ChannelLayout: ffi::cubeb_channel_layout { 11 | const FRONT_LEFT = ffi::CHANNEL_FRONT_LEFT; 12 | const FRONT_RIGHT = ffi::CHANNEL_FRONT_RIGHT; 13 | const FRONT_CENTER = ffi::CHANNEL_FRONT_CENTER; 14 | const LOW_FREQUENCY = ffi::CHANNEL_LOW_FREQUENCY; 15 | const BACK_LEFT = ffi::CHANNEL_BACK_LEFT; 16 | const BACK_RIGHT = ffi::CHANNEL_BACK_RIGHT; 17 | const FRONT_LEFT_OF_CENTER = ffi::CHANNEL_FRONT_LEFT_OF_CENTER; 18 | const FRONT_RIGHT_OF_CENTER = ffi::CHANNEL_FRONT_RIGHT_OF_CENTER; 19 | const BACK_CENTER = ffi::CHANNEL_BACK_CENTER; 20 | const SIDE_LEFT = ffi::CHANNEL_SIDE_LEFT; 21 | const SIDE_RIGHT = ffi::CHANNEL_SIDE_RIGHT; 22 | const TOP_CENTER = ffi::CHANNEL_TOP_CENTER; 23 | const TOP_FRONT_LEFT = ffi::CHANNEL_TOP_FRONT_LEFT; 24 | const TOP_FRONT_CENTER = ffi::CHANNEL_TOP_FRONT_CENTER; 25 | const TOP_FRONT_RIGHT = ffi::CHANNEL_TOP_FRONT_RIGHT; 26 | const TOP_BACK_LEFT = ffi::CHANNEL_TOP_BACK_LEFT; 27 | const TOP_BACK_CENTER = ffi::CHANNEL_TOP_BACK_CENTER; 28 | const TOP_BACK_RIGHT = ffi::CHANNEL_TOP_BACK_RIGHT; 29 | } 30 | } 31 | 32 | macro_rules! bits { 33 | ($($x:ident => $y:ident),*) => { 34 | $(pub const $x: ChannelLayout = ChannelLayout::from_bits_truncate(ffi::$y);)* 35 | } 36 | } 37 | 38 | impl ChannelLayout { 39 | bits!(UNDEFINED => CUBEB_LAYOUT_UNDEFINED, 40 | MONO => CUBEB_LAYOUT_MONO, 41 | MONO_LFE => CUBEB_LAYOUT_MONO_LFE, 42 | STEREO => CUBEB_LAYOUT_STEREO, 43 | STEREO_LFE => CUBEB_LAYOUT_STEREO_LFE, 44 | _3F => CUBEB_LAYOUT_3F, 45 | _3F_LFE => CUBEB_LAYOUT_3F_LFE, 46 | _2F1 => CUBEB_LAYOUT_2F1, 47 | _2F1_LFE => CUBEB_LAYOUT_2F1_LFE, 48 | _3F1 => CUBEB_LAYOUT_3F1, 49 | _3F1_LFE => CUBEB_LAYOUT_3F1_LFE, 50 | _2F2 => CUBEB_LAYOUT_2F2, 51 | _2F2_LFE => CUBEB_LAYOUT_2F2_LFE, 52 | QUAD => CUBEB_LAYOUT_QUAD, 53 | QUAD_LFE => CUBEB_LAYOUT_QUAD_LFE, 54 | _3F2 => CUBEB_LAYOUT_3F2, 55 | _3F2_LFE => CUBEB_LAYOUT_3F2_LFE, 56 | _3F2_BACK => CUBEB_LAYOUT_3F2_BACK, 57 | _3F2_LFE_BACK => CUBEB_LAYOUT_3F2_LFE_BACK, 58 | _3F3R_LFE => CUBEB_LAYOUT_3F3R_LFE, 59 | _3F4_LFE => CUBEB_LAYOUT_3F4_LFE 60 | ); 61 | } 62 | 63 | impl From for ChannelLayout { 64 | fn from(x: ffi::cubeb_channel) -> Self { 65 | ChannelLayout::from_bits_truncate(x) 66 | } 67 | } 68 | 69 | impl From for ffi::cubeb_channel { 70 | fn from(x: ChannelLayout) -> Self { 71 | x.bits() 72 | } 73 | } 74 | 75 | impl ChannelLayout { 76 | pub fn num_channels(&self) -> u32 { 77 | unsafe { ffi::cubeb_channel_layout_nb_channels(self.bits()) } 78 | } 79 | } 80 | 81 | #[cfg(test)] 82 | mod test { 83 | use ffi; 84 | 85 | #[test] 86 | fn channel_layout_from_raw() { 87 | macro_rules! check( 88 | ($($raw:ident => $real:ident),*) => ( 89 | $(let x = super::ChannelLayout::from(ffi::$raw); 90 | assert_eq!(x, super::ChannelLayout::$real); 91 | )* 92 | ) ); 93 | 94 | check!(CUBEB_LAYOUT_UNDEFINED => UNDEFINED, 95 | CUBEB_LAYOUT_MONO => MONO, 96 | CUBEB_LAYOUT_MONO_LFE => MONO_LFE, 97 | CUBEB_LAYOUT_STEREO => STEREO, 98 | CUBEB_LAYOUT_STEREO_LFE => STEREO_LFE, 99 | CUBEB_LAYOUT_3F => _3F, 100 | CUBEB_LAYOUT_3F_LFE => _3F_LFE, 101 | CUBEB_LAYOUT_2F1 => _2F1, 102 | CUBEB_LAYOUT_2F1_LFE => _2F1_LFE, 103 | CUBEB_LAYOUT_3F1 => _3F1, 104 | CUBEB_LAYOUT_3F1_LFE => _3F1_LFE, 105 | CUBEB_LAYOUT_2F2 => _2F2, 106 | CUBEB_LAYOUT_2F2_LFE => _2F2_LFE, 107 | CUBEB_LAYOUT_QUAD => QUAD, 108 | CUBEB_LAYOUT_QUAD_LFE => QUAD_LFE, 109 | CUBEB_LAYOUT_3F2 => _3F2, 110 | CUBEB_LAYOUT_3F2_LFE => _3F2_LFE, 111 | CUBEB_LAYOUT_3F2_BACK => _3F2_BACK, 112 | CUBEB_LAYOUT_3F2_LFE_BACK => _3F2_LFE_BACK, 113 | CUBEB_LAYOUT_3F3R_LFE => _3F3R_LFE, 114 | CUBEB_LAYOUT_3F4_LFE => _3F4_LFE); 115 | } 116 | 117 | #[test] 118 | fn channel_layout_into_raw() { 119 | macro_rules! check( 120 | ($($real:ident => $raw:ident),*) => ( 121 | $(let x = super::ChannelLayout::$real; 122 | let x: ffi::cubeb_channel_layout = x.into(); 123 | assert_eq!(x, ffi::$raw); 124 | )* 125 | ) ); 126 | 127 | check!(UNDEFINED => CUBEB_LAYOUT_UNDEFINED, 128 | MONO => CUBEB_LAYOUT_MONO, 129 | MONO_LFE => CUBEB_LAYOUT_MONO_LFE, 130 | STEREO => CUBEB_LAYOUT_STEREO, 131 | STEREO_LFE => CUBEB_LAYOUT_STEREO_LFE, 132 | _3F => CUBEB_LAYOUT_3F, 133 | _3F_LFE => CUBEB_LAYOUT_3F_LFE, 134 | _2F1 => CUBEB_LAYOUT_2F1, 135 | _2F1_LFE=> CUBEB_LAYOUT_2F1_LFE, 136 | _3F1 => CUBEB_LAYOUT_3F1, 137 | _3F1_LFE => CUBEB_LAYOUT_3F1_LFE, 138 | _2F2 => CUBEB_LAYOUT_2F2, 139 | _2F2_LFE => CUBEB_LAYOUT_2F2_LFE, 140 | QUAD => CUBEB_LAYOUT_QUAD, 141 | QUAD_LFE => CUBEB_LAYOUT_QUAD_LFE, 142 | _3F2 => CUBEB_LAYOUT_3F2, 143 | _3F2_LFE => CUBEB_LAYOUT_3F2_LFE, 144 | _3F2_BACK => CUBEB_LAYOUT_3F2_BACK, 145 | _3F2_LFE_BACK => CUBEB_LAYOUT_3F2_LFE_BACK, 146 | _3F3R_LFE => CUBEB_LAYOUT_3F3R_LFE, 147 | _3F4_LFE => CUBEB_LAYOUT_3F4_LFE); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /cubeb-backend/src/log.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use std::ffi::CStr; 7 | use std::os::raw::c_char; 8 | 9 | /// Maximum length in bytes for a log message. 10 | /// Longer messages are silently truncated. See `write_str`. 11 | const LOG_LIMIT: usize = 1024; 12 | 13 | struct StaticCString { 14 | buf: [std::mem::MaybeUninit; N], 15 | len: usize, 16 | } 17 | 18 | impl StaticCString { 19 | fn new() -> Self { 20 | StaticCString { 21 | buf: unsafe { std::mem::MaybeUninit::uninit().assume_init() }, 22 | len: 0, 23 | } 24 | } 25 | 26 | fn as_cstr(&self) -> &std::ffi::CStr { 27 | unsafe { 28 | std::ffi::CStr::from_bytes_with_nul_unchecked(std::slice::from_raw_parts( 29 | self.buf.as_ptr().cast::(), 30 | self.len, 31 | )) 32 | } 33 | } 34 | } 35 | 36 | impl std::fmt::Write for StaticCString { 37 | fn write_str(&mut self, s: &str) -> std::fmt::Result { 38 | use std::convert::TryInto; 39 | let s = s.as_bytes(); 40 | let end = s.len().min(N.checked_sub(1).unwrap() - self.len); 41 | debug_assert_eq!(s.len(), end, "message truncated"); 42 | unsafe { 43 | std::ptr::copy_nonoverlapping( 44 | s[..end].as_ptr(), 45 | self.buf 46 | .as_mut_ptr() 47 | .cast::() 48 | .offset(self.len.try_into().unwrap()), 49 | end, 50 | ) 51 | }; 52 | self.len += end; 53 | self.buf[self.len].write(0); 54 | Ok(()) 55 | } 56 | } 57 | 58 | /// Formats `$file:line: $msg\n` into an on-stack buffer of size `LOG_LIMIT`, 59 | /// then calls `log_callback` with a pointer to the formatted message. 60 | pub fn cubeb_log_internal_buf_fmt( 61 | log_callback: unsafe extern "C" fn(*const c_char, ...), 62 | file: &str, 63 | line: u32, 64 | msg: std::fmt::Arguments, 65 | ) { 66 | let filename = std::path::Path::new(file) 67 | .file_name() 68 | .unwrap() 69 | .to_str() 70 | .unwrap(); 71 | let mut buf = StaticCString::::new(); 72 | let _ = std::fmt::write(&mut buf, format_args!("{filename}:{line}: {msg}\n")); 73 | unsafe { 74 | // Don't process this as a format string in C 75 | log_callback( 76 | CStr::from_bytes_with_nul_unchecked(b"%s\0").as_ptr(), 77 | buf.as_cstr().as_ptr(), 78 | ); 79 | }; 80 | } 81 | 82 | #[macro_export] 83 | macro_rules! cubeb_log_internal { 84 | ($log_callback: expr, $level: expr, $fmt: expr, $($arg: expr),+) => { 85 | #[allow(unused_unsafe)] 86 | unsafe { 87 | if $level <= $crate::ffi::cubeb_log_get_level().into() { 88 | if let Some(log_callback) = $log_callback { 89 | $crate::log::cubeb_log_internal_buf_fmt(log_callback, file!(), line!(), format_args!($fmt, $($arg),+)); 90 | } 91 | } 92 | } 93 | }; 94 | ($log_callback: expr, $level: expr, $msg: expr) => { 95 | cubeb_log_internal!($log_callback, $level, "{}", format_args!($msg)); 96 | }; 97 | } 98 | 99 | #[macro_export] 100 | macro_rules! cubeb_log { 101 | ($($arg: expr),+) => (cubeb_log_internal!($crate::ffi::cubeb_log_get_callback(), $crate::LogLevel::Normal, $($arg),+)); 102 | } 103 | 104 | #[macro_export] 105 | macro_rules! cubeb_logv { 106 | ($($arg: expr),+) => (cubeb_log_internal!($crate::ffi::cubeb_log_get_callback(), $crate::LogLevel::Verbose, $($arg),+)); 107 | } 108 | 109 | #[macro_export] 110 | macro_rules! cubeb_alog { 111 | ($($arg: expr),+) => (cubeb_log_internal!($crate::ffi::cubeb_async_log.into(), $crate::LogLevel::Normal, $($arg),+)); 112 | } 113 | 114 | #[macro_export] 115 | macro_rules! cubeb_alogv { 116 | ($($arg: expr),+) => (cubeb_log_internal!($crate::ffi::cubeb_async_log.into(), $crate::LogLevel::Verbose, $($arg),+)); 117 | } 118 | 119 | #[cfg(test)] 120 | mod tests { 121 | use crate::{set_logging, LogLevel}; 122 | extern crate regex; 123 | use self::regex::Regex; 124 | use std::sync::RwLock; 125 | 126 | // Ensure that tests that modify the global log callback (e.g. to assert) 127 | // cannot run concurrently with others 128 | static LOG_MODIFIER: RwLock<()> = RwLock::new(()); 129 | 130 | #[test] 131 | fn test_normal_logging_sync() { 132 | let _guard = LOG_MODIFIER.read(); 133 | cubeb_log!("This is synchronous log output at normal level"); 134 | cubeb_log!("{} Formatted log", 1); 135 | cubeb_log!("{} Formatted {} log {}", 1, 2, 3); 136 | } 137 | 138 | #[test] 139 | fn test_normal_logging_sync_format_specififer() { 140 | let _guard = LOG_MODIFIER.write(); 141 | set_logging( 142 | LogLevel::Normal, 143 | Some(|s| { 144 | let s = s.to_str().unwrap().trim(); 145 | println!("{}", s); 146 | let re = Regex::new(r"log.rs:\d+: This has a %s in it").unwrap(); 147 | assert!(re.is_match(s)); 148 | }), 149 | ) 150 | .unwrap(); 151 | cubeb_log!("This has a %s in it"); 152 | set_logging(LogLevel::Disabled, None).unwrap(); 153 | } 154 | 155 | #[test] 156 | fn test_normal_logging_inline() { 157 | let _guard = LOG_MODIFIER.write(); 158 | // log.rs:128: 1 log\n 159 | set_logging( 160 | LogLevel::Normal, 161 | Some(|s| { 162 | let s = s.to_str().unwrap().trim(); 163 | println!("{}", s); 164 | let re = Regex::new(r"log.rs:\d+: 1 log").unwrap(); 165 | assert!(re.is_match(s)); 166 | }), 167 | ) 168 | .unwrap(); 169 | let x = 1; 170 | cubeb_log!("{x} log"); 171 | set_logging(LogLevel::Disabled, None).unwrap(); 172 | } 173 | 174 | #[test] 175 | fn test_verbose_logging_sync() { 176 | let _guard = LOG_MODIFIER.read(); 177 | cubeb_logv!("This is synchronous log output at verbose level"); 178 | cubeb_logv!("{} Formatted log", 1); 179 | cubeb_logv!("{} Formatted {} log {}", 1, 2, 3); 180 | } 181 | 182 | #[test] 183 | fn test_normal_logging_async() { 184 | let _guard = LOG_MODIFIER.read(); 185 | cubeb_alog!("This is asynchronous log output at normal level"); 186 | cubeb_alog!("{} Formatted log", 1); 187 | cubeb_alog!("{} Formatted {} log {}", 1, 2, 3); 188 | } 189 | 190 | #[test] 191 | fn test_verbose_logging_async() { 192 | let _guard = LOG_MODIFIER.read(); 193 | cubeb_alogv!("This is asynchronous log output at verbose level"); 194 | cubeb_alogv!("{} Formatted log", 1); 195 | cubeb_alogv!("{} Formatted {} log {}", 1, 2, 3); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /cubeb-sys/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | #[cfg(not(feature = "gecko-in-tree"))] 7 | extern crate cmake; 8 | #[cfg(not(feature = "gecko-in-tree"))] 9 | extern crate pkg_config; 10 | 11 | use std::env; 12 | use std::fs; 13 | use std::path::Path; 14 | 15 | macro_rules! t { 16 | ($e:expr) => { 17 | match $e { 18 | Ok(e) => e, 19 | Err(e) => panic!("{} failed with {}", stringify!($e), e), 20 | } 21 | }; 22 | } 23 | 24 | #[cfg(feature = "gecko-in-tree")] 25 | fn main() {} 26 | 27 | #[cfg(not(feature = "gecko-in-tree"))] 28 | fn main() { 29 | if env::var("LIBCUBEB_SYS_USE_PKG_CONFIG").is_ok() 30 | && pkg_config::find_library("libcubeb").is_ok() 31 | { 32 | return; 33 | } 34 | 35 | let out_dir = env::var("OUT_DIR").unwrap(); 36 | let _ = fs::remove_dir_all(&out_dir); 37 | 38 | env::remove_var("DESTDIR"); 39 | 40 | // Copy libcubeb to the output directory. 41 | let libcubeb_path = format!("{out_dir}/libcubeb"); 42 | 43 | fn copy_dir_all(src: &Path, dst: &Path) -> std::io::Result<()> { 44 | fs::create_dir_all(dst)?; 45 | for entry in fs::read_dir(src)? { 46 | let entry = entry?; 47 | let file_type = entry.file_type()?; 48 | let child_dst = dst.join(entry.file_name()); 49 | if file_type.is_dir() { 50 | copy_dir_all(&entry.path(), &child_dst)?; 51 | } else { 52 | fs::copy(entry.path(), &child_dst)?; 53 | } 54 | } 55 | Ok(()) 56 | } 57 | 58 | t!(copy_dir_all( 59 | Path::new("libcubeb"), 60 | Path::new(&libcubeb_path) 61 | )); 62 | 63 | fn visit_dirs(dir: &Path, cb: &dyn Fn(&fs::DirEntry)) -> std::io::Result<()> { 64 | if dir.is_dir() { 65 | for entry in fs::read_dir(dir)? { 66 | let entry = entry?; 67 | let path = entry.path(); 68 | if path.is_dir() { 69 | visit_dirs(&path, cb)?; 70 | } else { 71 | cb(&entry); 72 | } 73 | } 74 | } 75 | Ok(()) 76 | } 77 | // For packaged build: rename libcubeb Cargo.toml.in files to Cargo.toml. 78 | t!(visit_dirs(Path::new(&libcubeb_path), &|entry| { 79 | let path = entry.path(); 80 | if path 81 | .file_name() 82 | .unwrap() 83 | .to_str() 84 | .unwrap() 85 | .ends_with("Cargo.toml.in") 86 | { 87 | let new_path = path.with_file_name("Cargo.toml"); 88 | fs::rename(&path, &new_path).unwrap(); 89 | } 90 | })); 91 | 92 | let target = env::var("TARGET").unwrap(); 93 | let windows = target.contains("windows"); 94 | let darwin = target.contains("darwin"); 95 | let freebsd = target.contains("freebsd"); 96 | let android = target.contains("android"); 97 | let mut cfg = cmake::Config::new(&libcubeb_path); 98 | 99 | if darwin { 100 | let cmake_osx_arch = if target.contains("aarch64") { 101 | // Apple Silicon 102 | "arm64" 103 | } else { 104 | // Assuming Intel (x86_64) 105 | "x86_64" 106 | }; 107 | cfg.define("CMAKE_OSX_ARCHITECTURES", cmake_osx_arch); 108 | } 109 | 110 | // Do not build the rust backends for tests: doing so causes duplicate 111 | // symbol definitions. 112 | let build_rust_libs = cfg!(not(feature = "unittest-build")) && env::var("DOCS_RS").is_err(); 113 | let dst = cfg 114 | .define("BUILD_SHARED_LIBS", "OFF") 115 | .define("BUILD_TESTS", "OFF") 116 | .define("BUILD_TOOLS", "OFF") 117 | .define( 118 | "BUILD_RUST_LIBS", 119 | if build_rust_libs { "ON" } else { "OFF" }, 120 | ) 121 | .define("USE_STATIC_MSVC_RUNTIME", "ON") 122 | // Force rust libs to include target triple when outputting, 123 | // for easier linking when cross-compiling. 124 | .env("CARGO_BUILD_TARGET", &target) 125 | .build(); 126 | 127 | let debug = env::var("PROFILE").unwrap() == "debug"; 128 | 129 | println!("cargo:rustc-link-lib=static=cubeb"); 130 | if windows { 131 | println!("cargo:rustc-link-lib=dylib=avrt"); 132 | println!("cargo:rustc-link-lib=dylib=ksuser"); 133 | println!("cargo:rustc-link-lib=dylib=ole32"); 134 | println!("cargo:rustc-link-lib=dylib=user32"); 135 | println!("cargo:rustc-link-lib=dylib=winmm"); 136 | println!("cargo:rustc-link-search=native={}/lib", dst.display()); 137 | if debug { 138 | println!("cargo:rustc-link-lib=msvcrtd"); 139 | } 140 | } else if darwin { 141 | println!("cargo:rustc-link-lib=framework=AudioUnit"); 142 | println!("cargo:rustc-link-lib=framework=CoreAudio"); 143 | println!("cargo:rustc-link-lib=framework=CoreServices"); 144 | println!("cargo:rustc-link-lib=dylib=c++"); 145 | 146 | // Do not link the rust backends for tests: doing so causes duplicate 147 | // symbol definitions. 148 | if build_rust_libs { 149 | println!("cargo:rustc-link-lib=static=cubeb_coreaudio"); 150 | let mut search_path = std::env::current_dir().unwrap(); 151 | search_path.push(&(libcubeb_path + "/src/cubeb-coreaudio-rs/target")); 152 | search_path.push(&target); 153 | if debug { 154 | search_path.push("debug"); 155 | } else { 156 | search_path.push("release"); 157 | } 158 | println!("cargo:rustc-link-search=native={}", search_path.display()); 159 | } 160 | 161 | println!("cargo:rustc-link-search=native={}/lib", dst.display()); 162 | } else { 163 | if freebsd || android { 164 | println!("cargo:rustc-link-lib=dylib=c++"); 165 | } else { 166 | println!("cargo:rustc-link-lib=dylib=stdc++"); 167 | } 168 | println!("cargo:rustc-link-search=native={}/lib", dst.display()); 169 | println!("cargo:rustc-link-search=native={}/lib64", dst.display()); 170 | 171 | // Ignore the result of find_library. We don't care if the 172 | // libraries are missing. 173 | let _ = pkg_config::find_library("alsa"); 174 | if pkg_config::find_library("libpulse").is_ok() { 175 | // Do not link the rust backends for tests: doing so causes duplicate 176 | // symbol definitions. 177 | if build_rust_libs { 178 | println!("cargo:rustc-link-lib=static=cubeb_pulse"); 179 | let mut search_path = std::env::current_dir().unwrap(); 180 | search_path.push(&(libcubeb_path + "/src/cubeb-pulse-rs/target")); 181 | search_path.push(&target); 182 | if debug { 183 | search_path.push("debug"); 184 | } else { 185 | search_path.push("release"); 186 | } 187 | println!("cargo:rustc-link-search=native={}", search_path.display()); 188 | } 189 | } 190 | let _ = pkg_config::find_library("jack"); 191 | let _ = pkg_config::find_library("speexdsp"); 192 | if android { 193 | println!("cargo:rustc-link-lib=dylib=OpenSLES"); 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /cubeb-core/src/device.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use ffi; 7 | use std::str; 8 | use util::opt_bytes; 9 | 10 | /// The state of a device. 11 | #[derive(PartialEq, Eq, Clone, Debug, Copy)] 12 | pub enum DeviceState { 13 | /// The device has been disabled at the system level. 14 | Disabled, 15 | /// The device is enabled, but nothing is plugged into it. 16 | Unplugged, 17 | /// The device is enabled. 18 | Enabled, 19 | } 20 | 21 | bitflags! { 22 | /// Architecture specific sample type. 23 | pub struct DeviceFormat: ffi::cubeb_device_fmt { 24 | const S16LE = ffi::CUBEB_DEVICE_FMT_S16LE; 25 | const S16BE = ffi::CUBEB_DEVICE_FMT_S16BE; 26 | const F32LE = ffi::CUBEB_DEVICE_FMT_F32LE; 27 | const F32BE = ffi::CUBEB_DEVICE_FMT_F32BE; 28 | } 29 | } 30 | 31 | bitflags! { 32 | /// Channel type for a `cubeb_stream`. Depending on the backend and platform 33 | /// used, this can control inter-stream interruption, ducking, and volume 34 | /// control. 35 | pub struct DevicePref: ffi::cubeb_device_pref { 36 | const MULTIMEDIA = ffi::CUBEB_DEVICE_PREF_MULTIMEDIA; 37 | const VOICE = ffi::CUBEB_DEVICE_PREF_VOICE; 38 | const NOTIFICATION = ffi::CUBEB_DEVICE_PREF_NOTIFICATION; 39 | const ALL = ffi::CUBEB_DEVICE_PREF_ALL; 40 | } 41 | } 42 | 43 | impl DevicePref { 44 | pub const NONE: Self = Self::empty(); 45 | } 46 | 47 | bitflags! { 48 | /// Whether a particular device is an input device (e.g. a microphone), or an 49 | /// output device (e.g. headphones). 50 | pub struct DeviceType: ffi::cubeb_device_type { 51 | const INPUT = ffi::CUBEB_DEVICE_TYPE_INPUT as _; 52 | const OUTPUT = ffi::CUBEB_DEVICE_TYPE_OUTPUT as _; 53 | } 54 | } 55 | 56 | impl DeviceType { 57 | pub const UNKNOWN: Self = Self::empty(); 58 | } 59 | 60 | /// An opaque handle used to refer to a particular input or output device 61 | /// across calls. 62 | pub type DeviceId = ffi::cubeb_devid; 63 | 64 | ffi_type_heap! { 65 | /// Audio device description 66 | type CType = ffi::cubeb_device; 67 | #[derive(Debug)] 68 | pub struct Device; 69 | pub struct DeviceRef; 70 | } 71 | 72 | impl DeviceRef { 73 | fn get_ref(&self) -> &ffi::cubeb_device { 74 | unsafe { &*self.as_ptr() } 75 | } 76 | 77 | /// Gets the output device name. 78 | /// 79 | /// May return `None` if there is no output device. 80 | pub fn output_name(&self) -> Option<&str> { 81 | self.output_name_bytes().map(|b| str::from_utf8(b).unwrap()) 82 | } 83 | 84 | pub fn output_name_bytes(&self) -> Option<&[u8]> { 85 | unsafe { opt_bytes(self.get_ref().output_name) } 86 | } 87 | 88 | /// Gets the input device name. 89 | /// 90 | /// May return `None` if there is no input device. 91 | pub fn input_name(&self) -> Option<&str> { 92 | self.input_name_bytes().map(|b| str::from_utf8(b).unwrap()) 93 | } 94 | 95 | pub fn input_name_bytes(&self) -> Option<&[u8]> { 96 | unsafe { opt_bytes(self.get_ref().input_name) } 97 | } 98 | } 99 | 100 | ffi_type_stack! { 101 | /// This structure holds the characteristics of an input or output 102 | /// audio device. It is obtained using `enumerate_devices`, which 103 | /// returns these structures via `device_collection` and must be 104 | /// destroyed via `device_collection_destroy`. 105 | type CType = ffi::cubeb_device_info; 106 | pub struct DeviceInfo; 107 | pub struct DeviceInfoRef; 108 | } 109 | 110 | impl DeviceInfoRef { 111 | fn get_ref(&self) -> &ffi::cubeb_device_info { 112 | unsafe { &*self.as_ptr() } 113 | } 114 | 115 | /// Device identifier handle. 116 | pub fn devid(&self) -> DeviceId { 117 | self.get_ref().devid 118 | } 119 | 120 | /// Device identifier which might be presented in a UI. 121 | pub fn device_id(&self) -> Option<&str> { 122 | self.device_id_bytes().map(|b| str::from_utf8(b).unwrap()) 123 | } 124 | 125 | pub fn device_id_bytes(&self) -> Option<&[u8]> { 126 | unsafe { opt_bytes(self.get_ref().device_id) } 127 | } 128 | 129 | /// Friendly device name which might be presented in a UI. 130 | pub fn friendly_name(&self) -> Option<&str> { 131 | self.friendly_name_bytes() 132 | .map(|b| str::from_utf8(b).unwrap()) 133 | } 134 | 135 | pub fn friendly_name_bytes(&self) -> Option<&[u8]> { 136 | unsafe { opt_bytes(self.get_ref().friendly_name) } 137 | } 138 | 139 | /// Two devices have the same group identifier if they belong to 140 | /// the same physical device; for example a headset and 141 | /// microphone. 142 | pub fn group_id(&self) -> Option<&str> { 143 | self.group_id_bytes().map(|b| str::from_utf8(b).unwrap()) 144 | } 145 | 146 | pub fn group_id_bytes(&self) -> Option<&[u8]> { 147 | unsafe { opt_bytes(self.get_ref().group_id) } 148 | } 149 | 150 | /// Optional vendor name, may be None. 151 | pub fn vendor_name(&self) -> Option<&str> { 152 | self.vendor_name_bytes().map(|b| str::from_utf8(b).unwrap()) 153 | } 154 | 155 | pub fn vendor_name_bytes(&self) -> Option<&[u8]> { 156 | unsafe { opt_bytes(self.get_ref().vendor_name) } 157 | } 158 | 159 | /// Type of device (Input/Output). 160 | pub fn device_type(&self) -> DeviceType { 161 | DeviceType::from_bits_truncate(self.get_ref().device_type) 162 | } 163 | 164 | /// State of device disabled/enabled/unplugged. 165 | pub fn state(&self) -> DeviceState { 166 | let state = self.get_ref().state; 167 | macro_rules! check( ($($raw:ident => $real:ident),*) => ( 168 | $(if state == ffi::$raw { 169 | DeviceState::$real 170 | }) else * 171 | else { 172 | panic!("unknown device state: {}", state) 173 | } 174 | )); 175 | 176 | check!(CUBEB_DEVICE_STATE_DISABLED => Disabled, 177 | CUBEB_DEVICE_STATE_UNPLUGGED => Unplugged, 178 | CUBEB_DEVICE_STATE_ENABLED => Enabled) 179 | } 180 | 181 | /// Preferred device. 182 | pub fn preferred(&self) -> DevicePref { 183 | DevicePref::from_bits(self.get_ref().preferred).unwrap() 184 | } 185 | 186 | /// Sample format supported. 187 | pub fn format(&self) -> DeviceFormat { 188 | DeviceFormat::from_bits(self.get_ref().format).unwrap() 189 | } 190 | 191 | /// The default sample format for this device. 192 | pub fn default_format(&self) -> DeviceFormat { 193 | DeviceFormat::from_bits(self.get_ref().default_format).unwrap() 194 | } 195 | 196 | /// Channels. 197 | pub fn max_channels(&self) -> u32 { 198 | self.get_ref().max_channels 199 | } 200 | 201 | /// Default/Preferred sample rate. 202 | pub fn default_rate(&self) -> u32 { 203 | self.get_ref().default_rate 204 | } 205 | 206 | /// Maximum sample rate supported. 207 | pub fn max_rate(&self) -> u32 { 208 | self.get_ref().max_rate 209 | } 210 | 211 | /// Minimum sample rate supported. 212 | pub fn min_rate(&self) -> u32 { 213 | self.get_ref().min_rate 214 | } 215 | 216 | /// Lowest possible latency in frames. 217 | pub fn latency_lo(&self) -> u32 { 218 | self.get_ref().latency_lo 219 | } 220 | 221 | /// Higest possible latency in frames. 222 | pub fn latency_hi(&self) -> u32 { 223 | self.get_ref().latency_hi 224 | } 225 | } 226 | 227 | #[cfg(test)] 228 | mod tests { 229 | use ffi::cubeb_device; 230 | use Device; 231 | 232 | #[test] 233 | fn device_device_ref_same_ptr() { 234 | let ptr: *mut cubeb_device = 0xDEAD_BEEF as *mut _; 235 | let device = unsafe { Device::from_ptr(ptr) }; 236 | assert_eq!(device.as_ptr(), ptr); 237 | assert_eq!(device.as_ptr(), device.as_ref().as_ptr()); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /cubeb-core/src/ffi_types.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use std::cell::UnsafeCell; 7 | pub struct Opaque(UnsafeCell<()>); 8 | 9 | /// Generate a newtype wrapper `$owned` and reference wrapper 10 | /// `$borrowed` around a POD FFI type that lives on the heap. 11 | macro_rules! ffi_type_heap { 12 | ( 13 | $(#[$impl_attr:meta])* 14 | type CType = $ctype:ty; 15 | $(fn drop = $drop:expr;)* 16 | $(fn clone = $clone:expr;)* 17 | $(#[$owned_attr:meta])* 18 | pub struct $owned:ident; 19 | $(#[$borrowed_attr:meta])* 20 | pub struct $borrowed:ident; 21 | ) => { 22 | $(#[$owned_attr])* 23 | pub struct $owned(*mut $ctype); 24 | 25 | impl $owned { 26 | /// # Safety 27 | /// 28 | /// This function is unsafe because it dereferences the given `ptr` pointer. 29 | /// The caller should ensure that pointer is valid. 30 | #[inline] 31 | pub unsafe fn from_ptr(ptr: *mut $ctype) -> $owned { 32 | $owned(ptr) 33 | } 34 | 35 | #[inline] 36 | pub fn as_ptr(&self) -> *mut $ctype { 37 | self.0 38 | } 39 | } 40 | 41 | $( 42 | impl Drop for $owned { 43 | #[inline] 44 | fn drop(&mut self) { 45 | unsafe { $drop(self.0) } 46 | } 47 | } 48 | )* 49 | 50 | $( 51 | impl Clone for $owned { 52 | #[inline] 53 | fn clone(&self) -> $owned { 54 | unsafe { 55 | let handle: *mut $ctype = $clone(self.0); 56 | $owned::from_ptr(handle) 57 | } 58 | } 59 | } 60 | 61 | impl ::std::borrow::ToOwned for $borrowed { 62 | type Owned = $owned; 63 | #[inline] 64 | fn to_owned(&self) -> $owned { 65 | unsafe { 66 | let handle: *mut $ctype = $clone(self.as_ptr()); 67 | $owned::from_ptr(handle) 68 | } 69 | } 70 | } 71 | )* 72 | 73 | impl ::std::ops::Deref for $owned { 74 | type Target = $borrowed; 75 | 76 | #[inline] 77 | fn deref(&self) -> &$borrowed { 78 | unsafe { $borrowed::from_ptr(self.0) } 79 | } 80 | } 81 | 82 | impl ::std::ops::DerefMut for $owned { 83 | #[inline] 84 | fn deref_mut(&mut self) -> &mut $borrowed { 85 | unsafe { $borrowed::from_ptr_mut(self.0) } 86 | } 87 | } 88 | 89 | impl ::std::borrow::Borrow<$borrowed> for $owned { 90 | #[inline] 91 | fn borrow(&self) -> &$borrowed { 92 | &**self 93 | } 94 | } 95 | 96 | impl ::std::convert::AsRef<$borrowed> for $owned { 97 | #[inline] 98 | fn as_ref(&self) -> &$borrowed { 99 | &**self 100 | } 101 | } 102 | 103 | $(#[$borrowed_attr])* 104 | pub struct $borrowed($crate::ffi_types::Opaque); 105 | 106 | impl $borrowed { 107 | /// # Safety 108 | /// 109 | /// This function is unsafe because it dereferences the given `ptr` pointer. 110 | /// The caller should ensure that pointer is valid. 111 | #[inline] 112 | pub unsafe fn from_ptr<'a>(ptr: *mut $ctype) -> &'a Self { 113 | &*(ptr as *mut _) 114 | } 115 | 116 | /// # Safety 117 | /// 118 | /// This function is unsafe because it dereferences the given `ptr` pointer. 119 | /// The caller should ensure that pointer is valid. 120 | #[inline] 121 | pub unsafe fn from_ptr_mut<'a>(ptr: *mut $ctype) -> &'a mut Self { 122 | &mut *(ptr as *mut _) 123 | } 124 | 125 | #[inline] 126 | pub fn as_ptr(&self) -> *mut $ctype { 127 | self as *const _ as *mut _ 128 | } 129 | } 130 | 131 | impl ::std::fmt::Debug for $borrowed { 132 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 133 | let ptr = self as *const $borrowed as usize; 134 | f.debug_tuple(stringify!($borrowed)) 135 | .field(&ptr) 136 | .finish() 137 | } 138 | } 139 | } 140 | } 141 | 142 | /// Generate a newtype wrapper `$owned` and reference wrapper 143 | /// `$borrowed` around a POD FFI type that lives on the stack. 144 | macro_rules! ffi_type_stack { 145 | ($(#[$impl_attr:meta])* 146 | type CType = $ctype:ty; 147 | $(#[$owned_attr:meta])* 148 | pub struct $owned:ident; 149 | $(#[$borrowed_attr:meta])* 150 | pub struct $borrowed:ident; 151 | ) => { 152 | $(#[$owned_attr])* 153 | pub struct $owned($ctype); 154 | 155 | impl $owned { 156 | pub fn as_ptr(&self) -> *mut $ctype { 157 | &self.0 as *const $ctype as *mut $ctype 158 | } 159 | } 160 | 161 | impl Default for $owned { 162 | fn default() -> $owned { 163 | $owned(Default::default()) 164 | } 165 | } 166 | 167 | impl From<$ctype> for $owned { 168 | fn from(x: $ctype) -> $owned { 169 | $owned(x) 170 | } 171 | } 172 | 173 | impl From<$owned> for $ctype { 174 | fn from(x: $owned) -> $ctype { 175 | x.0 176 | } 177 | } 178 | 179 | impl ::std::ops::Deref for $owned { 180 | type Target = $borrowed; 181 | 182 | #[inline] 183 | fn deref(&self) -> &$borrowed { 184 | let ptr = &self.0 as *const $ctype as *mut $ctype; 185 | unsafe { $borrowed::from_ptr(ptr) } 186 | } 187 | } 188 | 189 | impl ::std::ops::DerefMut for $owned { 190 | #[inline] 191 | fn deref_mut(&mut self) -> &mut $borrowed { 192 | let ptr = &self.0 as *const $ctype as *mut $ctype; 193 | unsafe { $borrowed::from_ptr_mut(ptr) } 194 | } 195 | } 196 | 197 | impl ::std::borrow::Borrow<$borrowed> for $owned { 198 | #[inline] 199 | fn borrow(&self) -> &$borrowed { 200 | &**self 201 | } 202 | } 203 | 204 | impl ::std::convert::AsRef<$borrowed> for $owned { 205 | #[inline] 206 | fn as_ref(&self) -> &$borrowed { 207 | &**self 208 | } 209 | } 210 | 211 | $(#[$borrowed_attr])* 212 | pub struct $borrowed($crate::ffi_types::Opaque); 213 | 214 | impl $borrowed { 215 | /// # Safety 216 | /// 217 | /// This function is unsafe because it dereferences the given `ptr` pointer. 218 | /// The caller should ensure that pointer is valid. 219 | #[inline] 220 | pub unsafe fn from_ptr<'a>(ptr: *mut $ctype) -> &'a Self { 221 | &*(ptr as *mut _) 222 | } 223 | 224 | /// # Safety 225 | /// 226 | /// This function is unsafe because it dereferences the given `ptr` pointer. 227 | /// The caller should ensure that pointer is valid. 228 | #[inline] 229 | pub unsafe fn from_ptr_mut<'a>(ptr: *mut $ctype) -> &'a mut Self { 230 | &mut *(ptr as *mut _) 231 | } 232 | 233 | #[inline] 234 | pub fn as_ptr(&self) -> *mut $ctype { 235 | self as *const _ as *mut _ 236 | } 237 | } 238 | 239 | impl ::std::fmt::Debug for $borrowed { 240 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 241 | let ptr = self as *const $borrowed as usize; 242 | f.debug_tuple(stringify!($borrowed)) 243 | .field(&ptr) 244 | .finish() 245 | } 246 | } 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /cubeb-sys/src/device.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use callbacks::cubeb_device_collection_changed_callback; 7 | use context::cubeb; 8 | use std::ffi::CStr; 9 | use std::os::raw::{c_char, c_int, c_uint, c_void}; 10 | use std::{fmt, mem, ptr}; 11 | 12 | cubeb_enum! { 13 | pub enum cubeb_device_fmt { 14 | CUBEB_DEVICE_FMT_S16LE = 0x0010, 15 | CUBEB_DEVICE_FMT_S16BE = 0x0020, 16 | CUBEB_DEVICE_FMT_F32LE = 0x1000, 17 | CUBEB_DEVICE_FMT_F32BE = 0x2000, 18 | } 19 | } 20 | 21 | #[cfg(target_endian = "big")] 22 | pub const CUBEB_DEVICE_FMT_S16NE: cubeb_device_fmt = CUBEB_DEVICE_FMT_S16BE; 23 | #[cfg(target_endian = "big")] 24 | pub const CUBEB_DEVICE_FMT_F32NE: cubeb_device_fmt = CUBEB_DEVICE_FMT_F32BE; 25 | #[cfg(target_endian = "little")] 26 | pub const CUBEB_DEVICE_FMT_S16NE: cubeb_device_fmt = CUBEB_DEVICE_FMT_S16LE; 27 | #[cfg(target_endian = "little")] 28 | pub const CUBEB_DEVICE_FMT_F32NE: cubeb_device_fmt = CUBEB_DEVICE_FMT_F32LE; 29 | 30 | pub const CUBEB_DEVICE_FMT_S16_MASK: cubeb_device_fmt = 31 | CUBEB_DEVICE_FMT_S16LE | CUBEB_DEVICE_FMT_S16BE; 32 | pub const CUBEB_DEVICE_FMT_F32_MASK: cubeb_device_fmt = 33 | CUBEB_DEVICE_FMT_F32LE | CUBEB_DEVICE_FMT_F32BE; 34 | pub const CUBEB_DEVICE_FMT_ALL: cubeb_device_fmt = 35 | CUBEB_DEVICE_FMT_S16_MASK | CUBEB_DEVICE_FMT_F32_MASK; 36 | 37 | fn fmt_device_fmt(f: &cubeb_device_fmt) -> &'static str { 38 | match *f { 39 | CUBEB_DEVICE_FMT_S16LE => "S16LE", 40 | CUBEB_DEVICE_FMT_S16BE => "S16BE", 41 | CUBEB_DEVICE_FMT_F32LE => "F32LE", 42 | CUBEB_DEVICE_FMT_F32BE => "F32BE", 43 | CUBEB_DEVICE_FMT_S16_MASK => "S16LE | S16BE", 44 | CUBEB_DEVICE_FMT_F32_MASK => "F32LE | F32BE", 45 | CUBEB_DEVICE_FMT_ALL => "S16LE | S16BE | F32LE | F32BE", 46 | _ => "Unexpected device format", 47 | } 48 | } 49 | 50 | cubeb_enum! { 51 | pub enum cubeb_device_pref { 52 | CUBEB_DEVICE_PREF_NONE = 0x00, 53 | CUBEB_DEVICE_PREF_MULTIMEDIA = 0x01, 54 | CUBEB_DEVICE_PREF_VOICE = 0x02, 55 | CUBEB_DEVICE_PREF_NOTIFICATION = 0x04, 56 | CUBEB_DEVICE_PREF_ALL = 0x0F, 57 | } 58 | } 59 | 60 | fn fmt_device_pref(p: &cubeb_device_pref) -> &'static str { 61 | match *p { 62 | CUBEB_DEVICE_PREF_NONE => "None", 63 | CUBEB_DEVICE_PREF_MULTIMEDIA => "Multimedia", 64 | CUBEB_DEVICE_PREF_VOICE => "Voice", 65 | CUBEB_DEVICE_PREF_NOTIFICATION => "Notification", 66 | CUBEB_DEVICE_PREF_ALL => "All", 67 | _ => "Unexpected", 68 | } 69 | } 70 | 71 | cubeb_enum! { 72 | pub enum cubeb_device_state { 73 | CUBEB_DEVICE_STATE_DISABLED, 74 | CUBEB_DEVICE_STATE_UNPLUGGED, 75 | CUBEB_DEVICE_STATE_ENABLED, 76 | } 77 | } 78 | 79 | fn fmt_device_state(s: &cubeb_device_state) -> &'static str { 80 | match *s { 81 | CUBEB_DEVICE_STATE_DISABLED => "Disabled", 82 | CUBEB_DEVICE_STATE_UNPLUGGED => "Unplugged", 83 | CUBEB_DEVICE_STATE_ENABLED => "Enabled", 84 | _ => "Unexpected", 85 | } 86 | } 87 | 88 | cubeb_enum! { 89 | pub enum cubeb_device_type { 90 | CUBEB_DEVICE_TYPE_UNKNOWN, 91 | CUBEB_DEVICE_TYPE_INPUT, 92 | CUBEB_DEVICE_TYPE_OUTPUT, 93 | } 94 | } 95 | 96 | fn fmt_device_type(t: &cubeb_device_type) -> &'static str { 97 | match *t { 98 | CUBEB_DEVICE_TYPE_UNKNOWN => "Unknown", 99 | CUBEB_DEVICE_TYPE_INPUT => "Input", 100 | CUBEB_DEVICE_TYPE_OUTPUT => "Output", 101 | t if t == CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT => "Input+Output", 102 | _ => "Unexpected", 103 | } 104 | } 105 | 106 | pub type cubeb_devid = *const c_void; 107 | 108 | #[repr(C)] 109 | pub struct cubeb_device { 110 | pub output_name: *mut c_char, 111 | pub input_name: *mut c_char, 112 | } 113 | 114 | // Explicit Debug impl to work around bug in ctest 115 | impl Default for cubeb_device { 116 | fn default() -> Self { 117 | unsafe { mem::zeroed() } 118 | } 119 | } 120 | 121 | impl fmt::Debug for cubeb_device { 122 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 123 | f.debug_struct("cubeb_device") 124 | .field("output_name", &self.output_name) 125 | .field("input_name", &self.input_name) 126 | .finish() 127 | } 128 | } 129 | 130 | #[repr(C)] 131 | pub struct cubeb_device_collection { 132 | pub device: *mut cubeb_device_info, 133 | pub count: usize, 134 | } 135 | 136 | impl Default for cubeb_device_collection { 137 | fn default() -> Self { 138 | unsafe { mem::zeroed() } 139 | } 140 | } 141 | 142 | impl fmt::Debug for cubeb_device_collection { 143 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 144 | let devices = ptr::slice_from_raw_parts(self.device, self.count); 145 | let devices = unsafe { &*devices }; 146 | let mut dbg = f.debug_list(); 147 | for d in devices { 148 | dbg.entry(d); 149 | } 150 | dbg.finish() 151 | } 152 | } 153 | 154 | #[repr(C)] 155 | pub struct cubeb_device_info { 156 | pub devid: cubeb_devid, 157 | pub device_id: *const c_char, 158 | pub friendly_name: *const c_char, 159 | pub group_id: *const c_char, 160 | pub vendor_name: *const c_char, 161 | 162 | pub device_type: cubeb_device_type, 163 | pub state: cubeb_device_state, 164 | pub preferred: cubeb_device_pref, 165 | 166 | pub format: cubeb_device_fmt, 167 | pub default_format: cubeb_device_fmt, 168 | pub max_channels: c_uint, 169 | pub default_rate: c_uint, 170 | pub max_rate: c_uint, 171 | pub min_rate: c_uint, 172 | 173 | pub latency_lo: c_uint, 174 | pub latency_hi: c_uint, 175 | } 176 | 177 | impl Default for cubeb_device_info { 178 | fn default() -> Self { 179 | unsafe { mem::zeroed() } 180 | } 181 | } 182 | 183 | impl fmt::Debug for cubeb_device_info { 184 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 185 | fn optional_c_str(c_str: *const c_char) -> Option<*const c_char> { 186 | (unsafe { c_str.as_ref() }).map(ptr::from_ref) 187 | } 188 | f.debug_struct("cubeb_device_info") 189 | .field("devid", &(self.devid as u32)) 190 | .field( 191 | "device_id", 192 | &optional_c_str(self.device_id).map(|p| unsafe { CStr::from_ptr(p) }), 193 | ) 194 | .field( 195 | "friendly_name", 196 | &optional_c_str(self.friendly_name).map(|p| unsafe { CStr::from_ptr(p) }), 197 | ) 198 | .field( 199 | "group_id", 200 | &optional_c_str(self.group_id).map(|p| unsafe { CStr::from_ptr(p) }), 201 | ) 202 | .field( 203 | "vendor_name", 204 | &optional_c_str(self.vendor_name).map(|p| unsafe { CStr::from_ptr(p) }), 205 | ) 206 | .field("device_type", &fmt_device_type(&self.device_type)) 207 | .field("state", &fmt_device_state(&self.state)) 208 | .field("preferred", &fmt_device_pref(&self.preferred)) 209 | .field("format", &fmt_device_fmt(&self.format)) 210 | .field("default_format", &fmt_device_fmt(&self.default_format)) 211 | .field("max_channels", &self.max_channels) 212 | .field("default_rate", &self.default_rate) 213 | .field("max_rate", &self.max_rate) 214 | .field("min_rate", &self.min_rate) 215 | .field("latency_lo", &self.latency_lo) 216 | .field("latency_hi", &self.latency_hi) 217 | .finish() 218 | } 219 | } 220 | 221 | extern "C" { 222 | pub fn cubeb_enumerate_devices( 223 | context: *mut cubeb, 224 | devtype: cubeb_device_type, 225 | collection: *mut cubeb_device_collection, 226 | ) -> c_int; 227 | pub fn cubeb_device_collection_destroy( 228 | context: *mut cubeb, 229 | collection: *mut cubeb_device_collection, 230 | ) -> c_int; 231 | pub fn cubeb_register_device_collection_changed( 232 | context: *mut cubeb, 233 | devtype: cubeb_device_type, 234 | callback: cubeb_device_collection_changed_callback, 235 | user_ptr: *mut c_void, 236 | ) -> c_int; 237 | } 238 | -------------------------------------------------------------------------------- /cubeb-core/src/builders.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use ffi; 7 | use {ChannelLayout, InputProcessingParams, SampleFormat, StreamParams, StreamPrefs}; 8 | 9 | #[derive(Debug)] 10 | pub struct StreamParamsBuilder(ffi::cubeb_stream_params); 11 | 12 | impl Default for StreamParamsBuilder { 13 | fn default() -> Self { 14 | StreamParamsBuilder(ffi::cubeb_stream_params { 15 | format: ffi::CUBEB_SAMPLE_S16NE, 16 | ..Default::default() 17 | }) 18 | } 19 | } 20 | 21 | impl StreamParamsBuilder { 22 | pub fn new() -> Self { 23 | Default::default() 24 | } 25 | 26 | pub fn format(mut self, format: SampleFormat) -> Self { 27 | self.0.format = format.into(); 28 | self 29 | } 30 | 31 | pub fn rate(mut self, rate: u32) -> Self { 32 | self.0.rate = rate; 33 | self 34 | } 35 | 36 | pub fn channels(mut self, channels: u32) -> Self { 37 | self.0.channels = channels; 38 | self 39 | } 40 | 41 | pub fn layout(mut self, layout: ChannelLayout) -> Self { 42 | self.0.layout = layout.into(); 43 | self 44 | } 45 | 46 | pub fn prefs(mut self, prefs: StreamPrefs) -> Self { 47 | self.0.prefs = prefs.bits(); 48 | self 49 | } 50 | 51 | pub fn input_params(mut self, input_params: InputProcessingParams) -> Self { 52 | self.0.input_params = input_params.bits(); 53 | self 54 | } 55 | 56 | pub fn take(&self) -> StreamParams { 57 | StreamParams::from(self.0) 58 | } 59 | } 60 | 61 | #[cfg(test)] 62 | mod tests { 63 | use SampleFormat; 64 | use {ffi, InputProcessingParams, StreamParamsBuilder, StreamPrefs}; 65 | 66 | #[test] 67 | fn stream_params_builder_channels() { 68 | let params = StreamParamsBuilder::new().channels(2).take(); 69 | assert_eq!(params.channels(), 2); 70 | } 71 | 72 | #[test] 73 | fn stream_params_builder_format() { 74 | macro_rules! check( 75 | ($($real:ident),*) => ( 76 | $(let params = StreamParamsBuilder::new() 77 | .format(super::SampleFormat::$real) 78 | .take(); 79 | assert_eq!(params.format(), super::SampleFormat::$real); 80 | )* 81 | ) ); 82 | 83 | check!(S16LE, S16BE, Float32LE, Float32BE); 84 | } 85 | 86 | #[test] 87 | fn stream_params_builder_format_native_endian() { 88 | let params = StreamParamsBuilder::new() 89 | .format(SampleFormat::S16NE) 90 | .take(); 91 | assert_eq!( 92 | params.format(), 93 | if cfg!(target_endian = "little") { 94 | super::SampleFormat::S16LE 95 | } else { 96 | super::SampleFormat::S16BE 97 | } 98 | ); 99 | 100 | let params = StreamParamsBuilder::new() 101 | .format(SampleFormat::Float32NE) 102 | .take(); 103 | assert_eq!( 104 | params.format(), 105 | if cfg!(target_endian = "little") { 106 | SampleFormat::Float32LE 107 | } else { 108 | SampleFormat::Float32BE 109 | } 110 | ); 111 | } 112 | 113 | #[test] 114 | fn stream_params_builder_layout() { 115 | macro_rules! check( 116 | ($($real:ident),*) => ( 117 | $(let params = StreamParamsBuilder::new() 118 | .layout(super::ChannelLayout::$real) 119 | .take(); 120 | assert_eq!(params.layout(), super::ChannelLayout::$real); 121 | )* 122 | ) ); 123 | 124 | check!( 125 | UNDEFINED, 126 | MONO, 127 | MONO_LFE, 128 | STEREO, 129 | STEREO_LFE, 130 | _3F, 131 | _3F_LFE, 132 | _2F1, 133 | _2F1_LFE, 134 | _3F1, 135 | _3F1_LFE, 136 | _2F2, 137 | _2F2_LFE, 138 | QUAD, 139 | QUAD_LFE, 140 | _3F2, 141 | _3F2_LFE, 142 | _3F2_BACK, 143 | _3F2_LFE_BACK, 144 | _3F3R_LFE, 145 | _3F4_LFE 146 | ); 147 | } 148 | 149 | #[test] 150 | fn stream_params_builder_rate() { 151 | let params = StreamParamsBuilder::new().rate(44100).take(); 152 | assert_eq!(params.rate(), 44100); 153 | } 154 | 155 | #[test] 156 | fn stream_params_builder_to_raw_channels() { 157 | let params = StreamParamsBuilder::new().channels(2).take(); 158 | let raw = unsafe { &*params.as_ptr() }; 159 | assert_eq!(raw.channels, 2); 160 | } 161 | 162 | #[test] 163 | fn stream_params_builder_to_raw_format() { 164 | macro_rules! check( 165 | ($($real:ident => $raw:ident),*) => ( 166 | $(let params = super::StreamParamsBuilder::new() 167 | .format(SampleFormat::$real) 168 | .take(); 169 | let raw = unsafe { &*params.as_ptr() }; 170 | assert_eq!(raw.format, ffi::$raw); 171 | )* 172 | ) ); 173 | 174 | check!(S16LE => CUBEB_SAMPLE_S16LE, 175 | S16BE => CUBEB_SAMPLE_S16BE, 176 | Float32LE => CUBEB_SAMPLE_FLOAT32LE, 177 | Float32BE => CUBEB_SAMPLE_FLOAT32BE); 178 | } 179 | 180 | #[test] 181 | fn stream_params_builder_format_to_raw_native_endian() { 182 | let params = StreamParamsBuilder::new() 183 | .format(SampleFormat::S16NE) 184 | .take(); 185 | let raw = unsafe { &*params.as_ptr() }; 186 | assert_eq!( 187 | raw.format, 188 | if cfg!(target_endian = "little") { 189 | ffi::CUBEB_SAMPLE_S16LE 190 | } else { 191 | ffi::CUBEB_SAMPLE_S16BE 192 | } 193 | ); 194 | 195 | let params = StreamParamsBuilder::new() 196 | .format(SampleFormat::Float32NE) 197 | .take(); 198 | let raw = unsafe { &*params.as_ptr() }; 199 | assert_eq!( 200 | raw.format, 201 | if cfg!(target_endian = "little") { 202 | ffi::CUBEB_SAMPLE_FLOAT32LE 203 | } else { 204 | ffi::CUBEB_SAMPLE_FLOAT32BE 205 | } 206 | ); 207 | } 208 | 209 | #[test] 210 | fn stream_params_builder_to_raw_layout() { 211 | macro_rules! check( 212 | ($($real:ident => $raw:ident),*) => ( 213 | $(let params = super::StreamParamsBuilder::new() 214 | .layout(super::ChannelLayout::$real) 215 | .take(); 216 | let raw = unsafe { &*params.as_ptr() }; 217 | assert_eq!(raw.layout, ffi::$raw); 218 | )* 219 | ) ); 220 | 221 | check!(UNDEFINED => CUBEB_LAYOUT_UNDEFINED, 222 | MONO => CUBEB_LAYOUT_MONO, 223 | MONO_LFE => CUBEB_LAYOUT_MONO_LFE, 224 | STEREO => CUBEB_LAYOUT_STEREO, 225 | STEREO_LFE => CUBEB_LAYOUT_STEREO_LFE, 226 | _3F => CUBEB_LAYOUT_3F, 227 | _3F_LFE => CUBEB_LAYOUT_3F_LFE, 228 | _2F1 => CUBEB_LAYOUT_2F1, 229 | _2F1_LFE=> CUBEB_LAYOUT_2F1_LFE, 230 | _3F1 => CUBEB_LAYOUT_3F1, 231 | _3F1_LFE => CUBEB_LAYOUT_3F1_LFE, 232 | _2F2 => CUBEB_LAYOUT_2F2, 233 | _2F2_LFE => CUBEB_LAYOUT_2F2_LFE, 234 | QUAD => CUBEB_LAYOUT_QUAD, 235 | QUAD_LFE => CUBEB_LAYOUT_QUAD_LFE, 236 | _3F2 => CUBEB_LAYOUT_3F2, 237 | _3F2_LFE => CUBEB_LAYOUT_3F2_LFE, 238 | _3F2_BACK => CUBEB_LAYOUT_3F2_BACK, 239 | _3F2_LFE_BACK => CUBEB_LAYOUT_3F2_LFE_BACK, 240 | _3F3R_LFE => CUBEB_LAYOUT_3F3R_LFE, 241 | _3F4_LFE => CUBEB_LAYOUT_3F4_LFE); 242 | } 243 | 244 | #[test] 245 | fn stream_params_builder_to_raw_rate() { 246 | let params = StreamParamsBuilder::new().rate(44100).take(); 247 | let raw = unsafe { &*params.as_ptr() }; 248 | assert_eq!(raw.rate, 44100); 249 | } 250 | 251 | #[test] 252 | fn stream_params_builder_prefs_default() { 253 | let params = StreamParamsBuilder::new().take(); 254 | assert_eq!(params.prefs(), StreamPrefs::NONE); 255 | } 256 | 257 | #[test] 258 | fn stream_params_builder_prefs() { 259 | let params = StreamParamsBuilder::new() 260 | .prefs(StreamPrefs::LOOPBACK) 261 | .take(); 262 | assert_eq!(params.prefs(), StreamPrefs::LOOPBACK); 263 | } 264 | 265 | #[test] 266 | fn stream_params_builder_input_params() { 267 | let params = StreamParamsBuilder::new() 268 | .input_params(InputProcessingParams::NOISE_SUPPRESSION) 269 | .take(); 270 | assert_eq!( 271 | params.input_params(), 272 | InputProcessingParams::NOISE_SUPPRESSION 273 | ); 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /cubeb-backend/tests/test_capi.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details 5 | 6 | #![allow(clippy::float_cmp)] 7 | 8 | #[macro_use] 9 | extern crate cubeb_backend; 10 | 11 | use cubeb_backend::{ 12 | ffi, ContextOps, DeviceId, DeviceInfo, DeviceRef, DeviceType, InputProcessingParams, Ops, 13 | Result, Stream, StreamOps, StreamParams, StreamParamsRef, 14 | }; 15 | use std::ffi::CStr; 16 | use std::mem::ManuallyDrop; 17 | use std::os::raw::c_void; 18 | use std::ptr; 19 | use std::sync::OnceLock; 20 | 21 | pub const OPS: Ops = capi_new!(TestContext, TestStream); 22 | 23 | struct TestContext { 24 | #[allow(dead_code)] 25 | pub ops: *const Ops, 26 | } 27 | 28 | impl ContextOps for TestContext { 29 | fn init(_context_name: Option<&CStr>) -> Result> { 30 | Ok(Box::new(TestContext { 31 | ops: &OPS as *const _, 32 | })) 33 | } 34 | 35 | fn backend_id(&mut self) -> &'static CStr { 36 | unsafe { CStr::from_ptr(b"remote\0".as_ptr() as *const _) } 37 | } 38 | fn max_channel_count(&mut self) -> Result { 39 | Ok(0u32) 40 | } 41 | fn min_latency(&mut self, _params: StreamParams) -> Result { 42 | Ok(0u32) 43 | } 44 | fn preferred_sample_rate(&mut self) -> Result { 45 | Ok(0u32) 46 | } 47 | fn supported_input_processing_params(&mut self) -> Result { 48 | Ok(InputProcessingParams::NONE) 49 | } 50 | fn enumerate_devices(&mut self, _devtype: DeviceType) -> Result> { 51 | Ok(vec![DeviceInfo::default()].into_boxed_slice()) 52 | } 53 | fn device_collection_destroy(&mut self, collection: Box<[DeviceInfo]>) -> Result<()> { 54 | assert_eq!(collection.len(), 1); 55 | assert_ne!(collection[0].as_ptr(), std::ptr::null_mut()); 56 | Ok(()) 57 | } 58 | fn stream_init( 59 | &mut self, 60 | _stream_name: Option<&CStr>, 61 | _input_device: DeviceId, 62 | _input_stream_params: Option<&StreamParamsRef>, 63 | _output_device: DeviceId, 64 | _output_stream_params: Option<&StreamParamsRef>, 65 | _latency_frame: u32, 66 | _data_callback: ffi::cubeb_data_callback, 67 | _state_callback: ffi::cubeb_state_callback, 68 | _user_ptr: *mut c_void, 69 | ) -> Result { 70 | Ok(unsafe { Stream::from_ptr(0xDEAD_BEEF as *mut _) }) 71 | } 72 | fn register_device_collection_changed( 73 | &mut self, 74 | _dev_type: DeviceType, 75 | _collection_changed_callback: ffi::cubeb_device_collection_changed_callback, 76 | _user_ptr: *mut c_void, 77 | ) -> Result<()> { 78 | Ok(()) 79 | } 80 | } 81 | 82 | struct TestStream {} 83 | 84 | impl StreamOps for TestStream { 85 | fn start(&mut self) -> Result<()> { 86 | Ok(()) 87 | } 88 | fn stop(&mut self) -> Result<()> { 89 | Ok(()) 90 | } 91 | fn position(&mut self) -> Result { 92 | Ok(0u64) 93 | } 94 | fn latency(&mut self) -> Result { 95 | Ok(0u32) 96 | } 97 | fn input_latency(&mut self) -> Result { 98 | Ok(0u32) 99 | } 100 | fn set_volume(&mut self, volume: f32) -> Result<()> { 101 | assert_eq!(volume, 0.5); 102 | Ok(()) 103 | } 104 | fn set_name(&mut self, name: &CStr) -> Result<()> { 105 | assert_eq!(name, CStr::from_bytes_with_nul(b"test\0").unwrap()); 106 | Ok(()) 107 | } 108 | fn current_device(&mut self) -> Result<&DeviceRef> { 109 | Ok(unsafe { DeviceRef::from_ptr(0xDEAD_BEEF as *mut _) }) 110 | } 111 | fn set_input_mute(&mut self, mute: bool) -> Result<()> { 112 | assert_eq!(mute, true); 113 | Ok(()) 114 | } 115 | fn set_input_processing_params(&mut self, params: InputProcessingParams) -> Result<()> { 116 | assert_eq!(params, InputProcessingParams::ECHO_CANCELLATION); 117 | Ok(()) 118 | } 119 | fn device_destroy(&mut self, device: &DeviceRef) -> Result<()> { 120 | assert_eq!(device.as_ptr(), 0xDEAD_BEEF as *mut _); 121 | Ok(()) 122 | } 123 | fn register_device_changed_callback( 124 | &mut self, 125 | _: ffi::cubeb_device_changed_callback, 126 | ) -> Result<()> { 127 | Ok(()) 128 | } 129 | } 130 | 131 | #[test] 132 | fn test_ops_context_init() { 133 | let mut c: *mut ffi::cubeb = ptr::null_mut(); 134 | assert_eq!( 135 | unsafe { OPS.init.unwrap()(&mut c, ptr::null()) }, 136 | ffi::CUBEB_OK 137 | ); 138 | assert!(!c.is_null()); 139 | unsafe { OPS.destroy.unwrap()(c) } 140 | } 141 | 142 | #[test] 143 | fn test_ops_context_max_channel_count() { 144 | let c: *mut ffi::cubeb = get_ctx(); 145 | let mut max_channel_count = u32::max_value(); 146 | assert_eq!( 147 | unsafe { OPS.get_max_channel_count.unwrap()(c, &mut max_channel_count) }, 148 | ffi::CUBEB_OK 149 | ); 150 | assert_eq!(max_channel_count, 0); 151 | } 152 | 153 | #[test] 154 | fn test_ops_context_min_latency() { 155 | let c: *mut ffi::cubeb = get_ctx(); 156 | let params: ffi::cubeb_stream_params = unsafe { ::std::mem::zeroed() }; 157 | let mut latency = u32::max_value(); 158 | assert_eq!( 159 | unsafe { OPS.get_min_latency.unwrap()(c, params, &mut latency) }, 160 | ffi::CUBEB_OK 161 | ); 162 | assert_eq!(latency, 0); 163 | } 164 | 165 | #[test] 166 | fn test_ops_context_preferred_sample_rate() { 167 | let c: *mut ffi::cubeb = get_ctx(); 168 | let mut rate = u32::max_value(); 169 | assert_eq!( 170 | unsafe { OPS.get_preferred_sample_rate.unwrap()(c, &mut rate) }, 171 | ffi::CUBEB_OK 172 | ); 173 | assert_eq!(rate, 0); 174 | } 175 | 176 | #[test] 177 | fn test_ops_context_supported_input_processing_params() { 178 | let c: *mut ffi::cubeb = get_ctx(); 179 | let mut params: ffi::cubeb_input_processing_params = InputProcessingParams::all().bits(); 180 | assert_eq!( 181 | unsafe { OPS.get_supported_input_processing_params.unwrap()(c, &mut params) }, 182 | ffi::CUBEB_OK 183 | ); 184 | assert_eq!(params, ffi::CUBEB_INPUT_PROCESSING_PARAM_NONE); 185 | } 186 | 187 | #[test] 188 | fn test_ops_context_enumerate_devices() { 189 | let c: *mut ffi::cubeb = get_ctx(); 190 | let mut coll = ffi::cubeb_device_collection { 191 | device: ptr::null_mut(), 192 | count: 0, 193 | }; 194 | assert_eq!( 195 | unsafe { OPS.enumerate_devices.unwrap()(c, 0, &mut coll) }, 196 | ffi::CUBEB_OK 197 | ); 198 | assert_ne!(coll.device, std::ptr::null_mut()); 199 | assert_eq!(coll.count, 1) 200 | } 201 | 202 | #[test] 203 | fn test_ops_context_device_collection_destroy() { 204 | let c: *mut ffi::cubeb = get_ctx(); 205 | let mut device_infos = ManuallyDrop::new(Box::new([DeviceInfo::default().into()])); 206 | 207 | let mut coll = ffi::cubeb_device_collection { 208 | device: device_infos.as_mut_ptr(), 209 | count: device_infos.len(), 210 | }; 211 | assert_eq!( 212 | unsafe { OPS.device_collection_destroy.unwrap()(c, &mut coll) }, 213 | ffi::CUBEB_OK 214 | ); 215 | assert_eq!(coll.device, ptr::null_mut()); 216 | assert_eq!(coll.count, 0); 217 | } 218 | 219 | // stream_init: Some($crate::capi::capi_stream_init::<$ctx>), 220 | // stream_destroy: Some($crate::capi::capi_stream_destroy::<$stm>), 221 | // stream_start: Some($crate::capi::capi_stream_start::<$stm>), 222 | // stream_stop: Some($crate::capi::capi_stream_stop::<$stm>), 223 | // stream_get_position: Some($crate::capi::capi_stream_get_position::<$stm>), 224 | 225 | #[test] 226 | fn test_ops_stream_latency() { 227 | let s: *mut ffi::cubeb_stream = get_stream(); 228 | let mut latency = u32::max_value(); 229 | assert_eq!( 230 | unsafe { OPS.stream_get_latency.unwrap()(s, &mut latency) }, 231 | ffi::CUBEB_OK 232 | ); 233 | assert_eq!(latency, 0); 234 | } 235 | 236 | #[test] 237 | fn test_ops_stream_set_volume() { 238 | let s: *mut ffi::cubeb_stream = get_stream(); 239 | unsafe { 240 | OPS.stream_set_volume.unwrap()(s, 0.5); 241 | } 242 | } 243 | 244 | #[test] 245 | fn test_ops_stream_set_name() { 246 | let s: *mut ffi::cubeb_stream = get_stream(); 247 | unsafe { 248 | OPS.stream_set_name.unwrap()(s, CStr::from_bytes_with_nul(b"test\0").unwrap().as_ptr()); 249 | } 250 | } 251 | 252 | #[test] 253 | fn test_ops_stream_current_device() { 254 | let s: *mut ffi::cubeb_stream = get_stream(); 255 | let mut device: *mut ffi::cubeb_device = ptr::null_mut(); 256 | assert_eq!( 257 | unsafe { OPS.stream_get_current_device.unwrap()(s, &mut device) }, 258 | ffi::CUBEB_OK 259 | ); 260 | assert_eq!(device, 0xDEAD_BEEF as *mut _); 261 | } 262 | 263 | #[test] 264 | fn test_ops_stream_set_input_mute() { 265 | let s: *mut ffi::cubeb_stream = get_stream(); 266 | assert_eq!( 267 | unsafe { OPS.stream_set_input_mute.unwrap()(s, 1) }, 268 | ffi::CUBEB_OK 269 | ); 270 | } 271 | 272 | #[test] 273 | fn test_ops_stream_set_input_processing_params() { 274 | let s: *mut ffi::cubeb_stream = get_stream(); 275 | assert_eq!( 276 | unsafe { 277 | OPS.stream_set_input_processing_params.unwrap()( 278 | s, 279 | ffi::CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION, 280 | ) 281 | }, 282 | ffi::CUBEB_OK 283 | ); 284 | } 285 | 286 | fn get_ctx() -> *mut ffi::cubeb { 287 | CONTEXT.get_or_init(TestContextPtr::new).ptr 288 | } 289 | 290 | static CONTEXT: OnceLock = OnceLock::new(); 291 | 292 | struct TestContextPtr { 293 | ptr: *mut ffi::cubeb, 294 | } 295 | 296 | // Safety: ffi::cubeb implementations are expected to be thread-safe. 297 | unsafe impl Send for TestContextPtr {} 298 | unsafe impl Sync for TestContextPtr {} 299 | 300 | impl TestContextPtr { 301 | fn new() -> Self { 302 | let mut c: *mut ffi::cubeb = ptr::null_mut(); 303 | assert_eq!( 304 | unsafe { OPS.init.unwrap()(&mut c, ptr::null()) }, 305 | ffi::CUBEB_OK 306 | ); 307 | assert!(!c.is_null()); 308 | TestContextPtr { ptr: c } 309 | } 310 | } 311 | 312 | impl Drop for TestContextPtr { 313 | fn drop(&mut self) { 314 | unsafe { OPS.destroy.unwrap()(self.ptr) } 315 | } 316 | } 317 | 318 | fn get_stream() -> *mut ffi::cubeb_stream { 319 | STREAM.get_or_init(TestStreamPtr::new).ptr 320 | } 321 | 322 | static STREAM: OnceLock = OnceLock::new(); 323 | 324 | struct TestStreamPtr { 325 | ptr: *mut ffi::cubeb_stream, 326 | } 327 | 328 | // Safety: ffi::cubeb_stream implementations are expected to be thread-safe. 329 | unsafe impl Send for TestStreamPtr {} 330 | unsafe impl Sync for TestStreamPtr {} 331 | 332 | impl TestStreamPtr { 333 | fn new() -> Self { 334 | let c: *mut ffi::cubeb = get_ctx(); 335 | let mut s: *mut ffi::cubeb_stream = ptr::null_mut(); 336 | assert_eq!( 337 | unsafe { 338 | OPS.stream_init.unwrap()( 339 | c, 340 | &mut s, 341 | ptr::null(), 342 | ptr::null(), 343 | ptr::null_mut(), 344 | ptr::null(), 345 | ptr::null_mut(), 346 | 0, 347 | None, 348 | None, 349 | ptr::null_mut(), 350 | ) 351 | }, 352 | ffi::CUBEB_OK 353 | ); 354 | assert!(!s.is_null()); 355 | TestStreamPtr { ptr: s } 356 | } 357 | } 358 | 359 | impl Drop for TestStreamPtr { 360 | fn drop(&mut self) { 361 | unsafe { OPS.stream_destroy.unwrap()(self.ptr) } 362 | } 363 | } 364 | -------------------------------------------------------------------------------- /cubeb-api/src/stream.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use cubeb_core; 7 | use ffi; 8 | use std::ffi::CString; 9 | use std::marker::PhantomData; 10 | use std::mem::ManuallyDrop; 11 | use std::os::raw::{c_long, c_void}; 12 | use std::slice::{from_raw_parts, from_raw_parts_mut}; 13 | use std::{ops, panic, ptr}; 14 | use {ContextRef, DeviceId, Error, Result, State, StreamParamsRef}; 15 | 16 | /// User supplied data callback. 17 | /// 18 | /// - Calling other cubeb functions from this callback is unsafe. 19 | /// - The code in the callback should be non-blocking. 20 | /// - Returning less than the number of frames this callback asks for or 21 | /// provides puts the stream in drain mode. This callback will not be called 22 | /// again, and the state callback will be called with CUBEB_STATE_DRAINED when 23 | /// all the frames have been output. 24 | /// 25 | /// # Arguments 26 | /// 27 | /// - `input_buffer`: A slice containing the input data, zero-len if this is an output-only stream. 28 | /// - `output_buffer`: A mutable slice to be filled with audio samples, zero-len if this is an input-only stream. 29 | /// 30 | /// # Return value 31 | /// 32 | /// If the stream has output, this is the number of frames written to the output buffer. In this 33 | /// case, if this number is less than the length of the output buffer, then the stream will start to 34 | /// drain. 35 | /// 36 | /// If the stream is input only, then returning the length of the input buffer indicates data has 37 | /// been read. In this case, a value less than that will result in the stream being stopped. 38 | pub type DataCallback = dyn FnMut(&[F], &mut [F]) -> isize + Send + Sync + 'static; 39 | 40 | /// User supplied state callback. 41 | /// 42 | /// # Arguments 43 | /// 44 | /// `state`: The new state of the stream 45 | pub type StateCallback = dyn FnMut(State) + Send + Sync + 'static; 46 | 47 | /// User supplied callback called when the underlying device changed. 48 | pub type DeviceChangedCallback = dyn FnMut() + Send + Sync + 'static; 49 | 50 | pub struct StreamCallbacks { 51 | pub(crate) data: Box>, 52 | pub(crate) state: Box, 53 | pub(crate) device_changed: Option>, 54 | } 55 | 56 | /// Audio input/output stream 57 | /// 58 | /// # Example 59 | /// ```no_run 60 | /// extern crate cubeb; 61 | /// use std::thread; 62 | /// use std::time::Duration; 63 | /// 64 | /// type Frame = cubeb::MonoFrame; 65 | /// 66 | /// fn main() { 67 | /// let ctx = cubeb::init("Cubeb tone example").unwrap(); 68 | /// 69 | /// let params = cubeb::StreamParamsBuilder::new() 70 | /// .format(cubeb::SampleFormat::Float32LE) 71 | /// .rate(44_100) 72 | /// .channels(1) 73 | /// .layout(cubeb::ChannelLayout::MONO) 74 | /// .prefs(cubeb::StreamPrefs::NONE) 75 | /// .take(); 76 | /// 77 | /// let phase_inc = 440.0 / 44_100.0; 78 | /// let mut phase = 0.0; 79 | /// let volume = 0.25; 80 | /// 81 | /// let mut builder = cubeb::StreamBuilder::::new(); 82 | /// builder 83 | /// .name("Cubeb Square Wave") 84 | /// .default_output(¶ms) 85 | /// .latency(0x1000) 86 | /// .data_callback(move |_, output| { 87 | /// // Generate a square wave 88 | /// for x in output.iter_mut() { 89 | /// x.m = if phase < 0.5 { volume } else { -volume }; 90 | /// phase = (phase + phase_inc) % 1.0; 91 | /// } 92 | /// 93 | /// output.len() as isize 94 | /// }) 95 | /// .state_callback(|state| { 96 | /// println!("stream {:?}", state); 97 | /// }); 98 | /// let stream = builder.init(&ctx).expect("Failed to create stream."); 99 | /// 100 | /// // Start playback 101 | /// stream.start().unwrap(); 102 | /// 103 | /// // Play for 1/2 second 104 | /// thread::sleep(Duration::from_millis(500)); 105 | /// 106 | /// // Shutdown 107 | /// stream.stop().unwrap(); 108 | /// } 109 | /// ``` 110 | pub struct Stream(ManuallyDrop, PhantomData<*const F>); 111 | 112 | impl Stream { 113 | fn new(s: cubeb_core::Stream) -> Stream { 114 | Stream(ManuallyDrop::new(s), PhantomData) 115 | } 116 | } 117 | 118 | impl Drop for Stream { 119 | fn drop(&mut self) { 120 | let user_ptr = self.user_ptr(); 121 | unsafe { ManuallyDrop::drop(&mut self.0) }; 122 | let _ = unsafe { Box::from_raw(user_ptr as *mut StreamCallbacks) }; 123 | } 124 | } 125 | 126 | impl ops::Deref for Stream { 127 | type Target = cubeb_core::Stream; 128 | 129 | fn deref(&self) -> &Self::Target { 130 | &self.0 131 | } 132 | } 133 | 134 | /// Stream builder 135 | /// 136 | /// ```no_run 137 | /// use cubeb::{Context, MonoFrame, Sample}; 138 | /// use std::f32::consts::PI; 139 | /// use std::thread; 140 | /// use std::time::Duration; 141 | /// 142 | /// const SAMPLE_FREQUENCY: u32 = 48_000; 143 | /// const STREAM_FORMAT: cubeb::SampleFormat = cubeb::SampleFormat::S16LE; 144 | /// type Frame = MonoFrame; 145 | /// 146 | /// let ctx = Context::init(None, None).unwrap(); 147 | /// 148 | /// let params = cubeb::StreamParamsBuilder::new() 149 | /// .format(STREAM_FORMAT) 150 | /// .rate(SAMPLE_FREQUENCY) 151 | /// .channels(1) 152 | /// .layout(cubeb::ChannelLayout::MONO) 153 | /// .take(); 154 | /// 155 | /// let mut position = 0u32; 156 | /// 157 | /// let mut builder = cubeb::StreamBuilder::::new(); 158 | /// builder 159 | /// .name("Cubeb tone (mono)") 160 | /// .default_output(¶ms) 161 | /// .latency(0x1000) 162 | /// .data_callback(move |_, output| { 163 | /// // generate our test tone on the fly 164 | /// for f in output.iter_mut() { 165 | /// // North American dial tone 166 | /// let t1 = (2.0 * PI * 350.0 * position as f32 / SAMPLE_FREQUENCY as f32).sin(); 167 | /// let t2 = (2.0 * PI * 440.0 * position as f32 / SAMPLE_FREQUENCY as f32).sin(); 168 | /// 169 | /// f.m = i16::from_float(0.5 * (t1 + t2)); 170 | /// 171 | /// position += 1; 172 | /// } 173 | /// output.len() as isize 174 | /// }) 175 | /// .state_callback(|state| { 176 | /// println!("stream {:?}", state); 177 | /// }); 178 | /// 179 | /// let stream = builder.init(&ctx).expect("Failed to create cubeb stream"); 180 | /// ``` 181 | pub struct StreamBuilder<'a, F> { 182 | name: Option, 183 | input: Option<(DeviceId, &'a StreamParamsRef)>, 184 | output: Option<(DeviceId, &'a StreamParamsRef)>, 185 | latency: Option, 186 | data_cb: Option>>, 187 | state_cb: Option>, 188 | device_changed_cb: Option>, 189 | } 190 | 191 | impl<'a, F> StreamBuilder<'a, F> { 192 | pub fn new() -> StreamBuilder<'a, F> { 193 | Default::default() 194 | } 195 | 196 | /// User supplied data callback, see [`DataCallback`] 197 | pub fn data_callback(&mut self, cb: D) -> &mut Self 198 | where 199 | D: FnMut(&[F], &mut [F]) -> isize + Send + Sync + 'static, 200 | { 201 | self.data_cb = Some(Box::new(cb) as Box>); 202 | self 203 | } 204 | 205 | /// User supplied state callback, see [`StateCallback`] 206 | pub fn state_callback(&mut self, cb: S) -> &mut Self 207 | where 208 | S: FnMut(State) + Send + Sync + 'static, 209 | { 210 | self.state_cb = Some(Box::new(cb) as Box); 211 | self 212 | } 213 | 214 | /// A name for this stream. 215 | pub fn name>>(&mut self, name: T) -> &mut Self { 216 | self.name = Some(CString::new(name).unwrap()); 217 | self 218 | } 219 | 220 | /// Use the default input device with `params` 221 | /// 222 | /// Optional if the stream is output only 223 | pub fn default_input(&mut self, params: &'a StreamParamsRef) -> &mut Self { 224 | self.input = Some((ptr::null(), params)); 225 | self 226 | } 227 | 228 | /// Use a specific input device with `params` 229 | /// 230 | /// Optional if the stream is output only 231 | pub fn input(&mut self, device: DeviceId, params: &'a StreamParamsRef) -> &mut Self { 232 | self.input = Some((device, params)); 233 | self 234 | } 235 | 236 | /// Use the default output device with `params` 237 | /// 238 | /// Optional if the stream is input only 239 | pub fn default_output(&mut self, params: &'a StreamParamsRef) -> &mut Self { 240 | self.output = Some((ptr::null(), params)); 241 | self 242 | } 243 | 244 | /// Use a specific output device with `params` 245 | /// 246 | /// Optional if the stream is input only 247 | pub fn output(&mut self, device: DeviceId, params: &'a StreamParamsRef) -> &mut Self { 248 | self.output = Some((device, params)); 249 | self 250 | } 251 | 252 | /// Stream latency in frames. 253 | /// 254 | /// Valid range is [1, 96000]. 255 | pub fn latency(&mut self, latency: u32) -> &mut Self { 256 | self.latency = Some(latency); 257 | self 258 | } 259 | 260 | /// User supplied callback called when the underlying device changed. 261 | /// 262 | /// See [`StateCallback`] 263 | /// 264 | /// Optional 265 | pub fn device_changed_cb(&mut self, cb: CB) -> &mut Self 266 | where 267 | CB: FnMut() + Send + Sync + 'static, 268 | { 269 | self.device_changed_cb = Some(Box::new(cb) as Box); 270 | self 271 | } 272 | 273 | /// Build the stream 274 | pub fn init(self, ctx: &ContextRef) -> Result> { 275 | if self.data_cb.is_none() || self.state_cb.is_none() { 276 | return Err(Error::Error); 277 | } 278 | 279 | let has_device_changed = self.device_changed_cb.is_some(); 280 | let cbs = Box::into_raw(Box::new(StreamCallbacks { 281 | data: self.data_cb.unwrap(), 282 | state: self.state_cb.unwrap(), 283 | device_changed: self.device_changed_cb, 284 | })); 285 | 286 | let stream_name = self.name.as_deref(); 287 | let (input_device, input_stream_params) = 288 | self.input.map_or((ptr::null(), None), |x| (x.0, Some(x.1))); 289 | let (output_device, output_stream_params) = self 290 | .output 291 | .map_or((ptr::null(), None), |x| (x.0, Some(x.1))); 292 | let latency = self.latency.unwrap_or(1); 293 | let data_callback: ffi::cubeb_data_callback = Some(data_cb_c::); 294 | let state_callback: ffi::cubeb_state_callback = Some(state_cb_c::); 295 | 296 | let stream = unsafe { 297 | ctx.stream_init( 298 | stream_name, 299 | input_device, 300 | input_stream_params, 301 | output_device, 302 | output_stream_params, 303 | latency, 304 | data_callback, 305 | state_callback, 306 | cbs as *mut _, 307 | )? 308 | }; 309 | if has_device_changed { 310 | let device_changed_callback: ffi::cubeb_device_changed_callback = 311 | Some(device_changed_cb_c::); 312 | stream.register_device_changed_callback(device_changed_callback)?; 313 | } 314 | Ok(Stream::new(stream)) 315 | } 316 | } 317 | 318 | impl Default for StreamBuilder<'_, F> { 319 | fn default() -> Self { 320 | StreamBuilder { 321 | name: None, 322 | input: None, 323 | output: None, 324 | latency: None, 325 | data_cb: None, 326 | state_cb: None, 327 | device_changed_cb: None, 328 | } 329 | } 330 | } 331 | 332 | // C callable callbacks 333 | unsafe extern "C" fn data_cb_c( 334 | _: *mut ffi::cubeb_stream, 335 | user_ptr: *mut c_void, 336 | input_buffer: *const c_void, 337 | output_buffer: *mut c_void, 338 | nframes: c_long, 339 | ) -> c_long { 340 | let ok = panic::catch_unwind(|| { 341 | let cbs = &mut *(user_ptr as *mut StreamCallbacks); 342 | let input: &[F] = if input_buffer.is_null() { 343 | &[] 344 | } else { 345 | from_raw_parts(input_buffer as *const _, nframes as usize) 346 | }; 347 | let output: &mut [F] = if output_buffer.is_null() { 348 | &mut [] 349 | } else { 350 | from_raw_parts_mut(output_buffer as *mut _, nframes as usize) 351 | }; 352 | (cbs.data)(input, output) as c_long 353 | }); 354 | ok.unwrap_or(0) 355 | } 356 | 357 | unsafe extern "C" fn state_cb_c( 358 | _: *mut ffi::cubeb_stream, 359 | user_ptr: *mut c_void, 360 | state: ffi::cubeb_state, 361 | ) { 362 | let ok = panic::catch_unwind(|| { 363 | let state = State::from(state); 364 | let cbs = &mut *(user_ptr as *mut StreamCallbacks); 365 | (cbs.state)(state); 366 | }); 367 | ok.expect("State callback panicked"); 368 | } 369 | 370 | unsafe extern "C" fn device_changed_cb_c(user_ptr: *mut c_void) { 371 | let ok = panic::catch_unwind(|| { 372 | let cbs = &mut *(user_ptr as *mut StreamCallbacks); 373 | if let Some(ref mut device_changed) = cbs.device_changed { 374 | device_changed(); 375 | } 376 | }); 377 | ok.expect("Device changed callback panicked"); 378 | } 379 | -------------------------------------------------------------------------------- /cubeb-core/src/stream.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017-2018 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details. 5 | 6 | use ffi; 7 | use std::ffi::CStr; 8 | use std::os::raw::{c_int, c_void}; 9 | use std::ptr; 10 | use {ChannelLayout, DeviceRef, Result, SampleFormat}; 11 | 12 | /// Stream states signaled via `state_callback`. 13 | #[derive(PartialEq, Eq, Clone, Debug, Copy)] 14 | pub enum State { 15 | /// Stream started. 16 | Started, 17 | /// Stream stopped. 18 | Stopped, 19 | /// Stream drained. 20 | Drained, 21 | /// Stream disabled due to error. 22 | Error, 23 | } 24 | 25 | impl From for State { 26 | fn from(x: ffi::cubeb_state) -> Self { 27 | use State::*; 28 | match x { 29 | ffi::CUBEB_STATE_STARTED => Started, 30 | ffi::CUBEB_STATE_STOPPED => Stopped, 31 | ffi::CUBEB_STATE_DRAINED => Drained, 32 | _ => Error, 33 | } 34 | } 35 | } 36 | 37 | impl From for ffi::cubeb_state { 38 | fn from(x: State) -> Self { 39 | use State::*; 40 | match x { 41 | Started => ffi::CUBEB_STATE_STARTED, 42 | Stopped => ffi::CUBEB_STATE_STOPPED, 43 | Drained => ffi::CUBEB_STATE_DRAINED, 44 | Error => ffi::CUBEB_STATE_ERROR, 45 | } 46 | } 47 | } 48 | 49 | bitflags! { 50 | /// Miscellaneous stream preferences. 51 | pub struct StreamPrefs: ffi::cubeb_stream_prefs { 52 | const LOOPBACK = ffi::CUBEB_STREAM_PREF_LOOPBACK; 53 | const DISABLE_DEVICE_SWITCHING = ffi::CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING; 54 | const VOICE = ffi::CUBEB_STREAM_PREF_VOICE; 55 | } 56 | } 57 | 58 | impl StreamPrefs { 59 | pub const NONE: Self = Self::empty(); 60 | } 61 | 62 | bitflags! { 63 | /// Input stream processing parameters. 64 | pub struct InputProcessingParams: ffi::cubeb_input_processing_params { 65 | const ECHO_CANCELLATION = ffi::CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION; 66 | const NOISE_SUPPRESSION = ffi::CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION; 67 | const AUTOMATIC_GAIN_CONTROL = ffi::CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL; 68 | const VOICE_ISOLATION = ffi::CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION; 69 | } 70 | } 71 | 72 | impl InputProcessingParams { 73 | pub const NONE: Self = Self::empty(); 74 | } 75 | 76 | ffi_type_stack! { 77 | /// Stream format initialization parameters. 78 | type CType = ffi::cubeb_stream_params; 79 | #[derive(Debug)] 80 | pub struct StreamParams; 81 | pub struct StreamParamsRef; 82 | } 83 | 84 | impl StreamParamsRef { 85 | fn get_ref(&self) -> &ffi::cubeb_stream_params { 86 | unsafe { &*self.as_ptr() } 87 | } 88 | 89 | pub fn format(&self) -> SampleFormat { 90 | use SampleFormat::*; 91 | match self.get_ref().format { 92 | ffi::CUBEB_SAMPLE_S16LE => S16LE, 93 | ffi::CUBEB_SAMPLE_S16BE => S16BE, 94 | ffi::CUBEB_SAMPLE_FLOAT32LE => Float32LE, 95 | ffi::CUBEB_SAMPLE_FLOAT32BE => Float32BE, 96 | x => panic!("unknown sample format: {}", x), 97 | } 98 | } 99 | 100 | pub fn rate(&self) -> u32 { 101 | self.get_ref().rate 102 | } 103 | pub fn channels(&self) -> u32 { 104 | self.get_ref().channels 105 | } 106 | 107 | pub fn layout(&self) -> ChannelLayout { 108 | ChannelLayout::from(self.get_ref().layout) 109 | } 110 | 111 | pub fn prefs(&self) -> StreamPrefs { 112 | StreamPrefs::from_bits_truncate(self.get_ref().prefs) 113 | } 114 | 115 | pub fn input_params(&self) -> InputProcessingParams { 116 | InputProcessingParams::from_bits_truncate(self.get_ref().input_params) 117 | } 118 | } 119 | 120 | unsafe fn wrapped_cubeb_stream_destroy(stream: *mut ffi::cubeb_stream) { 121 | ffi::cubeb_stream_stop(stream); 122 | ffi::cubeb_stream_destroy(stream); 123 | } 124 | 125 | ffi_type_heap! { 126 | type CType = ffi::cubeb_stream; 127 | fn drop = wrapped_cubeb_stream_destroy; 128 | pub struct Stream; 129 | pub struct StreamRef; 130 | } 131 | 132 | impl StreamRef { 133 | /// Start playback. 134 | pub fn start(&self) -> Result<()> { 135 | unsafe { call!(ffi::cubeb_stream_start(self.as_ptr())) } 136 | } 137 | 138 | /// Stop playback. 139 | pub fn stop(&self) -> Result<()> { 140 | unsafe { call!(ffi::cubeb_stream_stop(self.as_ptr())) } 141 | } 142 | 143 | /// Get the current stream playback position. 144 | pub fn position(&self) -> Result { 145 | let mut position = 0u64; 146 | unsafe { 147 | call!(ffi::cubeb_stream_get_position(self.as_ptr(), &mut position))?; 148 | } 149 | Ok(position) 150 | } 151 | 152 | /// Get the latency for this stream, in frames. This is the number 153 | /// of frames between the time cubeb acquires the data in the 154 | /// callback and the listener can hear the sound. 155 | pub fn latency(&self) -> Result { 156 | let mut latency = 0u32; 157 | unsafe { 158 | call!(ffi::cubeb_stream_get_latency(self.as_ptr(), &mut latency))?; 159 | } 160 | Ok(latency) 161 | } 162 | 163 | /// Get the input latency for this stream, in frames. This is the number of frames between the 164 | /// time the audio input device records the audio, and the cubeb callback delivers it. 165 | /// This returns an error if the stream is output-only. 166 | pub fn input_latency(&self) -> Result { 167 | let mut latency = 0u32; 168 | unsafe { 169 | call!(ffi::cubeb_stream_get_input_latency( 170 | self.as_ptr(), 171 | &mut latency 172 | ))?; 173 | } 174 | Ok(latency) 175 | } 176 | 177 | /// Set the volume for a stream. 178 | pub fn set_volume(&self, volume: f32) -> Result<()> { 179 | unsafe { call!(ffi::cubeb_stream_set_volume(self.as_ptr(), volume)) } 180 | } 181 | 182 | /// Change a stream's name 183 | pub fn set_name(&self, name: &CStr) -> Result<()> { 184 | unsafe { call!(ffi::cubeb_stream_set_name(self.as_ptr(), name.as_ptr())) } 185 | } 186 | 187 | /// Get the current output device for this stream. 188 | pub fn current_device(&self) -> Result<&DeviceRef> { 189 | let mut device: *mut ffi::cubeb_device = ptr::null_mut(); 190 | unsafe { 191 | call!(ffi::cubeb_stream_get_current_device( 192 | self.as_ptr(), 193 | &mut device 194 | ))?; 195 | Ok(DeviceRef::from_ptr(device)) 196 | } 197 | } 198 | 199 | /// Set the mute state for an input stream. 200 | pub fn set_input_mute(&self, mute: bool) -> Result<()> { 201 | let mute: c_int = if mute { 1 } else { 0 }; 202 | unsafe { call!(ffi::cubeb_stream_set_input_mute(self.as_ptr(), mute)) } 203 | } 204 | 205 | /// Set the processing parameters for an input stream. 206 | pub fn set_input_processing_params(&self, params: InputProcessingParams) -> Result<()> { 207 | unsafe { 208 | call!(ffi::cubeb_stream_set_input_processing_params( 209 | self.as_ptr(), 210 | params.bits() 211 | )) 212 | } 213 | } 214 | 215 | /// Destroy a cubeb_device structure. 216 | pub fn device_destroy(&self, device: DeviceRef) -> Result<()> { 217 | unsafe { 218 | call!(ffi::cubeb_stream_device_destroy( 219 | self.as_ptr(), 220 | device.as_ptr() 221 | )) 222 | } 223 | } 224 | 225 | /// Set a callback to be notified when the output device changes. 226 | pub fn register_device_changed_callback( 227 | &self, 228 | callback: ffi::cubeb_device_changed_callback, 229 | ) -> Result<()> { 230 | unsafe { 231 | call!(ffi::cubeb_stream_register_device_changed_callback( 232 | self.as_ptr(), 233 | callback 234 | )) 235 | } 236 | } 237 | 238 | pub fn user_ptr(&self) -> *mut c_void { 239 | unsafe { ffi::cubeb_stream_user_ptr(self.as_ptr()) } 240 | } 241 | } 242 | 243 | #[cfg(test)] 244 | mod tests { 245 | use std::mem; 246 | use {InputProcessingParams, StreamParams, StreamParamsRef, StreamPrefs}; 247 | 248 | #[test] 249 | fn stream_params_default() { 250 | let params = StreamParams::default(); 251 | assert_eq!(params.channels(), 0); 252 | } 253 | 254 | #[test] 255 | fn stream_params_raw_channels() { 256 | let mut raw: super::ffi::cubeb_stream_params = unsafe { mem::zeroed() }; 257 | raw.channels = 2; 258 | let params = unsafe { StreamParamsRef::from_ptr(&mut raw) }; 259 | assert_eq!(params.channels(), 2); 260 | } 261 | 262 | #[test] 263 | fn stream_params_raw_format() { 264 | let mut raw: super::ffi::cubeb_stream_params = unsafe { mem::zeroed() }; 265 | macro_rules! check( 266 | ($($raw:ident => $real:ident),*) => ( 267 | $(raw.format = super::ffi::$raw; 268 | let params = unsafe { 269 | StreamParamsRef::from_ptr(&mut raw) 270 | }; 271 | assert_eq!(params.format(), super::SampleFormat::$real); 272 | )* 273 | ) ); 274 | 275 | check!(CUBEB_SAMPLE_S16LE => S16LE, 276 | CUBEB_SAMPLE_S16BE => S16BE, 277 | CUBEB_SAMPLE_FLOAT32LE => Float32LE, 278 | CUBEB_SAMPLE_FLOAT32BE => Float32BE); 279 | } 280 | 281 | #[test] 282 | fn stream_params_raw_format_native_endian() { 283 | let mut raw: super::ffi::cubeb_stream_params = unsafe { mem::zeroed() }; 284 | raw.format = super::ffi::CUBEB_SAMPLE_S16NE; 285 | let params = unsafe { StreamParamsRef::from_ptr(&mut raw) }; 286 | assert_eq!( 287 | params.format(), 288 | if cfg!(target_endian = "little") { 289 | super::SampleFormat::S16LE 290 | } else { 291 | super::SampleFormat::S16BE 292 | } 293 | ); 294 | 295 | raw.format = super::ffi::CUBEB_SAMPLE_FLOAT32NE; 296 | let params = unsafe { StreamParamsRef::from_ptr(&mut raw) }; 297 | assert_eq!( 298 | params.format(), 299 | if cfg!(target_endian = "little") { 300 | super::SampleFormat::Float32LE 301 | } else { 302 | super::SampleFormat::Float32BE 303 | } 304 | ); 305 | } 306 | 307 | #[test] 308 | fn stream_params_raw_layout() { 309 | let mut raw: super::ffi::cubeb_stream_params = unsafe { mem::zeroed() }; 310 | macro_rules! check( 311 | ($($raw:ident => $real:ident),*) => ( 312 | $(raw.layout = super::ffi::$raw; 313 | let params = unsafe { 314 | StreamParamsRef::from_ptr(&mut raw) 315 | }; 316 | assert_eq!(params.layout(), super::ChannelLayout::$real); 317 | )* 318 | ) ); 319 | 320 | check!(CUBEB_LAYOUT_UNDEFINED => UNDEFINED, 321 | CUBEB_LAYOUT_MONO => MONO, 322 | CUBEB_LAYOUT_MONO_LFE => MONO_LFE, 323 | CUBEB_LAYOUT_STEREO => STEREO, 324 | CUBEB_LAYOUT_STEREO_LFE => STEREO_LFE, 325 | CUBEB_LAYOUT_3F => _3F, 326 | CUBEB_LAYOUT_3F_LFE => _3F_LFE, 327 | CUBEB_LAYOUT_2F1 => _2F1, 328 | CUBEB_LAYOUT_2F1_LFE => _2F1_LFE, 329 | CUBEB_LAYOUT_3F1 => _3F1, 330 | CUBEB_LAYOUT_3F1_LFE => _3F1_LFE, 331 | CUBEB_LAYOUT_2F2 => _2F2, 332 | CUBEB_LAYOUT_2F2_LFE => _2F2_LFE, 333 | CUBEB_LAYOUT_QUAD => QUAD, 334 | CUBEB_LAYOUT_QUAD_LFE => QUAD_LFE, 335 | CUBEB_LAYOUT_3F2 => _3F2, 336 | CUBEB_LAYOUT_3F2_LFE => _3F2_LFE, 337 | CUBEB_LAYOUT_3F2_BACK => _3F2_BACK, 338 | CUBEB_LAYOUT_3F2_LFE_BACK => _3F2_LFE_BACK, 339 | CUBEB_LAYOUT_3F3R_LFE => _3F3R_LFE, 340 | CUBEB_LAYOUT_3F4_LFE => _3F4_LFE); 341 | } 342 | 343 | #[test] 344 | fn stream_params_raw_rate() { 345 | let mut raw: super::ffi::cubeb_stream_params = unsafe { mem::zeroed() }; 346 | raw.rate = 44_100; 347 | let params = unsafe { StreamParamsRef::from_ptr(&mut raw) }; 348 | assert_eq!(params.rate(), 44_100); 349 | } 350 | 351 | #[test] 352 | fn stream_params_raw_prefs() { 353 | let mut raw: super::ffi::cubeb_stream_params = unsafe { mem::zeroed() }; 354 | raw.prefs = super::ffi::CUBEB_STREAM_PREF_LOOPBACK; 355 | let params = unsafe { StreamParamsRef::from_ptr(&mut raw) }; 356 | assert_eq!(params.prefs(), StreamPrefs::LOOPBACK); 357 | } 358 | 359 | #[test] 360 | fn stream_params_raw_input_params() { 361 | let mut raw: super::ffi::cubeb_stream_params = unsafe { mem::zeroed() }; 362 | raw.input_params = super::ffi::CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION; 363 | let params = unsafe { StreamParamsRef::from_ptr(&mut raw) }; 364 | assert_eq!( 365 | params.input_params(), 366 | InputProcessingParams::ECHO_CANCELLATION 367 | ); 368 | } 369 | 370 | #[test] 371 | fn stream_params_stream_params_ref_ptr_match() { 372 | let params = StreamParams::default(); 373 | assert_eq!(params.as_ptr(), params.as_ref().as_ptr()); 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /cubeb-backend/src/capi.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Mozilla Foundation 2 | // 3 | // This program is made available under an ISC-style license. See the 4 | // accompanying file LICENSE for details 5 | 6 | use cubeb_core::{ 7 | ffi, DeviceInfo, DeviceRef, DeviceType, InputProcessingParams, StreamParams, StreamParamsRef, 8 | }; 9 | use std::ffi::CStr; 10 | use std::mem; 11 | use std::os::raw::{c_char, c_int, c_void}; 12 | use {ContextOps, StreamOps}; 13 | 14 | // Helper macro for unwrapping `Result` values from rust-api calls 15 | // while returning early with a c-api error code if the value of the 16 | // expression is `Err`. 17 | macro_rules! _try( 18 | ($e:expr) => (match $e { 19 | Ok(e) => e, 20 | Err(e) => return e as c_int 21 | }) 22 | ); 23 | 24 | macro_rules! as_opt_ref { 25 | ($e:expr) => { 26 | if $e.is_null() { 27 | None 28 | } else { 29 | Some(StreamParamsRef::from_ptr($e)) 30 | } 31 | }; 32 | } 33 | 34 | #[macro_export] 35 | macro_rules! capi_new( 36 | ($ctx:ident, $stm:ident) => ( 37 | Ops { 38 | init: Some($crate::capi::capi_init::<$ctx>), 39 | get_backend_id: Some($crate::capi::capi_get_backend_id::<$ctx>), 40 | get_max_channel_count: Some($crate::capi::capi_get_max_channel_count::<$ctx>), 41 | get_min_latency: Some($crate::capi::capi_get_min_latency::<$ctx>), 42 | get_preferred_sample_rate: Some($crate::capi::capi_get_preferred_sample_rate::<$ctx>), 43 | get_supported_input_processing_params: 44 | Some($crate::capi::capi_get_supported_input_processing_params::<$ctx>), 45 | enumerate_devices: Some($crate::capi::capi_enumerate_devices::<$ctx>), 46 | device_collection_destroy: Some($crate::capi::capi_device_collection_destroy::<$ctx>), 47 | destroy: Some($crate::capi::capi_destroy::<$ctx>), 48 | stream_init: Some($crate::capi::capi_stream_init::<$ctx>), 49 | stream_destroy: Some($crate::capi::capi_stream_destroy::<$stm>), 50 | stream_start: Some($crate::capi::capi_stream_start::<$stm>), 51 | stream_stop: Some($crate::capi::capi_stream_stop::<$stm>), 52 | stream_get_position: Some($crate::capi::capi_stream_get_position::<$stm>), 53 | stream_get_latency: Some($crate::capi::capi_stream_get_latency::<$stm>), 54 | stream_get_input_latency: Some($crate::capi::capi_stream_get_input_latency::<$stm>), 55 | stream_set_volume: Some($crate::capi::capi_stream_set_volume::<$stm>), 56 | stream_set_name: Some($crate::capi::capi_stream_set_name::<$stm>), 57 | stream_get_current_device: Some($crate::capi::capi_stream_get_current_device::<$stm>), 58 | stream_set_input_mute: Some($crate::capi::capi_stream_set_input_mute::<$stm>), 59 | stream_set_input_processing_params: 60 | Some($crate::capi::capi_stream_set_input_processing_params::<$stm>), 61 | stream_device_destroy: Some($crate::capi::capi_stream_device_destroy::<$stm>), 62 | stream_register_device_changed_callback: 63 | Some($crate::capi::capi_stream_register_device_changed_callback::<$stm>), 64 | register_device_collection_changed: 65 | Some($crate::capi::capi_register_device_collection_changed::<$ctx>) 66 | })); 67 | 68 | /// # Safety 69 | /// 70 | /// Entry point from C code. 71 | /// 72 | /// This function is unsafe because it dereferences the given `c` and `context` pointers. 73 | /// The caller should ensure those pointers are valid. 74 | pub unsafe extern "C" fn capi_init( 75 | c: *mut *mut ffi::cubeb, 76 | context_name: *const c_char, 77 | ) -> c_int { 78 | let anchor = &(); 79 | let context_name = opt_cstr(anchor, context_name); 80 | let context = _try!(CTX::init(context_name)); 81 | c.write(Box::into_raw(context) as *mut _); 82 | ffi::CUBEB_OK 83 | } 84 | 85 | /// # Safety 86 | /// 87 | /// Entry point from C code. 88 | /// 89 | /// This function is unsafe because it dereferences the given `c` pointer. 90 | /// The caller should ensure that pointer is valid. 91 | pub unsafe extern "C" fn capi_get_backend_id(c: *mut ffi::cubeb) -> *const c_char { 92 | let ctx = &mut *(c as *mut CTX); 93 | ctx.backend_id().as_ptr() 94 | } 95 | 96 | /// # Safety 97 | /// 98 | /// Entry point from C code. 99 | /// 100 | /// This function is unsafe because it dereferences the given `c` and `max_channels` pointers. 101 | /// The caller should ensure those pointers are valid. 102 | pub unsafe extern "C" fn capi_get_max_channel_count( 103 | c: *mut ffi::cubeb, 104 | max_channels: *mut u32, 105 | ) -> c_int { 106 | let ctx = &mut *(c as *mut CTX); 107 | 108 | *max_channels = _try!(ctx.max_channel_count()); 109 | ffi::CUBEB_OK 110 | } 111 | 112 | /// # Safety 113 | /// 114 | /// Entry point from C code. 115 | /// 116 | /// This function is unsafe because it dereferences the given `c` and `latency_frames` pointers. 117 | /// The caller should ensure those pointers are valid. 118 | pub unsafe extern "C" fn capi_get_min_latency( 119 | c: *mut ffi::cubeb, 120 | param: ffi::cubeb_stream_params, 121 | latency_frames: *mut u32, 122 | ) -> c_int { 123 | let ctx = &mut *(c as *mut CTX); 124 | let param = StreamParams::from(param); 125 | *latency_frames = _try!(ctx.min_latency(param)); 126 | ffi::CUBEB_OK 127 | } 128 | 129 | /// # Safety 130 | /// 131 | /// Entry point from C code. 132 | /// 133 | /// This function is unsafe because it dereferences the given `c` and `rate` pointers. 134 | /// The caller should ensure those pointers are valid. 135 | pub unsafe extern "C" fn capi_get_preferred_sample_rate( 136 | c: *mut ffi::cubeb, 137 | rate: *mut u32, 138 | ) -> c_int { 139 | let ctx = &mut *(c as *mut CTX); 140 | 141 | *rate = _try!(ctx.preferred_sample_rate()); 142 | ffi::CUBEB_OK 143 | } 144 | 145 | /// # Safety 146 | /// 147 | /// Entry point from C code. 148 | /// 149 | /// This function is unsafe because it dereferences the given `c` and `params` pointers. 150 | /// The caller should ensure those pointers are valid. 151 | pub unsafe extern "C" fn capi_get_supported_input_processing_params( 152 | c: *mut ffi::cubeb, 153 | params: *mut ffi::cubeb_input_processing_params, 154 | ) -> c_int { 155 | let ctx = &mut *(c as *mut CTX); 156 | *params = _try!(ctx.supported_input_processing_params()).bits(); 157 | ffi::CUBEB_OK 158 | } 159 | 160 | /// # Safety 161 | /// 162 | /// Entry point from C code. 163 | /// 164 | /// This function is unsafe because it dereferences the given `c` and `collection` pointers. 165 | /// The caller should ensure those pointers are valid. 166 | pub unsafe extern "C" fn capi_enumerate_devices( 167 | c: *mut ffi::cubeb, 168 | devtype: ffi::cubeb_device_type, 169 | collection: *mut ffi::cubeb_device_collection, 170 | ) -> c_int { 171 | debug_assert!(!c.is_null()); 172 | debug_assert!(!collection.is_null()); 173 | 174 | let ctx = &mut *(c as *mut CTX); 175 | let devtype = DeviceType::from_bits_truncate(devtype); 176 | 177 | let coll = Box::into_raw(_try!(ctx.enumerate_devices(devtype))); 178 | collection.write(ffi::cubeb_device_collection { 179 | device: coll as *mut _, 180 | count: coll.len(), 181 | }); 182 | ffi::CUBEB_OK 183 | } 184 | 185 | /// # Safety 186 | /// 187 | /// Entry point from C code. 188 | /// 189 | /// This function is unsafe because it dereferences the given `c` and `collection` pointers. 190 | /// The caller should ensure those pointers are valid. 191 | pub unsafe extern "C" fn capi_device_collection_destroy( 192 | c: *mut ffi::cubeb, 193 | collection: *mut ffi::cubeb_device_collection, 194 | ) -> c_int { 195 | debug_assert!(!c.is_null()); 196 | debug_assert!(!collection.is_null()); 197 | 198 | let ctx = &mut *(c as *mut CTX); 199 | let collection = &mut *(collection); 200 | 201 | let coll = Box::from_raw(std::ptr::slice_from_raw_parts_mut( 202 | collection.device as *mut DeviceInfo, 203 | collection.count, 204 | )); 205 | _try!(ctx.device_collection_destroy(coll)); 206 | collection.device = std::ptr::null_mut(); 207 | collection.count = 0; 208 | ffi::CUBEB_OK 209 | } 210 | 211 | /// # Safety 212 | /// 213 | /// Entry point from C code. 214 | /// 215 | /// This function is unsafe because it dereferences the given `c` pointer. 216 | /// The caller should ensure that pointer is valid. 217 | pub unsafe extern "C" fn capi_destroy(c: *mut ffi::cubeb) { 218 | let _: Box = Box::from_raw(c as *mut _); 219 | } 220 | 221 | /// # Safety 222 | /// 223 | /// Entry point from C code. 224 | /// 225 | /// This function is unsafe because it dereferences the given `c`, `s`, `stream_name`, `input_stream_params`, 226 | /// `output_stream_params`, `data_callback`, `state_callback`, and `user_ptr` pointers. 227 | /// The caller should ensure those pointers are valid. 228 | pub unsafe extern "C" fn capi_stream_init( 229 | c: *mut ffi::cubeb, 230 | s: *mut *mut ffi::cubeb_stream, 231 | stream_name: *const c_char, 232 | input_device: ffi::cubeb_devid, 233 | input_stream_params: *mut ffi::cubeb_stream_params, 234 | output_device: ffi::cubeb_devid, 235 | output_stream_params: *mut ffi::cubeb_stream_params, 236 | latency_frames: u32, 237 | data_callback: ffi::cubeb_data_callback, 238 | state_callback: ffi::cubeb_state_callback, 239 | user_ptr: *mut c_void, 240 | ) -> c_int { 241 | let ctx = &mut *(c as *mut CTX); 242 | let anchor = &(); // for lifetime of stream_name as CStr 243 | 244 | let input_stream_params = as_opt_ref!(input_stream_params); 245 | let output_stream_params = as_opt_ref!(output_stream_params); 246 | 247 | let stream = _try!(ctx.stream_init( 248 | opt_cstr(anchor, stream_name), 249 | input_device, 250 | input_stream_params, 251 | output_device, 252 | output_stream_params, 253 | latency_frames, 254 | data_callback, 255 | state_callback, 256 | user_ptr 257 | )); 258 | *s = stream.as_ptr(); 259 | // Leaking pointer across C FFI 260 | mem::forget(stream); 261 | ffi::CUBEB_OK 262 | } 263 | 264 | /// # Safety 265 | /// 266 | /// Entry point from C code. 267 | /// 268 | /// This function is unsafe because it dereferences the given `s` pointer. 269 | /// The caller should ensure that pointer is valid. 270 | pub unsafe extern "C" fn capi_stream_destroy(s: *mut ffi::cubeb_stream) { 271 | let _ = Box::from_raw(s as *mut STM); 272 | } 273 | 274 | /// # Safety 275 | /// 276 | /// Entry point from C code. 277 | /// 278 | /// This function is unsafe because it dereferences the given `s` pointer. 279 | /// The caller should ensure that pointer is valid. 280 | pub unsafe extern "C" fn capi_stream_start(s: *mut ffi::cubeb_stream) -> c_int { 281 | let stm = &mut *(s as *mut STM); 282 | 283 | _try!(stm.start()); 284 | ffi::CUBEB_OK 285 | } 286 | 287 | /// # Safety 288 | /// 289 | /// Entry point from C code. 290 | /// 291 | /// This function is unsafe because it dereferences the given `s` pointer. 292 | /// The caller should ensure that pointer is valid. 293 | pub unsafe extern "C" fn capi_stream_stop(s: *mut ffi::cubeb_stream) -> c_int { 294 | let stm = &mut *(s as *mut STM); 295 | 296 | _try!(stm.stop()); 297 | ffi::CUBEB_OK 298 | } 299 | 300 | /// # Safety 301 | /// 302 | /// Entry point from C code. 303 | /// 304 | /// This function is unsafe because it dereferences the given `s` and `position` pointers. 305 | /// The caller should ensure those pointers are valid. 306 | pub unsafe extern "C" fn capi_stream_get_position( 307 | s: *mut ffi::cubeb_stream, 308 | position: *mut u64, 309 | ) -> c_int { 310 | let stm = &mut *(s as *mut STM); 311 | 312 | *position = _try!(stm.position()); 313 | ffi::CUBEB_OK 314 | } 315 | 316 | /// # Safety 317 | /// 318 | /// Entry point from C code. 319 | /// 320 | /// This function is unsafe because it dereferences the given `s` and `latency` pointers. 321 | /// The caller should ensure those pointers are valid. 322 | pub unsafe extern "C" fn capi_stream_get_latency( 323 | s: *mut ffi::cubeb_stream, 324 | latency: *mut u32, 325 | ) -> c_int { 326 | let stm = &mut *(s as *mut STM); 327 | 328 | *latency = _try!(stm.latency()); 329 | ffi::CUBEB_OK 330 | } 331 | 332 | /// # Safety 333 | /// 334 | /// Entry point from C code. 335 | /// 336 | /// This function is unsafe because it dereferences the given `s` and `latency` pointers. 337 | /// The caller should ensure those pointers are valid. 338 | pub unsafe extern "C" fn capi_stream_get_input_latency( 339 | s: *mut ffi::cubeb_stream, 340 | latency: *mut u32, 341 | ) -> c_int { 342 | let stm = &mut *(s as *mut STM); 343 | 344 | *latency = _try!(stm.input_latency()); 345 | ffi::CUBEB_OK 346 | } 347 | 348 | /// # Safety 349 | /// 350 | /// Entry point from C code. 351 | /// 352 | /// This function is unsafe because it dereferences the given `s` pointer. 353 | /// The caller should ensure that pointer is valid. 354 | pub unsafe extern "C" fn capi_stream_set_volume( 355 | s: *mut ffi::cubeb_stream, 356 | volume: f32, 357 | ) -> c_int { 358 | let stm = &mut *(s as *mut STM); 359 | 360 | _try!(stm.set_volume(volume)); 361 | ffi::CUBEB_OK 362 | } 363 | 364 | /// # Safety 365 | /// 366 | /// Entry point from C code. 367 | /// 368 | /// This function is unsafe because it dereferences the given `s` and `name` pointers. 369 | /// The caller should ensure those pointers are valid. 370 | pub unsafe extern "C" fn capi_stream_set_name( 371 | s: *mut ffi::cubeb_stream, 372 | name: *const c_char, 373 | ) -> c_int { 374 | let stm = &mut *(s as *mut STM); 375 | let anchor = &(); 376 | if let Some(name) = opt_cstr(anchor, name) { 377 | _try!(stm.set_name(name)); 378 | ffi::CUBEB_OK 379 | } else { 380 | ffi::CUBEB_ERROR_INVALID_PARAMETER 381 | } 382 | } 383 | 384 | /// # Safety 385 | /// 386 | /// Entry point from C code. 387 | /// 388 | /// This function is unsafe because it dereferences the given `s` and `device` pointers. 389 | /// The caller should ensure those pointers are valid. 390 | pub unsafe extern "C" fn capi_stream_get_current_device( 391 | s: *mut ffi::cubeb_stream, 392 | device: *mut *mut ffi::cubeb_device, 393 | ) -> i32 { 394 | let stm = &mut *(s as *mut STM); 395 | 396 | *device = _try!(stm.current_device()).as_ptr(); 397 | ffi::CUBEB_OK 398 | } 399 | 400 | /// # Safety 401 | /// 402 | /// Entry point from C code. 403 | /// 404 | /// This function is unsafe because it dereferences the given `s` pointer. 405 | /// The caller should ensure those pointers are valid. 406 | pub unsafe extern "C" fn capi_stream_set_input_mute( 407 | s: *mut ffi::cubeb_stream, 408 | mute: c_int, 409 | ) -> c_int { 410 | let stm = &mut *(s as *mut STM); 411 | _try!(stm.set_input_mute(mute != 0)); 412 | ffi::CUBEB_OK 413 | } 414 | 415 | /// # Safety 416 | /// 417 | /// Entry point from C code. 418 | /// 419 | /// This function is unsafe because it dereferences the given `s` pointer. 420 | /// The caller should ensure those pointers are valid. 421 | pub unsafe extern "C" fn capi_stream_set_input_processing_params( 422 | s: *mut ffi::cubeb_stream, 423 | params: ffi::cubeb_input_processing_params, 424 | ) -> c_int { 425 | let stm = &mut *(s as *mut STM); 426 | _try!(stm.set_input_processing_params(InputProcessingParams::from_bits_truncate(params))); 427 | ffi::CUBEB_OK 428 | } 429 | 430 | /// # Safety 431 | /// 432 | /// Entry point from C code. 433 | /// 434 | /// This function is unsafe because it dereferences the given `s` and `device` pointers. 435 | /// The caller should ensure those pointers are valid. 436 | pub unsafe extern "C" fn capi_stream_device_destroy( 437 | s: *mut ffi::cubeb_stream, 438 | device: *mut ffi::cubeb_device, 439 | ) -> c_int { 440 | let stm = &mut *(s as *mut STM); 441 | if device.is_null() { 442 | return ffi::CUBEB_ERROR_INVALID_PARAMETER; 443 | } 444 | let device = DeviceRef::from_ptr(device); 445 | let _ = stm.device_destroy(device); 446 | ffi::CUBEB_OK 447 | } 448 | 449 | /// # Safety 450 | /// 451 | /// Entry point from C code. 452 | /// 453 | /// This function is unsafe because it dereferences the given `s` and `device_changed_callback` pointers. 454 | /// The caller should ensure those pointers are valid. 455 | pub unsafe extern "C" fn capi_stream_register_device_changed_callback( 456 | s: *mut ffi::cubeb_stream, 457 | device_changed_callback: ffi::cubeb_device_changed_callback, 458 | ) -> c_int { 459 | let stm = &mut *(s as *mut STM); 460 | 461 | _try!(stm.register_device_changed_callback(device_changed_callback)); 462 | ffi::CUBEB_OK 463 | } 464 | 465 | /// # Safety 466 | /// 467 | /// Entry point from C code. 468 | /// 469 | /// This function is unsafe because it dereferences the given `s`, `collection_changed_callback`, and 470 | /// `user_ptr` pointers. 471 | /// The caller should ensure those pointers are valid. 472 | pub unsafe extern "C" fn capi_register_device_collection_changed( 473 | c: *mut ffi::cubeb, 474 | devtype: ffi::cubeb_device_type, 475 | collection_changed_callback: ffi::cubeb_device_collection_changed_callback, 476 | user_ptr: *mut c_void, 477 | ) -> i32 { 478 | let ctx = &mut *(c as *mut CTX); 479 | let devtype = DeviceType::from_bits_truncate(devtype); 480 | _try!(ctx.register_device_collection_changed(devtype, collection_changed_callback, user_ptr)); 481 | ffi::CUBEB_OK 482 | } 483 | 484 | fn opt_cstr(_anchor: &T, ptr: *const c_char) -> Option<&CStr> { 485 | if ptr.is_null() { 486 | None 487 | } else { 488 | Some(unsafe { CStr::from_ptr(ptr) }) 489 | } 490 | } 491 | --------------------------------------------------------------------------------