Rust implementation of Cubeb on the MacOS platform.
- Rewrite the C code into Rust on a line-by-line basis
 - Create some tests for later refactoring
 - Defuse the 
OwnedCriticalSection. See proposal here. 
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.
The plain translation version from the C code is on plain-translation-from-c branch. The working draft version is on trailblazer branch. Both branches can pass all the tests on tryserver for firefox. However, we are replacing our custom mutex, which is translated from C version directly, by standard Rust mutex. The code is on ocs-disposal branch and ocs-disposal-stm branch.
The project can also be tracked on bugzilla 1530715. The instructios to integrate this project into firefox gecko can be found there. You can also find the formal patches and the reviews there. The easiest way to integrate this project into firefox gecko is to apply all the patches.
Now all the custom mutexes in cubeb context is replaced. The code is on ocs-disposal branch.
The replacement for the custom mutexes in cubeb stream is still a work in process. The code is in ocs-disposal-stm branch.
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.
The system default device will be changed during our tests. All the available devices will take turns being the system default device. However, after finishing the tests, the default device will be set to the original one. The sounds in the tests should be able to continue whatever the system default device is.
We implement APIs simulating plugging or unplugging a device by adding or removing an aggregate device programmatically. It's used to verify our callbacks for minitoring the system devices work.
- Output devices switching
$ cargo test test_switch_output_device -- --ignored --nocapture- Enter 
sto switch output devices - Enter 
qto 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.
 
 
See TO-DOs
- 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_sectiondoes in C version, but it doens't has equivalent API forassert_current_thread_owns. - We implement a 
OwnedCriticalSectionaroundpthread_mutex_tlike 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 theOwnedCriticalSectionused in the test isn't be dropped in a correct order, then the test will get a crash inOwnedCriticalSection. 
 - A dummy mutex like 
 - Atomic:
- We need atomic type around 
f32but there is no this type in the stardard Rust - Using atomic-rs to do this.
 
 - We need atomic type around 
 - Unworkable API: 
dispatch_asyncanddispatch_sync- Replace 
dispatch_asyncbyasync_dispatch, which is based ondispatch_async_f. - Replace 
dispatch_syncbysync_dispatch, which is based ondispatch_sync_f. 
 - Replace 
 - Mutex Borrowing Issues
- We have a 
mutexinAudioUnitContext, and we have a reference toAudioUnitContextinAudioUnitStream. To sync what we do in C version, we need to lock themutexinAudioUnitContextthen pass a reference toAudioUnitContexttoAudioUnitStream::new(...). - To lock the 
mutexinAudioUnitContext, we callAutoLock::new(&mut AudioUnitContext.mutex). That is, we will borrow a reference toAudioUnitContextas a mutable first then borrow it again. It's forbidden in Rust. - Some workarounds are
- Replace 
AutoLockby callingmutex.lock()andmutex.unlock()explicitly. - Save the pointer to 
mutexfirst, then callAutoLock::new(unsafe { &mut (*mutex_ptr) }). - Cast immutable reference to a 
*constthen to a*mut:pthread_mutex_lock(&self.mutex as *const pthread_mutex_t as *mut pthread_mutex_t) 
 - Replace 
 
 - We have a 
 - No guarantee on 
audiounit_set_channel_layout- This call doesn't work all the times
 - Returned 
NO_ERRdoesn't guarantee the layout is set to the one we want - The layouts on some devices won't be changed even no errors are returned, e.g., we can set stereo layout to a 4-channels aggregate device with QUAD layout (created by Audio MIDI Setup) without any error. However, the layout of this 4-channels aggregate device is still QUAD after setting it without error
 - Another weird thing is that we will get a 
kAudioUnitErr_InvalidPropertyValueif we set the layout to QUAD. It's the same layout as its original one but it cannot be set! 
 
- 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(
OwnedCriticalSectionactually) - 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_devicewithtest_add_device_listeners_dont_affect_other_scopes_with_*at the same timeaudiounit_create_blank_aggregate_devicewill fire the callbacks intest_add_device_listeners_dont_affect_other_scopes_with_*
 - Fail to run 
test_configure_{input, output}_with_zero_latency_framesandtest_configure_{input, output}at the same time.- The APIs depending on 
audiounit_set_buffer_sizecannot be called in parallelkAudioDevicePropertyBufferFrameSizecannot 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_changedcausesEXC_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 
AudioUnitContextdirectly instead of calling them viaOPSfor now. 
 - A panic in 
 TestDeviceSwitchercannot 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.
 TestDeviceSwitcherwill 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 
TestDeviceSwitcheris still working, it will set a destroyed device as the default device - See details in device_change.rs