зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1605471 - Update cubeb-coreaudio to acb90e9. r=alwu
Pick commits: acb90e9 - Add license for mixer crate 82e7ff9 - Move criterion crate to [dev-dependencies] f8b7d96 - Implement a new any-to-any mixer (#23) ae43813 - Remove the test_set_channel_layout_input (#32) 9d0a0e8 - Improve device switch tests (#14) 833a062 - Enable test_ops_stream_register_device_changed_callback (#33) Differential Revision: https://phabricator.services.mozilla.com/D58177 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
2627475cc8
Коммит
7e8bb1db07
|
@ -65,7 +65,7 @@ replace-with = "vendored-sources"
|
|||
[source."https://github.com/ChunMinChang/cubeb-coreaudio-rs"]
|
||||
git = "https://github.com/ChunMinChang/cubeb-coreaudio-rs"
|
||||
replace-with = "vendored-sources"
|
||||
rev = "868d847c2e95096b6eec629dfed77ef363363ecb"
|
||||
rev = "acb90e9bf36e6e035ac6bbe51efa0a8825b5b6be"
|
||||
|
||||
[source.crates-io]
|
||||
replace-with = "vendored-sources"
|
||||
|
|
|
@ -626,7 +626,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "coreaudio-sys-utils"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=868d847c2e95096b6eec629dfed77ef363363ecb#868d847c2e95096b6eec629dfed77ef363363ecb"
|
||||
source = "git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=acb90e9bf36e6e035ac6bbe51efa0a8825b5b6be#acb90e9bf36e6e035ac6bbe51efa0a8825b5b6be"
|
||||
dependencies = [
|
||||
"core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"coreaudio-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -848,15 +848,16 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cubeb-coreaudio"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=868d847c2e95096b6eec629dfed77ef363363ecb#868d847c2e95096b6eec629dfed77ef363363ecb"
|
||||
source = "git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=acb90e9bf36e6e035ac6bbe51efa0a8825b5b6be#acb90e9bf36e6e035ac6bbe51efa0a8825b5b6be"
|
||||
dependencies = [
|
||||
"atomic 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"coreaudio-sys-utils 0.1.0 (git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=868d847c2e95096b6eec629dfed77ef363363ecb)",
|
||||
"coreaudio-sys-utils 0.1.0 (git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=acb90e9bf36e6e035ac6bbe51efa0a8825b5b6be)",
|
||||
"cubeb-backend 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mach 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mixer 0.1.0 (git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=acb90e9bf36e6e035ac6bbe51efa0a8825b5b6be)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1473,7 +1474,7 @@ dependencies = [
|
|||
"cert_storage 0.0.1",
|
||||
"chardetng_c 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cose-c 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cubeb-coreaudio 0.1.0 (git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=868d847c2e95096b6eec629dfed77ef363363ecb)",
|
||||
"cubeb-coreaudio 0.1.0 (git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=acb90e9bf36e6e035ac6bbe51efa0a8825b5b6be)",
|
||||
"cubeb-pulse 0.3.0 (git+https://github.com/djg/cubeb-pulse-rs?rev=8069f8f4189982e0b38fa6dc8993dd4fab41f728)",
|
||||
"cubeb-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"encoding_glue 0.1.0",
|
||||
|
@ -2241,6 +2242,14 @@ dependencies = [
|
|||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mixer"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=acb90e9bf36e6e035ac6bbe51efa0a8825b5b6be#acb90e9bf36e6e035ac6bbe51efa0a8825b5b6be"
|
||||
dependencies = [
|
||||
"bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "moz_cbor"
|
||||
version = "0.1.1"
|
||||
|
@ -4696,7 +4705,7 @@ dependencies = [
|
|||
"checksum core-graphics 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)" = "56790968ab1c8a1202a102e6de05fc6e1ec87da99e4e93e9a7d13efbfc1e95a9"
|
||||
"checksum core-text 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f3f46450d6f2397261af420b4ccce23807add2e45fa206410a03d66fb7f050ae"
|
||||
"checksum coreaudio-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e8f5954c1c7ccb55340443e8b29fca24013545a5e7d72c1ca7db4fc02b982ce"
|
||||
"checksum coreaudio-sys-utils 0.1.0 (git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=868d847c2e95096b6eec629dfed77ef363363ecb)" = "<none>"
|
||||
"checksum coreaudio-sys-utils 0.1.0 (git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=acb90e9bf36e6e035ac6bbe51efa0a8825b5b6be)" = "<none>"
|
||||
"checksum cose 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "72fa26cb151d3ae4b70f63d67d0fed57ce04220feafafbae7f503bef7aae590d"
|
||||
"checksum cose-c 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "49726015ab0ca765144fcca61e4a7a543a16b795a777fa53f554da2fffff9a94"
|
||||
"checksum cranelift-bforest 0.51.0 (git+https://github.com/bytecodealliance/cranelift?rev=ec787eb281bb2e18e191508c17abe694e91f0677)" = "<none>"
|
||||
|
@ -4720,7 +4729,7 @@ dependencies = [
|
|||
"checksum cubeb 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3cbcdfde9ea319160af6eff068ffaa96aad3532e1b5c0ebc134614cfacacae24"
|
||||
"checksum cubeb-backend 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5a1e7add4e7642a8aebb24172922318482bed52389a12cb339f728bbd4c4ed9c"
|
||||
"checksum cubeb-core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfd9b2ea1cb6afed9419b0d18fc4093df552ccb2300eb57793629f8cd370b4c8"
|
||||
"checksum cubeb-coreaudio 0.1.0 (git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=868d847c2e95096b6eec629dfed77ef363363ecb)" = "<none>"
|
||||
"checksum cubeb-coreaudio 0.1.0 (git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=acb90e9bf36e6e035ac6bbe51efa0a8825b5b6be)" = "<none>"
|
||||
"checksum cubeb-pulse 0.3.0 (git+https://github.com/djg/cubeb-pulse-rs?rev=8069f8f4189982e0b38fa6dc8993dd4fab41f728)" = "<none>"
|
||||
"checksum cubeb-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "309c5839c5fa03c08363bd308566cbe4654b25a9984342d7546a33d55b80a3d6"
|
||||
"checksum d3d12 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc7ed48e89905e5e146bcc1951cc3facb9e44aea9adf5dc01078cda1bd24b662"
|
||||
|
@ -4834,6 +4843,7 @@ dependencies = [
|
|||
"checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125"
|
||||
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
|
||||
"checksum miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226"
|
||||
"checksum mixer 0.1.0 (git+https://github.com/ChunMinChang/cubeb-coreaudio-rs?rev=acb90e9bf36e6e035ac6bbe51efa0a8825b5b6be)" = "<none>"
|
||||
"checksum moz_cbor 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20c82a57087fd5990d7122dbff1607c3b20c3d2958e9d9ad9765aab415e2c91c"
|
||||
"checksum mp4parse_fallible 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6626c2aef76eb8f984eef02e475883d3fe9112e114720446c5810fc5f045cd30"
|
||||
"checksum msdos_time 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aad9dfe950c057b1bfe9c1f2aa51583a8468ef2a5baba2ebbe06d775efeb7729"
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"files":{".editorconfig":"4e53b182bcc78b83d7e1b5c03efa14d22d4955c4ed2514d1ba4e99c1eb1a50ba",".travis.yml":"bea421508af5f4d00b941866dae0ae7d94a51b9276688a7f626686e8ed8fbbf3","Cargo.toml":"d1b1a03695cf8c92daa58cccb391a03b1d0b82a70505424c25fa40aba19411af","LICENSE":"6e6f56aff5bbf3cbc60747e152fb1a719bd0716aaf6d711c554f57d92e96297c","README.md":"72d8a890d6bda3cdba393432e5ae2018a385980785ebb2b96e9c3f82a48a1b59","run_tests.sh":"c764a89fe2a6b7ccdeb01895d29b2017c5b76f468aff456050babd66c1472687","src/backend/aggregate_device.rs":"7cd732f3e1e71876753515b26ee69a315414e58869216e99ff95c6828408c4db","src/backend/auto_array.rs":"5f35545baba2b005e13a2225bd1cbdd94ffc2097554d61479929bfc5442a6dd6","src/backend/auto_release.rs":"050fdcee74cf46b9a8a85a877e166d72a853d33220f59cf734cbb6ea09daa441","src/backend/device_property.rs":"1b066b48ed09026a9286b1b8f40e2720854c3410b0f02c795a580792fda34ea9","src/backend/mixer.rs":"74dcac459493e2f919b61ed3bebe500027e422eb06b1ecd30b73a47079c61f7c","src/backend/mod.rs":"67b445d216cad0658c3844cd608ddf1bfc8ab1559ffbee8edf5c340e08a9ce77","src/backend/resampler.rs":"fd1281d28a4db1659d2f75e43b8457651745e1b6eb5a53a77f04d752135f6dc7","src/backend/tests/aggregate_device.rs":"107f5c637844cd5ae43d2b42cec4ef3369bb702751586078c0a9d50f039161cd","src/backend/tests/api.rs":"d76c1574179085e0c1342614cd674e5aa211dd456f3725aeb294def6b32750fd","src/backend/tests/backlog.rs":"3b189a7e036543c467cc242af0ed3332721179ee2b1c8847a6db563546f1ac52","src/backend/tests/device_change.rs":"bebda3dbbcd432d7c545dbe27c388773f61f68a1c8b947f61deb358682dbe53b","src/backend/tests/device_property.rs":"b1a9ae79aa5b9a3f180040d0ef0954b134680d586882d2062c5e017b555bff57","src/backend/tests/interfaces.rs":"01fc2d54ddb50f014a44f9c137f078645738bcc81e48140a3e7ae68e18a61b6b","src/backend/tests/manual.rs":"c0b8888e1f7fc09913862300023452753384e038b7433e3975b772988925f52c","src/backend/tests/mod.rs":"8dba770023d7f9c4228f0e11915347f0e07da5fd818e3ee4478c4b197af9aa2a","src/backend/tests/parallel.rs":"f9e1883660d6146b6e5075806561f5f689810e25c5e7764dfd28c9b939821a49","src/backend/tests/tone.rs":"16150438317ce501986734167b5fb97bfec567228acbcd8f3b4c4484c22f29e0","src/backend/tests/utils.rs":"981e4669902231d10f8c3d5c39d82358acc8159f026b8be27f4ab76ef19fcda3","src/backend/utils.rs":"ee77bc266d672d3d9e23eb3290c1f897687394c6e459338804a17433380a6fd2","src/capi.rs":"61f8f0c4373adaefba1eb6e7084687e83a10136db96438bc35884327668e411f","src/lib.rs":"1ff4b738ed194061fca4ff745f847dea4de4e7a4fa1f898e7b4ad5e70c62386d","todo.md":"a66296c220cad24d08ee780308007a702f7e421edf0bb60464c3ce8feeda1882"},"package":null}
|
||||
{"files":{".editorconfig":"4e53b182bcc78b83d7e1b5c03efa14d22d4955c4ed2514d1ba4e99c1eb1a50ba",".travis.yml":"bea421508af5f4d00b941866dae0ae7d94a51b9276688a7f626686e8ed8fbbf3","Cargo.toml":"d0fa3332c5101548d5d52afe3ed57488c6d221189c20f9316fc5930f41a50281","LICENSE":"6e6f56aff5bbf3cbc60747e152fb1a719bd0716aaf6d711c554f57d92e96297c","README.md":"72d8a890d6bda3cdba393432e5ae2018a385980785ebb2b96e9c3f82a48a1b59","run_tests.sh":"c764a89fe2a6b7ccdeb01895d29b2017c5b76f468aff456050babd66c1472687","src/backend/aggregate_device.rs":"7cd732f3e1e71876753515b26ee69a315414e58869216e99ff95c6828408c4db","src/backend/auto_array.rs":"5f35545baba2b005e13a2225bd1cbdd94ffc2097554d61479929bfc5442a6dd6","src/backend/auto_release.rs":"050fdcee74cf46b9a8a85a877e166d72a853d33220f59cf734cbb6ea09daa441","src/backend/device_property.rs":"1b066b48ed09026a9286b1b8f40e2720854c3410b0f02c795a580792fda34ea9","src/backend/mixer.rs":"538b53b66695303b9c330c452cad346c0cc27d5064a3fbc63ad8fa29afb4b70a","src/backend/mod.rs":"c8904e1498383f56a86b65aa150325a8c6eb44a25fef854b40efd34b8fa18dff","src/backend/resampler.rs":"fd1281d28a4db1659d2f75e43b8457651745e1b6eb5a53a77f04d752135f6dc7","src/backend/tests/aggregate_device.rs":"107f5c637844cd5ae43d2b42cec4ef3369bb702751586078c0a9d50f039161cd","src/backend/tests/api.rs":"0c78b255921a5c13310132134bb194fc6d9c6fbe90fd05624b82b552711c5a8a","src/backend/tests/backlog.rs":"3b189a7e036543c467cc242af0ed3332721179ee2b1c8847a6db563546f1ac52","src/backend/tests/device_change.rs":"8533df1357c154922200a706bbb21d20007fd3a343d9c47697314f91c1c6a281","src/backend/tests/device_property.rs":"b1a9ae79aa5b9a3f180040d0ef0954b134680d586882d2062c5e017b555bff57","src/backend/tests/interfaces.rs":"1db1aa665a7f85968ca084ce1be725d182b00be5d7fa28d8a3cf178df145f43e","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":"b74f10216bd02d5336f46db371c9c8e1a648deead84068d9397eca03986aae1e","src/backend/utils.rs":"ee77bc266d672d3d9e23eb3290c1f897687394c6e459338804a17433380a6fd2","src/capi.rs":"61f8f0c4373adaefba1eb6e7084687e83a10136db96438bc35884327668e411f","src/lib.rs":"1ff4b738ed194061fca4ff745f847dea4de4e7a4fa1f898e7b4ad5e70c62386d","todo.md":"29545b4d9c516396f82bd392797e2713d4602036eaba0f151b384af764f8515f"},"package":null}
|
|
@ -15,3 +15,4 @@ cubeb-backend = "0.6"
|
|||
libc = "0.2"
|
||||
lazy_static = "1.2"
|
||||
mach = "0.3"
|
||||
mixer = { path = "mixer" }
|
||||
|
|
|
@ -1,18 +1,159 @@
|
|||
use super::auto_release::*;
|
||||
use super::utils::cubeb_sample_size;
|
||||
use cubeb_backend::{ffi, ChannelLayout, SampleFormat};
|
||||
use cubeb_backend::{ChannelLayout, SampleFormat};
|
||||
use std::mem;
|
||||
use std::os::raw::{c_int, c_void};
|
||||
use std::ptr;
|
||||
|
||||
extern crate mixer;
|
||||
pub use self::mixer::Channel;
|
||||
|
||||
const CHANNEL_OERDER: [mixer::Channel; mixer::Channel::count()] = [
|
||||
mixer::Channel::FrontLeft,
|
||||
mixer::Channel::FrontRight,
|
||||
mixer::Channel::FrontCenter,
|
||||
mixer::Channel::LowFrequency,
|
||||
mixer::Channel::BackLeft,
|
||||
mixer::Channel::BackRight,
|
||||
mixer::Channel::FrontLeftOfCenter,
|
||||
mixer::Channel::FrontRightOfCenter,
|
||||
mixer::Channel::BackCenter,
|
||||
mixer::Channel::SideLeft,
|
||||
mixer::Channel::SideRight,
|
||||
mixer::Channel::TopCenter,
|
||||
mixer::Channel::TopFrontLeft,
|
||||
mixer::Channel::TopFrontCenter,
|
||||
mixer::Channel::TopFrontRight,
|
||||
mixer::Channel::TopBackLeft,
|
||||
mixer::Channel::TopBackCenter,
|
||||
mixer::Channel::TopBackRight,
|
||||
mixer::Channel::Silence,
|
||||
];
|
||||
|
||||
pub fn get_channel_order(channel_layout: ChannelLayout) -> Vec<mixer::Channel> {
|
||||
let mut map = channel_layout.bits();
|
||||
let mut order = Vec::new();
|
||||
let mut channel_index: usize = 0;
|
||||
while map != 0 {
|
||||
if map & 1 == 1 {
|
||||
order.push(CHANNEL_OERDER[channel_index]);
|
||||
}
|
||||
map >>= 1;
|
||||
channel_index += 1;
|
||||
}
|
||||
order
|
||||
}
|
||||
|
||||
fn get_default_channel_order(channel_count: usize) -> Vec<mixer::Channel> {
|
||||
assert_ne!(channel_count, 0);
|
||||
let mut channels = Vec::with_capacity(channel_count);
|
||||
for i in 0..channel_count {
|
||||
channels.push(if i < CHANNEL_OERDER.len() {
|
||||
CHANNEL_OERDER[i]
|
||||
} else {
|
||||
mixer::Channel::Silence
|
||||
});
|
||||
}
|
||||
channels
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum MixerType {
|
||||
IntegerMixer(mixer::Mixer<i16>),
|
||||
FloatMixer(mixer::Mixer<f32>),
|
||||
}
|
||||
|
||||
impl MixerType {
|
||||
fn new(
|
||||
format: SampleFormat,
|
||||
input_channels: Vec<mixer::Channel>,
|
||||
output_channels: Vec<mixer::Channel>,
|
||||
) -> Self {
|
||||
match format {
|
||||
SampleFormat::S16LE | SampleFormat::S16BE | SampleFormat::S16NE => {
|
||||
cubeb_log!("Create an integer type(i16) mixer");
|
||||
Self::IntegerMixer(mixer::Mixer::<i16>::new(input_channels, output_channels))
|
||||
}
|
||||
SampleFormat::Float32LE | SampleFormat::Float32BE | SampleFormat::Float32NE => {
|
||||
cubeb_log!("Create an floating type(f32) mixer");
|
||||
Self::FloatMixer(mixer::Mixer::<f32>::new(input_channels, output_channels))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_size(&self) -> usize {
|
||||
match self {
|
||||
MixerType::IntegerMixer(_) => mem::size_of::<i16>(),
|
||||
MixerType::FloatMixer(_) => mem::size_of::<f32>(),
|
||||
}
|
||||
}
|
||||
|
||||
fn input_channels(&self) -> &[Channel] {
|
||||
match self {
|
||||
MixerType::IntegerMixer(m) => m.input_channels(),
|
||||
MixerType::FloatMixer(m) => m.input_channels(),
|
||||
}
|
||||
}
|
||||
|
||||
fn output_channels(&self) -> &[Channel] {
|
||||
match self {
|
||||
MixerType::IntegerMixer(m) => m.output_channels(),
|
||||
MixerType::FloatMixer(m) => m.output_channels(),
|
||||
}
|
||||
}
|
||||
|
||||
fn mix(
|
||||
&self,
|
||||
input_buffer_ptr: *const u8,
|
||||
input_buffer_size: usize,
|
||||
output_buffer_ptr: *mut u8,
|
||||
output_buffer_size: usize,
|
||||
frames: usize,
|
||||
) {
|
||||
use std::slice;
|
||||
|
||||
// Check input buffer size.
|
||||
let size_needed = frames * self.input_channels().len() * self.sample_size();
|
||||
assert!(input_buffer_size >= size_needed);
|
||||
// Check output buffer size.
|
||||
let size_needed = frames * self.output_channels().len() * self.sample_size();
|
||||
assert!(output_buffer_size >= size_needed);
|
||||
|
||||
match self {
|
||||
MixerType::IntegerMixer(m) => {
|
||||
let in_buf_ptr = input_buffer_ptr as *const i16;
|
||||
let out_buf_ptr = output_buffer_ptr as *mut i16;
|
||||
let input_buffer = unsafe {
|
||||
slice::from_raw_parts(in_buf_ptr, frames * self.input_channels().len())
|
||||
};
|
||||
let output_buffer = unsafe {
|
||||
slice::from_raw_parts_mut(out_buf_ptr, frames * self.output_channels().len())
|
||||
};
|
||||
let mut in_buf = input_buffer.chunks(self.input_channels().len());
|
||||
let mut out_buf = output_buffer.chunks_mut(self.output_channels().len());
|
||||
for _ in 0..frames {
|
||||
m.mix(in_buf.next().unwrap(), out_buf.next().unwrap());
|
||||
}
|
||||
}
|
||||
MixerType::FloatMixer(m) => {
|
||||
let in_buf_ptr = input_buffer_ptr as *const f32;
|
||||
let out_buf_ptr = output_buffer_ptr as *mut f32;
|
||||
let input_buffer = unsafe {
|
||||
slice::from_raw_parts(in_buf_ptr, frames * self.input_channels().len())
|
||||
};
|
||||
let output_buffer = unsafe {
|
||||
slice::from_raw_parts_mut(out_buf_ptr, frames * self.output_channels().len())
|
||||
};
|
||||
let mut in_buf = input_buffer.chunks(self.input_channels().len());
|
||||
let mut out_buf = output_buffer.chunks_mut(self.output_channels().len());
|
||||
for _ in 0..frames {
|
||||
m.mix(in_buf.next().unwrap(), out_buf.next().unwrap());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mixer {
|
||||
mixer: AutoRelease<ffi::cubeb_mixer>,
|
||||
format: SampleFormat,
|
||||
in_channels: u32,
|
||||
in_layout: ChannelLayout,
|
||||
out_channels: u32,
|
||||
out_layout: ChannelLayout,
|
||||
mixer: MixerType,
|
||||
// Only accessed from callback thread.
|
||||
buffer: Vec<u8>,
|
||||
}
|
||||
|
@ -20,34 +161,54 @@ pub struct Mixer {
|
|||
impl Mixer {
|
||||
pub fn new(
|
||||
format: SampleFormat,
|
||||
in_channels: u32,
|
||||
in_layout: ChannelLayout,
|
||||
out_channels: u32,
|
||||
out_layout: ChannelLayout,
|
||||
in_channel_count: usize,
|
||||
input_layout: ChannelLayout,
|
||||
out_channel_count: usize,
|
||||
mut output_channels: Vec<mixer::Channel>,
|
||||
) -> Self {
|
||||
let raw_mixer = unsafe {
|
||||
ffi::cubeb_mixer_create(
|
||||
format.into(),
|
||||
in_channels,
|
||||
in_layout.into(),
|
||||
out_channels,
|
||||
out_layout.into(),
|
||||
)
|
||||
cubeb_log!(
|
||||
"Create a mixer with input channel count: {}, input layout: {:?}, \
|
||||
out channel count: {}, output channels: {:?}",
|
||||
in_channel_count,
|
||||
input_layout,
|
||||
out_channel_count,
|
||||
output_channels
|
||||
);
|
||||
|
||||
let input_channels = if in_channel_count as u32 != input_layout.bits().count_ones() {
|
||||
cubeb_log!("Mismatch between input channels and layout. Apply default layout instead");
|
||||
get_default_channel_order(in_channel_count)
|
||||
} else {
|
||||
get_channel_order(input_layout)
|
||||
};
|
||||
assert!(!raw_mixer.is_null(), "Failed to create mixer");
|
||||
|
||||
// When having one or two channel, force mono or stereo. Some devices (namely,
|
||||
// Bose QC35, mark 1 and 2), expose a single channel mapped to the right for
|
||||
// some reason.
|
||||
// TODO: Only apply this setting when device is Bose QC35 (by device_property.rs).
|
||||
if out_channel_count == 1 {
|
||||
output_channels = vec![mixer::Channel::FrontCenter];
|
||||
} else if out_channel_count == 2 {
|
||||
output_channels = vec![mixer::Channel::FrontLeft, mixer::Channel::FrontRight];
|
||||
}
|
||||
|
||||
let all_silence = vec![mixer::Channel::Silence; out_channel_count];
|
||||
if output_channels.len() == 0
|
||||
|| out_channel_count != output_channels.len()
|
||||
|| all_silence == output_channels
|
||||
{
|
||||
cubeb_log!("Mismatch between output channels and layout. Apply default layout instead");
|
||||
output_channels = get_default_channel_order(out_channel_count);
|
||||
}
|
||||
|
||||
Self {
|
||||
mixer: AutoRelease::new(raw_mixer, ffi::cubeb_mixer_destroy),
|
||||
format,
|
||||
in_channels,
|
||||
in_layout,
|
||||
out_channels,
|
||||
out_layout,
|
||||
mixer: MixerType::new(format, input_channels, output_channels),
|
||||
buffer: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_buffer_size(&mut self, frames: usize) -> bool {
|
||||
let size_needed = frames * self.in_channels as usize * cubeb_sample_size(self.format);
|
||||
let size_needed = frames * self.mixer.input_channels().len() * self.mixer.sample_size();
|
||||
let elements_needed = size_needed / mem::size_of::<u8>();
|
||||
if self.buffer.len() < elements_needed {
|
||||
self.buffer.resize(elements_needed, 0);
|
||||
|
@ -61,58 +222,202 @@ impl Mixer {
|
|||
self.buffer.as_mut_ptr()
|
||||
}
|
||||
|
||||
// `update_buffer_size` must be called before this.
|
||||
pub fn mix(&self, frames: usize, dest_buffer: *mut c_void, dest_buffer_size: usize) -> c_int {
|
||||
let (src_buffer_ptr, src_buffer_size) = self.get_buffer_info();
|
||||
self.mixer.mix(
|
||||
src_buffer_ptr,
|
||||
src_buffer_size,
|
||||
dest_buffer as *mut u8,
|
||||
dest_buffer_size,
|
||||
frames,
|
||||
);
|
||||
0
|
||||
}
|
||||
|
||||
fn get_buffer_info(&self) -> (*const u8, usize) {
|
||||
(
|
||||
self.buffer.as_ptr(),
|
||||
self.buffer.len() * mem::size_of::<u8>(),
|
||||
)
|
||||
}
|
||||
|
||||
// `update_buffer_size` must be called before this.
|
||||
pub fn mix(
|
||||
&mut self,
|
||||
frames: usize,
|
||||
dest_buffer: *mut c_void,
|
||||
dest_buffer_size: usize,
|
||||
) -> c_int {
|
||||
let size_needed = frames * self.in_channels as usize * cubeb_sample_size(self.format);
|
||||
let (src_buffer_ptr, src_buffer_size) = self.get_buffer_info();
|
||||
assert!(src_buffer_size >= size_needed);
|
||||
unsafe {
|
||||
ffi::cubeb_mixer_mix(
|
||||
self.mixer.as_mut(),
|
||||
frames,
|
||||
src_buffer_ptr as *const c_void,
|
||||
src_buffer_size,
|
||||
dest_buffer,
|
||||
dest_buffer_size,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn destroy(&mut self) {
|
||||
if !self.mixer.as_ptr().is_null() {
|
||||
self.mixer.reset(ptr::null_mut());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Mixer {
|
||||
fn drop(&mut self) {
|
||||
self.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Mixer {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mixer: AutoRelease::new(ptr::null_mut(), ffi::cubeb_mixer_destroy),
|
||||
format: SampleFormat::Float32NE,
|
||||
in_channels: 0,
|
||||
in_layout: ChannelLayout::UNDEFINED,
|
||||
out_channels: 0,
|
||||
out_layout: ChannelLayout::UNDEFINED,
|
||||
buffer: Vec::default(),
|
||||
}
|
||||
}
|
||||
// This test gives a clear channel order of the ChannelLayout passed from cubeb interface.
|
||||
#[test]
|
||||
fn test_get_channel_order() {
|
||||
assert_eq!(
|
||||
get_channel_order(ChannelLayout::MONO),
|
||||
[Channel::FrontCenter]
|
||||
);
|
||||
assert_eq!(
|
||||
get_channel_order(ChannelLayout::MONO_LFE),
|
||||
[Channel::FrontCenter, Channel::LowFrequency]
|
||||
);
|
||||
assert_eq!(
|
||||
get_channel_order(ChannelLayout::STEREO),
|
||||
[Channel::FrontLeft, Channel::FrontRight]
|
||||
);
|
||||
assert_eq!(
|
||||
get_channel_order(ChannelLayout::STEREO_LFE),
|
||||
[
|
||||
Channel::FrontLeft,
|
||||
Channel::FrontRight,
|
||||
Channel::LowFrequency
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
get_channel_order(ChannelLayout::_3F),
|
||||
[
|
||||
Channel::FrontLeft,
|
||||
Channel::FrontRight,
|
||||
Channel::FrontCenter
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
get_channel_order(ChannelLayout::_3F_LFE),
|
||||
[
|
||||
Channel::FrontLeft,
|
||||
Channel::FrontRight,
|
||||
Channel::FrontCenter,
|
||||
Channel::LowFrequency
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
get_channel_order(ChannelLayout::_2F1),
|
||||
[Channel::FrontLeft, Channel::FrontRight, Channel::BackCenter]
|
||||
);
|
||||
assert_eq!(
|
||||
get_channel_order(ChannelLayout::_2F1_LFE),
|
||||
[
|
||||
Channel::FrontLeft,
|
||||
Channel::FrontRight,
|
||||
Channel::LowFrequency,
|
||||
Channel::BackCenter
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
get_channel_order(ChannelLayout::_3F1),
|
||||
[
|
||||
Channel::FrontLeft,
|
||||
Channel::FrontRight,
|
||||
Channel::FrontCenter,
|
||||
Channel::BackCenter
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
get_channel_order(ChannelLayout::_3F1_LFE),
|
||||
[
|
||||
Channel::FrontLeft,
|
||||
Channel::FrontRight,
|
||||
Channel::FrontCenter,
|
||||
Channel::LowFrequency,
|
||||
Channel::BackCenter
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
get_channel_order(ChannelLayout::_2F2),
|
||||
[
|
||||
Channel::FrontLeft,
|
||||
Channel::FrontRight,
|
||||
Channel::SideLeft,
|
||||
Channel::SideRight
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
get_channel_order(ChannelLayout::_2F2_LFE),
|
||||
[
|
||||
Channel::FrontLeft,
|
||||
Channel::FrontRight,
|
||||
Channel::LowFrequency,
|
||||
Channel::SideLeft,
|
||||
Channel::SideRight
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
get_channel_order(ChannelLayout::QUAD),
|
||||
[
|
||||
Channel::FrontLeft,
|
||||
Channel::FrontRight,
|
||||
Channel::BackLeft,
|
||||
Channel::BackRight
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
get_channel_order(ChannelLayout::QUAD_LFE),
|
||||
[
|
||||
Channel::FrontLeft,
|
||||
Channel::FrontRight,
|
||||
Channel::LowFrequency,
|
||||
Channel::BackLeft,
|
||||
Channel::BackRight
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
get_channel_order(ChannelLayout::_3F2),
|
||||
[
|
||||
Channel::FrontLeft,
|
||||
Channel::FrontRight,
|
||||
Channel::FrontCenter,
|
||||
Channel::SideLeft,
|
||||
Channel::SideRight
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
get_channel_order(ChannelLayout::_3F2_LFE),
|
||||
[
|
||||
Channel::FrontLeft,
|
||||
Channel::FrontRight,
|
||||
Channel::FrontCenter,
|
||||
Channel::LowFrequency,
|
||||
Channel::SideLeft,
|
||||
Channel::SideRight
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
get_channel_order(ChannelLayout::_3F2_BACK),
|
||||
[
|
||||
Channel::FrontLeft,
|
||||
Channel::FrontRight,
|
||||
Channel::FrontCenter,
|
||||
Channel::BackLeft,
|
||||
Channel::BackRight
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
get_channel_order(ChannelLayout::_3F2_LFE_BACK),
|
||||
[
|
||||
Channel::FrontLeft,
|
||||
Channel::FrontRight,
|
||||
Channel::FrontCenter,
|
||||
Channel::LowFrequency,
|
||||
Channel::BackLeft,
|
||||
Channel::BackRight
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
get_channel_order(ChannelLayout::_3F3R_LFE),
|
||||
[
|
||||
Channel::FrontLeft,
|
||||
Channel::FrontRight,
|
||||
Channel::FrontCenter,
|
||||
Channel::LowFrequency,
|
||||
Channel::BackCenter,
|
||||
Channel::SideLeft,
|
||||
Channel::SideRight
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
get_channel_order(ChannelLayout::_3F4_LFE),
|
||||
[
|
||||
Channel::FrontLeft,
|
||||
Channel::FrontRight,
|
||||
Channel::FrontCenter,
|
||||
Channel::LowFrequency,
|
||||
Channel::BackLeft,
|
||||
Channel::BackRight,
|
||||
Channel::SideLeft,
|
||||
Channel::SideRight
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -32,9 +32,8 @@ use self::resampler::*;
|
|||
use self::utils::*;
|
||||
use atomic;
|
||||
use cubeb_backend::{
|
||||
ffi, ChannelLayout, Context, ContextOps, DeviceCollectionRef, DeviceId, DeviceRef, DeviceType,
|
||||
Error, Ops, Result, SampleFormat, State, Stream, StreamOps, StreamParams, StreamParamsRef,
|
||||
StreamPrefs,
|
||||
ffi, Context, ContextOps, DeviceCollectionRef, DeviceId, DeviceRef, DeviceType, Error, Ops,
|
||||
Result, SampleFormat, State, Stream, StreamOps, StreamParams, StreamParamsRef, StreamPrefs,
|
||||
};
|
||||
use mach::mach_time::{mach_absolute_time, mach_timebase_info};
|
||||
use std::cmp;
|
||||
|
@ -137,64 +136,31 @@ impl device_property_listener {
|
|||
#[derive(Debug, PartialEq)]
|
||||
struct CAChannelLabel(AudioChannelLabel);
|
||||
|
||||
impl CAChannelLabel {
|
||||
fn get_raw_label(&self) -> AudioChannelLabel {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChannelLayout> for CAChannelLabel {
|
||||
fn from(layout: ChannelLayout) -> Self {
|
||||
// Make sure the layout is a channel (only one bit set to 1)
|
||||
assert_eq!(layout.bits() & (layout.bits() - 1), 0);
|
||||
let channel = match layout {
|
||||
ChannelLayout::FRONT_LEFT => kAudioChannelLabel_Left,
|
||||
ChannelLayout::FRONT_RIGHT => kAudioChannelLabel_Right,
|
||||
ChannelLayout::FRONT_CENTER => kAudioChannelLabel_Center,
|
||||
ChannelLayout::LOW_FREQUENCY => kAudioChannelLabel_LFEScreen,
|
||||
ChannelLayout::BACK_LEFT => kAudioChannelLabel_LeftSurround,
|
||||
ChannelLayout::BACK_RIGHT => kAudioChannelLabel_RightSurround,
|
||||
ChannelLayout::FRONT_LEFT_OF_CENTER => kAudioChannelLabel_LeftCenter,
|
||||
ChannelLayout::FRONT_RIGHT_OF_CENTER => kAudioChannelLabel_RightCenter,
|
||||
ChannelLayout::BACK_CENTER => kAudioChannelLabel_CenterSurround,
|
||||
ChannelLayout::SIDE_LEFT => kAudioChannelLabel_LeftSurroundDirect,
|
||||
ChannelLayout::SIDE_RIGHT => kAudioChannelLabel_RightSurroundDirect,
|
||||
ChannelLayout::TOP_CENTER => kAudioChannelLabel_TopCenterSurround,
|
||||
ChannelLayout::TOP_FRONT_LEFT => kAudioChannelLabel_VerticalHeightLeft,
|
||||
ChannelLayout::TOP_FRONT_CENTER => kAudioChannelLabel_VerticalHeightCenter,
|
||||
ChannelLayout::TOP_FRONT_RIGHT => kAudioChannelLabel_VerticalHeightRight,
|
||||
ChannelLayout::TOP_BACK_LEFT => kAudioChannelLabel_TopBackLeft,
|
||||
ChannelLayout::TOP_BACK_CENTER => kAudioChannelLabel_TopBackCenter,
|
||||
ChannelLayout::TOP_BACK_RIGHT => kAudioChannelLabel_TopBackRight,
|
||||
_ => kAudioChannelLabel_Unknown,
|
||||
};
|
||||
Self(channel)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<ChannelLayout> for CAChannelLabel {
|
||||
fn into(self) -> ChannelLayout {
|
||||
impl Into<mixer::Channel> for CAChannelLabel {
|
||||
fn into(self) -> mixer::Channel {
|
||||
use self::coreaudio_sys_utils::sys;
|
||||
match self.0 {
|
||||
sys::kAudioChannelLabel_Left => ChannelLayout::FRONT_LEFT,
|
||||
sys::kAudioChannelLabel_Right => ChannelLayout::FRONT_RIGHT,
|
||||
sys::kAudioChannelLabel_Center => ChannelLayout::FRONT_CENTER,
|
||||
sys::kAudioChannelLabel_LFEScreen => ChannelLayout::LOW_FREQUENCY,
|
||||
sys::kAudioChannelLabel_LeftSurround => ChannelLayout::BACK_LEFT,
|
||||
sys::kAudioChannelLabel_RightSurround => ChannelLayout::BACK_RIGHT,
|
||||
sys::kAudioChannelLabel_LeftCenter => ChannelLayout::FRONT_LEFT_OF_CENTER,
|
||||
sys::kAudioChannelLabel_RightCenter => ChannelLayout::FRONT_RIGHT_OF_CENTER,
|
||||
sys::kAudioChannelLabel_CenterSurround => ChannelLayout::BACK_CENTER,
|
||||
sys::kAudioChannelLabel_LeftSurroundDirect => ChannelLayout::SIDE_LEFT,
|
||||
sys::kAudioChannelLabel_RightSurroundDirect => ChannelLayout::SIDE_RIGHT,
|
||||
sys::kAudioChannelLabel_TopCenterSurround => ChannelLayout::TOP_CENTER,
|
||||
sys::kAudioChannelLabel_VerticalHeightLeft => ChannelLayout::TOP_FRONT_LEFT,
|
||||
sys::kAudioChannelLabel_VerticalHeightCenter => ChannelLayout::TOP_FRONT_CENTER,
|
||||
sys::kAudioChannelLabel_VerticalHeightRight => ChannelLayout::TOP_FRONT_RIGHT,
|
||||
sys::kAudioChannelLabel_TopBackLeft => ChannelLayout::TOP_BACK_LEFT,
|
||||
sys::kAudioChannelLabel_TopBackCenter => ChannelLayout::TOP_BACK_CENTER,
|
||||
sys::kAudioChannelLabel_TopBackRight => ChannelLayout::TOP_BACK_RIGHT,
|
||||
_ => ChannelLayout::UNDEFINED,
|
||||
sys::kAudioChannelLabel_Left => mixer::Channel::FrontLeft,
|
||||
sys::kAudioChannelLabel_Right => mixer::Channel::FrontRight,
|
||||
sys::kAudioChannelLabel_Center | sys::kAudioChannelLabel_Mono => {
|
||||
mixer::Channel::FrontCenter
|
||||
}
|
||||
sys::kAudioChannelLabel_LFEScreen => mixer::Channel::LowFrequency,
|
||||
sys::kAudioChannelLabel_LeftSurround => mixer::Channel::BackLeft,
|
||||
sys::kAudioChannelLabel_RightSurround => mixer::Channel::BackRight,
|
||||
sys::kAudioChannelLabel_LeftCenter => mixer::Channel::FrontLeftOfCenter,
|
||||
sys::kAudioChannelLabel_RightCenter => mixer::Channel::FrontRightOfCenter,
|
||||
sys::kAudioChannelLabel_CenterSurround => mixer::Channel::BackCenter,
|
||||
sys::kAudioChannelLabel_LeftSurroundDirect => mixer::Channel::SideLeft,
|
||||
sys::kAudioChannelLabel_RightSurroundDirect => mixer::Channel::SideRight,
|
||||
sys::kAudioChannelLabel_TopCenterSurround => mixer::Channel::TopCenter,
|
||||
sys::kAudioChannelLabel_VerticalHeightLeft => mixer::Channel::TopFrontLeft,
|
||||
sys::kAudioChannelLabel_VerticalHeightCenter => mixer::Channel::TopFrontCenter,
|
||||
sys::kAudioChannelLabel_VerticalHeightRight => mixer::Channel::TopFrontRight,
|
||||
sys::kAudioChannelLabel_TopBackLeft => mixer::Channel::TopBackLeft,
|
||||
sys::kAudioChannelLabel_TopBackCenter => mixer::Channel::TopBackCenter,
|
||||
sys::kAudioChannelLabel_TopBackRight => mixer::Channel::TopBackRight,
|
||||
_ => mixer::Channel::Silence,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -922,23 +888,14 @@ fn audiounit_get_default_device_id(devtype: DeviceType) -> AudioObjectID {
|
|||
devid
|
||||
}
|
||||
|
||||
fn audiounit_convert_channel_layout(layout: &AudioChannelLayout) -> ChannelLayout {
|
||||
// When having one or two channel, force mono or stereo. Some devices (namely,
|
||||
// Bose QC35, mark 1 and 2), expose a single channel mapped to the right for
|
||||
// some reason.
|
||||
if layout.mNumberChannelDescriptions == 1 {
|
||||
return ChannelLayout::MONO;
|
||||
} else if layout.mNumberChannelDescriptions == 2 {
|
||||
return ChannelLayout::STEREO;
|
||||
}
|
||||
|
||||
fn audiounit_convert_channel_layout(layout: &AudioChannelLayout) -> Vec<mixer::Channel> {
|
||||
if layout.mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions {
|
||||
// kAudioChannelLayoutTag_UseChannelBitmap
|
||||
// kAudioChannelLayoutTag_Mono
|
||||
// kAudioChannelLayoutTag_Stereo
|
||||
// ....
|
||||
cubeb_log!("Only handle UseChannelDescriptions for now.\n");
|
||||
return ChannelLayout::UNDEFINED;
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let channel_descriptions = unsafe {
|
||||
|
@ -948,20 +905,16 @@ fn audiounit_convert_channel_layout(layout: &AudioChannelLayout) -> ChannelLayou
|
|||
)
|
||||
};
|
||||
|
||||
let mut cl = ChannelLayout::UNDEFINED;
|
||||
let mut channels = Vec::with_capacity(layout.mNumberChannelDescriptions as usize);
|
||||
for description in channel_descriptions {
|
||||
let label = CAChannelLabel(description.mChannelLabel);
|
||||
let channel: ChannelLayout = label.into();
|
||||
if channel == ChannelLayout::UNDEFINED {
|
||||
return ChannelLayout::UNDEFINED;
|
||||
}
|
||||
cl |= channel;
|
||||
channels.push(label.into());
|
||||
}
|
||||
|
||||
cl
|
||||
channels
|
||||
}
|
||||
|
||||
fn audiounit_get_preferred_channel_layout(output_unit: AudioUnit) -> ChannelLayout {
|
||||
fn audiounit_get_preferred_channel_layout(output_unit: AudioUnit) -> Vec<mixer::Channel> {
|
||||
let mut rv = NO_ERR;
|
||||
let mut size: usize = 0;
|
||||
rv = audio_unit_get_property_info(
|
||||
|
@ -977,7 +930,7 @@ fn audiounit_get_preferred_channel_layout(output_unit: AudioUnit) -> ChannelLayo
|
|||
"AudioUnitGetPropertyInfo/kAudioDevicePropertyPreferredChannelLayout rv={}",
|
||||
rv
|
||||
);
|
||||
return ChannelLayout::UNDEFINED;
|
||||
return Vec::new();
|
||||
}
|
||||
assert!(size > 0);
|
||||
|
||||
|
@ -995,13 +948,15 @@ fn audiounit_get_preferred_channel_layout(output_unit: AudioUnit) -> ChannelLayo
|
|||
"AudioUnitGetProperty/kAudioDevicePropertyPreferredChannelLayout rv={}",
|
||||
rv
|
||||
);
|
||||
return ChannelLayout::UNDEFINED;
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
audiounit_convert_channel_layout(layout.as_ref())
|
||||
}
|
||||
|
||||
fn audiounit_get_current_channel_layout(output_unit: AudioUnit) -> ChannelLayout {
|
||||
// This is for output AudioUnit only. Calling this by input-only AudioUnit is prone
|
||||
// to crash intermittently.
|
||||
fn audiounit_get_current_channel_layout(output_unit: AudioUnit) -> Vec<mixer::Channel> {
|
||||
let mut rv = NO_ERR;
|
||||
let mut size: usize = 0;
|
||||
rv = audio_unit_get_property_info(
|
||||
|
@ -1036,71 +991,12 @@ fn audiounit_get_current_channel_layout(output_unit: AudioUnit) -> ChannelLayout
|
|||
"AudioUnitGetProperty/kAudioUnitProperty_AudioChannelLayout rv={}",
|
||||
rv
|
||||
);
|
||||
return ChannelLayout::UNDEFINED;
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
audiounit_convert_channel_layout(layout.as_ref())
|
||||
}
|
||||
|
||||
fn audiounit_set_channel_layout(
|
||||
unit: AudioUnit,
|
||||
layout: ChannelLayout,
|
||||
) -> std::result::Result<(), OSStatus> {
|
||||
assert!(!unit.is_null());
|
||||
|
||||
if layout == ChannelLayout::UNDEFINED {
|
||||
// We leave everything as-is...
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let nb_channels = unsafe { ffi::cubeb_channel_layout_nb_channels(layout.into()) };
|
||||
|
||||
// We do not use CoreAudio standard layout for lack of documentation on what
|
||||
// the actual channel orders are. So we set a custom layout.
|
||||
assert!(nb_channels >= 1);
|
||||
let size = mem::size_of::<AudioChannelLayout>()
|
||||
+ (nb_channels as usize - 1) * mem::size_of::<AudioChannelDescription>();
|
||||
let mut au_layout = make_sized_audio_channel_layout(size);
|
||||
au_layout.as_mut().mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
|
||||
au_layout.as_mut().mNumberChannelDescriptions = nb_channels;
|
||||
let channel_descriptions = unsafe {
|
||||
slice::from_raw_parts_mut(
|
||||
au_layout.as_mut().mChannelDescriptions.as_mut_ptr(),
|
||||
nb_channels as usize,
|
||||
)
|
||||
};
|
||||
|
||||
let mut channels: usize = 0;
|
||||
let mut channel_map: ffi::cubeb_channel_layout = layout.into();
|
||||
let i = 0;
|
||||
while channel_map != 0 {
|
||||
assert!(channels < nb_channels as usize);
|
||||
let channel = (channel_map & 1) << i;
|
||||
if channel != 0 {
|
||||
let layout = ChannelLayout::from(channel);
|
||||
let label = CAChannelLabel::from(layout);
|
||||
channel_descriptions[channels].mChannelLabel = label.get_raw_label();
|
||||
channel_descriptions[channels].mChannelFlags = kAudioChannelFlags_AllOff;
|
||||
channels += 1;
|
||||
}
|
||||
channel_map >>= 1;
|
||||
}
|
||||
|
||||
let err = audio_unit_set_property(
|
||||
unit,
|
||||
kAudioUnitProperty_AudioChannelLayout,
|
||||
kAudioUnitScope_Input,
|
||||
AU_OUT_BUS,
|
||||
au_layout.as_ref(),
|
||||
size,
|
||||
);
|
||||
if err == NO_ERR {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
|
||||
fn start_audiounit(unit: AudioUnit) -> Result<()> {
|
||||
let status = audio_output_unit_start(unit);
|
||||
if status == NO_ERR {
|
||||
|
@ -2348,7 +2244,7 @@ struct CoreStreamData<'ctx> {
|
|||
input_hw_rate: f64,
|
||||
output_hw_rate: f64,
|
||||
// Channel layout of the output AudioUnit.
|
||||
device_layout: ChannelLayout,
|
||||
device_layout: Vec<mixer::Channel>,
|
||||
// Hold the input samples in every input callback iteration.
|
||||
// Only accessed on input/output callback thread and during initial configure.
|
||||
input_linear_buffer: Option<Box<dyn AutoArrayWrapper>>,
|
||||
|
@ -2389,7 +2285,7 @@ impl<'ctx> Default for CoreStreamData<'ctx> {
|
|||
output_device: device_info::default(),
|
||||
input_hw_rate: 0_f64,
|
||||
output_hw_rate: 0_f64,
|
||||
device_layout: ChannelLayout::UNDEFINED,
|
||||
device_layout: Vec::new(),
|
||||
input_linear_buffer: None,
|
||||
default_input_listener: None,
|
||||
default_output_listener: None,
|
||||
|
@ -2434,7 +2330,7 @@ impl<'ctx> CoreStreamData<'ctx> {
|
|||
output_device: out_dev,
|
||||
input_hw_rate: 0_f64,
|
||||
output_hw_rate: 0_f64,
|
||||
device_layout: ChannelLayout::UNDEFINED,
|
||||
device_layout: Vec::new(),
|
||||
input_linear_buffer: None,
|
||||
default_input_listener: None,
|
||||
default_output_listener: None,
|
||||
|
@ -2727,21 +2623,11 @@ impl<'ctx> CoreStreamData<'ctx> {
|
|||
self.output_hw_rate = output_hw_desc.mSampleRate;
|
||||
let hw_channels = output_hw_desc.mChannelsPerFrame;
|
||||
|
||||
// Set the input layout to match the output device layout.
|
||||
self.device_layout = audiounit_get_current_channel_layout(self.output_unit);
|
||||
let msg = match audiounit_set_channel_layout(self.output_unit, self.device_layout) {
|
||||
Ok(_) => "successfully".to_string(),
|
||||
Err(e) => format!("failed. Error: {}", e),
|
||||
};
|
||||
cubeb_log!(
|
||||
"({:p}) Set output hardware layout to {:?} {}.",
|
||||
self.stm_ptr,
|
||||
self.device_layout,
|
||||
msg
|
||||
);
|
||||
|
||||
self.mixer = if hw_channels != self.output_stream_params.channels()
|
||||
|| self.device_layout != self.output_stream_params.layout()
|
||||
|| self.device_layout
|
||||
!= mixer::get_channel_order(self.output_stream_params.layout())
|
||||
{
|
||||
cubeb_log!("Incompatible channel layouts detected, setting up remixer");
|
||||
// We will be remixing the data before it reaches the output device.
|
||||
|
@ -2754,10 +2640,10 @@ impl<'ctx> CoreStreamData<'ctx> {
|
|||
self.output_desc.mBytesPerFrame * self.output_desc.mFramesPerPacket;
|
||||
Some(Mixer::new(
|
||||
self.output_stream_params.format(),
|
||||
self.output_stream_params.channels(),
|
||||
self.output_stream_params.channels() as usize,
|
||||
self.output_stream_params.layout(),
|
||||
hw_channels,
|
||||
self.device_layout,
|
||||
hw_channels as usize,
|
||||
self.device_layout.clone(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
|
|
|
@ -31,162 +31,6 @@ fn test_make_sized_audio_channel_layout_with_wrong_size() {
|
|||
let _ = make_sized_audio_channel_layout(wrong_size);
|
||||
}
|
||||
|
||||
// has_input
|
||||
// ------------------------------------
|
||||
// TODO
|
||||
|
||||
// has_output
|
||||
// ------------------------------------
|
||||
// TODO
|
||||
|
||||
// channel_label_to_cubeb_channel
|
||||
// ------------------------------------
|
||||
// Convert a CAChannelLabel into a ChannelLayout
|
||||
#[test]
|
||||
fn test_channel_label_to_cubeb_channel_layout() {
|
||||
let pairs = [
|
||||
(kAudioChannelLabel_Left, ChannelLayout::FRONT_LEFT),
|
||||
(kAudioChannelLabel_Right, ChannelLayout::FRONT_RIGHT),
|
||||
(kAudioChannelLabel_Center, ChannelLayout::FRONT_CENTER),
|
||||
(kAudioChannelLabel_LFEScreen, ChannelLayout::LOW_FREQUENCY),
|
||||
(kAudioChannelLabel_LeftSurround, ChannelLayout::BACK_LEFT),
|
||||
(kAudioChannelLabel_RightSurround, ChannelLayout::BACK_RIGHT),
|
||||
(
|
||||
kAudioChannelLabel_LeftCenter,
|
||||
ChannelLayout::FRONT_LEFT_OF_CENTER,
|
||||
),
|
||||
(
|
||||
kAudioChannelLabel_RightCenter,
|
||||
ChannelLayout::FRONT_RIGHT_OF_CENTER,
|
||||
),
|
||||
(
|
||||
kAudioChannelLabel_CenterSurround,
|
||||
ChannelLayout::BACK_CENTER,
|
||||
),
|
||||
(
|
||||
kAudioChannelLabel_LeftSurroundDirect,
|
||||
ChannelLayout::SIDE_LEFT,
|
||||
),
|
||||
(
|
||||
kAudioChannelLabel_RightSurroundDirect,
|
||||
ChannelLayout::SIDE_RIGHT,
|
||||
),
|
||||
(
|
||||
kAudioChannelLabel_TopCenterSurround,
|
||||
ChannelLayout::TOP_CENTER,
|
||||
),
|
||||
(
|
||||
kAudioChannelLabel_VerticalHeightLeft,
|
||||
ChannelLayout::TOP_FRONT_LEFT,
|
||||
),
|
||||
(
|
||||
kAudioChannelLabel_VerticalHeightCenter,
|
||||
ChannelLayout::TOP_FRONT_CENTER,
|
||||
),
|
||||
(
|
||||
kAudioChannelLabel_VerticalHeightRight,
|
||||
ChannelLayout::TOP_FRONT_RIGHT,
|
||||
),
|
||||
(kAudioChannelLabel_TopBackLeft, ChannelLayout::TOP_BACK_LEFT),
|
||||
(
|
||||
kAudioChannelLabel_TopBackCenter,
|
||||
ChannelLayout::TOP_BACK_CENTER,
|
||||
),
|
||||
(
|
||||
kAudioChannelLabel_TopBackRight,
|
||||
ChannelLayout::TOP_BACK_RIGHT,
|
||||
),
|
||||
(kAudioChannelLabel_Unknown, ChannelLayout::UNDEFINED),
|
||||
];
|
||||
|
||||
for (label, channel) in pairs.iter() {
|
||||
let channel_label = CAChannelLabel(*label);
|
||||
let layout: ChannelLayout = channel_label.into();
|
||||
assert_eq!(layout, *channel);
|
||||
}
|
||||
}
|
||||
|
||||
// cubeb_channel_to_channel_label
|
||||
// ------------------------------------
|
||||
// Convert a ChannelLayout into a CAChannelLabel
|
||||
#[test]
|
||||
fn test_cubeb_channel_layout_to_channel_label() {
|
||||
let pairs = [
|
||||
(ChannelLayout::FRONT_LEFT, kAudioChannelLabel_Left),
|
||||
(ChannelLayout::FRONT_RIGHT, kAudioChannelLabel_Right),
|
||||
(ChannelLayout::FRONT_CENTER, kAudioChannelLabel_Center),
|
||||
(ChannelLayout::LOW_FREQUENCY, kAudioChannelLabel_LFEScreen),
|
||||
(ChannelLayout::BACK_LEFT, kAudioChannelLabel_LeftSurround),
|
||||
(ChannelLayout::BACK_RIGHT, kAudioChannelLabel_RightSurround),
|
||||
(
|
||||
ChannelLayout::FRONT_LEFT_OF_CENTER,
|
||||
kAudioChannelLabel_LeftCenter,
|
||||
),
|
||||
(
|
||||
ChannelLayout::FRONT_RIGHT_OF_CENTER,
|
||||
kAudioChannelLabel_RightCenter,
|
||||
),
|
||||
(
|
||||
ChannelLayout::BACK_CENTER,
|
||||
kAudioChannelLabel_CenterSurround,
|
||||
),
|
||||
(
|
||||
ChannelLayout::SIDE_LEFT,
|
||||
kAudioChannelLabel_LeftSurroundDirect,
|
||||
),
|
||||
(
|
||||
ChannelLayout::SIDE_RIGHT,
|
||||
kAudioChannelLabel_RightSurroundDirect,
|
||||
),
|
||||
(
|
||||
ChannelLayout::TOP_CENTER,
|
||||
kAudioChannelLabel_TopCenterSurround,
|
||||
),
|
||||
(
|
||||
ChannelLayout::TOP_FRONT_LEFT,
|
||||
kAudioChannelLabel_VerticalHeightLeft,
|
||||
),
|
||||
(
|
||||
ChannelLayout::TOP_FRONT_CENTER,
|
||||
kAudioChannelLabel_VerticalHeightCenter,
|
||||
),
|
||||
(
|
||||
ChannelLayout::TOP_FRONT_RIGHT,
|
||||
kAudioChannelLabel_VerticalHeightRight,
|
||||
),
|
||||
(ChannelLayout::TOP_BACK_LEFT, kAudioChannelLabel_TopBackLeft),
|
||||
(
|
||||
ChannelLayout::TOP_BACK_CENTER,
|
||||
kAudioChannelLabel_TopBackCenter,
|
||||
),
|
||||
(
|
||||
ChannelLayout::TOP_BACK_RIGHT,
|
||||
kAudioChannelLabel_TopBackRight,
|
||||
),
|
||||
];
|
||||
|
||||
for (channel, label) in pairs.iter() {
|
||||
let channel_label = CAChannelLabel(*label);
|
||||
assert_eq!(CAChannelLabel::from(*channel), channel_label);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_cubeb_channel_layout_to_channel_label_with_invalid_channel() {
|
||||
let _label = CAChannelLabel::from(ChannelLayout::_3F4_LFE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_cubeb_channel_layout_to_channel_label_with_unknown_channel() {
|
||||
assert_eq!(
|
||||
ChannelLayout::from(ffi::CHANNEL_UNKNOWN),
|
||||
ChannelLayout::UNDEFINED
|
||||
);
|
||||
let _label = CAChannelLabel::from(ChannelLayout::UNDEFINED);
|
||||
}
|
||||
|
||||
// active_streams
|
||||
// update_latency_by_adding_stream
|
||||
// update_latency_by_removing_stream
|
||||
|
@ -264,14 +108,6 @@ fn test_make_silent() {
|
|||
}
|
||||
}
|
||||
|
||||
// render_input
|
||||
// ------------------------------------
|
||||
// TODO
|
||||
|
||||
// input_callback
|
||||
// ------------------------------------
|
||||
// TODO
|
||||
|
||||
// minimum_resampling_input_frames
|
||||
// ------------------------------------
|
||||
#[test]
|
||||
|
@ -310,10 +146,6 @@ fn test_minimum_resampling_input_frames_equal_input_output_rate() {
|
|||
);
|
||||
}
|
||||
|
||||
// output_callback
|
||||
// ------------------------------------
|
||||
// TODO
|
||||
|
||||
// create_device_info
|
||||
// ------------------------------------
|
||||
#[test]
|
||||
|
@ -378,22 +210,6 @@ fn test_set_device_info_to_nonexistent_output_device() {
|
|||
let _device = create_device_info(nonexistent_id, DeviceType::OUTPUT);
|
||||
}
|
||||
|
||||
// reinit_stream
|
||||
// ------------------------------------
|
||||
// TODO
|
||||
|
||||
// reinit_stream_async
|
||||
// ------------------------------------
|
||||
// TODO
|
||||
|
||||
// event_addr_to_string
|
||||
// ------------------------------------
|
||||
// TODO
|
||||
|
||||
// property_listener_callback
|
||||
// ------------------------------------
|
||||
// TODO
|
||||
|
||||
// add_listener (for default output device)
|
||||
// ------------------------------------
|
||||
#[test]
|
||||
|
@ -505,14 +321,6 @@ fn test_remove_listener_unknown_device() {
|
|||
});
|
||||
}
|
||||
|
||||
// install_system_changed_callback
|
||||
// ------------------------------------
|
||||
// TODO
|
||||
|
||||
// uninstall_system_changed_callback
|
||||
// ------------------------------------
|
||||
// TODO
|
||||
|
||||
// get_default_device_id
|
||||
// ------------------------------------
|
||||
#[test]
|
||||
|
@ -555,26 +363,30 @@ fn test_get_default_device_id_with_inout_type() {
|
|||
#[test]
|
||||
fn test_convert_channel_layout() {
|
||||
let pairs = [
|
||||
// The single channel is mapped to mono now.
|
||||
(vec![kAudioObjectUnknown], ChannelLayout::MONO),
|
||||
(vec![kAudioChannelLabel_Mono], ChannelLayout::MONO),
|
||||
// The dual channels are mapped to stereo now.
|
||||
(vec![kAudioObjectUnknown], vec![mixer::Channel::Silence]),
|
||||
(
|
||||
vec![kAudioChannelLabel_Mono],
|
||||
vec![mixer::Channel::FrontCenter],
|
||||
),
|
||||
(
|
||||
vec![kAudioChannelLabel_Mono, kAudioChannelLabel_LFEScreen],
|
||||
ChannelLayout::STEREO,
|
||||
vec![mixer::Channel::FrontCenter, mixer::Channel::LowFrequency],
|
||||
),
|
||||
(
|
||||
vec![kAudioChannelLabel_Left, kAudioChannelLabel_Right],
|
||||
ChannelLayout::STEREO,
|
||||
vec![mixer::Channel::FrontLeft, mixer::Channel::FrontRight],
|
||||
),
|
||||
// The Layouts containing any unknonwn channel will be mapped to UNDEFINED.
|
||||
(
|
||||
vec![
|
||||
kAudioChannelLabel_Left,
|
||||
kAudioChannelLabel_Right,
|
||||
kAudioChannelLabel_Unknown,
|
||||
],
|
||||
ChannelLayout::UNDEFINED,
|
||||
vec![
|
||||
mixer::Channel::FrontLeft,
|
||||
mixer::Channel::FrontRight,
|
||||
mixer::Channel::Silence,
|
||||
],
|
||||
),
|
||||
(
|
||||
vec![
|
||||
|
@ -582,7 +394,11 @@ fn test_convert_channel_layout() {
|
|||
kAudioChannelLabel_Right,
|
||||
kAudioChannelLabel_Unused,
|
||||
],
|
||||
ChannelLayout::UNDEFINED,
|
||||
vec![
|
||||
mixer::Channel::FrontLeft,
|
||||
mixer::Channel::FrontRight,
|
||||
mixer::Channel::Silence,
|
||||
],
|
||||
),
|
||||
(
|
||||
vec![
|
||||
|
@ -590,7 +406,11 @@ fn test_convert_channel_layout() {
|
|||
kAudioChannelLabel_Right,
|
||||
kAudioChannelLabel_ForeignLanguage,
|
||||
],
|
||||
ChannelLayout::UNDEFINED,
|
||||
vec![
|
||||
mixer::Channel::FrontLeft,
|
||||
mixer::Channel::FrontRight,
|
||||
mixer::Channel::Silence,
|
||||
],
|
||||
),
|
||||
// The SMPTE layouts.
|
||||
(
|
||||
|
@ -599,7 +419,11 @@ fn test_convert_channel_layout() {
|
|||
kAudioChannelLabel_Right,
|
||||
kAudioChannelLabel_LFEScreen,
|
||||
],
|
||||
ChannelLayout::STEREO_LFE,
|
||||
vec![
|
||||
mixer::Channel::FrontLeft,
|
||||
mixer::Channel::FrontRight,
|
||||
mixer::Channel::LowFrequency,
|
||||
],
|
||||
),
|
||||
(
|
||||
vec![
|
||||
|
@ -607,7 +431,11 @@ fn test_convert_channel_layout() {
|
|||
kAudioChannelLabel_Right,
|
||||
kAudioChannelLabel_Center,
|
||||
],
|
||||
ChannelLayout::_3F,
|
||||
vec![
|
||||
mixer::Channel::FrontLeft,
|
||||
mixer::Channel::FrontRight,
|
||||
mixer::Channel::FrontCenter,
|
||||
],
|
||||
),
|
||||
(
|
||||
vec![
|
||||
|
@ -616,7 +444,12 @@ fn test_convert_channel_layout() {
|
|||
kAudioChannelLabel_Center,
|
||||
kAudioChannelLabel_LFEScreen,
|
||||
],
|
||||
ChannelLayout::_3F_LFE,
|
||||
vec![
|
||||
mixer::Channel::FrontLeft,
|
||||
mixer::Channel::FrontRight,
|
||||
mixer::Channel::FrontCenter,
|
||||
mixer::Channel::LowFrequency,
|
||||
],
|
||||
),
|
||||
(
|
||||
vec![
|
||||
|
@ -624,7 +457,11 @@ fn test_convert_channel_layout() {
|
|||
kAudioChannelLabel_Right,
|
||||
kAudioChannelLabel_CenterSurround,
|
||||
],
|
||||
ChannelLayout::_2F1,
|
||||
vec![
|
||||
mixer::Channel::FrontLeft,
|
||||
mixer::Channel::FrontRight,
|
||||
mixer::Channel::BackCenter,
|
||||
],
|
||||
),
|
||||
(
|
||||
vec![
|
||||
|
@ -633,7 +470,12 @@ fn test_convert_channel_layout() {
|
|||
kAudioChannelLabel_CenterSurround,
|
||||
kAudioChannelLabel_LFEScreen,
|
||||
],
|
||||
ChannelLayout::_2F1_LFE,
|
||||
vec![
|
||||
mixer::Channel::FrontLeft,
|
||||
mixer::Channel::FrontRight,
|
||||
mixer::Channel::BackCenter,
|
||||
mixer::Channel::LowFrequency,
|
||||
],
|
||||
),
|
||||
(
|
||||
vec![
|
||||
|
@ -642,7 +484,12 @@ fn test_convert_channel_layout() {
|
|||
kAudioChannelLabel_Center,
|
||||
kAudioChannelLabel_CenterSurround,
|
||||
],
|
||||
ChannelLayout::_3F1,
|
||||
vec![
|
||||
mixer::Channel::FrontLeft,
|
||||
mixer::Channel::FrontRight,
|
||||
mixer::Channel::FrontCenter,
|
||||
mixer::Channel::BackCenter,
|
||||
],
|
||||
),
|
||||
(
|
||||
vec![
|
||||
|
@ -652,7 +499,13 @@ fn test_convert_channel_layout() {
|
|||
kAudioChannelLabel_CenterSurround,
|
||||
kAudioChannelLabel_LFEScreen,
|
||||
],
|
||||
ChannelLayout::_3F1_LFE,
|
||||
vec![
|
||||
mixer::Channel::FrontLeft,
|
||||
mixer::Channel::FrontRight,
|
||||
mixer::Channel::FrontCenter,
|
||||
mixer::Channel::BackCenter,
|
||||
mixer::Channel::LowFrequency,
|
||||
],
|
||||
),
|
||||
(
|
||||
vec![
|
||||
|
@ -661,7 +514,12 @@ fn test_convert_channel_layout() {
|
|||
kAudioChannelLabel_LeftSurroundDirect,
|
||||
kAudioChannelLabel_RightSurroundDirect,
|
||||
],
|
||||
ChannelLayout::_2F2,
|
||||
vec![
|
||||
mixer::Channel::FrontLeft,
|
||||
mixer::Channel::FrontRight,
|
||||
mixer::Channel::SideLeft,
|
||||
mixer::Channel::SideRight,
|
||||
],
|
||||
),
|
||||
(
|
||||
vec![
|
||||
|
@ -671,7 +529,13 @@ fn test_convert_channel_layout() {
|
|||
kAudioChannelLabel_RightSurroundDirect,
|
||||
kAudioChannelLabel_LFEScreen,
|
||||
],
|
||||
ChannelLayout::_2F2_LFE,
|
||||
vec![
|
||||
mixer::Channel::FrontLeft,
|
||||
mixer::Channel::FrontRight,
|
||||
mixer::Channel::SideLeft,
|
||||
mixer::Channel::SideRight,
|
||||
mixer::Channel::LowFrequency,
|
||||
],
|
||||
),
|
||||
(
|
||||
vec![
|
||||
|
@ -680,7 +544,12 @@ fn test_convert_channel_layout() {
|
|||
kAudioChannelLabel_LeftSurround,
|
||||
kAudioChannelLabel_RightSurround,
|
||||
],
|
||||
ChannelLayout::QUAD,
|
||||
vec![
|
||||
mixer::Channel::FrontLeft,
|
||||
mixer::Channel::FrontRight,
|
||||
mixer::Channel::BackLeft,
|
||||
mixer::Channel::BackRight,
|
||||
],
|
||||
),
|
||||
(
|
||||
vec![
|
||||
|
@ -690,7 +559,13 @@ fn test_convert_channel_layout() {
|
|||
kAudioChannelLabel_RightSurround,
|
||||
kAudioChannelLabel_LFEScreen,
|
||||
],
|
||||
ChannelLayout::QUAD_LFE,
|
||||
vec![
|
||||
mixer::Channel::FrontLeft,
|
||||
mixer::Channel::FrontRight,
|
||||
mixer::Channel::BackLeft,
|
||||
mixer::Channel::BackRight,
|
||||
mixer::Channel::LowFrequency,
|
||||
],
|
||||
),
|
||||
(
|
||||
vec![
|
||||
|
@ -700,7 +575,13 @@ fn test_convert_channel_layout() {
|
|||
kAudioChannelLabel_LeftSurroundDirect,
|
||||
kAudioChannelLabel_RightSurroundDirect,
|
||||
],
|
||||
ChannelLayout::_3F2,
|
||||
vec![
|
||||
mixer::Channel::FrontLeft,
|
||||
mixer::Channel::FrontRight,
|
||||
mixer::Channel::FrontCenter,
|
||||
mixer::Channel::SideLeft,
|
||||
mixer::Channel::SideRight,
|
||||
],
|
||||
),
|
||||
(
|
||||
vec![
|
||||
|
@ -711,7 +592,14 @@ fn test_convert_channel_layout() {
|
|||
kAudioChannelLabel_RightSurroundDirect,
|
||||
kAudioChannelLabel_LFEScreen,
|
||||
],
|
||||
ChannelLayout::_3F2_LFE,
|
||||
vec![
|
||||
mixer::Channel::FrontLeft,
|
||||
mixer::Channel::FrontRight,
|
||||
mixer::Channel::FrontCenter,
|
||||
mixer::Channel::SideLeft,
|
||||
mixer::Channel::SideRight,
|
||||
mixer::Channel::LowFrequency,
|
||||
],
|
||||
),
|
||||
(
|
||||
vec![
|
||||
|
@ -721,7 +609,13 @@ fn test_convert_channel_layout() {
|
|||
kAudioChannelLabel_RightSurround,
|
||||
kAudioChannelLabel_Center,
|
||||
],
|
||||
ChannelLayout::_3F2_BACK,
|
||||
vec![
|
||||
mixer::Channel::FrontLeft,
|
||||
mixer::Channel::FrontRight,
|
||||
mixer::Channel::BackLeft,
|
||||
mixer::Channel::BackRight,
|
||||
mixer::Channel::FrontCenter,
|
||||
],
|
||||
),
|
||||
(
|
||||
vec![
|
||||
|
@ -732,7 +626,14 @@ fn test_convert_channel_layout() {
|
|||
kAudioChannelLabel_Center,
|
||||
kAudioChannelLabel_LFEScreen,
|
||||
],
|
||||
ChannelLayout::_3F2_LFE_BACK,
|
||||
vec![
|
||||
mixer::Channel::FrontLeft,
|
||||
mixer::Channel::FrontRight,
|
||||
mixer::Channel::BackLeft,
|
||||
mixer::Channel::BackRight,
|
||||
mixer::Channel::FrontCenter,
|
||||
mixer::Channel::LowFrequency,
|
||||
],
|
||||
),
|
||||
(
|
||||
vec![
|
||||
|
@ -744,7 +645,15 @@ fn test_convert_channel_layout() {
|
|||
kAudioChannelLabel_LeftSurroundDirect,
|
||||
kAudioChannelLabel_RightSurroundDirect,
|
||||
],
|
||||
ChannelLayout::_3F3R_LFE,
|
||||
vec![
|
||||
mixer::Channel::FrontLeft,
|
||||
mixer::Channel::FrontRight,
|
||||
mixer::Channel::FrontCenter,
|
||||
mixer::Channel::LowFrequency,
|
||||
mixer::Channel::BackCenter,
|
||||
mixer::Channel::SideLeft,
|
||||
mixer::Channel::SideRight,
|
||||
],
|
||||
),
|
||||
(
|
||||
vec![
|
||||
|
@ -757,7 +666,16 @@ fn test_convert_channel_layout() {
|
|||
kAudioChannelLabel_LeftSurroundDirect,
|
||||
kAudioChannelLabel_RightSurroundDirect,
|
||||
],
|
||||
ChannelLayout::_3F4_LFE,
|
||||
vec![
|
||||
mixer::Channel::FrontLeft,
|
||||
mixer::Channel::FrontRight,
|
||||
mixer::Channel::FrontCenter,
|
||||
mixer::Channel::LowFrequency,
|
||||
mixer::Channel::BackLeft,
|
||||
mixer::Channel::BackRight,
|
||||
mixer::Channel::SideLeft,
|
||||
mixer::Channel::SideRight,
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
|
@ -794,8 +712,8 @@ fn test_convert_channel_layout() {
|
|||
}
|
||||
let layout_ref = unsafe { &(*(&layout as *const TestLayout as *const AudioChannelLayout)) };
|
||||
assert_eq!(
|
||||
audiounit_convert_channel_layout(layout_ref),
|
||||
*expected_layout
|
||||
&audiounit_convert_channel_layout(layout_ref),
|
||||
expected_layout
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -804,12 +722,13 @@ fn test_convert_channel_layout() {
|
|||
// ------------------------------------
|
||||
#[test]
|
||||
fn test_get_preferred_channel_layout_output() {
|
||||
const STEREO: [mixer::Channel; 2] = [mixer::Channel::FrontLeft, mixer::Channel::FrontRight];
|
||||
// Predefined whitelist
|
||||
use std::collections::HashMap;
|
||||
let devices_layouts: HashMap<&'static str, ChannelLayout> = [
|
||||
("hdpn", ChannelLayout::STEREO),
|
||||
("ispk", ChannelLayout::STEREO),
|
||||
("FApd", ChannelLayout::STEREO),
|
||||
let devices_layouts: HashMap<&'static str, Vec<mixer::Channel>> = [
|
||||
("hdpn", STEREO.to_vec()),
|
||||
("ispk", STEREO.to_vec()),
|
||||
("FApd", STEREO.to_vec()),
|
||||
]
|
||||
.into_iter()
|
||||
.cloned()
|
||||
|
@ -826,29 +745,26 @@ fn test_get_preferred_channel_layout_output() {
|
|||
let unit = unit.unwrap();
|
||||
if let Some(layout) = devices_layouts.get(source.as_str()) {
|
||||
assert_eq!(
|
||||
audiounit_get_preferred_channel_layout(unit.get_inner()),
|
||||
*layout
|
||||
&audiounit_get_preferred_channel_layout(unit.get_inner()),
|
||||
layout
|
||||
);
|
||||
} else {
|
||||
println!("Device {} is not in the whitelist.", source);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Should it be banned ? It only works with output audiounit for now.
|
||||
// #[test]
|
||||
// fn test_get_preferred_channel_layout_input() {
|
||||
// }
|
||||
|
||||
// get_current_channel_layout
|
||||
// ------------------------------------
|
||||
#[test]
|
||||
fn test_get_current_channel_layout_output() {
|
||||
const STEREO: [mixer::Channel; 2] = [mixer::Channel::FrontLeft, mixer::Channel::FrontRight];
|
||||
|
||||
// Predefined whitelist
|
||||
use std::collections::HashMap;
|
||||
let devices_layouts: HashMap<&'static str, ChannelLayout> = [
|
||||
("hdpn", ChannelLayout::STEREO),
|
||||
("ispk", ChannelLayout::STEREO),
|
||||
("FApd", ChannelLayout::STEREO),
|
||||
let devices_layouts: HashMap<&'static str, Vec<mixer::Channel>> = [
|
||||
("hdpn", STEREO.to_vec()),
|
||||
("ispk", STEREO.to_vec()),
|
||||
("FApd", STEREO.to_vec()),
|
||||
]
|
||||
.into_iter()
|
||||
.cloned()
|
||||
|
@ -873,11 +789,6 @@ fn test_get_current_channel_layout_output() {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Should it be banned ? It only works with output audiounit for now.
|
||||
// #[test]
|
||||
// fn test_get_current_channel_layout_input() {
|
||||
// }
|
||||
|
||||
// create_stream_description
|
||||
// ------------------------------------
|
||||
#[test]
|
||||
|
@ -928,81 +839,6 @@ fn test_create_stream_description() {
|
|||
}
|
||||
}
|
||||
|
||||
// set_channel_layout
|
||||
// ------------------------------------
|
||||
#[test]
|
||||
fn test_set_channel_layout_output() {
|
||||
// Predefined whitelist
|
||||
use std::collections::HashMap;
|
||||
let devices_layouts: HashMap<&'static str, ChannelLayout> = [
|
||||
("hdpn", ChannelLayout::STEREO),
|
||||
("ispk", ChannelLayout::STEREO),
|
||||
("FApd", ChannelLayout::STEREO),
|
||||
]
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let source = test_get_default_source_name(Scope::Output);
|
||||
let unit = test_get_default_audiounit(Scope::Output);
|
||||
if source.is_none() || unit.is_none() {
|
||||
println!("No output audiounit or device source name found.");
|
||||
return;
|
||||
}
|
||||
|
||||
let source = source.unwrap();
|
||||
let unit = unit.unwrap();
|
||||
if let Some(layout) = devices_layouts.get(source.as_str()) {
|
||||
assert!(audiounit_set_channel_layout(unit.get_inner(), *layout).is_ok());
|
||||
assert_eq!(
|
||||
audiounit_get_current_channel_layout(unit.get_inner()),
|
||||
*layout
|
||||
);
|
||||
} else {
|
||||
println!("Device {} is not in the whitelist.", source);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_channel_layout_output_undefind() {
|
||||
if let Some(unit) = test_get_default_audiounit(Scope::Output) {
|
||||
// Get original layout.
|
||||
let original_layout = audiounit_get_current_channel_layout(unit.get_inner());
|
||||
// Leave layout as it is.
|
||||
assert!(audiounit_set_channel_layout(unit.get_inner(), ChannelLayout::UNDEFINED).is_ok());
|
||||
// Check the layout is same as the original one.
|
||||
assert_eq!(
|
||||
audiounit_get_current_channel_layout(unit.get_inner()),
|
||||
original_layout
|
||||
);
|
||||
} else {
|
||||
println!("No output audiounit.");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_channel_layout_input() {
|
||||
if let Some(unit) = test_get_default_audiounit(Scope::Input) {
|
||||
// Get original layout.
|
||||
let original_layout = audiounit_get_current_channel_layout(unit.get_inner());
|
||||
// Leave layout as it is.
|
||||
assert!(audiounit_set_channel_layout(unit.get_inner(), ChannelLayout::UNDEFINED).is_ok());
|
||||
// Check the layout is same as the original one.
|
||||
assert_eq!(
|
||||
audiounit_get_current_channel_layout(unit.get_inner()),
|
||||
original_layout
|
||||
);
|
||||
} else {
|
||||
println!("No input audiounit.");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_set_channel_layout_with_null_unit() {
|
||||
assert!(audiounit_set_channel_layout(ptr::null_mut(), ChannelLayout::UNDEFINED).is_err());
|
||||
}
|
||||
|
||||
// create_default_audiounit
|
||||
// ------------------------------------
|
||||
#[test]
|
||||
|
@ -1290,30 +1126,6 @@ fn test_set_buffer_size_sync_by_scope_with_null_unit(scope: Scope) {
|
|||
assert!(set_buffer_size_sync(unit, scope.into(), 2048).is_err());
|
||||
}
|
||||
|
||||
// setup_stream
|
||||
// ------------------------------------
|
||||
// TODO
|
||||
|
||||
// stream_destroy_internal
|
||||
// ------------------------------------
|
||||
// TODO
|
||||
|
||||
// stream_destroy
|
||||
// ------------------------------------
|
||||
// TODO
|
||||
|
||||
// stream_start_internal
|
||||
// ------------------------------------
|
||||
// TODO
|
||||
|
||||
// stream_start
|
||||
// ------------------------------------
|
||||
// TODO
|
||||
|
||||
// stream_stop_internal
|
||||
// ------------------------------------
|
||||
// TODO
|
||||
|
||||
// get_volume, set_volume
|
||||
// ------------------------------------
|
||||
#[test]
|
||||
|
@ -1359,10 +1171,6 @@ fn test_get_default_device_name() {
|
|||
}
|
||||
}
|
||||
|
||||
// strref_to_cstr_utf8
|
||||
// ------------------------------------
|
||||
// TODO
|
||||
|
||||
// is_device_a_type_of
|
||||
// ------------------------------------
|
||||
#[test]
|
||||
|
|
|
@ -44,7 +44,7 @@ fn test_switch_device_in_scope(scope: Scope) {
|
|||
scope
|
||||
);
|
||||
|
||||
let device_switcher = TestDeviceSwitcher::new(scope.clone());
|
||||
let mut device_switcher = TestDeviceSwitcher::new(scope.clone());
|
||||
|
||||
let count = Arc::new(Mutex::new(0));
|
||||
let also_count = Arc::clone(&count);
|
||||
|
@ -59,7 +59,7 @@ fn test_switch_device_in_scope(scope: Scope) {
|
|||
test_get_started_stream_in_scope(scope.clone(), move |_stream| loop {
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
changed_watcher.prepare();
|
||||
assert!(device_switcher.next().unwrap());
|
||||
device_switcher.next();
|
||||
changed_watcher.wait_for_change();
|
||||
if changed_watcher.current_result() >= devices.len() {
|
||||
break;
|
||||
|
@ -354,8 +354,8 @@ fn test_register_device_changed_callback_to_check_default_device_changed(stm_typ
|
|||
0
|
||||
};
|
||||
|
||||
let input_device_switcher = TestDeviceSwitcher::new(Scope::Input);
|
||||
let output_device_switcher = TestDeviceSwitcher::new(Scope::Output);
|
||||
let mut input_device_switcher = TestDeviceSwitcher::new(Scope::Input);
|
||||
let mut output_device_switcher = TestDeviceSwitcher::new(Scope::Output);
|
||||
|
||||
test_get_stream_with_device_changed_callback(
|
||||
"stream: test callback for default device changed",
|
||||
|
@ -378,7 +378,7 @@ fn test_register_device_changed_callback_to_check_default_device_changed(stm_typ
|
|||
// switching for the default device again will be ignored.
|
||||
while stream.switching_device.load(atomic::Ordering::SeqCst) {}
|
||||
changed_watcher.prepare();
|
||||
assert!(input_device_switcher.next().unwrap());
|
||||
input_device_switcher.next();
|
||||
changed_watcher.wait_for_change();
|
||||
}
|
||||
|
||||
|
@ -387,7 +387,7 @@ fn test_register_device_changed_callback_to_check_default_device_changed(stm_typ
|
|||
// switching for the default device again will be ignored.
|
||||
while stream.switching_device.load(atomic::Ordering::SeqCst) {}
|
||||
changed_watcher.prepare();
|
||||
assert!(output_device_switcher.next().unwrap());
|
||||
output_device_switcher.next();
|
||||
changed_watcher.wait_for_change();
|
||||
}
|
||||
},
|
||||
|
|
|
@ -527,21 +527,22 @@ fn test_ops_stream_device_destroy() {
|
|||
});
|
||||
}
|
||||
|
||||
// Enable this after cubeb-rs version is updated to one that implements
|
||||
// stream_register_device_changed_callback operation.
|
||||
// #[test]
|
||||
// fn test_ops_stream_register_device_changed_callback() {
|
||||
// extern "C" fn callback(_: *mut c_void) {}
|
||||
#[test]
|
||||
fn test_ops_stream_register_device_changed_callback() {
|
||||
extern "C" fn callback(_: *mut c_void) {}
|
||||
|
||||
// test_default_output_stream_operation("stream: register device changed callback", |stream| {
|
||||
// assert_eq!(
|
||||
// unsafe {
|
||||
// OPS.stream_register_device_changed_callback.unwrap()(
|
||||
// stream,
|
||||
// Some(callback)
|
||||
// )
|
||||
// },
|
||||
// ffi::CUBEB_OK
|
||||
// );
|
||||
// });
|
||||
// }
|
||||
test_default_output_stream_operation("stream: register device changed callback", |stream| {
|
||||
assert_eq!(
|
||||
unsafe { OPS.stream_register_device_changed_callback.unwrap()(stream, Some(callback)) },
|
||||
ffi::CUBEB_OK
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { OPS.stream_register_device_changed_callback.unwrap()(stream, Some(callback)) },
|
||||
ffi::CUBEB_ERROR_INVALID_PARAMETER
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { OPS.stream_register_device_changed_callback.unwrap()(stream, None) },
|
||||
ffi::CUBEB_OK
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ fn test_switch_output_device() {
|
|||
return;
|
||||
}
|
||||
|
||||
let output_device_switcher = TestDeviceSwitcher::new(Scope::Output);
|
||||
let mut output_device_switcher = TestDeviceSwitcher::new(Scope::Output);
|
||||
|
||||
// Make sure the parameters meet the requirements of AudioUnitContext::stream_init
|
||||
// (in the comments).
|
||||
|
@ -52,7 +52,7 @@ fn test_switch_output_device() {
|
|||
assert_eq!(input.pop().unwrap(), '\n');
|
||||
match input.as_str() {
|
||||
"s" => {
|
||||
assert!(output_device_switcher.next().unwrap());
|
||||
output_device_switcher.next();
|
||||
}
|
||||
"q" => {
|
||||
println!("Quit.");
|
||||
|
|
|
@ -293,6 +293,53 @@ pub fn test_get_devices_in_scope(scope: Scope) -> Vec<AudioObjectID> {
|
|||
devices
|
||||
}
|
||||
|
||||
fn test_print_devices_in_scope(devices: &Vec<AudioObjectID>, scope: Scope) {
|
||||
println!(
|
||||
"\n{:?} devices\n\
|
||||
--------------------",
|
||||
scope
|
||||
);
|
||||
for device in devices {
|
||||
let info = TestDeviceInfo::new(*device, scope.clone());
|
||||
print_info(&info);
|
||||
}
|
||||
println!("");
|
||||
|
||||
fn print_info(info: &TestDeviceInfo) {
|
||||
println!("{:>4}: {}\n\tuid: {}", info.id, info.label, info.uid);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestDeviceInfo {
|
||||
id: AudioObjectID,
|
||||
label: String,
|
||||
uid: String,
|
||||
}
|
||||
impl TestDeviceInfo {
|
||||
fn new(id: AudioObjectID, scope: Scope) -> Self {
|
||||
Self {
|
||||
id,
|
||||
label: Self::get_label(id, scope.clone()),
|
||||
uid: Self::get_uid(id, scope),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_label(id: AudioObjectID, scope: Scope) -> String {
|
||||
match get_device_uid(id, scope.into()) {
|
||||
Ok(uid) => uid.into_string(),
|
||||
Err(status) => format!("Unknow. Error: {}", status).to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_uid(id: AudioObjectID, scope: Scope) -> String {
|
||||
match get_device_label(id, scope.into()) {
|
||||
Ok(label) => label.into_string(),
|
||||
Err(status) => format!("Unknown. Error: {}", status).to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_device_channels_in_scope(
|
||||
id: AudioObjectID,
|
||||
scope: Scope,
|
||||
|
@ -548,30 +595,39 @@ pub fn test_set_default_device(
|
|||
pub struct TestDeviceSwitcher {
|
||||
scope: Scope,
|
||||
devices: Vec<AudioObjectID>,
|
||||
current_device_index: usize,
|
||||
}
|
||||
|
||||
impl TestDeviceSwitcher {
|
||||
pub fn new(scope: Scope) -> Self {
|
||||
Self {
|
||||
scope: scope.clone(),
|
||||
devices: test_get_devices_in_scope(scope),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&self) -> std::result::Result<bool, OSStatus> {
|
||||
let current = test_get_default_device(self.scope.clone()).unwrap();
|
||||
let mut index = self
|
||||
.devices
|
||||
let devices = test_get_devices_in_scope(scope.clone());
|
||||
let current = test_get_default_device(scope.clone()).unwrap();
|
||||
let index = devices
|
||||
.iter()
|
||||
.position(|device| *device == current)
|
||||
.unwrap();
|
||||
index = (index + 1) % self.devices.len();
|
||||
let next = self.devices[index];
|
||||
test_print_devices_in_scope(&devices, scope.clone());
|
||||
Self {
|
||||
scope: scope,
|
||||
devices: devices,
|
||||
current_device_index: index,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&mut self) {
|
||||
let current = self.devices[self.current_device_index];
|
||||
let next_index = (self.current_device_index + 1) % self.devices.len();
|
||||
let next = self.devices[next_index];
|
||||
println!(
|
||||
"Switch device for {:?}: {} -> {}",
|
||||
self.scope, current, next
|
||||
);
|
||||
test_set_default_device(next, self.scope.clone())
|
||||
assert!(self.set_device(next).unwrap());
|
||||
self.current_device_index = next_index;
|
||||
}
|
||||
|
||||
fn set_device(&self, device: AudioObjectID) -> std::result::Result<bool, OSStatus> {
|
||||
test_set_default_device(device, self.scope.clone())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -71,6 +71,10 @@ and create a new one. It's easier than the current implementation.
|
|||
sub devices list of the aggregate device
|
||||
- Check the `name: CFStringRef` of the master device is not `NULL`
|
||||
|
||||
### Mixer
|
||||
- Don't force output device to mono or stereo when the output device has one or two channel
|
||||
- unless the output devicv is _Bose QC35, mark 1 and 2_.
|
||||
|
||||
## Interface to system types and APIs
|
||||
- Check if we need `AudioDeviceID` and `AudioObjectID` at the same time
|
||||
- Create wrapper for `AudioObjectGetPropertyData(Size)` with _qualifier_ info
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{"files":{"Cargo.toml":"8de7d76d0444b2692fcca3197f1197a13a06575586c7f10b21d18ed641155138","benches/benchmark.rs":"52a51cc36d17e609e63d61de65e49e52fe0df33ba1949da6fad662e70bca7c9b","src/channel.rs":"9a5aad342674629b895f4a3c9b3a3e90f4c05ef1cd4cb0b524cf16afa754edd8","src/coefficient.rs":"c972f8ba510fa72674f4db71db0db45b0e11ff0e5da6a27316b9adef5f498247","src/lib.rs":"19482db0ec5421dbe2269b76736ba64dd50b9faa1d8586e2bcf24256192fa44c","src/main.rs":"f45aec40c4b3a01e63857a0f42ae0b6ab513fac0eb083635af99a755abc36a11"},"package":null}
|
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "mixer"
|
||||
version = "0.1.0"
|
||||
authors = ["Chun-Min Chang <chun.m.chang@gmail.com>"]
|
||||
edition = "2018"
|
||||
license = "MPL-2.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.2"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3"
|
||||
|
||||
[[bench]]
|
||||
name = "benchmark"
|
||||
harness = false
|
|
@ -0,0 +1,95 @@
|
|||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use mixer::{Channel, Mixer};
|
||||
|
||||
use std::any::{Any, TypeId};
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let frames = 512;
|
||||
c.bench_function("downmix_f32", |b| {
|
||||
b.iter(|| downmix::<f32>(black_box(frames)))
|
||||
});
|
||||
c.bench_function("downmix_i16", |b| {
|
||||
b.iter(|| downmix::<i16>(black_box(frames)))
|
||||
});
|
||||
c.bench_function("upmix_f32", |b| b.iter(|| upmix::<f32>(black_box(frames))));
|
||||
c.bench_function("upmix_i16", |b| b.iter(|| upmix::<i16>(black_box(frames))));
|
||||
}
|
||||
|
||||
fn downmix<T>(frames: usize)
|
||||
where
|
||||
T: Clone + Default + From<u8> + ?Sized + Any,
|
||||
{
|
||||
// Downmix from 5.1 to stereo.
|
||||
let input_channels = vec![
|
||||
Channel::FrontLeft,
|
||||
Channel::FrontRight,
|
||||
Channel::FrontCenter,
|
||||
Channel::LowFrequency,
|
||||
Channel::BackLeft,
|
||||
Channel::BackRight,
|
||||
];
|
||||
let output_channels = vec![Channel::FrontLeft, Channel::FrontRight];
|
||||
mix::<T>(input_channels, output_channels, frames);
|
||||
}
|
||||
|
||||
fn upmix<T>(frames: usize)
|
||||
where
|
||||
T: Clone + Default + From<u8> + ?Sized + Any,
|
||||
{
|
||||
// upmix from mono to stereo.
|
||||
let input_channels = vec![Channel::FrontCenter];
|
||||
let output_channels = vec![Channel::FrontLeft, Channel::FrontRight];
|
||||
mix::<T>(input_channels, output_channels, frames);
|
||||
}
|
||||
|
||||
fn mix<T>(input_channels: Vec<Channel>, output_channels: Vec<Channel>, frames: usize)
|
||||
where
|
||||
T: Clone + Default + From<u8> + ?Sized + Any,
|
||||
{
|
||||
if TypeId::of::<T>() == TypeId::of::<f32>() {
|
||||
let (input_buffer, mut output_buffer) = create_buffers::<f32>(
|
||||
input_channels.len() * frames,
|
||||
output_channels.len() * frames,
|
||||
);
|
||||
let mut in_buf = input_buffer.chunks(input_channels.len());
|
||||
let mut out_buf = output_buffer.chunks_mut(output_channels.len());
|
||||
let mixer = Mixer::<f32>::new(input_channels, output_channels);
|
||||
for _ in 0..frames {
|
||||
mixer.mix(in_buf.next().unwrap(), out_buf.next().unwrap());
|
||||
}
|
||||
} else if TypeId::of::<T>() == TypeId::of::<i16>() {
|
||||
let (input_buffer, mut output_buffer) = create_buffers::<i16>(
|
||||
input_channels.len() * frames,
|
||||
output_channels.len() * frames,
|
||||
);
|
||||
let mut in_buf = input_buffer.chunks(input_channels.len());
|
||||
let mut out_buf = output_buffer.chunks_mut(output_channels.len());
|
||||
let mixer = Mixer::<i16>::new(input_channels, output_channels);
|
||||
for _ in 0..frames {
|
||||
mixer.mix(in_buf.next().unwrap(), out_buf.next().unwrap());
|
||||
}
|
||||
} else {
|
||||
panic!("Unsupport type");
|
||||
}
|
||||
}
|
||||
|
||||
fn create_buffers<T: Clone + Default + From<u8>>(
|
||||
input_size: usize,
|
||||
output_size: usize,
|
||||
) -> (Vec<T>, Vec<T>) {
|
||||
let mut input_buffer = default_buffer::<T>(input_size);
|
||||
for (i, data) in input_buffer.iter_mut().enumerate() {
|
||||
*data = T::from((i + 1) as u8);
|
||||
}
|
||||
let output_buffer = default_buffer::<T>(output_size);
|
||||
(input_buffer, output_buffer)
|
||||
}
|
||||
|
||||
fn default_buffer<T: Clone + Default>(size: usize) -> Vec<T> {
|
||||
let mut v = Vec::with_capacity(size);
|
||||
v.resize(size, T::default());
|
||||
v
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
|
@ -0,0 +1,85 @@
|
|||
// The number of channels must be unique and start from 0. They will be treated as indice in the
|
||||
// mixing matrix and used to form unique bitflags in the channel map, which is a bitmap.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum Channel {
|
||||
FrontLeft = 0,
|
||||
FrontRight = 1,
|
||||
FrontCenter = 2,
|
||||
LowFrequency = 3,
|
||||
BackLeft = 4,
|
||||
BackRight = 5,
|
||||
FrontLeftOfCenter = 6,
|
||||
FrontRightOfCenter = 7,
|
||||
BackCenter = 8,
|
||||
SideLeft = 9,
|
||||
SideRight = 10,
|
||||
TopCenter = 11,
|
||||
TopFrontLeft = 12,
|
||||
TopFrontCenter = 13,
|
||||
TopFrontRight = 14,
|
||||
TopBackLeft = 15,
|
||||
TopBackCenter = 16,
|
||||
TopBackRight = 17,
|
||||
Silence = 18,
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
pub const fn number(self) -> usize {
|
||||
self as usize
|
||||
}
|
||||
|
||||
pub const fn count() -> usize {
|
||||
Channel::Silence as usize + 1
|
||||
}
|
||||
|
||||
pub const fn bitmask(self) -> u32 {
|
||||
1 << self as usize
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct ChannelMap: u32 {
|
||||
const FRONT_LEFT = Channel::FrontLeft.bitmask();
|
||||
const FRONT_RIGHT = Channel::FrontRight.bitmask();
|
||||
const FRONT_CENTER = Channel::FrontCenter.bitmask();
|
||||
const LOW_FREQUENCY = Channel::LowFrequency.bitmask();
|
||||
const BACK_LEFT = Channel::BackLeft.bitmask();
|
||||
const BACK_RIGHT = Channel::BackRight.bitmask();
|
||||
const FRONT_LEFT_OF_CENTER = Channel::FrontLeftOfCenter.bitmask();
|
||||
const FRONT_RIGHT_OF_CENTER = Channel::FrontRightOfCenter.bitmask();
|
||||
const BACK_CENTER = Channel::BackCenter.bitmask();
|
||||
const SIDE_LEFT = Channel::SideLeft.bitmask();
|
||||
const SIDE_RIGHT = Channel::SideRight.bitmask();
|
||||
const TOP_CENTER = Channel::TopCenter.bitmask();
|
||||
const TOP_FRONT_LEFT = Channel::TopFrontLeft.bitmask();
|
||||
const TOP_FRONT_CENTER = Channel::TopFrontCenter.bitmask();
|
||||
const TOP_FRONT_RIGHT = Channel::TopFrontRight.bitmask();
|
||||
const TOP_BACK_LEFT = Channel::TopBackLeft.bitmask();
|
||||
const TOP_BACK_CENTER = Channel::TopBackCenter.bitmask();
|
||||
const TOP_BACK_RIGHT = Channel::TopBackRight.bitmask();
|
||||
const SILENCE = Channel::Silence.bitmask();
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid printing the following types in debugging context {:?} by declaring them in impl
|
||||
// rather than bitflags! {} scope.
|
||||
impl ChannelMap {
|
||||
pub const FRONT_2: Self = Self {
|
||||
bits: Self::FRONT_LEFT.bits() | Self::FRONT_RIGHT.bits(),
|
||||
};
|
||||
pub const BACK_2: Self = Self {
|
||||
bits: Self::BACK_LEFT.bits() | Self::BACK_RIGHT.bits(),
|
||||
};
|
||||
pub const FRONT_2_OF_CENTER: Self = Self {
|
||||
bits: Self::FRONT_LEFT_OF_CENTER.bits() | Self::FRONT_RIGHT_OF_CENTER.bits(),
|
||||
};
|
||||
pub const SIDE_2: Self = Self {
|
||||
bits: Self::SIDE_LEFT.bits() | Self::SIDE_RIGHT.bits(),
|
||||
};
|
||||
}
|
||||
|
||||
impl From<Channel> for ChannelMap {
|
||||
fn from(channel: Channel) -> Self {
|
||||
ChannelMap::from_bits(channel.bitmask()).expect("convert an invalid channel")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,682 @@
|
|||
// The code is based from libcubeb's cubeb_mixer.cpp,
|
||||
// which adapts the code from libswresample's rematrix.c
|
||||
|
||||
use crate::channel::{Channel, ChannelMap};
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
const CHANNELS: usize = Channel::count();
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
DuplicateNonSilenceChannel,
|
||||
AsymmetricChannels,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ChannelLayout {
|
||||
channels: Vec<Channel>,
|
||||
channel_map: ChannelMap,
|
||||
}
|
||||
|
||||
impl ChannelLayout {
|
||||
fn new(channels: Vec<Channel>) -> Result<Self, Error> {
|
||||
let channel_map = Self::get_channel_map(&channels)?;
|
||||
Ok(Self {
|
||||
channels,
|
||||
channel_map,
|
||||
})
|
||||
}
|
||||
|
||||
// Except Silence channel, the duplicate channels are not allowed.
|
||||
fn get_channel_map(channels: &[Channel]) -> Result<ChannelMap, Error> {
|
||||
let mut map = ChannelMap::empty();
|
||||
for channel in channels {
|
||||
let bitmask = ChannelMap::from(*channel);
|
||||
if channel != &Channel::Silence && map.contains(bitmask) {
|
||||
return Err(Error::DuplicateNonSilenceChannel);
|
||||
}
|
||||
map.insert(bitmask);
|
||||
}
|
||||
Ok(map)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Coefficient<T>
|
||||
where
|
||||
T: MixingCoefficient,
|
||||
T::Coef: Copy,
|
||||
{
|
||||
input_layout: ChannelLayout,
|
||||
output_layout: ChannelLayout,
|
||||
matrix: Vec<Vec<T::Coef>>,
|
||||
would_overflow_from_coefficient_value: Option<bool>, // Only used when T is i16
|
||||
}
|
||||
|
||||
impl<T> Coefficient<T>
|
||||
where
|
||||
T: MixingCoefficient,
|
||||
T::Coef: Copy,
|
||||
{
|
||||
// Given a M-channel input layout and a N-channel output layout, generate a NxM coefficients
|
||||
// matrix m such that out_audio = m * in_audio, where in_audio, out_audio are Mx1, Nx1 matrix
|
||||
// storing input and output audio data in their rows respectively.
|
||||
//
|
||||
// data in channel #1 ▸ │ Silence │ │ 0, 0, 0, 0 │ │ FrontRight │ ◂ data in channel #1
|
||||
// data in channel #2 ▸ │ FrontRight │ = │ 1, C, 0, L │ x │ FrontCenter │ ◂ data in channel #2
|
||||
// data in channel #3 ▸ │ FrontLeft │ │ 0, C, 1, L │ │ FrontLeft │ ◂ data in channel #3
|
||||
// ▴ ▴ │ LowFrequency │ ◂ data in channel #4
|
||||
// ┊ ┊ ▴
|
||||
// ┊ ┊ ┊
|
||||
// out_audio mixing matrix m in_audio
|
||||
//
|
||||
// The FrontLeft, FrontRight, ... etc label the data for front-left, front-right ... etc channel
|
||||
// in both input and output audio data buffer.
|
||||
//
|
||||
// C and L are coefficients mixing input data from front-center channel and low-frequency channel
|
||||
// to front-left and front-right.
|
||||
//
|
||||
// In math, the in_audio and out_audio should be a 2D-matrix with several rows containing only
|
||||
// one column. However, the in_audio and out_audio are passed by 1-D matrix here for convenience.
|
||||
pub fn create(input_channels: Vec<Channel>, output_channels: Vec<Channel>) -> Self {
|
||||
let input_layout = ChannelLayout::new(input_channels).expect("Invalid input layout");
|
||||
let output_layout = ChannelLayout::new(output_channels).expect("Invalid output layout");
|
||||
|
||||
let mixing_matrix =
|
||||
Self::build_mixing_matrix(input_layout.channel_map, output_layout.channel_map)
|
||||
.unwrap_or_else(|_| Self::get_basic_matrix());
|
||||
|
||||
let coefficient_matrix = Self::pick_coefficients(
|
||||
&input_layout.channels,
|
||||
&output_layout.channels,
|
||||
&mixing_matrix,
|
||||
);
|
||||
|
||||
let normalized_matrix = Self::normalize(T::max_coefficients_sum(), coefficient_matrix);
|
||||
|
||||
let would_overflow = T::would_overflow_from_coefficient_value(&normalized_matrix);
|
||||
|
||||
// Convert the type of the coefficients from f64 to T::Coef.
|
||||
let matrix = normalized_matrix
|
||||
.into_iter()
|
||||
.map(|row| row.into_iter().map(T::coefficient_from_f64).collect())
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
input_layout,
|
||||
output_layout,
|
||||
matrix,
|
||||
would_overflow_from_coefficient_value: would_overflow,
|
||||
}
|
||||
}
|
||||
|
||||
// Return the coefficient for mixing input channel data into output channel.
|
||||
pub fn get(&self, input: usize, output: usize) -> T::Coef {
|
||||
assert!(output < self.matrix.len());
|
||||
assert!(input < self.matrix[output].len());
|
||||
self.matrix[output][input] // Perform copy so T::Coef must implement Copy.
|
||||
}
|
||||
|
||||
pub fn would_overflow_from_coefficient_value(&self) -> Option<bool> {
|
||||
self.would_overflow_from_coefficient_value
|
||||
}
|
||||
|
||||
pub fn input_channels(&self) -> &[Channel] {
|
||||
&self.input_layout.channels
|
||||
}
|
||||
|
||||
pub fn output_channels(&self) -> &[Channel] {
|
||||
&self.output_layout.channels
|
||||
}
|
||||
|
||||
// Given audio input and output channel-maps, generate a CxC mixing coefficients matrix M,
|
||||
// whose indice are ordered by the values defined in enum Channel, such that
|
||||
// output_data(i) = Σ M[i][j] * input_data(j), for all j in [0, C),
|
||||
// where i is in [0, C) and C is the number of channels defined in enum Channel,
|
||||
// output_data and input_data are buffers containing data for channels that are also ordered
|
||||
// by the values defined in enum Channel.
|
||||
//
|
||||
// │ FrontLeft │ │ 1, 0, ..., 0 │ │ FrontLeft │ ◂ data in front-left channel
|
||||
// │ FrontRight │ │ 0, 1, ..., 0 │ │ FrontRight │ ◂ data in front-right channel
|
||||
// │ FrontCenter │ = │ ........., 0 │ x │ FrontCenter │ ◂ data in front-center channel
|
||||
// │ ........... │ │ ........., 0 | │ ........... │ ◂ ...
|
||||
// │ Silence │ │ 0, 0, ..., 0 | │ Silence │ ◂ data in silence channel
|
||||
// ▴ ▴ ▴
|
||||
// out_audio coef matrix M in_audio
|
||||
//
|
||||
// ChannelMap would be used as a hash table to check the existence of channels.
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn build_mixing_matrix(
|
||||
input_map: ChannelMap,
|
||||
output_map: ChannelMap,
|
||||
) -> Result<[[f64; CHANNELS]; CHANNELS], Error> {
|
||||
// Mixing coefficients constants.
|
||||
use std::f64::consts::FRAC_1_SQRT_2;
|
||||
use std::f64::consts::SQRT_2;
|
||||
const CENTER_MIX_LEVEL: f64 = FRAC_1_SQRT_2;
|
||||
const SURROUND_MIX_LEVEL: f64 = FRAC_1_SQRT_2;
|
||||
const LFE_MIX_LEVEL: f64 = 1.0;
|
||||
|
||||
// The indices of channels in the mixing coefficients matrix.
|
||||
const FRONT_LEFT: usize = Channel::FrontLeft.number();
|
||||
const FRONT_RIGHT: usize = Channel::FrontRight.number();
|
||||
const FRONT_CENTER: usize = Channel::FrontCenter.number();
|
||||
const LOW_FREQUENCY: usize = Channel::LowFrequency.number();
|
||||
const BACK_LEFT: usize = Channel::BackLeft.number();
|
||||
const BACK_RIGHT: usize = Channel::BackRight.number();
|
||||
const FRONT_LEFT_OF_CENTER: usize = Channel::FrontLeftOfCenter.number();
|
||||
const FRONT_RIGHT_OF_CENTER: usize = Channel::FrontRightOfCenter.number();
|
||||
const BACK_CENTER: usize = Channel::BackCenter.number();
|
||||
const SIDE_LEFT: usize = Channel::SideLeft.number();
|
||||
const SIDE_RIGHT: usize = Channel::SideRight.number();
|
||||
|
||||
// Return true if mixable channels are symmetric.
|
||||
fn is_symmetric(map: ChannelMap) -> bool {
|
||||
fn even(map: ChannelMap) -> bool {
|
||||
map.bits().count_ones() % 2 == 0
|
||||
}
|
||||
even(map & ChannelMap::FRONT_2)
|
||||
&& even(map & ChannelMap::BACK_2)
|
||||
&& even(map & ChannelMap::FRONT_2_OF_CENTER)
|
||||
&& even(map & ChannelMap::SIDE_2)
|
||||
}
|
||||
|
||||
if !is_symmetric(input_map) || !is_symmetric(output_map) {
|
||||
return Err(Error::AsymmetricChannels);
|
||||
}
|
||||
|
||||
let mut matrix = Self::get_basic_matrix();
|
||||
|
||||
// Get input channels that are not in the output channels.
|
||||
let unaccounted_input_map = input_map & !output_map;
|
||||
|
||||
// When input has front-center but output has not, and output has front-stereo,
|
||||
// mix input's front-center to output's front-stereo.
|
||||
if unaccounted_input_map.contains(ChannelMap::FRONT_CENTER)
|
||||
&& output_map.contains(ChannelMap::FRONT_2)
|
||||
{
|
||||
let coefficient = if input_map.contains(ChannelMap::FRONT_2) {
|
||||
CENTER_MIX_LEVEL
|
||||
} else {
|
||||
FRAC_1_SQRT_2
|
||||
};
|
||||
matrix[FRONT_LEFT][FRONT_CENTER] += coefficient;
|
||||
matrix[FRONT_RIGHT][FRONT_CENTER] += coefficient;
|
||||
}
|
||||
|
||||
// When input has front-stereo but output has not, and output has front-center,
|
||||
// mix input's front-stereo to output's front-center.
|
||||
if unaccounted_input_map.contains(ChannelMap::FRONT_2)
|
||||
&& output_map.contains(ChannelMap::FRONT_CENTER)
|
||||
{
|
||||
matrix[FRONT_CENTER][FRONT_LEFT] += FRAC_1_SQRT_2;
|
||||
matrix[FRONT_CENTER][FRONT_RIGHT] += FRAC_1_SQRT_2;
|
||||
if input_map.contains(ChannelMap::FRONT_CENTER) {
|
||||
matrix[FRONT_CENTER][FRONT_CENTER] = CENTER_MIX_LEVEL * SQRT_2;
|
||||
}
|
||||
}
|
||||
|
||||
// When input has back-center but output has not,
|
||||
if unaccounted_input_map.contains(ChannelMap::BACK_CENTER) {
|
||||
// if output has back-stereo, mix input's back-center to output's back-stereo.
|
||||
if output_map.contains(ChannelMap::BACK_2) {
|
||||
matrix[BACK_LEFT][BACK_CENTER] += FRAC_1_SQRT_2;
|
||||
matrix[BACK_RIGHT][BACK_CENTER] += FRAC_1_SQRT_2;
|
||||
// or if output has side-stereo, mix input's back-center to output's side-stereo.
|
||||
} else if output_map.contains(ChannelMap::SIDE_2) {
|
||||
matrix[SIDE_LEFT][BACK_CENTER] += FRAC_1_SQRT_2;
|
||||
matrix[SIDE_RIGHT][BACK_CENTER] += FRAC_1_SQRT_2;
|
||||
// or if output has front-stereo, mix input's back-center to output's front-stereo.
|
||||
} else if output_map.contains(ChannelMap::FRONT_2) {
|
||||
matrix[FRONT_LEFT][BACK_CENTER] += SURROUND_MIX_LEVEL * FRAC_1_SQRT_2;
|
||||
matrix[FRONT_RIGHT][BACK_CENTER] += SURROUND_MIX_LEVEL * FRAC_1_SQRT_2;
|
||||
// or if output has front-center, mix input's back-center to output's front-center.
|
||||
} else if output_map.contains(ChannelMap::FRONT_CENTER) {
|
||||
matrix[FRONT_CENTER][BACK_CENTER] += SURROUND_MIX_LEVEL * FRAC_1_SQRT_2;
|
||||
}
|
||||
}
|
||||
|
||||
// When input has back-stereo but output has not,
|
||||
if unaccounted_input_map.contains(ChannelMap::BACK_2) {
|
||||
// if output has back-center, mix input's back-stereo to output's back-center.
|
||||
if output_map.contains(ChannelMap::BACK_CENTER) {
|
||||
matrix[BACK_CENTER][BACK_LEFT] += FRAC_1_SQRT_2;
|
||||
matrix[BACK_CENTER][BACK_RIGHT] += FRAC_1_SQRT_2;
|
||||
// or if output has side-stereo, mix input's back-stereo to output's side-stereo.
|
||||
} else if output_map.contains(ChannelMap::SIDE_2) {
|
||||
let coefficient = if input_map.contains(ChannelMap::SIDE_2) {
|
||||
FRAC_1_SQRT_2
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
matrix[SIDE_LEFT][BACK_LEFT] += coefficient;
|
||||
matrix[SIDE_RIGHT][BACK_RIGHT] += coefficient;
|
||||
// or if output has front-stereo, mix input's back-stereo to output's side-stereo.
|
||||
} else if output_map.contains(ChannelMap::FRONT_2) {
|
||||
matrix[FRONT_LEFT][BACK_LEFT] += SURROUND_MIX_LEVEL;
|
||||
matrix[FRONT_RIGHT][BACK_RIGHT] += SURROUND_MIX_LEVEL;
|
||||
// or if output has front-center, mix input's back-stereo to output's front-center.
|
||||
} else if output_map.contains(ChannelMap::FRONT_CENTER) {
|
||||
matrix[FRONT_CENTER][BACK_LEFT] += SURROUND_MIX_LEVEL * FRAC_1_SQRT_2;
|
||||
matrix[FRONT_CENTER][BACK_RIGHT] += SURROUND_MIX_LEVEL * FRAC_1_SQRT_2;
|
||||
}
|
||||
}
|
||||
|
||||
// When input has side-stereo but output has not,
|
||||
if unaccounted_input_map.contains(ChannelMap::SIDE_2) {
|
||||
// if output has back-stereo, mix input's side-stereo to output's back-stereo.
|
||||
if output_map.contains(ChannelMap::BACK_2) {
|
||||
let coefficient = if input_map.contains(ChannelMap::BACK_2) {
|
||||
FRAC_1_SQRT_2
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
matrix[BACK_LEFT][SIDE_LEFT] += coefficient;
|
||||
matrix[BACK_RIGHT][SIDE_RIGHT] += coefficient;
|
||||
// or if output has back-center, mix input's side-stereo to output's back-center.
|
||||
} else if output_map.contains(ChannelMap::BACK_CENTER) {
|
||||
matrix[BACK_CENTER][SIDE_LEFT] += FRAC_1_SQRT_2;
|
||||
matrix[BACK_CENTER][SIDE_RIGHT] += FRAC_1_SQRT_2;
|
||||
// or if output has front-stereo, mix input's side-stereo to output's front-stereo.
|
||||
} else if output_map.contains(ChannelMap::FRONT_2) {
|
||||
matrix[FRONT_LEFT][SIDE_LEFT] += SURROUND_MIX_LEVEL;
|
||||
matrix[FRONT_RIGHT][SIDE_RIGHT] += SURROUND_MIX_LEVEL;
|
||||
// or if output has front-center, mix input's side-stereo to output's front-center.
|
||||
} else if output_map.contains(ChannelMap::FRONT_CENTER) {
|
||||
matrix[FRONT_CENTER][SIDE_LEFT] += SURROUND_MIX_LEVEL * FRAC_1_SQRT_2;
|
||||
matrix[FRONT_CENTER][SIDE_RIGHT] += SURROUND_MIX_LEVEL * FRAC_1_SQRT_2;
|
||||
}
|
||||
}
|
||||
|
||||
// When input has front-stereo-of-center but output has not,
|
||||
if unaccounted_input_map.contains(ChannelMap::FRONT_2_OF_CENTER) {
|
||||
// if output has front-stereo, mix input's front-stereo-of-center to output's front-stereo.
|
||||
if output_map.contains(ChannelMap::FRONT_2) {
|
||||
matrix[FRONT_LEFT][FRONT_LEFT_OF_CENTER] += 1.0;
|
||||
matrix[FRONT_RIGHT][FRONT_RIGHT_OF_CENTER] += 1.0;
|
||||
// or if output has front-center, mix input's front-stereo-of-center to output's front-center.
|
||||
} else if output_map.contains(ChannelMap::FRONT_CENTER) {
|
||||
matrix[FRONT_CENTER][FRONT_LEFT_OF_CENTER] += FRAC_1_SQRT_2;
|
||||
matrix[FRONT_CENTER][FRONT_RIGHT_OF_CENTER] += FRAC_1_SQRT_2;
|
||||
}
|
||||
}
|
||||
|
||||
// When input has low-frequency but output has not,
|
||||
if unaccounted_input_map.contains(ChannelMap::LOW_FREQUENCY) {
|
||||
// if output has front-center, mix input's low-frequency to output's front-center.
|
||||
if output_map.contains(ChannelMap::FRONT_CENTER) {
|
||||
matrix[FRONT_CENTER][LOW_FREQUENCY] += LFE_MIX_LEVEL;
|
||||
// or if output has front-stereo, mix input's low-frequency to output's front-stereo.
|
||||
} else if output_map.contains(ChannelMap::FRONT_2) {
|
||||
matrix[FRONT_LEFT][LOW_FREQUENCY] += LFE_MIX_LEVEL * FRAC_1_SQRT_2;
|
||||
matrix[FRONT_RIGHT][LOW_FREQUENCY] += LFE_MIX_LEVEL * FRAC_1_SQRT_2;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(matrix)
|
||||
}
|
||||
|
||||
// Return a CHANNELSxCHANNELS matrix M that is (CHANNELS-1)x(CHANNELS-1) identity matrix
|
||||
// padding with one extra row and one column containing only zero values. The result would be:
|
||||
//
|
||||
// identity padding
|
||||
// matrix column
|
||||
// ▾ ▾
|
||||
// ┌┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┐ i ┐
|
||||
// │ 1, 0, 0, ..., 0 ┊, 0 │ ◂ 0 ┊ channel i
|
||||
// │ 0, 1, 0, ..., 0 ┊, 0 │ ◂ 1 ┊ for
|
||||
// │ 0, 0, 1, ..., 0 ┊, 0 │ ◂ 2 ┊ audio
|
||||
// │ 0, 0, 0, ..., 0 ┊, 0 │ . ┊ output
|
||||
// │ ............... ┊ │ . ┊
|
||||
// │ 0, 0, 0, ..., 1 ┊, 0 │ ◂ 16 ┊
|
||||
// ├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┼┈┈┈┈┤ ◂ 17 ┊
|
||||
// │ 0, 0, 0, ..., 0 ┊, 0 │ ◂ padding row ◂ 18 ┊
|
||||
// ▴ ▴ ▴ .... ▴ ▴ ┘
|
||||
// j 0 1 2 .... 17 18
|
||||
// └┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┘
|
||||
// channel j for audio input
|
||||
//
|
||||
// Given an audio input buffer, in_audio, and an output buffer, out_audio,
|
||||
// and their channel data are both ordered by the values defined in enum Channel.
|
||||
// The generated matrix M makes sure that:
|
||||
//
|
||||
// out_audio(i) = in_audio(j), if i == j and both i, j are non-silence channel
|
||||
// = 0, if i != j or i, j are silence channel
|
||||
//
|
||||
// │ FrontLeft │ │ FrontLeft │ ◂ data in front-left channel
|
||||
// │ FrontRight │ │ FrontRight │ ◂ data in front-right channel
|
||||
// │ FrontCenter │ = M x │ FrontCenter │ ◂ data in front-center channel
|
||||
// │ ........... │ │ ........... │ ◂ ...
|
||||
// │ Silence │ │ Silence │ ◂ data in silence channel
|
||||
// ▴ ▴
|
||||
// out_audio in_audio
|
||||
//
|
||||
// That is,
|
||||
// 1. If the input-channel is silence, it won't be mixed into any channel.
|
||||
// 2. If the output-channel is silence, its output-channel data will be zero (silence).
|
||||
// 3. If input-channel j is different from output-channel i, audio data in input channel j
|
||||
// won't be mixed into the audio output data in channel i
|
||||
// 4. If input-channel j is same as output-channel i, audio data in input channel j will be
|
||||
// copied to audio output data in channel i
|
||||
//
|
||||
fn get_basic_matrix() -> [[f64; CHANNELS]; CHANNELS] {
|
||||
const SILENCE: usize = Channel::Silence.number();
|
||||
let mut matrix = [[0.0; CHANNELS]; CHANNELS];
|
||||
for (i, row) in matrix.iter_mut().enumerate() {
|
||||
if i != SILENCE {
|
||||
row[i] = 1.0;
|
||||
}
|
||||
}
|
||||
matrix
|
||||
}
|
||||
|
||||
// Given is an CHANNELSxCHANNELS mixing matrix whose indice are ordered by the values defined
|
||||
// in enum Channel, and the channel orders of M-channel input and N-channel output, generate a
|
||||
// mixing matrix m such that output_data(i) = Σ m[i][j] * input_data(j), for all j in [0, M),
|
||||
// where i is in [0, N) and {input/output}_data(k) means the data of the number k channel in
|
||||
// the input/output buffer.
|
||||
fn pick_coefficients(
|
||||
input_channels: &[Channel],
|
||||
output_channels: &[Channel],
|
||||
source: &[[f64; CHANNELS]; CHANNELS],
|
||||
) -> Vec<Vec<f64>> {
|
||||
let mut matrix = Vec::with_capacity(output_channels.len());
|
||||
for output_channel in output_channels {
|
||||
let output_channel_index = output_channel.clone().number();
|
||||
let mut coefficients = Vec::with_capacity(input_channels.len());
|
||||
for input_channel in input_channels {
|
||||
let input_channel_index = input_channel.clone().number();
|
||||
coefficients.push(source[output_channel_index][input_channel_index]);
|
||||
}
|
||||
matrix.push(coefficients);
|
||||
}
|
||||
matrix
|
||||
}
|
||||
|
||||
fn normalize(max_coefficients_sum: f64, mut coefficients: Vec<Vec<f64>>) -> Vec<Vec<f64>> {
|
||||
let mut max_sum: f64 = 0.0;
|
||||
for coefs in &coefficients {
|
||||
max_sum = max_sum.max(coefs.iter().sum());
|
||||
}
|
||||
if max_sum != 0.0 && max_sum > max_coefficients_sum {
|
||||
max_sum /= max_coefficients_sum;
|
||||
for coefs in &mut coefficients {
|
||||
for coef in coefs {
|
||||
*coef /= max_sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
coefficients
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MixingCoefficient {
|
||||
type Coef;
|
||||
|
||||
// TODO: These should be private.
|
||||
fn max_coefficients_sum() -> f64; // Used for normalizing.
|
||||
fn coefficient_from_f64(value: f64) -> Self::Coef;
|
||||
// Precheck if overflow occurs when converting value from Self::Coef type to Self type.
|
||||
fn would_overflow_from_coefficient_value(coefficient: &[Vec<f64>]) -> Option<bool>;
|
||||
|
||||
fn to_coefficient_value(value: Self) -> Self::Coef;
|
||||
fn from_coefficient_value(value: Self::Coef, would_overflow: Option<bool>) -> Self;
|
||||
}
|
||||
|
||||
impl MixingCoefficient for f32 {
|
||||
type Coef = f32;
|
||||
|
||||
fn max_coefficients_sum() -> f64 {
|
||||
f64::from(std::i32::MAX)
|
||||
}
|
||||
|
||||
fn coefficient_from_f64(value: f64) -> Self::Coef {
|
||||
value as Self::Coef
|
||||
}
|
||||
|
||||
fn would_overflow_from_coefficient_value(_coefficient: &[Vec<f64>]) -> Option<bool> {
|
||||
None
|
||||
}
|
||||
|
||||
fn to_coefficient_value(value: Self) -> Self::Coef {
|
||||
value
|
||||
}
|
||||
|
||||
fn from_coefficient_value(value: Self::Coef, would_overflow: Option<bool>) -> Self {
|
||||
assert!(would_overflow.is_none());
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
impl MixingCoefficient for i16 {
|
||||
type Coef = i32;
|
||||
|
||||
fn max_coefficients_sum() -> f64 {
|
||||
1.0
|
||||
}
|
||||
|
||||
fn coefficient_from_f64(value: f64) -> Self::Coef {
|
||||
(value * f64::from(1 << 15)).round() as Self::Coef
|
||||
}
|
||||
|
||||
fn would_overflow_from_coefficient_value(coefficient: &[Vec<f64>]) -> Option<bool> {
|
||||
let mut max_sum: Self::Coef = 0;
|
||||
for row in coefficient {
|
||||
let mut sum: Self::Coef = 0;
|
||||
let mut rem: f64 = 0.0;
|
||||
for coef in row {
|
||||
let target = coef * f64::from(1 << 15) + rem;
|
||||
let value = target.round() as Self::Coef;
|
||||
rem += target - target.round();
|
||||
sum += value.abs();
|
||||
}
|
||||
max_sum = max_sum.max(sum);
|
||||
}
|
||||
Some(max_sum > (1 << 15))
|
||||
}
|
||||
|
||||
fn to_coefficient_value(value: Self) -> Self::Coef {
|
||||
Self::Coef::from(value)
|
||||
}
|
||||
|
||||
fn from_coefficient_value(value: Self::Coef, would_overflow: Option<bool>) -> Self {
|
||||
use std::convert::TryFrom;
|
||||
let would_overflow = would_overflow.expect("would_overflow must have value for i16 type");
|
||||
let mut converted = (value + (1 << 14)) >> 15;
|
||||
// clip the signed integer value into the -32768,32767 range.
|
||||
if would_overflow && ((converted + 0x8000) & !0xFFFF != 0) {
|
||||
converted = (converted >> 31) ^ 0x7FFF;
|
||||
}
|
||||
Self::try_from(converted).expect("Cannot convert coefficient from i32 to i16")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn test_create<T>()
|
||||
where
|
||||
T: MixingCoefficient,
|
||||
T::Coef: Copy + Debug,
|
||||
{
|
||||
let input_channels = vec![
|
||||
Channel::Silence,
|
||||
Channel::FrontRight,
|
||||
Channel::FrontLeft,
|
||||
Channel::Silence,
|
||||
Channel::FrontCenter,
|
||||
Channel::BackCenter,
|
||||
Channel::LowFrequency,
|
||||
];
|
||||
let output_channels = vec![Channel::Silence, Channel::FrontRight, Channel::FrontLeft];
|
||||
let coefficient = Coefficient::<T>::create(input_channels.clone(), output_channels.clone());
|
||||
println!(
|
||||
"{:?} = {:?} * {:?}",
|
||||
output_channels, coefficient.matrix, input_channels
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_f32() {
|
||||
test_create::<f32>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_i16() {
|
||||
test_create::<i16>();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn test_create_with_duplicate_input_channels<T>()
|
||||
where
|
||||
T: MixingCoefficient,
|
||||
T::Coef: Copy,
|
||||
{
|
||||
let input_channels = vec![
|
||||
Channel::FrontLeft,
|
||||
Channel::Silence,
|
||||
Channel::FrontLeft,
|
||||
Channel::FrontCenter,
|
||||
];
|
||||
let output_channels = vec![
|
||||
Channel::Silence,
|
||||
Channel::FrontRight,
|
||||
Channel::FrontLeft,
|
||||
Channel::Silence,
|
||||
Channel::FrontCenter,
|
||||
Channel::BackCenter,
|
||||
];
|
||||
let _ = Coefficient::<T>::create(input_channels, output_channels);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_create_with_duplicate_input_channels_f32() {
|
||||
test_create_with_duplicate_input_channels::<f32>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_create_with_duplicate_input_channels_i16() {
|
||||
test_create_with_duplicate_input_channels::<i16>()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn test_create_with_duplicate_output_channels<T>()
|
||||
where
|
||||
T: MixingCoefficient,
|
||||
T::Coef: Copy,
|
||||
{
|
||||
let input_channels = vec![
|
||||
Channel::FrontLeft,
|
||||
Channel::Silence,
|
||||
Channel::FrontRight,
|
||||
Channel::FrontCenter,
|
||||
];
|
||||
let output_channels = vec![
|
||||
Channel::Silence,
|
||||
Channel::FrontRight,
|
||||
Channel::FrontLeft,
|
||||
Channel::FrontCenter,
|
||||
Channel::FrontCenter,
|
||||
Channel::BackCenter,
|
||||
];
|
||||
let _ = Coefficient::<T>::create(input_channels, output_channels);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_create_with_duplicate_output_channels_f32() {
|
||||
test_create_with_duplicate_output_channels::<f32>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_create_with_duplicate_output_channels_i16() {
|
||||
test_create_with_duplicate_output_channels::<i16>()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn test_get_redirect_matrix<T>()
|
||||
where
|
||||
T: MixingCoefficient,
|
||||
T::Coef: Copy + Debug + PartialEq,
|
||||
{
|
||||
// Create a matrix that only redirect the channels from input side to output side,
|
||||
// without mixing input audio data to output audio data.
|
||||
fn compute_redirect_matrix<T>(
|
||||
input_channels: &[Channel],
|
||||
output_channels: &[Channel],
|
||||
) -> Vec<Vec<T::Coef>>
|
||||
where
|
||||
T: MixingCoefficient,
|
||||
{
|
||||
let mut matrix = Vec::with_capacity(output_channels.len());
|
||||
for output_channel in output_channels {
|
||||
let mut row = Vec::with_capacity(input_channels.len());
|
||||
for input_channel in input_channels {
|
||||
row.push(
|
||||
if input_channel != output_channel
|
||||
|| input_channel == &Channel::Silence
|
||||
|| output_channel == &Channel::Silence
|
||||
{
|
||||
0.0
|
||||
} else {
|
||||
1.0
|
||||
},
|
||||
);
|
||||
}
|
||||
matrix.push(row);
|
||||
}
|
||||
|
||||
// Convert the type of the coefficients from f64 to T::Coef.
|
||||
matrix
|
||||
.into_iter()
|
||||
.map(|row| row.into_iter().map(T::coefficient_from_f64).collect())
|
||||
.collect()
|
||||
}
|
||||
|
||||
let input_channels = vec![
|
||||
Channel::FrontLeft,
|
||||
Channel::Silence,
|
||||
Channel::FrontRight,
|
||||
Channel::FrontCenter,
|
||||
];
|
||||
let output_channels = vec![
|
||||
Channel::Silence,
|
||||
Channel::FrontLeft,
|
||||
Channel::Silence,
|
||||
Channel::FrontCenter,
|
||||
Channel::BackCenter,
|
||||
];
|
||||
|
||||
// Get a redirect matrix since the output layout is asymmetric.
|
||||
let coefficient = Coefficient::<T>::create(input_channels.clone(), output_channels.clone());
|
||||
|
||||
let expected = compute_redirect_matrix::<T>(&input_channels, &output_channels);
|
||||
assert_eq!(coefficient.matrix, expected);
|
||||
|
||||
println!(
|
||||
"{:?} = {:?} * {:?}",
|
||||
output_channels, coefficient.matrix, input_channels
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_redirect_matrix_f32() {
|
||||
test_get_redirect_matrix::<f32>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_redirect_matrix_i16() {
|
||||
test_get_redirect_matrix::<i16>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize() {
|
||||
let m = vec![
|
||||
vec![1.0_f64, 2.0_f64, 3.0_f64],
|
||||
vec![4.0_f64, 6.0_f64, 10.0_f64],
|
||||
];
|
||||
let n = Coefficient::<f32>::normalize(10.0, m.clone());
|
||||
println!("m: {:?}, n: {:?}", m, n);
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
|
||||
mod channel;
|
||||
mod coefficient;
|
||||
|
||||
// Export Channel outside.
|
||||
pub use channel::Channel;
|
||||
use coefficient::{Coefficient, MixingCoefficient};
|
||||
|
||||
use std::default::Default;
|
||||
use std::fmt::Debug;
|
||||
use std::ops::{AddAssign, Mul};
|
||||
|
||||
// A mixer mixing M-channel input data to N-channel output data.
|
||||
// T::Coef is an associated type defined in MixingCoefficient, which indicates the type of the
|
||||
// mixing coefficient that would be used for type T. When T is f32, the T::Coef is f32. When T
|
||||
// is i16, the T::Coef is i32. When mixing data, a temporary variable with type T::Coef would be
|
||||
// created to hold the mixing result. Since the type of input and output audio data is T,
|
||||
// the methods provided from MixingCoefficient trait would be used to convert the value between
|
||||
// type T and T::Coef.
|
||||
#[derive(Debug)]
|
||||
pub struct Mixer<T>
|
||||
where
|
||||
T: Copy + Debug + MixingCoefficient,
|
||||
T::Coef: AddAssign + Copy + Debug + Default + Mul<T::Coef, Output = T::Coef>,
|
||||
{
|
||||
coefficient: Coefficient<T>,
|
||||
}
|
||||
|
||||
impl<T> Mixer<T>
|
||||
where
|
||||
T: Copy + Debug + MixingCoefficient,
|
||||
T::Coef: AddAssign + Copy + Debug + Default + Mul<T::Coef, Output = T::Coef>,
|
||||
{
|
||||
pub fn new(input_channels: Vec<Channel>, output_channels: Vec<Channel>) -> Self {
|
||||
Self {
|
||||
coefficient: Coefficient::create(input_channels, output_channels),
|
||||
}
|
||||
}
|
||||
|
||||
// To mix M-channel audio input data to N-channel output data, the data in output-channel i
|
||||
// is the sum of product of data in input-channel j and the coefficient for mixing from
|
||||
// input-channel j to output-channel i, for all j in M channels. That is,
|
||||
// output_data(i) = Σ coefficient(j, i) * input_data(j), for all j in [0, M),
|
||||
// where i is in [0, N) and coefficient is a function returning mixing coefficient from
|
||||
// input channel j to output channel i.
|
||||
pub fn mix(&self, input_buffer: &[T], output_buffer: &mut [T]) {
|
||||
assert_eq!(
|
||||
input_buffer.len(),
|
||||
self.input_channels().len(),
|
||||
"input slice must have the same size as the input channel's one."
|
||||
);
|
||||
assert_eq!(
|
||||
output_buffer.len(),
|
||||
self.output_channels().len(),
|
||||
"output slice must have the same size as the output channel's one."
|
||||
);
|
||||
for (i, output) in output_buffer.iter_mut().enumerate() {
|
||||
// T must implement Default that returns a zero value from default().
|
||||
let mut value = T::Coef::default(); // Create a zero value.
|
||||
for (j, input) in input_buffer.iter().enumerate() {
|
||||
// T::Coef needs to implement `AddAssign` and `Mul` to make `+=` and `*` work.
|
||||
// T needs to implement `Copy` so `*input` can be copied.
|
||||
value += self.coefficient.get(j, i) * T::to_coefficient_value(*input);
|
||||
}
|
||||
*output = T::from_coefficient_value(
|
||||
value,
|
||||
self.coefficient.would_overflow_from_coefficient_value(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn input_channels(&self) -> &[Channel] {
|
||||
&self.coefficient.input_channels()
|
||||
}
|
||||
|
||||
pub fn output_channels(&self) -> &[Channel] {
|
||||
&self.coefficient.output_channels()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
extern crate mixer;
|
||||
use mixer::{Channel, Mixer};
|
||||
|
||||
fn main() {
|
||||
// f32
|
||||
let input_channels = vec![
|
||||
Channel::FrontLeft,
|
||||
Channel::Silence,
|
||||
Channel::FrontRight,
|
||||
Channel::FrontCenter,
|
||||
];
|
||||
let output_channels = vec![Channel::FrontLeft, Channel::FrontRight];
|
||||
|
||||
let mut input_buffer = vec![0.0; input_channels.len()];
|
||||
for (i, data) in input_buffer.iter_mut().enumerate() {
|
||||
*data = (i + 1) as f32;
|
||||
}
|
||||
let mut output_buffer = vec![0.0; output_channels.len()];
|
||||
|
||||
let mixer = Mixer::new(input_channels, output_channels);
|
||||
|
||||
mixer.mix(&input_buffer.as_slice(), &mut output_buffer.as_mut_slice());
|
||||
println!("{:?} is mixed to {:?}", input_buffer, output_buffer);
|
||||
|
||||
// i16
|
||||
let input_channels = vec![
|
||||
Channel::FrontLeft,
|
||||
Channel::Silence,
|
||||
Channel::FrontRight,
|
||||
Channel::FrontCenter,
|
||||
Channel::BackLeft,
|
||||
Channel::SideRight,
|
||||
Channel::LowFrequency,
|
||||
Channel::SideLeft,
|
||||
Channel::BackCenter,
|
||||
Channel::BackRight,
|
||||
];
|
||||
let output_channels = vec![Channel::Silence, Channel::FrontRight, Channel::FrontLeft];
|
||||
|
||||
let mut input_buffer = vec![0; input_channels.len()];
|
||||
for (i, data) in input_buffer.iter_mut().enumerate() {
|
||||
*data = (i + 0x7FFE) as i16;
|
||||
}
|
||||
let mut output_buffer = vec![0; output_channels.len()];
|
||||
|
||||
let mixer = Mixer::new(input_channels, output_channels);
|
||||
|
||||
mixer.mix(&input_buffer.as_slice(), &mut output_buffer.as_mut_slice());
|
||||
println!("{:?} is mixed to {:?}", input_buffer, output_buffer);
|
||||
}
|
|
@ -19,7 +19,7 @@ static_prefs = { path = "../../../../modules/libpref/init/static_prefs" }
|
|||
profiler_helper = { path = "../../../../tools/profiler/rust-helper", optional = true }
|
||||
mozurl = { path = "../../../../netwerk/base/mozurl" }
|
||||
webrender_bindings = { path = "../../../../gfx/webrender_bindings", optional = true }
|
||||
cubeb-coreaudio = { git = "https://github.com/ChunMinChang/cubeb-coreaudio-rs", rev = "868d847c2e95096b6eec629dfed77ef363363ecb", optional = true }
|
||||
cubeb-coreaudio = { git = "https://github.com/ChunMinChang/cubeb-coreaudio-rs", rev = "acb90e9bf36e6e035ac6bbe51efa0a8825b5b6be", optional = true }
|
||||
cubeb-pulse = { git = "https://github.com/djg/cubeb-pulse-rs", rev="8069f8f4189982e0b38fa6dc8993dd4fab41f728", optional = true, features=["pulse-dlopen"] }
|
||||
cubeb-sys = { version = "0.6", optional = true, features=["gecko-in-tree"] }
|
||||
encoding_glue = { path = "../../../../intl/encoding_glue" }
|
||||
|
|
Загрузка…
Ссылка в новой задаче