Bug 1635404 - mach vendor rust. r=achronop

Depends on D73871

Differential Revision: https://phabricator.services.mozilla.com/D73872
This commit is contained in:
Paul Adenot 2020-05-05 13:39:15 +00:00
parent 0a01403ee5
commit 6a7f9ca0d5
61 changed files with 1205 additions and 520 deletions

View file

@ -45,7 +45,7 @@ rev = "3541e3818fdc7c2a24f87e3459151a4ce955a67a"
[source."https://github.com/djg/cubeb-pulse-rs"]
git = "https://github.com/djg/cubeb-pulse-rs"
replace-with = "vendored-sources"
rev = "5eb38163103b0dae86de81cdaf46070bdeedc0d1"
rev = "70431f444cf164177cb3c0f060698fc35f811be5"
[source."https://github.com/bytecodealliance/wasmtime"]
git = "https://github.com/bytecodealliance/wasmtime"
@ -70,7 +70,7 @@ rev = "5e870faf6f95d79d11efc813e56370ad124bbed5"
[source."https://github.com/ChunMinChang/cubeb-coreaudio-rs"]
git = "https://github.com/ChunMinChang/cubeb-coreaudio-rs"
replace-with = "vendored-sources"
rev = "6156e941e5f46a641d2da10ca53a9ad318c10722"
rev = "c5aacdc75618025e72f62f727a7a0d91606e6276"
[source.crates-io]
replace-with = "vendored-sources"

26
Cargo.lock generated
View file

