7da41f97e5 | ||
---|---|---|
coreaudio-sys-utils | ||
src | ||
.editorconfig | ||
.gitignore | ||
.travis.yml | ||
Cargo.toml | ||
LICENSE | ||
README.md | ||
mutex-disposal.md | ||
run_tests.sh |
README.md
cubeb-coreaudio-rs
Rust implementation of Cubeb on the MacOS platform.
Current Goals
- Rewrite the C code into Rust on a line-by-line basis
- Create some tests for later refactoring
- Defuse the
OwnedCriticalSection
. See proposal here.
Status
All the lines in cubeb_audiounit.cpp are translated.
By applying the patch to integrate within Cubeb, it can pass all the tests under cubeb/test and it's able to switch devices when the stream is working (we are unable to test this automatically yet).
Now the draft version can pass all the tests within gecko on mozilla try-server. The project can be tracked on bugzilla 1530715.
The plain translation version from the C code is in plain-translation-from-c branch.
Test
Please run sh run_tests.sh
.
Some tests cannot be run in parallel. They may operate the same device at the same time, or indirectly fire some system events that are listened by some tests.
The tests that may affect others are marked #[ignore]
.
They will be run by cargo test ... -- --ignored ...
after finishing normal tests.
Most of the tests are executed in run_tests.sh
.
Only those tests commented with FIXIT are left.
Manual Test
- Output devices switching
$ cargo test test_switch_output_device -- --ignored --nocapture
- Enter
s
to switch output devices - Enter
q
to finish test
- Device change events listener
$ cargo test test_add_then_remove_listeners -- --ignored --nocapture
- Plug/Unplug devices or switch input/output devices to see events log.
- Device collection change
cargo test test_device_collection_change -- --ignored --nocapture
- Plug/Unplug devices to see events log.
TODO
- Maybe it's better to move all
fn some_func(stm: &AudioUnitStream, ...)
functions intoimpl AudioUnitStream
to avoid useless references toAudioUnitStream
. - Remove
#[allow(non_camel_case_types)]
,#![allow(unused_assignments)]
,#![allow(unused_must_use)]
and apply rust coding styles - Use
Atomic{I64, U32, U64}
instead ofAtomic<{i64, u32, u64}>
, once they are stable. - Tests
- Rewrite some tests under cubeb/test/* in Rust as part of the integration tests
- Add tests for capturing/recording, output, duplex streams
- Tests cleaned up: Only tests under aggregate_device.rs left now.
- Rewrite some tests under cubeb/test/* in Rust as part of the integration tests
- Some of bugs are found when adding tests. Search FIXIT to find them.
- cubeb-rs
- Implement
to_owned
inStreamParamsRef
- Check the passed parameters like what cubeb.c does!
- Check the input
StreamParams
parameters properly, or we will set a invalid format intoAudioUnit
. In fact, we should check all the parameters properly so we can make sure we don't mess up the streams/devices settings!
- Check the input
- Implement
- Find a efficient way to catch memory leaks
- Instrument on OSX
Issues
- See discussion here
- Mutex: Find a replacement for
owned_critical_section
- A dummy mutex like
Mutex<()>
should work (seetest_dummy_mutex_multithread
) as whatowned_critical_section
does in C version, but it doens't has equivalent API forassert_current_thread_owns
. - We implement a
OwnedCriticalSection
aroundpthread_mutex_t
like what we do in C version for now. - It's hard to debug with the variables using
OwnedCriticalSection
. Within a test with a variable usingOwnedCriticalSection
, if theOwnedCriticalSection
used in the test isn't be dropped in a correct order, then the test will get a crash inOwnedCriticalSection
. The examples aretest_stream_drop_mutex_(in)correct
. The tests must be created very carefully.
- A dummy mutex like
- Atomic:
- The stable atomic types only support
bool
,usize
,isize
, andptr
, but we needu64
,i64
, andf32
. - Using atomic-rs instead.
- Rust-Nightly supports
AtomicU32
andAtomicU64
so we use that.
- The stable atomic types only support
- Unworkable API:
dispatch_async
anddispatch_sync
- The second parameter of
dispatch_async
anddispatch_sync
isdispatch_block_t
, which is defined bytypedef void (^dispatch_block_t)(void)
. - The caret symbol
^
defines a block. - The block is a lambda expression-like syntax to create closures. (See Apple's document: Working with Blocks)
- Not sure if Rust can work with it. Rust has its own closure.
- For now, we implement an API
async_dispatch
andsync_dispatch
to replacedispatch_async
anddispatch_sync
(prototype on gist.)async_dispatch
is based ondispatch_async_f
.sync_dispatch
is based ondispatch_sync_f
.async_dispatch
andsync_dispatch
take Rust closures, instead of Apple's block, as one of their parameters.- The Rust closure (it's actually a struct) will be
box
ed, which means the closure will be moved into heap, so the closure cannot be optimized as inline code. (Need to find a way to optimize it?) - Since the closure will be run on an asynchronous thread, we need to move the closure to heap to make sure it's alive and then it will be destroyed after the task of the closure is done.
- The second parameter of
- Borrowing Issues
- Pass
AudioUnitContext
across threads. In C version, we pass the pointer tocubeb
context across threads, but it's forbidden in Rust. A workarounds are- Cast the pointer to a
usize
value so the value can be copied to another thread. - Or Unsafely implements
Send
andSync
traits so the compiler ignores the checks.
- Cast the pointer to a
- We have a
mutex
inAudioUnitContext
, and we have a reference toAudioUnitContext
inAudioUnitStream
. To sync what we do in C version, we need to lock themutex
inAudioUnitContext
then pass a reference toAudioUnitContext
toAudioUnitStream::new(...)
. To lock themutex
inAudioUnitContext
, we callAutoLock::new(&mut AudioUnitContext.mutex)
. That is, we will borrow a reference toAudioUnitContext
as a mutable first then borrow it again. It's forbidden in Rust. Some workarounds are- Replace
AutoLock
by callingmutex.lock()
andmutex.unlock()
explicitly. - Save the pointer to
mutex
first, then callAutoLock::new(unsafe { &mut (*mutex_ptr) })
. - Cast immutable reference to a
*const
then to a*mut
:pthread_mutex_lock(&self.mutex as *const pthread_mutex_t as *mut pthread_mutex_t)
- Replace
- Pass
Test issues
- Complexity of creating unit tests
- We have lots of dependent APIs, so it's hard to test one API only, specially for those APIs using mutex(
OwnedCriticalSection
actually) - It's better to split them into several APIs so it's easier to test them
- We have lots of dependent APIs, so it's hard to test one API only, specially for those APIs using mutex(
- Fail to run
test_create_blank_aggregate_device
withtest_add_device_listeners_dont_affect_other_scopes_with_*
at the same time- I guess
audiounit_create_blank_aggregate_device
will fire the callbacks intest_add_device_listeners_dont_affect_other_scopes_with_*
- I guess
- Fail to run
test_configure_{input, output}_with_zero_latency_frames
andtest_configure_{input, output}
at the same time.- The APIs depending on
audiounit_set_buffer_size
cannot be called in parallelkAudioDevicePropertyBufferFrameSize
cannot be set when another stream using the same device with smaller buffer size is active. See here for reference.- The buffer frame size within same device may be overwritten (For those AudioUnits using same device ?)
- The APIs depending on
- Fail to run
test_ops_context_register_device_collection_changed_twice_*
on my MacBook Air and Travis CI.- A panic in
capi_register_device_collection_changed
causesEXC_BAD_INSTRUCTION
. - Works fine if replacing
register_device_collection_changed: Option<unsafe extern "C" fn(..,) -> c_int>
toregister_device_collection_changed: unsafe extern "C" fn(..,) -> c_int
- Test them in
AudioUnitContext
directly instead of calling them viaOPS
for now.
- A panic in
TestDeviceSwitcher
cannot work when there is an alive full-duplex stream- An aggregate device will be created for a duplex stream when its input and output devices are different.
TestDeviceSwitcher
will cached the available devices, upon it's created, as the candidates for default device- Hence the created aggregate device may be cached in
TestDeviceSwitcher
- If the aggregate device is destroyed (when the destroying the duplex stream created it) but the
TestDeviceSwitcher
is still working, it will set a destroyed device as the default device - See details in device_change.rs