16 KiB
16 KiB
cubeb-coreaudio-rs
Implementation of MacOS Audio backend in CoreAudio framework for Cubeb written in Rust.
Currently it can only be built by rust-nightly since we use nightly-only atomic types(AtomicU32
and AtomicU64
).
Current Goals
- Translate C code line by line into Rust (so the coding style is almost same as C)
- Create tests for later refactoring
Branches
- trailblazer: Draft Rust code without being reviewed. Commits are scribbled.
- release: The offical version. All the commits are reviewed.
- dev: All the commits are cherry-picked from trailblazer branch. This branch is used to create pull-requests to release branch.
Status
- 🥚 : Not implemented.
- 🐣 : Work in progress. May be implemented partially or blocked by dependent APIs.
- 🐥 : Implemented.
- 🐓 : Already ride the trains.
Cubeb APIs (Public APIs)
- 🥚 : 0/20 (0%)
- 🐣 : 2/20 (10%)
- 🐥 : 18/20 (90%)
Cubeb APIs | status |
---|---|
cubub_init | 🐥 |
cubub_get_backend_id | 🐥 |
cubub_get_max_channel_count | 🐥 |
cubub_get_min_latency | 🐥 |
cubub_get_preferred_sample_rate | 🐥 |
cubub_enumerate_devices | 🐥 |
cubeb_device_collection_destroy | 🐥 |
cubeb_stream_init | 🐣 |
cubeb_stream_destroy | 🐣 |
cubeb_stream_start | 🐥 |
cubeb_stream_stop | 🐥 |
cubeb_reset_default_device | 🐥 |
cubeb_stream_get_position | 🐥 |
cubeb_stream_get_latency | 🐥 |
cubeb_stream_set_volume | 🐥 |
cubeb_stream_set_panning | 🐥 |
cubeb_stream_get_current_device | 🐥 |
cubeb_stream_device_destroy | 🐥 |
cubeb_stream_register_device_changed_callback | 🐥 |
cubub_register_device_collection_changed | 🐥 |
Interanl APIs
- 🥚 : 16/75 (21.3%)
- 🐣 : 7/75 (9.3%)
- 🐥 : 52/75 (69.3%)
Interanl AudioUnit APIs | status |
---|---|
make_sized_audio_channel_layout | 🥚 |
to_string | 🐥 |
has_input | 🐥 |
has_output | 🐥 |
channel_label_to_cubeb_channel | 🥚 |
cubeb_channel_to_channel_label | 🥚 |
audiounit_increment_active_streams | 🐥 |
audiounit_decrement_active_streams | 🐥 |
audiounit_active_streams | 🐥 |
audiounit_set_global_latency | 🐥 |
audiounit_make_silent | 🥚 |
audiounit_render_input | 🥚 |
audiounit_input_callback | 🐣 |
audiounit_mix_output_buffer | 🥚 |
minimum_resampling_input_frames | 🥚 |
audiounit_output_callback | 🥚 |
audiounit_set_device_info | 🐥 |
audiounit_reinit_stream | 🥚 |
audiounit_reinit_stream_async | 🐣 |
event_addr_to_string | 🐥 |
audiounit_property_listener_callback | 🐥 |
audiounit_add_listener | 🐥 |
audiounit_remove_listener | 🐥 |
audiounit_install_device_changed_callback | 🐥 |
audiounit_install_system_changed_callback | 🐥 |
audiounit_uninstall_device_changed_callback | 🐥 |
audiounit_uninstall_system_changed_callback | 🐥 |
audiounit_get_acceptable_latency_range | 🐥 |
audiounit_get_default_device_id | 🐥 |
audiounit_convert_channel_layout | 🥚 |
audiounit_get_preferred_channel_layout | 🥚 |
audiounit_get_current_channel_layout | 🥚 |
audiounit_destroy | 🥚 |
audio_stream_desc_init | 🐥 |
audiounit_init_mixer | 🥚 |
audiounit_set_channel_layout | 🥚 |
audiounit_layout_init | 🥚 |
audiounit_get_sub_devices | 🐥 |
audiounit_create_blank_aggregate_device | 🐥 |
get_device_name | 🐥 |
audiounit_set_aggregate_sub_device_list | 🐥 |
audiounit_set_master_aggregate_device | 🐥 |
audiounit_activate_clock_drift_compensation | 🐥 |
audiounit_workaround_for_airpod | 🐥 |
audiounit_create_aggregate_device | 🐥 |
audiounit_destroy_aggregate_device | 🐥 |
audiounit_new_unit_instance | 🐥 |
audiounit_enable_unit_scope | 🐥 |
audiounit_create_unit | 🐥 |
audiounit_init_input_linear_buffer | 🐥 |
audiounit_clamp_latency | 🐥 |
buffer_size_changed_callback | 🐥 |
audiounit_set_buffer_size | 🐥 |
audiounit_configure_input | 🐥 |
audiounit_configure_output | 🐣 |
audiounit_setup_stream | 🐣 |
audiounit_close_stream | 🐣 |
audiounit_stream_destroy_internal | 🐣 |
audiounit_stream_destroy | 🐣 |
audiounit_stream_start_internal | 🐥 |
audiounit_stream_stop_internal | 🐥 |
audiounit_stream_get_volume | 🐥 |
convert_uint32_into_string | 🐥 |
audiounit_get_default_device_datasource | 🐥 |
audiounit_get_default_device_name | 🐥 |
audiounit_strref_to_cstr_utf8 | 🐥 |
audiounit_get_channel_count | 🐥 |
audiounit_get_available_samplerate | 🐥 |
audiounit_get_device_presentation_latency | 🐥 |
audiounit_create_device_from_hwdev | 🐥 |
is_aggregate_device | 🐥 |
audiounit_get_devices_of_type | 🐥 |
audiounit_collection_changed_callback | 🐥 |
audiounit_add_device_listener | 🐥 |
audiounit_remove_device_listener | 🐥 |
TODO
- cubeb-rs
- Implement
to_owned
inStreamParamsRef
- Implement
- Integration Tests
- Add a test-only API to change the default audio devices
- Use above API to test the device-changed callback
- Create Rust bindings/modules for cubeb-mixer and cubeb-resampler
- Move issues below to github issues.
- Test aggregate devices
- Test with AirPods
- Test for stream operations
- Clean up the tests. Merge the duplicated pieces in to a function.
- Find a way to catch memory leaks
- Try Instrument on OSX
- Some of bugs are found when adding tests. Search FIXIT to find them.
- Maybe it's better to move all
fn some_func(stm: &AudioUnitStream, ...)
functions intoimpl AudioUnitStream
. - Add comments for APIs in
utils
- 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_ops_context_register_device_collection_changed_twice_*
on my MacBook Air.- 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
- Fail to run
test_configure_input_with_zero_latency_frames
andtest_configure_input
at the same time.audiounit_set_buffer_size
cannot be called in parallel- We should not set
kAudioDevicePropertyBufferFrameSize
in parallel when another stream using the same device with smaller buffer size is active. See here for reference. - Buffer frame size within same device may be overwritten (no matter the AudioUnits are different or not) ?
- Implement
auto_array_wrapper
andauto_array
(by trait and generic I think)
Issues
- 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
that will get a panic, if theOwnedCriticalSection
used in the test isn't be dropped before where the code get a panic, then the test might get a crash inOwnedCriticalSection
rather than the line having a panic. One example istest_stream_get_panic_before_releasing_mutex
. 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 workaround here is to- Cast the pointer to a
cubeb
context into ausize
value - Pass that value to threads. The value is actually be copied into the code-block that will be run on another thread
- When the task on another thread is run, the value is casted to a pointer to a
cubeb
context
- 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
- 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(
- APIs that cannot be called in parallel
- 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 (no matter the AudioUnits are different or not) ?
- The APIs depending on