@ -743,7 +743,7 @@ dependencies = [
[[package]]
name = "coreaudio-sys-utils"
version = "0.1.0"
source = "git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=6156e941e5f46a641d2da10ca53a9ad318c10722#6156e941e5f46a641d2da10ca53a9ad318c10722"
source = "git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=c5aacdc75618025e72f62f727a7a0d91606e6276#c5aacdc75618025e72f62f727a7a0d91606e6276"
dependencies = [
"core-foundation-sys",
"coreaudio-sys",
@ -952,27 +952,27 @@ dependencies = [
[[package]]
name = "cubeb"
version = "0.6.2"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cbcdfde9ea319160af6eff068ffaa96aad3532e1b5c0ebc134614cfacacae24"
checksum = "1116606d6045c9199f6a1e082f3cf63383ba6f9961339701faa6370dcf73135f"
dependencies = [
"cubeb-core",
]
[[package]]
name = "cubeb-backend"
version = "0.6.3"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "228778b00abd85a2ccca9330bc0ee7e72795dd255039814baef02b99757ad12e"
checksum = "cc644425cb33d45994a5df4351958369c00e3f7bbf785b8cab270c7840b75774"
dependencies = [
"cubeb-core",
]
[[package]]
name = "cubeb-core"
version = "0.6.2"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfd9b2ea1cb6afed9419b0d18fc4093df552ccb2300eb57793629f8cd370b4c8"
checksum = "f7c55529b8f47926e4242e1fc01d31b08a5a4847967c5c250644e33fe237cfe5"
dependencies = [
"bitflags",
"cubeb-sys",
@ -981,7 +981,7 @@ dependencies = [
[[package]]
name = "cubeb-coreaudio"
version = "0.1.0"
source = "git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=6156e941e5f46a641d2da10ca53a9ad318c10722#6156e941e5f46a641d2da10ca53a9ad318c10722"
source = "git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=c5aacdc75618025e72f62f727a7a0d91606e6276#c5aacdc75618025e72f62f727a7a0d91606e6276"
dependencies = [
"atomic",
"audio-mixer",
@ -998,7 +998,7 @@ dependencies = [
[[package]]
name = "cubeb-pulse"
version = "0.3.0"
source = "git+https://github.com/djg/cubeb-pulse-rs?rev=5eb38163103b0dae86de81cdaf46070bdeedc0d1#5eb38163103b0dae86de81cdaf46070bdeedc0d1"
source = "git+https://github.com/djg/cubeb-pulse-rs?rev=70431f444cf164177cb3c0f060698fc35f811be5#70431f444cf164177cb3c0f060698fc35f811be5"
dependencies = [
"cubeb-backend",
"pulse",
@ -1009,9 +1009,9 @@ dependencies = [
[[package]]
name = "cubeb-sys"
version = "0.6.2"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "309c5839c5fa03c08363bd308566cbe4654b25a9984342d7546a33d55b80a3d6"
checksum = "dcbc562eb6ccf62abacf9e3eebce992e5c36b230ca313ebd7c2d7d0e99deae90"
dependencies = [
"cmake",
"pkg-config",
@ -3594,7 +3594,7 @@ dependencies = [
[[package]]
name = "pulse"
version = "0.3.0"
source = "git+https://github.com/djg/cubeb-pulse-rs?rev=5eb38163103b0dae86de81cdaf46070bdeedc0d1#5eb38163103b0dae86de81cdaf46070bdeedc0d1"
source = "git+https://github.com/djg/cubeb-pulse-rs?rev=70431f444cf164177cb3c0f060698fc35f811be5#70431f444cf164177cb3c0f060698fc35f811be5"
dependencies = [
"bitflags",
"pulse-ffi",
@ -3603,7 +3603,7 @@ dependencies = [
[[package]]
name = "pulse-ffi"
version = "0.1.0"
source = "git+https://github.com/djg/cubeb-pulse-rs?rev=5eb38163103b0dae86de81cdaf46070bdeedc0d1#5eb38163103b0dae86de81cdaf46070bdeedc0d1"
source = "git+https://github.com/djg/cubeb-pulse-rs?rev=70431f444cf164177cb3c0f060698fc35f811be5#70431f444cf164177cb3c0f060698fc35f811be5"
dependencies = [
"libc",
]

View file

@ -1 +1 @@
{"files":{"Cargo.toml":"f14e28a47501dd6861a5f3de61bdf70a2e67d8516c78f0b3a73fcd790ef8c143","LICENSE":"8c044baa5d883274736eeece0b955249076c2697b826e576fce59496235b2cf5","src/capi.rs":"faec78d41430ae26ac24b46d5833c76f94ac6eebb4b2186383ebb5e9de32cf8f","src/lib.rs":"94b80747ae1037423a2281f2572fc6d15cd7702417974ae3730adccd71c7a300","src/log.rs":"4962e9c5fdaf44b660ea6813b26337283496ffae3e9beaf2cda86621e253ae8b","src/ops.rs":"d9465abd16c2ce7414aecfc8f983fc9c29dc9b4780c0ea2074974a9814653576","src/traits.rs":"fa8463bd030c1d7716b05401fd2425ff0a399ee0c4dacfeed22341d89ecad871","tests/test_capi.rs":"25860adff6e9e2bcb06371f1e7e52e5f8a20c61fd6b2efe61b7713f3bc4f1031"},"package":"228778b00abd85a2ccca9330bc0ee7e72795dd255039814baef02b99757ad12e"}
{"files":{"Cargo.toml":"1e79db9b3f52dc7d3b4d8354cd92022d6d9540aa2b8eee1be6f653cf0f9b6f79","LICENSE":"8c044baa5d883274736eeece0b955249076c2697b826e576fce59496235b2cf5","src/capi.rs":"a772331992b8e058a11e0a96515092ca19182c8eb4c0766e459ccceeb434b2ad","src/lib.rs":"94b80747ae1037423a2281f2572fc6d15cd7702417974ae3730adccd71c7a300","src/log.rs":"cf8e3a778f6b72d4cd80c1c56963355aa2224f19fd4fdf07d03f6fb366000899","src/ops.rs":"12c808c465a8e40bfd19ef8449035924d795fe19bab25b2f13779dca68040010","src/traits.rs":"e8c268343f21799b806e85b0ab1c0f4350b109d83af3e565ac6ff1eb17413672","tests/test_capi.rs":"eea51e60d631d0dab2c8e7f5172467b4fbafa652487f8840e9c5c89da42bf598"},"package":"cc644425cb33d45994a5df4351958369c00e3f7bbf785b8cab270c7840b75774"}

View file

@ -12,7 +12,7 @@
[package]
name = "cubeb-backend"
version = "0.6.3"
version = "0.7.0"
authors = ["Dan Glastonbury <dglastonbury@mozilla.com>"]
description = "Bindings to libcubeb internals to facilitate implementing cubeb backends in rust.\n"
homepage = "https://github.com/djg/cubeb-rs"
@ -21,7 +21,7 @@ categories = ["api-bindings"]
license = "ISC"
repository = "https://github.com/djg/cubeb-rs"
[dependencies.cubeb-core]
version = "0.6.2"
version = "0.7.0"
[features]
gecko-in-tree = ["cubeb-core/gecko-in-tree"]

View file

@ -49,6 +49,7 @@ macro_rules! capi_new(
Some($crate::capi::capi_stream_reset_default_device::<$stm>),
stream_get_position: Some($crate::capi::capi_stream_get_position::<$stm>),
stream_get_latency: Some($crate::capi::capi_stream_get_latency::<$stm>),
stream_get_input_latency: Some($crate::capi::capi_stream_get_input_latency::<$stm>),
stream_set_volume: Some($crate::capi::capi_stream_set_volume::<$stm>),
stream_get_current_device: Some($crate::capi::capi_stream_get_current_device::<$stm>),
stream_device_destroy: Some($crate::capi::capi_stream_device_destroy::<$stm>),
@ -216,6 +217,16 @@ pub unsafe extern "C" fn capi_stream_get_latency<STM: StreamOps>(
ffi::CUBEB_OK
}
pub unsafe extern "C" fn capi_stream_get_input_latency<STM: StreamOps>(
s: *mut ffi::cubeb_stream,
latency: *mut u32,
) -> c_int {
let stm = &mut *(s as *mut STM);
*latency = _try!(stm.input_latency());
ffi::CUBEB_OK
}
pub unsafe extern "C" fn capi_stream_set_volume<STM: StreamOps>(
s: *mut ffi::cubeb_stream,
volume: f32,

View file

@ -37,7 +37,7 @@ macro_rules! cubeb_log_internal {
let _ = write!(&mut buf[..], "{}:{}: {}\n", filename, line!(), $msg);
let last = std::cmp::min(len, buf.len() - 1);
buf[last] = 0;
let cstr = unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(&buf[..last + 1]) };
let cstr = unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(&buf[..=last]) };
log_callback(cstr.as_ptr());
}
}

View file

@ -63,6 +63,8 @@ pub struct Ops {
Option<unsafe extern "C" fn(stream: *mut ffi::cubeb_stream, position: *mut u64) -> c_int>,
pub stream_get_latency:
Option<unsafe extern "C" fn(stream: *mut ffi::cubeb_stream, latency: *mut u32) -> c_int>,
pub stream_get_input_latency:
Option<unsafe extern "C" fn(stream: *mut ffi::cubeb_stream, latency: *mut u32) -> c_int>,
pub stream_set_volume:
Option<unsafe extern "C" fn(stream: *mut ffi::cubeb_stream, volumes: c_float) -> c_int>,
pub stream_get_current_device: Option<

View file

@ -48,6 +48,7 @@ pub trait StreamOps {
fn reset_default_device(&mut self) -> Result<()>;
fn position(&mut self) -> Result<u64>;
fn latency(&mut self) -> Result<u32>;
fn input_latency(&mut self) -> Result<u32>;
fn set_volume(&mut self, volume: f32) -> Result<()>;
fn current_device(&mut self) -> Result<&DeviceRef>;
fn device_destroy(&mut self, device: &DeviceRef) -> Result<()>;

View file

@ -100,6 +100,9 @@ impl StreamOps for TestStream {
fn latency(&mut self) -> Result<u32> {
Ok(0u32)
}
fn input_latency(&mut self) -> Result<u32> {
Ok(0u32)
}
fn set_volume(&mut self, volume: f32) -> Result<()> {
assert_eq!(volume, 0.5);
Ok(())

View file

@ -1 +1 @@
{"files":{"Cargo.toml":"12fc57e44d5f3962acec8860191fe0e9aca6ebe654924f4736b35cf15450b0d1","LICENSE":"8c044baa5d883274736eeece0b955249076c2697b826e576fce59496235b2cf5","src/builders.rs":"ca97e3a3d1f3fc451c17851c8538964ec67f3964dfe29e902d904ee7445becca","src/channel.rs":"c8d5a76ef3ecdd96cd4de516e3d4d139bbb83c4690d1c3f5fd07fffc47be51f1","src/context.rs":"09625b75070ec88d566a907ab2e574e2d85df4c6df295f798b3372df2cdc8f7a","src/device.rs":"490d2e94ecae1e149476c2e8d9aa03c4163987c3efccc962b2d3123e4c09dedf","src/device_collection.rs":"f6d0c1628cc34b524f86b84a1e1c79971c3f64ebc4ac64eeb10a1330bbe8c238","src/error.rs":"855ff3d3597753f832ecea00e403c71129afd80db3d39456cf3e23cb9aeb91e7","src/ffi_types.rs":"d815d7a80895b5e86907e708dc0219fca4ac4668cde114afee434e7d702a145d","src/format.rs":"5513c537a72af1c222ee7c30b26d4de9d368a69772688b95d88b1a99f6892d5c","src/lib.rs":"6010a5e20b836b8e5c9fba382fde819e6f3c18c0ec2016e6e7e118eabedbcd51","src/log.rs":"c46bae3472043fd076df3229c3421d948a87fae8495c1524b41ab2d8608f612a","src/stream.rs":"56de9f3ad372c123932eca23659f76e7b65fc4502c8da5fe91f3ed35666c306d","src/try_call.rs":"231bfa3f3448f7531427bb228beb2bcd4fd711f0b13d2d8f412af013470f40c7","src/util.rs":"308cfbaacd615ff600e74415c52daeef007fff34a4a0648a73c0042f6067f84f"},"package":"bfd9b2ea1cb6afed9419b0d18fc4093df552ccb2300eb57793629f8cd370b4c8"}
{"files":{"Cargo.toml":"acc28d182a00d78ed6e11c8773d40939016d3df2cd9f3593fae8983eb05db540","LICENSE":"8c044baa5d883274736eeece0b955249076c2697b826e576fce59496235b2cf5","src/builders.rs":"ca97e3a3d1f3fc451c17851c8538964ec67f3964dfe29e902d904ee7445becca","src/channel.rs":"971488fca24a11d49046f8fe9c396765560c070013e5d022afcbe27cfd51d941","src/context.rs":"09625b75070ec88d566a907ab2e574e2d85df4c6df295f798b3372df2cdc8f7a","src/device.rs":"c526d8f992c8ad6d137b0bda803c5182247c9c32797a6f86e0d86d4f6361eb4c","src/device_collection.rs":"f6d0c1628cc34b524f86b84a1e1c79971c3f64ebc4ac64eeb10a1330bbe8c238","src/error.rs":"60454b30bd496dffa94ed9c1bee5a39f29219f2411b76cff6a4f3074f3154701","src/ffi_types.rs":"d815d7a80895b5e86907e708dc0219fca4ac4668cde114afee434e7d702a145d","src/format.rs":"5513c537a72af1c222ee7c30b26d4de9d368a69772688b95d88b1a99f6892d5c","src/lib.rs":"6010a5e20b836b8e5c9fba382fde819e6f3c18c0ec2016e6e7e118eabedbcd51","src/log.rs":"c46bae3472043fd076df3229c3421d948a87fae8495c1524b41ab2d8608f612a","src/stream.rs":"c237f288e6c78597d5a8f53c30927b1aaa40d482f2edb4343991b4d9d0bb1230","src/try_call.rs":"99c59a13db90326613d8cf91909e8a7eaef80646984d10927cbc7cde2cb4b066","src/util.rs":"308cfbaacd615ff600e74415c52daeef007fff34a4a0648a73c0042f6067f84f"},"package":"f7c55529b8f47926e4242e1fc01d31b08a5a4847967c5c250644e33fe237cfe5"}

View file

@ -12,7 +12,7 @@
[package]
name = "cubeb-core"
version = "0.6.2"
version = "0.7.0"
authors = ["Dan Glastonbury <dglastonbury@mozilla.com>"]
description = "Common types and definitions for cubeb rust and C bindings. Not intended for direct use.\n"
homepage = "https://github.com/djg/cubeb-rs"
@ -21,10 +21,10 @@ categories = ["api-bindings"]
license = "ISC"
repository = "https://github.com/djg/cubeb-rs"
[dependencies.bitflags]
version = "1.0"
version = "1.2.0"
[dependencies.cubeb-sys]
version = "0.6.2"
version = "0.7.0"
[features]
gecko-in-tree = ["cubeb-sys/gecko-in-tree"]

View file

@ -88,7 +88,7 @@ mod test {
fn channel_layout_from_raw() {
macro_rules! check(
($($raw:ident => $real:ident),*) => (
$(let x = super::ChannelLayout::from(ffi::$raw);;
$(let x = super::ChannelLayout::from(ffi::$raw);
assert_eq!(x, super::ChannelLayout::$real);
)*
) );

View file

@ -18,8 +18,8 @@ pub enum DeviceState {
Enabled,
}
/// Architecture specific sample type.
bitflags! {
/// Architecture specific sample type.
pub struct DeviceFormat: ffi::cubeb_device_fmt {
const S16LE = ffi::CUBEB_DEVICE_FMT_S16LE;
const S16BE = ffi::CUBEB_DEVICE_FMT_S16BE;
@ -28,10 +28,10 @@ bitflags! {
}
}
/// Channel type for a `cubeb_stream`. Depending on the backend and platform
/// used, this can control inter-stream interruption, ducking, and volume
/// control.
bitflags! {
/// Channel type for a `cubeb_stream`. Depending on the backend and platform
/// used, this can control inter-stream interruption, ducking, and volume
/// control.
pub struct DevicePref: ffi::cubeb_device_pref {
const NONE = ffi::CUBEB_DEVICE_PREF_NONE;
const MULTIMEDIA = ffi::CUBEB_DEVICE_PREF_MULTIMEDIA;
@ -41,9 +41,9 @@ bitflags! {
}
}
/// Whether a particular device is an input device (e.g. a microphone), or an
/// output device (e.g. headphones).
bitflags! {
/// Whether a particular device is an input device (e.g. a microphone), or an
/// output device (e.g. headphones).
pub struct DeviceType: ffi::cubeb_device_type {
const UNKNOWN = ffi::CUBEB_DEVICE_TYPE_UNKNOWN as _;
const INPUT = ffi::CUBEB_DEVICE_TYPE_INPUT as _;
@ -55,8 +55,8 @@ bitflags! {
/// across calls.
pub type DeviceId = ffi::cubeb_devid;
/// Audio device description
ffi_type_heap! {
/// Audio device description
type CType = ffi::cubeb_device;
#[derive(Debug)]
pub struct Device;
@ -91,11 +91,11 @@ impl DeviceRef {
}
}
/// This structure holds the characteristics of an input or output
/// audio device. It is obtained using `enumerate_devices`, which
/// returns these structures via `device_collection` and must be
/// destroyed via `device_collection_destroy`.
ffi_type_stack! {
/// This structure holds the characteristics of an input or output
/// audio device. It is obtained using `enumerate_devices`, which
/// returns these structures via `device_collection` and must be
/// destroyed via `device_collection_destroy`.
type CType = ffi::cubeb_device_info;
pub struct DeviceInfo;
pub struct DeviceInfoRef;

View file

@ -100,8 +100,7 @@ impl error::Error for Error {
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use std::error::Error;
write!(f, "{}", self.description())
write!(f, "{}", self)
}
}

View file

@ -45,8 +45,8 @@ impl Into<ffi::cubeb_state> for State {
}
}
/// Miscellaneous stream preferences.
bitflags! {
/// Miscellaneous stream preferences.
pub struct StreamPrefs: ffi::cubeb_stream_prefs {
const NONE = ffi::CUBEB_STREAM_PREF_NONE;
const LOOPBACK = ffi::CUBEB_STREAM_PREF_LOOPBACK;
@ -55,8 +55,8 @@ bitflags! {
}
}
/// Stream format initialization parameters.
ffi_type_stack!{
/// Stream format initialization parameters.
type CType = ffi::cubeb_stream_params;
#[derive(Debug)]
pub struct StreamParams;
@ -143,6 +143,17 @@ impl StreamRef {
Ok(latency)
}
/// Get the input latency for this stream, in frames. This is the number of frames between the
/// time the audio input device records the audio, and the cubeb callback delivers it.
/// This returns an error if the stream is output-only.
pub fn input_latency(&self) -> Result<u32> {
let mut latency = 0u32;
unsafe {
let _ = try_call!(ffi::cubeb_stream_get_input_latency(self.as_ptr(), &mut latency));
}
Ok(latency)
}
/// Set the volume for a stream.
pub fn set_volume(&self, volume: f32) -> Result<()> {
unsafe { call!(ffi::cubeb_stream_set_volume(self.as_ptr(), volume)) }

View file

@ -22,6 +22,6 @@ macro_rules! call {
macro_rules! try_call {
(ffi::$p:ident ($($e:expr),*)) => ({
try!(::try_call::cvt_r(ffi::$p($($e),*)))
::try_call::cvt_r(ffi::$p($($e),*))?
})
}

View file

@ -1 +1 @@
{"files":{".editorconfig":"4e53b182bcc78b83d7e1b5c03efa14d22d4955c4ed2514d1ba4e99c1eb1a50ba",".githooks/pre-push":"8b8b26544cd56f54c0c33812551f786bb25cb08c86dbfeb6bf3daad881c826a1",".travis.yml":"dc07bac53f70f16c9bdf52264bdc58500ae6018c1b4c567bc7642f6b4ca3cc35","Cargo.toml":"333de4946bd1fbe250d68241d3f085a47d6990053a54999e2b16f3c46779d228","LICENSE":"6e6f56aff5bbf3cbc60747e152fb1a719bd0716aaf6d711c554f57d92e96297c","README.md":"ab0f168080dfdfc1512484aeb4a41e1625f6b4147e534899fab56b2cb6d9f32a","install_git_hook.sh":"d38c8e51e636f6b90b489621ac34ccd1d1b1f40dccce3d178ed1da1c5068f16d","install_rustfmt_clippy.sh":"4ae90d8dcb9757cb3ae4ae142ef80e5377c0dde61c63f4a3c32418646e80ca7b","run_device_tests.sh":"4b7d65eb638c1a278ffc8ecb6d30d47b3b8405392e976cae38c6f744e2bed532","run_sanitizers.sh":"2f0934ba01cbcd74485f19d50147f6b604cf9730bbd3a3d3f3d958e40d0f799f","run_tests.sh":"3dd76659f6dceeb0490dd92b355e113301ba0d0a8f034993a56f40e09edd25b2","src/backend/aggregate_device.rs":"ae21129aa6b3c7bd3376751b6a94d1ebe6c9f7afcd1db3107fb4d703d04da6b3","src/backend/auto_release.rs":"050fdcee74cf46b9a8a85a877e166d72a853d33220f59cf734cbb6ea09daa441","src/backend/buffer_manager.rs":"9c10a261792e32e75833b5f976b18547c338ca6beb2330eeab1ad203cc8c32bf","src/backend/device_property.rs":"d43642ea6e5f40e29c2a59ec7d81b42c154134685e417585045785359aa31686","src/backend/mixer.rs":"14e2156a8c1aeabcd4adb3336c3c9401b9c8526ec82a8c78942af7a79648f0f8","src/backend/mod.rs":"b566a4f853563109b74c84bc519323b42fb96d8af58e4030225aade7cd47d6c6","src/backend/resampler.rs":"fd1281d28a4db1659d2f75e43b8457651745e1b6eb5a53a77f04d752135f6dc7","src/backend/tests/aggregate_device.rs":"107f5c637844cd5ae43d2b42cec4ef3369bb702751586078c0a9d50f039161cd","src/backend/tests/api.rs":"b6692aa2cab751e8d52aee40c8177ce15912338cdbe3daa93d3a25ce9e92031b","src/backend/tests/backlog.rs":"3b189a7e036543c467cc242af0ed3332721179ee2b1c8847a6db563546f1ac52","src/backend/tests/device_change.rs":"8261f561f69dabd374ac47d69aa484812b65070a9e9581dfb2605e8404eaad6d","src/backend/tests/device_property.rs":"373f76d3bee83b263db3f02be3b94b408bdf852d84e4b5153273fda34b11a374","src/backend/tests/interfaces.rs":"14943e84a79976a7ef52882edeb9330350705d5190db6647f98b4ffa851a8396","src/backend/tests/manual.rs":"dc707836dab31f83d4b325afbc4dc4c8104ac8036e87f59ade3309ee83fe2d3f","src/backend/tests/mod.rs":"8dba770023d7f9c4228f0e11915347f0e07da5fd818e3ee4478c4b197af9aa2a","src/backend/tests/parallel.rs":"f9e1883660d6146b6e5075806561f5f689810e25c5e7764dfd28c9b939821a49","src/backend/tests/tone.rs":"16150438317ce501986734167b5fb97bfec567228acbcd8f3b4c4484c22f29e0","src/backend/tests/utils.rs":"1bb99ef71d3c18545bca49767e7b6bfffbe11896246994f41be7ed372772fd48","src/backend/utils.rs":"5ce1b753af0ffb654b6b2038d649aea88eebd27390a607a6d5988a9e40a4a717","src/capi.rs":"21b66b70545bf04ec719928004d1d9adb45b24ced51288f5b2993d79aaf78f5f","src/lib.rs":"5e586d45cd6b3722f0a6736d9252593299269817a153eef1930a5fb9bfbb56f5","todo.md":"7323051fa7f0c51eb2eb0d495dcd951502e4cc8ce0088e6e7b3b3a95180f43d4"},"package":null}
{"files":{".editorconfig":"4e53b182bcc78b83d7e1b5c03efa14d22d4955c4ed2514d1ba4e99c1eb1a50ba",".githooks/pre-push":"8b8b26544cd56f54c0c33812551f786bb25cb08c86dbfeb6bf3daad881c826a1",".travis.yml":"dc07bac53f70f16c9bdf52264bdc58500ae6018c1b4c567bc7642f6b4ca3cc35","Cargo.toml":"93449267612163fe621c75c6195fcf5f961c2bddd975468d67fc2121d538f1c7","LICENSE":"6e6f56aff5bbf3cbc60747e152fb1a719bd0716aaf6d711c554f57d92e96297c","README.md":"ab0f168080dfdfc1512484aeb4a41e1625f6b4147e534899fab56b2cb6d9f32a","install_git_hook.sh":"d38c8e51e636f6b90b489621ac34ccd1d1b1f40dccce3d178ed1da1c5068f16d","install_rustfmt_clippy.sh":"4ae90d8dcb9757cb3ae4ae142ef80e5377c0dde61c63f4a3c32418646e80ca7b","run_device_tests.sh":"4b7d65eb638c1a278ffc8ecb6d30d47b3b8405392e976cae38c6f744e2bed532","run_sanitizers.sh":"2f0934ba01cbcd74485f19d50147f6b604cf9730bbd3a3d3f3d958e40d0f799f","run_tests.sh":"3dd76659f6dceeb0490dd92b355e113301ba0d0a8f034993a56f40e09edd25b2","src/backend/aggregate_device.rs":"ae21129aa6b3c7bd3376751b6a94d1ebe6c9f7afcd1db3107fb4d703d04da6b3","src/backend/auto_release.rs":"050fdcee74cf46b9a8a85a877e166d72a853d33220f59cf734cbb6ea09daa441","src/backend/buffer_manager.rs":"9c10a261792e32e75833b5f976b18547c338ca6beb2330eeab1ad203cc8c32bf","src/backend/device_property.rs":"d43642ea6e5f40e29c2a59ec7d81b42c154134685e417585045785359aa31686","src/backend/mixer.rs":"14e2156a8c1aeabcd4adb3336c3c9401b9c8526ec82a8c78942af7a79648f0f8","src/backend/mod.rs":"4c2967b7f581cebce875ea342658d32650695ebd57a026bd36e92ef3807866ae","src/backend/resampler.rs":"fd1281d28a4db1659d2f75e43b8457651745e1b6eb5a53a77f04d752135f6dc7","src/backend/tests/aggregate_device.rs":"107f5c637844cd5ae43d2b42cec4ef3369bb702751586078c0a9d50f039161cd","src/backend/tests/api.rs":"9ce44a867519d7b7a2b43c7f833327c35be38af7ba6fcc3d277ed1d7d8e7c8c2","src/backend/tests/backlog.rs":"3b189a7e036543c467cc242af0ed3332721179ee2b1c8847a6db563546f1ac52","src/backend/tests/device_change.rs":"8261f561f69dabd374ac47d69aa484812b65070a9e9581dfb2605e8404eaad6d","src/backend/tests/device_property.rs":"373f76d3bee83b263db3f02be3b94b408bdf852d84e4b5153273fda34b11a374","src/backend/tests/interfaces.rs":"14943e84a79976a7ef52882edeb9330350705d5190db6647f98b4ffa851a8396","src/backend/tests/manual.rs":"dc707836dab31f83d4b325afbc4dc4c8104ac8036e87f59ade3309ee83fe2d3f","src/backend/tests/mod.rs":"8dba770023d7f9c4228f0e11915347f0e07da5fd818e3ee4478c4b197af9aa2a","src/backend/tests/parallel.rs":"f9e1883660d6146b6e5075806561f5f689810e25c5e7764dfd28c9b939821a49","src/backend/tests/tone.rs":"16150438317ce501986734167b5fb97bfec567228acbcd8f3b4c4484c22f29e0","src/backend/tests/utils.rs":"1bb99ef71d3c18545bca49767e7b6bfffbe11896246994f41be7ed372772fd48","src/backend/utils.rs":"5ce1b753af0ffb654b6b2038d649aea88eebd27390a607a6d5988a9e40a4a717","src/capi.rs":"21b66b70545bf04ec719928004d1d9adb45b24ced51288f5b2993d79aaf78f5f","src/lib.rs":"5e586d45cd6b3722f0a6736d9252593299269817a153eef1930a5fb9bfbb56f5","todo.md":"7323051fa7f0c51eb2eb0d495dcd951502e4cc8ce0088e6e7b3b3a95180f43d4"},"package":null}

View file

@ -11,7 +11,7 @@ crate-type = ["staticlib", "rlib"]
atomic = "0.4"
bitflags = "1.0"
coreaudio-sys-utils = { path = "coreaudio-sys-utils" }
cubeb-backend = "0.6.3"
cubeb-backend = "0.7"
float-cmp = "0.6"
libc = "0.2"
lazy_static = "1.2"

View file

@ -344,6 +344,10 @@ extern "C" fn audiounit_input_callback(
assert!(!user_ptr.is_null());
let stm = unsafe { &mut *(user_ptr as *mut AudioUnitStream) };
let input_latency_frames = compute_input_latency(&stm, unsafe { (*tstamp).mHostTime });
stm.total_input_latency_frames
.store(input_latency_frames, Ordering::SeqCst);
if stm.shutdown.load(Ordering::SeqCst) {
cubeb_log!("({:p}) input shutdown", stm as *const AudioUnitStream);
return NO_ERR;
@ -501,7 +505,24 @@ fn compute_output_latency(stm: &AudioUnitStream, host_time: u64) -> u32 {
// the hardware latency.
let out_hw_rate = stm.core_stream_data.output_hw_rate as u64;
(output_latency_ns * out_hw_rate / NS2S
+ stm.current_latency_frames.load(Ordering::SeqCst) as u64) as u32
+ stm.current_output_latency_frames.load(Ordering::SeqCst) as u64) as u32
}
fn compute_input_latency(stm: &AudioUnitStream, host_time: u64) -> u32 {
let now = host_time_to_ns(unsafe { mach_absolute_time() });
let audio_input_time = host_time_to_ns(host_time);
let input_latency_ns = if audio_input_time > now {
0
} else {
now - audio_input_time
};
const NS2S: u64 = 1_000_000_000;
// The total input latency is the timestamp difference + the stream latency +
// the hardware latency.
let input_hw_rate = stm.core_stream_data.input_hw_rate as u64;
(input_latency_ns * input_hw_rate / NS2S
+ stm.current_input_latency_frames.load(Ordering::SeqCst) as u64) as u32
}
extern "C" fn audiounit_output_callback(
@ -1337,7 +1358,7 @@ fn get_range_of_sample_rates(
Ok((min, max))
}
fn get_presentation_latency(devid: AudioObjectID, devtype: DeviceType) -> u32 {
fn get_fixed_latency(devid: AudioObjectID, devtype: DeviceType) -> u32 {
let device_latency = match get_device_latency(devid, devtype) {
Ok(latency) => latency,
Err(e) => {
@ -1587,7 +1608,7 @@ fn create_cubeb_device_info(
}
}
let latency = get_presentation_latency(devid, devtype);
let latency = get_fixed_latency(devid, devtype);
let (latency_low, latency_high) = match get_device_buffer_frame_size_range(devid, devtype) {
Ok(range) => (
@ -2813,6 +2834,11 @@ impl<'ctx> CoreStreamData<'ctx> {
cubeb_log!("AudioUnitInitialize/input rv={}", r);
return Err(Error::error());
}
stream.current_input_latency_frames.store(
get_fixed_latency(self.input_device.id, DeviceType::INPUT),
Ordering::SeqCst,
);
}
if !self.output_unit.is_null() {
@ -2822,8 +2848,8 @@ impl<'ctx> CoreStreamData<'ctx> {
return Err(Error::error());
}
stream.current_latency_frames.store(
get_presentation_latency(self.output_device.id, DeviceType::OUTPUT),
stream.current_output_latency_frames.store(
get_fixed_latency(self.output_device.id, DeviceType::OUTPUT),
Ordering::SeqCst,
);
@ -2838,7 +2864,7 @@ impl<'ctx> CoreStreamData<'ctx> {
&mut size,
) == NO_ERR
{
stream.current_latency_frames.fetch_add(
stream.current_output_latency_frames.fetch_add(
(unit_s * self.output_desc.mSampleRate) as u32,
Ordering::SeqCst,
);
@ -3115,8 +3141,10 @@ struct AudioUnitStream<'ctx> {
destroy_pending: AtomicBool,
// Latency requested by the user.
latency_frames: u32,
current_latency_frames: AtomicU32,
current_output_latency_frames: AtomicU32,
current_input_latency_frames: AtomicU32,
total_output_latency_frames: AtomicU32,
total_input_latency_frames: AtomicU32,
// This is true if a device change callback is currently running.
switching_device: AtomicBool,
core_stream_data: CoreStreamData<'ctx>,
@ -3146,8 +3174,10 @@ impl<'ctx> AudioUnitStream<'ctx> {
reinit_pending: AtomicBool::new(false),
destroy_pending: AtomicBool::new(false),
latency_frames,
current_latency_frames: AtomicU32::new(0),
current_output_latency_frames: AtomicU32::new(0),
current_input_latency_frames: AtomicU32::new(0),
total_output_latency_frames: AtomicU32::new(0),
total_input_latency_frames: AtomicU32::new(0),
switching_device: AtomicBool::new(false),
core_stream_data: CoreStreamData::default(),
}
@ -3428,12 +3458,13 @@ impl<'ctx> StreamOps for AudioUnitStream<'ctx> {
Err(Error::not_supported())
}
fn position(&mut self) -> Result<u64> {
let current_latency_frames = u64::from(self.current_latency_frames.load(Ordering::SeqCst));
let current_output_latency_frames =
u64::from(self.current_output_latency_frames.load(Ordering::SeqCst));
let frames_played = self.frames_played.load(Ordering::SeqCst);
let position = if current_latency_frames > frames_played {
let position = if current_output_latency_frames > frames_played {
0
} else {
frames_played - current_latency_frames
frames_played - current_output_latency_frames
};
Ok(position)
}
@ -3445,6 +3476,21 @@ impl<'ctx> StreamOps for AudioUnitStream<'ctx> {
fn latency(&mut self) -> Result<u32> {
Ok(self.total_output_latency_frames.load(Ordering::SeqCst))
}
#[cfg(target_os = "ios")]
fn input_latency(&mut self) -> Result<u32> {
Err(not_supported())
}
#[cfg(not(target_os = "ios"))]
fn input_latency(&mut self) -> Result<u32> {
let user_rate = self.core_stream_data.input_stream_params.rate();
let hw_rate = self.core_stream_data.input_hw_rate as u32;
let frames = self.total_input_latency_frames.load(Ordering::SeqCst);
if hw_rate == user_rate {
Ok(frames)
} else {
Ok(frames * (user_rate / hw_rate))
}
}
fn set_volume(&mut self, volume: f32) -> Result<()> {
set_volume(self.core_stream_data.output_unit, volume)
}

View file

@ -1200,7 +1200,7 @@ fn test_get_device_presentation_latency() {
fn test_get_device_presentation_latencies_in_scope(scope: Scope) {
if let Some(device) = test_get_default_device(scope.clone()) {
// TODO: The latencies very from devices to devices. Check nothing here.
let latency = get_presentation_latency(device, scope.clone().into());
let latency = get_fixed_latency(device, scope.clone().into());
println!(
"present latency on the device {} in scope {:?}: {}",
device, scope, latency

View file

@ -1 +1 @@
{"files":{".editorconfig":"bf047bd1da10cabb99eea666d1e57c321eba4716dccb3e4ed0e2c5fe3ca53858",".travis.yml":"0394e2adb041175457685cde5ee05ff04bdab8885fd8a62551f2ff43d9e48872","AUTHORS":"0e0ac930a68ce2f6b876126b195add177f0d3886facb9260f4d9b69f1988f0cc","Cargo.toml":"297f0144bbebcbc2f3b5478fed750f39adb3b60f9d6c273dfa72d9736818423d","LICENSE":"44c6b5ae5ec3fe2fbc608b00e6f4896f4d2d5c7e525fcbaa3eaa3cf2f3d5a983","README.md":"e6a98ee5630b9ce1a096a2907d095454f2770e298a5b0976ab552cc53ca96cfc","src/backend/context.rs":"33d9fdf1504fe1ae43d301e288daf6eaeabeb47aa0ef86efa135c6d984425fc4","src/backend/cork_state.rs":"4a0f1afc7d9f333dac89218cc56d7d32fbffb487cd48c1c9a4e03d79cb3b5e28","src/backend/intern.rs":"374a9a3bd79fddc47739dda1dbfc5929aea5a91946794fe65fba3c8d130fbda9","src/backend/mod.rs":"06ce9250865abf0ea461f215b128470636d072a6776821efef3caf5a7b992fb9","src/backend/stream.rs":"8ff67b76b8663a952fe2d2f7d370a05271f156601c929d19caa56fc892a4a4ab","src/capi.rs":"b2c1be8128cadd36caa65c80950440f9d6f2aa0c24cc7bae6a9eaf6347ac454d","src/lib.rs":"7282560d84b134b09acfd8d6282600982e42fb3557f72454c535637cc26c7bf6"},"package":null}
{"files":{".editorconfig":"bf047bd1da10cabb99eea666d1e57c321eba4716dccb3e4ed0e2c5fe3ca53858",".travis.yml":"0394e2adb041175457685cde5ee05ff04bdab8885fd8a62551f2ff43d9e48872","AUTHORS":"0e0ac930a68ce2f6b876126b195add177f0d3886facb9260f4d9b69f1988f0cc","Cargo.toml":"603fb672fb83699707559d13a651963321991b9bec6ea208723d3ee37c40694e","LICENSE":"44c6b5ae5ec3fe2fbc608b00e6f4896f4d2d5c7e525fcbaa3eaa3cf2f3d5a983","README.md":"e6a98ee5630b9ce1a096a2907d095454f2770e298a5b0976ab552cc53ca96cfc","src/backend/context.rs":"33d9fdf1504fe1ae43d301e288daf6eaeabeb47aa0ef86efa135c6d984425fc4","src/backend/cork_state.rs":"4a0f1afc7d9f333dac89218cc56d7d32fbffb487cd48c1c9a4e03d79cb3b5e28","src/backend/intern.rs":"374a9a3bd79fddc47739dda1dbfc5929aea5a91946794fe65fba3c8d130fbda9","src/backend/mod.rs":"06ce9250865abf0ea461f215b128470636d072a6776821efef3caf5a7b992fb9","src/backend/stream.rs":"ce537c89dc5329f19d287e547b6c556a836ef42439cffce9d2cf3eaa18a65a0b","src/capi.rs":"b2c1be8128cadd36caa65c80950440f9d6f2aa0c24cc7bae6a9eaf6347ac454d","src/lib.rs":"7282560d84b134b09acfd8d6282600982e42fb3557f72454c535637cc26c7bf6"},"package":null}

View file

@ -12,7 +12,7 @@ pulse-dlopen = ["pulse-ffi/dlopen"]
crate-type = ["staticlib", "rlib"]
[dependencies]
cubeb-backend = "0.6"
cubeb-backend = "0.7"
pulse-ffi = { path = "pulse-ffi" }
pulse = { path = "pulse-rs" }
semver = "^0.9"

View file

@ -642,6 +642,10 @@ impl<'ctx> StreamOps for PulseStream<'ctx> {
}
}
fn input_latency(&mut self) -> Result<u32> {
Err(Error::error())
}
fn set_volume(&mut self, volume: f32) -> Result<()> {
match self.output_stream {
None => Err(Error::error()),

File diff suppressed because one or more lines are too long

View file

@ -12,7 +12,7 @@
[package]
name = "cubeb-sys"
version = "0.6.2"
version = "0.7.0"
authors = ["Dan Glastonbury <dglastonbury@mozilla.com>"]
build = "build.rs"
links = "cubeb"

View file

@ -40,6 +40,7 @@ fn main() {
// let host = env::var("HOST").unwrap();
let windows = target.contains("windows");
let darwin = target.contains("darwin");
let freebsd = target.contains("freebsd");
let mut cfg = cmake::Config::new("libcubeb");
let _ = fs::remove_dir_all(env::var("OUT_DIR").unwrap());
@ -48,25 +49,28 @@ fn main() {
env::remove_var("DESTDIR");
let dst = cfg.define("BUILD_SHARED_LIBS", "OFF")
.define("BUILD_TESTS", "OFF")
.define("BUILD_TOOLS", "OFF")
.build();
println!("cargo:rustc-link-lib=static=cubeb");
if windows {
println!("cargo:rustc-link-lib=static=cubeb");
println!("cargo:rustc-link-lib=dylib=avrt");
println!("cargo:rustc-link-lib=dylib=ole32");
println!("cargo:rustc-link-lib=dylib=user32");
println!("cargo:rustc-link-lib=dylib=winmm");
println!("cargo:rustc-link-search=native={}/lib", dst.display());
} else if darwin {
println!("cargo:rustc-link-lib=static=cubeb");
println!("cargo:rustc-link-lib=framework=AudioUnit");
println!("cargo:rustc-link-lib=framework=CoreAudio");
println!("cargo:rustc-link-lib=framework=CoreServices");
println!("cargo:rustc-link-lib=dylib=c++");
println!("cargo:rustc-link-search=native={}/lib", dst.display());
} else {
println!("cargo:rustc-link-lib=static=cubeb");
println!("cargo:rustc-link-lib=dylib=stdc++");
if freebsd {
println!("cargo:rustc-link-lib=dylib=c++");
} else {
println!("cargo:rustc-link-lib=dylib=stdc++");
}
println!("cargo:rustc-link-search=native={}/lib", dst.display());
println!("cargo:rustc-link-search=native={}/lib64", dst.display());

View file

@ -155,10 +155,7 @@ if(USE_PULSE)
target_sources(cubeb PRIVATE
src/cubeb_pulse.c)
target_compile_definitions(cubeb PRIVATE USE_PULSE)
target_link_libraries(cubeb PRIVATE pulse pthread)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
target_link_libraries(cubeb PRIVATE dl)
endif()
target_link_libraries(cubeb PRIVATE pthread ${CMAKE_DL_LIBS})
endif()
check_include_files(alsa/asoundlib.h USE_ALSA)
@ -166,10 +163,7 @@ if(USE_ALSA)
target_sources(cubeb PRIVATE
src/cubeb_alsa.c)
target_compile_definitions(cubeb PRIVATE USE_ALSA)
target_link_libraries(cubeb PRIVATE asound pthread)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
target_link_libraries(cubeb PRIVATE dl)
endif()
target_link_libraries(cubeb PRIVATE pthread ${CMAKE_DL_LIBS})
endif()
check_include_files(jack/jack.h USE_JACK)
@ -177,10 +171,7 @@ if(USE_JACK)
target_sources(cubeb PRIVATE
src/cubeb_jack.cpp)
target_compile_definitions(cubeb PRIVATE USE_JACK)
target_link_libraries(cubeb PRIVATE jack pthread)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
target_link_libraries(cubeb PRIVATE dl)
endif()
target_link_libraries(cubeb PRIVATE pthread ${CMAKE_DL_LIBS})
endif()
check_include_files(audioclient.h USE_WASAPI)
@ -221,7 +212,7 @@ if(USE_SNDIO)
target_sources(cubeb PRIVATE
src/cubeb_sndio.c)
target_compile_definitions(cubeb PRIVATE USE_SNDIO)
target_link_libraries(cubeb PRIVATE sndio)
target_link_libraries(cubeb PRIVATE pthread ${CMAKE_DL_LIBS})
endif()
check_include_files(sys/audioio.h USE_SUN)
@ -248,6 +239,7 @@ if(USE_PULSE_RUST)
DOWNLOAD_COMMAND ""
CONFIGURE_COMMAND ""
BUILD_COMMAND cargo build COMMAND cargo build --release
BUILD_ALWAYS ON
BINARY_DIR "${CMAKE_SOURCE_DIR}/src/cubeb-pulse-rs"
INSTALL_COMMAND ""
LOG_BUILD ON)
@ -266,6 +258,7 @@ if(USE_AUDIOUNIT_RUST)
DOWNLOAD_COMMAND ""
CONFIGURE_COMMAND ""
BUILD_COMMAND cargo build COMMAND cargo build --release
BUILD_ALWAYS ON
BINARY_DIR "${CMAKE_SOURCE_DIR}/src/cubeb-coreaudio-rs"
INSTALL_COMMAND ""
LOG_BUILD ON)

View file

@ -3,4 +3,6 @@
See INSTALL.md for build instructions.
See [Backend Support](https://github.com/kinetiknz/cubeb/wiki/Backend-Support) in the wiki for the support level of each backend.
Licensed under an ISC-style license. See LICENSE for details.

View file

@ -1,41 +0,0 @@
TODO:
- directsound: incomplete and somewhat broken
- osx: understand why AudioQueueGetCurrentTime can return negative mSampleTime
- test (and fix) sub-prefill size data playback
- report stream delay instead of position; leave position calculation to user
- capture support
- capture and output enumeration and configuration
- also expose default hardware config to allow decisions on speaker layout
- prefill occurs at different times in each backend:
- pulse prefills async off worker thread after init
- coreaudio prefills during init
- alsa prefills async after start
- expose configured prefill size; may differ from requested latency
- solved by exposing stream delay
- xruns may occur in user callback but also in audio hardware
may need to expose details of hardware xruns to user api
- document thread safety
- document which calls may block, and when effects take effect
- document what's permissible inside callbacks
- implement basic channel mapping for surround
- vorbis has documented mapping based on channel count (if mapping type ==
0) -- http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-800004.3.9
1 -> M
2 -> L, R
3 -> L, C, R
4 -> L, R, RL, RR
5 -> L, C, R, RL, RR
6 -> L, C, R, RL, RR, LFE
7 -> L, C, R, SL, SR, RC, LFE
8 -> L, C, R, SL, SR, RL, RR, LFE
>8 -> application defined
- wave files with channel count only
3 -> L, R, C
4 -> L, R, RL, RR
5 -> L, R, C, RL, RR
6 -> L, R, C, LFE, RL, RR
7 -> L, R, C, LFE, RC, SL, SR
8 -> L, R, C, LFE, RL, RR, SL, SR
- wave files with WAVE_FORMAT_EXTENSIBLE have explicitly mappings, can
extract these
- implement configurable channel mapping

View file

@ -31,19 +31,13 @@ extern "C" {
@code
cubeb * app_ctx;
cubeb_init(&app_ctx, "Example Application");
cubeb_init(&app_ctx, "Example Application", NULL);
int rv;
uint32_t rate;
uint32_t latency_frames;
uint64_t ts;
rv = cubeb_get_min_latency(app_ctx, &output_params, &latency_frames);
if (rv != CUBEB_OK) {
fprintf(stderr, "Could not get minimum latency");
return rv;
}
rv = cubeb_get_preferred_sample_rate(app_ctx, output_params, &rate);
rv = cubeb_get_preferred_sample_rate(app_ctx, &rate);
if (rv != CUBEB_OK) {
fprintf(stderr, "Could not get preferred sample-rate");
return rv;
@ -56,6 +50,12 @@ extern "C" {
output_params.layout = CUBEB_LAYOUT_UNDEFINED;
output_params.prefs = CUBEB_STREAM_PREF_NONE;
rv = cubeb_get_min_latency(app_ctx, &output_params, &latency_frames);
if (rv != CUBEB_OK) {
fprintf(stderr, "Could not get minimum latency");
return rv;
}
cubeb_stream_params input_params;
input_params.format = CUBEB_SAMPLE_FLOAT32NE;
input_params.rate = rate;
@ -97,14 +97,14 @@ extern "C" {
@code
long data_cb(cubeb_stream * stm, void * user,
void * input_buffer, void * output_buffer, long nframes)
const void * input_buffer, void * output_buffer, long nframes)
{
float * in = input_buffer;
const float * in = input_buffer;
float * out = output_buffer;
for (i = 0; i < nframes; ++i) {
for (c = 0; c < 2; ++c) {
out[i][c] = in[i];
for (int i = 0; i < nframes; ++i) {
for (int c = 0; c < 2; ++c) {
out[2 * i + c] = in[i];
}
}
return nframes;
@ -559,6 +559,16 @@ CUBEB_EXPORT int cubeb_stream_get_position(cubeb_stream * stream, uint64_t * pos
@retval CUBEB_ERROR */
CUBEB_EXPORT int cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency);
/** Get the input latency for this stream, in frames. This is the number of
frames between the time the audio input devices records the data, and they
are available in the data callback.
This returns CUBEB_ERROR when the stream is output-only.
@param stream
@param latency Current approximate stream latency in frames.
@retval CUBEB_OK
@retval CUBEB_ERROR_NOT_SUPPORTED
@retval CUBEB_ERROR */
CUBEB_EXPORT int cubeb_stream_get_input_latency(cubeb_stream * stream, uint32_t * latency);
/** Set the volume for a stream.
@param stream the stream for which to adjust the volume.
@param volume a float between 0.0 (muted) and 1.0 (maximum volume)

View file

@ -35,7 +35,7 @@ cubeb_output_latency_load_method(int version)
bool
cubeb_output_latency_method_is_loaded(output_latency_function * ol)
{
assert(ol && (ol->from_jni || ol->from_lib));
assert(ol);
if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2){
return !!ol->from_jni;
}

View file

@ -63,6 +63,7 @@ struct cubeb_ops {
int (* stream_reset_default_device)(cubeb_stream * stream);
int (* stream_get_position)(cubeb_stream * stream, uint64_t * position);
int (* stream_get_latency)(cubeb_stream * stream, uint32_t * latency);
int (* stream_get_input_latency)(cubeb_stream * stream, uint32_t * latency);
int (* stream_set_volume)(cubeb_stream * stream, float volumes);
int (* stream_get_current_device)(cubeb_stream * stream,
cubeb_device ** const device);

View file

@ -80,7 +80,7 @@ validate_stream_params(cubeb_stream_params * input_stream_params,
}
if (input_stream_params) {
if (input_stream_params->rate < 1000 || input_stream_params->rate > 192000 ||
input_stream_params->channels < 1 || input_stream_params->channels > 8) {
input_stream_params->channels < 1 || input_stream_params->channels > UINT8_MAX) {
return CUBEB_ERROR_INVALID_FORMAT;
}
}
@ -194,6 +194,9 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam
#if defined(USE_JACK)
jack_init,
#endif
#if defined(USE_SNDIO)
sndio_init,
#endif
#if defined(USE_ALSA)
alsa_init,
#endif
@ -209,9 +212,6 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam
#if defined(USE_WINMM)
winmm_init,
#endif
#if defined(USE_SNDIO)
sndio_init,
#endif
#if defined(USE_SUN)
sun_init,
#endif
@ -420,6 +420,20 @@ cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
return stream->context->ops->stream_get_latency(stream, latency);
}
int
cubeb_stream_get_input_latency(cubeb_stream * stream, uint32_t * latency)
{
if (!stream || !latency) {
return CUBEB_ERROR_INVALID_PARAMETER;
}
if (!stream->context->ops->stream_get_input_latency) {
return CUBEB_ERROR_NOT_SUPPORTED;
}
return stream->context->ops->stream_get_input_latency(stream, latency);
}
int
cubeb_stream_set_volume(cubeb_stream * stream, float volume)
{

View file

@ -14,10 +14,58 @@
#include <limits.h>
#include <poll.h>
#include <unistd.h>
#include <dlfcn.h>
#include <alsa/asoundlib.h>
#include "cubeb/cubeb.h"
#include "cubeb-internal.h"
#ifdef DISABLE_LIBASOUND_DLOPEN
#define WRAP(x) x
#else
#define WRAP(x) cubeb_##x
#define LIBASOUND_API_VISIT(X) \
X(snd_config) \
X(snd_config_add) \
X(snd_config_copy) \
X(snd_config_delete) \
X(snd_config_get_id) \
X(snd_config_get_string) \
X(snd_config_imake_integer) \
X(snd_config_search) \
X(snd_config_search_definition) \
X(snd_lib_error_set_handler) \
X(snd_pcm_avail_update) \
X(snd_pcm_close) \
X(snd_pcm_delay) \
X(snd_pcm_drain) \
X(snd_pcm_frames_to_bytes) \
X(snd_pcm_get_params) \
X(snd_pcm_hw_params_any) \
X(snd_pcm_hw_params_get_channels_max) \
X(snd_pcm_hw_params_get_rate) \
X(snd_pcm_hw_params_set_rate_near) \
X(snd_pcm_hw_params_sizeof) \
X(snd_pcm_nonblock) \
X(snd_pcm_open) \
X(snd_pcm_open_lconf) \
X(snd_pcm_pause) \
X(snd_pcm_poll_descriptors) \
X(snd_pcm_poll_descriptors_count) \
X(snd_pcm_poll_descriptors_revents) \
X(snd_pcm_readi) \
X(snd_pcm_recover) \
X(snd_pcm_set_params) \
X(snd_pcm_start) \
X(snd_pcm_state) \
X(snd_pcm_writei) \
#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
LIBASOUND_API_VISIT(MAKE_TYPEDEF);
#undef MAKE_TYPEDEF
/* snd_pcm_hw_params_alloca is actually a macro */
#define snd_pcm_hw_params_sizeof cubeb_snd_pcm_hw_params_sizeof
#endif
#define CUBEB_STREAM_MAX 16
#define CUBEB_WATCHDOG_MS 10000
@ -36,6 +84,7 @@ static struct cubeb_ops const alsa_ops;
struct cubeb {
struct cubeb_ops const * ops;
void * libasound;
pthread_t thread;
@ -245,8 +294,8 @@ set_timeout(struct timeval * timeout, unsigned int ms)
static void
stream_buffer_decrement(cubeb_stream * stm, long count)
{
char * bufremains = stm->buffer + snd_pcm_frames_to_bytes(stm->pcm, count);
memmove(stm->buffer, bufremains, snd_pcm_frames_to_bytes(stm->pcm, stm->bufframes - count));
char * bufremains = stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, count);
memmove(stm->buffer, bufremains, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes - count));
stm->bufframes -= count;
}
@ -278,9 +327,9 @@ alsa_process_stream(cubeb_stream * stm)
/* Call _poll_descriptors_revents() even if we don't use it
to let underlying plugins clear null events. Otherwise poll()
may wake up again and again, producing unnecessary CPU usage. */
snd_pcm_poll_descriptors_revents(stm->pcm, stm->fds, stm->nfds, &revents);
WRAP(snd_pcm_poll_descriptors_revents)(stm->pcm, stm->fds, stm->nfds, &revents);
avail = snd_pcm_avail_update(stm->pcm);
avail = WRAP(snd_pcm_avail_update)(stm->pcm);
/* Got null event? Bail and wait for another wakeup. */
if (avail == 0) {
@ -303,7 +352,7 @@ alsa_process_stream(cubeb_stream * stm)
// TODO: should it be marked as DRAINING?
}
got = snd_pcm_readi(stm->pcm, stm->buffer+stm->bufframes, avail);
got = WRAP(snd_pcm_readi)(stm->pcm, stm->buffer+stm->bufframes, avail);
if (got < 0) {
avail = got; // the error handler below will recover us
@ -347,7 +396,7 @@ alsa_process_stream(cubeb_stream * stm)
(!stm->other_stream || stm->other_stream->bufframes > 0)) {
long got = avail - stm->bufframes;
void * other_buffer = stm->other_stream ? stm->other_stream->buffer : NULL;
char * buftail = stm->buffer + snd_pcm_frames_to_bytes(stm->pcm, stm->bufframes);
char * buftail = stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes);
/* Correct read size to the other stream available frames */
if (stm->other_stream && got > (snd_pcm_sframes_t) stm->other_stream->bufframes) {
@ -374,8 +423,8 @@ alsa_process_stream(cubeb_stream * stm)
long drain_frames = avail - stm->bufframes;
double drain_time = (double) drain_frames / stm->params.rate;
char * buftail = stm->buffer + snd_pcm_frames_to_bytes(stm->pcm, stm->bufframes);
memset(buftail, 0, snd_pcm_frames_to_bytes(stm->pcm, drain_frames));
char * buftail = stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes);
memset(buftail, 0, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, drain_frames));
stm->bufframes = avail;
/* Mark as draining, unless we're waiting for capture */
@ -402,7 +451,7 @@ alsa_process_stream(cubeb_stream * stm)
}
}
wrote = snd_pcm_writei(stm->pcm, stm->buffer, avail);
wrote = WRAP(snd_pcm_writei)(stm->pcm, stm->buffer, avail);
if (wrote < 0) {
avail = wrote; // the error handler below will recover us
} else {
@ -415,13 +464,13 @@ alsa_process_stream(cubeb_stream * stm)
/* Got some error? Let's try to recover the stream. */
if (avail < 0) {
avail = snd_pcm_recover(stm->pcm, avail, 0);
avail = WRAP(snd_pcm_recover)(stm->pcm, avail, 0);
/* Capture pcm must be started after initial setup/recover */
if (avail >= 0 &&
stm->stream_type == SND_PCM_STREAM_CAPTURE &&
snd_pcm_state(stm->pcm) == SND_PCM_STATE_PREPARED) {
avail = snd_pcm_start(stm->pcm);
WRAP(snd_pcm_state)(stm->pcm) == SND_PCM_STATE_PREPARED) {
avail = WRAP(snd_pcm_start)(stm->pcm);
}
}
@ -537,26 +586,26 @@ get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm)
slave_def = NULL;
r = snd_config_search(root_pcm, "slave", &slave_pcm);
r = WRAP(snd_config_search)(root_pcm, "slave", &slave_pcm);
if (r < 0) {
return NULL;
}
r = snd_config_get_string(slave_pcm, &string);
r = WRAP(snd_config_get_string)(slave_pcm, &string);
if (r >= 0) {
r = snd_config_search_definition(lconf, "pcm_slave", string, &slave_def);
r = WRAP(snd_config_search_definition)(lconf, "pcm_slave", string, &slave_def);
if (r < 0) {
return NULL;
}
}
do {
r = snd_config_search(slave_def ? slave_def : slave_pcm, "pcm", &pcm);
r = WRAP(snd_config_search)(slave_def ? slave_def : slave_pcm, "pcm", &pcm);
if (r < 0) {
break;
}
r = snd_config_get_string(slave_def ? slave_def : slave_pcm, &string);
r = WRAP(snd_config_get_string)(slave_def ? slave_def : slave_pcm, &string);
if (r < 0) {
break;
}
@ -565,7 +614,7 @@ get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm)
if (r < 0 || r > (int) sizeof(node_name)) {
break;
}
r = snd_config_search(lconf, node_name, &pcm);
r = WRAP(snd_config_search)(lconf, node_name, &pcm);
if (r < 0) {
break;
}
@ -574,7 +623,7 @@ get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm)
} while (0);
if (slave_def) {
snd_config_delete(slave_def);
WRAP(snd_config_delete)(slave_def);
}
return NULL;
@ -597,22 +646,22 @@ init_local_config_with_workaround(char const * pcm_name)
lconf = NULL;
if (snd_config == NULL) {
if (*WRAP(snd_config) == NULL) {
return NULL;
}
r = snd_config_copy(&lconf, snd_config);
r = WRAP(snd_config_copy)(&lconf, *WRAP(snd_config));
if (r < 0) {
return NULL;
}
do {
r = snd_config_search_definition(lconf, "pcm", pcm_name, &pcm_node);
r = WRAP(snd_config_search_definition)(lconf, "pcm", pcm_name, &pcm_node);
if (r < 0) {
break;
}
r = snd_config_get_id(pcm_node, &string);
r = WRAP(snd_config_get_id)(pcm_node, &string);
if (r < 0) {
break;
}
@ -621,7 +670,7 @@ init_local_config_with_workaround(char const * pcm_name)
if (r < 0 || r > (int) sizeof(node_name)) {
break;
}
r = snd_config_search(lconf, node_name, &pcm_node);
r = WRAP(snd_config_search)(lconf, node_name, &pcm_node);
if (r < 0) {
break;
}
@ -632,12 +681,12 @@ init_local_config_with_workaround(char const * pcm_name)
}
/* Fetch the PCM node's type, and bail out if it's not the PulseAudio plugin. */
r = snd_config_search(pcm_node, "type", &node);
r = WRAP(snd_config_search)(pcm_node, "type", &node);
if (r < 0) {
break;
}
r = snd_config_get_string(node, &string);
r = WRAP(snd_config_get_string)(node, &string);
if (r < 0) {
break;
}
@ -648,18 +697,18 @@ init_local_config_with_workaround(char const * pcm_name)
/* Don't clobber an explicit existing handle_underrun value, set it only
if it doesn't already exist. */
r = snd_config_search(pcm_node, "handle_underrun", &node);
r = WRAP(snd_config_search)(pcm_node, "handle_underrun", &node);
if (r != -ENOENT) {
break;
}
/* Disable pcm_pulse's asynchronous underrun handling. */
r = snd_config_imake_integer(&node, "handle_underrun", 0);
r = WRAP(snd_config_imake_integer)(&node, "handle_underrun", 0);
if (r < 0) {
break;
}
r = snd_config_add(pcm_node, node);
r = WRAP(snd_config_add)(pcm_node, node);
if (r < 0) {
break;
}
@ -667,7 +716,7 @@ init_local_config_with_workaround(char const * pcm_name)
return lconf;
} while (0);
snd_config_delete(lconf);
WRAP(snd_config_delete)(lconf);
return NULL;
}
@ -679,9 +728,9 @@ alsa_locked_pcm_open(snd_pcm_t ** pcm, char const * pcm_name, snd_pcm_stream_t s
pthread_mutex_lock(&cubeb_alsa_mutex);
if (local_config) {
r = snd_pcm_open_lconf(pcm, pcm_name, stream, SND_PCM_NONBLOCK, local_config);
r = WRAP(snd_pcm_open_lconf)(pcm, pcm_name, stream, SND_PCM_NONBLOCK, local_config);
} else {
r = snd_pcm_open(pcm, pcm_name, stream, SND_PCM_NONBLOCK);
r = WRAP(snd_pcm_open)(pcm, pcm_name, stream, SND_PCM_NONBLOCK);
}
pthread_mutex_unlock(&cubeb_alsa_mutex);
@ -694,7 +743,7 @@ alsa_locked_pcm_close(snd_pcm_t * pcm)
int r;
pthread_mutex_lock(&cubeb_alsa_mutex);
r = snd_pcm_close(pcm);
r = WRAP(snd_pcm_close)(pcm);
pthread_mutex_unlock(&cubeb_alsa_mutex);
return r;
@ -750,6 +799,7 @@ silent_error_handler(char const * file, int line, char const * function,
alsa_init(cubeb ** context, char const * context_name)
{
(void)context_name;
void * libasound = NULL;
cubeb * ctx;
int r;
int i;
@ -760,9 +810,30 @@ alsa_init(cubeb ** context, char const * context_name)
assert(context);
*context = NULL;
#ifndef DISABLE_LIBASOUND_DLOPEN
libasound = dlopen("libasound.so.2", RTLD_LAZY);
if (!libasound) {
libasound = dlopen("libasound.so", RTLD_LAZY);
if (!libasound) {
return CUBEB_ERROR;
}
}
#define LOAD(x) { \
cubeb_##x = dlsym(libasound, #x); \
if (!cubeb_##x) { \
dlclose(libasound); \
return CUBEB_ERROR; \
} \
}
LIBASOUND_API_VISIT(LOAD);
#undef LOAD
#endif
pthread_mutex_lock(&cubeb_alsa_mutex);
if (!cubeb_alsa_error_handler_set) {
snd_lib_error_set_handler(silent_error_handler);
WRAP(snd_lib_error_set_handler)(silent_error_handler);
cubeb_alsa_error_handler_set = 1;
}
pthread_mutex_unlock(&cubeb_alsa_mutex);
@ -771,6 +842,7 @@ alsa_init(cubeb ** context, char const * context_name)
assert(ctx);
ctx->ops = &alsa_ops;
ctx->libasound = libasound;
r = pthread_mutex_init(&ctx->mutex, NULL);
assert(r == 0);
@ -819,7 +891,7 @@ alsa_init(cubeb ** context, char const * context_name)
config fails with EINVAL, the PA PCM is too old for this workaround. */
if (r == -EINVAL) {
pthread_mutex_lock(&cubeb_alsa_mutex);
snd_config_delete(ctx->local_config);
WRAP(snd_config_delete)(ctx->local_config);
pthread_mutex_unlock(&cubeb_alsa_mutex);
ctx->local_config = NULL;
} else if (r >= 0) {
@ -861,10 +933,14 @@ alsa_destroy(cubeb * ctx)
if (ctx->local_config) {
pthread_mutex_lock(&cubeb_alsa_mutex);
snd_config_delete(ctx->local_config);
WRAP(snd_config_delete)(ctx->local_config);
pthread_mutex_unlock(&cubeb_alsa_mutex);
}
if (ctx->libasound) {
dlclose(ctx->libasound);
}
free(ctx);
}
@ -948,7 +1024,7 @@ alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream
return CUBEB_ERROR;
}
r = snd_pcm_nonblock(stm->pcm, 1);
r = WRAP(snd_pcm_nonblock)(stm->pcm, 1);
assert(r == 0);
latency_us = latency_frames * 1e6 / stm->params.rate;
@ -961,7 +1037,7 @@ alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream
latency_us = latency_us < min_latency ? min_latency: latency_us;
}
r = snd_pcm_set_params(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED,
r = WRAP(snd_pcm_set_params)(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED,
stm->params.channels, stm->params.rate, 1,
latency_us);
if (r < 0) {
@ -969,20 +1045,20 @@ alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream
return CUBEB_ERROR_INVALID_FORMAT;
}
r = snd_pcm_get_params(stm->pcm, &stm->buffer_size, &period_size);
r = WRAP(snd_pcm_get_params)(stm->pcm, &stm->buffer_size, &period_size);
assert(r == 0);
/* Double internal buffer size to have enough space when waiting for the other side of duplex connection */
stm->buffer_size *= 2;
stm->buffer = calloc(1, snd_pcm_frames_to_bytes(stm->pcm, stm->buffer_size));
stm->buffer = calloc(1, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->buffer_size));
assert(stm->buffer);
stm->nfds = snd_pcm_poll_descriptors_count(stm->pcm);
stm->nfds = WRAP(snd_pcm_poll_descriptors_count)(stm->pcm);
assert(stm->nfds > 0);
stm->saved_fds = calloc(stm->nfds, sizeof(struct pollfd));
assert(stm->saved_fds);
r = snd_pcm_poll_descriptors(stm->pcm, stm->saved_fds, stm->nfds);
r = WRAP(snd_pcm_poll_descriptors)(stm->pcm, stm->saved_fds, stm->nfds);
assert((nfds_t) r == stm->nfds);
if (alsa_register_stream(ctx, stm) != 0) {
@ -1054,7 +1130,7 @@ alsa_stream_destroy(cubeb_stream * stm)
pthread_mutex_lock(&stm->mutex);
if (stm->pcm) {
if (stm->state == DRAINING) {
snd_pcm_drain(stm->pcm);
WRAP(snd_pcm_drain)(stm->pcm);
}
alsa_locked_pcm_close(stm->pcm);
stm->pcm = NULL;
@ -1100,12 +1176,12 @@ alsa_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
assert(stm);
r = snd_pcm_hw_params_any(stm->pcm, hw_params);
r = WRAP(snd_pcm_hw_params_any)(stm->pcm, hw_params);
if (r < 0) {
return CUBEB_ERROR;
}
r = snd_pcm_hw_params_get_channels_max(hw_params, max_channels);
r = WRAP(snd_pcm_hw_params_get_channels_max)(hw_params, max_channels);
if (r < 0) {
return CUBEB_ERROR;
}
@ -1126,34 +1202,34 @@ alsa_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) {
/* get a pcm, disabling resampling, so we get a rate the
* hardware/dmix/pulse/etc. supports. */
r = snd_pcm_open(&pcm, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, SND_PCM_NO_AUTO_RESAMPLE);
r = WRAP(snd_pcm_open)(&pcm, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, SND_PCM_NO_AUTO_RESAMPLE);
if (r < 0) {
return CUBEB_ERROR;
}
r = snd_pcm_hw_params_any(pcm, hw_params);
r = WRAP(snd_pcm_hw_params_any)(pcm, hw_params);
if (r < 0) {
snd_pcm_close(pcm);
WRAP(snd_pcm_close)(pcm);
return CUBEB_ERROR;
}
r = snd_pcm_hw_params_get_rate(hw_params, rate, &dir);
r = WRAP(snd_pcm_hw_params_get_rate)(hw_params, rate, &dir);
if (r >= 0) {
/* There is a default rate: use it. */
snd_pcm_close(pcm);
WRAP(snd_pcm_close)(pcm);
return CUBEB_OK;
}
/* Use a common rate, alsa may adjust it based on hw/etc. capabilities. */
*rate = 44100;
r = snd_pcm_hw_params_set_rate_near(pcm, hw_params, rate, NULL);
r = WRAP(snd_pcm_hw_params_set_rate_near)(pcm, hw_params, rate, NULL);
if (r < 0) {
snd_pcm_close(pcm);
WRAP(snd_pcm_close)(pcm);
return CUBEB_ERROR;
}
snd_pcm_close(pcm);
WRAP(snd_pcm_close)(pcm);
return CUBEB_OK;
}
@ -1186,10 +1262,10 @@ alsa_stream_start(cubeb_stream * stm)
pthread_mutex_lock(&stm->mutex);
/* Capture pcm must be started after initial setup/recover */
if (stm->stream_type == SND_PCM_STREAM_CAPTURE &&
snd_pcm_state(stm->pcm) == SND_PCM_STATE_PREPARED) {
snd_pcm_start(stm->pcm);
WRAP(snd_pcm_state)(stm->pcm) == SND_PCM_STATE_PREPARED) {
WRAP(snd_pcm_start)(stm->pcm);
}
snd_pcm_pause(stm->pcm, 0);
WRAP(snd_pcm_pause)(stm->pcm, 0);
gettimeofday(&stm->last_activity, NULL);
pthread_mutex_unlock(&stm->mutex);
@ -1229,7 +1305,7 @@ alsa_stream_stop(cubeb_stream * stm)
pthread_mutex_unlock(&ctx->mutex);
pthread_mutex_lock(&stm->mutex);
snd_pcm_pause(stm->pcm, 1);
WRAP(snd_pcm_pause)(stm->pcm, 1);
pthread_mutex_unlock(&stm->mutex);
return CUBEB_OK;
@ -1245,8 +1321,8 @@ alsa_stream_get_position(cubeb_stream * stm, uint64_t * position)
pthread_mutex_lock(&stm->mutex);
delay = -1;
if (snd_pcm_state(stm->pcm) != SND_PCM_STATE_RUNNING ||
snd_pcm_delay(stm->pcm, &delay) != 0) {
if (WRAP(snd_pcm_state)(stm->pcm) != SND_PCM_STATE_RUNNING ||
WRAP(snd_pcm_delay)(stm->pcm, &delay) != 0) {
*position = stm->last_position;
pthread_mutex_unlock(&stm->mutex);
return CUBEB_OK;
@ -1271,7 +1347,7 @@ alsa_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
snd_pcm_sframes_t delay;
/* This function returns the delay in frames until a frame written using
snd_pcm_writei is sent to the DAC. The DAC delay should be < 1ms anyways. */
if (snd_pcm_delay(stm->pcm, &delay)) {
if (WRAP(snd_pcm_delay)(stm->pcm, &delay)) {
return CUBEB_ERROR;
}
@ -1368,6 +1444,7 @@ static struct cubeb_ops const alsa_ops = {
.stream_reset_default_device = NULL,
.stream_get_position = alsa_stream_get_position,
.stream_get_latency = alsa_stream_get_latency,
.stream_get_input_latency = NULL,
.stream_set_volume = alsa_stream_set_volume,
.stream_get_current_device = NULL,
.stream_device_destroy = NULL,

View file

@ -433,6 +433,7 @@ static struct cubeb_ops const audiotrack_ops = {
.stream_reset_default_device = NULL,
.stream_get_position = audiotrack_stream_get_position,
.stream_get_latency = audiotrack_stream_get_latency,
.stream_get_input_latency = NULL,
.stream_set_volume = audiotrack_stream_set_volume,
.stream_get_current_device = NULL,
.stream_device_destroy = NULL,

View file

@ -501,6 +501,17 @@ audiounit_input_callback(void * user_ptr,
return noErr;
}
if (stm->draining) {
OSStatus r = AudioOutputUnitStop(stm->input_unit);
assert(r == 0);
// Only fire state callback in input-only stream. For duplex stream,
// the state callback will be fired in output callback.
if (stm->output_unit == NULL) {
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
}
return noErr;
}
OSStatus r = audiounit_render_input(stm, flags, tstamp, bus, input_frames);
if (r != noErr) {
return r;
@ -520,12 +531,7 @@ audiounit_input_callback(void * user_ptr,
&total_input_frames,
NULL,
0);
if (outframes < total_input_frames) {
OSStatus r = AudioOutputUnitStop(stm->input_unit);
assert(r == 0);
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
return noErr;
}
stm->draining = outframes < total_input_frames;
// Reset input buffer
stm->input_linear_buffer->clear();
@ -612,10 +618,6 @@ audiounit_output_callback(void * user_ptr,
if (stm->draining) {
OSStatus r = AudioOutputUnitStop(stm->output_unit);
assert(r == 0);
if (stm->input_unit) {
r = AudioOutputUnitStop(stm->input_unit);
assert(r == 0);
}
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
audiounit_make_silent(&outBufferList->mBuffers[0]);
return noErr;
@ -692,9 +694,14 @@ audiounit_output_callback(void * user_ptr,
/* Post process output samples. */
if (stm->draining) {
size_t outbpf = cubeb_sample_size(stm->output_stream_params.format);
/* Clear missing frames (silence) */
memset((uint8_t*)output_buffer + outframes * outbpf, 0, (output_frames - outframes) * outbpf);
size_t channels = stm->output_stream_params.channels;
size_t missing_samples = (output_frames - outframes) * channels;
size_t size_sample = cubeb_sample_size(stm->output_stream_params.format);
/* number of bytes that have been filled with valid audio by the callback. */
size_t audio_byte_count = outframes * channels * size_sample;
PodZero((uint8_t*)output_buffer + audio_byte_count,
missing_samples * size_sample);
}
/* Mixing */
@ -2864,6 +2871,15 @@ audiounit_stream_destroy_internal(cubeb_stream *stm)
static void
audiounit_stream_destroy(cubeb_stream * stm)
{
int r = audiounit_uninstall_system_changed_callback(stm);
if (r != CUBEB_OK) {
LOG("(%p) Could not uninstall the device changed callback", stm);
}
r = audiounit_uninstall_device_changed_callback(stm);
if (r != CUBEB_OK) {
LOG("(%p) Could not uninstall all device change listeners", stm);
}
if (!stm->shutdown.load()){
auto_lock context_lock(stm->context->mutex);
audiounit_stream_stop_internal(stm);
@ -3605,6 +3621,7 @@ cubeb_ops const audiounit_ops = {
/*.stream_reset_default_device =*/ nullptr,
/*.stream_get_position =*/ audiounit_stream_get_position,
/*.stream_get_latency =*/ audiounit_stream_get_latency,
/*.stream_get_input_latency =*/ NULL,
/*.stream_set_volume =*/ audiounit_stream_set_volume,
/*.stream_get_current_device =*/ audiounit_stream_get_current_device,
/*.stream_device_destroy =*/ audiounit_stream_device_destroy,

View file

@ -50,6 +50,9 @@
#define IMPORT_FUNC(x) static decltype(x) * api_##x;
JACK_API_VISIT(IMPORT_FUNC);
#define JACK_DEFAULT_IN "JACK capture"
#define JACK_DEFAULT_OUT "JACK playback"
static const int MAX_STREAMS = 16;
static const int MAX_CHANNELS = 8;
static const int FIFO_SIZE = 4096 * sizeof(float);
@ -129,6 +132,7 @@ static struct cubeb_ops const cbjack_ops = {
.stream_reset_default_device = NULL,
.stream_get_position = cbjack_stream_get_position,
.stream_get_latency = cbjack_get_latency,
.stream_get_input_latency = NULL,
.stream_set_volume = cbjack_stream_set_volume,
.stream_get_current_device = cbjack_stream_get_current_device,
.stream_device_destroy = cbjack_stream_device_destroy,
@ -211,6 +215,9 @@ load_jack_lib(cubeb * context)
# endif
#else
context->libjack = dlopen("libjack.so.0", RTLD_LAZY);
if (!context->libjack) {
context->libjack = dlopen("libjack.so", RTLD_LAZY);
}
#endif
if (!context->libjack) {
return CUBEB_ERROR;
@ -740,8 +747,10 @@ cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_
return CUBEB_ERROR_INVALID_FORMAT;
}
if (input_device || output_device)
if ((input_device && input_device != JACK_DEFAULT_IN) ||
(output_device && output_device != JACK_DEFAULT_OUT)) {
return CUBEB_ERROR_NOT_SUPPORTED;
}
// Loopback is unsupported
if ((input_stream_params && (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK)) ||
@ -964,8 +973,8 @@ cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const devic
if (*device == NULL)
return CUBEB_ERROR;
const char * j_in = "JACK capture";
const char * j_out = "JACK playback";
const char * j_in = JACK_DEFAULT_IN;
const char * j_out = JACK_DEFAULT_OUT;
const char * empty = "";
if (stm->devs == DUPLEX) {
@ -994,9 +1003,6 @@ cbjack_stream_device_destroy(cubeb_stream * /*stream*/,
return CUBEB_OK;
}
#define JACK_DEFAULT_IN "JACK capture"
#define JACK_DEFAULT_OUT "JACK playback"
static int
cbjack_enumerate_devices(cubeb * context, cubeb_device_type type,
cubeb_device_collection * collection)

View file

@ -361,6 +361,7 @@ static struct cubeb_ops const kai_ops = {
/*.stream_reset_default_device =*/ NULL,
/*.stream_get_position =*/ kai_stream_get_position,
/*.stream_get_latency = */ kai_stream_get_latency,
/*.stream_get_input_latency = */ NULL,
/*.stream_set_volume =*/ kai_stream_set_volume,
/*.stream_get_current_device =*/ NULL,
/*.stream_device_destroy =*/ NULL,

View file

@ -16,8 +16,15 @@ extern "C" {
#if defined(__GNUC__) || defined(__clang__)
#define PRINTF_FORMAT(fmt, args) __attribute__((format(printf, fmt, args)))
#if defined(__FILE_NAME__)
#define __FILENAME__ __FILE_NAME__
#else
#define __FILENAME__ (__builtin_strrchr(__FILE__, '/') ? __builtin_strrchr(__FILE__, '/') + 1 : __FILE__)
#endif
#else
#define PRINTF_FORMAT(fmt, args)
#include <string.h>
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#endif
extern cubeb_log_level g_cubeb_log_level;
@ -32,10 +39,10 @@ void cubeb_async_log_reset_threads();
#define LOGV(msg, ...) LOG_INTERNAL(CUBEB_LOG_VERBOSE, msg, ##__VA_ARGS__)
#define LOG(msg, ...) LOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__)
#define LOG_INTERNAL(level, fmt, ...) do { \
if (g_cubeb_log_callback && level <= g_cubeb_log_level) { \
g_cubeb_log_callback("%s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \
} \
#define LOG_INTERNAL(level, fmt, ...) do { \
if (g_cubeb_log_callback && level <= g_cubeb_log_level) { \
g_cubeb_log_callback("%s:%d: " fmt "\n", __FILENAME__, __LINE__, ##__VA_ARGS__); \
} \
} while(0)
/* Asynchronous verbose logging, to log in real-time callbacks. */

View file

@ -752,7 +752,7 @@ opensl_init(cubeb ** context, char const * context_name)
}
ctx->p_output_latency_function = cubeb_output_latency_load_method(android_version);
if (!ctx->p_output_latency_function) {
if (!cubeb_output_latency_method_is_loaded(ctx->p_output_latency_function)) {
LOG("Warning: output latency is not available, cubeb_stream_get_position() is not supported");
}
@ -794,6 +794,53 @@ opensl_destroy(cubeb * ctx)
static void opensl_stream_destroy(cubeb_stream * stm);
#if defined(__ANDROID__) && (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
static int
opensl_set_format_ext(SLAndroidDataFormat_PCM_EX * format, cubeb_stream_params * params)
{
assert(format);
assert(params);
format->formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
format->numChannels = params->channels;
// sampleRate is in milliHertz
format->sampleRate = params->rate * 1000;
format->channelMask = params->channels == 1 ?
SL_SPEAKER_FRONT_CENTER :
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
switch (params->format) {
case CUBEB_SAMPLE_S16LE:
format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
format->representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
format->endianness = SL_BYTEORDER_LITTLEENDIAN;
break;
case CUBEB_SAMPLE_S16BE:
format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
format->representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
format->endianness = SL_BYTEORDER_BIGENDIAN;
break;
case CUBEB_SAMPLE_FLOAT32LE:
format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32;
format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_32;
format->representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
format->endianness = SL_BYTEORDER_LITTLEENDIAN;
break;
case CUBEB_SAMPLE_FLOAT32BE:
format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32;
format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_32;
format->representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
format->endianness = SL_BYTEORDER_BIGENDIAN;
break;
default:
return CUBEB_ERROR_INVALID_FORMAT;
}
return CUBEB_OK;
}
#endif
static int
opensl_set_format(SLDataFormat_PCM * format, cubeb_stream_params * params)
{
@ -1020,15 +1067,36 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) {
assert(params);
stm->user_output_rate = params->rate;
stm->framesize = params->channels * sizeof(int16_t);
if(params->format == CUBEB_SAMPLE_S16NE || params->format == CUBEB_SAMPLE_S16BE) {
stm->framesize = params->channels * sizeof(int16_t);
} else if(params->format == CUBEB_SAMPLE_FLOAT32NE || params->format == CUBEB_SAMPLE_FLOAT32BE) {
stm->framesize = params->channels * sizeof(float);
}
stm->lastPosition = -1;
stm->lastPositionTimeStamp = 0;
stm->lastCompensativePosition = -1;
SLDataFormat_PCM format;
int r = opensl_set_format(&format, params);
if (r != CUBEB_OK) {
return CUBEB_ERROR_INVALID_FORMAT;
void* format = NULL;
SLuint32* format_sample_rate = NULL;
#if defined(__ANDROID__) && (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
SLAndroidDataFormat_PCM_EX pcm_ext_format;
if (get_android_version() >= ANDROID_VERSION_LOLLIPOP) {
if (opensl_set_format_ext(&pcm_ext_format, params) != CUBEB_OK) {
return CUBEB_ERROR_INVALID_FORMAT;
}
format = &pcm_ext_format;
format_sample_rate = &pcm_ext_format.sampleRate;
}
#endif
SLDataFormat_PCM pcm_format;
if(!format) {
if(opensl_set_format(&pcm_format, params) != CUBEB_OK) {
return CUBEB_ERROR_INVALID_FORMAT;
}
format = &pcm_format;
format_sample_rate = &pcm_format.samplesPerSec;
}
SLDataLocator_BufferQueue loc_bufq;
@ -1036,7 +1104,7 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) {
loc_bufq.numBuffers = NBUFS;
SLDataSource source;
source.pLocator = &loc_bufq;
source.pFormat = &format;
source.pFormat = format;
SLDataLocator_OutputMix loc_outmix;
loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
@ -1072,7 +1140,7 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) {
if (res == SL_RESULT_CONTENT_UNSUPPORTED &&
preferred_sampling_rate != DEFAULT_SAMPLE_RATE) {
preferred_sampling_rate = DEFAULT_SAMPLE_RATE;
format.samplesPerSec = preferred_sampling_rate * 1000;
*format_sample_rate = preferred_sampling_rate * 1000;
res = (*stm->context->eng)->CreateAudioPlayer(stm->context->eng,
&stm->playerObj,
&source,
@ -1625,6 +1693,18 @@ opensl_stream_get_position(cubeb_stream * stm, uint64_t * position)
return CUBEB_OK;
}
static int
opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
{
assert(stm);
assert(latency);
uint32_t stream_latency_frames =
stm->user_output_rate * (stm->output_latency_ms / 1000);
return stream_latency_frames + cubeb_resampler_latency(stm->resampler);
}
int
opensl_stream_set_volume(cubeb_stream * stm, float volume)
{
@ -1671,7 +1751,8 @@ static struct cubeb_ops const opensl_ops = {
.stream_stop = opensl_stream_stop,
.stream_reset_default_device = NULL,
.stream_get_position = opensl_stream_get_position,
.stream_get_latency = NULL,
.stream_get_latency = opensl_stream_get_latency,
.stream_get_input_latency = NULL,
.stream_set_volume = opensl_stream_set_volume,
.stream_get_current_device = NULL,
.stream_device_destroy = NULL,

View file

@ -576,6 +576,11 @@ layout_to_channel_map(cubeb_channel_layout layout, pa_channel_map * cm)
unsigned int channels_from_layout = cubeb_channel_layout_nb_channels(layout);
assert(channels_from_layout <= UINT8_MAX);
cm->channels = (uint8_t) channels_from_layout;
// Special case single channel center mapping as mono.
if (cm->channels == 1 && cm->map[0] == PA_CHANNEL_POSITION_FRONT_CENTER) {
cm->map[0] = PA_CHANNEL_POSITION_MONO;
}
}
static void pulse_context_destroy(cubeb * ctx);
@ -615,6 +620,9 @@ pulse_context_init(cubeb * ctx)
return 0;
}
static int pulse_subscribe_notifications(cubeb * context,
pa_subscription_mask_t mask);
/*static*/ int
pulse_init(cubeb ** context, char const * context_name)
{
@ -627,7 +635,10 @@ pulse_init(cubeb ** context, char const * context_name)
#ifndef DISABLE_LIBPULSE_DLOPEN
libpulse = dlopen("libpulse.so.0", RTLD_LAZY);
if (!libpulse) {
return CUBEB_ERROR;
libpulse = dlopen("libpulse.so", RTLD_LAZY);
if (!libpulse) {
return CUBEB_ERROR;
}
}
#define LOAD(x) { \
@ -679,6 +690,9 @@ pulse_init(cubeb ** context, char const * context_name)
}
WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
/* Update `default_sink_info` when the default device changes. */
pulse_subscribe_notifications(ctx, PA_SUBSCRIPTION_MASK_SERVER);
*context = ctx;
return CUBEB_OK;
@ -1169,21 +1183,6 @@ pulse_stream_set_volume(cubeb_stream * stm, float volume)
return CUBEB_OK;
}
struct sink_input_info_result {
pa_cvolume * cvol;
pa_threaded_mainloop * mainloop;
};
static void
sink_input_info_cb(pa_context * c, pa_sink_input_info const * i, int eol, void * u)
{
struct sink_input_info_result * r = u;
if (!eol) {
*r->cvol = i->volume;
}
WRAP(pa_threaded_mainloop_signal)(r->mainloop, 0);
}
typedef struct {
char * default_sink_name;
char * default_source_name;
@ -1482,6 +1481,12 @@ pulse_subscribe_callback(pa_context * ctx,
cubeb * context = userdata;
switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
case PA_SUBSCRIPTION_EVENT_SERVER:
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) {
LOG("Server changed %d", index);
WRAP(pa_context_get_server_info)(context->context, server_info_callback, context);
}
break;
case PA_SUBSCRIPTION_EVENT_SOURCE:
case PA_SUBSCRIPTION_EVENT_SINK:
@ -1525,38 +1530,10 @@ subscribe_success(pa_context *c, int success, void *userdata)
}
static int
pulse_register_device_collection_changed(cubeb * context,
cubeb_device_type devtype,
cubeb_device_collection_changed_callback collection_changed_callback,
void * user_ptr)
{
if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
context->input_collection_changed_callback = collection_changed_callback;
context->input_collection_changed_user_ptr = user_ptr;
}
if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
context->output_collection_changed_callback = collection_changed_callback;
context->output_collection_changed_user_ptr = user_ptr;
}
pulse_subscribe_notifications(cubeb * context, pa_subscription_mask_t mask) {
WRAP(pa_threaded_mainloop_lock)(context->mainloop);
pa_subscription_mask_t mask = PA_SUBSCRIPTION_MASK_NULL;
if (context->input_collection_changed_callback) {
mask |= PA_SUBSCRIPTION_MASK_SOURCE;
}
if (context->output_collection_changed_callback) {
mask |= PA_SUBSCRIPTION_MASK_SINK;
}
if (collection_changed_callback == NULL) {
// Unregister subscription.
if (mask == PA_SUBSCRIPTION_MASK_NULL) {
WRAP(pa_context_set_subscribe_callback)(context->context, NULL, NULL);
}
} else {
WRAP(pa_context_set_subscribe_callback)(context->context, pulse_subscribe_callback, context);
}
WRAP(pa_context_set_subscribe_callback)(context->context, pulse_subscribe_callback, context);
pa_operation * o;
o = WRAP(pa_context_subscribe)(context->context, mask, subscribe_success, context);
@ -1573,6 +1550,37 @@ pulse_register_device_collection_changed(cubeb * context,
return CUBEB_OK;
}
static int
pulse_register_device_collection_changed(cubeb * context,
cubeb_device_type devtype,
cubeb_device_collection_changed_callback collection_changed_callback,
void * user_ptr)
{
if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
context->input_collection_changed_callback = collection_changed_callback;
context->input_collection_changed_user_ptr = user_ptr;
}
if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
context->output_collection_changed_callback = collection_changed_callback;
context->output_collection_changed_user_ptr = user_ptr;
}
pa_subscription_mask_t mask = PA_SUBSCRIPTION_MASK_NULL;
if (context->input_collection_changed_callback) {
/* Input added or removed */
mask |= PA_SUBSCRIPTION_MASK_SOURCE;
}
if (context->output_collection_changed_callback) {
/* Output added or removed */
mask |= PA_SUBSCRIPTION_MASK_SINK;
}
/* Default device changed, this is always registered in order to update the
* `default_sink_info` when the default device changes. */
mask |= PA_SUBSCRIPTION_MASK_SERVER;
return pulse_subscribe_notifications(context, mask);
}
static struct cubeb_ops const pulse_ops = {
.init = pulse_init,
.get_backend_id = pulse_get_backend_id,
@ -1589,6 +1597,7 @@ static struct cubeb_ops const pulse_ops = {
.stream_reset_default_device = NULL,
.stream_get_position = pulse_stream_get_position,
.stream_get_latency = pulse_stream_get_latency,
.stream_get_input_latency = NULL,
.stream_set_volume = pulse_stream_set_volume,
.stream_get_current_device = pulse_stream_get_current_device,
.stream_device_destroy = pulse_stream_device_destroy,

View file

@ -61,31 +61,69 @@ long passthrough_resampler<T>::fill(void * input_buffer, long * input_frames_cou
if (input_buffer) {
assert(input_frames_count);
}
assert((input_buffer && output_buffer &&
*input_frames_count + static_cast<int>(samples_to_frames(internal_input_buffer.length())) >= output_frames) ||
assert((input_buffer && output_buffer) ||
(output_buffer && !input_buffer && (!input_frames_count || *input_frames_count == 0)) ||
(input_buffer && !output_buffer && output_frames == 0));
if (input_buffer) {
if (!output_buffer) {
// When we have no pending input data and exactly as much input
// as output data, we don't need to copy it into the internal buffer
// and can directly forward it to the callback.
void * in_buf = input_buffer;
unsigned long pop_input_count = 0u;
if (input_buffer && !output_buffer) {
output_frames = *input_frames_count;
} else if(input_buffer) {
if (internal_input_buffer.length() != 0 ||
*input_frames_count < output_frames) {
// If we have pending input data left and have to first append the input
// so we can pass it as one pointer to the callback. Or this is a glitch.
// It can happen when system's performance is poor. Audible silence is
// being pushed at the end of the short input buffer. An improvement for
// the future is to resample to the output number of frames, when that happens.
internal_input_buffer.push(static_cast<T*>(input_buffer),
frames_to_samples(*input_frames_count));
if (internal_input_buffer.length() < frames_to_samples(output_frames)) {
// This is unxpected but it can happen when a glitch occurs. Fill the
// buffer with silence. First keep the actual number of input samples
// used without the silence.
pop_input_count = internal_input_buffer.length();
internal_input_buffer.push_silence(
frames_to_samples(output_frames) - internal_input_buffer.length());
} else {
pop_input_count = frames_to_samples(output_frames);
}
in_buf = internal_input_buffer.data();
} else if(*input_frames_count > output_frames) {
// In this case we have more input that we need output and
// fill the overflowing input into internal_input_buffer
// Since we have no other pending data, we can nonetheless
// pass the current input data directly to the callback
assert(pop_input_count == 0);
unsigned long samples_off = frames_to_samples(output_frames);
internal_input_buffer.push(static_cast<T*>(input_buffer) + samples_off,
frames_to_samples(*input_frames_count - output_frames));
}
internal_input_buffer.push(static_cast<T*>(input_buffer),
frames_to_samples(*input_frames_count));
}
long rv = data_callback(stream, user_ptr, internal_input_buffer.data(),
output_buffer, output_frames);
long rv = data_callback(stream, user_ptr, in_buf, output_buffer, output_frames);
if (input_buffer) {
internal_input_buffer.pop(nullptr, frames_to_samples(output_frames));
*input_frames_count = output_frames;
if (pop_input_count) {
internal_input_buffer.pop(nullptr, pop_input_count);
*input_frames_count = samples_to_frames(pop_input_count);
} else {
*input_frames_count = output_frames;
}
drop_audio_if_needed();
}
return rv;
}
// Explicit instantiation of template class.
template class passthrough_resampler<float>;
template class passthrough_resampler<short>;
template<typename T, typename InputProcessor, typename OutputProcessor>
cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
::cubeb_resampler_speex(InputProcessor * input_processor,

View file

@ -12,6 +12,7 @@
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
#include <assert.h>
#include "cubeb/cubeb.h"
#include "cubeb-internal.h"
@ -22,10 +23,36 @@
#define DPR(...) do {} while(0)
#endif
#ifdef DISABLE_LIBSNDIO_DLOPEN
#define WRAP(x) x
#else
#define WRAP(x) cubeb_##x
#define LIBSNDIO_API_VISIT(X) \
X(sio_close) \
X(sio_eof) \
X(sio_getpar) \
X(sio_initpar) \
X(sio_nfds) \
X(sio_onmove) \
X(sio_open) \
X(sio_pollfd) \
X(sio_read) \
X(sio_revents) \
X(sio_setpar) \
X(sio_start) \
X(sio_stop) \
X(sio_write) \
#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
LIBSNDIO_API_VISIT(MAKE_TYPEDEF);
#undef MAKE_TYPEDEF
#endif
static struct cubeb_ops const sndio_ops;
struct cubeb {
struct cubeb_ops const * ops;
void * libsndio;
};
struct cubeb_stream {
@ -98,6 +125,23 @@ s16_to_float(void *ptr, long nsamp)
*(--dst) = (1. / 32768) * *(--src);
}
static const char *
sndio_get_device()
{
#ifdef __linux__
/*
* On other platforms default to sndio devices,
* so cubebs other backends can be used instead.
*/
const char *dev = getenv("AUDIODEVICE");
if (dev == NULL || *dev == '\0')
return "snd/0";
return dev;
#else
return SIO_DEVANY;
#endif
}
static void
sndio_onmove(void *arg, int delta)
{
@ -109,18 +153,23 @@ sndio_onmove(void *arg, int delta)
static void *
sndio_mainloop(void *arg)
{
#define MAXFDS 8
struct pollfd pfds[MAXFDS];
struct pollfd *pfds;
cubeb_stream *s = arg;
int n, eof = 0, prime, nfds, events, revents, state = CUBEB_STATE_STARTED;
size_t pstart = 0, pend = 0, rstart = 0, rend = 0;
long nfr;
nfds = WRAP(sio_nfds)(s->hdl);
pfds = calloc(nfds, sizeof (struct pollfd));
if (pfds == NULL)
return NULL;
DPR("sndio_mainloop()\n");
s->state_cb(s, s->arg, CUBEB_STATE_STARTED);
pthread_mutex_lock(&s->mtx);
if (!sio_start(s->hdl)) {
if (!WRAP(sio_start)(s->hdl)) {
pthread_mutex_unlock(&s->mtx);
free(pfds);
return NULL;
}
DPR("sndio_mainloop(), started\n");
@ -203,7 +252,7 @@ sndio_mainloop(void *arg)
events |= POLLIN;
if ((s->mode & SIO_PLAY) && pstart < pend)
events |= POLLOUT;
nfds = sio_pollfd(s->hdl, pfds, events);
nfds = WRAP(sio_pollfd)(s->hdl, pfds, events);
if (nfds > 0) {
pthread_mutex_unlock(&s->mtx);
@ -213,7 +262,7 @@ sndio_mainloop(void *arg)
continue;
}
revents = sio_revents(s->hdl, pfds);
revents = WRAP(sio_revents)(s->hdl, pfds);
if (revents & POLLHUP) {
state = CUBEB_STATE_ERROR;
@ -221,8 +270,8 @@ sndio_mainloop(void *arg)
}
if (revents & POLLOUT) {
n = sio_write(s->hdl, s->pbuf + pstart, pend - pstart);
if (n == 0 && sio_eof(s->hdl)) {
n = WRAP(sio_write)(s->hdl, s->pbuf + pstart, pend - pstart);
if (n == 0 && WRAP(sio_eof)(s->hdl)) {
DPR("sndio_mainloop() werr\n");
state = CUBEB_STATE_ERROR;
break;
@ -231,8 +280,8 @@ sndio_mainloop(void *arg)
}
if (revents & POLLIN) {
n = sio_read(s->hdl, s->rbuf + rstart, rend - rstart);
if (n == 0 && sio_eof(s->hdl)) {
n = WRAP(sio_read)(s->hdl, s->rbuf + rstart, rend - rstart);
if (n == 0 && WRAP(sio_eof)(s->hdl)) {
DPR("sndio_mainloop() rerr\n");
state = CUBEB_STATE_ERROR;
break;
@ -244,18 +293,57 @@ sndio_mainloop(void *arg)
if (prime > 0 && (s->mode & SIO_REC))
rstart = rend;
}
sio_stop(s->hdl);
WRAP(sio_stop)(s->hdl);
s->hwpos = s->swpos;
pthread_mutex_unlock(&s->mtx);
s->state_cb(s, s->arg, state);
free(pfds);
return NULL;
}
/*static*/ int
sndio_init(cubeb **context, char const *context_name)
{
void * libsndio = NULL;
struct sio_hdl *hdl;
assert(context);
#ifndef DISABLE_LIBSNDIO_DLOPEN
libsndio = dlopen("libsndio.so.7.0", RTLD_LAZY);
if (!libsndio) {
libsndio = dlopen("libsndio.so", RTLD_LAZY);
if (!libsndio) {
DPR("sndio_init(%s) failed dlopen(libsndio.so)\n", context_name);
return CUBEB_ERROR;
}
}
#define LOAD(x) { \
cubeb_##x = dlsym(libsndio, #x); \
if (!cubeb_##x) { \
DPR("sndio_init(%s) failed dlsym(%s)\n", context_name, #x); \
dlclose(libsndio); \
return CUBEB_ERROR; \
} \
}
LIBSNDIO_API_VISIT(LOAD);
#undef LOAD
#endif
/* test if sndio works */
hdl = WRAP(sio_open)(sndio_get_device(), SIO_PLAY, 1);
if (hdl == NULL) {
return CUBEB_ERROR;
}
WRAP(sio_close)(hdl);
DPR("sndio_init(%s)\n", context_name);
*context = malloc(sizeof(*context));
*context = malloc(sizeof(**context));
if (*context == NULL)
return CUBEB_ERROR;
(*context)->libsndio = libsndio;
(*context)->ops = &sndio_ops;
(void)context_name;
return CUBEB_OK;
@ -271,6 +359,8 @@ static void
sndio_destroy(cubeb *context)
{
DPR("sndio_destroy()\n");
if (context->libsndio)
dlclose(context->libsndio);
free(context);
}
@ -323,12 +413,12 @@ sndio_stream_init(cubeb * context,
goto err;
}
s->context = context;
s->hdl = sio_open(NULL, s->mode, 1);
s->hdl = WRAP(sio_open)(sndio_get_device(), s->mode, 1);
if (s->hdl == NULL) {
DPR("sndio_stream_init(), sio_open() failed\n");
goto err;
}
sio_initpar(&wpar);
WRAP(sio_initpar)(&wpar);
wpar.sig = 1;
wpar.bits = 16;
switch (format) {
@ -351,7 +441,7 @@ sndio_stream_init(cubeb * context,
if (s->mode & SIO_PLAY)
wpar.pchan = output_stream_params->channels;
wpar.appbufsz = latency_frames;
if (!sio_setpar(s->hdl, &wpar) || !sio_getpar(s->hdl, &rpar)) {
if (!WRAP(sio_setpar)(s->hdl, &wpar) || !WRAP(sio_getpar)(s->hdl, &rpar)) {
DPR("sndio_stream_init(), sio_setpar() failed\n");
goto err;
}
@ -362,7 +452,7 @@ sndio_stream_init(cubeb * context,
DPR("sndio_stream_init() unsupported params\n");
goto err;
}
sio_onmove(s->hdl, sndio_onmove, s);
WRAP(sio_onmove)(s->hdl, sndio_onmove, s);
s->active = 0;
s->nfr = rpar.round;
s->rbpf = rpar.bps * rpar.rchan;
@ -400,7 +490,7 @@ sndio_stream_init(cubeb * context,
return CUBEB_OK;
err:
if (s->hdl)
sio_close(s->hdl);
WRAP(sio_close)(s->hdl);
if (s->pbuf)
free(s->pbuf);
if (s->rbuf)
@ -446,7 +536,7 @@ static void
sndio_stream_destroy(cubeb_stream *s)
{
DPR("sndio_stream_destroy()\n");
sio_close(s->hdl);
WRAP(sio_close)(s->hdl);
if (s->mode & SIO_PLAY)
free(s->pbuf);
if (s->mode & SIO_REC)

View file

@ -1,5 +1,5 @@
/*
* Copyright © 2019 Nia Alarie
* Copyright © 2019-2020 Nia Alarie <nia@NetBSD.org>
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
@ -9,19 +9,14 @@
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <limits.h>
#include "cubeb/cubeb.h"
#include "cubeb-internal.h"
#define BYTES_TO_FRAMES(bytes, channels) \
(bytes / (channels * sizeof(int16_t)))
#define FRAMES_TO_BYTES(frames, channels) \
(frames * (channels * sizeof(int16_t)))
/* Default to 4 + 1 for the default device. */
#ifndef SUN_DEVICE_COUNT
#define SUN_DEVICE_COUNT (5)
@ -41,10 +36,6 @@
#define SUN_DEFAULT_DEVICE "/dev/audio"
#endif
#ifndef SUN_POLL_TIMEOUT
#define SUN_POLL_TIMEOUT (1000)
#endif
#ifndef SUN_BUFFER_FRAMES
#define SUN_BUFFER_FRAMES (32)
#endif
@ -75,26 +66,26 @@ struct cubeb {
struct cubeb_ops const * ops;
};
struct sun_stream {
char name[32];
int fd;
void * buf;
struct audio_info info;
unsigned frame_size; /* precision in bytes * channels */
bool floating;
};
struct cubeb_stream {
struct cubeb * context;
void * user_ptr;
pthread_t thread;
pthread_mutex_t mutex; /* protects running, volume, frames_written */
int floating;
int running;
int play_fd;
int record_fd;
bool running;
float volume;
struct audio_info p_info; /* info for the play fd */
struct audio_info r_info; /* info for the record fd */
struct sun_stream play;
struct sun_stream record;
cubeb_data_callback data_cb;
cubeb_state_callback state_cb;
int16_t * play_buf;
int16_t * record_buf;
float * f_play_buf;
float * f_record_buf;
char input_name[32];
char output_name[32];
uint64_t frames_written;
uint64_t blocks_written;
};
@ -312,18 +303,19 @@ sun_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params,
{
prinfo->channels = params->channels;
prinfo->sample_rate = params->rate;
prinfo->precision = 16;
#ifdef AUDIO_ENCODING_SLINEAR_LE
switch (params->format) {
case CUBEB_SAMPLE_S16LE:
prinfo->encoding = AUDIO_ENCODING_SLINEAR_LE;
prinfo->precision = 16;
break;
case CUBEB_SAMPLE_S16BE:
prinfo->encoding = AUDIO_ENCODING_SLINEAR_BE;
prinfo->precision = 16;
break;
case CUBEB_SAMPLE_FLOAT32NE:
stream->floating = 1;
prinfo->encoding = AUDIO_ENCODING_SLINEAR;
prinfo->precision = 32;
break;
default:
LOG("Unsupported format");
@ -333,10 +325,11 @@ sun_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params,
switch (params->format) {
case CUBEB_SAMPLE_S16NE:
prinfo->encoding = AUDIO_ENCODING_LINEAR;
prinfo->precision = 16;
break;
case CUBEB_SAMPLE_FLOAT32NE:
stream->floating = 1;
prinfo->encoding = AUDIO_ENCODING_LINEAR;
prinfo->precision = 32;
break;
default:
LOG("Unsupported format");
@ -357,7 +350,7 @@ sun_stream_stop(cubeb_stream * s)
{
pthread_mutex_lock(&s->mutex);
if (s->running) {
s->running = 0;
s->running = false;
pthread_mutex_unlock(&s->mutex);
pthread_join(s->thread, NULL);
} else {
@ -371,53 +364,50 @@ sun_stream_destroy(cubeb_stream * s)
{
pthread_mutex_destroy(&s->mutex);
sun_stream_stop(s);
if (s->play_fd != -1) {
close(s->play_fd);
if (s->play.fd != -1) {
close(s->play.fd);
}
if (s->record_fd != -1) {
close(s->record_fd);
if (s->record.fd != -1) {
close(s->record.fd);
}
free(s->f_play_buf);
free(s->f_record_buf);
free(s->play_buf);
free(s->record_buf);
free(s->play.buf);
free(s->record.buf);
free(s);
}
static void
sun_float_to_linear(float * in, int16_t * out,
unsigned channels, long frames, float vol)
sun_float_to_linear32(void * buf, unsigned sample_count, float vol)
{
unsigned i, sample_count = frames * channels;
float multiplier = vol * 0x8000;
float * in = buf;
int32_t * out = buf;
int32_t * tail = out + sample_count;
for (i = 0; i < sample_count; ++i) {
int32_t sample = lrintf(in[i] * multiplier);
if (sample < -0x8000) {
out[i] = -0x8000;
} else if (sample > 0x7fff) {
out[i] = 0x7fff;
} else {
out[i] = sample;
}
while (out < tail) {
float f = *(in++) * vol;
if (f < -1.0)
f = -1.0;
else if (f > 1.0)
f = 1.0;
*(out++) = f * (float)INT32_MAX;
}
}
static void
sun_linear_to_float(int16_t * in, float * out,
unsigned channels, long frames)
sun_linear32_to_float(void * buf, unsigned sample_count)
{
unsigned i, sample_count = frames * channels;
int32_t * in = buf;
float * out = buf;
float * tail = out + sample_count;
for (i = 0; i < sample_count; ++i) {
out[i] = (1.0 / 0x8000) * in[i];
while (out < tail) {
*(out++) = (1.0 / 0x80000000) * *(in++);
}
}
static void
sun_linear_set_vol(int16_t * buf, unsigned channels, long frames, float vol)
sun_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol)
{
unsigned i, sample_count = frames * channels;
unsigned i;
int32_t multiplier = vol * 0x8000;
for (i = 0; i < sample_count; ++i) {
@ -445,41 +435,36 @@ sun_io_routine(void * arg)
break;
}
pthread_mutex_unlock(&s->mutex);
if (s->floating) {
if (s->record_fd != -1) {
sun_linear_to_float(s->record_buf, s->f_record_buf,
s->r_info.record.channels, SUN_BUFFER_FRAMES);
}
to_write = s->data_cb(s, s->user_ptr,
s->f_record_buf, s->f_play_buf, SUN_BUFFER_FRAMES);
if (to_write == CUBEB_ERROR) {
state = CUBEB_STATE_ERROR;
break;
}
if (s->play_fd != -1) {
pthread_mutex_lock(&s->mutex);
sun_float_to_linear(s->f_play_buf, s->play_buf,
s->p_info.play.channels, to_write, s->volume);
pthread_mutex_unlock(&s->mutex);
}
} else {
to_write = s->data_cb(s, s->user_ptr,
s->record_buf, s->play_buf, SUN_BUFFER_FRAMES);
if (to_write == CUBEB_ERROR) {
state = CUBEB_STATE_ERROR;
break;
}
if (s->play_fd != -1) {
pthread_mutex_lock(&s->mutex);
sun_linear_set_vol(s->play_buf, s->p_info.play.channels, to_write, s->volume);
pthread_mutex_unlock(&s->mutex);
if (s->record.fd != -1 && s->record.floating) {
sun_linear32_to_float(s->record.buf,
s->record.info.record.channels * SUN_BUFFER_FRAMES);
}
to_write = s->data_cb(s, s->user_ptr,
s->record.buf, s->play.buf, SUN_BUFFER_FRAMES);
if (to_write == CUBEB_ERROR) {
state = CUBEB_STATE_ERROR;
break;
}
if (s->play.fd != -1) {
float vol;
pthread_mutex_lock(&s->mutex);
vol = s->volume;
pthread_mutex_unlock(&s->mutex);
if (s->play.floating) {
sun_float_to_linear32(s->play.buf,
s->play.info.play.channels * to_write, vol);
} else {
sun_linear16_set_vol(s->play.buf,
s->play.info.play.channels * to_write, vol);
}
}
if (to_write < SUN_BUFFER_FRAMES) {
drain = 1;
}
to_write = s->play_fd != -1 ? to_write : 0;
to_read = s->record_fd != -1 ? SUN_BUFFER_FRAMES : 0;
to_write = s->play.fd != -1 ? to_write : 0;
to_read = s->record.fd != -1 ? SUN_BUFFER_FRAMES : 0;
write_ofs = 0;
read_ofs = 0;
while (to_write > 0 || to_read > 0) {
@ -487,12 +472,12 @@ sun_io_routine(void * arg)
ssize_t n, frames;
if (to_write > 0) {
bytes = FRAMES_TO_BYTES(to_write, s->p_info.play.channels);
if ((n = write(s->play_fd, s->play_buf + write_ofs, bytes)) < 0) {
bytes = to_write * s->play.frame_size;
if ((n = write(s->play.fd, s->play.buf + write_ofs, bytes)) < 0) {
state = CUBEB_STATE_ERROR;
break;
}
frames = BYTES_TO_FRAMES(n, s->p_info.play.channels);
frames = n / s->play.frame_size;
pthread_mutex_lock(&s->mutex);
s->frames_written += frames;
pthread_mutex_unlock(&s->mutex);
@ -500,12 +485,12 @@ sun_io_routine(void * arg)
write_ofs += frames;
}
if (to_read > 0) {
bytes = FRAMES_TO_BYTES(to_read, s->r_info.record.channels);
if ((n = read(s->record_fd, s->record_buf + read_ofs, bytes)) < 0) {
bytes = to_read * s->record.frame_size;
if ((n = read(s->record.fd, s->record.buf + read_ofs, bytes)) < 0) {
state = CUBEB_STATE_ERROR;
break;
}
frames = BYTES_TO_FRAMES(n, s->r_info.record.channels);
frames = n / s->record.frame_size;
to_read -= frames;
read_ofs += frames;
}
@ -541,19 +526,19 @@ sun_stream_init(cubeb * context,
ret = CUBEB_ERROR;
goto error;
}
s->record_fd = -1;
s->play_fd = -1;
s->record.fd = -1;
s->play.fd = -1;
if (input_device != 0) {
snprintf(s->input_name, sizeof(s->input_name),
snprintf(s->record.name, sizeof(s->record.name),
"/dev/audio%zu", (uintptr_t)input_device - 1);
} else {
snprintf(s->input_name, sizeof(s->input_name), "%s", SUN_DEFAULT_DEVICE);
snprintf(s->record.name, sizeof(s->record.name), "%s", SUN_DEFAULT_DEVICE);
}
if (output_device != 0) {
snprintf(s->output_name, sizeof(s->output_name),
snprintf(s->play.name, sizeof(s->play.name),
"/dev/audio%zu", (uintptr_t)output_device - 1);
} else {
snprintf(s->output_name, sizeof(s->output_name), "%s", SUN_DEFAULT_DEVICE);
snprintf(s->play.name, sizeof(s->play.name), "%s", SUN_DEFAULT_DEVICE);
}
if (input_stream_params != NULL) {
if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
@ -561,22 +546,23 @@ sun_stream_init(cubeb * context,
ret = CUBEB_ERROR_NOT_SUPPORTED;
goto error;
}
if (s->record_fd == -1) {
if ((s->record_fd = open(s->input_name, O_RDONLY)) == -1) {
LOG("Audio device cannot be opened as read-only");
if (s->record.fd == -1) {
if ((s->record.fd = open(s->record.name, O_RDONLY)) == -1) {
LOG("Audio device could not be opened as read-only");
ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
goto error;
}
}
AUDIO_INITINFO(&s->r_info);
AUDIO_INITINFO(&s->record.info);
#ifdef AUMODE_RECORD
s->r_info.mode = AUMODE_RECORD;
s->record.info.mode = AUMODE_RECORD;
#endif
if ((ret = sun_copy_params(s->record_fd, s, input_stream_params,
&s->r_info, &s->r_info.record)) != CUBEB_OK) {
if ((ret = sun_copy_params(s->record.fd, s, input_stream_params,
&s->record.info, &s->record.info.record)) != CUBEB_OK) {
LOG("Setting record params failed");
goto error;
}
s->record.floating = (input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
}
if (output_stream_params != NULL) {
if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
@ -584,22 +570,23 @@ sun_stream_init(cubeb * context,
ret = CUBEB_ERROR_NOT_SUPPORTED;
goto error;
}
if (s->play_fd == -1) {
if ((s->play_fd = open(s->output_name, O_WRONLY)) == -1) {
LOG("Audio device cannot be opened as write-only");
if (s->play.fd == -1) {
if ((s->play.fd = open(s->play.name, O_WRONLY)) == -1) {
LOG("Audio device could not be opened as write-only");
ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
goto error;
}
}
AUDIO_INITINFO(&s->p_info);
AUDIO_INITINFO(&s->play.info);
#ifdef AUMODE_PLAY
s->p_info.mode = AUMODE_PLAY;
s->play.info.mode = AUMODE_PLAY;
#endif
if ((ret = sun_copy_params(s->play_fd, s, output_stream_params,
&s->p_info, &s->p_info.play)) != CUBEB_OK) {
if ((ret = sun_copy_params(s->play.fd, s, output_stream_params,
&s->play.info, &s->play.info.play)) != CUBEB_OK) {
LOG("Setting play params failed");
goto error;
}
s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
}
s->context = context;
s->volume = 1.0;
@ -610,28 +597,20 @@ sun_stream_init(cubeb * context,
LOG("Failed to create mutex");
goto error;
}
if (s->play_fd != -1 && (s->play_buf = calloc(SUN_BUFFER_FRAMES,
s->p_info.play.channels * sizeof(int16_t))) == NULL) {
s->play.frame_size = s->play.info.play.channels *
(s->play.info.play.precision / 8);
if (s->play.fd != -1 &&
(s->play.buf = calloc(SUN_BUFFER_FRAMES, s->play.frame_size)) == NULL) {
ret = CUBEB_ERROR;
goto error;
}
if (s->record_fd != -1 && (s->record_buf = calloc(SUN_BUFFER_FRAMES,
s->r_info.record.channels * sizeof(int16_t))) == NULL) {
s->record.frame_size = s->record.info.record.channels *
(s->record.info.record.precision / 8);
if (s->record.fd != -1 &&
(s->record.buf = calloc(SUN_BUFFER_FRAMES, s->record.frame_size)) == NULL) {
ret = CUBEB_ERROR;
goto error;
}
if (s->floating) {
if (s->play_fd != -1 && (s->f_play_buf = calloc(SUN_BUFFER_FRAMES,
s->p_info.play.channels * sizeof(float))) == NULL) {
ret = CUBEB_ERROR;
goto error;
}
if (s->record_fd != -1 && (s->f_record_buf = calloc(SUN_BUFFER_FRAMES,
s->r_info.record.channels * sizeof(float))) == NULL) {
ret = CUBEB_ERROR;
goto error;
}
}
*stream = s;
return CUBEB_OK;
error:
@ -644,7 +623,7 @@ error:
static int
sun_stream_start(cubeb_stream * s)
{
s->running = 1;
s->running = true;
if (pthread_create(&s->thread, NULL, sun_io_routine, s) != 0) {
LOG("Couldn't create thread");
return CUBEB_ERROR;
@ -658,12 +637,11 @@ sun_stream_get_position(cubeb_stream * s, uint64_t * position)
#ifdef AUDIO_GETOOFFS
struct audio_offset offset;
if (ioctl(s->play_fd, AUDIO_GETOOFFS, &offset) == -1) {
if (ioctl(s->play.fd, AUDIO_GETOOFFS, &offset) == -1) {
return CUBEB_ERROR;
}
s->blocks_written += offset.deltablks;
*position = BYTES_TO_FRAMES(s->blocks_written * s->p_info.blocksize,
s->p_info.play.channels);
*position = (s->blocks_written * s->play.info.blocksize) / s->play.frame_size;
return CUBEB_OK;
#else
pthread_mutex_lock(&s->mutex);
@ -674,22 +652,21 @@ sun_stream_get_position(cubeb_stream * s, uint64_t * position)
}
static int
sun_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
sun_stream_get_latency(cubeb_stream * s, uint32_t * latency)
{
#ifdef AUDIO_GETBUFINFO
struct audio_info info;
if (ioctl(stream->play_fd, AUDIO_GETBUFINFO, &info) == -1) {
if (ioctl(s->play.fd, AUDIO_GETBUFINFO, &info) == -1) {
return CUBEB_ERROR;
}
*latency = BYTES_TO_FRAMES(info.play.seek + info.blocksize,
info.play.channels);
*latency = (info.play.seek + info.blocksize) / s->play.frame_size;
return CUBEB_OK;
#else
cubeb_stream_params params;
params.rate = stream->p_info.play.sample_rate;
params.rate = stream->play.info.play.sample_rate;
return sun_get_min_latency(NULL, params, latency);
#endif
@ -711,10 +688,10 @@ sun_get_current_device(cubeb_stream * stream, cubeb_device ** const device)
if (*device == NULL) {
return CUBEB_ERROR;
}
(*device)->input_name = stream->record_fd != -1 ?
strdup(stream->input_name) : NULL;
(*device)->output_name = stream->play_fd != -1 ?
strdup(stream->output_name) : NULL;
(*device)->input_name = stream->record.fd != -1 ?
strdup(stream->record.name) : NULL;
(*device)->output_name = stream->play.fd != -1 ?
strdup(stream->play.name) : NULL;
return CUBEB_OK;
}
@ -744,6 +721,7 @@ static struct cubeb_ops const sun_ops = {
.stream_reset_default_device = NULL,
.stream_get_position = sun_stream_get_position,
.stream_get_latency = sun_stream_get_latency,
.stream_get_input_latency = NULL,
.stream_set_volume = sun_stream_set_volume,
.stream_get_current_device = sun_get_current_device,
.stream_device_destroy = sun_stream_device_destroy,

View file

@ -19,5 +19,6 @@ size_t cubeb_sample_size(cubeb_sample_format format)
default:
// should never happen as all cases are handled above.
assert(false);
return 0;
}
}

View file

@ -89,6 +89,9 @@ DEFINE_PROPERTYKEY(PKEY_Device_InstanceId, 0x78c34fc8, 0x104a, 0x4aca, 0x9e
#endif
namespace {
const int64_t LATENCY_NOT_AVAILABLE_YET = -1;
struct com_heap_ptr_deleter {
void operator()(void * ptr) const noexcept {
CoTaskMemFree(ptr);
@ -211,6 +214,7 @@ struct cubeb {
/* Collection changed for output (render) devices. */
cubeb_device_collection_changed_callback output_collection_changed_callback = nullptr;
void * output_collection_changed_user_ptr = nullptr;
UINT64 performance_counter_frequency;
};
class wasapi_endpoint_notification_client;
@ -331,7 +335,12 @@ struct cubeb_stream {
bool draining = false;
/* True when we've destroyed the stream. This pointer is leaked on stream
* destruction if we could not join the thread. */
std::atomic<std::atomic<bool>*> emergency_bailout;
std::atomic<std::atomic<bool>*> emergency_bailout { nullptr };
/* Synchronizes render thread start to ensure safe access to emergency_bailout. */
HANDLE thread_ready_event = 0;
/* This needs an active audio input stream to be known, and is updated in the
* first audio input callback. */
std::atomic<int64_t> input_latency_hns { LATENCY_NOT_AVAILABLE_YET };
};
class monitor_device_notifications {
@ -344,13 +353,14 @@ public:
~monitor_device_notifications()
{
SetEvent(shutdown);
WaitForSingleObject(thread, 5000);
SetEvent(begin_shutdown);
WaitForSingleObject(shutdown_complete, INFINITE);
CloseHandle(thread);
CloseHandle(input_changed);
CloseHandle(output_changed);
CloseHandle(shutdown);
CloseHandle(begin_shutdown);
CloseHandle(shutdown_complete);
}
void notify(EDataFlow flow)
@ -375,8 +385,9 @@ private:
thread_proc(LPVOID args)
{
XASSERT(args);
static_cast<monitor_device_notifications*>(args)
->notification_thread_loop();
auto mdn = static_cast<monitor_device_notifications*>(args);
mdn->notification_thread_loop();
SetEvent(mdn->shutdown_complete);
return 0;
}
@ -395,7 +406,7 @@ private:
HANDLE wait_array[3] = {
input_changed,
output_changed,
shutdown,
begin_shutdown,
};
while (true) {
@ -433,9 +444,15 @@ private:
return;
}
shutdown = CreateEvent(nullptr, 0, 0, nullptr);
if (!shutdown) {
LOG("Failed to create shutdown event.");
begin_shutdown = CreateEvent(nullptr, 0, 0, nullptr);
if (!begin_shutdown) {
LOG("Failed to create begin_shutdown event.");
return;
}
shutdown_complete = CreateEvent(nullptr, 0, 0, nullptr);
if (!shutdown_complete) {
LOG("Failed to create shutdown_complete event.");
return;
}
@ -454,7 +471,8 @@ private:
HANDLE thread = INVALID_HANDLE_VALUE;
HANDLE output_changed = INVALID_HANDLE_VALUE;
HANDLE input_changed = INVALID_HANDLE_VALUE;
HANDLE shutdown = INVALID_HANDLE_VALUE;
HANDLE begin_shutdown = INVALID_HANDLE_VALUE;
HANDLE shutdown_complete = INVALID_HANDLE_VALUE;
cubeb * cubeb_context = nullptr;
};
@ -845,6 +863,7 @@ bool get_input_buffer(cubeb_stream * stm)
BYTE * input_packet = NULL;
DWORD flags;
UINT64 dev_pos;
UINT64 pc_position;
UINT32 next;
/* Get input packets until we have captured enough frames, and put them in a
* contiguous buffer. */
@ -874,13 +893,25 @@ bool get_input_buffer(cubeb_stream * stm)
&frames,
&flags,
&dev_pos,
NULL);
&pc_position);
if (FAILED(hr)) {
LOG("GetBuffer failed for capture: %lx", hr);
return false;
}
XASSERT(frames == next);
if (stm->context->performance_counter_frequency) {
LARGE_INTEGER now;
UINT64 now_hns;
// See https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudiocaptureclient-getbuffer, section "Remarks".
QueryPerformanceCounter(&now);
now_hns = 10000000 * now.QuadPart / stm->context->performance_counter_frequency;
if (now_hns >= pc_position) {
stm->input_latency_hns = now_hns - pc_position;
}
}
UINT32 input_stream_samples = frames * stm->input_stream_params.channels;
// We do not explicitly handle the AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY
// flag. There a two primary (non exhaustive) scenarios we anticipate this
@ -1143,6 +1174,13 @@ wasapi_stream_render_loop(LPVOID stream)
cubeb_stream * stm = static_cast<cubeb_stream *>(stream);
std::atomic<bool> * emergency_bailout = stm->emergency_bailout;
// Signal wasapi_stream_start that we've copied emergency_bailout.
BOOL ok = SetEvent(stm->thread_ready_event);
if (!ok) {
LOG("thread_ready SetEvent failed: %lx", GetLastError());
return 0;
}
bool is_playing = true;
HANDLE wait_array[4] = {
stm->shutdown_event,
@ -1171,11 +1209,6 @@ wasapi_stream_render_loop(LPVOID stream)
LOG("Unable to use mmcss to bump the render thread priority: %lx", GetLastError());
}
// This has already been nulled out, simply exit.
if (!emergency_bailout) {
is_playing = false;
}
/* WaitForMultipleObjects timeout can trigger in cases where we don't want to
treat it as a timeout, such as across a system sleep/wake cycle. Trigger
the timeout error handling only when the timeout_limit is reached, which is
@ -1514,6 +1547,14 @@ int wasapi_init(cubeb ** context, char const * context_name)
return CUBEB_ERROR;
}
LARGE_INTEGER frequency;
if (QueryPerformanceFrequency(&frequency)) {
LOG("Failed getting performance counter frequency, latency reporting will be inacurate");
ctx->performance_counter_frequency = 0;
} else {
ctx->performance_counter_frequency = frequency.QuadPart;
}
*context = ctx;
return CUBEB_OK;
@ -1544,24 +1585,15 @@ bool stop_and_join_render_thread(cubeb_stream * stm)
/* Wait five seconds for the rendering thread to return. It's supposed to
* check its event loop very often, five seconds is rather conservative. */
DWORD r = WaitForSingleObject(stm->thread, 5000);
if (r == WAIT_TIMEOUT) {
if (r != WAIT_OBJECT_0) {
/* Something weird happened, leak the thread and continue the shutdown
* process. */
*(stm->emergency_bailout) = true;
// We give the ownership to the rendering thread.
stm->emergency_bailout = nullptr;
LOG("Destroy WaitForSingleObject on thread timed out,"
" leaking the thread: %lx", GetLastError());
LOG("Destroy WaitForSingleObject on thread failed: %lx, %lx", r, GetLastError());
rv = false;
}
if (r == WAIT_FAILED) {
*(stm->emergency_bailout) = true;
// We give the ownership to the rendering thread.
stm->emergency_bailout = nullptr;
LOG("Destroy WaitForSingleObject on thread failed: %lx", GetLastError());
rv = false;
}
// Only attempts to close and null out the thread and event if the
// WaitForSingleObject above succeeded, so that calling this function again
@ -1926,14 +1958,18 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
/* Get a client. We will get all other interfaces we need from
* this pointer. */
#if 0 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1590902
hr = device->Activate(__uuidof(IAudioClient3),
CLSCTX_INPROC_SERVER,
NULL, audio_client.receive_vpp());
if (hr == E_NOINTERFACE) {
#endif
hr = device->Activate(__uuidof(IAudioClient),
CLSCTX_INPROC_SERVER,
NULL, audio_client.receive_vpp());
#if 0
}
#endif
if (FAILED(hr)) {
LOG("Could not activate the device to get an audio"
@ -1999,16 +2035,20 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
}
#if 0 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1590902
if (initialize_iaudioclient3(audio_client, stm, mix_format, flags, direction)) {
LOG("Initialized with IAudioClient3");
} else {
#endif
hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED,
flags,
frames_to_hns(stm, stm->latency),
0,
mix_format.get(),
NULL);
#if 0
}
#endif
if (FAILED(hr)) {
LOG("Unable to initialize audio client for %s: %lx.", DIRECTION_NAME, hr);
return CUBEB_ERROR;
@ -2471,6 +2511,12 @@ int wasapi_stream_start(cubeb_stream * stm)
return CUBEB_ERROR;
}
stm->thread_ready_event = CreateEvent(NULL, 0, 0, NULL);
if (!stm->thread_ready_event) {
LOG("Can't create the thread_ready event, error: %lx", GetLastError());
return CUBEB_ERROR;
}
cubeb_async_log_reset_threads();
stm->thread = (HANDLE) _beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
if (stm->thread == NULL) {
@ -2478,6 +2524,14 @@ int wasapi_stream_start(cubeb_stream * stm)
return CUBEB_ERROR;
}
// Wait for wasapi_stream_render_loop to signal that emergency_bailout has
// been read, avoiding a bailout situation where we could free `stm`
// before wasapi_stream_render_loop had a chance to run.
HRESULT hr = WaitForSingleObject(stm->thread_ready_event, INFINITE);
XASSERT(hr == WAIT_OBJECT_0);
CloseHandle(stm->thread_ready_event);
stm->thread_ready_event = 0;
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
return CUBEB_OK;
@ -2511,11 +2565,8 @@ int wasapi_stream_stop(cubeb_stream * stm)
}
if (stop_and_join_render_thread(stm)) {
// This is null if we've given the pointer to the other thread
if (stm->emergency_bailout.load()) {
delete stm->emergency_bailout.load();
stm->emergency_bailout = nullptr;
}
delete stm->emergency_bailout.load();
stm->emergency_bailout = nullptr;
} else {
// If we could not join the thread, put the stream in error.
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
@ -2591,6 +2642,26 @@ int wasapi_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
return CUBEB_OK;
}
int wasapi_stream_get_input_latency(cubeb_stream * stm, uint32_t * latency)
{
XASSERT(stm && latency);
if (!has_input(stm)) {
return CUBEB_ERROR;
}
auto_lock lock(stm->stream_reset_lock);
if (stm->input_latency_hns == LATENCY_NOT_AVAILABLE_YET) {
return CUBEB_ERROR;
}
*latency = hns_to_frames(stm, stm->input_latency_hns);
return CUBEB_OK;
}
int wasapi_stream_set_volume(cubeb_stream * stm, float volume)
{
auto_lock lock(stm->stream_reset_lock);
@ -2950,6 +3021,7 @@ cubeb_ops const wasapi_ops = {
/*.stream_reset_default_device =*/ wasapi_stream_reset_default_device,
/*.stream_get_position =*/ wasapi_stream_get_position,
/*.stream_get_latency =*/ wasapi_stream_get_latency,
/*.stream_get_input_latency =*/ wasapi_stream_get_input_latency,
/*.stream_set_volume =*/ wasapi_stream_set_volume,
/*.stream_get_current_device =*/ NULL,
/*.stream_device_destroy =*/ NULL,

View file

@ -1059,6 +1059,7 @@ static struct cubeb_ops const winmm_ops = {
/*.stream_reset_default_device =*/ NULL,
/*.stream_get_position =*/ winmm_stream_get_position,
/*.stream_get_latency = */ winmm_stream_get_latency,
/*.stream_get_input_latency = */ NULL,
/*.stream_set_volume =*/ winmm_stream_set_volume,
/*.stream_get_current_device =*/ NULL,
/*.stream_device_destroy =*/ NULL,

View file

@ -66,22 +66,6 @@ void test_registering_and_unregistering_callback(cubeb_stream * stream)
ASSERT_EQ(r, CUBEB_OK) << "Error unregistering device changed callback";
}
void test_registering_second_callbacks(cubeb_stream * stream)
{
int r = cubeb_stream_register_device_changed_callback(stream, device_changed_callback);
if (r == CUBEB_ERROR_NOT_SUPPORTED) {
return;
}
ASSERT_EQ(r, CUBEB_OK) << "Error registering device changed callback";
// Get an assertion fails when registering a callback
// without unregistering the original one.
ASSERT_DEATH(
cubeb_stream_register_device_changed_callback(stream, device_changed_callback),
""
);
}
TEST(cubeb, device_changed_callbacks)
{
cubeb * ctx;
@ -121,7 +105,5 @@ TEST(cubeb, device_changed_callbacks)
test_registering_and_unregistering_callback(stream);
test_registering_second_callbacks(stream);
cubeb_stream_destroy(stream);
}

View file

@ -161,7 +161,7 @@ TEST(cubeb, enumerate_devices)
if (r == CUBEB_ERROR_NOT_SUPPORTED) {
fprintf(stderr, "Device enumeration not supported"
" for this backend, skipping this test.\n");
r = CUBEB_OK;
return;
}
ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;

View file

@ -26,7 +26,7 @@
const uint32_t SAMPLE_FREQUENCY = 48000;
const uint32_t TONE_FREQUENCY = 440;
const double OUTPUT_AMPLITUDE = 0.25;
const uint32_t NUM_FRAMES_TO_OUTPUT = SAMPLE_FREQUENCY / 20; /* play ~50ms of samples */
const int32_t NUM_FRAMES_TO_OUTPUT = SAMPLE_FREQUENCY / 20; /* play ~50ms of samples */
template<typename T> T ConvertSampleToOutput(double input);
template<> float ConvertSampleToOutput(double input) { return float(input); }

View file

@ -889,3 +889,175 @@ TEST(cubeb, resampler_drift_drop_data)
}
}
static long
passthrough_resampler_fill_eq_input(cubeb_stream * stream,
void * user_ptr,
void const * input_buffer,
void * output_buffer,
long nframes) {
// gtest does not support using ASSERT_EQ and friends in a
// function that returns a value.
[nframes, input_buffer]() {
ASSERT_EQ(nframes, 32);
const float* input = static_cast<const float*>(input_buffer);
for (int i = 0; i < 64; ++i) {
ASSERT_FLOAT_EQ(input[i], 0.01 * i);
}
}();
return nframes;
}
TEST(cubeb, passthrough_resampler_fill_eq_input) {
uint32_t channels = 2;
uint32_t sample_rate = 44100;
passthrough_resampler<float> resampler =
passthrough_resampler<float>(nullptr, passthrough_resampler_fill_eq_input,
nullptr, channels, sample_rate);
long input_frame_count = 32;
long output_frame_count = 32;
float input[64] = {};
float output[64] = {};
for (uint32_t i = 0; i < input_frame_count * channels; ++i) {
input[i] = 0.01 * i;
}
long got = resampler.fill(input, &input_frame_count, output, output_frame_count);
ASSERT_EQ(got, output_frame_count);
// Input frames used must be equal to output frames.
ASSERT_EQ(input_frame_count, output_frame_count);
}
static long
passthrough_resampler_fill_short_input(cubeb_stream * stream,
void * user_ptr,
void const * input_buffer,
void * output_buffer,
long nframes) {
// gtest does not support using ASSERT_EQ and friends in a
// function that returns a value.
[nframes, input_buffer]() {
ASSERT_EQ(nframes, 32);
const float* input = static_cast<const float*>(input_buffer);
// First part contains the input
for (int i = 0; i < 32; ++i) {
ASSERT_FLOAT_EQ(input[i], 0.01 * i);
}
// missing part contains silence
for (int i = 32; i < 64; ++i) {
ASSERT_FLOAT_EQ(input[i], 0.0);
}
}();
return nframes;
}
TEST(cubeb, passthrough_resampler_fill_short_input) {
uint32_t channels = 2;
uint32_t sample_rate = 44100;
passthrough_resampler<float> resampler =
passthrough_resampler<float>(nullptr, passthrough_resampler_fill_short_input,
nullptr, channels, sample_rate);
long input_frame_count = 16;
long output_frame_count = 32;
float input[64] = {};
float output[64] = {};
for (uint32_t i = 0; i < input_frame_count * channels; ++i) {
input[i] = 0.01 * i;
}
long got = resampler.fill(input, &input_frame_count, output, output_frame_count);
ASSERT_EQ(got, output_frame_count);
// Input frames used are less than the output frames due to glitch.
ASSERT_EQ(input_frame_count, output_frame_count - 16);
}
static long
passthrough_resampler_fill_input_left(cubeb_stream * stream,
void * user_ptr,
void const * input_buffer,
void * output_buffer,
long nframes) {
// gtest does not support using ASSERT_EQ and friends in a
// function that returns a value.
int iteration = *static_cast<int*>(user_ptr);
if (iteration == 1) {
[nframes, input_buffer]() {
ASSERT_EQ(nframes, 32);
const float* input = static_cast<const float*>(input_buffer);
for (int i = 0; i < 64; ++i) {
ASSERT_FLOAT_EQ(input[i], 0.01 * i);
}
}();
} else if (iteration == 2) {
[nframes, input_buffer]() {
ASSERT_EQ(nframes, 32);
const float* input = static_cast<const float*>(input_buffer);
for (int i = 0; i < 32; ++i) {
// First part contains the reamaining input samples from previous
// iteration (since they were more).
ASSERT_FLOAT_EQ(input[i], 0.01 * (i + 64));
// next part contains the new buffer
ASSERT_FLOAT_EQ(input[i + 32], 0.01 * i);
}
}();
} else if (iteration == 3) {
[nframes, input_buffer]() {
ASSERT_EQ(nframes, 32);
const float* input = static_cast<const float*>(input_buffer);
for (int i = 0; i < 32; ++i) {
// First part (16 frames) contains the reamaining input samples
// from previous iteration (since they were more).
ASSERT_FLOAT_EQ(input[i], 0.01 * (i + 32));
}
for (int i = 0; i < 16; ++i) {
// next part (8 frames) contains the new input buffer.
ASSERT_FLOAT_EQ(input[i + 32], 0.01 * i);
// last part (8 frames) contains silence.
ASSERT_FLOAT_EQ(input[i + 32 + 16], 0.0);
}
}();
}
return nframes;
}
TEST(cubeb, passthrough_resampler_fill_input_left) {
const uint32_t channels = 2;
const uint32_t sample_rate = 44100;
int iteration = 0;
passthrough_resampler<float> resampler =
passthrough_resampler<float>(nullptr, passthrough_resampler_fill_input_left,
&iteration, channels, sample_rate);
long input_frame_count = 48; // 32 + 16
const long output_frame_count = 32;
float input[96] = {};
float output[64] = {};
for (uint32_t i = 0; i < input_frame_count * channels; ++i) {
input[i] = 0.01 * i;
}
// 1st iteration, add the extra input.
iteration = 1;
long got = resampler.fill(input, &input_frame_count, output, output_frame_count);
ASSERT_EQ(got, output_frame_count);
// Input frames used must be equal to output frames.
ASSERT_EQ(input_frame_count, output_frame_count);
// 2st iteration, use the extra input from previous iteration,
// 16 frames are remaining in the input buffer.
input_frame_count = 32; // we need 16 input frames but we get more;
iteration = 2;
got = resampler.fill(input, &input_frame_count, output, output_frame_count);
ASSERT_EQ(got, output_frame_count);
// Input frames used must be equal to output frames.
ASSERT_EQ(input_frame_count, output_frame_count);
// 3rd iteration, use the extra input from previous iteration.
// 16 frames are remaining in the input buffer.
input_frame_count = 16 - 8; // We need 16 more input frames but we only get 8.
iteration = 3;
got = resampler.fill(input, &input_frame_count, output, output_frame_count);
ASSERT_EQ(got, output_frame_count);
// Input frames used are less than the output frames due to glitch.
ASSERT_EQ(input_frame_count, output_frame_count - 8);
}

View file

@ -102,7 +102,7 @@ void test_ring_multi(lock_free_audio_ring_buffer<T>& buf, int channels, int capa
sequence_generator<T> gen(channels);
while(iterations--) {
std::this_thread::sleep_for(std::chrono::microseconds(10));
std::this_thread::yield();
gen.get(in_buffer.get(), block_size);
int rv = buf.enqueue(in_buffer.get(), block_size);
ASSERT_TRUE(rv <= block_size);
@ -115,7 +115,7 @@ void test_ring_multi(lock_free_audio_ring_buffer<T>& buf, int channels, int capa
int remaining = 1002;
while(remaining--) {
std::this_thread::sleep_for(std::chrono::microseconds(10));
std::this_thread::yield();
int rv = buf.dequeue(out_buffer.get(), block_size);
ASSERT_TRUE(rv <= block_size);
checker.check(out_buffer.get(), rv);

View file

@ -49,8 +49,8 @@ public:
bool init(char const * backend_name = nullptr);
bool init_stream();
bool start_stream() const;
bool stop_stream() const;
bool start_stream();
bool stop_stream();
bool destroy_stream() const;
bool destroy();
bool activate_log(cubeb_log_level log_level) const;
@ -58,6 +58,7 @@ public:
void set_latency_frames(uint32_t latency_frames);
uint64_t get_stream_position() const;
uint32_t get_stream_latency() const;
uint32_t get_max_channel_count() const;
long user_data_cb(cubeb_stream* stm, void* user, const void* input_buffer,
void* output_buffer, long nframes);
@ -70,6 +71,8 @@ public:
cubeb_stream_params output_params = {};
cubeb_stream_params input_params = {};
void force_drain() { _force_drain = true; }
private:
bool has_input() { return input_params.rate != 0; }
bool has_output() { return output_params.rate != 0; }
@ -85,6 +88,7 @@ private:
std::atomic<uint32_t> _channels = {0};
std::atomic<bool> _latency_testing = {false};
std::atomic<uint32_t> _latency_frames = {0}; // if !0, override. Else, use min.
std::atomic<bool> _force_drain = {false};
/* Accessed only from audio thread. */
@ -92,11 +96,12 @@ private:
};
bool cubeb_client::init(char const * backend_name) {
int rv = cubeb_init(&context, "Cubeb Test Application", nullptr);
int rv = cubeb_init(&context, "Cubeb Test Application", backend_name);
if (rv != CUBEB_OK) {
fprintf(stderr, "Could not init cubeb\n");
return false;
}
fprintf(stderr, "Init cubeb backend: %s\n", cubeb_get_backend_id(context));
return true;
}
@ -159,7 +164,8 @@ bool cubeb_client::init_stream() {
return true;
}
bool cubeb_client::start_stream() const {
bool cubeb_client::start_stream() {
_force_drain = false;
int rv = cubeb_stream_start(stream);
if (rv != CUBEB_OK) {
fprintf(stderr, "Could not start the stream\n");
@ -168,7 +174,8 @@ bool cubeb_client::start_stream() const {
return true;
}
bool cubeb_client::stop_stream() const {
bool cubeb_client::stop_stream() {
_force_drain = false;
int rv = cubeb_stream_stop(stream);
if (rv != CUBEB_OK) {
fprintf(stderr, "Could not stop the stream\n");
@ -197,6 +204,16 @@ uint32_t cubeb_client::get_stream_latency() const {
return latency;
}
uint32_t cubeb_client::get_max_channel_count() const {
uint32_t channels = 0;
int rv = cubeb_get_max_channel_count(context, &channels);
if (rv != CUBEB_OK) {
fprintf(stderr, "Could not get max channel count\n");
return 0;
}
return channels;
}
bool cubeb_client::destroy_stream() const {
cubeb_stream_destroy(stream);
return true;
@ -248,7 +265,7 @@ long cubeb_client::user_data_cb(cubeb_stream* stm, void* user,
const float* in = static_cast<const float*>(input_buffer);
float* out = static_cast<float*>(output_buffer);
if (_latency_testing) {
for (uint32_t i = 0; i < nframes; i++) {
for (int32_t i = 0; i < nframes; i++) {
// Impulses every second, mixed with the input signal fed back at half
// gain, to measure the input-to-output latency via feedback.
uint32_t clock = ((_total_frames + i) % _rate);
@ -263,7 +280,7 @@ long cubeb_client::user_data_cb(cubeb_stream* stm, void* user,
}
}
} else {
for (uint32_t i = 0; i < nframes; i++) {
for (int32_t i = 0; i < nframes; i++) {
for (uint32_t j = 0; j < _channels; j++) {
out[i * _channels + j] = in[i];
}
@ -275,6 +292,11 @@ long cubeb_client::user_data_cb(cubeb_stream* stm, void* user,
}
_total_frames += nframes;
if (_force_drain) {
return nframes - 1;
}
return nframes;
}
@ -331,9 +353,12 @@ void print_help() {
"0: change log level to disabled\n"
"1: change log level to normal\n"
"2: change log level to verbose\n"
"c: get max number of channels\n"
"p: start a initialized stream\n"
"s: stop a started stream\n"
"c: get stream position (client thread)\n"
"d: destroy stream\n"
"e: force stream to drain\n"
"f: get stream position (client thread)\n"
"i: change device type to input\n"
"o: change device type to output\n"
"a: change device type to input and output\n"
@ -345,7 +370,7 @@ void print_help() {
fprintf(stderr, "%s\n", msg);
}
bool choose_action(const cubeb_client& cl, operation_data * op, int c) {
bool choose_action(cubeb_client& cl, operation_data * op, int c) {
// Consume "enter" and "space"
while (c == 10 || c == 32) {
c = getchar();
@ -398,7 +423,19 @@ bool choose_action(const cubeb_client& cl, operation_data * op, int c) {
} else {
fprintf(stderr, "stop_stream failed\n");
}
} else if (c == 'd') {
bool res = cl.destroy_stream();
if (res) {
fprintf(stderr, "destroy_stream succeed\n");
} else {
fprintf(stderr, "destroy_stream failed\n");
}
} else if (c == 'e') {
cl.force_drain();
} else if (c == 'c') {
uint32_t channel_count = cl.get_max_channel_count();
fprintf(stderr, "max channel count (default output device): %u\n", channel_count);
} else if (c == 'f') {
uint64_t pos = cl.get_stream_position();
uint64_t latency = cl.get_stream_latency();
fprintf(stderr, "stream position %" PRIu64 " (latency %" PRIu64 ")\n", pos, latency);
@ -468,7 +505,7 @@ int main(int argc, char* argv[]) {
cubeb_client cl;
cl.activate_log(CUBEB_LOG_DISABLED);
fprintf(stderr, "Log level is DISABLED\n");
cl.init();
cl.init(/* default backend */);
op.collection_device_type = CUBEB_DEVICE_TYPE_UNKNOWN;
fprintf(stderr, "collection device type is UNKNOWN\n");

View file

@ -27,11 +27,11 @@ pub const CUBEB_DEVICE_FMT_S16NE: cubeb_device_fmt = CUBEB_DEVICE_FMT_S16LE;
pub const CUBEB_DEVICE_FMT_F32NE: cubeb_device_fmt = CUBEB_DEVICE_FMT_F32LE;
pub const CUBEB_DEVICE_FMT_S16_MASK: cubeb_device_fmt =
(CUBEB_DEVICE_FMT_S16LE | CUBEB_DEVICE_FMT_S16BE);
CUBEB_DEVICE_FMT_S16LE | CUBEB_DEVICE_FMT_S16BE;
pub const CUBEB_DEVICE_FMT_F32_MASK: cubeb_device_fmt =
(CUBEB_DEVICE_FMT_F32LE | CUBEB_DEVICE_FMT_F32BE);
CUBEB_DEVICE_FMT_F32LE | CUBEB_DEVICE_FMT_F32BE;
pub const CUBEB_DEVICE_FMT_ALL: cubeb_device_fmt =
(CUBEB_DEVICE_FMT_S16_MASK | CUBEB_DEVICE_FMT_F32_MASK);
CUBEB_DEVICE_FMT_S16_MASK | CUBEB_DEVICE_FMT_F32_MASK;
cubeb_enum! {
pub enum cubeb_device_pref {

View file

@ -66,6 +66,7 @@ extern "C" {
pub fn cubeb_stream_reset_default_device(stream: *mut cubeb_stream) -> c_int;
pub fn cubeb_stream_get_position(stream: *mut cubeb_stream, position: *mut u64) -> c_int;
pub fn cubeb_stream_get_latency(stream: *mut cubeb_stream, latency: *mut c_uint) -> c_int;
pub fn cubeb_stream_get_input_latency(stream: *mut cubeb_stream, latency: *mut c_uint) -> c_int;
pub fn cubeb_stream_set_volume(stream: *mut cubeb_stream, volume: c_float) -> c_int;
pub fn cubeb_stream_get_current_device(
stream: *mut cubeb_stream,

View file

@ -1 +1 @@
{"files":{"Cargo.toml":"3f16c9c35795e9a05783518797fba3fd031ae46b61232ce0c05e13e5203506d7","LICENSE":"8c044baa5d883274736eeece0b955249076c2697b826e576fce59496235b2cf5","README.md":"408c573ec240927cf5b9c036098e94e374ec41f71991415422586f450586b214","examples/common/mod.rs":"a5e1b79fc2b4addff1e442879ba3dbcb1cf5973e76b9a62d97dd0042597480db","examples/devices.rs":"89e13542853995d1ae4a49d6829156efb29dd25c6caffdf22496c28c8263ffeb","examples/tone.rs":"8f5f9851b6d99f6f16c597fcb9312e3ef81769cbfb89341d2ea2522ca2e2214e","src/context.rs":"03511fa960a411728163e700edc2fd6cfbfcf09766ffe62ee82a2cbd08fdf243","src/frame.rs":"ed1e8f4576022d0c23106bb115125e5a2967b0375a10d0c54bbe99f04a70cc3f","src/lib.rs":"98e9280890551ac9305f2f808e315b6aa6bcd5781b8e96a078787ded0ef91e2a","src/log.rs":"704faeb31934dad6bc6d02e01caa85118754209bd559d30d03fcfa5cb8c1603c","src/sample.rs":"e23be3b691052001916f920ce9c1a0051bd097e39c9d34cbcb80ab8120265f45","src/stream.rs":"3ee0432f655cd42959cd5d8e75cb4fe2322e1f88fa5d9cc83e615ae229cdeb8a"},"package":"3cbcdfde9ea319160af6eff068ffaa96aad3532e1b5c0ebc134614cfacacae24"}
{"files":{"Cargo.lock":"8768f2709f7b520aa3bd0973a03333a1880eb65625af912514d1a26b712326fe","Cargo.toml":"28be23f743402b5e1b63a04246aeb186cdae205bf253617ef4bab42408ee1263","LICENSE":"8c044baa5d883274736eeece0b955249076c2697b826e576fce59496235b2cf5","README.md":"408c573ec240927cf5b9c036098e94e374ec41f71991415422586f450586b214","examples/common/mod.rs":"a5e1b79fc2b4addff1e442879ba3dbcb1cf5973e76b9a62d97dd0042597480db","examples/devices.rs":"ff5dcd588e7036165c4b4c20ec355d036e0ae90cf88b3b0f5cd86621fe2ce61d","examples/tone.rs":"8f5f9851b6d99f6f16c597fcb9312e3ef81769cbfb89341d2ea2522ca2e2214e","src/context.rs":"72507f5338a2f520fef9e2eface0638afba4c0d9e87fde92a0aaade643ba1335","src/frame.rs":"ed1e8f4576022d0c23106bb115125e5a2967b0375a10d0c54bbe99f04a70cc3f","src/lib.rs":"98e9280890551ac9305f2f808e315b6aa6bcd5781b8e96a078787ded0ef91e2a","src/log.rs":"704faeb31934dad6bc6d02e01caa85118754209bd559d30d03fcfa5cb8c1603c","src/sample.rs":"e23be3b691052001916f920ce9c1a0051bd097e39c9d34cbcb80ab8120265f45","src/stream.rs":"b3babf86252cd19cfbc98ffbc8f48bb033284f89db9cbdc46836611893356eff"},"package":"1116606d6045c9199f6a1e082f3cf63383ba6f9961339701faa6370dcf73135f"}

55
third_party/rust/cubeb/Cargo.lock generated vendored Normal file
View file

@ -0,0 +1,55 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "cc"
version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d87b23d6a92cd03af510a5ade527033f6aa6fa92161e2d5863a907d4c5e31d"
[[package]]
name = "cmake"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81fb25b677f8bf1eb325017cb6bb8452f87969db0fedb4f757b297bee78a7c62"
dependencies = [
"cc",
]
[[package]]
name = "cubeb"
version = "0.7.0"
dependencies = [
"cubeb-core",
]
[[package]]
name = "cubeb-core"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c55529b8f47926e4242e1fc01d31b08a5a4847967c5c250644e33fe237cfe5"
dependencies = [
"bitflags",
"cubeb-sys",
]
[[package]]
name = "cubeb-sys"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcbc562eb6ccf62abacf9e3eebce992e5c36b230ca313ebd7c2d7d0e99deae90"
dependencies = [
"cmake",
"pkg-config",
]
[[package]]
name = "pkg-config"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"

View file

@ -12,7 +12,7 @@
[package]
name = "cubeb"
version = "0.6.2"
version = "0.7.0"
authors = ["Dan Glastonbury <dglastonbury@mozilla.com>"]
description = "Bindings to libcubeb for interacting with system audio from rust.\n"
homepage = "https://github.com/djg/cubeb-rs"
@ -22,7 +22,7 @@ categories = ["api-bindings"]
license = "ISC"
repository = "https://github.com/djg/cubeb-rs"
[dependencies.cubeb-core]
version = "0.6.2"
version = "0.7.0"
[features]
gecko-in-tree = ["cubeb-core/gecko-in-tree"]

View file

@ -11,7 +11,6 @@ extern crate cubeb;
mod common;
use cubeb::{DeviceFormat, DeviceType};
use std::error::Error;
fn print_device_info(info: &cubeb::DeviceInfo) {
let devtype = if info.device_type().contains(DeviceType::INPUT) {
@ -101,7 +100,7 @@ fn main() {
return;
}
Err(e) => {
println!("Error enumerating devices: {}", e.description());
println!("Error enumerating devices: {}", e);
return;
}
};
@ -119,7 +118,7 @@ fn main() {
let devices = match ctx.enumerate_devices(DeviceType::OUTPUT) {
Ok(devices) => devices,
Err(e) => {
println!("Error enumerating devices: {}", e.description());
println!("Error enumerating devices: {}", e);
return;
}
};

View file

@ -2,7 +2,7 @@ use {Context, Result};
use std::ffi::CString;
pub fn init<T: Into<Vec<u8>>>(name: T) -> Result<Context> {
let name = try!(CString::new(name));
let name = CString::new(name)?;
Context::init(Some(name.as_c_str()), None)
}

View file

@ -68,9 +68,9 @@ use std::mem::ManuallyDrop;
use std::os::raw::{c_long, c_void};
use std::slice::{from_raw_parts, from_raw_parts_mut};
pub type DataCallback<F> = FnMut(&[F], &mut [F]) -> isize + Send + Sync + 'static;
pub type StateCallback = FnMut(State) + Send + Sync + 'static;
pub type DeviceChangedCallback = FnMut() + Send + Sync + 'static;
pub type DataCallback<F> = dyn FnMut(&[F], &mut [F]) -> isize + Send + Sync + 'static;
pub type StateCallback = dyn FnMut(State) + Send + Sync + 'static;
pub type DeviceChangedCallback = dyn FnMut() + Send + Sync + 'static;
pub struct StreamCallbacks<F> {
pub(crate) data: Box<DataCallback<F>>,
@ -201,7 +201,7 @@ impl<'a, F> StreamBuilder<'a, F> {
let state_callback: ffi::cubeb_state_callback = Some(state_cb_c::<F>);
let stream = unsafe {
try!(ctx.stream_init(
ctx.stream_init(
stream_name,
input_device,
input_stream_params,
@ -211,12 +211,12 @@ impl<'a, F> StreamBuilder<'a, F> {
data_callback,
state_callback,
cbs as *mut _
))
)?
};
if has_device_changed {
let device_changed_callback: ffi::cubeb_device_changed_callback =
Some(device_changed_cb_c::<F>);
try!(stream.register_device_changed_callback(device_changed_callback));
stream.register_device_changed_callback(device_changed_callback)?;
}
Ok(Stream::new(stream))
}