summaryrefslogtreecommitdiffstats
path: root/third_party/rust/cubeb-sys
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/rust/cubeb-sys
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/cubeb-sys')
-rw-r--r--third_party/rust/cubeb-sys/.cargo-checksum.json1
-rw-r--r--third_party/rust/cubeb-sys/Cargo.toml33
-rw-r--r--third_party/rust/cubeb-sys/LICENSE13
-rw-r--r--third_party/rust/cubeb-sys/build.rs103
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/AUTHORS16
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/CMakeLists.txt446
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/Config.cmake.in4
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/INSTALL.md45
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/LICENSE13
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/README.md7
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/cmake/compile_tests/oss_is_v4.c10
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/CMakeLists.txt51
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/LICENSE22
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/README.md73
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/FindASan.cmake59
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/FindMSan.cmake57
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/FindSanitizers.cmake94
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/FindTSan.cmake65
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/FindUBSan.cmake46
-rwxr-xr-xthird_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/asan-wrapper55
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/sanitize-helpers.cmake172
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/tests/CMakeLists.txt67
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/tests/asan_test.cpp40
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/tests/shortest.ext.test.cpp40
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/cmake/toolchain-cross-mingw.cmake14
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/cubeb.supp36
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/docs/Doxyfile.in12
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/include/cubeb/cubeb.h777
-rwxr-xr-xthird_party/rust/cubeb-sys/libcubeb/scan-build-install.sh15
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/android/audiotrack_definitions.h85
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/android/cubeb-output-latency.h79
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/android/cubeb_media_library.h70
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/android/sles_definitions.h104
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb-internal.h79
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb-jni-instances.h30
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb-jni.cpp82
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb-jni.h21
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb-speex-resampler.h1
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb.c756
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_aaudio.cpp1798
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_alsa.c1493
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_android.h17
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_array_queue.h99
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_assert.h27
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_audiotrack.c475
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_audiounit.cpp3688
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_jack.cpp1183
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_kai.c372
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_log.cpp233
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_log.h74
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_mixer.cpp621
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_mixer.h36
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_opensl.cpp1955
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_oss.c1356
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_osx_run_loop.cpp34
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_osx_run_loop.h23
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_pulse.c1712
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_resampler.cpp373
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_resampler.h91
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_resampler_internal.h591
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_ring_array.h142
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_ringbuffer.h468
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_sndio.c687
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_strings.c154
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_strings.h47
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_sun.c740
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_tracing.h23
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_triple_buffer.h80
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_utils.cpp25
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_utils.h318
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_utils_unix.h88
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_utils_win.h67
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_wasapi.cpp3585
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/src/cubeb_winmm.c1213
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/subprojects/speex/arch.h235
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/subprojects/speex/fixed_generic.h110
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/subprojects/speex/resample.c1240
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/subprojects/speex/resample_neon.h201
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/subprojects/speex/resample_sse.h128
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/subprojects/speex/speex_config_types.h10
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/subprojects/speex/speex_resampler.h343
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/subprojects/speex/stack_alloc.h115
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/test/README.md13
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/test/common.h180
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/test/test_audio.cpp253
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/test/test_callback_ret.cpp254
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/test/test_device_changed_callback.cpp128
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/test/test_devices.cpp264
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/test/test_duplex.cpp376
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/test/test_latency.cpp43
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/test/test_logging.cpp196
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/test/test_loopback.cpp679
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/test/test_overload_callback.cpp108
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/test/test_record.cpp126
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/test/test_resampler.cpp1164
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/test/test_ring_array.cpp72
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/test/test_ring_buffer.cpp229
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/test/test_sanity.cpp721
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/test/test_tone.cpp130
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/test/test_triple_buffer.cpp67
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/test/test_utils.cpp70
-rw-r--r--third_party/rust/cubeb-sys/libcubeb/tools/cubeb-test.cpp660
-rw-r--r--third_party/rust/cubeb-sys/src/callbacks.rs23
-rw-r--r--third_party/rust/cubeb-sys/src/channel.rs72
-rw-r--r--third_party/rust/cubeb-sys/src/context.rs45
-rw-r--r--third_party/rust/cubeb-sys/src/device.rs174
-rw-r--r--third_party/rust/cubeb-sys/src/error.rs13
-rw-r--r--third_party/rust/cubeb-sys/src/format.rs22
-rw-r--r--third_party/rust/cubeb-sys/src/internal.rs29
-rw-r--r--third_party/rust/cubeb-sys/src/lib.rs31
-rw-r--r--third_party/rust/cubeb-sys/src/log.rs29
-rw-r--r--third_party/rust/cubeb-sys/src/macros.rs27
-rw-r--r--third_party/rust/cubeb-sys/src/mixer.rs31
-rw-r--r--third_party/rust/cubeb-sys/src/resampler.rs49
-rw-r--r--third_party/rust/cubeb-sys/src/stream.rs100
115 files changed, 36341 insertions, 0 deletions
diff --git a/third_party/rust/cubeb-sys/.cargo-checksum.json b/third_party/rust/cubeb-sys/.cargo-checksum.json
new file mode 100644
index 0000000000..4beef82df8
--- /dev/null
+++ b/third_party/rust/cubeb-sys/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"2c8c46a775578cfb496f14a40131753dcda6228a6692257886ca0092f6b1ed5d","LICENSE":"8c044baa5d883274736eeece0b955249076c2697b826e576fce59496235b2cf5","build.rs":"4572cbb7b2bb0ac41330dc1535dbc6685d38ea4f485029ee932639e2feb94d7b","libcubeb/AUTHORS":"829e45d138c7c8827799f302806fa4be8cd8bd4bad70a2fe26c3a27a5cf36948","libcubeb/CMakeLists.txt":"7b1a53117f2520471ef4293f9b8107e04f7d60021580418e8d4255c69d481dda","libcubeb/Config.cmake.in":"a156fb5cd30208aedcb9ef025d94187df82142fd4442e3a7d0e2a688668a3da9","libcubeb/INSTALL.md":"1fa77e15423e6f583c30861528cd2732d52a028982020cd3a7a9da4a452d7311","libcubeb/LICENSE":"44c6b5ae5ec3fe2fbc608b00e6f4896f4d2d5c7e525fcbaa3eaa3cf2f3d5a983","libcubeb/README.md":"9b01cda3464a448373176c07368d1ce4a2b4f87ec9f8f02cbac0b4a40719c141","libcubeb/cmake/compile_tests/oss_is_v4.c":"482bcd4c569e8a26f4ab0fe3a3bed25e9d4b296bf678896a2cc1dc8abbad492d","libcubeb/cmake/sanitizers-cmake/CMakeLists.txt":"89b282c19b3897ff666f7847d5e679ae928ca7e76ffd0d23f7c58c9464048b61","libcubeb/cmake/sanitizers-cmake/LICENSE":"4b67e7ae8c91e68e1a929eb1cbaa4c773c6d19aa91aaa12c390cf9560d1c9799","libcubeb/cmake/sanitizers-cmake/README.md":"f3b85a594e1601ae65bb09393c56fbc8a98e3f3f49234e56f86bd78803c3544e","libcubeb/cmake/sanitizers-cmake/cmake/FindASan.cmake":"bd30b1c3cb0682b8a2b0b599f6c51fbf99ef4908b00eba8826f0d484c623b01b","libcubeb/cmake/sanitizers-cmake/cmake/FindMSan.cmake":"55e40f0994b47f8e779622af2007c69252d9456be99f4aa4fba7dd49c23d5580","libcubeb/cmake/sanitizers-cmake/cmake/FindSanitizers.cmake":"2ffd9717a49e3e9de848ec0d91097bdbab3f75a7866211d902f9659f59271ffc","libcubeb/cmake/sanitizers-cmake/cmake/FindTSan.cmake":"ceace234abd5a463ad1b8134ad06c4c8004895b2e8fc9edb67e64620ea97ca47","libcubeb/cmake/sanitizers-cmake/cmake/FindUBSan.cmake":"4720ec96ea42a2a9c07d887c1fc5562ed3e8c222f5778a18b78c46678b23f156","libcubeb/cmake/sanitizers-cmake/cmake/asan-wrapper":"4e543936e6374e24b80a0f92135c07c2e2101c0d110e51bddaf0e70ae8ec391e","libcubeb/cmake/sanitizers-cmake/cmake/sanitize-helpers.cmake":"c0c225f22bebd27eb68b807b2a61962d28afc61609735fe652c2fde6aa6a005b","libcubeb/cmake/sanitizers-cmake/tests/CMakeLists.txt":"64b7aea469a043f27414e3d652aaa89a03a11843d152c4cd42f30561e6ef51bc","libcubeb/cmake/sanitizers-cmake/tests/asan_test.cpp":"a25de95282aaee22d977d0b474468b946c1422b80b15424c04266dba038eba2e","libcubeb/cmake/sanitizers-cmake/tests/shortest.ext.test.cpp":"a25de95282aaee22d977d0b474468b946c1422b80b15424c04266dba038eba2e","libcubeb/cmake/toolchain-cross-mingw.cmake":"1c26749465364061ddf37aaa00f363eaf057c4b6a70181086e13b0a4d09bd376","libcubeb/cubeb.supp":"19f33e59f8dc91a327c923e44c2c3f9af0a043ce1d6a8cac275ba094b4bfe0da","libcubeb/docs/Doxyfile.in":"e721a50ccf3a010b9b5169b97f93a24f66bd4b88cade17b5b5088de19fa89716","libcubeb/include/cubeb/cubeb.h":"6485142e549973b455f01b878ea46e12c5c9b69d4c2d648d455976e6ab60668e","libcubeb/scan-build-install.sh":"1ecf22aca367a4d02f810c4cb78db8636e08a12787e94e30c441ce439cf4a265","libcubeb/src/android/audiotrack_definitions.h":"7cbb2a68924d20ec651cead5a923f63eb20192f109b597a3fa1594a79196693b","libcubeb/src/android/cubeb-output-latency.h":"a3862d9caf60d5c805a61e94bd696f6687ee30d2daa4acc25bd686be38497670","libcubeb/src/android/cubeb_media_library.h":"fa2a26a31adc727f35e9f746c287734ecbc7e15ceb52baf821429919a06c2e52","libcubeb/src/android/sles_definitions.h":"4c9c3cedfc06c8df479cc675fbadb1f158b8219223a96b9cffbcbfac738d7f28","libcubeb/src/cubeb-internal.h":"50d2c0be2945136ff66666dbd8fcedc338f8f1d66eb2d9b09e09399193683ae8","libcubeb/src/cubeb-jni-instances.h":"8195554372bf60dea569873c9e5fb1106a2cf5dedc66a13d2bc967da0ff48a12","libcubeb/src/cubeb-jni.cpp":"cee58136cde7ef82045b59eee032e6eaf9bb677db12e053d8805ad9d67ee304e","libcubeb/src/cubeb-jni.h":"8f95cb6bc410bb62adae84f5e9d28fb8b57610107ed66941db0b4e4124548a72","libcubeb/src/cubeb-speex-resampler.h":"dbf543eee4cc6e40ba3531a39d327e2cec884c19e26c570aa7eae0647e5f7420","libcubeb/src/cubeb.c":"0e60bd826422bd3f2c2290e65fb296fa7ef6e578d234feb37ebcb97b11531af3","libcubeb/src/cubeb_aaudio.cpp":"1d68178a463fe0919d7578eba9a9c20c9144fdb0ef0c3b178fc19b7909066809","libcubeb/src/cubeb_alsa.c":"0f061750afa89f0054a4655bbb23dd35f77124c53a90fcce1eccdb32e23937a9","libcubeb/src/cubeb_android.h":"396922d2bb994c39acb745f7449a78e8dee00ad5146583f647545a3e0a9dd010","libcubeb/src/cubeb_array_queue.h":"86ab9c32eef5f4427413091d5932843ee870144475cddb1f0e18cd39d49f6115","libcubeb/src/cubeb_assert.h":"07841be6cca09999a913d0c1e61ab0a5de02200efed5b66fbf7c481b1d0489b0","libcubeb/src/cubeb_audiotrack.c":"18382213f4b3a9808f3dd6b8c18173d21c7230718ecb3771762850ce6420e602","libcubeb/src/cubeb_audiounit.cpp":"b130de9603ef0e171ac16e80472ddfc3b897346be6d5b1bc866ba73d8fa05f56","libcubeb/src/cubeb_jack.cpp":"9493ca3f274cd9a5dba4b5023783747bf186c4fed0dc14ff34f841dc94c988ee","libcubeb/src/cubeb_kai.c":"3b6d52e4b0eaf2db34bcf902baf4784f72805bfb6349ebe769764d09195186c5","libcubeb/src/cubeb_log.cpp":"387f3f8989b260a1ca6067c25a0fd868e38c3d5bb81263924fad5341415a96fe","libcubeb/src/cubeb_log.h":"8511c7640f2856d107900f6c40775c5dfe711187204fb5b2557562b1a855399f","libcubeb/src/cubeb_mixer.cpp":"6779938a7fd70b0ad23fdf1194dab27b57e9afb8f03ff45885601796f76a1dfe","libcubeb/src/cubeb_mixer.h":"64425dcefad3632acf68d6ae9fbdf59ff7b7b0e92487c34d18cd67f52431dc23","libcubeb/src/cubeb_opensl.cpp":"a669549e89c664a9bd2b6b5195b27e6bd27b8b2d5f4a3e9c74046e4a913f0e4a","libcubeb/src/cubeb_oss.c":"743dfe68354963e4a8553eb57fe36af6a639a7769da0b767afda5294edf865b9","libcubeb/src/cubeb_osx_run_loop.cpp":"e5c9ef5591eced85310af94575f84ea5d3051c7aaef4e41cfd221f8f8b7c4331","libcubeb/src/cubeb_osx_run_loop.h":"aa10dd0d6111973b37488d83403d204ecf6001a3530aac6bffab00c6bb31df15","libcubeb/src/cubeb_pulse.c":"034b4ad4eb072573ff2b97ed33c53622be4ff12523fb21d470d6106ca520a5e7","libcubeb/src/cubeb_resampler.cpp":"78a0c5becc11a92c77ff8215c618fac952e62623cbf87ce9c52dce94014a9775","libcubeb/src/cubeb_resampler.h":"2baa28aaf29143145bde23d12d21aa3b38e325d1db692c62da4d0e908c0a5719","libcubeb/src/cubeb_resampler_internal.h":"9b8b20a9f6afcb608e87633248d7e18e063814f39f90b8d8d9aa3273d8228bc4","libcubeb/src/cubeb_ring_array.h":"cd5b7d807c229e925fe61416d70b6ecfe41c735c361bfc01593ed10a6dad5849","libcubeb/src/cubeb_ringbuffer.h":"8f5dc7a58d8b7ad265ff3bbcac80ae160ff22243d869a5b502bf413fa58fd9b0","libcubeb/src/cubeb_sndio.c":"07b47b0993c4c37f45cef8df08bc555d7915354792edf4ee57b3e026ccf23a14","libcubeb/src/cubeb_strings.c":"6004618393cf4dc3e06cd4f44cb53589459dad21384de3695f8b3d16774edf26","libcubeb/src/cubeb_strings.h":"312f5c2230d9a02e369be1d991ec44a1793373046f208529b3cc54cb9a5b0610","libcubeb/src/cubeb_sun.c":"c9aaf34c2163dde73be4c98a4cf1059c00ca66954a78a9680384c9cb5a8927ed","libcubeb/src/cubeb_tracing.h":"def71a9c9659383ea3cf7e12ff0ba2f2c51a0ead03fef6d418eafde6e70c8f39","libcubeb/src/cubeb_triple_buffer.h":"5e3f25e1de251014b804276247c60a2cd823b5657ccf3e27d9dd46ec7433f90b","libcubeb/src/cubeb_utils.cpp":"8f41899df2f5f82541f816dadcee3d18baab7a1aecf2bc6d0b382567209351aa","libcubeb/src/cubeb_utils.h":"7a3b1e2a97752e96ff11badc19149e1582d4289e4e200817a9ed3f905bf8ab10","libcubeb/src/cubeb_utils_unix.h":"2e67e39daed8ec17092a2d1a9b660a3d4e153fc351698d9e1f3d51ef974979c5","libcubeb/src/cubeb_utils_win.h":"4d7a397f475e08dd7af47fdc4003e9f78cf9058e095348eeb554135ea3c3c454","libcubeb/src/cubeb_wasapi.cpp":"731316daf4350064d8106105f98826607360210118010bbcdcb21e5782455595","libcubeb/src/cubeb_winmm.c":"089987489bea20690e825b3107e31446b89270f316fa8219b2c501941c3bc98d","libcubeb/subprojects/speex/arch.h":"2300bce68c588270cdc684dc7f01377e5e251529f4545d93771e111c56d51b0f","libcubeb/subprojects/speex/fixed_generic.h":"306ee7453677fa6067f16c79d358c6c90a9d3d008850b493cdaa59c07e6375c1","libcubeb/subprojects/speex/resample.c":"9c3a1c64ecf3750af82c980d01ea73d3682f73c332a580465d1e787e5c54cd00","libcubeb/subprojects/speex/resample_neon.h":"7d3fd7af9a1ddde22518b9c7b4419073b72b2dfa5be4c3bc8796992bc87b3da0","libcubeb/subprojects/speex/resample_sse.h":"5a196d8e2d8ab5c956f5252f09f5ddc55aee1f99b1341af3fa54a1f4c2157924","libcubeb/subprojects/speex/speex_config_types.h":"24e3ffbf29e5519611a48e5acb959645b01d166dcb4369380d5f776c3f53d4cd","libcubeb/subprojects/speex/speex_resampler.h":"7e439ec0dd30c32216b3ced17135f8992e5aaf53389d3f5996a7d900c453e65f","libcubeb/subprojects/speex/stack_alloc.h":"e8a2fc0874942d2c7177475fcc141fdd0c0156200b8a4e7656d4a20313e2e569","libcubeb/test/README.md":"1c11b038f87daf10ea78abc17bdbdd157940c241df548c24d5872d142a98c1af","libcubeb/test/common.h":"b61e28e52dc5747f73a232b5758a65608b55755ec7a164ef90471bfa697065a1","libcubeb/test/test_audio.cpp":"cf094d11993c9d7602cc0a7f2dcf92d47ffd6142fdd5813c3010143fa08db080","libcubeb/test/test_callback_ret.cpp":"4f004b79476c44e059136478e2368c030920c9137180637e548eb2e6db7e5f6e","libcubeb/test/test_device_changed_callback.cpp":"9e02dd526602d7abfc5485c80c8b86ffa27c0a8c87e587a850f77eff9bfd14aa","libcubeb/test/test_devices.cpp":"1ea25e6ae19940d224011cd8c255d1038a858d1d9be0e7c32ea0455a0e8163f9","libcubeb/test/test_duplex.cpp":"0ea570fc3d9d19091bd4053325833a44a736e20d6c2bc725c9d5fd2e1e660102","libcubeb/test/test_latency.cpp":"a1d75355e49895ea105eb814d20c6e1a37e8cf98be01f73832f4768eb7e1176f","libcubeb/test/test_logging.cpp":"72522cf7794212dceb2f26a98f5c9297a6366f2eab90128066f59422a6edb7c5","libcubeb/test/test_loopback.cpp":"92dbdebd71aa2e844694100818ac11e5145bccb01aa3e6d7ce3bc990931dda3d","libcubeb/test/test_overload_callback.cpp":"593c36fc78ea2fccd4dc2b6e565cb3bf53503f402522737e9005d9eab7fc4732","libcubeb/test/test_record.cpp":"90484423d4e5c4ffb8e6f65c9bae1f7384cac7eeeb4f42c0fcca768ee0e5c8e6","libcubeb/test/test_resampler.cpp":"e821f7e114f772cc163aa12c274efe12a3abda1a66961c4b46106244c826a912","libcubeb/test/test_ring_array.cpp":"c0420d04c914b0f61bdc81b9c4511c1b535518b2f87f32a5539f9ca6310dd5b1","libcubeb/test/test_ring_buffer.cpp":"cf247fda806f976d2b8dd2790e5a25bda68943423d7b0bcd4ac69a57f94e2f88","libcubeb/test/test_sanity.cpp":"b1b474be02e90fa310c265d4abdc712e5637bc5420c670f26d79be98b092804f","libcubeb/test/test_tone.cpp":"75e32348aa18583130bc3f2849862a4153729f19f7e5b1bcc9e91cc6ab7bcbe7","libcubeb/test/test_triple_buffer.cpp":"4f6bd0dad20c2badb5a8374002eb6b0b4c9ed7fe48d21b7421e582401195b3ab","libcubeb/test/test_utils.cpp":"ec18e3c14519d053cfd544ae59b2ba39716500cd7da72b5b50366eefff0a0481","libcubeb/tools/cubeb-test.cpp":"e1a29a10ba824a49dad4d0d6c9f495786148f0dc942558ae990786dba6065f4e","src/callbacks.rs":"a83d9a16ee66b3cd7100dd06e1f409698a727d642df0dfa797c067b5cf1def66","src/channel.rs":"02fd91384195180a56663aba87f00660c20e71fe7654c7b68d8f470173f40d5f","src/context.rs":"23b9326f58494b0860d080ec5f0e76be2325172da7f0274ebba152dc35182f3b","src/device.rs":"08a4292bb35f09c129c4b6dcb85653ffe7e7aa98dee3202d47605f3a2b8c4244","src/error.rs":"406e3b843ed2d263fe677c4b34fb96a6d780a68bcb56a6f85f041d20d70227bd","src/format.rs":"d4d27790c20eab0b16592f60d5e487425a45a268cf4c74cf843c10ac91bbff4c","src/internal.rs":"c3af5f53dc7957860bf3bc0cd9737d094fb8ac000e7b40c569304cfa76a43145","src/lib.rs":"cdc36b11f18e274d10df592d216f142e05e1d22db5aac4c7e346893d6b1510db","src/log.rs":"8ac402b6c4d20db651cbddb87b27b3aa5ab0c3c50dac08a6c09fb1b7f9a3a391","src/macros.rs":"caef13f5d23f7a3ec1a54ec3ca2390ac4ad89d521893f1d0864daf70d57a20aa","src/mixer.rs":"e72e92855614da187da6419ed0a115062a05670cb73443b2ea4313d8f5108a68","src/resampler.rs":"b16bd6a4b1179b25f2a5391133fcd4410467e6a68c0a46cf15a12d280c1862e0","src/stream.rs":"5d3ec1e48fdb96054e8ff678ae6c784e23b551b496154958433b746ee473f3c8"},"package":"3c20c457d7b34dad6e0c1a9c759c96b4420b9e9917a572998b81835799a07e1d"} \ No newline at end of file
diff --git a/third_party/rust/cubeb-sys/Cargo.toml b/third_party/rust/cubeb-sys/Cargo.toml
new file mode 100644
index 0000000000..36c447c707
--- /dev/null
+++ b/third_party/rust/cubeb-sys/Cargo.toml
@@ -0,0 +1,33 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+name = "cubeb-sys"
+version = "0.12.0"
+authors = ["Dan Glastonbury <dglastonbury@mozilla.com>"]
+build = "build.rs"
+links = "cubeb"
+exclude = ["libcubeb/googletest/"]
+description = "Native bindings to the cubeb library"
+license = "ISC"
+repository = "https://github.com/mozilla/cubeb-rs"
+
+[build-dependencies.cmake]
+version = "0.1.2"
+
+[build-dependencies.pkg-config]
+version = "0.3"
+
+[features]
+gecko-in-tree = []
+
+[badges.circle-ci]
+repository = "mozilla/cubeb-rs"
diff --git a/third_party/rust/cubeb-sys/LICENSE b/third_party/rust/cubeb-sys/LICENSE
new file mode 100644
index 0000000000..ec9718cf02
--- /dev/null
+++ b/third_party/rust/cubeb-sys/LICENSE
@@ -0,0 +1,13 @@
+Copyright © 2017 Mozilla Foundation
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/third_party/rust/cubeb-sys/build.rs b/third_party/rust/cubeb-sys/build.rs
new file mode 100644
index 0000000000..509ff46c7a
--- /dev/null
+++ b/third_party/rust/cubeb-sys/build.rs
@@ -0,0 +1,103 @@
+// Copyright © 2018 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+#[cfg(not(feature = "gecko-in-tree"))]
+extern crate cmake;
+#[cfg(not(feature = "gecko-in-tree"))]
+extern crate pkg_config;
+
+use std::env;
+use std::fs;
+use std::path::Path;
+use std::process::Command;
+
+macro_rules! t {
+ ($e:expr) => {
+ match $e {
+ Ok(e) => e,
+ Err(e) => panic!("{} failed with {}", stringify!($e), e),
+ }
+ };
+}
+
+#[cfg(feature = "gecko-in-tree")]
+fn main() {}
+
+#[cfg(not(feature = "gecko-in-tree"))]
+fn main() {
+ if env::var("LIBCUBEB_SYS_USE_PKG_CONFIG").is_ok()
+ && pkg_config::find_library("libcubeb").is_ok()
+ {
+ return;
+ }
+
+ if !Path::new("libcubeb/.git").exists() {
+ let _ = Command::new("git")
+ .args(["submodule", "update", "--init", "--recursive"])
+ .status();
+ }
+
+ let target = env::var("TARGET").unwrap();
+ let windows = target.contains("windows");
+ let darwin = target.contains("darwin");
+ let freebsd = target.contains("freebsd");
+ let android = target.contains("android");
+ let mut cfg = cmake::Config::new("libcubeb");
+
+ if darwin {
+ let cmake_osx_arch = if target.contains("aarch64") {
+ // Apple Silicon
+ "arm64"
+ } else {
+ // Assuming Intel (x86_64)
+ "x86_64"
+ };
+ cfg.define("CMAKE_OSX_ARCHITECTURES", cmake_osx_arch);
+ }
+
+ let _ = fs::remove_dir_all(env::var("OUT_DIR").unwrap());
+ t!(fs::create_dir_all(env::var("OUT_DIR").unwrap()));
+
+ env::remove_var("DESTDIR");
+ let dst = cfg
+ .define("BUILD_SHARED_LIBS", "OFF")
+ .define("BUILD_TESTS", "OFF")
+ .define("BUILD_TOOLS", "OFF")
+ .build();
+
+ println!("cargo:rustc-link-lib=static=cubeb");
+ if windows {
+ println!("cargo:rustc-link-lib=dylib=avrt");
+ println!("cargo:rustc-link-lib=dylib=ksuser");
+ println!("cargo:rustc-link-lib=dylib=ole32");
+ println!("cargo:rustc-link-lib=dylib=user32");
+ println!("cargo:rustc-link-lib=dylib=winmm");
+ println!("cargo:rustc-link-search=native={}/lib", dst.display());
+ } else if darwin {
+ println!("cargo:rustc-link-lib=framework=AudioUnit");
+ println!("cargo:rustc-link-lib=framework=CoreAudio");
+ println!("cargo:rustc-link-lib=framework=CoreServices");
+ println!("cargo:rustc-link-lib=dylib=c++");
+ println!("cargo:rustc-link-search=native={}/lib", dst.display());
+ } else {
+ if freebsd || android {
+ println!("cargo:rustc-link-lib=dylib=c++");
+ } else {
+ println!("cargo:rustc-link-lib=dylib=stdc++");
+ }
+ println!("cargo:rustc-link-search=native={}/lib", dst.display());
+ println!("cargo:rustc-link-search=native={}/lib64", dst.display());
+
+ // Ignore the result of find_library. We don't care if the
+ // libraries are missing.
+ let _ = pkg_config::find_library("alsa");
+ let _ = pkg_config::find_library("libpulse");
+ let _ = pkg_config::find_library("jack");
+ let _ = pkg_config::find_library("speexdsp");
+ if android {
+ println!("cargo:rustc-link-lib=dylib=OpenSLES");
+ }
+ }
+}
diff --git a/third_party/rust/cubeb-sys/libcubeb/AUTHORS b/third_party/rust/cubeb-sys/libcubeb/AUTHORS
new file mode 100644
index 0000000000..f0f9595227
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/AUTHORS
@@ -0,0 +1,16 @@
+Matthew Gregan <kinetik@flim.org>
+Alexandre Ratchov <alex@caoua.org>
+Michael Wu <mwu@mozilla.com>
+Paul Adenot <paul@paul.cx>
+David Richards <drichards@mozilla.com>
+Sebastien Alaiwan <sebastien.alaiwan@gmail.com>
+KO Myung-Hun <komh@chollian.net>
+Haakon Sporsheim <haakon.sporsheim@telenordigital.com>
+Alex Chronopoulos <achronop@gmail.com>
+Jan Beich <jbeich@FreeBSD.org>
+Vito Caputo <vito.caputo@coreos.com>
+Landry Breuil <landry@openbsd.org>
+Jacek Caban <jacek@codeweavers.com>
+Paul Hancock <Paul.Hancock.17041993@live.com>
+Ted Mielczarek <ted@mielczarek.org>
+Chun-Min Chang <chun.m.chang@gmail.com>
diff --git a/third_party/rust/cubeb-sys/libcubeb/CMakeLists.txt b/third_party/rust/cubeb-sys/libcubeb/CMakeLists.txt
new file mode 100644
index 0000000000..530e3a4601
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/CMakeLists.txt
@@ -0,0 +1,446 @@
+# TODO
+# - backend selection via command line, rather than simply detecting headers.
+
+cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
+project(cubeb
+ VERSION 0.0.0)
+
+option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
+option(BUILD_TESTS "Build tests" ON)
+option(BUILD_RUST_LIBS "Build rust backends" OFF)
+option(BUILD_TOOLS "Build tools" ON)
+option(BUNDLE_SPEEX "Bundle the speex library" OFF)
+option(LAZY_LOAD_LIBS "Lazily load shared libraries" ON)
+option(USE_SANITIZERS "Use sanitizers" ON)
+
+if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING
+ "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
+endif()
+
+set(CMAKE_C_STANDARD 99)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+if(USE_SANITIZERS)
+ if(NOT COMMAND add_sanitizers)
+ list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/sanitizers-cmake/cmake")
+ find_package(Sanitizers)
+ if(NOT COMMAND add_sanitizers)
+ message(FATAL_ERROR "Could not find sanitizers-cmake: run\n\tgit submodule update --init --recursive\nin base git checkout")
+ endif()
+ endif()
+else()
+ macro(add_sanitizers UNUSED)
+ endmacro()
+endif()
+
+if(BUILD_TESTS)
+ find_package(GTest QUIET)
+ if(TARGET GTest::Main)
+ add_library(gtest_main ALIAS GTest::Main)
+ endif()
+ if(NOT TARGET gtest_main)
+ if(NOT EXISTS "${PROJECT_SOURCE_DIR}/googletest/CMakeLists.txt")
+ message(FATAL_ERROR "Could not find googletest: run\n\tgit submodule update --init --recursive\nin base git checkout")
+ endif()
+ add_definitions(-DGTEST_HAS_TR1_TUPLE=0 -DGTEST_HAS_RTTI=0)
+ set(gtest_force_shared_crt ON CACHE BOOL "")
+ add_subdirectory(googletest)
+ endif()
+endif()
+
+if (BUILD_RUST_LIBS)
+ if(EXISTS "${PROJECT_SOURCE_DIR}/src/cubeb-pulse-rs")
+ set(USE_PULSE_RUST 1)
+ endif()
+ if(EXISTS "${PROJECT_SOURCE_DIR}/src/cubeb-coreaudio-rs")
+ set(USE_AUDIOUNIT_RUST 1)
+ endif()
+endif()
+
+# On OS/2, visibility attribute is not supported.
+if(NOT OS2)
+ set(CMAKE_C_VISIBILITY_PRESET hidden)
+ set(CMAKE_CXX_VISIBILITY_PRESET hidden)
+ set(CMAKE_VISIBILITY_INLINES_HIDDEN 1)
+endif()
+
+set(CMAKE_CXX_WARNING_LEVEL 4)
+if(NOT MSVC)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-parameter")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter -fno-exceptions -fno-rtti")
+else()
+ string(REPLACE "/GR" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") # Disable RTTI
+ string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") # Disable Exceptions
+endif()
+
+add_library(cubeb
+ src/cubeb.c
+ src/cubeb_mixer.cpp
+ src/cubeb_resampler.cpp
+ src/cubeb_log.cpp
+ src/cubeb_strings.c
+ src/cubeb_utils.cpp
+)
+target_include_directories(cubeb
+ PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include>
+)
+set_target_properties(cubeb PROPERTIES
+ VERSION ${cubeb_VERSION}
+ SOVERSION ${cubeb_VERSION_MAJOR}
+)
+
+add_sanitizers(cubeb)
+
+include(GenerateExportHeader)
+generate_export_header(cubeb EXPORT_FILE_NAME ${CMAKE_BINARY_DIR}/exports/cubeb_export.h)
+target_include_directories(cubeb
+ PUBLIC $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/exports>
+)
+
+include(GNUInstallDirs)
+
+install(DIRECTORY ${CMAKE_SOURCE_DIR}/include/${PROJECT_NAME} TYPE INCLUDE)
+install(DIRECTORY ${CMAKE_BINARY_DIR}/exports/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
+
+include(CMakePackageConfigHelpers)
+write_basic_package_version_file(
+ "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
+ COMPATIBILITY SameMajorVersion
+)
+
+configure_package_config_file(
+ "Config.cmake.in"
+ "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
+ INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
+)
+
+install(
+ FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
+)
+
+install(TARGETS cubeb EXPORT "${PROJECT_NAME}Targets")
+install(
+ EXPORT "${PROJECT_NAME}Targets"
+ NAMESPACE "${PROJECT_NAME}::"
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
+)
+
+if(NOT BUNDLE_SPEEX)
+ find_package(PkgConfig)
+ if(PKG_CONFIG_FOUND)
+ pkg_check_modules(speexdsp IMPORTED_TARGET speexdsp)
+ if(speexdsp_FOUND)
+ add_library(speex ALIAS PkgConfig::speexdsp)
+ endif()
+ endif()
+endif()
+
+if(NOT TARGET speex)
+ add_library(speex OBJECT subprojects/speex/resample.c)
+ set_target_properties(speex PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
+ target_include_directories(speex INTERFACE subprojects)
+ target_compile_definitions(speex PUBLIC
+ OUTSIDE_SPEEX
+ FLOATING_POINT
+ EXPORT=
+ RANDOM_PREFIX=speex
+ )
+endif()
+
+# $<BUILD_INTERFACE:> required because of https://gitlab.kitware.com/cmake/cmake/-/issues/15415
+target_link_libraries(cubeb PRIVATE $<BUILD_INTERFACE:speex>)
+
+include(CheckIncludeFiles)
+
+# Threads needed by cubeb_log, _pulse, _alsa, _jack, _sndio, _oss and _sun
+set(THREADS_PREFER_PTHREAD_FLAG ON)
+find_package(Threads)
+target_link_libraries(cubeb PRIVATE Threads::Threads)
+
+if(LAZY_LOAD_LIBS)
+ check_include_files(pulse/pulseaudio.h USE_PULSE)
+ check_include_files(alsa/asoundlib.h USE_ALSA)
+ check_include_files(jack/jack.h USE_JACK)
+ check_include_files(sndio.h USE_SNDIO)
+ check_include_files(aaudio/AAudio.h USE_AAUDIO)
+
+ if(USE_PULSE OR USE_ALSA OR USE_JACK OR USE_SNDIO OR USE_AAUDIO)
+ target_link_libraries(cubeb PRIVATE ${CMAKE_DL_LIBS})
+
+ if(ANDROID)
+ target_compile_definitions(cubeb PRIVATE __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__)
+ endif()
+ endif()
+
+else()
+
+ find_package(PkgConfig REQUIRED)
+
+ pkg_check_modules(libpulse IMPORTED_TARGET libpulse)
+ if(libpulse_FOUND)
+ set(USE_PULSE ON)
+ target_compile_definitions(cubeb PRIVATE DISABLE_LIBPULSE_DLOPEN)
+ target_link_libraries(cubeb PRIVATE PkgConfig::libpulse)
+ endif()
+
+ pkg_check_modules(alsa IMPORTED_TARGET alsa)
+ if(alsa_FOUND)
+ set(USE_ALSA ON)
+ target_compile_definitions(cubeb PRIVATE DISABLE_LIBASOUND_DLOPEN)
+ target_link_libraries(cubeb PRIVATE PkgConfig::alsa)
+ endif()
+
+ pkg_check_modules(jack IMPORTED_TARGET jack)
+ if(jack_FOUND)
+ set(USE_JACK ON)
+ target_compile_definitions(cubeb PRIVATE DISABLE_LIBJACK_DLOPEN)
+ target_link_libraries(cubeb PRIVATE PkgConfig::jack)
+ endif()
+
+ check_include_files(sndio.h USE_SNDIO)
+ if(USE_SNDIO)
+ target_compile_definitions(cubeb PRIVATE DISABLE_LIBSNDIO_DLOPEN)
+ target_link_libraries(cubeb PRIVATE sndio)
+ endif()
+
+ check_include_files(aaudio/AAudio.h USE_AAUDIO)
+ if(USE_AAUDIO)
+ target_compile_definitions(cubeb PRIVATE DISABLE_LIBAAUDIO_DLOPEN)
+ target_link_libraries(cubeb PRIVATE aaudio)
+ endif()
+endif()
+
+if(USE_PULSE)
+ target_sources(cubeb PRIVATE src/cubeb_pulse.c)
+ target_compile_definitions(cubeb PRIVATE USE_PULSE)
+endif()
+
+if(USE_ALSA)
+ target_sources(cubeb PRIVATE src/cubeb_alsa.c)
+ target_compile_definitions(cubeb PRIVATE USE_ALSA)
+endif()
+
+if(USE_JACK)
+ target_sources(cubeb PRIVATE src/cubeb_jack.cpp)
+ target_compile_definitions(cubeb PRIVATE USE_JACK)
+endif()
+
+if(USE_SNDIO)
+ target_sources(cubeb PRIVATE src/cubeb_sndio.c)
+ target_compile_definitions(cubeb PRIVATE USE_SNDIO)
+endif()
+
+if(USE_AAUDIO)
+ target_sources(cubeb PRIVATE src/cubeb_aaudio.cpp)
+ target_compile_definitions(cubeb PRIVATE USE_AAUDIO)
+
+ # set this definition to enable low latency mode. Possibly bad for battery
+ target_compile_definitions(cubeb PRIVATE CUBEB_AAUDIO_LOW_LATENCY)
+
+ # set this definition to enable power saving mode. Possibly resulting
+ # in high latency
+ # target_compile_definitions(cubeb PRIVATE CUBEB_AAUDIO_LOW_POWER_SAVING)
+
+ # set this mode to make the backend use an exclusive stream.
+ # will decrease latency.
+ # target_compile_definitions(cubeb PRIVATE CUBEB_AAUDIO_EXCLUSIVE_STREAM)
+endif()
+
+check_include_files(AudioUnit/AudioUnit.h USE_AUDIOUNIT)
+if(USE_AUDIOUNIT)
+ target_sources(cubeb PRIVATE
+ src/cubeb_audiounit.cpp
+ src/cubeb_osx_run_loop.cpp)
+ target_compile_definitions(cubeb PRIVATE USE_AUDIOUNIT)
+ target_link_libraries(cubeb PRIVATE "-framework AudioUnit" "-framework CoreAudio" "-framework CoreServices")
+endif()
+
+check_include_files(audioclient.h USE_WASAPI)
+if(USE_WASAPI)
+ target_sources(cubeb PRIVATE
+ src/cubeb_wasapi.cpp)
+ target_compile_definitions(cubeb PRIVATE USE_WASAPI)
+ target_link_libraries(cubeb PRIVATE avrt ole32 ksuser)
+endif()
+
+check_include_files("windows.h;mmsystem.h" USE_WINMM)
+if(USE_WINMM)
+ target_sources(cubeb PRIVATE
+ src/cubeb_winmm.c)
+ target_compile_definitions(cubeb PRIVATE USE_WINMM)
+ target_link_libraries(cubeb PRIVATE winmm)
+endif()
+
+check_include_files(SLES/OpenSLES.h USE_OPENSL)
+if(USE_OPENSL)
+ target_sources(cubeb PRIVATE
+ src/cubeb_opensl.cpp
+ src/cubeb-jni.cpp)
+ target_compile_definitions(cubeb PRIVATE USE_OPENSL)
+ target_link_libraries(cubeb PRIVATE OpenSLES)
+endif()
+
+check_include_files(sys/soundcard.h HAVE_SYS_SOUNDCARD_H)
+if(HAVE_SYS_SOUNDCARD_H)
+ try_compile(USE_OSS "${PROJECT_BINARY_DIR}/compile_tests"
+ ${PROJECT_SOURCE_DIR}/cmake/compile_tests/oss_is_v4.c)
+ if(USE_OSS)
+ # strlcpy is not available on BSD systems that use glibc,
+ # like Debian kfreebsd, so try using libbsd if available
+ include(CheckSymbolExists)
+ check_symbol_exists(strlcpy string.h HAVE_STRLCPY)
+ if(NOT HAVE_STRLCPY)
+ pkg_check_modules(libbsd-overlay IMPORTED_TARGET libbsd-overlay)
+ if(libbsd-overlay_FOUND)
+ target_link_libraries(cubeb PRIVATE PkgConfig::libbsd-overlay)
+ set(HAVE_STRLCPY true)
+ endif()
+ endif()
+ if (HAVE_STRLCPY)
+ target_sources(cubeb PRIVATE
+ src/cubeb_oss.c)
+ target_compile_definitions(cubeb PRIVATE USE_OSS)
+ endif()
+ endif()
+endif()
+
+check_include_files(android/log.h USE_AUDIOTRACK)
+if(USE_AUDIOTRACK)
+ target_sources(cubeb PRIVATE
+ src/cubeb_audiotrack.c)
+ target_compile_definitions(cubeb PRIVATE USE_AUDIOTRACK)
+ target_link_libraries(cubeb PRIVATE log)
+endif()
+
+check_include_files(sys/audioio.h USE_SUN)
+if(USE_SUN)
+ target_sources(cubeb PRIVATE
+ src/cubeb_sun.c)
+ target_compile_definitions(cubeb PRIVATE USE_SUN)
+endif()
+
+check_include_files(kai.h USE_KAI)
+if(USE_KAI)
+ target_sources(cubeb PRIVATE
+ src/cubeb_kai.c)
+ target_compile_definitions(cubeb PRIVATE USE_KAI)
+ target_link_libraries(cubeb PRIVATE kai)
+endif()
+
+if(USE_PULSE AND USE_PULSE_RUST)
+ include(ExternalProject)
+ set_directory_properties(PROPERTIES EP_PREFIX ${CMAKE_BINARY_DIR}/rust)
+ ExternalProject_Add(
+ cubeb_pulse_rs
+ DOWNLOAD_COMMAND ""
+ CONFIGURE_COMMAND ""
+ BUILD_COMMAND cargo build COMMAND cargo build --release
+ BUILD_ALWAYS ON
+ BINARY_DIR "${PROJECT_SOURCE_DIR}/src/cubeb-pulse-rs"
+ INSTALL_COMMAND ""
+ LOG_BUILD ON)
+ add_dependencies(cubeb cubeb_pulse_rs)
+ target_compile_definitions(cubeb PRIVATE USE_PULSE_RUST)
+ target_link_libraries(cubeb PRIVATE
+ debug "${PROJECT_SOURCE_DIR}/src/cubeb-pulse-rs/target/debug/libcubeb_pulse.a"
+ optimized "${PROJECT_SOURCE_DIR}/src/cubeb-pulse-rs/target/release/libcubeb_pulse.a" pulse)
+endif()
+
+if(USE_AUDIOUNIT AND USE_AUDIOUNIT_RUST)
+ include(ExternalProject)
+ set_directory_properties(PROPERTIES EP_PREFIX ${CMAKE_BINARY_DIR}/rust)
+ ExternalProject_Add(
+ cubeb_coreaudio_rs
+ DOWNLOAD_COMMAND ""
+ CONFIGURE_COMMAND ""
+ BUILD_COMMAND cargo build COMMAND cargo build --release
+ BUILD_ALWAYS ON
+ BINARY_DIR "${PROJECT_SOURCE_DIR}/src/cubeb-coreaudio-rs"
+ INSTALL_COMMAND ""
+ LOG_BUILD ON)
+ add_dependencies(cubeb cubeb_coreaudio_rs)
+ target_compile_definitions(cubeb PRIVATE USE_AUDIOUNIT_RUST)
+ target_link_libraries(cubeb PRIVATE
+ debug "${PROJECT_SOURCE_DIR}/src/cubeb-coreaudio-rs/target/debug/libcubeb_coreaudio.a"
+ optimized "${PROJECT_SOURCE_DIR}/src/cubeb-coreaudio-rs/target/release/libcubeb_coreaudio.a")
+endif()
+
+find_package(Doxygen)
+if(DOXYGEN_FOUND)
+ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/docs/Doxyfile @ONLY)
+ add_custom_target(doc ALL
+ ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/docs/Doxyfile
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/docs
+ COMMENT "Generating API documentation with Doxygen" VERBATIM)
+ install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/docs/html/ TYPE DOC)
+endif()
+
+if(BUILD_TESTS)
+ enable_testing()
+
+ macro(cubeb_add_test NAME)
+ add_executable(test_${NAME} test/test_${NAME}.cpp)
+ target_include_directories(test_${NAME} PRIVATE ${gtest_SOURCE_DIR}/include src)
+ target_link_libraries(test_${NAME} PRIVATE cubeb gtest_main)
+ add_test(${NAME} test_${NAME} --gtest_death_test_style=threadsafe)
+ add_sanitizers(test_${NAME})
+ endmacro(cubeb_add_test)
+
+ cubeb_add_test(sanity)
+ cubeb_add_test(tone)
+ cubeb_add_test(audio)
+ cubeb_add_test(record)
+ cubeb_add_test(devices)
+ cubeb_add_test(callback_ret)
+
+ add_executable(test_resampler test/test_resampler.cpp src/cubeb_resampler.cpp src/cubeb_log.cpp)
+ target_include_directories(test_resampler PRIVATE ${gtest_SOURCE_DIR}/include src)
+ target_link_libraries(test_resampler PRIVATE cubeb gtest_main speex)
+ add_test(resampler test_resampler)
+ add_sanitizers(test_resampler)
+
+ cubeb_add_test(duplex)
+ cubeb_add_test(logging)
+ cubeb_add_test(triple_buffer)
+
+ if (USE_WASAPI)
+ cubeb_add_test(overload_callback)
+ cubeb_add_test(loopback)
+ endif()
+
+ cubeb_add_test(latency test_latency)
+ cubeb_add_test(ring_array)
+
+ cubeb_add_test(utils)
+ cubeb_add_test(ring_buffer)
+ cubeb_add_test(device_changed_callback)
+endif()
+
+if(BUILD_TOOLS)
+ add_executable(cubeb-test tools/cubeb-test.cpp)
+ target_include_directories(cubeb-test PRIVATE src)
+ target_link_libraries(cubeb-test PRIVATE cubeb)
+ add_sanitizers(cubeb-test)
+ install(TARGETS cubeb-test)
+endif()
+
+if(NOT CLANG_FORMAT_BINARY)
+set(CLANG_FORMAT_BINARY clang-format)
+endif()
+
+add_custom_target(clang-format-check
+ find
+ ${CMAKE_CURRENT_SOURCE_DIR}/src
+ ${CMAKE_CURRENT_SOURCE_DIR}/include
+ ${CMAKE_CURRENT_SOURCE_DIR}/test
+ -type f (-name "*.cpp" -o -name "*.c" -o -name "*.h")
+ -not -path "*/subprojects/speex/*"
+ -print0
+ | xargs -0 ${CLANG_FORMAT_BINARY} -Werror -n
+ COMMENT "Check formatting with clang-format"
+ VERBATIM)
+
diff --git a/third_party/rust/cubeb-sys/libcubeb/Config.cmake.in b/third_party/rust/cubeb-sys/libcubeb/Config.cmake.in
new file mode 100644
index 0000000000..be464aa492
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/Config.cmake.in
@@ -0,0 +1,4 @@
+@PACKAGE_INIT@
+
+include("${CMAKE_CURRENT_LIST_DIR}/cubebTargets.cmake")
+check_required_components(cubeb)
diff --git a/third_party/rust/cubeb-sys/libcubeb/INSTALL.md b/third_party/rust/cubeb-sys/libcubeb/INSTALL.md
new file mode 100644
index 0000000000..4f83a65200
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/INSTALL.md
@@ -0,0 +1,45 @@
+# Build instructions for libcubeb
+
+You must have CMake v3.14 or later installed.
+
+1. `git clone --recursive https://github.com/mozilla/cubeb.git`
+2. `cd cubeb`
+3. `cmake -B ./build .`
+4. `cmake --build ./build`
+5. `cd build && ctest`
+
+# Windows build notes
+
+Windows builds can use Microsoft Visual Studio 2015, Microsoft Visual Studio
+2017, or MinGW-w64 with Win32 threads (by passing `cmake -G` to generate the
+appropriate build configuration).
+
+## Microsoft Visual Studio 2015 or 2017 Command Line
+
+CMake can be used from the command line by following the build steps at the top
+of this file. CMake will select a default generator based on the environment,
+or one can be specified with the `-G` argument.
+
+## Microsoft Visual Studio 2017 IDE
+
+Visual Studio 2017 adds in built support for CMake. CMake can be used from
+within the IDE via the following steps:
+
+- Navigate to `File -> Open -> Cmake...`
+- Open `CMakeLists.txt` file in the root of the project.
+
+Note, to generate the build in the cubeb dir CMake settings need to be updated
+via: `CMake -> Change CMake Settings -> CMakeLists.txt`. The default
+configuration used by Visual Studio will place the build in a different location
+than the steps detailed at the top of this file.
+
+## MinGW-w64
+
+To build with MinGW-w64, install the following items:
+
+- Download and install MinGW-w64 with Win32 threads.
+- Download and install CMake.
+- Run MinGW-w64 Terminal from the Start Menu.
+- Follow the build steps at the top of this file, but at step 4 run:
+ `cmake -G "MinGW Makefiles" ../cubeb`
+- Continue the build steps at the top of this file.
diff --git a/third_party/rust/cubeb-sys/libcubeb/LICENSE b/third_party/rust/cubeb-sys/libcubeb/LICENSE
new file mode 100644
index 0000000000..fffc9dc405
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/LICENSE
@@ -0,0 +1,13 @@
+Copyright © 2011 Mozilla Foundation
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/third_party/rust/cubeb-sys/libcubeb/README.md b/third_party/rust/cubeb-sys/libcubeb/README.md
new file mode 100644
index 0000000000..e4e1658824
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/README.md
@@ -0,0 +1,7 @@
+[![Build Status](https://github.com/mozilla/cubeb/actions/workflows/build.yml/badge.svg)](https://github.com/mozilla/cubeb/actions/workflows/build.yml)
+
+See INSTALL.md for build instructions.
+
+See [Backend Support](https://github.com/mozilla/cubeb/wiki/Backend-Support) in the wiki for the support level of each backend.
+
+Licensed under an ISC-style license. See LICENSE for details.
diff --git a/third_party/rust/cubeb-sys/libcubeb/cmake/compile_tests/oss_is_v4.c b/third_party/rust/cubeb-sys/libcubeb/cmake/compile_tests/oss_is_v4.c
new file mode 100644
index 0000000000..a46bb1554d
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/cmake/compile_tests/oss_is_v4.c
@@ -0,0 +1,10 @@
+#include <sys/soundcard.h>
+
+#if SOUND_VERSION < 0x040000
+# error "OSSv4 is not available in sys/soundcard.h"
+#endif
+
+int main()
+{
+ return 0;
+}
diff --git a/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/CMakeLists.txt b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/CMakeLists.txt
new file mode 100644
index 0000000000..d249a4db5c
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/CMakeLists.txt
@@ -0,0 +1,51 @@
+# This file is part of CMake-sanitizers.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+#
+# Copyright (c)
+# 2013-2015 Matt Arsenault
+# 2015 RWTH Aachen University, Federal Republic of Germany
+#
+
+
+#
+# project information
+#
+
+# minimum required cmake version
+cmake_minimum_required(VERSION 2.8)
+
+# project name
+project("CMake-sanitizers")
+
+
+
+#
+# cmake configuration
+#
+set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
+
+
+
+#
+# add tests
+#
+enable_testing()
+add_subdirectory(tests)
diff --git a/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/LICENSE b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/LICENSE
new file mode 100644
index 0000000000..2520efdc03
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c)
+ 2013 Matthew Arsenault
+ 2015-2016 RWTH Aachen University, Federal Republic of Germany
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/README.md b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/README.md
new file mode 100644
index 0000000000..8df8d175c2
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/README.md
@@ -0,0 +1,73 @@
+# sanitizers-cmake
+
+ [![](https://img.shields.io/github/issues-raw/arsenm/sanitizers-cmake.svg?style=flat-square)](https://github.com/arsenm/sanitizers-cmake/issues)
+[![MIT](http://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE)
+
+CMake module to enable sanitizers for binary targets.
+
+
+## Include into your project
+
+To use [FindSanitizers.cmake](cmake/FindSanitizers.cmake), simply add this repository as git submodule into your own repository
+```Shell
+mkdir externals
+git submodule add git://github.com/arsenm/sanitizers-cmake.git externals/sanitizers-cmake
+```
+and adding ```externals/sanitizers-cmake/cmake``` to your ```CMAKE_MODULE_PATH```
+```CMake
+set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/externals/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH})
+```
+
+If you don't use git or dislike submodules you can copy the files in [cmake directory](cmake) into your repository. *Be careful and keep updates in mind!*
+
+Now you can simply run ```find_package``` in your CMake files:
+```CMake
+find_package(Sanitizers)
+```
+
+
+## Usage
+
+You can enable the sanitizers with ``SANITIZE_ADDRESS``, ``SANITIZE_MEMORY``, ``SANITIZE_THREAD`` or ``SANITIZE_UNDEFINED`` options in your CMake configuration. You can do this by passing e.g. ``-DSANITIZE_ADDRESS=On`` on your command line or with your graphical interface.
+
+If sanitizers are supported by your compiler, the specified targets will be build with sanitizer support. If your compiler has no sanitizing capabilities (I asume intel compiler doesn't) you'll get a warning but CMake will continue processing and sanitizing will simply just be ignored.
+
+#### Compiler issues
+
+Different compilers may be using different implementations for sanitizers. If you'll try to sanitize targets with C and Fortran code but don't use gcc & gfortran but clang & gfortran, this will cause linking problems. To avoid this, such problems will be detected and sanitizing will be disabled for these targets.
+
+Even C only targets may cause problems in certain situations. Some problems have been seen with AddressSanitizer for preloading or dynamic linking. In such cases you may try the ``SANITIZE_LINK_STATIC`` to link sanitizers for gcc static.
+
+
+
+## Build targets with sanitizer support
+
+To enable sanitizer support you simply have to add ``add_sanitizers(<TARGET>)`` after defining your target. To provide a sanitizer blacklist file you can use the ``sanitizer_add_blacklist_file(<FILE>)`` function:
+```CMake
+find_package(Sanitizers)
+
+sanitizer_add_blacklist_file("blacklist.txt")
+
+add_executable(some_exe foo.c bar.c)
+add_sanitizers(some_exe)
+
+add_library(some_lib foo.c bar.c)
+add_sanitizers(some_lib)
+```
+
+## Run your application
+
+The sanitizers check your program, while it's running. In some situations (e.g. LD_PRELOAD your target) it might be required to preload the used AddressSanitizer library first. In this case you may use the ``asan-wrapper`` script defined in ``ASan_WRAPPER`` variable to execute your application with ``${ASan_WRAPPER} myexe arg1 ...``.
+
+
+## Contribute
+
+Anyone is welcome to contribute. Simply fork this repository, make your changes **in an own branch** and create a pull-request for your change. Please do only one change per pull-request.
+
+You found a bug? Please fill out an [issue](https://github.com/arsenm/sanitizers-cmake/issues) and include any data to reproduce the bug.
+
+
+#### Contributors
+
+* [Matt Arsenault](https://github.com/arsenm)
+* [Alexander Haase](https://github.com/alehaa)
diff --git a/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/FindASan.cmake b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/FindASan.cmake
new file mode 100644
index 0000000000..98ea7cb311
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/FindASan.cmake
@@ -0,0 +1,59 @@
+# The MIT License (MIT)
+#
+# Copyright (c)
+# 2013 Matthew Arsenault
+# 2015-2016 RWTH Aachen University, Federal Republic of Germany
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+option(SANITIZE_ADDRESS "Enable AddressSanitizer for sanitized targets." Off)
+
+set(FLAG_CANDIDATES
+ # Clang 3.2+ use this version. The no-omit-frame-pointer option is optional.
+ "-g -fsanitize=address -fno-omit-frame-pointer"
+ "-g -fsanitize=address"
+
+ # Older deprecated flag for ASan
+ "-g -faddress-sanitizer"
+)
+
+
+if (SANITIZE_ADDRESS AND (SANITIZE_THREAD OR SANITIZE_MEMORY))
+ message(FATAL_ERROR "AddressSanitizer is not compatible with "
+ "ThreadSanitizer or MemorySanitizer.")
+endif ()
+
+
+include(sanitize-helpers)
+
+if (SANITIZE_ADDRESS)
+ sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "AddressSanitizer"
+ "ASan")
+
+ find_program(ASan_WRAPPER "asan-wrapper" PATHS ${CMAKE_MODULE_PATH})
+ mark_as_advanced(ASan_WRAPPER)
+endif ()
+
+function (add_sanitize_address TARGET)
+ if (NOT SANITIZE_ADDRESS)
+ return()
+ endif ()
+
+ sanitizer_add_flags(${TARGET} "AddressSanitizer" "ASan")
+endfunction ()
diff --git a/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/FindMSan.cmake b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/FindMSan.cmake
new file mode 100644
index 0000000000..22d0050e97
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/FindMSan.cmake
@@ -0,0 +1,57 @@
+# The MIT License (MIT)
+#
+# Copyright (c)
+# 2013 Matthew Arsenault
+# 2015-2016 RWTH Aachen University, Federal Republic of Germany
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+option(SANITIZE_MEMORY "Enable MemorySanitizer for sanitized targets." Off)
+
+set(FLAG_CANDIDATES
+ "-g -fsanitize=memory"
+)
+
+
+include(sanitize-helpers)
+
+if (SANITIZE_MEMORY)
+ if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+ message(WARNING "MemorySanitizer disabled for target ${TARGET} because "
+ "MemorySanitizer is supported for Linux systems only.")
+ set(SANITIZE_MEMORY Off CACHE BOOL
+ "Enable MemorySanitizer for sanitized targets." FORCE)
+ elseif (NOT ${CMAKE_SIZEOF_VOID_P} EQUAL 8)
+ message(WARNING "MemorySanitizer disabled for target ${TARGET} because "
+ "MemorySanitizer is supported for 64bit systems only.")
+ set(SANITIZE_MEMORY Off CACHE BOOL
+ "Enable MemorySanitizer for sanitized targets." FORCE)
+ else ()
+ sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "MemorySanitizer"
+ "MSan")
+ endif ()
+endif ()
+
+function (add_sanitize_memory TARGET)
+ if (NOT SANITIZE_MEMORY)
+ return()
+ endif ()
+
+ sanitizer_add_flags(${TARGET} "MemorySanitizer" "MSan")
+endfunction ()
diff --git a/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/FindSanitizers.cmake b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/FindSanitizers.cmake
new file mode 100644
index 0000000000..a11809b0bb
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/FindSanitizers.cmake
@@ -0,0 +1,94 @@
+# The MIT License (MIT)
+#
+# Copyright (c)
+# 2013 Matthew Arsenault
+# 2015-2016 RWTH Aachen University, Federal Republic of Germany
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# If any of the used compiler is a GNU compiler, add a second option to static
+# link against the sanitizers.
+option(SANITIZE_LINK_STATIC "Try to link static against sanitizers." Off)
+
+
+
+
+set(FIND_QUIETLY_FLAG "")
+if (DEFINED Sanitizers_FIND_QUIETLY)
+ set(FIND_QUIETLY_FLAG "QUIET")
+endif ()
+
+find_package(ASan ${FIND_QUIETLY_FLAG})
+find_package(TSan ${FIND_QUIETLY_FLAG})
+find_package(MSan ${FIND_QUIETLY_FLAG})
+find_package(UBSan ${FIND_QUIETLY_FLAG})
+
+
+
+
+function(sanitizer_add_blacklist_file FILE)
+ if(NOT IS_ABSOLUTE ${FILE})
+ set(FILE "${CMAKE_CURRENT_SOURCE_DIR}/${FILE}")
+ endif()
+ get_filename_component(FILE "${FILE}" REALPATH)
+
+ sanitizer_check_compiler_flags("-fsanitize-blacklist=${FILE}"
+ "SanitizerBlacklist" "SanBlist")
+endfunction()
+
+function(add_sanitizers ...)
+ # If no sanitizer is enabled, return immediately.
+ if (NOT (SANITIZE_ADDRESS OR SANITIZE_MEMORY OR SANITIZE_THREAD OR
+ SANITIZE_UNDEFINED))
+ return()
+ endif ()
+
+ foreach (TARGET ${ARGV})
+ # Check if this target will be compiled by exactly one compiler. Other-
+ # wise sanitizers can't be used and a warning should be printed once.
+ get_target_property(TARGET_TYPE ${TARGET} TYPE)
+ if (TARGET_TYPE STREQUAL "INTERFACE_LIBRARY")
+ message(WARNING "Can't use any sanitizers for target ${TARGET}, "
+ "because it is an interface library and cannot be "
+ "compiled directly.")
+ return()
+ endif ()
+ sanitizer_target_compilers(${TARGET} TARGET_COMPILER)
+ list(LENGTH TARGET_COMPILER NUM_COMPILERS)
+ if (NUM_COMPILERS GREATER 1)
+ message(WARNING "Can't use any sanitizers for target ${TARGET}, "
+ "because it will be compiled by incompatible compilers. "
+ "Target will be compiled without sanitizers.")
+ return()
+
+ # If the target is compiled by no known compiler, ignore it.
+ elseif (NUM_COMPILERS EQUAL 0)
+ message(WARNING "Can't use any sanitizers for target ${TARGET}, "
+ "because it uses an unknown compiler. Target will be "
+ "compiled without sanitizers.")
+ return()
+ endif ()
+
+ # Add sanitizers for target.
+ add_sanitize_address(${TARGET})
+ add_sanitize_thread(${TARGET})
+ add_sanitize_memory(${TARGET})
+ add_sanitize_undefined(${TARGET})
+ endforeach ()
+endfunction(add_sanitizers)
diff --git a/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/FindTSan.cmake b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/FindTSan.cmake
new file mode 100644
index 0000000000..3cba3c03b6
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/FindTSan.cmake
@@ -0,0 +1,65 @@
+# The MIT License (MIT)
+#
+# Copyright (c)
+# 2013 Matthew Arsenault
+# 2015-2016 RWTH Aachen University, Federal Republic of Germany
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+option(SANITIZE_THREAD "Enable ThreadSanitizer for sanitized targets." Off)
+
+set(FLAG_CANDIDATES
+ "-g -fsanitize=thread"
+)
+
+
+# ThreadSanitizer is not compatible with MemorySanitizer.
+if (SANITIZE_THREAD AND SANITIZE_MEMORY)
+ message(FATAL_ERROR "ThreadSanitizer is not compatible with "
+ "MemorySanitizer.")
+endif ()
+
+
+include(sanitize-helpers)
+
+if (SANITIZE_THREAD)
+ if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND
+ NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+ message(WARNING "ThreadSanitizer disabled for target ${TARGET} because "
+ "ThreadSanitizer is supported for Linux systems and macOS only.")
+ set(SANITIZE_THREAD Off CACHE BOOL
+ "Enable ThreadSanitizer for sanitized targets." FORCE)
+ elseif (NOT ${CMAKE_SIZEOF_VOID_P} EQUAL 8)
+ message(WARNING "ThreadSanitizer disabled for target ${TARGET} because "
+ "ThreadSanitizer is supported for 64bit systems only.")
+ set(SANITIZE_THREAD Off CACHE BOOL
+ "Enable ThreadSanitizer for sanitized targets." FORCE)
+ else ()
+ sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "ThreadSanitizer"
+ "TSan")
+ endif ()
+endif ()
+
+function (add_sanitize_thread TARGET)
+ if (NOT SANITIZE_THREAD)
+ return()
+ endif ()
+
+ sanitizer_add_flags(${TARGET} "ThreadSanitizer" "TSan")
+endfunction ()
diff --git a/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/FindUBSan.cmake b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/FindUBSan.cmake
new file mode 100644
index 0000000000..ae103f7172
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/FindUBSan.cmake
@@ -0,0 +1,46 @@
+# The MIT License (MIT)
+#
+# Copyright (c)
+# 2013 Matthew Arsenault
+# 2015-2016 RWTH Aachen University, Federal Republic of Germany
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+option(SANITIZE_UNDEFINED
+ "Enable UndefinedBehaviorSanitizer for sanitized targets." Off)
+
+set(FLAG_CANDIDATES
+ "-g -fsanitize=undefined"
+)
+
+
+include(sanitize-helpers)
+
+if (SANITIZE_UNDEFINED)
+ sanitizer_check_compiler_flags("${FLAG_CANDIDATES}"
+ "UndefinedBehaviorSanitizer" "UBSan")
+endif ()
+
+function (add_sanitize_undefined TARGET)
+ if (NOT SANITIZE_UNDEFINED)
+ return()
+ endif ()
+
+ sanitizer_add_flags(${TARGET} "UndefinedBehaviorSanitizer" "UBSan")
+endfunction ()
diff --git a/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/asan-wrapper b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/asan-wrapper
new file mode 100755
index 0000000000..5d54103372
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/asan-wrapper
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+# The MIT License (MIT)
+#
+# Copyright (c)
+# 2013 Matthew Arsenault
+# 2015-2016 RWTH Aachen University, Federal Republic of Germany
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# This script is a wrapper for AddressSanitizer. In some special cases you need
+# to preload AddressSanitizer to avoid error messages - e.g. if you're
+# preloading another library to your application. At the moment this script will
+# only do something, if we're running on a Linux platform. OSX might not be
+# affected.
+
+
+# Exit immediately, if platform is not Linux.
+if [ "$(uname)" != "Linux" ]
+then
+ exec $@
+fi
+
+
+# Get the used libasan of the application ($1). If a libasan was found, it will
+# be prepended to LD_PRELOAD.
+libasan=$(ldd $1 | grep libasan | sed "s/^[[:space:]]//" | cut -d' ' -f1)
+if [ -n "$libasan" ]
+then
+ if [ -n "$LD_PRELOAD" ]
+ then
+ export LD_PRELOAD="$libasan:$LD_PRELOAD"
+ else
+ export LD_PRELOAD="$libasan"
+ fi
+fi
+
+# Execute the application.
+exec $@
diff --git a/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/sanitize-helpers.cmake b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/sanitize-helpers.cmake
new file mode 100644
index 0000000000..610f895742
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/cmake/sanitize-helpers.cmake
@@ -0,0 +1,172 @@
+# The MIT License (MIT)
+#
+# Copyright (c)
+# 2013 Matthew Arsenault
+# 2015-2016 RWTH Aachen University, Federal Republic of Germany
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# Helper function to get the language of a source file.
+function (sanitizer_lang_of_source FILE RETURN_VAR)
+ get_filename_component(LONGEST_EXT "${FILE}" EXT)
+ # Get shortest extension as some files can have dot in their names
+ string(REGEX REPLACE "^.*(\\.[^.]+)$" "\\1" FILE_EXT ${LONGEST_EXT})
+ string(TOLOWER "${FILE_EXT}" FILE_EXT)
+ string(SUBSTRING "${FILE_EXT}" 1 -1 FILE_EXT)
+
+ get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
+ foreach (LANG ${ENABLED_LANGUAGES})
+ list(FIND CMAKE_${LANG}_SOURCE_FILE_EXTENSIONS "${FILE_EXT}" TEMP)
+ if (NOT ${TEMP} EQUAL -1)
+ set(${RETURN_VAR} "${LANG}" PARENT_SCOPE)
+ return()
+ endif ()
+ endforeach()
+
+ set(${RETURN_VAR} "" PARENT_SCOPE)
+endfunction ()
+
+
+# Helper function to get compilers used by a target.
+function (sanitizer_target_compilers TARGET RETURN_VAR)
+ # Check if all sources for target use the same compiler. If a target uses
+ # e.g. C and Fortran mixed and uses different compilers (e.g. clang and
+ # gfortran) this can trigger huge problems, because different compilers may
+ # use different implementations for sanitizers.
+ set(BUFFER "")
+ get_target_property(TSOURCES ${TARGET} SOURCES)
+ foreach (FILE ${TSOURCES})
+ # If expression was found, FILE is a generator-expression for an object
+ # library. Object libraries will be ignored.
+ string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _file ${FILE})
+ if ("${_file}" STREQUAL "")
+ sanitizer_lang_of_source(${FILE} LANG)
+ if (LANG)
+ list(APPEND BUFFER ${CMAKE_${LANG}_COMPILER_ID})
+ endif ()
+ endif ()
+ endforeach ()
+
+ list(REMOVE_DUPLICATES BUFFER)
+ set(${RETURN_VAR} "${BUFFER}" PARENT_SCOPE)
+endfunction ()
+
+
+# Helper function to check compiler flags for language compiler.
+function (sanitizer_check_compiler_flag FLAG LANG VARIABLE)
+ if (${LANG} STREQUAL "C")
+ include(CheckCCompilerFlag)
+ check_c_compiler_flag("${FLAG}" ${VARIABLE})
+
+ elseif (${LANG} STREQUAL "CXX")
+ include(CheckCXXCompilerFlag)
+ check_cxx_compiler_flag("${FLAG}" ${VARIABLE})
+
+ elseif (${LANG} STREQUAL "Fortran")
+ # CheckFortranCompilerFlag was introduced in CMake 3.x. To be compatible
+ # with older Cmake versions, we will check if this module is present
+ # before we use it. Otherwise we will define Fortran coverage support as
+ # not available.
+ include(CheckFortranCompilerFlag OPTIONAL RESULT_VARIABLE INCLUDED)
+ if (INCLUDED)
+ check_fortran_compiler_flag("${FLAG}" ${VARIABLE})
+ elseif (NOT CMAKE_REQUIRED_QUIET)
+ message(STATUS "Performing Test ${VARIABLE}")
+ message(STATUS "Performing Test ${VARIABLE}"
+ " - Failed (Check not supported)")
+ endif ()
+ endif()
+endfunction ()
+
+
+# Helper function to test compiler flags.
+function (sanitizer_check_compiler_flags FLAG_CANDIDATES NAME PREFIX)
+ set(CMAKE_REQUIRED_QUIET ${${PREFIX}_FIND_QUIETLY})
+
+ get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
+ foreach (LANG ${ENABLED_LANGUAGES})
+ # Sanitizer flags are not dependend on language, but the used compiler.
+ # So instead of searching flags foreach language, search flags foreach
+ # compiler used.
+ set(COMPILER ${CMAKE_${LANG}_COMPILER_ID})
+ if (NOT DEFINED ${PREFIX}_${COMPILER}_FLAGS)
+ foreach (FLAG ${FLAG_CANDIDATES})
+ if(NOT CMAKE_REQUIRED_QUIET)
+ message(STATUS "Try ${COMPILER} ${NAME} flag = [${FLAG}]")
+ endif()
+
+ set(CMAKE_REQUIRED_FLAGS "${FLAG}")
+ unset(${PREFIX}_FLAG_DETECTED CACHE)
+ sanitizer_check_compiler_flag("${FLAG}" ${LANG}
+ ${PREFIX}_FLAG_DETECTED)
+
+ if (${PREFIX}_FLAG_DETECTED)
+ # If compiler is a GNU compiler, search for static flag, if
+ # SANITIZE_LINK_STATIC is enabled.
+ if (SANITIZE_LINK_STATIC AND (${COMPILER} STREQUAL "GNU"))
+ string(TOLOWER ${PREFIX} PREFIX_lower)
+ sanitizer_check_compiler_flag(
+ "-static-lib${PREFIX_lower}" ${LANG}
+ ${PREFIX}_STATIC_FLAG_DETECTED)
+
+ if (${PREFIX}_STATIC_FLAG_DETECTED)
+ set(FLAG "-static-lib${PREFIX_lower} ${FLAG}")
+ endif ()
+ endif ()
+
+ set(${PREFIX}_${COMPILER}_FLAGS "${FLAG}" CACHE STRING
+ "${NAME} flags for ${COMPILER} compiler.")
+ mark_as_advanced(${PREFIX}_${COMPILER}_FLAGS)
+ break()
+ endif ()
+ endforeach ()
+
+ if (NOT ${PREFIX}_FLAG_DETECTED)
+ set(${PREFIX}_${COMPILER}_FLAGS "" CACHE STRING
+ "${NAME} flags for ${COMPILER} compiler.")
+ mark_as_advanced(${PREFIX}_${COMPILER}_FLAGS)
+
+ message(WARNING "${NAME} is not available for ${COMPILER} "
+ "compiler. Targets using this compiler will be "
+ "compiled without ${NAME}.")
+ endif ()
+ endif ()
+ endforeach ()
+endfunction ()
+
+
+# Helper to assign sanitizer flags for TARGET.
+function (sanitizer_add_flags TARGET NAME PREFIX)
+ # Get list of compilers used by target and check, if sanitizer is available
+ # for this target. Other compiler checks like check for conflicting
+ # compilers will be done in add_sanitizers function.
+ sanitizer_target_compilers(${TARGET} TARGET_COMPILER)
+ list(LENGTH TARGET_COMPILER NUM_COMPILERS)
+ if ("${${PREFIX}_${TARGET_COMPILER}_FLAGS}" STREQUAL "")
+ return()
+ endif()
+
+ # Set compile- and link-flags for target.
+ set_property(TARGET ${TARGET} APPEND_STRING
+ PROPERTY COMPILE_FLAGS " ${${PREFIX}_${TARGET_COMPILER}_FLAGS}")
+ set_property(TARGET ${TARGET} APPEND_STRING
+ PROPERTY COMPILE_FLAGS " ${SanBlist_${TARGET_COMPILER}_FLAGS}")
+ set_property(TARGET ${TARGET} APPEND_STRING
+ PROPERTY LINK_FLAGS " ${${PREFIX}_${TARGET_COMPILER}_FLAGS}")
+endfunction ()
diff --git a/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/tests/CMakeLists.txt b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/tests/CMakeLists.txt
new file mode 100644
index 0000000000..8553e63267
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/tests/CMakeLists.txt
@@ -0,0 +1,67 @@
+# This file is part of CMake-sanitizers.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+#
+# Copyright (c)
+# 2013-2015 Matt Arsenault
+# 2015 RWTH Aachen University, Federal Republic of Germany
+#
+
+# Function to add testcases.
+function(add_testcase TESTNAME SOURCEFILES)
+ # remove ${TESTNAME} from ${ARGV} to use ${ARGV} as ${SOURCEFILES}
+ list(REMOVE_AT ARGV 0)
+
+ # add a new executable
+ add_executable(${TESTNAME} ${ARGV})
+
+ # add a testcase for executable
+ add_test(${TESTNAME} ${TESTNAME})
+endfunction(add_testcase)
+
+# Function to add testcases with asan enabled.
+function(add_sanitized_testcase TESTNAME SOURCEFILES)
+ add_testcase(${TESTNAME} ${SOURCEFILES})
+ add_sanitizers(${TESTNAME})
+endfunction(add_sanitized_testcase)
+
+
+
+set(SANITIZE_ADDRESS TRUE)
+
+#
+# search for sanitizers
+#
+find_package(Sanitizers)
+
+
+#
+# add testcases
+#
+add_sanitized_testcase("asan_test_cpp" asan_test.cpp)
+add_sanitized_testcase("shortest_ext_test_cpp" shortest.ext.test.cpp)
+
+set_tests_properties(
+ "asan_test_cpp"
+ "shortest_ext_test_cpp"
+PROPERTIES
+ WILL_FAIL TRUE
+)
+
diff --git a/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/tests/asan_test.cpp b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/tests/asan_test.cpp
new file mode 100644
index 0000000000..6c0a370c36
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/tests/asan_test.cpp
@@ -0,0 +1,40 @@
+/* This file is part of CMake-sanitizers.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ *
+ * Copyright (c)
+ * 2013-2015 Matt Arsenault
+ * 2015 RWTH Aachen University, Federal Republic of Germany
+ */
+
+
+int
+main(int argc, char **argv)
+{
+ // Allocate a new array and delete it.
+ int *array = new int[argc + 1];
+ array[argc] = 0;
+ delete[] array;
+
+ /* Access element of the deleted array. This will cause an memory error with
+ * address sanitizer.
+ */
+ return array[argc];
+}
diff --git a/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/tests/shortest.ext.test.cpp b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/tests/shortest.ext.test.cpp
new file mode 100644
index 0000000000..6c0a370c36
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/cmake/sanitizers-cmake/tests/shortest.ext.test.cpp
@@ -0,0 +1,40 @@
+/* This file is part of CMake-sanitizers.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ *
+ * Copyright (c)
+ * 2013-2015 Matt Arsenault
+ * 2015 RWTH Aachen University, Federal Republic of Germany
+ */
+
+
+int
+main(int argc, char **argv)
+{
+ // Allocate a new array and delete it.
+ int *array = new int[argc + 1];
+ array[argc] = 0;
+ delete[] array;
+
+ /* Access element of the deleted array. This will cause an memory error with
+ * address sanitizer.
+ */
+ return array[argc];
+}
diff --git a/third_party/rust/cubeb-sys/libcubeb/cmake/toolchain-cross-mingw.cmake b/third_party/rust/cubeb-sys/libcubeb/cmake/toolchain-cross-mingw.cmake
new file mode 100644
index 0000000000..6d29fe2405
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/cmake/toolchain-cross-mingw.cmake
@@ -0,0 +1,14 @@
+SET(CMAKE_SYSTEM_NAME Windows)
+
+set(COMPILER_PREFIX "i686-w64-mingw32")
+
+find_program(CMAKE_RC_COMPILER NAMES ${COMPILER_PREFIX}-windres)
+find_program(CMAKE_C_COMPILER NAMES ${COMPILER_PREFIX}-gcc-posix)
+find_program(CMAKE_CXX_COMPILER NAMES ${COMPILER_PREFIX}-g++-posix)
+
+SET(CMAKE_FIND_ROOT_PATH /usr/${COMPILER_PREFIX})
+
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+
diff --git a/third_party/rust/cubeb-sys/libcubeb/cubeb.supp b/third_party/rust/cubeb-sys/libcubeb/cubeb.supp
new file mode 100644
index 0000000000..0012ea51e6
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/cubeb.supp
@@ -0,0 +1,36 @@
+{
+ snd_config_update-malloc
+ Memcheck:Leak
+ fun:malloc
+ ...
+ fun:snd_config_update_r
+}
+{
+ snd1_dlobj_cache_get-malloc
+ Memcheck:Leak
+ fun:malloc
+ ...
+ fun:snd1_dlobj_cache_get
+}
+{
+ parse_defs-malloc
+ Memcheck:Leak
+ fun:malloc
+ ...
+ fun:parse_defs
+}
+{
+ parse_defs-calloc
+ Memcheck:Leak
+ fun:calloc
+ ...
+ fun:parse_defs
+}
+{
+ pa_client_conf_from_x11-malloc
+ Memcheck:Leak
+ fun:malloc
+ ...
+ fun:pa_client_conf_from_x11
+}
+
diff --git a/third_party/rust/cubeb-sys/libcubeb/docs/Doxyfile.in b/third_party/rust/cubeb-sys/libcubeb/docs/Doxyfile.in
new file mode 100644
index 0000000000..8ec863be8e
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/docs/Doxyfile.in
@@ -0,0 +1,12 @@
+PROJECT_NAME = @PROJECT_NAME@
+PROJECT_NUMBER = @PROJECT_VERSION@
+OUTPUT_DIRECTORY = .
+JAVADOC_AUTOBRIEF = YES
+OPTIMIZE_OUTPUT_FOR_C = YES
+CASE_SENSE_NAMES = NO
+SORT_MEMBER_DOCS = NO
+QUIET = YES
+WARN_NO_PARAMDOC = YES
+INPUT = @CMAKE_CURRENT_SOURCE_DIR@/include/cubeb
+GENERATE_HTML = YES
+GENERATE_LATEX = NO
diff --git a/third_party/rust/cubeb-sys/libcubeb/include/cubeb/cubeb.h b/third_party/rust/cubeb-sys/libcubeb/include/cubeb/cubeb.h
new file mode 100644
index 0000000000..17ead45b1b
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/include/cubeb/cubeb.h
@@ -0,0 +1,777 @@
+/*
+ * Copyright © 2011 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#if !defined(CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382)
+#define CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382
+
+#include "cubeb_export.h"
+#include <stdint.h>
+#include <stdlib.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/** @mainpage
+
+ @section intro Introduction
+
+ This is the documentation for the <tt>libcubeb</tt> C API.
+ <tt>libcubeb</tt> is a callback-based audio API library allowing the
+ authoring of portable multiplatform audio playback and recording.
+
+ @section example Example code
+
+ This example shows how to create a duplex stream that pipes the microphone
+ to the speakers, with minimal latency and the proper sample-rate for the
+ platform.
+
+ @code
+ cubeb * app_ctx;
+ cubeb_init(&app_ctx, "Example Application", NULL);
+ int rv;
+ uint32_t rate;
+ uint32_t latency_frames;
+ uint64_t ts;
+
+ rv = cubeb_get_preferred_sample_rate(app_ctx, &rate);
+ if (rv != CUBEB_OK) {
+ fprintf(stderr, "Could not get preferred sample-rate");
+ return rv;
+ }
+
+ cubeb_stream_params output_params;
+ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ output_params.rate = rate;
+ output_params.channels = 2;
+ output_params.layout = CUBEB_LAYOUT_UNDEFINED;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ rv = cubeb_get_min_latency(app_ctx, &output_params, &latency_frames);
+ if (rv != CUBEB_OK) {
+ fprintf(stderr, "Could not get minimum latency");
+ return rv;
+ }
+
+ cubeb_stream_params input_params;
+ input_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ input_params.rate = rate;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_UNDEFINED;
+ input_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ cubeb_stream * stm;
+ rv = cubeb_stream_init(app_ctx, &stm, "Example Stream 1",
+ NULL, &input_params,
+ NULL, &output_params,
+ latency_frames,
+ data_cb, state_cb,
+ NULL);
+ if (rv != CUBEB_OK) {
+ fprintf(stderr, "Could not open the stream");
+ return rv;
+ }
+
+ rv = cubeb_stream_start(stm);
+ if (rv != CUBEB_OK) {
+ fprintf(stderr, "Could not start the stream");
+ return rv;
+ }
+ for (;;) {
+ cubeb_stream_get_position(stm, &ts);
+ printf("time=%llu\n", ts);
+ sleep(1);
+ }
+ rv = cubeb_stream_stop(stm);
+ if (rv != CUBEB_OK) {
+ fprintf(stderr, "Could not stop the stream");
+ return rv;
+ }
+
+ cubeb_stream_destroy(stm);
+ cubeb_destroy(app_ctx);
+ @endcode
+
+ @code
+ long data_cb(cubeb_stream * stm, void * user,
+ const void * input_buffer, void * output_buffer, long nframes)
+ {
+ const float * in = input_buffer;
+ float * out = output_buffer;
+
+ for (int i = 0; i < nframes; ++i) {
+ for (int c = 0; c < 2; ++c) {
+ out[2 * i + c] = in[i];
+ }
+ }
+ return nframes;
+ }
+ @endcode
+
+ @code
+ void state_cb(cubeb_stream * stm, void * user, cubeb_state state)
+ {
+ printf("state=%d\n", state);
+ }
+ @endcode
+*/
+
+/** @file
+ The <tt>libcubeb</tt> C API. */
+
+typedef struct cubeb
+ cubeb; /**< Opaque handle referencing the application state. */
+typedef struct cubeb_stream
+ cubeb_stream; /**< Opaque handle referencing the stream state. */
+
+/** Sample format enumeration. */
+typedef enum {
+ /**< Little endian 16-bit signed PCM. */
+ CUBEB_SAMPLE_S16LE,
+ /**< Big endian 16-bit signed PCM. */
+ CUBEB_SAMPLE_S16BE,
+ /**< Little endian 32-bit IEEE floating point PCM. */
+ CUBEB_SAMPLE_FLOAT32LE,
+ /**< Big endian 32-bit IEEE floating point PCM. */
+ CUBEB_SAMPLE_FLOAT32BE,
+#if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__)
+ /**< Native endian 16-bit signed PCM. */
+ CUBEB_SAMPLE_S16NE = CUBEB_SAMPLE_S16BE,
+ /**< Native endian 32-bit IEEE floating point PCM. */
+ CUBEB_SAMPLE_FLOAT32NE = CUBEB_SAMPLE_FLOAT32BE
+#else
+ /**< Native endian 16-bit signed PCM. */
+ CUBEB_SAMPLE_S16NE = CUBEB_SAMPLE_S16LE,
+ /**< Native endian 32-bit IEEE floating point PCM. */
+ CUBEB_SAMPLE_FLOAT32NE = CUBEB_SAMPLE_FLOAT32LE
+#endif
+} cubeb_sample_format;
+
+/** An opaque handle used to refer a particular input or output device
+ * across calls. */
+typedef void const * cubeb_devid;
+
+/** Level (verbosity) of logging for a particular cubeb context. */
+typedef enum {
+ CUBEB_LOG_DISABLED = 0, /** < Logging disabled */
+ CUBEB_LOG_NORMAL =
+ 1, /**< Logging lifetime operation (creation/destruction). */
+ CUBEB_LOG_VERBOSE = 2, /**< Verbose logging of callbacks, can have performance
+ implications. */
+} cubeb_log_level;
+
+/// A single channel position, to be used in a bitmask.
+typedef enum {
+ CHANNEL_UNKNOWN = 0,
+ CHANNEL_FRONT_LEFT = 1 << 0,
+ CHANNEL_FRONT_RIGHT = 1 << 1,
+ CHANNEL_FRONT_CENTER = 1 << 2,
+ CHANNEL_LOW_FREQUENCY = 1 << 3,
+ CHANNEL_BACK_LEFT = 1 << 4,
+ CHANNEL_BACK_RIGHT = 1 << 5,
+ CHANNEL_FRONT_LEFT_OF_CENTER = 1 << 6,
+ CHANNEL_FRONT_RIGHT_OF_CENTER = 1 << 7,
+ CHANNEL_BACK_CENTER = 1 << 8,
+ CHANNEL_SIDE_LEFT = 1 << 9,
+ CHANNEL_SIDE_RIGHT = 1 << 10,
+ CHANNEL_TOP_CENTER = 1 << 11,
+ CHANNEL_TOP_FRONT_LEFT = 1 << 12,
+ CHANNEL_TOP_FRONT_CENTER = 1 << 13,
+ CHANNEL_TOP_FRONT_RIGHT = 1 << 14,
+ CHANNEL_TOP_BACK_LEFT = 1 << 15,
+ CHANNEL_TOP_BACK_CENTER = 1 << 16,
+ CHANNEL_TOP_BACK_RIGHT = 1 << 17
+} cubeb_channel;
+
+/// A bitmask representing the channel layout of a cubeb stream. This is
+/// bit-compatible with WAVEFORMATEXENSIBLE and in the same order as the SMPTE
+/// ordering.
+typedef uint32_t cubeb_channel_layout;
+// Some common layout definitions.
+enum {
+ CUBEB_LAYOUT_UNDEFINED = 0, // Indicate the speaker's layout is undefined.
+ CUBEB_LAYOUT_MONO = CHANNEL_FRONT_CENTER,
+ CUBEB_LAYOUT_MONO_LFE = CUBEB_LAYOUT_MONO | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_STEREO = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT,
+ CUBEB_LAYOUT_STEREO_LFE = CUBEB_LAYOUT_STEREO | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F =
+ CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_FRONT_CENTER,
+ CUBEB_LAYOUT_3F_LFE = CUBEB_LAYOUT_3F | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_2F1 =
+ CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_BACK_CENTER,
+ CUBEB_LAYOUT_2F1_LFE = CUBEB_LAYOUT_2F1 | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F1 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_BACK_CENTER,
+ CUBEB_LAYOUT_3F1_LFE = CUBEB_LAYOUT_3F1 | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_2F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT,
+ CUBEB_LAYOUT_2F2_LFE = CUBEB_LAYOUT_2F2 | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_QUAD = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT,
+ CUBEB_LAYOUT_QUAD_LFE = CUBEB_LAYOUT_QUAD | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_SIDE_LEFT |
+ CHANNEL_SIDE_RIGHT,
+ CUBEB_LAYOUT_3F2_LFE = CUBEB_LAYOUT_3F2 | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F2_BACK = CUBEB_LAYOUT_QUAD | CHANNEL_FRONT_CENTER,
+ CUBEB_LAYOUT_3F2_LFE_BACK = CUBEB_LAYOUT_3F2_BACK | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F3R_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY |
+ CHANNEL_BACK_CENTER | CHANNEL_SIDE_LEFT |
+ CHANNEL_SIDE_RIGHT,
+ CUBEB_LAYOUT_3F4_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY |
+ CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT |
+ CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT,
+};
+
+/** Miscellaneous stream preferences. */
+typedef enum {
+ CUBEB_STREAM_PREF_NONE = 0x00, /**< No stream preferences are requested. */
+ CUBEB_STREAM_PREF_LOOPBACK =
+ 0x01, /**< Request a loopback stream. Should be
+ specified on the input params and an
+ output device to loopback from should
+ be passed in place of an input device. */
+ CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING = 0x02, /**< Disable switching
+ default device on OS
+ changes. */
+ CUBEB_STREAM_PREF_VOICE =
+ 0x04, /**< This stream is going to transport voice data.
+ Depending on the backend and platform, this can
+ change the audio input or output devices
+ selected, as well as the quality of the stream,
+ for example to accomodate bluetooth SCO modes on
+ bluetooth devices. */
+ CUBEB_STREAM_PREF_RAW =
+ 0x08, /**< Windows only. Bypass all signal processing
+ except for always on APO, driver and hardware. */
+ CUBEB_STREAM_PREF_PERSIST = 0x10, /**< Request that the volume and mute
+ settings should persist across restarts
+ of the stream and/or application. This is
+ obsolete and ignored by all backends. */
+ CUBEB_STREAM_PREF_JACK_NO_AUTO_CONNECT = 0x20 /**< Don't automatically try to
+ connect ports. Only affects
+ the jack backend. */
+} cubeb_stream_prefs;
+
+/**
+ * Input stream audio processing parameters. Only applicable with
+ * CUBEB_STREAM_PREF_VOICE.
+ */
+typedef enum {
+ CUBEB_INPUT_PROCESSING_PARAM_NONE = 0x00,
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION = 0x01,
+ CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION = 0x02,
+ CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL = 0x04,
+ CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION = 0x08,
+} cubeb_input_processing_params;
+
+/** Stream format initialization parameters. */
+typedef struct {
+ cubeb_sample_format format; /**< Requested sample format. One of
+ #cubeb_sample_format. */
+ uint32_t rate; /**< Requested sample rate. Valid range is [1000, 192000]. */
+ uint32_t channels; /**< Requested channel count. Valid range is [1, 8]. */
+ cubeb_channel_layout
+ layout; /**< Requested channel layout. This must be consistent with the
+ provided channels. CUBEB_LAYOUT_UNDEFINED if unknown */
+ cubeb_stream_prefs prefs; /**< Requested preferences. */
+} cubeb_stream_params;
+
+/** Audio device description */
+typedef struct {
+ char * output_name; /**< The name of the output device */
+ char * input_name; /**< The name of the input device */
+} cubeb_device;
+
+/** Stream states signaled via state_callback. */
+typedef enum {
+ CUBEB_STATE_STARTED, /**< Stream started. */
+ CUBEB_STATE_STOPPED, /**< Stream stopped. */
+ CUBEB_STATE_DRAINED, /**< Stream drained. */
+ CUBEB_STATE_ERROR /**< Stream disabled due to error. */
+} cubeb_state;
+
+/** Result code enumeration. */
+enum {
+ CUBEB_OK = 0, /**< Success. */
+ CUBEB_ERROR = -1, /**< Unclassified error. */
+ CUBEB_ERROR_INVALID_FORMAT =
+ -2, /**< Unsupported #cubeb_stream_params requested. */
+ CUBEB_ERROR_INVALID_PARAMETER = -3, /**< Invalid parameter specified. */
+ CUBEB_ERROR_NOT_SUPPORTED =
+ -4, /**< Optional function not implemented in current backend. */
+ CUBEB_ERROR_DEVICE_UNAVAILABLE =
+ -5 /**< Device specified by #cubeb_devid not available. */
+};
+
+/**
+ * Whether a particular device is an input device (e.g. a microphone), or an
+ * output device (e.g. headphones). */
+typedef enum {
+ CUBEB_DEVICE_TYPE_UNKNOWN,
+ CUBEB_DEVICE_TYPE_INPUT,
+ CUBEB_DEVICE_TYPE_OUTPUT
+} cubeb_device_type;
+
+/**
+ * The state of a device.
+ */
+typedef enum {
+ CUBEB_DEVICE_STATE_DISABLED, /**< The device has been disabled at the system
+ level. */
+ CUBEB_DEVICE_STATE_UNPLUGGED, /**< The device is enabled, but nothing is
+ plugged into it. */
+ CUBEB_DEVICE_STATE_ENABLED /**< The device is enabled. */
+} cubeb_device_state;
+
+/**
+ * Architecture specific sample type.
+ */
+typedef enum {
+ CUBEB_DEVICE_FMT_S16LE = 0x0010, /**< 16-bit integers, Little Endian. */
+ CUBEB_DEVICE_FMT_S16BE = 0x0020, /**< 16-bit integers, Big Endian. */
+ CUBEB_DEVICE_FMT_F32LE = 0x1000, /**< 32-bit floating point, Little Endian. */
+ CUBEB_DEVICE_FMT_F32BE = 0x2000 /**< 32-bit floating point, Big Endian. */
+} cubeb_device_fmt;
+
+#if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__)
+/** 16-bit integers, native endianess, when on a Big Endian environment. */
+#define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16BE
+/** 32-bit floating points, native endianess, when on a Big Endian environment.
+ */
+#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32BE
+#else
+/** 16-bit integers, native endianess, when on a Little Endian environment. */
+#define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16LE
+/** 32-bit floating points, native endianess, when on a Little Endian
+ * environment. */
+#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32LE
+#endif
+/** All the 16-bit integers types. */
+#define CUBEB_DEVICE_FMT_S16_MASK \
+ (CUBEB_DEVICE_FMT_S16LE | CUBEB_DEVICE_FMT_S16BE)
+/** All the 32-bit floating points types. */
+#define CUBEB_DEVICE_FMT_F32_MASK \
+ (CUBEB_DEVICE_FMT_F32LE | CUBEB_DEVICE_FMT_F32BE)
+/** All the device formats types. */
+#define CUBEB_DEVICE_FMT_ALL \
+ (CUBEB_DEVICE_FMT_S16_MASK | CUBEB_DEVICE_FMT_F32_MASK)
+
+/** Channel type for a `cubeb_stream`. Depending on the backend and platform
+ * used, this can control inter-stream interruption, ducking, and volume
+ * control.
+ */
+typedef enum {
+ CUBEB_DEVICE_PREF_NONE = 0x00,
+ CUBEB_DEVICE_PREF_MULTIMEDIA = 0x01,
+ CUBEB_DEVICE_PREF_VOICE = 0x02,
+ CUBEB_DEVICE_PREF_NOTIFICATION = 0x04,
+ CUBEB_DEVICE_PREF_ALL = 0x0F
+} cubeb_device_pref;
+
+/** This structure holds the characteristics
+ * of an input or output audio device. It is obtained using
+ * `cubeb_enumerate_devices`, which returns these structures via
+ * `cubeb_device_collection` and must be destroyed via
+ * `cubeb_device_collection_destroy`. */
+typedef struct {
+ cubeb_devid devid; /**< Device identifier handle. */
+ char const *
+ device_id; /**< Device identifier which might be presented in a UI. */
+ char const * friendly_name; /**< Friendly device name which might be presented
+ in a UI. */
+ char const * group_id; /**< Two devices have the same group identifier if they
+ belong to the same physical device; for example a
+ headset and microphone. */
+ char const * vendor_name; /**< Optional vendor name, may be NULL. */
+
+ cubeb_device_type type; /**< Type of device (Input/Output). */
+ cubeb_device_state state; /**< State of device disabled/enabled/unplugged. */
+ cubeb_device_pref preferred; /**< Preferred device. */
+
+ cubeb_device_fmt format; /**< Sample format supported. */
+ cubeb_device_fmt
+ default_format; /**< The default sample format for this device. */
+ uint32_t max_channels; /**< Channels. */
+ uint32_t default_rate; /**< Default/Preferred sample rate. */
+ uint32_t max_rate; /**< Maximum sample rate supported. */
+ uint32_t min_rate; /**< Minimum sample rate supported. */
+
+ uint32_t latency_lo; /**< Lowest possible latency in frames. */
+ uint32_t latency_hi; /**< Higest possible latency in frames. */
+} cubeb_device_info;
+
+/** Device collection.
+ * Returned by `cubeb_enumerate_devices` and destroyed by
+ * `cubeb_device_collection_destroy`. */
+typedef struct {
+ cubeb_device_info * device; /**< Array of pointers to device info. */
+ size_t count; /**< Device count in collection. */
+} cubeb_device_collection;
+
+/** User supplied data callback.
+ - Calling other cubeb functions from this callback is unsafe.
+ - The code in the callback should be non-blocking.
+ - Returning less than the number of frames this callback asks for or
+ provides puts the stream in drain mode. This callback will not be called
+ again, and the state callback will be called with CUBEB_STATE_DRAINED when
+ all the frames have been output.
+ @param stream The stream for which this callback fired.
+ @param user_ptr The pointer passed to cubeb_stream_init.
+ @param input_buffer A pointer containing the input data, or nullptr
+ if this is an output-only stream.
+ @param output_buffer A pointer to a buffer to be filled with audio samples,
+ or nullptr if this is an input-only stream.
+ @param nframes The number of frames of the two buffer.
+ @retval If the stream has output, this is the number of frames written to
+ the output buffer. In this case, if this number is less than
+ nframes then the stream will start to drain. If the stream is
+ input only, then returning nframes indicates data has been read.
+ In this case, a value less than nframes will result in the stream
+ being stopped.
+ @retval CUBEB_ERROR on error, in which case the data callback will stop
+ and the stream will enter a shutdown state. */
+typedef long (*cubeb_data_callback)(cubeb_stream * stream, void * user_ptr,
+ void const * input_buffer,
+ void * output_buffer, long nframes);
+
+/** User supplied state callback.
+ @param stream The stream for this this callback fired.
+ @param user_ptr The pointer passed to cubeb_stream_init.
+ @param state The new state of the stream. */
+typedef void (*cubeb_state_callback)(cubeb_stream * stream, void * user_ptr,
+ cubeb_state state);
+
+/**
+ * User supplied callback called when the underlying device changed.
+ * @param user_ptr The pointer passed to cubeb_stream_init. */
+typedef void (*cubeb_device_changed_callback)(void * user_ptr);
+
+/**
+ * User supplied callback called when the underlying device collection changed.
+ * @param context A pointer to the cubeb context.
+ * @param user_ptr The pointer passed to
+ * cubeb_register_device_collection_changed. */
+typedef void (*cubeb_device_collection_changed_callback)(cubeb * context,
+ void * user_ptr);
+
+/** User supplied callback called when a message needs logging. */
+typedef void (*cubeb_log_callback)(char const * fmt, ...);
+
+/** Initialize an application context. This will perform any library or
+ application scoped initialization.
+
+ Note: On Windows platforms, COM must be initialized in MTA mode on
+ any thread that will call the cubeb API.
+
+ @param context A out param where an opaque pointer to the application
+ context will be returned.
+ @param context_name A name for the context. Depending on the platform this
+ can appear in different locations.
+ @param backend_name The name of the cubeb backend user desires to select.
+ Accepted values self-documented in cubeb.c: init_oneshot
+ If NULL, a default ordering is used for backend choice.
+ A valid choice overrides all other possible backends,
+ so long as the backend was included at compile time.
+ @retval CUBEB_OK in case of success.
+ @retval CUBEB_ERROR in case of error, for example because the host
+ has no audio hardware. */
+CUBEB_EXPORT int
+cubeb_init(cubeb ** context, char const * context_name,
+ char const * backend_name);
+
+/** Get a read-only string identifying this context's current backend.
+ @param context A pointer to the cubeb context.
+ @retval Read-only string identifying current backend. */
+CUBEB_EXPORT char const *
+cubeb_get_backend_id(cubeb * context);
+
+/** Get the maximum possible number of channels.
+ @param context A pointer to the cubeb context.
+ @param max_channels The maximum number of channels.
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR_INVALID_PARAMETER
+ @retval CUBEB_ERROR_NOT_SUPPORTED
+ @retval CUBEB_ERROR */
+CUBEB_EXPORT int
+cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels);
+
+/** Get the minimal latency value, in frames, that is guaranteed to work
+ when creating a stream for the specified sample rate. This is platform,
+ hardware and backend dependent.
+ @param context A pointer to the cubeb context.
+ @param params On some backends, the minimum achievable latency depends on
+ the characteristics of the stream.
+ @param latency_frames The latency value, in frames, to pass to
+ cubeb_stream_init.
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR_INVALID_PARAMETER
+ @retval CUBEB_ERROR_NOT_SUPPORTED */
+CUBEB_EXPORT int
+cubeb_get_min_latency(cubeb * context, cubeb_stream_params * params,
+ uint32_t * latency_frames);
+
+/** Get the preferred sample rate for this backend: this is hardware and
+ platform dependent, and can avoid resampling, and/or trigger fastpaths.
+ @param context A pointer to the cubeb context.
+ @param rate The samplerate (in Hz) the current configuration prefers.
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR_INVALID_PARAMETER
+ @retval CUBEB_ERROR_NOT_SUPPORTED */
+CUBEB_EXPORT int
+cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate);
+
+/** Get the supported input processing features for this backend. See
+ cubeb_stream_set_input_processing for how to set them for a particular input
+ stream.
+ @param context A pointer to the cubeb context.
+ @param params Out parameter for the input processing params supported by
+ this backend.
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR_NOT_SUPPORTED */
+CUBEB_EXPORT int
+cubeb_get_supported_input_processing_params(
+ cubeb * context, cubeb_input_processing_params * params);
+
+/** Destroy an application context. This must be called after all stream have
+ * been destroyed.
+ @param context A pointer to the cubeb context.*/
+CUBEB_EXPORT void
+cubeb_destroy(cubeb * context);
+
+/** Initialize a stream associated with the supplied application context.
+ @param context A pointer to the cubeb context.
+ @param stream An out parameter to be filled with the an opaque pointer to a
+ cubeb stream.
+ @param stream_name A name for this stream.
+ @param input_device Device for the input side of the stream. If NULL the
+ default input device is used. Passing a valid
+ cubeb_devid means the stream only ever uses that device. Passing a NULL
+ cubeb_devid allows the stream to follow that device
+ type's OS default.
+ @param input_stream_params Parameters for the input side of the stream, or
+ NULL if this stream is output only.
+ @param output_device Device for the output side of the stream. If NULL the
+ default output device is used. Passing a valid
+ cubeb_devid means the stream only ever uses that device. Passing a NULL
+ cubeb_devid allows the stream to follow that device
+ type's OS default.
+ @param output_stream_params Parameters for the output side of the stream, or
+ NULL if this stream is input only. When input
+ and output stream parameters are supplied, their
+ rate has to be the same.
+ @param latency_frames Stream latency in frames. Valid range
+ is [1, 96000].
+ @param data_callback Will be called to preroll data before playback is
+ started by cubeb_stream_start.
+ @param state_callback A pointer to a state callback.
+ @param user_ptr A pointer that will be passed to the callbacks. This pointer
+ must outlive the life time of the stream.
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR
+ @retval CUBEB_ERROR_INVALID_FORMAT
+ @retval CUBEB_ERROR_DEVICE_UNAVAILABLE */
+CUBEB_EXPORT int
+cubeb_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ uint32_t latency_frames, cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr);
+
+/** Destroy a stream. `cubeb_stream_stop` MUST be called before destroying a
+ stream.
+ @param stream The stream to destroy. */
+CUBEB_EXPORT void
+cubeb_stream_destroy(cubeb_stream * stream);
+
+/** Start playback.
+ @param stream
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR */
+CUBEB_EXPORT int
+cubeb_stream_start(cubeb_stream * stream);
+
+/** Stop playback.
+ @param stream
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR */
+CUBEB_EXPORT int
+cubeb_stream_stop(cubeb_stream * stream);
+
+/** Get the current stream playback position.
+ @param stream
+ @param position Playback position in frames.
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR */
+CUBEB_EXPORT int
+cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position);
+
+/** Get the latency for this stream, in frames. This is the number of frames
+ between the time cubeb acquires the data in the callback and the listener
+ can hear the sound.
+ @param stream
+ @param latency Current approximate stream latency in frames.
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR_NOT_SUPPORTED
+ @retval CUBEB_ERROR */
+CUBEB_EXPORT int
+cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency);
+
+/** Get the input latency for this stream, in frames. This is the number of
+ frames between the time the audio input devices records the data, and they
+ are available in the data callback.
+ This returns CUBEB_ERROR when the stream is output-only.
+ @param stream
+ @param latency Current approximate stream latency in frames.
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR_NOT_SUPPORTED
+ @retval CUBEB_ERROR */
+CUBEB_EXPORT int
+cubeb_stream_get_input_latency(cubeb_stream * stream, uint32_t * latency);
+/** Set the volume for a stream.
+ @param stream the stream for which to adjust the volume.
+ @param volume a float between 0.0 (muted) and 1.0 (maximum volume)
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR_INVALID_PARAMETER volume is outside [0.0, 1.0] or
+ stream is an invalid pointer
+ @retval CUBEB_ERROR_NOT_SUPPORTED */
+CUBEB_EXPORT int
+cubeb_stream_set_volume(cubeb_stream * stream, float volume);
+
+/** Change a stream's name.
+ @param stream the stream for which to set the name.
+ @param stream_name the new name for the stream
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR_INVALID_PARAMETER if any pointer is invalid
+ @retval CUBEB_ERROR_NOT_SUPPORTED */
+CUBEB_EXPORT int
+cubeb_stream_set_name(cubeb_stream * stream, char const * stream_name);
+
+/** Get the current output device for this stream.
+ @param stm the stream for which to query the current output device
+ @param device a pointer in which the current output device will be stored.
+ @retval CUBEB_OK in case of success
+ @retval CUBEB_ERROR_INVALID_PARAMETER if either stm, device or count are
+ invalid pointers
+ @retval CUBEB_ERROR_NOT_SUPPORTED */
+CUBEB_EXPORT int
+cubeb_stream_get_current_device(cubeb_stream * stm,
+ cubeb_device ** const device);
+
+/** Set input mute state for this stream. Some platforms notify the user when an
+ application is accessing audio input. When all inputs are muted they can
+ prove to the user that the application is not actively capturing any input.
+ @param stream the stream for which to set input mute state
+ @param muted whether the input should mute or not
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR_INVALID_PARAMETER if this stream does not have an input
+ device
+ @retval CUBEB_ERROR_NOT_SUPPORTED */
+CUBEB_EXPORT int
+cubeb_stream_set_input_mute(cubeb_stream * stream, int mute);
+
+/** Set what input processing features to enable for this stream.
+ @param stream the stream for which to set input processing features.
+ @param params what input processing features to use
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR if params could not be applied
+ @retval CUBEB_ERROR_INVALID_PARAMETER if a given param is not supported by
+ this backend, or if this stream does not have an input device
+ @retval CUBEB_ERROR_NOT_SUPPORTED */
+CUBEB_EXPORT int
+cubeb_stream_set_input_processing_params(cubeb_stream * stream,
+ cubeb_input_processing_params params);
+
+/** Destroy a cubeb_device structure.
+ @param stream the stream passed in cubeb_stream_get_current_device
+ @param devices the devices to destroy
+ @retval CUBEB_OK in case of success
+ @retval CUBEB_ERROR_INVALID_PARAMETER if devices is an invalid pointer
+ @retval CUBEB_ERROR_NOT_SUPPORTED */
+CUBEB_EXPORT int
+cubeb_stream_device_destroy(cubeb_stream * stream, cubeb_device * devices);
+
+/** Set a callback to be notified when the output device changes.
+ @param stream the stream for which to set the callback.
+ @param device_changed_callback a function called whenever the device has
+ changed. Passing NULL allow to unregister a function
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR_INVALID_PARAMETER if either stream or
+ device_changed_callback are invalid pointers.
+ @retval CUBEB_ERROR_NOT_SUPPORTED */
+CUBEB_EXPORT int
+cubeb_stream_register_device_changed_callback(
+ cubeb_stream * stream,
+ cubeb_device_changed_callback device_changed_callback);
+
+/** Return the user data pointer registered with the stream with
+ cubeb_stream_init.
+ @param stream the stream for which to retrieve user data pointer.
+ @retval user data pointer */
+CUBEB_EXPORT void *
+cubeb_stream_user_ptr(cubeb_stream * stream);
+
+/** Returns enumerated devices.
+ @param context
+ @param devtype device type to include
+ @param collection output collection. Must be destroyed with
+ cubeb_device_collection_destroy
+ @retval CUBEB_OK in case of success
+ @retval CUBEB_ERROR_INVALID_PARAMETER if collection is an invalid pointer
+ @retval CUBEB_ERROR_NOT_SUPPORTED */
+CUBEB_EXPORT int
+cubeb_enumerate_devices(cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection * collection);
+
+/** Destroy a cubeb_device_collection, and its `cubeb_device_info`.
+ @param context
+ @param collection collection to destroy
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR_INVALID_PARAMETER if collection is an invalid pointer */
+CUBEB_EXPORT int
+cubeb_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection);
+
+/** Registers a callback which is called when the system detects
+ a new device or a device is removed.
+ @param context
+ @param devtype device type to include. Different callbacks and user pointers
+ can be registered for each devtype. The hybrid devtype
+ `CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT` is also valid
+ and will register the provided callback and user pointer in both
+ sides.
+ @param callback a function called whenever the system device list changes.
+ Passing NULL allow to unregister a function. You have to unregister
+ first before you register a new callback.
+ @param user_ptr pointer to user specified data which will be present in
+ subsequent callbacks.
+ @retval CUBEB_ERROR_NOT_SUPPORTED */
+CUBEB_EXPORT int
+cubeb_register_device_collection_changed(
+ cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback callback, void * user_ptr);
+
+/** Set a callback to be called with a message.
+ @param log_level CUBEB_LOG_VERBOSE, CUBEB_LOG_NORMAL.
+ @param log_callback A function called with a message when there is
+ something to log. Pass NULL to unregister.
+ @retval CUBEB_OK in case of success.
+ @retval CUBEB_ERROR_INVALID_PARAMETER if either context or log_callback are
+ invalid pointers, or if level is not
+ in cubeb_log_level. */
+CUBEB_EXPORT int
+cubeb_set_log_callback(cubeb_log_level log_level,
+ cubeb_log_callback log_callback);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382 */
diff --git a/third_party/rust/cubeb-sys/libcubeb/scan-build-install.sh b/third_party/rust/cubeb-sys/libcubeb/scan-build-install.sh
new file mode 100755
index 0000000000..3e1b4969e7
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/scan-build-install.sh
@@ -0,0 +1,15 @@
+#!/bin/sh -x
+
+CLANG_CHECKER_NAME=checker-278
+
+cd ~
+
+if [ ! -d ~/$CLANG_CHECKER_NAME ]
+then
+ curl http://clang-analyzer.llvm.org/downloads/$CLANG_CHECKER_NAME.tar.bz2 -o ~/$CLANG_CHECKER_NAME.tar.bz2
+ tar -xf ~/$CLANG_CHECKER_NAME.tar.bz2
+fi
+
+export SCAN_BUILD_PATH=~/$CLANG_CHECKER_NAME/bin/scan-build
+
+cd -
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/android/audiotrack_definitions.h b/third_party/rust/cubeb-sys/libcubeb/src/android/audiotrack_definitions.h
new file mode 100644
index 0000000000..f6b6931fa4
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/android/audiotrack_definitions.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+
+/*
+ * The following definitions are copied from the android sources. Only the
+ * relevant enum member and values needed are copied.
+ */
+
+/*
+ * From
+ * https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/utils/Errors.h
+ */
+typedef int32_t status_t;
+
+/*
+ * From
+ * https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/media/AudioTrack.h
+ */
+struct Buffer {
+ uint32_t flags;
+ int channelCount;
+ int format;
+ size_t frameCount;
+ size_t size;
+ union {
+ void * raw;
+ short * i16;
+ int8_t * i8;
+ };
+};
+
+enum event_type {
+ EVENT_MORE_DATA = 0,
+ EVENT_UNDERRUN = 1,
+ EVENT_LOOP_END = 2,
+ EVENT_MARKER = 3,
+ EVENT_NEW_POS = 4,
+ EVENT_BUFFER_END = 5
+};
+
+/**
+ * From
+ * https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/media/AudioSystem.h
+ * and
+ * https://android.googlesource.com/platform/system/core/+/android-4.2.2_r1/include/system/audio.h
+ */
+
+#define AUDIO_STREAM_TYPE_MUSIC 3
+
+enum {
+ AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS = 0x1,
+ AUDIO_CHANNEL_OUT_FRONT_RIGHT_ICS = 0x2,
+ AUDIO_CHANNEL_OUT_MONO_ICS = AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS,
+ AUDIO_CHANNEL_OUT_STEREO_ICS =
+ (AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS | AUDIO_CHANNEL_OUT_FRONT_RIGHT_ICS)
+} AudioTrack_ChannelMapping_ICS;
+
+enum {
+ AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy = 0x4,
+ AUDIO_CHANNEL_OUT_FRONT_RIGHT_Legacy = 0x8,
+ AUDIO_CHANNEL_OUT_MONO_Legacy = AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy,
+ AUDIO_CHANNEL_OUT_STEREO_Legacy = (AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy |
+ AUDIO_CHANNEL_OUT_FRONT_RIGHT_Legacy)
+} AudioTrack_ChannelMapping_Legacy;
+
+typedef enum {
+ AUDIO_FORMAT_PCM = 0x00000000,
+ AUDIO_FORMAT_PCM_SUB_16_BIT = 0x1,
+ AUDIO_FORMAT_PCM_16_BIT = (AUDIO_FORMAT_PCM | AUDIO_FORMAT_PCM_SUB_16_BIT),
+} AudioTrack_SampleType;
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/android/cubeb-output-latency.h b/third_party/rust/cubeb-sys/libcubeb/src/android/cubeb-output-latency.h
new file mode 100644
index 0000000000..403276a749
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/android/cubeb-output-latency.h
@@ -0,0 +1,79 @@
+#ifndef _CUBEB_OUTPUT_LATENCY_H_
+#define _CUBEB_OUTPUT_LATENCY_H_
+
+#include "../cubeb-jni.h"
+#include "cubeb_media_library.h"
+#include <stdbool.h>
+
+struct output_latency_function {
+ media_lib * from_lib;
+ cubeb_jni * from_jni;
+ int version;
+};
+
+typedef struct output_latency_function output_latency_function;
+
+const int ANDROID_JELLY_BEAN_MR1_4_2 = 17;
+
+output_latency_function *
+cubeb_output_latency_load_method(int version)
+{
+ output_latency_function * ol = NULL;
+ ol = (output_latency_function *)calloc(1, sizeof(output_latency_function));
+
+ ol->version = version;
+
+ if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) {
+ ol->from_jni = cubeb_jni_init();
+ return ol;
+ }
+
+ ol->from_lib = cubeb_load_media_library();
+ return ol;
+}
+
+bool
+cubeb_output_latency_method_is_loaded(output_latency_function * ol)
+{
+ assert(ol);
+ if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) {
+ return !!ol->from_jni;
+ }
+
+ return !!ol->from_lib;
+}
+
+void
+cubeb_output_latency_unload_method(output_latency_function * ol)
+{
+ if (!ol) {
+ return;
+ }
+
+ if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2 && ol->from_jni) {
+ cubeb_jni_destroy(ol->from_jni);
+ }
+
+ if (ol->version <= ANDROID_JELLY_BEAN_MR1_4_2 && ol->from_lib) {
+ cubeb_close_media_library(ol->from_lib);
+ }
+
+ free(ol);
+}
+
+extern "C" {
+
+uint32_t
+cubeb_get_output_latency(output_latency_function * ol)
+{
+ assert(cubeb_output_latency_method_is_loaded(ol));
+
+ if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) {
+ return cubeb_get_output_latency_from_jni(ol->from_jni);
+ }
+
+ return cubeb_get_output_latency_from_media_library(ol->from_lib);
+}
+}
+
+#endif // _CUBEB_OUTPUT_LATENCY_H_
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/android/cubeb_media_library.h b/third_party/rust/cubeb-sys/libcubeb/src/android/cubeb_media_library.h
new file mode 100644
index 0000000000..a54427b4f7
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/android/cubeb_media_library.h
@@ -0,0 +1,70 @@
+#include <cassert>
+#include <cstdint>
+#include <dlfcn.h>
+#include <stdlib.h>
+#ifndef _CUBEB_MEDIA_LIBRARY_H_
+#define _CUBEB_MEDIA_LIBRARY_H_
+
+typedef int32_t (*get_output_latency_ptr)(uint32_t * latency, int stream_type);
+
+struct media_lib {
+ void * libmedia;
+ get_output_latency_ptr get_output_latency;
+};
+
+typedef struct media_lib media_lib;
+
+media_lib *
+cubeb_load_media_library()
+{
+ media_lib ml = {};
+ ml.libmedia = dlopen("libmedia.so", RTLD_LAZY);
+ if (!ml.libmedia) {
+ return nullptr;
+ }
+
+ // Get the latency, in ms, from AudioFlinger. First, try the most recent
+ // signature. status_t AudioSystem::getOutputLatency(uint32_t* latency,
+ // audio_stream_type_t streamType)
+ ml.get_output_latency = (get_output_latency_ptr)dlsym(
+ ml.libmedia,
+ "_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t");
+ if (!ml.get_output_latency) {
+ // In case of failure, try the signature from legacy version.
+ // status_t AudioSystem::getOutputLatency(uint32_t* latency, int streamType)
+ ml.get_output_latency = (get_output_latency_ptr)dlsym(
+ ml.libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji");
+ if (!ml.get_output_latency) {
+ return nullptr;
+ }
+ }
+
+ media_lib * rv = nullptr;
+ rv = (media_lib *)calloc(1, sizeof(media_lib));
+ assert(rv);
+ *rv = ml;
+ return rv;
+}
+
+void
+cubeb_close_media_library(media_lib * ml)
+{
+ dlclose(ml->libmedia);
+ ml->libmedia = NULL;
+ ml->get_output_latency = NULL;
+ free(ml);
+}
+
+uint32_t
+cubeb_get_output_latency_from_media_library(media_lib * ml)
+{
+ uint32_t latency = 0;
+ const int audio_stream_type_music = 3;
+ int32_t r = ml->get_output_latency(&latency, audio_stream_type_music);
+ if (r) {
+ return 0;
+ }
+ return latency;
+}
+
+#endif // _CUBEB_MEDIA_LIBRARY_H_
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/android/sles_definitions.h b/third_party/rust/cubeb-sys/libcubeb/src/android/sles_definitions.h
new file mode 100644
index 0000000000..b107003d1b
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/android/sles_definitions.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This file is similar to the file "OpenSLES_AndroidConfiguration.h" found in
+ * the Android NDK, but removes the #ifdef __cplusplus defines, so we can keep
+ * using a C compiler in cubeb.
+ */
+
+#ifndef OPENSL_ES_ANDROIDCONFIGURATION_H_
+#define OPENSL_ES_ANDROIDCONFIGURATION_H_
+
+/*---------------------------------------------------------------------------*/
+/* Android AudioRecorder configuration */
+/*---------------------------------------------------------------------------*/
+
+/** Audio recording preset */
+/** Audio recording preset key */
+#define SL_ANDROID_KEY_RECORDING_PRESET \
+ ((const SLchar *)"androidRecordingPreset")
+/** Audio recording preset values */
+/** preset "none" cannot be set, it is used to indicate the current settings
+ * do not match any of the presets. */
+#define SL_ANDROID_RECORDING_PRESET_NONE ((SLuint32)0x00000000)
+/** generic recording configuration on the platform */
+#define SL_ANDROID_RECORDING_PRESET_GENERIC ((SLuint32)0x00000001)
+/** uses the microphone audio source with the same orientation as the camera
+ * if available, the main device microphone otherwise */
+#define SL_ANDROID_RECORDING_PRESET_CAMCORDER ((SLuint32)0x00000002)
+/** uses the main microphone tuned for voice recognition */
+#define SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION ((SLuint32)0x00000003)
+/** uses the main microphone tuned for audio communications */
+#define SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION ((SLuint32)0x00000004)
+/** uses the main microphone unprocessed */
+#define SL_ANDROID_RECORDING_PRESET_UNPROCESSED ((SLuint32)0x00000005)
+
+/*---------------------------------------------------------------------------*/
+/* Android AudioPlayer configuration */
+/*---------------------------------------------------------------------------*/
+
+/** Audio playback stream type */
+/** Audio playback stream type key */
+#define SL_ANDROID_KEY_STREAM_TYPE ((const SLchar *)"androidPlaybackStreamType")
+
+/** Audio playback stream type values */
+/* same as android.media.AudioManager.STREAM_VOICE_CALL */
+#define SL_ANDROID_STREAM_VOICE ((SLint32)0x00000000)
+/* same as android.media.AudioManager.STREAM_SYSTEM */
+#define SL_ANDROID_STREAM_SYSTEM ((SLint32)0x00000001)
+/* same as android.media.AudioManager.STREAM_RING */
+#define SL_ANDROID_STREAM_RING ((SLint32)0x00000002)
+/* same as android.media.AudioManager.STREAM_MUSIC */
+#define SL_ANDROID_STREAM_MEDIA ((SLint32)0x00000003)
+/* same as android.media.AudioManager.STREAM_ALARM */
+#define SL_ANDROID_STREAM_ALARM ((SLint32)0x00000004)
+/* same as android.media.AudioManager.STREAM_NOTIFICATION */
+#define SL_ANDROID_STREAM_NOTIFICATION ((SLint32)0x00000005)
+
+/*---------------------------------------------------------------------------*/
+/* Android AudioPlayer and AudioRecorder configuration */
+/*---------------------------------------------------------------------------*/
+
+/** Audio Performance mode.
+ * Performance mode tells the framework how to configure the audio path
+ * for a player or recorder according to application performance and
+ * functional requirements.
+ * It affects the output or input latency based on acceptable tradeoffs on
+ * battery drain and use of pre or post processing effects.
+ * Performance mode should be set before realizing the object and should be
+ * read after realizing the object to check if the requested mode could be
+ * granted or not.
+ */
+/** Audio Performance mode key */
+#define SL_ANDROID_KEY_PERFORMANCE_MODE \
+ ((const SLchar *)"androidPerformanceMode")
+
+/** Audio performance values */
+/* No specific performance requirement. Allows HW and SW pre/post
+ * processing. */
+#define SL_ANDROID_PERFORMANCE_NONE ((SLuint32)0x00000000)
+/* Priority given to latency. No HW or software pre/post processing.
+ * This is the default if no performance mode is specified. */
+#define SL_ANDROID_PERFORMANCE_LATENCY ((SLuint32)0x00000001)
+/* Priority given to latency while still allowing HW pre and post
+ * processing. */
+#define SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS ((SLuint32)0x00000002)
+/* Priority given to power saving if latency is not a concern.
+ * Allows HW and SW pre/post processing. */
+#define SL_ANDROID_PERFORMANCE_POWER_SAVING ((SLuint32)0x00000003)
+
+#endif /* OPENSL_ES_ANDROIDCONFIGURATION_H_ */
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb-internal.h b/third_party/rust/cubeb-sys/libcubeb/src/cubeb-internal.h
new file mode 100644
index 0000000000..8357cdc7e1
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb-internal.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright © 2013 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#if !defined(CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5)
+#define CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5
+
+#include "cubeb/cubeb.h"
+#include "cubeb_assert.h"
+#include "cubeb_log.h"
+#include <stdio.h>
+#include <string.h>
+
+#ifdef __clang__
+#ifndef CLANG_ANALYZER_NORETURN
+#if __has_feature(attribute_analyzer_noreturn)
+#define CLANG_ANALYZER_NORETURN __attribute__((analyzer_noreturn))
+#else
+#define CLANG_ANALYZER_NORETURN
+#endif // ifndef CLANG_ANALYZER_NORETURN
+#endif // __has_feature(attribute_analyzer_noreturn)
+#else // __clang__
+#define CLANG_ANALYZER_NORETURN
+#endif
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#if defined(__cplusplus)
+}
+#endif
+
+struct cubeb_ops {
+ int (*init)(cubeb ** context, char const * context_name);
+ char const * (*get_backend_id)(cubeb * context);
+ int (*get_max_channel_count)(cubeb * context, uint32_t * max_channels);
+ int (*get_min_latency)(cubeb * context, cubeb_stream_params params,
+ uint32_t * latency_ms);
+ int (*get_preferred_sample_rate)(cubeb * context, uint32_t * rate);
+ int (*get_supported_input_processing_params)(
+ cubeb * context, cubeb_input_processing_params * params);
+ int (*enumerate_devices)(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection);
+ int (*device_collection_destroy)(cubeb * context,
+ cubeb_device_collection * collection);
+ void (*destroy)(cubeb * context);
+ int (*stream_init)(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency, cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr);
+ void (*stream_destroy)(cubeb_stream * stream);
+ int (*stream_start)(cubeb_stream * stream);
+ int (*stream_stop)(cubeb_stream * stream);
+ int (*stream_get_position)(cubeb_stream * stream, uint64_t * position);
+ int (*stream_get_latency)(cubeb_stream * stream, uint32_t * latency);
+ int (*stream_get_input_latency)(cubeb_stream * stream, uint32_t * latency);
+ int (*stream_set_volume)(cubeb_stream * stream, float volumes);
+ int (*stream_set_name)(cubeb_stream * stream, char const * stream_name);
+ int (*stream_get_current_device)(cubeb_stream * stream,
+ cubeb_device ** const device);
+ int (*stream_set_input_mute)(cubeb_stream * stream, int mute);
+ int (*stream_set_input_processing_params)(
+ cubeb_stream * stream, cubeb_input_processing_params params);
+ int (*stream_device_destroy)(cubeb_stream * stream, cubeb_device * device);
+ int (*stream_register_device_changed_callback)(
+ cubeb_stream * stream,
+ cubeb_device_changed_callback device_changed_callback);
+ int (*register_device_collection_changed)(
+ cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback callback, void * user_ptr);
+};
+
+#endif /* CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5 */
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb-jni-instances.h b/third_party/rust/cubeb-sys/libcubeb/src/cubeb-jni-instances.h
new file mode 100644
index 0000000000..4fed046592
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb-jni-instances.h
@@ -0,0 +1,30 @@
+#ifndef _CUBEB_JNI_INSTANCES_H_
+#define _CUBEB_JNI_INSTANCES_H_
+
+/*
+ * The methods in this file offer a way to pass in the required
+ * JNI instances in the cubeb library. By default they return NULL.
+ * In this case part of the cubeb API that depends on JNI
+ * will return CUBEB_ERROR_NOT_SUPPORTED. Currently only one
+ * method depends on that:
+ *
+ * cubeb_stream_get_position()
+ *
+ * Users that want to use that cubeb API method must "override"
+ * the methods bellow to return a valid instance of JavaVM
+ * and application's Context object.
+ * */
+
+JNIEnv *
+cubeb_get_jni_env_for_thread()
+{
+ return nullptr;
+}
+
+jobject
+cubeb_jni_get_context_instance()
+{
+ return nullptr;
+}
+
+#endif //_CUBEB_JNI_INSTANCES_H_
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb-jni.cpp b/third_party/rust/cubeb-sys/libcubeb/src/cubeb-jni.cpp
new file mode 100644
index 0000000000..8e7345b8aa
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb-jni.cpp
@@ -0,0 +1,82 @@
+/* clang-format off */
+#include "jni.h"
+#include <assert.h>
+#include "cubeb-jni-instances.h"
+/* clang-format on */
+
+#define AUDIO_STREAM_TYPE_MUSIC 3
+
+struct cubeb_jni {
+ jobject s_audio_manager_obj = nullptr;
+ jclass s_audio_manager_class = nullptr;
+ jmethodID s_get_output_latency_id = nullptr;
+};
+
+extern "C" cubeb_jni *
+cubeb_jni_init()
+{
+ jobject ctx_obj = cubeb_jni_get_context_instance();
+ JNIEnv * jni_env = cubeb_get_jni_env_for_thread();
+ if (!jni_env || !ctx_obj) {
+ return nullptr;
+ }
+
+ cubeb_jni * cubeb_jni_ptr = new cubeb_jni;
+ assert(cubeb_jni_ptr);
+
+ // Find the audio manager object and make it global to call it from another
+ // method
+ jclass context_class = jni_env->FindClass("android/content/Context");
+ jfieldID audio_service_field = jni_env->GetStaticFieldID(
+ context_class, "AUDIO_SERVICE", "Ljava/lang/String;");
+ jstring jstr = (jstring)jni_env->GetStaticObjectField(context_class,
+ audio_service_field);
+ jmethodID get_system_service_id =
+ jni_env->GetMethodID(context_class, "getSystemService",
+ "(Ljava/lang/String;)Ljava/lang/Object;");
+ jobject audio_manager_obj =
+ jni_env->CallObjectMethod(ctx_obj, get_system_service_id, jstr);
+ cubeb_jni_ptr->s_audio_manager_obj =
+ reinterpret_cast<jobject>(jni_env->NewGlobalRef(audio_manager_obj));
+
+ // Make the audio manager class a global reference in order to preserve method
+ // id
+ jclass audio_manager_class = jni_env->FindClass("android/media/AudioManager");
+ cubeb_jni_ptr->s_audio_manager_class =
+ reinterpret_cast<jclass>(jni_env->NewGlobalRef(audio_manager_class));
+ cubeb_jni_ptr->s_get_output_latency_id =
+ jni_env->GetMethodID(audio_manager_class, "getOutputLatency", "(I)I");
+
+ jni_env->DeleteLocalRef(ctx_obj);
+ jni_env->DeleteLocalRef(context_class);
+ jni_env->DeleteLocalRef(jstr);
+ jni_env->DeleteLocalRef(audio_manager_obj);
+ jni_env->DeleteLocalRef(audio_manager_class);
+
+ return cubeb_jni_ptr;
+}
+
+extern "C" int
+cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr)
+{
+ assert(cubeb_jni_ptr);
+ JNIEnv * jni_env = cubeb_get_jni_env_for_thread();
+ return jni_env->CallIntMethod(
+ cubeb_jni_ptr->s_audio_manager_obj,
+ cubeb_jni_ptr->s_get_output_latency_id,
+ AUDIO_STREAM_TYPE_MUSIC); // param: AudioManager.STREAM_MUSIC
+}
+
+extern "C" void
+cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr)
+{
+ assert(cubeb_jni_ptr);
+
+ JNIEnv * jni_env = cubeb_get_jni_env_for_thread();
+ assert(jni_env);
+
+ jni_env->DeleteGlobalRef(cubeb_jni_ptr->s_audio_manager_obj);
+ jni_env->DeleteGlobalRef(cubeb_jni_ptr->s_audio_manager_class);
+
+ delete cubeb_jni_ptr;
+}
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb-jni.h b/third_party/rust/cubeb-sys/libcubeb/src/cubeb-jni.h
new file mode 100644
index 0000000000..d63629fb91
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb-jni.h
@@ -0,0 +1,21 @@
+#ifndef _CUBEB_JNI_H_
+#define _CUBEB_JNI_H_
+
+typedef struct cubeb_jni cubeb_jni;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+cubeb_jni *
+cubeb_jni_init();
+int
+cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr);
+void
+cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif // _CUBEB_JNI_H_
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb-speex-resampler.h b/third_party/rust/cubeb-sys/libcubeb/src/cubeb-speex-resampler.h
new file mode 100644
index 0000000000..9ecf747cb0
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb-speex-resampler.h
@@ -0,0 +1 @@
+#include <speex/speex_resampler.h>
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb.c b/third_party/rust/cubeb-sys/libcubeb/src/cubeb.c
new file mode 100644
index 0000000000..16bb84a9fa
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb.c
@@ -0,0 +1,756 @@
+/*
+ * Copyright © 2013 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#undef NDEBUG
+#include "cubeb/cubeb.h"
+#include "cubeb-internal.h"
+#include <assert.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define NELEMS(x) ((int)(sizeof(x) / sizeof(x[0])))
+
+struct cubeb {
+ struct cubeb_ops * ops;
+};
+
+struct cubeb_stream {
+ /*
+ * Note: All implementations of cubeb_stream must keep the following
+ * layout.
+ */
+ struct cubeb * context;
+ void * user_ptr;
+};
+
+#if defined(USE_PULSE)
+int
+pulse_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_PULSE_RUST)
+int
+pulse_rust_init(cubeb ** contet, char const * context_name);
+#endif
+#if defined(USE_JACK)
+int
+jack_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_ALSA)
+int
+alsa_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_AUDIOUNIT)
+int
+audiounit_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_AUDIOUNIT_RUST)
+int
+audiounit_rust_init(cubeb ** contet, char const * context_name);
+#endif
+#if defined(USE_WINMM)
+int
+winmm_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_WASAPI)
+int
+wasapi_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_SNDIO)
+int
+sndio_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_SUN)
+int
+sun_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_OPENSL)
+int
+opensl_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_OSS)
+int
+oss_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_AAUDIO)
+int
+aaudio_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_AUDIOTRACK)
+int
+audiotrack_init(cubeb ** context, char const * context_name);
+#endif
+#if defined(USE_KAI)
+int
+kai_init(cubeb ** context, char const * context_name);
+#endif
+
+static int
+validate_stream_params(cubeb_stream_params * input_stream_params,
+ cubeb_stream_params * output_stream_params)
+{
+ XASSERT(input_stream_params || output_stream_params);
+ if (output_stream_params) {
+ if (output_stream_params->rate < 1000 ||
+ output_stream_params->rate > 192000 ||
+ output_stream_params->channels < 1 ||
+ output_stream_params->channels > UINT8_MAX) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ }
+ if (input_stream_params) {
+ if (input_stream_params->rate < 1000 ||
+ input_stream_params->rate > 192000 ||
+ input_stream_params->channels < 1 ||
+ input_stream_params->channels > UINT8_MAX) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ }
+ // Rate and sample format must be the same for input and output, if using a
+ // duplex stream
+ if (input_stream_params && output_stream_params) {
+ if (input_stream_params->rate != output_stream_params->rate ||
+ input_stream_params->format != output_stream_params->format) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ }
+
+ cubeb_stream_params * params =
+ input_stream_params ? input_stream_params : output_stream_params;
+
+ switch (params->format) {
+ case CUBEB_SAMPLE_S16LE:
+ case CUBEB_SAMPLE_S16BE:
+ case CUBEB_SAMPLE_FLOAT32LE:
+ case CUBEB_SAMPLE_FLOAT32BE:
+ return CUBEB_OK;
+ }
+
+ return CUBEB_ERROR_INVALID_FORMAT;
+}
+
+static int
+validate_latency(int latency)
+{
+ if (latency < 1 || latency > 96000) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+ return CUBEB_OK;
+}
+
+int
+cubeb_init(cubeb ** context, char const * context_name,
+ char const * backend_name)
+{
+ int (*init_oneshot)(cubeb **, char const *) = NULL;
+
+ if (backend_name != NULL) {
+ if (!strcmp(backend_name, "pulse")) {
+#if defined(USE_PULSE)
+ init_oneshot = pulse_init;
+#endif
+ } else if (!strcmp(backend_name, "pulse-rust")) {
+#if defined(USE_PULSE_RUST)
+ init_oneshot = pulse_rust_init;
+#endif
+ } else if (!strcmp(backend_name, "jack")) {
+#if defined(USE_JACK)
+ init_oneshot = jack_init;
+#endif
+ } else if (!strcmp(backend_name, "alsa")) {
+#if defined(USE_ALSA)
+ init_oneshot = alsa_init;
+#endif
+ } else if (!strcmp(backend_name, "audiounit")) {
+#if defined(USE_AUDIOUNIT)
+ init_oneshot = audiounit_init;
+#endif
+ } else if (!strcmp(backend_name, "audiounit-rust")) {
+#if defined(USE_AUDIOUNIT_RUST)
+ init_oneshot = audiounit_rust_init;
+#endif
+ } else if (!strcmp(backend_name, "wasapi")) {
+#if defined(USE_WASAPI)
+ init_oneshot = wasapi_init;
+#endif
+ } else if (!strcmp(backend_name, "winmm")) {
+#if defined(USE_WINMM)
+ init_oneshot = winmm_init;
+#endif
+ } else if (!strcmp(backend_name, "sndio")) {
+#if defined(USE_SNDIO)
+ init_oneshot = sndio_init;
+#endif
+ } else if (!strcmp(backend_name, "sun")) {
+#if defined(USE_SUN)
+ init_oneshot = sun_init;
+#endif
+ } else if (!strcmp(backend_name, "opensl")) {
+#if defined(USE_OPENSL)
+ init_oneshot = opensl_init;
+#endif
+ } else if (!strcmp(backend_name, "oss")) {
+#if defined(USE_OSS)
+ init_oneshot = oss_init;
+#endif
+ } else if (!strcmp(backend_name, "aaudio")) {
+#if defined(USE_AAUDIO)
+ init_oneshot = aaudio_init;
+#endif
+ } else if (!strcmp(backend_name, "audiotrack")) {
+#if defined(USE_AUDIOTRACK)
+ init_oneshot = audiotrack_init;
+#endif
+ } else if (!strcmp(backend_name, "kai")) {
+#if defined(USE_KAI)
+ init_oneshot = kai_init;
+#endif
+ } else {
+ /* Already set */
+ }
+ }
+
+ int (*default_init[])(cubeb **, char const *) = {
+ /*
+ * init_oneshot must be at the top to allow user
+ * to override all other choices
+ */
+ init_oneshot,
+#if defined(USE_PULSE_RUST)
+ pulse_rust_init,
+#endif
+#if defined(USE_PULSE)
+ pulse_init,
+#endif
+#if defined(USE_JACK)
+ jack_init,
+#endif
+#if defined(USE_SNDIO)
+ sndio_init,
+#endif
+#if defined(USE_ALSA)
+ alsa_init,
+#endif
+#if defined(USE_OSS)
+ oss_init,
+#endif
+#if defined(USE_AUDIOUNIT_RUST)
+ audiounit_rust_init,
+#endif
+#if defined(USE_AUDIOUNIT)
+ audiounit_init,
+#endif
+#if defined(USE_WASAPI)
+ wasapi_init,
+#endif
+#if defined(USE_WINMM)
+ winmm_init,
+#endif
+#if defined(USE_SUN)
+ sun_init,
+#endif
+#if defined(USE_AAUDIO)
+ aaudio_init,
+#endif
+#if defined(USE_OPENSL)
+ opensl_init,
+#endif
+#if defined(USE_AUDIOTRACK)
+ audiotrack_init,
+#endif
+#if defined(USE_KAI)
+ kai_init,
+#endif
+ };
+ int i;
+
+ if (!context) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+#define OK(fn) assert((*context)->ops->fn)
+ for (i = 0; i < NELEMS(default_init); ++i) {
+ if (default_init[i] && default_init[i](context, context_name) == CUBEB_OK) {
+ /* Assert that the minimal API is implemented. */
+ OK(get_backend_id);
+ OK(destroy);
+ OK(stream_init);
+ OK(stream_destroy);
+ OK(stream_start);
+ OK(stream_stop);
+ OK(stream_get_position);
+ return CUBEB_OK;
+ }
+ }
+ return CUBEB_ERROR;
+}
+
+char const *
+cubeb_get_backend_id(cubeb * context)
+{
+ if (!context) {
+ return NULL;
+ }
+
+ return context->ops->get_backend_id(context);
+}
+
+int
+cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels)
+{
+ if (!context || !max_channels) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!context->ops->get_max_channel_count) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return context->ops->get_max_channel_count(context, max_channels);
+}
+
+int
+cubeb_get_min_latency(cubeb * context, cubeb_stream_params * params,
+ uint32_t * latency_ms)
+{
+ if (!context || !params || !latency_ms) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!context->ops->get_min_latency) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return context->ops->get_min_latency(context, *params, latency_ms);
+}
+
+int
+cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate)
+{
+ if (!context || !rate) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!context->ops->get_preferred_sample_rate) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return context->ops->get_preferred_sample_rate(context, rate);
+}
+
+int
+cubeb_get_supported_input_processing_params(
+ cubeb * context, cubeb_input_processing_params * params)
+{
+ if (!context || !params) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!context->ops->get_supported_input_processing_params) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return context->ops->get_supported_input_processing_params(context, params);
+}
+
+void
+cubeb_destroy(cubeb * context)
+{
+ if (!context) {
+ return;
+ }
+
+ context->ops->destroy(context);
+
+ cubeb_set_log_callback(CUBEB_LOG_DISABLED, NULL);
+}
+
+int
+cubeb_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency, cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ int r;
+
+ if (!context || !stream || !data_callback || !state_callback) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if ((r = validate_stream_params(input_stream_params, output_stream_params)) !=
+ CUBEB_OK ||
+ (r = validate_latency(latency)) != CUBEB_OK) {
+ return r;
+ }
+
+ r = context->ops->stream_init(context, stream, stream_name, input_device,
+ input_stream_params, output_device,
+ output_stream_params, latency, data_callback,
+ state_callback, user_ptr);
+
+ if (r == CUBEB_ERROR_INVALID_FORMAT) {
+ LOG("Invalid format, %p %p %d %d", output_stream_params,
+ input_stream_params,
+ output_stream_params && output_stream_params->format,
+ input_stream_params && input_stream_params->format);
+ }
+
+ return r;
+}
+
+void
+cubeb_stream_destroy(cubeb_stream * stream)
+{
+ if (!stream) {
+ return;
+ }
+
+ stream->context->ops->stream_destroy(stream);
+}
+
+int
+cubeb_stream_start(cubeb_stream * stream)
+{
+ if (!stream) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ return stream->context->ops->stream_start(stream);
+}
+
+int
+cubeb_stream_stop(cubeb_stream * stream)
+{
+ if (!stream) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ return stream->context->ops->stream_stop(stream);
+}
+
+int
+cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position)
+{
+ if (!stream || !position) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ return stream->context->ops->stream_get_position(stream, position);
+}
+
+int
+cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
+{
+ if (!stream || !latency) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!stream->context->ops->stream_get_latency) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return stream->context->ops->stream_get_latency(stream, latency);
+}
+
+int
+cubeb_stream_get_input_latency(cubeb_stream * stream, uint32_t * latency)
+{
+ if (!stream || !latency) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!stream->context->ops->stream_get_input_latency) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return stream->context->ops->stream_get_input_latency(stream, latency);
+}
+
+int
+cubeb_stream_set_volume(cubeb_stream * stream, float volume)
+{
+ if (!stream || volume > 1.0 || volume < 0.0) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!stream->context->ops->stream_set_volume) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return stream->context->ops->stream_set_volume(stream, volume);
+}
+
+int
+cubeb_stream_set_name(cubeb_stream * stream, char const * stream_name)
+{
+ if (!stream || !stream_name) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!stream->context->ops->stream_set_name) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return stream->context->ops->stream_set_name(stream, stream_name);
+}
+
+int
+cubeb_stream_get_current_device(cubeb_stream * stream,
+ cubeb_device ** const device)
+{
+ if (!stream || !device) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!stream->context->ops->stream_get_current_device) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return stream->context->ops->stream_get_current_device(stream, device);
+}
+
+int
+cubeb_stream_set_input_mute(cubeb_stream * stream, int mute)
+{
+ if (!stream) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!stream->context->ops->stream_set_input_mute) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return stream->context->ops->stream_set_input_mute(stream, mute);
+}
+
+int
+cubeb_stream_set_input_processing_params(cubeb_stream * stream,
+ cubeb_input_processing_params params)
+{
+ if (!stream || !params) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!stream->context->ops->stream_set_input_processing_params) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return stream->context->ops->stream_set_input_processing_params(stream,
+ params);
+}
+
+int
+cubeb_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
+{
+ if (!stream || !device) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!stream->context->ops->stream_device_destroy) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return stream->context->ops->stream_device_destroy(stream, device);
+}
+
+int
+cubeb_stream_register_device_changed_callback(
+ cubeb_stream * stream,
+ cubeb_device_changed_callback device_changed_callback)
+{
+ if (!stream) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (!stream->context->ops->stream_register_device_changed_callback) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return stream->context->ops->stream_register_device_changed_callback(
+ stream, device_changed_callback);
+}
+
+void *
+cubeb_stream_user_ptr(cubeb_stream * stream)
+{
+ if (!stream) {
+ return NULL;
+ }
+
+ return stream->user_ptr;
+}
+
+static void
+log_device(cubeb_device_info * device_info)
+{
+ char devfmts[128] = "";
+ const char *devtype, *devstate, *devdeffmt;
+
+ switch (device_info->type) {
+ case CUBEB_DEVICE_TYPE_INPUT:
+ devtype = "input";
+ break;
+ case CUBEB_DEVICE_TYPE_OUTPUT:
+ devtype = "output";
+ break;
+ case CUBEB_DEVICE_TYPE_UNKNOWN:
+ default:
+ devtype = "unknown?";
+ break;
+ };
+
+ switch (device_info->state) {
+ case CUBEB_DEVICE_STATE_DISABLED:
+ devstate = "disabled";
+ break;
+ case CUBEB_DEVICE_STATE_UNPLUGGED:
+ devstate = "unplugged";
+ break;
+ case CUBEB_DEVICE_STATE_ENABLED:
+ devstate = "enabled";
+ break;
+ default:
+ devstate = "unknown?";
+ break;
+ };
+
+ switch (device_info->default_format) {
+ case CUBEB_DEVICE_FMT_S16LE:
+ devdeffmt = "S16LE";
+ break;
+ case CUBEB_DEVICE_FMT_S16BE:
+ devdeffmt = "S16BE";
+ break;
+ case CUBEB_DEVICE_FMT_F32LE:
+ devdeffmt = "F32LE";
+ break;
+ case CUBEB_DEVICE_FMT_F32BE:
+ devdeffmt = "F32BE";
+ break;
+ default:
+ devdeffmt = "unknown?";
+ break;
+ };
+
+ if (device_info->format & CUBEB_DEVICE_FMT_S16LE) {
+ strcat(devfmts, " S16LE");
+ }
+ if (device_info->format & CUBEB_DEVICE_FMT_S16BE) {
+ strcat(devfmts, " S16BE");
+ }
+ if (device_info->format & CUBEB_DEVICE_FMT_F32LE) {
+ strcat(devfmts, " F32LE");
+ }
+ if (device_info->format & CUBEB_DEVICE_FMT_F32BE) {
+ strcat(devfmts, " F32BE");
+ }
+
+ LOG("DeviceID: \"%s\"%s\n"
+ "\tName:\t\"%s\"\n"
+ "\tGroup:\t\"%s\"\n"
+ "\tVendor:\t\"%s\"\n"
+ "\tType:\t%s\n"
+ "\tState:\t%s\n"
+ "\tMaximum channels:\t%u\n"
+ "\tFormat:\t%s (0x%x) (default: %s)\n"
+ "\tRate:\t[%u, %u] (default: %u)\n"
+ "\tLatency: lo %u frames, hi %u frames",
+ device_info->device_id, device_info->preferred ? " (PREFERRED)" : "",
+ device_info->friendly_name, device_info->group_id,
+ device_info->vendor_name, devtype, devstate, device_info->max_channels,
+ (devfmts[0] == '\0') ? devfmts : devfmts + 1,
+ (unsigned int)device_info->format, devdeffmt, device_info->min_rate,
+ device_info->max_rate, device_info->default_rate, device_info->latency_lo,
+ device_info->latency_hi);
+}
+
+int
+cubeb_enumerate_devices(cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection * collection)
+{
+ int rv;
+ if ((devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0)
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ if (context == NULL || collection == NULL)
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ if (!context->ops->enumerate_devices)
+ return CUBEB_ERROR_NOT_SUPPORTED;
+
+ rv = context->ops->enumerate_devices(context, devtype, collection);
+
+ if (cubeb_log_get_callback()) {
+ for (size_t i = 0; i < collection->count; i++) {
+ log_device(&collection->device[i]);
+ }
+ }
+
+ return rv;
+}
+
+int
+cubeb_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection)
+{
+ int r;
+
+ if (context == NULL || collection == NULL)
+ return CUBEB_ERROR_INVALID_PARAMETER;
+
+ if (!context->ops->device_collection_destroy)
+ return CUBEB_ERROR_NOT_SUPPORTED;
+
+ if (!collection->device)
+ return CUBEB_OK;
+
+ r = context->ops->device_collection_destroy(context, collection);
+ if (r == CUBEB_OK) {
+ collection->device = NULL;
+ collection->count = 0;
+ }
+
+ return r;
+}
+
+int
+cubeb_register_device_collection_changed(
+ cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback callback, void * user_ptr)
+{
+ if (context == NULL ||
+ (devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0)
+ return CUBEB_ERROR_INVALID_PARAMETER;
+
+ if (!context->ops->register_device_collection_changed) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ return context->ops->register_device_collection_changed(context, devtype,
+ callback, user_ptr);
+}
+
+int
+cubeb_set_log_callback(cubeb_log_level log_level,
+ cubeb_log_callback log_callback)
+{
+ if (log_level < CUBEB_LOG_DISABLED || log_level > CUBEB_LOG_VERBOSE) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ if (!log_callback && log_level != CUBEB_LOG_DISABLED) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (cubeb_log_get_callback() && log_callback) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ cubeb_log_set(log_level, log_callback);
+
+ return CUBEB_OK;
+}
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_aaudio.cpp b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_aaudio.cpp
new file mode 100644
index 0000000000..f6ed5c610d
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_aaudio.cpp
@@ -0,0 +1,1798 @@
+/* ex: set tabstop=2 shiftwidth=2 expandtab:
+ * Copyright © 2019 Jan Kelling
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_android.h"
+#include "cubeb_log.h"
+#include "cubeb_resampler.h"
+#include "cubeb_triple_buffer.h"
+#include <aaudio/AAudio.h>
+#include <atomic>
+#include <cassert>
+#include <chrono>
+#include <condition_variable>
+#include <cstdint>
+#include <cstring>
+#include <dlfcn.h>
+#include <inttypes.h>
+#include <limits>
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <vector>
+
+using namespace std;
+
+#ifdef DISABLE_LIBAAUDIO_DLOPEN
+#define WRAP(x) x
+#else
+#define WRAP(x) (*cubeb_##x)
+#define LIBAAUDIO_API_VISIT(X) \
+ X(AAudio_convertResultToText) \
+ X(AAudio_convertStreamStateToText) \
+ X(AAudio_createStreamBuilder) \
+ X(AAudioStreamBuilder_openStream) \
+ X(AAudioStreamBuilder_setChannelCount) \
+ X(AAudioStreamBuilder_setBufferCapacityInFrames) \
+ X(AAudioStreamBuilder_setDirection) \
+ X(AAudioStreamBuilder_setFormat) \
+ X(AAudioStreamBuilder_setSharingMode) \
+ X(AAudioStreamBuilder_setPerformanceMode) \
+ X(AAudioStreamBuilder_setSampleRate) \
+ X(AAudioStreamBuilder_delete) \
+ X(AAudioStreamBuilder_setDataCallback) \
+ X(AAudioStreamBuilder_setErrorCallback) \
+ X(AAudioStream_close) \
+ X(AAudioStream_read) \
+ X(AAudioStream_requestStart) \
+ X(AAudioStream_requestPause) \
+ X(AAudioStream_setBufferSizeInFrames) \
+ X(AAudioStream_getTimestamp) \
+ X(AAudioStream_requestFlush) \
+ X(AAudioStream_requestStop) \
+ X(AAudioStream_getPerformanceMode) \
+ X(AAudioStream_getSharingMode) \
+ X(AAudioStream_getBufferSizeInFrames) \
+ X(AAudioStream_getBufferCapacityInFrames) \
+ X(AAudioStream_getSampleRate) \
+ X(AAudioStream_waitForStateChange) \
+ X(AAudioStream_getFramesRead) \
+ X(AAudioStream_getState) \
+ X(AAudioStream_getFramesWritten) \
+ X(AAudioStream_getFramesPerBurst) \
+ X(AAudioStreamBuilder_setInputPreset) \
+ X(AAudioStreamBuilder_setUsage) \
+ X(AAudioStreamBuilder_setFramesPerDataCallback)
+
+// not needed or added later on
+// X(AAudioStreamBuilder_setDeviceId) \
+ // X(AAudioStreamBuilder_setSamplesPerFrame) \
+ // X(AAudioStream_getSamplesPerFrame) \
+ // X(AAudioStream_getDeviceId) \
+ // X(AAudioStream_write) \
+ // X(AAudioStream_getChannelCount) \
+ // X(AAudioStream_getFormat) \
+ // X(AAudioStream_getXRunCount) \
+ // X(AAudioStream_isMMapUsed) \
+ // X(AAudioStreamBuilder_setContentType) \
+ // X(AAudioStreamBuilder_setSessionId) \
+ // X(AAudioStream_getUsage) \
+ // X(AAudioStream_getContentType) \
+ // X(AAudioStream_getInputPreset) \
+ // X(AAudioStream_getSessionId) \
+// END: not needed or added later on
+
+#define MAKE_TYPEDEF(x) static decltype(x) * cubeb_##x;
+LIBAAUDIO_API_VISIT(MAKE_TYPEDEF)
+#undef MAKE_TYPEDEF
+#endif
+
+const uint8_t MAX_STREAMS = 16;
+const int64_t NS_PER_S = static_cast<int64_t>(1e9);
+
+static void
+aaudio_stream_destroy(cubeb_stream * stm);
+static int
+aaudio_stream_start(cubeb_stream * stm);
+static int
+aaudio_stream_stop(cubeb_stream * stm);
+
+static int
+aaudio_stream_init_impl(cubeb_stream * stm, lock_guard<mutex> & lock);
+static int
+aaudio_stream_stop_locked(cubeb_stream * stm, lock_guard<mutex> & lock);
+static void
+aaudio_stream_destroy_locked(cubeb_stream * stm, lock_guard<mutex> & lock);
+static int
+aaudio_stream_start_locked(cubeb_stream * stm, lock_guard<mutex> & lock);
+
+static void
+reinitialize_stream(cubeb_stream * stm);
+
+enum class stream_state {
+ INIT = 0,
+ STOPPED,
+ STOPPING,
+ STARTED,
+ STARTING,
+ DRAINING,
+ ERROR,
+ SHUTDOWN,
+};
+
+struct AAudioTimingInfo {
+ // The timestamp at which the audio engine last called the calback.
+ uint64_t tstamp;
+ // The number of output frames sent to the engine.
+ uint64_t output_frame_index;
+ // The current output latency in frames. 0 if there is no output stream.
+ uint32_t output_latency;
+ // The current input latency in frames. 0 if there is no input stream.
+ uint32_t input_latency;
+};
+
+struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
+ cubeb * context{};
+ void * user_ptr{};
+
+ std::atomic<bool> in_use{false};
+ std::atomic<bool> latency_metrics_available{false};
+ std::atomic<stream_state> state{stream_state::INIT};
+ std::atomic<bool> in_data_callback{false};
+ triple_buffer<AAudioTimingInfo> timing_info;
+
+ AAudioStream * ostream{};
+ AAudioStream * istream{};
+ cubeb_data_callback data_callback{};
+ cubeb_state_callback state_callback{};
+ cubeb_resampler * resampler{};
+
+ // mutex synchronizes access to the stream from the state thread
+ // and user-called functions. Everything that is accessed in the
+ // aaudio data (or error) callback is synchronized only via atomics.
+ // This lock is acquired for the entirety of the reinitialization period, when
+ // changing device.
+ std::mutex mutex;
+
+ std::vector<uint8_t> in_buf;
+ unsigned in_frame_size{}; // size of one input frame
+
+ unique_ptr<cubeb_stream_params> output_stream_params;
+ unique_ptr<cubeb_stream_params> input_stream_params;
+ uint32_t latency_frames{};
+ cubeb_sample_format out_format{};
+ uint32_t sample_rate{};
+ std::atomic<float> volume{1.f};
+ unsigned out_channels{};
+ unsigned out_frame_size{};
+ bool voice_input{};
+ bool voice_output{};
+ uint64_t previous_clock{};
+};
+
+struct cubeb {
+ struct cubeb_ops const * ops{};
+ void * libaaudio{};
+
+ struct {
+ // The state thread: it waits for state changes and stops
+ // drained streams.
+ std::thread thread;
+ std::thread notifier;
+ std::mutex mutex;
+ std::condition_variable cond;
+ std::atomic<bool> join{false};
+ std::atomic<bool> waiting{false};
+ } state;
+
+ // streams[i].in_use signals whether a stream is used
+ struct cubeb_stream streams[MAX_STREAMS];
+};
+
+struct AutoInCallback {
+ AutoInCallback(cubeb_stream * stm) : stm(stm)
+ {
+ stm->in_data_callback.store(true);
+ }
+ ~AutoInCallback() { stm->in_data_callback.store(false); }
+ cubeb_stream * stm;
+};
+
+// Returns when aaudio_stream's state is equal to desired_state.
+// poll_frequency_ns is the duration that is slept in between asking for
+// state updates and getting the new state.
+// When waiting for a stream to stop, it is best to pick a value similar
+// to the callback time because STOPPED will happen after
+// draining.
+static int
+wait_for_state_change(AAudioStream * aaudio_stream,
+ aaudio_stream_state_t desired_state,
+ int64_t poll_frequency_ns)
+{
+ aaudio_stream_state_t new_state;
+ do {
+ aaudio_result_t res = WRAP(AAudioStream_waitForStateChange)(
+ aaudio_stream, AAUDIO_STREAM_STATE_UNKNOWN, &new_state,
+ poll_frequency_ns);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_waitForStateChanged: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return CUBEB_ERROR;
+ }
+ } while (new_state != desired_state);
+
+ LOG("wait_for_state_change: current state now: %s",
+ cubeb_AAudio_convertStreamStateToText(new_state));
+
+ return CUBEB_OK;
+}
+
+// Only allowed from state thread, while mutex on stm is locked
+static void
+shutdown_with_error(cubeb_stream * stm)
+{
+ if (stm->istream) {
+ WRAP(AAudioStream_requestStop)(stm->istream);
+ }
+ if (stm->ostream) {
+ WRAP(AAudioStream_requestStop)(stm->ostream);
+ }
+
+ int64_t poll_frequency_ns = NS_PER_S * stm->out_frame_size / stm->sample_rate;
+ if (stm->istream) {
+ wait_for_state_change(stm->istream, AAUDIO_STREAM_STATE_STOPPED,
+ poll_frequency_ns);
+ }
+ if (stm->ostream) {
+ wait_for_state_change(stm->ostream, AAUDIO_STREAM_STATE_STOPPED,
+ poll_frequency_ns);
+ }
+
+ assert(!stm->in_data_callback.load());
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ stm->state.store(stream_state::SHUTDOWN);
+}
+
+// Returns whether the given state is one in which we wait for
+// an asynchronous change
+static bool
+waiting_state(stream_state state)
+{
+ switch (state) {
+ case stream_state::DRAINING:
+ case stream_state::STARTING:
+ case stream_state::STOPPING:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void
+update_state(cubeb_stream * stm)
+{
+ // Fast path for streams that don't wait for state change or are invalid
+ enum stream_state old_state = stm->state.load();
+ if (old_state == stream_state::INIT || old_state == stream_state::STARTED ||
+ old_state == stream_state::STOPPED ||
+ old_state == stream_state::SHUTDOWN) {
+ return;
+ }
+
+ // If the main thread currently operates on this thread, we don't
+ // have to wait for it
+ unique_lock lock(stm->mutex, std::try_to_lock);
+ if (!lock.owns_lock()) {
+ return;
+ }
+
+ // check again: if this is true now, the stream was destroyed or
+ // changed between our fast path check and locking the mutex
+ old_state = stm->state.load();
+ if (old_state == stream_state::INIT || old_state == stream_state::STARTED ||
+ old_state == stream_state::STOPPED ||
+ old_state == stream_state::SHUTDOWN) {
+ return;
+ }
+
+ // We compute the new state the stream has and then compare_exchange it
+ // if it has changed. This way we will never just overwrite state
+ // changes that were set from the audio thread in the meantime,
+ // such as a DRAINING or error state.
+ enum stream_state new_state;
+ do {
+ if (old_state == stream_state::SHUTDOWN) {
+ return;
+ }
+
+ if (old_state == stream_state::ERROR) {
+ shutdown_with_error(stm);
+ return;
+ }
+
+ new_state = old_state;
+
+ aaudio_stream_state_t istate = 0;
+ aaudio_stream_state_t ostate = 0;
+
+ // We use waitForStateChange (with zero timeout) instead of just
+ // getState since only the former internally updates the state.
+ // See the docs of aaudio getState/waitForStateChange for details,
+ // why we are passing STATE_UNKNOWN.
+ aaudio_result_t res;
+ if (stm->istream) {
+ res = WRAP(AAudioStream_waitForStateChange)(
+ stm->istream, AAUDIO_STREAM_STATE_UNKNOWN, &istate, 0);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_waitForStateChanged: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return;
+ }
+ assert(istate);
+ }
+
+ if (stm->ostream) {
+ res = WRAP(AAudioStream_waitForStateChange)(
+ stm->ostream, AAUDIO_STREAM_STATE_UNKNOWN, &ostate, 0);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_waitForStateChanged: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return;
+ }
+ assert(ostate);
+ }
+
+ // handle invalid stream states
+ if (istate == AAUDIO_STREAM_STATE_PAUSING ||
+ istate == AAUDIO_STREAM_STATE_PAUSED ||
+ istate == AAUDIO_STREAM_STATE_FLUSHING ||
+ istate == AAUDIO_STREAM_STATE_FLUSHED ||
+ istate == AAUDIO_STREAM_STATE_UNKNOWN ||
+ istate == AAUDIO_STREAM_STATE_DISCONNECTED) {
+ LOG("Unexpected android input stream state %s",
+ WRAP(AAudio_convertStreamStateToText)(istate));
+ shutdown_with_error(stm);
+ return;
+ }
+
+ if (ostate == AAUDIO_STREAM_STATE_PAUSING ||
+ ostate == AAUDIO_STREAM_STATE_PAUSED ||
+ ostate == AAUDIO_STREAM_STATE_FLUSHING ||
+ ostate == AAUDIO_STREAM_STATE_FLUSHED ||
+ ostate == AAUDIO_STREAM_STATE_UNKNOWN ||
+ ostate == AAUDIO_STREAM_STATE_DISCONNECTED) {
+ LOG("Unexpected android output stream state %s",
+ WRAP(AAudio_convertStreamStateToText)(istate));
+ shutdown_with_error(stm);
+ return;
+ }
+
+ switch (old_state) {
+ case stream_state::STARTING:
+ if ((!istate || istate == AAUDIO_STREAM_STATE_STARTED) &&
+ (!ostate || ostate == AAUDIO_STREAM_STATE_STARTED)) {
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
+ new_state = stream_state::STARTED;
+ }
+ break;
+ case stream_state::DRAINING:
+ // The DRAINING state means that we want to stop the streams but
+ // may not have done so yet.
+ // The aaudio docs state that returning STOP from the callback isn't
+ // enough, the stream has to be stopped from another thread
+ // afterwards.
+ // No callbacks are triggered anymore when requestStop returns.
+ // That is important as we otherwise might read from a closed istream
+ // for a duplex stream.
+ // Therefor it is important to close ostream first.
+ if (ostate && ostate != AAUDIO_STREAM_STATE_STOPPING &&
+ ostate != AAUDIO_STREAM_STATE_STOPPED) {
+ res = WRAP(AAudioStream_requestStop)(stm->ostream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_requestStop: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return;
+ }
+ }
+ if (istate && istate != AAUDIO_STREAM_STATE_STOPPING &&
+ istate != AAUDIO_STREAM_STATE_STOPPED) {
+ res = WRAP(AAudioStream_requestStop)(stm->istream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_requestStop: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return;
+ }
+ }
+
+ // we always wait until both streams are stopped until we
+ // send CUBEB_STATE_DRAINED. Then we can directly transition
+ // our logical state to STOPPED, not triggering
+ // an additional CUBEB_STATE_STOPPED callback (which might
+ // be unexpected for the user).
+ if ((!ostate || ostate == AAUDIO_STREAM_STATE_STOPPED) &&
+ (!istate || istate == AAUDIO_STREAM_STATE_STOPPED)) {
+ new_state = stream_state::STOPPED;
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+ }
+ break;
+ case stream_state::STOPPING:
+ assert(!istate || istate == AAUDIO_STREAM_STATE_STOPPING ||
+ istate == AAUDIO_STREAM_STATE_STOPPED);
+ assert(!ostate || ostate == AAUDIO_STREAM_STATE_STOPPING ||
+ ostate == AAUDIO_STREAM_STATE_STOPPED);
+ if ((!istate || istate == AAUDIO_STREAM_STATE_STOPPED) &&
+ (!ostate || ostate == AAUDIO_STREAM_STATE_STOPPED)) {
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
+ new_state = stream_state::STOPPED;
+ }
+ break;
+ default:
+ assert(false && "Unreachable: invalid state");
+ }
+ } while (old_state != new_state &&
+ !stm->state.compare_exchange_strong(old_state, new_state));
+}
+
+// See https://nyorain.github.io/lock-free-wakeup.html for a note
+// why this is needed. The audio thread notifies the state thread about
+// state changes and must not block. The state thread on the other hand should
+// sleep until there is work to be done. So we need a lockfree producer
+// and blocking producer. This can only be achieved safely with a new thread
+// that only serves as notifier backup (in case the notification happens
+// right between the state thread checking and going to sleep in which case
+// this thread will kick in and signal it right again).
+static void
+notifier_thread(cubeb * ctx)
+{
+ unique_lock lock(ctx->state.mutex);
+
+ while (!ctx->state.join.load()) {
+ ctx->state.cond.wait(lock);
+ if (ctx->state.waiting.load()) {
+ // This must signal our state thread since there is no other
+ // thread currently waiting on the condition variable.
+ // The state change thread is guaranteed to be waiting since
+ // we hold the mutex it locks when awake.
+ ctx->state.cond.notify_one();
+ }
+ }
+
+ // make sure other thread joins as well
+ ctx->state.cond.notify_one();
+ LOG("Exiting notifier thread");
+}
+
+static void
+state_thread(cubeb * ctx)
+{
+ unique_lock lock(ctx->state.mutex);
+
+ bool waiting = false;
+ while (!ctx->state.join.load()) {
+ waiting |= ctx->state.waiting.load();
+ if (waiting) {
+ ctx->state.waiting.store(false);
+ waiting = false;
+ for (auto & stream : ctx->streams) {
+ cubeb_stream * stm = &stream;
+ update_state(stm);
+ waiting |= waiting_state(atomic_load(&stm->state));
+ }
+
+ // state changed from another thread, update again immediately
+ if (ctx->state.waiting.load()) {
+ waiting = true;
+ continue;
+ }
+
+ // Not waiting for any change anymore: we can wait on the
+ // condition variable without timeout
+ if (!waiting) {
+ continue;
+ }
+
+ // while any stream is waiting for state change we sleep with regular
+ // timeouts. But we wake up immediately if signaled.
+ // This might seem like a poor man's implementation of state change
+ // waiting but (as of october 2020), the implementation of
+ // AAudioStream_waitForStateChange is just sleeping with regular
+ // timeouts as well:
+ // https://android.googlesource.com/platform/frameworks/av/+/refs/heads/master/media/libaaudio/src/core/AudioStream.cpp
+ auto dur = std::chrono::milliseconds(5);
+ ctx->state.cond.wait_for(lock, dur);
+ } else {
+ ctx->state.cond.wait(lock);
+ }
+ }
+
+ // make sure other thread joins as well
+ ctx->state.cond.notify_one();
+ LOG("Exiting state thread");
+}
+
+static char const *
+aaudio_get_backend_id(cubeb * /* ctx */)
+{
+ return "aaudio";
+}
+
+static int
+aaudio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
+{
+ assert(ctx && max_channels);
+ // NOTE: we might get more, AAudio docs don't specify anything.
+ *max_channels = 2;
+ return CUBEB_OK;
+}
+
+static void
+aaudio_destroy(cubeb * ctx)
+{
+ assert(ctx);
+
+#ifndef NDEBUG
+ // make sure all streams were destroyed
+ for (auto & stream : ctx->streams) {
+ assert(!stream.in_use.load());
+ }
+#endif
+
+ // broadcast joining to both threads
+ // they will additionally signal each other before joining
+ ctx->state.join.store(true);
+ ctx->state.cond.notify_all();
+
+ if (ctx->state.thread.joinable()) {
+ ctx->state.thread.join();
+ }
+ if (ctx->state.notifier.joinable()) {
+ ctx->state.notifier.join();
+ }
+#ifndef DISABLE_LIBAAUDIO_DLOPEN
+ if (ctx->libaaudio) {
+ dlclose(ctx->libaaudio);
+ }
+#endif
+ delete ctx;
+}
+
+static void
+apply_volume(cubeb_stream * stm, void * audio_data, uint32_t num_frames)
+{
+ float volume = stm->volume.load();
+ // optimization: we don't have to change anything in this case
+ if (volume == 1.f) {
+ return;
+ }
+
+ switch (stm->out_format) {
+ case CUBEB_SAMPLE_S16NE: {
+ int16_t * integer_data = static_cast<int16_t *>(audio_data);
+ for (uint32_t i = 0u; i < num_frames * stm->out_channels; ++i) {
+ integer_data[i] =
+ static_cast<int16_t>(static_cast<float>(integer_data[i]) * volume);
+ }
+ break;
+ }
+ case CUBEB_SAMPLE_FLOAT32NE:
+ for (uint32_t i = 0u; i < num_frames * stm->out_channels; ++i) {
+ (static_cast<float *>(audio_data))[i] *= volume;
+ }
+ break;
+ default:
+ assert(false && "Unreachable: invalid stream out_format");
+ }
+}
+
+uint64_t
+now_ns()
+{
+ using namespace std::chrono;
+ return duration_cast<nanoseconds>(steady_clock::now().time_since_epoch())
+ .count();
+}
+
+// To be called from the real-time audio callback
+uint64_t
+aaudio_get_latency(cubeb_stream * stm, aaudio_direction_t direction,
+ uint64_t tstamp_ns)
+{
+ bool is_output = direction == AAUDIO_DIRECTION_OUTPUT;
+ int64_t hw_frame_index;
+ int64_t hw_tstamp;
+ AAudioStream * stream = is_output ? stm->ostream : stm->istream;
+ // For an output stream (resp. input stream), get the number of frames
+ // written to (resp read from) the hardware.
+ int64_t app_frame_index = is_output
+ ? WRAP(AAudioStream_getFramesWritten)(stream)
+ : WRAP(AAudioStream_getFramesRead)(stream);
+
+ assert(tstamp_ns < std::numeric_limits<uint64_t>::max());
+ int64_t signed_tstamp_ns = static_cast<int64_t>(tstamp_ns);
+
+ // Get a timestamp for a particular frame index written to or read from the
+ // hardware.
+ auto result = WRAP(AAudioStream_getTimestamp)(stream, CLOCK_MONOTONIC,
+ &hw_frame_index, &hw_tstamp);
+ if (result != AAUDIO_OK) {
+ LOG("AAudioStream_getTimestamp failure for %s: %s",
+ is_output ? "output" : "input",
+ WRAP(AAudio_convertResultToText)(result));
+ return 0;
+ }
+
+ // Compute the difference between the app and the hardware indices.
+ int64_t frame_index_delta = app_frame_index - hw_frame_index;
+ // Convert to ns
+ int64_t frame_time_delta = (frame_index_delta * NS_PER_S) / stm->sample_rate;
+ // Extrapolate from the known timestamp for a particular frame presented.
+ int64_t app_frame_hw_time = hw_tstamp + frame_time_delta;
+ // For an output stream, the latency is positive, for an input stream, it's
+ // negative.
+ int64_t latency_ns = is_output ? app_frame_hw_time - signed_tstamp_ns
+ : signed_tstamp_ns - app_frame_hw_time;
+ int64_t latency_frames = stm->sample_rate * latency_ns / NS_PER_S;
+
+ LOGV("Latency in frames (%s): %d (%dms)", is_output ? "output" : "input",
+ latency_frames, latency_ns / 1e6);
+
+ return latency_frames;
+}
+
+void
+compute_and_report_latency_metrics(cubeb_stream * stm)
+{
+ AAudioTimingInfo info = {};
+
+ info.tstamp = now_ns();
+
+ if (stm->ostream) {
+ uint64_t latency_frames =
+ aaudio_get_latency(stm, AAUDIO_DIRECTION_OUTPUT, info.tstamp);
+ if (latency_frames) {
+ info.output_latency = latency_frames;
+ info.output_frame_index =
+ WRAP(AAudioStream_getFramesWritten)(stm->ostream);
+ }
+ }
+ if (stm->istream) {
+ uint64_t latency_frames =
+ aaudio_get_latency(stm, AAUDIO_DIRECTION_INPUT, info.tstamp);
+ if (latency_frames) {
+ info.input_latency = latency_frames;
+ }
+ }
+
+ if (info.output_latency || info.input_latency) {
+ stm->latency_metrics_available = true;
+ stm->timing_info.write(info);
+ }
+}
+
+// Returning AAUDIO_CALLBACK_RESULT_STOP seems to put the stream in
+// an invalid state. Seems like an AAudio bug/bad documentation.
+// We therefore only return it on error.
+
+static aaudio_data_callback_result_t
+aaudio_duplex_data_cb(AAudioStream * astream, void * user_data,
+ void * audio_data, int32_t num_frames)
+{
+ cubeb_stream * stm = (cubeb_stream *)user_data;
+ AutoInCallback aic(stm);
+ assert(stm->ostream == astream);
+ assert(stm->istream);
+ assert(num_frames >= 0);
+
+ stream_state state = atomic_load(&stm->state);
+ int istate = WRAP(AAudioStream_getState)(stm->istream);
+ int ostate = WRAP(AAudioStream_getState)(stm->ostream);
+
+ // all other states may happen since the callback might be called
+ // from within requestStart
+ assert(state != stream_state::SHUTDOWN);
+
+ // This might happen when we started draining but not yet actually
+ // stopped the stream from the state thread.
+ if (state == stream_state::DRAINING) {
+ LOG("Draining in duplex callback");
+ std::memset(audio_data, 0x0, num_frames * stm->out_frame_size);
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+ }
+
+ if (num_frames * stm->in_frame_size > stm->in_buf.size()) {
+ LOG("Resizing input buffer in duplex callback");
+ stm->in_buf.resize(num_frames * stm->in_frame_size);
+ }
+ // The aaudio docs state that AAudioStream_read must not be called on
+ // the stream associated with a callback. But we call it on the input stream
+ // while this callback is for the output stream so this is ok.
+ // We also pass timeout 0, giving us strong non-blocking guarantees.
+ // This is exactly how it's done in the aaudio duplex example code snippet.
+ long in_num_frames =
+ WRAP(AAudioStream_read)(stm->istream, stm->in_buf.data(), num_frames, 0);
+ if (in_num_frames < 0) { // error
+ if (in_num_frames == AAUDIO_STREAM_STATE_DISCONNECTED) {
+ LOG("AAudioStream_read: %s (reinitializing)",
+ WRAP(AAudio_convertResultToText)(in_num_frames));
+ reinitialize_stream(stm);
+ } else {
+ stm->state.store(stream_state::ERROR);
+ }
+ LOG("AAudioStream_read: %s",
+ WRAP(AAudio_convertResultToText)(in_num_frames));
+ return AAUDIO_CALLBACK_RESULT_STOP;
+ }
+
+ ALOGV("aaudio duplex data cb on stream %p: state %ld (in: %d, out: %d), "
+ "num_frames: %ld, read: %ld",
+ (void *)stm, state, istate, ostate, num_frames, in_num_frames);
+
+ compute_and_report_latency_metrics(stm);
+
+ // This can happen shortly after starting the stream. AAudio might immediately
+ // begin to buffer output but not have any input ready yet. We could
+ // block AAudioStream_read (passing a timeout > 0) but that leads to issues
+ // since blocking in this callback is a bad idea in general and it might break
+ // the stream when it is stopped by another thread shortly after being
+ // started. We therefore simply send silent input to the application, as shown
+ // in the AAudio duplex stream code example.
+ if (in_num_frames < num_frames) {
+ // LOG("AAudioStream_read returned not enough frames: %ld instead of %d",
+ // in_num_frames, num_frames);
+ unsigned left = num_frames - in_num_frames;
+ uint8_t * buf = stm->in_buf.data() + in_num_frames * stm->in_frame_size;
+ std::memset(buf, 0x0, left * stm->in_frame_size);
+ in_num_frames = num_frames;
+ }
+
+ long done_frames =
+ cubeb_resampler_fill(stm->resampler, stm->in_buf.data(), &in_num_frames,
+ audio_data, num_frames);
+
+ if (done_frames < 0 || done_frames > num_frames) {
+ LOG("Error in data callback or resampler: %ld", done_frames);
+ stm->state.store(stream_state::ERROR);
+ return AAUDIO_CALLBACK_RESULT_STOP;
+ }
+ if (done_frames < num_frames) {
+ stm->state.store(stream_state::DRAINING);
+ stm->context->state.waiting.store(true);
+ stm->context->state.cond.notify_one();
+
+ char * begin =
+ static_cast<char *>(audio_data) + done_frames * stm->out_frame_size;
+ std::memset(begin, 0x0, (num_frames - done_frames) * stm->out_frame_size);
+ }
+
+ apply_volume(stm, audio_data, done_frames);
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+}
+
+static aaudio_data_callback_result_t
+aaudio_output_data_cb(AAudioStream * astream, void * user_data,
+ void * audio_data, int32_t num_frames)
+{
+ cubeb_stream * stm = (cubeb_stream *)user_data;
+ AutoInCallback aic(stm);
+ assert(stm->ostream == astream);
+ assert(!stm->istream);
+ assert(num_frames >= 0);
+
+ stream_state state = stm->state.load();
+ int ostate = WRAP(AAudioStream_getState)(stm->ostream);
+ ALOGV("aaudio output data cb on stream %p: state %ld (%d), num_frames: %ld",
+ stm, state, ostate, num_frames);
+
+ // all other states may happen since the callback might be called
+ // from within requestStart
+ assert(state != stream_state::SHUTDOWN);
+
+ // This might happen when we started draining but not yet actually
+ // stopped the stream from the state thread.
+ if (state == stream_state::DRAINING) {
+ std::memset(audio_data, 0x0, num_frames * stm->out_frame_size);
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+ }
+
+ compute_and_report_latency_metrics(stm);
+
+ long done_frames = cubeb_resampler_fill(stm->resampler, nullptr, nullptr,
+ audio_data, num_frames);
+ if (done_frames < 0 || done_frames > num_frames) {
+ LOG("Error in data callback or resampler: %ld", done_frames);
+ stm->state.store(stream_state::ERROR);
+ return AAUDIO_CALLBACK_RESULT_STOP;
+ }
+
+ if (done_frames < num_frames) {
+ stm->state.store(stream_state::DRAINING);
+ stm->context->state.waiting.store(true);
+ stm->context->state.cond.notify_one();
+
+ char * begin =
+ static_cast<char *>(audio_data) + done_frames * stm->out_frame_size;
+ std::memset(begin, 0x0, (num_frames - done_frames) * stm->out_frame_size);
+ }
+
+ apply_volume(stm, audio_data, done_frames);
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+}
+
+static aaudio_data_callback_result_t
+aaudio_input_data_cb(AAudioStream * astream, void * user_data,
+ void * audio_data, int32_t num_frames)
+{
+ cubeb_stream * stm = (cubeb_stream *)user_data;
+ AutoInCallback aic(stm);
+ assert(stm->istream == astream);
+ assert(!stm->ostream);
+ assert(num_frames >= 0);
+
+ stream_state state = stm->state.load();
+ int istate = WRAP(AAudioStream_getState)(stm->istream);
+ ALOGV("aaudio input data cb on stream %p: state %ld (%d), num_frames: %ld",
+ stm, state, istate, num_frames);
+
+ // all other states may happen since the callback might be called
+ // from within requestStart
+ assert(state != stream_state::SHUTDOWN);
+
+ // This might happen when we started draining but not yet actually
+ // STOPPED the stream from the state thread.
+ if (state == stream_state::DRAINING) {
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+ }
+
+ compute_and_report_latency_metrics(stm);
+
+ long input_frame_count = num_frames;
+ long done_frames = cubeb_resampler_fill(stm->resampler, audio_data,
+ &input_frame_count, nullptr, 0);
+
+ if (done_frames < 0 || done_frames > num_frames) {
+ LOG("Error in data callback or resampler: %ld", done_frames);
+ stm->state.store(stream_state::ERROR);
+ return AAUDIO_CALLBACK_RESULT_STOP;
+ }
+
+ if (done_frames < input_frame_count) {
+ // we don't really drain an input stream, just have to
+ // stop it from the state thread. That is signaled via the
+ // DRAINING state.
+ stm->state.store(stream_state::DRAINING);
+ stm->context->state.waiting.store(true);
+ stm->context->state.cond.notify_one();
+ }
+
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+}
+
+static void
+reinitialize_stream(cubeb_stream * stm)
+{
+ // This cannot be done from within the error callback, bounce to another
+ // thread.
+ // In this situation, the lock is acquired for the entire duration of the
+ // function, so that this reinitialization period is atomic.
+ std::thread([stm] {
+ lock_guard lock(stm->mutex);
+ stream_state state = stm->state.load();
+ bool was_playing = state == stream_state::STARTED ||
+ state == stream_state::STARTING ||
+ state == stream_state::DRAINING;
+ int err = aaudio_stream_stop_locked(stm, lock);
+ // error ignored.
+ aaudio_stream_destroy_locked(stm, lock);
+ err = aaudio_stream_init_impl(stm, lock);
+
+ assert(stm->in_use.load());
+
+ if (err != CUBEB_OK) {
+ aaudio_stream_destroy_locked(stm, lock);
+ LOG("aaudio_stream_init_impl error while reiniting: %s",
+ WRAP(AAudio_convertResultToText)(err));
+ stm->state.store(stream_state::ERROR);
+ return;
+ }
+
+ if (was_playing) {
+ err = aaudio_stream_start_locked(stm, lock);
+ if (err != CUBEB_OK) {
+ aaudio_stream_destroy_locked(stm, lock);
+ LOG("aaudio_stream_start error while reiniting: %s",
+ WRAP(AAudio_convertResultToText)(err));
+ stm->state.store(stream_state::ERROR);
+ return;
+ }
+ }
+ }).detach();
+}
+
+static void
+aaudio_error_cb(AAudioStream * astream, void * user_data, aaudio_result_t error)
+{
+ cubeb_stream * stm = static_cast<cubeb_stream *>(user_data);
+ assert(stm->ostream == astream || stm->istream == astream);
+
+ // Device change -- reinitialize on the new default device.
+ if (error == AAUDIO_ERROR_DISCONNECTED) {
+ LOG("Audio device change, reinitializing stream");
+ reinitialize_stream(stm);
+ return;
+ }
+
+ LOG("AAudio error callback: %s", WRAP(AAudio_convertResultToText)(error));
+ stm->state.store(stream_state::ERROR);
+}
+
+static int
+realize_stream(AAudioStreamBuilder * sb, const cubeb_stream_params * params,
+ AAudioStream ** stream, unsigned * frame_size)
+{
+ aaudio_result_t res;
+ assert(params->rate);
+ assert(params->channels);
+
+ WRAP(AAudioStreamBuilder_setSampleRate)
+ (sb, static_cast<int32_t>(params->rate));
+ WRAP(AAudioStreamBuilder_setChannelCount)
+ (sb, static_cast<int32_t>(params->channels));
+
+ aaudio_format_t fmt;
+ switch (params->format) {
+ case CUBEB_SAMPLE_S16NE:
+ fmt = AAUDIO_FORMAT_PCM_I16;
+ *frame_size = sizeof(int16_t) * params->channels;
+ break;
+ case CUBEB_SAMPLE_FLOAT32NE:
+ fmt = AAUDIO_FORMAT_PCM_FLOAT;
+ *frame_size = sizeof(float) * params->channels;
+ break;
+ default:
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ WRAP(AAudioStreamBuilder_setFormat)(sb, fmt);
+ res = WRAP(AAudioStreamBuilder_openStream)(sb, stream);
+ if (res == AAUDIO_ERROR_INVALID_FORMAT) {
+ LOG("AAudio device doesn't support output format %d", fmt);
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ if (params->rate && res == AAUDIO_ERROR_INVALID_RATE) {
+ // The requested rate is not supported.
+ // Just try again with default rate, we create a resampler anyways
+ WRAP(AAudioStreamBuilder_setSampleRate)(sb, AAUDIO_UNSPECIFIED);
+ res = WRAP(AAudioStreamBuilder_openStream)(sb, stream);
+ LOG("Requested rate of %u is not supported, inserting resampler",
+ params->rate);
+ }
+
+ // When the app has no permission to record audio
+ // (android.permission.RECORD_AUDIO) but requested and input stream, this will
+ // return INVALID_ARGUMENT.
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStreamBuilder_openStream: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static void
+aaudio_stream_destroy(cubeb_stream * stm)
+{
+ lock_guard lock(stm->mutex);
+ stm->in_use.store(false);
+ aaudio_stream_destroy_locked(stm, lock);
+}
+
+static void
+aaudio_stream_destroy_locked(cubeb_stream * stm, lock_guard<mutex> & lock)
+{
+ assert(stm->state == stream_state::STOPPED ||
+ stm->state == stream_state::STOPPING ||
+ stm->state == stream_state::INIT ||
+ stm->state == stream_state::DRAINING ||
+ stm->state == stream_state::ERROR ||
+ stm->state == stream_state::SHUTDOWN);
+
+ aaudio_result_t res;
+
+ // No callbacks are triggered anymore when requestStop returns.
+ // That is important as we otherwise might read from a closed istream
+ // for a duplex stream.
+ if (stm->ostream) {
+ if (stm->state != stream_state::STOPPED &&
+ stm->state != stream_state::STOPPING &&
+ stm->state != stream_state::SHUTDOWN) {
+ res = WRAP(AAudioStream_requestStop)(stm->ostream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStreamBuilder_requestStop: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ }
+ }
+
+ WRAP(AAudioStream_close)(stm->ostream);
+ stm->ostream = nullptr;
+ }
+
+ if (stm->istream) {
+ if (stm->state != stream_state::STOPPED &&
+ stm->state != stream_state::STOPPING &&
+ stm->state != stream_state::SHUTDOWN) {
+ res = WRAP(AAudioStream_requestStop)(stm->istream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStreamBuilder_requestStop: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ }
+ }
+
+ WRAP(AAudioStream_close)(stm->istream);
+ stm->istream = nullptr;
+ }
+
+ if (stm->resampler) {
+ cubeb_resampler_destroy(stm->resampler);
+ stm->resampler = nullptr;
+ }
+
+ stm->in_buf = {};
+ stm->in_frame_size = {};
+ stm->out_format = {};
+ stm->out_channels = {};
+ stm->out_frame_size = {};
+
+ stm->state.store(stream_state::INIT);
+}
+
+static int
+aaudio_stream_init_impl(cubeb_stream * stm, lock_guard<mutex> & lock)
+{
+ assert(stm->state.load() == stream_state::INIT);
+
+ cubeb_async_log_reset_threads();
+
+ aaudio_result_t res;
+ AAudioStreamBuilder * sb;
+ res = WRAP(AAudio_createStreamBuilder)(&sb);
+ if (res != AAUDIO_OK) {
+ LOG("AAudio_createStreamBuilder: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return CUBEB_ERROR;
+ }
+
+ // make sure the builder is always destroyed
+ struct StreamBuilderDestructor {
+ void operator()(AAudioStreamBuilder * sb)
+ {
+ WRAP(AAudioStreamBuilder_delete)(sb);
+ }
+ };
+
+ std::unique_ptr<AAudioStreamBuilder, StreamBuilderDestructor> sbPtr(sb);
+
+ WRAP(AAudioStreamBuilder_setErrorCallback)(sb, aaudio_error_cb, stm);
+ WRAP(AAudioStreamBuilder_setBufferCapacityInFrames)
+ (sb, static_cast<int32_t>(stm->latency_frames));
+
+ AAudioStream_dataCallback in_data_callback{};
+ AAudioStream_dataCallback out_data_callback{};
+ if (stm->output_stream_params && stm->input_stream_params) {
+ out_data_callback = aaudio_duplex_data_cb;
+ in_data_callback = nullptr;
+ } else if (stm->input_stream_params) {
+ in_data_callback = aaudio_input_data_cb;
+ } else if (stm->output_stream_params) {
+ out_data_callback = aaudio_output_data_cb;
+ } else {
+ LOG("Tried to open stream without input or output parameters");
+ return CUBEB_ERROR;
+ }
+
+#ifdef CUBEB_AAUDIO_EXCLUSIVE_STREAM
+ LOG("AAudio setting exclusive share mode for stream");
+ WRAP(AAudioStreamBuilder_setSharingMode)(sb, AAUDIO_SHARING_MODE_EXCLUSIVE);
+#endif
+
+ if (stm->latency_frames <= POWERSAVE_LATENCY_FRAMES_THRESHOLD) {
+ LOG("AAudio setting low latency mode for stream");
+ WRAP(AAudioStreamBuilder_setPerformanceMode)
+ (sb, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+ } else {
+ LOG("AAudio setting power saving mode for stream");
+ WRAP(AAudioStreamBuilder_setPerformanceMode)
+ (sb, AAUDIO_PERFORMANCE_MODE_POWER_SAVING);
+ }
+
+ unsigned frame_size;
+
+ // initialize streams
+ // output
+ cubeb_stream_params out_params;
+ if (stm->output_stream_params) {
+ int output_preset = stm->voice_output ? AAUDIO_USAGE_VOICE_COMMUNICATION
+ : AAUDIO_USAGE_MEDIA;
+ WRAP(AAudioStreamBuilder_setUsage)(sb, output_preset);
+ WRAP(AAudioStreamBuilder_setDirection)(sb, AAUDIO_DIRECTION_OUTPUT);
+ WRAP(AAudioStreamBuilder_setDataCallback)(sb, out_data_callback, stm);
+ assert(stm->latency_frames < std::numeric_limits<int32_t>::max());
+ LOG("Frames per callback set to %d for output", stm->latency_frames);
+ WRAP(AAudioStreamBuilder_setFramesPerDataCallback)
+ (sb, static_cast<int32_t>(stm->latency_frames));
+
+ int res_err = realize_stream(sb, stm->output_stream_params.get(),
+ &stm->ostream, &frame_size);
+ if (res_err) {
+ return res_err;
+ }
+
+ int32_t output_burst_size =
+ WRAP(AAudioStream_getFramesPerBurst)(stm->ostream);
+ LOG("AAudio output burst size: %d", output_burst_size);
+ // 3 times the burst size seems to be robust.
+ res = WRAP(AAudioStream_setBufferSizeInFrames)(stm->ostream,
+ output_burst_size * 3);
+ if (res < 0) {
+ LOG("AAudioStream_setBufferSizeInFrames error (ostream): %s",
+ WRAP(AAudio_convertResultToText)(res));
+ // Not fatal
+ }
+
+ int rate = WRAP(AAudioStream_getSampleRate)(stm->ostream);
+ LOG("AAudio output stream sharing mode: %d",
+ WRAP(AAudioStream_getSharingMode)(stm->ostream));
+ LOG("AAudio output stream performance mode: %d",
+ WRAP(AAudioStream_getPerformanceMode)(stm->ostream));
+ LOG("AAudio output stream buffer capacity: %d",
+ WRAP(AAudioStream_getBufferCapacityInFrames)(stm->ostream));
+ LOG("AAudio output stream buffer size: %d",
+ WRAP(AAudioStream_getBufferSizeInFrames)(stm->ostream));
+ LOG("AAudio output stream sample-rate: %d", rate);
+
+ stm->sample_rate = stm->output_stream_params->rate;
+ out_params = *stm->output_stream_params;
+ out_params.rate = rate;
+
+ stm->out_channels = stm->output_stream_params->channels;
+ stm->out_format = stm->output_stream_params->format;
+ stm->out_frame_size = frame_size;
+ stm->volume.store(1.f);
+ }
+
+ // input
+ cubeb_stream_params in_params;
+ if (stm->input_stream_params) {
+ // Match what the OpenSL backend does for now, we could use UNPROCESSED and
+ // VOICE_COMMUNICATION here, but we'd need to make it clear that
+ // application-level AEC and other voice processing should be disabled
+ // there.
+ int input_preset = stm->voice_input ? AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
+ : AAUDIO_INPUT_PRESET_CAMCORDER;
+ WRAP(AAudioStreamBuilder_setInputPreset)(sb, input_preset);
+ WRAP(AAudioStreamBuilder_setDirection)(sb, AAUDIO_DIRECTION_INPUT);
+ WRAP(AAudioStreamBuilder_setDataCallback)(sb, in_data_callback, stm);
+ assert(stm->latency_frames < std::numeric_limits<int32_t>::max());
+ LOG("Frames per callback set to %d for input", stm->latency_frames);
+ WRAP(AAudioStreamBuilder_setFramesPerDataCallback)
+ (sb, static_cast<int32_t>(stm->latency_frames));
+ int res_err = realize_stream(sb, stm->input_stream_params.get(),
+ &stm->istream, &frame_size);
+ if (res_err) {
+ return res_err;
+ }
+
+ int32_t input_burst_size =
+ WRAP(AAudioStream_getFramesPerBurst)(stm->istream);
+ LOG("AAudio input burst size: %d", input_burst_size);
+ // 3 times the burst size seems to be robust.
+ res = WRAP(AAudioStream_setBufferSizeInFrames)(stm->istream,
+ input_burst_size * 3);
+ if (res < AAUDIO_OK) {
+ LOG("AAudioStream_setBufferSizeInFrames error (istream): %s",
+ WRAP(AAudio_convertResultToText)(res));
+ // Not fatal
+ }
+
+ int bcap = WRAP(AAudioStream_getBufferCapacityInFrames)(stm->istream);
+ int rate = WRAP(AAudioStream_getSampleRate)(stm->istream);
+ LOG("AAudio input stream sharing mode: %d",
+ WRAP(AAudioStream_getSharingMode)(stm->istream));
+ LOG("AAudio input stream performance mode: %d",
+ WRAP(AAudioStream_getPerformanceMode)(stm->istream));
+ LOG("AAudio input stream buffer capacity: %d", bcap);
+ LOG("AAudio input stream buffer size: %d",
+ WRAP(AAudioStream_getBufferSizeInFrames)(stm->istream));
+ LOG("AAudio input stream buffer rate: %d", rate);
+
+ stm->in_buf.resize(bcap * frame_size);
+ assert(!stm->sample_rate ||
+ stm->sample_rate == stm->input_stream_params->rate);
+
+ stm->sample_rate = stm->input_stream_params->rate;
+ in_params = *stm->input_stream_params;
+ in_params.rate = rate;
+ stm->in_frame_size = frame_size;
+ }
+
+ // initialize resampler
+ stm->resampler = cubeb_resampler_create(
+ stm, stm->input_stream_params ? &in_params : nullptr,
+ stm->output_stream_params ? &out_params : nullptr, stm->sample_rate,
+ stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DEFAULT,
+ CUBEB_RESAMPLER_RECLOCK_NONE);
+
+ if (!stm->resampler) {
+ LOG("Failed to create resampler");
+ return CUBEB_ERROR;
+ }
+
+ // the stream isn't started initially. We don't need to differentiate
+ // between a stream that was just initialized and one that played
+ // already but was stopped.
+ stm->state.store(stream_state::STOPPED);
+ LOG("Cubeb stream (%p) INIT success", (void *)stm);
+ return CUBEB_OK;
+}
+
+static int
+aaudio_stream_init(cubeb * ctx, cubeb_stream ** stream,
+ char const * /* stream_name */, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ assert(!input_device);
+ assert(!output_device);
+
+ // atomically find a free stream.
+ cubeb_stream * stm = nullptr;
+ unique_lock<mutex> lock;
+ for (auto & stream : ctx->streams) {
+ // This check is only an optimization, we don't strictly need it
+ // since we check again after locking the mutex.
+ if (stream.in_use.load()) {
+ continue;
+ }
+
+ // if this fails, another thread initialized this stream
+ // between our check of in_use and this.
+ lock = unique_lock(stream.mutex, std::try_to_lock);
+ if (!lock.owns_lock()) {
+ continue;
+ }
+
+ if (stream.in_use.load()) {
+ lock = {};
+ continue;
+ }
+
+ stm = &stream;
+ break;
+ }
+
+ if (!stm) {
+ LOG("Error: maximum number of streams reached");
+ return CUBEB_ERROR;
+ }
+
+ stm->in_use.store(true);
+ stm->context = ctx;
+ stm->user_ptr = user_ptr;
+ stm->data_callback = data_callback;
+ stm->state_callback = state_callback;
+ stm->voice_input = input_stream_params &&
+ !!(input_stream_params->prefs & CUBEB_STREAM_PREF_VOICE);
+ stm->voice_output = output_stream_params &&
+ !!(output_stream_params->prefs & CUBEB_STREAM_PREF_VOICE);
+ stm->previous_clock = 0;
+ stm->latency_frames = latency_frames;
+ if (output_stream_params) {
+ stm->output_stream_params = std::make_unique<cubeb_stream_params>();
+ *(stm->output_stream_params) = *output_stream_params;
+ }
+ if (input_stream_params) {
+ stm->input_stream_params = std::make_unique<cubeb_stream_params>();
+ *(stm->input_stream_params) = *input_stream_params;
+ }
+
+ LOG("cubeb stream prefs: voice_input: %s voice_output: %s",
+ stm->voice_input ? "true" : "false",
+ stm->voice_output ? "true" : "false");
+
+ // This is ok: the thread is marked as being in use
+ lock.unlock();
+ int err;
+
+ {
+ lock_guard guard(stm->mutex);
+ err = aaudio_stream_init_impl(stm, guard);
+ }
+
+ if (err != CUBEB_OK) {
+ aaudio_stream_destroy(stm);
+ return err;
+ }
+
+ *stream = stm;
+ return CUBEB_OK;
+}
+
+static int
+aaudio_stream_start(cubeb_stream * stm)
+{
+ lock_guard lock(stm->mutex);
+ return aaudio_stream_start_locked(stm, lock);
+}
+
+static int
+aaudio_stream_start_locked(cubeb_stream * stm, lock_guard<mutex> & lock)
+{
+ assert(stm && stm->in_use.load());
+ stream_state state = stm->state.load();
+ int istate = stm->istream ? WRAP(AAudioStream_getState)(stm->istream) : 0;
+ int ostate = stm->ostream ? WRAP(AAudioStream_getState)(stm->ostream) : 0;
+ LOGV("STARTING stream %p: %d (%d %d)", (void *)stm, state, istate, ostate);
+
+ switch (state) {
+ case stream_state::STARTED:
+ case stream_state::STARTING:
+ LOG("cubeb stream %p already STARTING/STARTED", (void *)stm);
+ return CUBEB_OK;
+ case stream_state::ERROR:
+ case stream_state::SHUTDOWN:
+ return CUBEB_ERROR;
+ case stream_state::INIT:
+ assert(false && "Invalid stream");
+ return CUBEB_ERROR;
+ case stream_state::STOPPED:
+ case stream_state::STOPPING:
+ case stream_state::DRAINING:
+ break;
+ }
+
+ aaudio_result_t res;
+
+ // Important to start istream before ostream.
+ // As soon as we start ostream, the callbacks might be triggered an we
+ // might read from istream (on duplex). If istream wasn't started yet
+ // this is a problem.
+ if (stm->istream) {
+ res = WRAP(AAudioStream_requestStart)(stm->istream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_requestStart (istream): %s",
+ WRAP(AAudio_convertResultToText)(res));
+ stm->state.store(stream_state::ERROR);
+ return CUBEB_ERROR;
+ }
+ }
+
+ if (stm->ostream) {
+ res = WRAP(AAudioStream_requestStart)(stm->ostream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_requestStart (ostream): %s",
+ WRAP(AAudio_convertResultToText)(res));
+ stm->state.store(stream_state::ERROR);
+ return CUBEB_ERROR;
+ }
+ }
+
+ int ret = CUBEB_OK;
+ bool success;
+
+ while (!(success = stm->state.compare_exchange_strong(
+ state, stream_state::STARTING))) {
+ // we land here only if the state has changed in the meantime
+ switch (state) {
+ // If an error ocurred in the meantime, we can't change that.
+ // The stream will be stopped when shut down.
+ case stream_state::ERROR:
+ ret = CUBEB_ERROR;
+ break;
+ // The only situation in which the state could have switched to draining
+ // is if the callback was already fired and requested draining. Don't
+ // overwrite that. It's not an error either though.
+ case stream_state::DRAINING:
+ break;
+
+ // If the state switched [DRAINING -> STOPPING] or [DRAINING/STOPPING ->
+ // STOPPED] in the meantime, we can simply overwrite that since we
+ // restarted the stream.
+ case stream_state::STOPPING:
+ case stream_state::STOPPED:
+ continue;
+
+ // There is no situation in which the state could have been valid before
+ // but now in shutdown mode, since we hold the streams mutex.
+ // There is also no way that it switched *into* STARTING or
+ // STARTED mode.
+ default:
+ assert(false && "Invalid state change");
+ ret = CUBEB_ERROR;
+ break;
+ }
+
+ break;
+ }
+
+ if (success) {
+ stm->context->state.waiting.store(true);
+ stm->context->state.cond.notify_one();
+ }
+
+ return ret;
+}
+
+static int
+aaudio_stream_stop(cubeb_stream * stm)
+{
+ assert(stm && stm->in_use.load());
+ lock_guard lock(stm->mutex);
+ return aaudio_stream_stop_locked(stm, lock);
+}
+
+static int
+aaudio_stream_stop_locked(cubeb_stream * stm, lock_guard<mutex> & lock)
+{
+ assert(stm && stm->in_use.load());
+
+ stream_state state = stm->state.load();
+ int istate = stm->istream ? WRAP(AAudioStream_getState)(stm->istream) : 0;
+ int ostate = stm->ostream ? WRAP(AAudioStream_getState)(stm->ostream) : 0;
+ LOG("STOPPING stream %p: %d (%d %d)", (void *)stm, state, istate, ostate);
+
+ switch (state) {
+ case stream_state::STOPPED:
+ case stream_state::STOPPING:
+ case stream_state::DRAINING:
+ LOG("cubeb stream %p already STOPPING/STOPPED", (void *)stm);
+ return CUBEB_OK;
+ case stream_state::ERROR:
+ case stream_state::SHUTDOWN:
+ return CUBEB_ERROR;
+ case stream_state::INIT:
+ assert(false && "Invalid stream");
+ return CUBEB_ERROR;
+ case stream_state::STARTED:
+ case stream_state::STARTING:
+ break;
+ }
+
+ aaudio_result_t res;
+
+ // No callbacks are triggered anymore when requestStop returns.
+ // That is important as we otherwise might read from a closed istream
+ // for a duplex stream.
+ // Therefor it is important to close ostream first.
+ if (stm->ostream) {
+ // Could use pause + flush here as well, the public cubeb interface
+ // doesn't state behavior.
+ res = WRAP(AAudioStream_requestStop)(stm->ostream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_requestStop (ostream): %s",
+ WRAP(AAudio_convertResultToText)(res));
+ stm->state.store(stream_state::ERROR);
+ return CUBEB_ERROR;
+ }
+ }
+
+ if (stm->istream) {
+ res = WRAP(AAudioStream_requestStop)(stm->istream);
+ if (res != AAUDIO_OK) {
+ LOG("AAudioStream_requestStop (istream): %s",
+ WRAP(AAudio_convertResultToText)(res));
+ stm->state.store(stream_state::ERROR);
+ return CUBEB_ERROR;
+ }
+ }
+
+ int ret = CUBEB_OK;
+ bool success;
+ while (!(success = atomic_compare_exchange_strong(&stm->state, &state,
+ stream_state::STOPPING))) {
+ // we land here only if the state has changed in the meantime
+ switch (state) {
+ // If an error ocurred in the meantime, we can't change that.
+ // The stream will be STOPPED when shut down.
+ case stream_state::ERROR:
+ ret = CUBEB_ERROR;
+ break;
+ // If it was switched to DRAINING in the meantime, it was or
+ // will be STOPPED soon anyways. We don't interfere with
+ // the DRAINING process, no matter in which state.
+ // Not an error
+ case stream_state::DRAINING:
+ case stream_state::STOPPING:
+ case stream_state::STOPPED:
+ break;
+
+ // If the state switched from STARTING to STARTED in the meantime
+ // we can simply overwrite that since we just STOPPED it.
+ case stream_state::STARTED:
+ continue;
+
+ // There is no situation in which the state could have been valid before
+ // but now in shutdown mode, since we hold the streams mutex.
+ // There is also no way that it switched *into* STARTING mode.
+ default:
+ assert(false && "Invalid state change");
+ ret = CUBEB_ERROR;
+ break;
+ }
+
+ break;
+ }
+
+ if (success) {
+ stm->context->state.waiting.store(true);
+ stm->context->state.cond.notify_one();
+ }
+
+ return ret;
+}
+
+static int
+aaudio_stream_get_position(cubeb_stream * stm, uint64_t * position)
+{
+ assert(stm && stm->in_use.load());
+ lock_guard lock(stm->mutex);
+
+ stream_state state = stm->state.load();
+ AAudioStream * stream = stm->ostream ? stm->ostream : stm->istream;
+ switch (state) {
+ case stream_state::ERROR:
+ case stream_state::SHUTDOWN:
+ return CUBEB_ERROR;
+ case stream_state::DRAINING:
+ case stream_state::STOPPED:
+ case stream_state::STOPPING:
+ // getTimestamp is only valid when the stream is playing.
+ // Simply return the number of frames passed to aaudio
+ *position = WRAP(AAudioStream_getFramesRead)(stream);
+ if (*position < stm->previous_clock) {
+ *position = stm->previous_clock;
+ } else {
+ stm->previous_clock = *position;
+ }
+ return CUBEB_OK;
+ case stream_state::INIT:
+ assert(false && "Invalid stream");
+ return CUBEB_ERROR;
+ case stream_state::STARTED:
+ case stream_state::STARTING:
+ break;
+ }
+
+ // No callback yet, the stream hasn't really started.
+ if (stm->previous_clock == 0 && !stm->timing_info.updated()) {
+ LOG("Not timing info yet");
+ *position = 0;
+ return CUBEB_OK;
+ }
+
+ AAudioTimingInfo info = stm->timing_info.read();
+ LOGV("AAudioTimingInfo idx:%lu tstamp:%lu latency:%u",
+ info.output_frame_index, info.tstamp, info.output_latency);
+ // Interpolate client side since the last callback.
+ uint64_t interpolation =
+ stm->sample_rate * (now_ns() - info.tstamp) / NS_PER_S;
+ *position = info.output_frame_index + interpolation - info.output_latency;
+ if (*position < stm->previous_clock) {
+ *position = stm->previous_clock;
+ } else {
+ stm->previous_clock = *position;
+ }
+
+ LOG("aaudio_stream_get_position: %" PRIu64 " frames", *position);
+
+ return CUBEB_OK;
+}
+
+static int
+aaudio_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
+{
+ if (!stm->ostream) {
+ LOG("error: aaudio_stream_get_latency on input-only stream");
+ return CUBEB_ERROR;
+ }
+
+ if (!stm->latency_metrics_available) {
+ LOG("Not timing info yet (output)");
+ return CUBEB_OK;
+ }
+
+ AAudioTimingInfo info = stm->timing_info.read();
+
+ *latency = info.output_latency;
+ LOG("aaudio_stream_get_latency, %u frames", *latency);
+
+ return CUBEB_OK;
+}
+
+static int
+aaudio_stream_get_input_latency(cubeb_stream * stm, uint32_t * latency)
+{
+ if (!stm->istream) {
+ LOG("error: aaudio_stream_get_input_latency on an output-only stream");
+ return CUBEB_ERROR;
+ }
+
+ if (!stm->latency_metrics_available) {
+ LOG("Not timing info yet (input)");
+ return CUBEB_OK;
+ }
+
+ AAudioTimingInfo info = stm->timing_info.read();
+
+ *latency = info.input_latency;
+ LOG("aaudio_stream_get_latency, %u frames", *latency);
+
+ return CUBEB_OK;
+}
+
+static int
+aaudio_stream_set_volume(cubeb_stream * stm, float volume)
+{
+ assert(stm && stm->in_use.load() && stm->ostream);
+ stm->volume.store(volume);
+ return CUBEB_OK;
+}
+
+aaudio_data_callback_result_t
+dummy_callback(AAudioStream * stream, void * userData, void * audioData,
+ int32_t numFrames)
+{
+ return AAUDIO_CALLBACK_RESULT_STOP;
+}
+
+// Returns a dummy stream with all default settings
+static AAudioStream *
+init_dummy_stream()
+{
+ AAudioStreamBuilder * streamBuilder;
+ aaudio_result_t res;
+ res = WRAP(AAudio_createStreamBuilder)(&streamBuilder);
+ if (res != AAUDIO_OK) {
+ LOG("init_dummy_stream: AAudio_createStreamBuilder: %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return nullptr;
+ }
+ WRAP(AAudioStreamBuilder_setDataCallback)
+ (streamBuilder, dummy_callback, nullptr);
+ WRAP(AAudioStreamBuilder_setPerformanceMode)
+ (streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+
+ AAudioStream * stream;
+ res = WRAP(AAudioStreamBuilder_openStream)(streamBuilder, &stream);
+ if (res != AAUDIO_OK) {
+ LOG("init_dummy_stream: AAudioStreamBuilder_openStream %s",
+ WRAP(AAudio_convertResultToText)(res));
+ return nullptr;
+ }
+ WRAP(AAudioStreamBuilder_delete)(streamBuilder);
+
+ return stream;
+}
+
+static void
+destroy_dummy_stream(AAudioStream * stream)
+{
+ WRAP(AAudioStream_close)(stream);
+}
+
+static int
+aaudio_get_min_latency(cubeb * ctx, cubeb_stream_params params,
+ uint32_t * latency_frames)
+{
+ AAudioStream * stream = init_dummy_stream();
+
+ if (!stream) {
+ return CUBEB_ERROR;
+ }
+
+ // https://android.googlesource.com/platform/compatibility/cdd/+/refs/heads/master/5_multimedia/5_6_audio-latency.md
+ *latency_frames = WRAP(AAudioStream_getFramesPerBurst)(stream);
+
+ LOG("aaudio_get_min_latency: %u frames", *latency_frames);
+
+ destroy_dummy_stream(stream);
+
+ return CUBEB_OK;
+}
+
+int
+aaudio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
+{
+ AAudioStream * stream = init_dummy_stream();
+
+ if (!stream) {
+ return CUBEB_ERROR;
+ }
+
+ *rate = WRAP(AAudioStream_getSampleRate)(stream);
+
+ LOG("aaudio_get_preferred_sample_rate %uHz", *rate);
+
+ destroy_dummy_stream(stream);
+
+ return CUBEB_OK;
+}
+
+extern "C" int
+aaudio_init(cubeb ** context, char const * context_name);
+
+const static struct cubeb_ops aaudio_ops = {
+ /*.init =*/aaudio_init,
+ /*.get_backend_id =*/aaudio_get_backend_id,
+ /*.get_max_channel_count =*/aaudio_get_max_channel_count,
+ /* .get_min_latency =*/aaudio_get_min_latency,
+ /*.get_preferred_sample_rate =*/aaudio_get_preferred_sample_rate,
+ /*.get_supported_input_processing_params =*/nullptr,
+ /*.enumerate_devices =*/nullptr,
+ /*.device_collection_destroy =*/nullptr,
+ /*.destroy =*/aaudio_destroy,
+ /*.stream_init =*/aaudio_stream_init,
+ /*.stream_destroy =*/aaudio_stream_destroy,
+ /*.stream_start =*/aaudio_stream_start,
+ /*.stream_stop =*/aaudio_stream_stop,
+ /*.stream_get_position =*/aaudio_stream_get_position,
+ /*.stream_get_latency =*/aaudio_stream_get_latency,
+ /*.stream_get_input_latency =*/aaudio_stream_get_input_latency,
+ /*.stream_set_volume =*/aaudio_stream_set_volume,
+ /*.stream_set_name =*/nullptr,
+ /*.stream_get_current_device =*/nullptr,
+ /*.stream_set_input_mute =*/nullptr,
+ /*.stream_set_input_processing_params =*/nullptr,
+ /*.stream_device_destroy =*/nullptr,
+ /*.stream_register_device_changed_callback =*/nullptr,
+ /*.register_device_collection_changed =*/nullptr};
+
+extern "C" /*static*/ int
+aaudio_init(cubeb ** context, char const * /* context_name */)
+{
+ // load api
+ void * libaaudio = nullptr;
+#ifndef DISABLE_LIBAAUDIO_DLOPEN
+ libaaudio = dlopen("libaaudio.so", RTLD_NOW);
+ if (!libaaudio) {
+ return CUBEB_ERROR;
+ }
+
+#define LOAD(x) \
+ { \
+ cubeb_##x = (decltype(x) *)(dlsym(libaaudio, #x)); \
+ if (!WRAP(x)) { \
+ LOG("AAudio: Failed to load %s", #x); \
+ dlclose(libaaudio); \
+ return CUBEB_ERROR; \
+ } \
+ }
+
+ LIBAAUDIO_API_VISIT(LOAD);
+#undef LOAD
+#endif
+
+ cubeb * ctx = new cubeb;
+ ctx->ops = &aaudio_ops;
+ ctx->libaaudio = libaaudio;
+
+ ctx->state.thread = std::thread(state_thread, ctx);
+
+ // NOTE: using platform-specific APIs we could set the priority of the
+ // notifier thread lower than the priority of the state thread.
+ // This way, it's more likely that the state thread will be woken up
+ // by the condition variable signal when both are currently waiting
+ ctx->state.notifier = std::thread(notifier_thread, ctx);
+
+ *context = ctx;
+ return CUBEB_OK;
+}
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_alsa.c b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_alsa.c
new file mode 100644
index 0000000000..f114f27d7b
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_alsa.c
@@ -0,0 +1,1493 @@
+/*
+ * Copyright © 2011 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#undef NDEBUG
+#define _DEFAULT_SOURCE
+#define _BSD_SOURCE
+#if defined(__NetBSD__)
+#define _NETBSD_SOURCE /* timersub() */
+#endif
+#define _XOPEN_SOURCE 500
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_tracing.h"
+#include <alsa/asoundlib.h>
+#include <assert.h>
+#include <dlfcn.h>
+#include <limits.h>
+#include <poll.h>
+#include <pthread.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#ifdef DISABLE_LIBASOUND_DLOPEN
+#define WRAP(x) x
+#else
+#define WRAP(x) (*cubeb_##x)
+#define LIBASOUND_API_VISIT(X) \
+ X(snd_config) \
+ X(snd_config_add) \
+ X(snd_config_copy) \
+ X(snd_config_delete) \
+ X(snd_config_get_id) \
+ X(snd_config_get_string) \
+ X(snd_config_imake_integer) \
+ X(snd_config_search) \
+ X(snd_config_search_definition) \
+ X(snd_lib_error_set_handler) \
+ X(snd_pcm_avail_update) \
+ X(snd_pcm_close) \
+ X(snd_pcm_delay) \
+ X(snd_pcm_drain) \
+ X(snd_pcm_frames_to_bytes) \
+ X(snd_pcm_get_params) \
+ X(snd_pcm_hw_params_any) \
+ X(snd_pcm_hw_params_get_channels_max) \
+ X(snd_pcm_hw_params_get_rate) \
+ X(snd_pcm_hw_params_set_rate_near) \
+ X(snd_pcm_hw_params_sizeof) \
+ X(snd_pcm_nonblock) \
+ X(snd_pcm_open) \
+ X(snd_pcm_open_lconf) \
+ X(snd_pcm_pause) \
+ X(snd_pcm_poll_descriptors) \
+ X(snd_pcm_poll_descriptors_count) \
+ X(snd_pcm_poll_descriptors_revents) \
+ X(snd_pcm_readi) \
+ X(snd_pcm_recover) \
+ X(snd_pcm_set_params) \
+ X(snd_pcm_start) \
+ X(snd_pcm_state) \
+ X(snd_pcm_writei)
+
+#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
+LIBASOUND_API_VISIT(MAKE_TYPEDEF);
+#undef MAKE_TYPEDEF
+/* snd_pcm_hw_params_alloca is actually a macro */
+#define snd_pcm_hw_params_sizeof cubeb_snd_pcm_hw_params_sizeof
+#endif
+
+#define CUBEB_STREAM_MAX 16
+#define CUBEB_WATCHDOG_MS 10000
+
+#define CUBEB_ALSA_PCM_NAME "default"
+
+#define ALSA_PA_PLUGIN "ALSA <-> PulseAudio PCM I/O Plugin"
+
+/* ALSA is not thread-safe. snd_pcm_t instances are individually protected
+ by the owning cubeb_stream's mutex. snd_pcm_t creation and destruction
+ is not thread-safe until ALSA 1.0.24 (see alsa-lib.git commit 91c9c8f1),
+ so those calls must be wrapped in the following mutex. */
+static pthread_mutex_t cubeb_alsa_mutex = PTHREAD_MUTEX_INITIALIZER;
+static int cubeb_alsa_error_handler_set = 0;
+
+static struct cubeb_ops const alsa_ops;
+
+struct cubeb {
+ struct cubeb_ops const * ops;
+ void * libasound;
+
+ pthread_t thread;
+
+ /* Mutex for streams array, must not be held while blocked in poll(2). */
+ pthread_mutex_t mutex;
+
+ /* Sparse array of streams managed by this context. */
+ cubeb_stream * streams[CUBEB_STREAM_MAX];
+
+ /* fds and nfds are only updated by alsa_run when rebuild is set. */
+ struct pollfd * fds;
+ nfds_t nfds;
+ int rebuild;
+
+ int shutdown;
+
+ /* Control pipe for forcing poll to wake and rebuild fds or recalculate the
+ * timeout. */
+ int control_fd_read;
+ int control_fd_write;
+
+ /* Track number of active streams. This is limited to CUBEB_STREAM_MAX
+ due to resource contraints. */
+ unsigned int active_streams;
+
+ /* Local configuration with handle_underrun workaround set for PulseAudio
+ ALSA plugin. Will be NULL if the PA ALSA plugin is not in use or the
+ workaround is not required. */
+ snd_config_t * local_config;
+ int is_pa;
+};
+
+enum stream_state { INACTIVE, RUNNING, DRAINING, PROCESSING, ERROR };
+
+struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
+ cubeb * context;
+ void * user_ptr;
+ /**/
+ pthread_mutex_t mutex;
+ snd_pcm_t * pcm;
+ cubeb_data_callback data_callback;
+ cubeb_state_callback state_callback;
+ snd_pcm_uframes_t stream_position;
+ snd_pcm_uframes_t last_position;
+ snd_pcm_uframes_t buffer_size;
+ cubeb_stream_params params;
+
+ /* Every member after this comment is protected by the owning context's
+ mutex rather than the stream's mutex, or is only used on the context's
+ run thread. */
+ pthread_cond_t cond; /* Signaled when the stream's state is changed. */
+
+ enum stream_state state;
+
+ struct pollfd * saved_fds; /* A copy of the pollfds passed in at init time. */
+ struct pollfd *
+ fds; /* Pointer to this waitable's pollfds within struct cubeb's fds. */
+ nfds_t nfds;
+
+ struct timeval drain_timeout;
+
+ /* XXX: Horrible hack -- if an active stream has been idle for
+ CUBEB_WATCHDOG_MS it will be disabled and the error callback will be
+ called. This works around a bug seen with older versions of ALSA and
+ PulseAudio where streams would stop requesting new data despite still
+ being logically active and playing. */
+ struct timeval last_activity;
+ float volume;
+
+ char * buffer;
+ snd_pcm_uframes_t bufframes;
+ snd_pcm_stream_t stream_type;
+
+ struct cubeb_stream * other_stream;
+};
+
+static int
+any_revents(struct pollfd * fds, nfds_t nfds)
+{
+ nfds_t i;
+
+ for (i = 0; i < nfds; ++i) {
+ if (fds[i].revents) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+cmp_timeval(struct timeval * a, struct timeval * b)
+{
+ if (a->tv_sec == b->tv_sec) {
+ if (a->tv_usec == b->tv_usec) {
+ return 0;
+ }
+ return a->tv_usec > b->tv_usec ? 1 : -1;
+ }
+ return a->tv_sec > b->tv_sec ? 1 : -1;
+}
+
+static int
+timeval_to_relative_ms(struct timeval * tv)
+{
+ struct timeval now;
+ struct timeval dt;
+ long long t;
+ int r;
+
+ gettimeofday(&now, NULL);
+ r = cmp_timeval(tv, &now);
+ if (r >= 0) {
+ timersub(tv, &now, &dt);
+ } else {
+ timersub(&now, tv, &dt);
+ }
+ t = dt.tv_sec;
+ t *= 1000;
+ t += (dt.tv_usec + 500) / 1000;
+
+ if (t > INT_MAX) {
+ t = INT_MAX;
+ } else if (t < INT_MIN) {
+ t = INT_MIN;
+ }
+
+ return r >= 0 ? t : -t;
+}
+
+static int
+ms_until(struct timeval * tv)
+{
+ return timeval_to_relative_ms(tv);
+}
+
+static int
+ms_since(struct timeval * tv)
+{
+ return -timeval_to_relative_ms(tv);
+}
+
+static void
+rebuild(cubeb * ctx)
+{
+ nfds_t nfds;
+ int i;
+ nfds_t j;
+ cubeb_stream * stm;
+
+ assert(ctx->rebuild);
+
+ /* Always count context's control pipe fd. */
+ nfds = 1;
+ for (i = 0; i < CUBEB_STREAM_MAX; ++i) {
+ stm = ctx->streams[i];
+ if (stm) {
+ stm->fds = NULL;
+ if (stm->state == RUNNING) {
+ nfds += stm->nfds;
+ }
+ }
+ }
+
+ free(ctx->fds);
+ ctx->fds = calloc(nfds, sizeof(struct pollfd));
+ assert(ctx->fds);
+ ctx->nfds = nfds;
+
+ /* Include context's control pipe fd. */
+ ctx->fds[0].fd = ctx->control_fd_read;
+ ctx->fds[0].events = POLLIN | POLLERR;
+
+ for (i = 0, j = 1; i < CUBEB_STREAM_MAX; ++i) {
+ stm = ctx->streams[i];
+ if (stm && stm->state == RUNNING) {
+ memcpy(&ctx->fds[j], stm->saved_fds, stm->nfds * sizeof(struct pollfd));
+ stm->fds = &ctx->fds[j];
+ j += stm->nfds;
+ }
+ }
+
+ ctx->rebuild = 0;
+}
+
+static void
+poll_wake(cubeb * ctx)
+{
+ if (write(ctx->control_fd_write, "x", 1) < 0) {
+ /* ignore write error */
+ }
+}
+
+static void
+set_timeout(struct timeval * timeout, unsigned int ms)
+{
+ gettimeofday(timeout, NULL);
+ timeout->tv_sec += ms / 1000;
+ timeout->tv_usec += (ms % 1000) * 1000;
+}
+
+static void
+stream_buffer_decrement(cubeb_stream * stm, long count)
+{
+ char * bufremains =
+ stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, count);
+ memmove(stm->buffer, bufremains,
+ WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes - count));
+ stm->bufframes -= count;
+}
+
+static void
+alsa_set_stream_state(cubeb_stream * stm, enum stream_state state)
+{
+ cubeb * ctx;
+ int r;
+
+ ctx = stm->context;
+ stm->state = state;
+ r = pthread_cond_broadcast(&stm->cond);
+ assert(r == 0);
+ ctx->rebuild = 1;
+ poll_wake(ctx);
+}
+
+static enum stream_state
+alsa_process_stream(cubeb_stream * stm)
+{
+ unsigned short revents;
+ snd_pcm_sframes_t avail;
+ int draining;
+
+ draining = 0;
+
+ pthread_mutex_lock(&stm->mutex);
+
+ /* Call _poll_descriptors_revents() even if we don't use it
+ to let underlying plugins clear null events. Otherwise poll()
+ may wake up again and again, producing unnecessary CPU usage. */
+ WRAP(snd_pcm_poll_descriptors_revents)
+ (stm->pcm, stm->fds, stm->nfds, &revents);
+
+ avail = WRAP(snd_pcm_avail_update)(stm->pcm);
+
+ /* Got null event? Bail and wait for another wakeup. */
+ if (avail == 0) {
+ pthread_mutex_unlock(&stm->mutex);
+ return RUNNING;
+ }
+
+ /* This could happen if we were suspended with SIGSTOP/Ctrl+Z for a long time.
+ */
+ if ((unsigned int)avail > stm->buffer_size) {
+ avail = stm->buffer_size;
+ }
+
+ /* Capture: Read available frames */
+ if (stm->stream_type == SND_PCM_STREAM_CAPTURE && avail > 0) {
+ snd_pcm_sframes_t got;
+
+ if (avail + stm->bufframes > stm->buffer_size) {
+ /* Buffer overflow. Skip and overwrite with new data. */
+ stm->bufframes = 0;
+ // TODO: should it be marked as DRAINING?
+ }
+
+ got = WRAP(snd_pcm_readi)(stm->pcm, stm->buffer + stm->bufframes, avail);
+
+ if (got < 0) {
+ avail = got; // the error handler below will recover us
+ } else {
+ stm->bufframes += got;
+ stm->stream_position += got;
+
+ gettimeofday(&stm->last_activity, NULL);
+ }
+ }
+
+ /* Capture: Pass read frames to callback function */
+ if (stm->stream_type == SND_PCM_STREAM_CAPTURE && stm->bufframes > 0 &&
+ (!stm->other_stream ||
+ stm->other_stream->bufframes < stm->other_stream->buffer_size)) {
+ snd_pcm_sframes_t wrote = stm->bufframes;
+ struct cubeb_stream * mainstm = stm->other_stream ? stm->other_stream : stm;
+ void * other_buffer = stm->other_stream ? stm->other_stream->buffer +
+ stm->other_stream->bufframes
+ : NULL;
+
+ /* Correct write size to the other stream available space */
+ if (stm->other_stream &&
+ wrote > (snd_pcm_sframes_t)(stm->other_stream->buffer_size -
+ stm->other_stream->bufframes)) {
+ wrote = stm->other_stream->buffer_size - stm->other_stream->bufframes;
+ }
+
+ pthread_mutex_unlock(&stm->mutex);
+ wrote = stm->data_callback(mainstm, stm->user_ptr, stm->buffer,
+ other_buffer, wrote);
+ pthread_mutex_lock(&stm->mutex);
+
+ if (wrote < 0) {
+ avail = wrote; // the error handler below will recover us
+ } else {
+ stream_buffer_decrement(stm, wrote);
+
+ if (stm->other_stream) {
+ stm->other_stream->bufframes += wrote;
+ }
+ }
+ }
+
+ /* Playback: Don't have enough data? Let's ask for more. */
+ if (stm->stream_type == SND_PCM_STREAM_PLAYBACK &&
+ avail > (snd_pcm_sframes_t)stm->bufframes &&
+ (!stm->other_stream || stm->other_stream->bufframes > 0)) {
+ long got = avail - stm->bufframes;
+ void * other_buffer = stm->other_stream ? stm->other_stream->buffer : NULL;
+ char * buftail =
+ stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes);
+
+ /* Correct read size to the other stream available frames */
+ if (stm->other_stream &&
+ got > (snd_pcm_sframes_t)stm->other_stream->bufframes) {
+ got = stm->other_stream->bufframes;
+ }
+
+ pthread_mutex_unlock(&stm->mutex);
+ got = stm->data_callback(stm, stm->user_ptr, other_buffer, buftail, got);
+ pthread_mutex_lock(&stm->mutex);
+
+ if (got < 0) {
+ avail = got; // the error handler below will recover us
+ } else {
+ stm->bufframes += got;
+
+ if (stm->other_stream) {
+ stream_buffer_decrement(stm->other_stream, got);
+ }
+ }
+ }
+
+ /* Playback: Still don't have enough data? Add some silence. */
+ if (stm->stream_type == SND_PCM_STREAM_PLAYBACK &&
+ avail > (snd_pcm_sframes_t)stm->bufframes) {
+ long drain_frames = avail - stm->bufframes;
+ double drain_time = (double)drain_frames / stm->params.rate;
+
+ char * buftail =
+ stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes);
+ memset(buftail, 0, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, drain_frames));
+ stm->bufframes = avail;
+
+ /* Mark as draining, unless we're waiting for capture */
+ if (!stm->other_stream || stm->other_stream->bufframes > 0) {
+ set_timeout(&stm->drain_timeout, drain_time * 1000);
+
+ draining = 1;
+ }
+ }
+
+ /* Playback: Have enough data and no errors. Let's write it out. */
+ if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > 0) {
+ snd_pcm_sframes_t wrote;
+
+ if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) {
+ float * b = (float *)stm->buffer;
+ for (uint32_t i = 0; i < avail * stm->params.channels; i++) {
+ b[i] *= stm->volume;
+ }
+ } else {
+ short * b = (short *)stm->buffer;
+ for (uint32_t i = 0; i < avail * stm->params.channels; i++) {
+ b[i] *= stm->volume;
+ }
+ }
+
+ wrote = WRAP(snd_pcm_writei)(stm->pcm, stm->buffer, avail);
+ if (wrote < 0) {
+ avail = wrote; // the error handler below will recover us
+ } else {
+ stream_buffer_decrement(stm, wrote);
+
+ stm->stream_position += wrote;
+ gettimeofday(&stm->last_activity, NULL);
+ }
+ }
+
+ /* Got some error? Let's try to recover the stream. */
+ if (avail < 0) {
+ avail = WRAP(snd_pcm_recover)(stm->pcm, avail, 0);
+
+ /* Capture pcm must be started after initial setup/recover */
+ if (avail >= 0 && stm->stream_type == SND_PCM_STREAM_CAPTURE &&
+ WRAP(snd_pcm_state)(stm->pcm) == SND_PCM_STATE_PREPARED) {
+ avail = WRAP(snd_pcm_start)(stm->pcm);
+ }
+ }
+
+ /* Failed to recover, this stream must be broken. */
+ if (avail < 0) {
+ pthread_mutex_unlock(&stm->mutex);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ return ERROR;
+ }
+
+ pthread_mutex_unlock(&stm->mutex);
+ return draining ? DRAINING : RUNNING;
+}
+
+static int
+alsa_run(cubeb * ctx)
+{
+ int r;
+ int timeout;
+ int i;
+ char dummy;
+ cubeb_stream * stm;
+ enum stream_state state;
+
+ pthread_mutex_lock(&ctx->mutex);
+
+ if (ctx->rebuild) {
+ rebuild(ctx);
+ }
+
+ /* Wake up at least once per second for the watchdog. */
+ timeout = 1000;
+ for (i = 0; i < CUBEB_STREAM_MAX; ++i) {
+ stm = ctx->streams[i];
+ if (stm && stm->state == DRAINING) {
+ r = ms_until(&stm->drain_timeout);
+ if (r >= 0 && timeout > r) {
+ timeout = r;
+ }
+ }
+ }
+
+ pthread_mutex_unlock(&ctx->mutex);
+ r = poll(ctx->fds, ctx->nfds, timeout);
+ pthread_mutex_lock(&ctx->mutex);
+
+ if (r > 0) {
+ if (ctx->fds[0].revents & POLLIN) {
+ if (read(ctx->control_fd_read, &dummy, 1) < 0) {
+ /* ignore read error */
+ }
+
+ if (ctx->shutdown) {
+ pthread_mutex_unlock(&ctx->mutex);
+ return -1;
+ }
+ }
+
+ for (i = 0; i < CUBEB_STREAM_MAX; ++i) {
+ stm = ctx->streams[i];
+ /* We can't use snd_pcm_poll_descriptors_revents here because of
+ https://github.com/kinetiknz/cubeb/issues/135. */
+ if (stm && stm->state == RUNNING && stm->fds &&
+ any_revents(stm->fds, stm->nfds)) {
+ alsa_set_stream_state(stm, PROCESSING);
+ pthread_mutex_unlock(&ctx->mutex);
+ state = alsa_process_stream(stm);
+ pthread_mutex_lock(&ctx->mutex);
+ alsa_set_stream_state(stm, state);
+ }
+ }
+ } else if (r == 0) {
+ for (i = 0; i < CUBEB_STREAM_MAX; ++i) {
+ stm = ctx->streams[i];
+ if (stm) {
+ if (stm->state == DRAINING && ms_since(&stm->drain_timeout) >= 0) {
+ alsa_set_stream_state(stm, INACTIVE);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+ } else if (stm->state == RUNNING &&
+ ms_since(&stm->last_activity) > CUBEB_WATCHDOG_MS) {
+ alsa_set_stream_state(stm, ERROR);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ }
+ }
+ }
+ }
+
+ pthread_mutex_unlock(&ctx->mutex);
+
+ return 0;
+}
+
+static void *
+alsa_run_thread(void * context)
+{
+ cubeb * ctx = context;
+ int r;
+
+ CUBEB_REGISTER_THREAD("cubeb rendering thread");
+
+ do {
+ r = alsa_run(ctx);
+ } while (r >= 0);
+
+ CUBEB_UNREGISTER_THREAD();
+
+ return NULL;
+}
+
+static snd_config_t *
+get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm)
+{
+ int r;
+ snd_config_t * slave_pcm;
+ snd_config_t * slave_def;
+ snd_config_t * pcm;
+ char const * string;
+ char node_name[64];
+
+ slave_def = NULL;
+
+ r = WRAP(snd_config_search)(root_pcm, "slave", &slave_pcm);
+ if (r < 0) {
+ return NULL;
+ }
+
+ r = WRAP(snd_config_get_string)(slave_pcm, &string);
+ if (r >= 0) {
+ r = WRAP(snd_config_search_definition)(lconf, "pcm_slave", string,
+ &slave_def);
+ if (r < 0) {
+ return NULL;
+ }
+ }
+
+ do {
+ r = WRAP(snd_config_search)(slave_def ? slave_def : slave_pcm, "pcm", &pcm);
+ if (r < 0) {
+ break;
+ }
+
+ r = WRAP(snd_config_get_string)(slave_def ? slave_def : slave_pcm, &string);
+ if (r < 0) {
+ break;
+ }
+
+ r = snprintf(node_name, sizeof(node_name), "pcm.%s", string);
+ if (r < 0 || r > (int)sizeof(node_name)) {
+ break;
+ }
+ r = WRAP(snd_config_search)(lconf, node_name, &pcm);
+ if (r < 0) {
+ break;
+ }
+
+ return pcm;
+ } while (0);
+
+ if (slave_def) {
+ WRAP(snd_config_delete)(slave_def);
+ }
+
+ return NULL;
+}
+
+/* Work around PulseAudio ALSA plugin bug where the PA server forces a
+ higher than requested latency, but the plugin does not update its (and
+ ALSA's) internal state to reflect that, leading to an immediate underrun
+ situation. Inspired by WINE's make_handle_underrun_config.
+ Reference: http://mailman.alsa-project.org/pipermail/alsa-devel/2012-July/05
+ */
+static snd_config_t *
+init_local_config_with_workaround(char const * pcm_name)
+{
+ int r;
+ snd_config_t * lconf;
+ snd_config_t * pcm_node;
+ snd_config_t * node;
+ char const * string;
+ char node_name[64];
+
+ lconf = NULL;
+
+ if (WRAP(snd_config) == NULL) {
+ return NULL;
+ }
+
+ r = WRAP(snd_config_copy)(&lconf, WRAP(snd_config));
+ if (r < 0) {
+ return NULL;
+ }
+
+ do {
+ r = WRAP(snd_config_search_definition)(lconf, "pcm", pcm_name, &pcm_node);
+ if (r < 0) {
+ break;
+ }
+
+ r = WRAP(snd_config_get_id)(pcm_node, &string);
+ if (r < 0) {
+ break;
+ }
+
+ r = snprintf(node_name, sizeof(node_name), "pcm.%s", string);
+ if (r < 0 || r > (int)sizeof(node_name)) {
+ break;
+ }
+ r = WRAP(snd_config_search)(lconf, node_name, &pcm_node);
+ if (r < 0) {
+ break;
+ }
+
+ /* If this PCM has a slave, walk the slave configurations until we reach the
+ * bottom. */
+ while ((node = get_slave_pcm_node(lconf, pcm_node)) != NULL) {
+ pcm_node = node;
+ }
+
+ /* Fetch the PCM node's type, and bail out if it's not the PulseAudio
+ * plugin. */
+ r = WRAP(snd_config_search)(pcm_node, "type", &node);
+ if (r < 0) {
+ break;
+ }
+
+ r = WRAP(snd_config_get_string)(node, &string);
+ if (r < 0) {
+ break;
+ }
+
+ if (strcmp(string, "pulse") != 0) {
+ break;
+ }
+
+ /* Don't clobber an explicit existing handle_underrun value, set it only
+ if it doesn't already exist. */
+ r = WRAP(snd_config_search)(pcm_node, "handle_underrun", &node);
+ if (r != -ENOENT) {
+ break;
+ }
+
+ /* Disable pcm_pulse's asynchronous underrun handling. */
+ r = WRAP(snd_config_imake_integer)(&node, "handle_underrun", 0);
+ if (r < 0) {
+ break;
+ }
+
+ r = WRAP(snd_config_add)(pcm_node, node);
+ if (r < 0) {
+ break;
+ }
+
+ return lconf;
+ } while (0);
+
+ WRAP(snd_config_delete)(lconf);
+
+ return NULL;
+}
+
+static int
+alsa_locked_pcm_open(snd_pcm_t ** pcm, char const * pcm_name,
+ snd_pcm_stream_t stream, snd_config_t * local_config)
+{
+ int r;
+
+ pthread_mutex_lock(&cubeb_alsa_mutex);
+ if (local_config) {
+ r = WRAP(snd_pcm_open_lconf)(pcm, pcm_name, stream, SND_PCM_NONBLOCK,
+ local_config);
+ } else {
+ r = WRAP(snd_pcm_open)(pcm, pcm_name, stream, SND_PCM_NONBLOCK);
+ }
+ pthread_mutex_unlock(&cubeb_alsa_mutex);
+
+ return r;
+}
+
+static int
+alsa_locked_pcm_close(snd_pcm_t * pcm)
+{
+ int r;
+
+ pthread_mutex_lock(&cubeb_alsa_mutex);
+ r = WRAP(snd_pcm_close)(pcm);
+ pthread_mutex_unlock(&cubeb_alsa_mutex);
+
+ return r;
+}
+
+static int
+alsa_register_stream(cubeb * ctx, cubeb_stream * stm)
+{
+ int i;
+
+ pthread_mutex_lock(&ctx->mutex);
+ for (i = 0; i < CUBEB_STREAM_MAX; ++i) {
+ if (!ctx->streams[i]) {
+ ctx->streams[i] = stm;
+ break;
+ }
+ }
+ pthread_mutex_unlock(&ctx->mutex);
+
+ return i == CUBEB_STREAM_MAX;
+}
+
+static void
+alsa_unregister_stream(cubeb_stream * stm)
+{
+ cubeb * ctx;
+ int i;
+
+ ctx = stm->context;
+
+ pthread_mutex_lock(&ctx->mutex);
+ for (i = 0; i < CUBEB_STREAM_MAX; ++i) {
+ if (ctx->streams[i] == stm) {
+ ctx->streams[i] = NULL;
+ break;
+ }
+ }
+ pthread_mutex_unlock(&ctx->mutex);
+}
+
+static void
+silent_error_handler(char const * file, int line, char const * function,
+ int err, char const * fmt, ...)
+{
+ (void)file;
+ (void)line;
+ (void)function;
+ (void)err;
+ (void)fmt;
+}
+
+/*static*/ int
+alsa_init(cubeb ** context, char const * context_name)
+{
+ (void)context_name;
+ void * libasound = NULL;
+ cubeb * ctx;
+ int r;
+ int i;
+ int fd[2];
+ pthread_attr_t attr;
+ snd_pcm_t * dummy;
+
+ assert(context);
+ *context = NULL;
+
+#ifndef DISABLE_LIBASOUND_DLOPEN
+ libasound = dlopen("libasound.so.2", RTLD_LAZY);
+ if (!libasound) {
+ libasound = dlopen("libasound.so", RTLD_LAZY);
+ if (!libasound) {
+ return CUBEB_ERROR;
+ }
+ }
+
+#define LOAD(x) \
+ { \
+ cubeb_##x = dlsym(libasound, #x); \
+ if (!cubeb_##x) { \
+ dlclose(libasound); \
+ return CUBEB_ERROR; \
+ } \
+ }
+
+ LIBASOUND_API_VISIT(LOAD);
+#undef LOAD
+#endif
+
+ pthread_mutex_lock(&cubeb_alsa_mutex);
+ if (!cubeb_alsa_error_handler_set) {
+ WRAP(snd_lib_error_set_handler)(silent_error_handler);
+ cubeb_alsa_error_handler_set = 1;
+ }
+ pthread_mutex_unlock(&cubeb_alsa_mutex);
+
+ ctx = calloc(1, sizeof(*ctx));
+ assert(ctx);
+
+ ctx->ops = &alsa_ops;
+ ctx->libasound = libasound;
+
+ r = pthread_mutex_init(&ctx->mutex, NULL);
+ assert(r == 0);
+
+ r = pipe(fd);
+ assert(r == 0);
+
+ for (i = 0; i < 2; ++i) {
+ fcntl(fd[i], F_SETFD, fcntl(fd[i], F_GETFD) | FD_CLOEXEC);
+ fcntl(fd[i], F_SETFL, fcntl(fd[i], F_GETFL) | O_NONBLOCK);
+ }
+
+ ctx->control_fd_read = fd[0];
+ ctx->control_fd_write = fd[1];
+
+ /* Force an early rebuild when alsa_run is first called to ensure fds and
+ nfds have been initialized. */
+ ctx->rebuild = 1;
+
+ r = pthread_attr_init(&attr);
+ assert(r == 0);
+
+ r = pthread_attr_setstacksize(&attr, 256 * 1024);
+ assert(r == 0);
+
+ r = pthread_create(&ctx->thread, &attr, alsa_run_thread, ctx);
+ assert(r == 0);
+
+ r = pthread_attr_destroy(&attr);
+ assert(r == 0);
+
+ /* Open a dummy PCM to force the configuration space to be evaluated so that
+ init_local_config_with_workaround can find and modify the default node. */
+ r = alsa_locked_pcm_open(&dummy, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK,
+ NULL);
+ if (r >= 0) {
+ alsa_locked_pcm_close(dummy);
+ }
+ ctx->is_pa = 0;
+ pthread_mutex_lock(&cubeb_alsa_mutex);
+ ctx->local_config = init_local_config_with_workaround(CUBEB_ALSA_PCM_NAME);
+ pthread_mutex_unlock(&cubeb_alsa_mutex);
+ if (ctx->local_config) {
+ ctx->is_pa = 1;
+ r = alsa_locked_pcm_open(&dummy, CUBEB_ALSA_PCM_NAME,
+ SND_PCM_STREAM_PLAYBACK, ctx->local_config);
+ /* If we got a local_config, we found a PA PCM. If opening a PCM with that
+ config fails with EINVAL, the PA PCM is too old for this workaround. */
+ if (r == -EINVAL) {
+ pthread_mutex_lock(&cubeb_alsa_mutex);
+ WRAP(snd_config_delete)(ctx->local_config);
+ pthread_mutex_unlock(&cubeb_alsa_mutex);
+ ctx->local_config = NULL;
+ } else if (r >= 0) {
+ alsa_locked_pcm_close(dummy);
+ }
+ }
+
+ *context = ctx;
+
+ return CUBEB_OK;
+}
+
+static char const *
+alsa_get_backend_id(cubeb * ctx)
+{
+ (void)ctx;
+ return "alsa";
+}
+
+static void
+alsa_destroy(cubeb * ctx)
+{
+ int r;
+
+ assert(ctx);
+
+ pthread_mutex_lock(&ctx->mutex);
+ ctx->shutdown = 1;
+ poll_wake(ctx);
+ pthread_mutex_unlock(&ctx->mutex);
+
+ r = pthread_join(ctx->thread, NULL);
+ assert(r == 0);
+
+ close(ctx->control_fd_read);
+ close(ctx->control_fd_write);
+ pthread_mutex_destroy(&ctx->mutex);
+ free(ctx->fds);
+
+ if (ctx->local_config) {
+ pthread_mutex_lock(&cubeb_alsa_mutex);
+ WRAP(snd_config_delete)(ctx->local_config);
+ pthread_mutex_unlock(&cubeb_alsa_mutex);
+ }
+#ifndef DISABLE_LIBASOUND_DLOPEN
+ if (ctx->libasound) {
+ dlclose(ctx->libasound);
+ }
+#endif
+ free(ctx);
+}
+
+static void
+alsa_stream_destroy(cubeb_stream * stm);
+
+static int
+alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream,
+ char const * stream_name, snd_pcm_stream_t stream_type,
+ cubeb_devid deviceid,
+ cubeb_stream_params * stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ (void)stream_name;
+ cubeb_stream * stm;
+ int r;
+ snd_pcm_format_t format;
+ snd_pcm_uframes_t period_size;
+ int latency_us = 0;
+ char const * pcm_name =
+ deviceid ? (char const *)deviceid : CUBEB_ALSA_PCM_NAME;
+
+ assert(ctx && stream);
+
+ *stream = NULL;
+
+ if (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ switch (stream_params->format) {
+ case CUBEB_SAMPLE_S16LE:
+ format = SND_PCM_FORMAT_S16_LE;
+ break;
+ case CUBEB_SAMPLE_S16BE:
+ format = SND_PCM_FORMAT_S16_BE;
+ break;
+ case CUBEB_SAMPLE_FLOAT32LE:
+ format = SND_PCM_FORMAT_FLOAT_LE;
+ break;
+ case CUBEB_SAMPLE_FLOAT32BE:
+ format = SND_PCM_FORMAT_FLOAT_BE;
+ break;
+ default:
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ pthread_mutex_lock(&ctx->mutex);
+ if (ctx->active_streams >= CUBEB_STREAM_MAX) {
+ pthread_mutex_unlock(&ctx->mutex);
+ return CUBEB_ERROR;
+ }
+ ctx->active_streams += 1;
+ pthread_mutex_unlock(&ctx->mutex);
+
+ stm = calloc(1, sizeof(*stm));
+ assert(stm);
+
+ stm->context = ctx;
+ stm->data_callback = data_callback;
+ stm->state_callback = state_callback;
+ stm->user_ptr = user_ptr;
+ stm->params = *stream_params;
+ stm->state = INACTIVE;
+ stm->volume = 1.0;
+ stm->buffer = NULL;
+ stm->bufframes = 0;
+ stm->stream_type = stream_type;
+ stm->other_stream = NULL;
+
+ r = pthread_mutex_init(&stm->mutex, NULL);
+ assert(r == 0);
+
+ r = pthread_cond_init(&stm->cond, NULL);
+ assert(r == 0);
+
+ r = alsa_locked_pcm_open(&stm->pcm, pcm_name, stm->stream_type,
+ ctx->local_config);
+ if (r < 0) {
+ alsa_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ r = WRAP(snd_pcm_nonblock)(stm->pcm, 1);
+ assert(r == 0);
+
+ latency_us = latency_frames * 1e6 / stm->params.rate;
+
+ /* Ugly hack: the PA ALSA plugin allows buffer configurations that can't
+ possibly work. See https://bugzilla.mozilla.org/show_bug.cgi?id=761274.
+ Only resort to this hack if the handle_underrun workaround failed. */
+ if (!ctx->local_config && ctx->is_pa) {
+ const int min_latency = 5e5;
+ latency_us = latency_us < min_latency ? min_latency : latency_us;
+ }
+
+ r = WRAP(snd_pcm_set_params)(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED,
+ stm->params.channels, stm->params.rate, 1,
+ latency_us);
+ if (r < 0) {
+ alsa_stream_destroy(stm);
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ r = WRAP(snd_pcm_get_params)(stm->pcm, &stm->buffer_size, &period_size);
+ assert(r == 0);
+
+ /* Double internal buffer size to have enough space when waiting for the other
+ * side of duplex connection */
+ stm->buffer_size *= 2;
+ stm->buffer =
+ calloc(1, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->buffer_size));
+ assert(stm->buffer);
+
+ stm->nfds = WRAP(snd_pcm_poll_descriptors_count)(stm->pcm);
+ assert(stm->nfds > 0);
+
+ stm->saved_fds = calloc(stm->nfds, sizeof(struct pollfd));
+ assert(stm->saved_fds);
+ r = WRAP(snd_pcm_poll_descriptors)(stm->pcm, stm->saved_fds, stm->nfds);
+ assert((nfds_t)r == stm->nfds);
+
+ if (alsa_register_stream(ctx, stm) != 0) {
+ alsa_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ *stream = stm;
+
+ return CUBEB_OK;
+}
+
+static int
+alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
+ cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames, cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ int result = CUBEB_OK;
+ cubeb_stream *instm = NULL, *outstm = NULL;
+
+ if (result == CUBEB_OK && input_stream_params) {
+ result = alsa_stream_init_single(ctx, &instm, stream_name,
+ SND_PCM_STREAM_CAPTURE, input_device,
+ input_stream_params, latency_frames,
+ data_callback, state_callback, user_ptr);
+ }
+
+ if (result == CUBEB_OK && output_stream_params) {
+ result = alsa_stream_init_single(ctx, &outstm, stream_name,
+ SND_PCM_STREAM_PLAYBACK, output_device,
+ output_stream_params, latency_frames,
+ data_callback, state_callback, user_ptr);
+ }
+
+ if (result == CUBEB_OK && input_stream_params && output_stream_params) {
+ instm->other_stream = outstm;
+ outstm->other_stream = instm;
+ }
+
+ if (result != CUBEB_OK && instm) {
+ alsa_stream_destroy(instm);
+ }
+
+ *stream = outstm ? outstm : instm;
+
+ return result;
+}
+
+static void
+alsa_stream_destroy(cubeb_stream * stm)
+{
+ int r;
+ cubeb * ctx;
+
+ assert(stm && (stm->state == INACTIVE || stm->state == ERROR ||
+ stm->state == DRAINING));
+
+ ctx = stm->context;
+
+ if (stm->other_stream) {
+ stm->other_stream->other_stream = NULL; // to stop infinite recursion
+ alsa_stream_destroy(stm->other_stream);
+ }
+
+ pthread_mutex_lock(&stm->mutex);
+ if (stm->pcm) {
+ if (stm->state == DRAINING) {
+ WRAP(snd_pcm_drain)(stm->pcm);
+ }
+ alsa_locked_pcm_close(stm->pcm);
+ stm->pcm = NULL;
+ }
+ free(stm->saved_fds);
+ pthread_mutex_unlock(&stm->mutex);
+ pthread_mutex_destroy(&stm->mutex);
+
+ r = pthread_cond_destroy(&stm->cond);
+ assert(r == 0);
+
+ alsa_unregister_stream(stm);
+
+ pthread_mutex_lock(&ctx->mutex);
+ assert(ctx->active_streams >= 1);
+ ctx->active_streams -= 1;
+ pthread_mutex_unlock(&ctx->mutex);
+
+ free(stm->buffer);
+
+ free(stm);
+}
+
+static int
+alsa_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
+{
+ int r;
+ cubeb_stream * stm;
+ snd_pcm_hw_params_t * hw_params;
+ cubeb_stream_params params;
+ params.rate = 44100;
+ params.format = CUBEB_SAMPLE_FLOAT32NE;
+ params.channels = 2;
+
+ snd_pcm_hw_params_alloca(&hw_params);
+
+ assert(ctx);
+
+ r = alsa_stream_init(ctx, &stm, "", NULL, NULL, NULL, &params, 100, NULL,
+ NULL, NULL);
+ if (r != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+
+ assert(stm);
+
+ r = WRAP(snd_pcm_hw_params_any)(stm->pcm, hw_params);
+ if (r < 0) {
+ return CUBEB_ERROR;
+ }
+
+ r = WRAP(snd_pcm_hw_params_get_channels_max)(hw_params, max_channels);
+ if (r < 0) {
+ return CUBEB_ERROR;
+ }
+
+ alsa_stream_destroy(stm);
+
+ return CUBEB_OK;
+}
+
+static int
+alsa_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
+{
+ (void)ctx;
+ int r, dir;
+ snd_pcm_t * pcm;
+ snd_pcm_hw_params_t * hw_params;
+
+ snd_pcm_hw_params_alloca(&hw_params);
+
+ /* get a pcm, disabling resampling, so we get a rate the
+ * hardware/dmix/pulse/etc. supports. */
+ r = WRAP(snd_pcm_open)(&pcm, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK,
+ SND_PCM_NO_AUTO_RESAMPLE);
+ if (r < 0) {
+ return CUBEB_ERROR;
+ }
+
+ r = WRAP(snd_pcm_hw_params_any)(pcm, hw_params);
+ if (r < 0) {
+ WRAP(snd_pcm_close)(pcm);
+ return CUBEB_ERROR;
+ }
+
+ r = WRAP(snd_pcm_hw_params_get_rate)(hw_params, rate, &dir);
+ if (r >= 0) {
+ /* There is a default rate: use it. */
+ WRAP(snd_pcm_close)(pcm);
+ return CUBEB_OK;
+ }
+
+ /* Use a common rate, alsa may adjust it based on hw/etc. capabilities. */
+ *rate = 44100;
+
+ r = WRAP(snd_pcm_hw_params_set_rate_near)(pcm, hw_params, rate, NULL);
+ if (r < 0) {
+ WRAP(snd_pcm_close)(pcm);
+ return CUBEB_ERROR;
+ }
+
+ WRAP(snd_pcm_close)(pcm);
+
+ return CUBEB_OK;
+}
+
+static int
+alsa_get_min_latency(cubeb * ctx, cubeb_stream_params params,
+ uint32_t * latency_frames)
+{
+ (void)ctx;
+ /* 40ms is found to be an acceptable minimum, even on a super low-end
+ * machine. */
+ *latency_frames = 40 * params.rate / 1000;
+
+ return CUBEB_OK;
+}
+
+static int
+alsa_stream_start(cubeb_stream * stm)
+{
+ cubeb * ctx;
+
+ assert(stm);
+ ctx = stm->context;
+
+ if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && stm->other_stream) {
+ int r = alsa_stream_start(stm->other_stream);
+ if (r != CUBEB_OK)
+ return r;
+ }
+
+ pthread_mutex_lock(&stm->mutex);
+ /* Capture pcm must be started after initial setup/recover */
+ if (stm->stream_type == SND_PCM_STREAM_CAPTURE &&
+ WRAP(snd_pcm_state)(stm->pcm) == SND_PCM_STATE_PREPARED) {
+ WRAP(snd_pcm_start)(stm->pcm);
+ }
+ WRAP(snd_pcm_pause)(stm->pcm, 0);
+ gettimeofday(&stm->last_activity, NULL);
+ pthread_mutex_unlock(&stm->mutex);
+
+ pthread_mutex_lock(&ctx->mutex);
+ if (stm->state != INACTIVE) {
+ pthread_mutex_unlock(&ctx->mutex);
+ return CUBEB_ERROR;
+ }
+ alsa_set_stream_state(stm, RUNNING);
+ pthread_mutex_unlock(&ctx->mutex);
+
+ return CUBEB_OK;
+}
+
+static int
+alsa_stream_stop(cubeb_stream * stm)
+{
+ cubeb * ctx;
+ int r;
+
+ assert(stm);
+ ctx = stm->context;
+
+ if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && stm->other_stream) {
+ int r = alsa_stream_stop(stm->other_stream);
+ if (r != CUBEB_OK)
+ return r;
+ }
+
+ pthread_mutex_lock(&ctx->mutex);
+ while (stm->state == PROCESSING) {
+ r = pthread_cond_wait(&stm->cond, &ctx->mutex);
+ assert(r == 0);
+ }
+
+ alsa_set_stream_state(stm, INACTIVE);
+ pthread_mutex_unlock(&ctx->mutex);
+
+ pthread_mutex_lock(&stm->mutex);
+ WRAP(snd_pcm_pause)(stm->pcm, 1);
+ pthread_mutex_unlock(&stm->mutex);
+
+ return CUBEB_OK;
+}
+
+static int
+alsa_stream_get_position(cubeb_stream * stm, uint64_t * position)
+{
+ snd_pcm_sframes_t delay;
+
+ assert(stm && position);
+
+ pthread_mutex_lock(&stm->mutex);
+
+ delay = -1;
+ if (WRAP(snd_pcm_state)(stm->pcm) != SND_PCM_STATE_RUNNING ||
+ WRAP(snd_pcm_delay)(stm->pcm, &delay) != 0) {
+ *position = stm->last_position;
+ pthread_mutex_unlock(&stm->mutex);
+ return CUBEB_OK;
+ }
+
+ assert(delay >= 0);
+
+ *position = 0;
+ if (stm->stream_position >= (snd_pcm_uframes_t)delay) {
+ *position = stm->stream_position - delay;
+ }
+
+ stm->last_position = *position;
+
+ pthread_mutex_unlock(&stm->mutex);
+ return CUBEB_OK;
+}
+
+static int
+alsa_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
+{
+ snd_pcm_sframes_t delay;
+ /* This function returns the delay in frames until a frame written using
+ snd_pcm_writei is sent to the DAC. The DAC delay should be < 1ms anyways.
+ */
+ if (WRAP(snd_pcm_delay)(stm->pcm, &delay)) {
+ return CUBEB_ERROR;
+ }
+
+ *latency = delay;
+
+ return CUBEB_OK;
+}
+
+static int
+alsa_stream_set_volume(cubeb_stream * stm, float volume)
+{
+ /* setting the volume using an API call does not seem very stable/supported */
+ pthread_mutex_lock(&stm->mutex);
+ stm->volume = volume;
+ pthread_mutex_unlock(&stm->mutex);
+
+ return CUBEB_OK;
+}
+
+static int
+alsa_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection)
+{
+ cubeb_device_info * device = NULL;
+
+ if (!context)
+ return CUBEB_ERROR;
+
+ uint32_t rate, max_channels;
+ int r;
+
+ r = alsa_get_preferred_sample_rate(context, &rate);
+ if (r != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+
+ r = alsa_get_max_channel_count(context, &max_channels);
+ if (r != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+
+ char const * a_name = "default";
+ device = (cubeb_device_info *)calloc(1, sizeof(cubeb_device_info));
+ assert(device);
+ if (!device)
+ return CUBEB_ERROR;
+
+ device->device_id = a_name;
+ device->devid = (cubeb_devid)device->device_id;
+ device->friendly_name = a_name;
+ device->group_id = a_name;
+ device->vendor_name = a_name;
+ device->type = type;
+ device->state = CUBEB_DEVICE_STATE_ENABLED;
+ device->preferred = CUBEB_DEVICE_PREF_ALL;
+ device->format = CUBEB_DEVICE_FMT_S16NE;
+ device->default_format = CUBEB_DEVICE_FMT_S16NE;
+ device->max_channels = max_channels;
+ device->min_rate = rate;
+ device->max_rate = rate;
+ device->default_rate = rate;
+ device->latency_lo = 0;
+ device->latency_hi = 0;
+
+ collection->device = device;
+ collection->count = 1;
+
+ return CUBEB_OK;
+}
+
+static int
+alsa_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection)
+{
+ assert(collection->count == 1);
+ (void)context;
+ free(collection->device);
+ return CUBEB_OK;
+}
+
+static struct cubeb_ops const alsa_ops = {
+ .init = alsa_init,
+ .get_backend_id = alsa_get_backend_id,
+ .get_max_channel_count = alsa_get_max_channel_count,
+ .get_min_latency = alsa_get_min_latency,
+ .get_preferred_sample_rate = alsa_get_preferred_sample_rate,
+ .get_supported_input_processing_params = NULL,
+ .enumerate_devices = alsa_enumerate_devices,
+ .device_collection_destroy = alsa_device_collection_destroy,
+ .destroy = alsa_destroy,
+ .stream_init = alsa_stream_init,
+ .stream_destroy = alsa_stream_destroy,
+ .stream_start = alsa_stream_start,
+ .stream_stop = alsa_stream_stop,
+ .stream_get_position = alsa_stream_get_position,
+ .stream_get_latency = alsa_stream_get_latency,
+ .stream_get_input_latency = NULL,
+ .stream_set_volume = alsa_stream_set_volume,
+ .stream_set_name = NULL,
+ .stream_get_current_device = NULL,
+ .stream_set_input_mute = NULL,
+ .stream_set_input_processing_params = NULL,
+ .stream_device_destroy = NULL,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL};
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_android.h b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_android.h
new file mode 100644
index 0000000000..c21a941ab5
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_android.h
@@ -0,0 +1,17 @@
+#ifndef CUBEB_ANDROID_H
+#define CUBEB_ANDROID_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+// If the latency requested is above this threshold, this stream is considered
+// intended for playback (vs. real-time). Tell Android it should favor saving
+// power over performance or latency.
+// This is around 100ms at 44100 or 48000
+const uint16_t POWERSAVE_LATENCY_FRAMES_THRESHOLD = 4000;
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif // CUBEB_ANDROID_H
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_array_queue.h b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_array_queue.h
new file mode 100644
index 0000000000..d6d9581325
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_array_queue.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_ARRAY_QUEUE_H
+#define CUBEB_ARRAY_QUEUE_H
+
+#include <assert.h>
+#include <pthread.h>
+#include <unistd.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct {
+ void ** buf;
+ size_t num;
+ size_t writePos;
+ size_t readPos;
+ pthread_mutex_t mutex;
+} array_queue;
+
+array_queue *
+array_queue_create(size_t num)
+{
+ assert(num != 0);
+ array_queue * new_queue = (array_queue *)calloc(1, sizeof(array_queue));
+ new_queue->buf = (void **)calloc(1, sizeof(void *) * num);
+ new_queue->readPos = 0;
+ new_queue->writePos = 0;
+ new_queue->num = num;
+
+ pthread_mutex_init(&new_queue->mutex, NULL);
+
+ return new_queue;
+}
+
+void
+array_queue_destroy(array_queue * aq)
+{
+ assert(aq);
+
+ free(aq->buf);
+ pthread_mutex_destroy(&aq->mutex);
+ free(aq);
+}
+
+int
+array_queue_push(array_queue * aq, void * item)
+{
+ assert(item);
+
+ pthread_mutex_lock(&aq->mutex);
+ int ret = -1;
+ if (aq->buf[aq->writePos % aq->num] == NULL) {
+ aq->buf[aq->writePos % aq->num] = item;
+ aq->writePos = (aq->writePos + 1) % aq->num;
+ ret = 0;
+ }
+ // else queue is full
+ pthread_mutex_unlock(&aq->mutex);
+ return ret;
+}
+
+void *
+array_queue_pop(array_queue * aq)
+{
+ pthread_mutex_lock(&aq->mutex);
+ void * value = aq->buf[aq->readPos % aq->num];
+ if (value) {
+ aq->buf[aq->readPos % aq->num] = NULL;
+ aq->readPos = (aq->readPos + 1) % aq->num;
+ }
+ pthread_mutex_unlock(&aq->mutex);
+ return value;
+}
+
+size_t
+array_queue_get_size(array_queue * aq)
+{
+ pthread_mutex_lock(&aq->mutex);
+ ssize_t r = aq->writePos - aq->readPos;
+ if (r < 0) {
+ r = aq->num + r;
+ assert(r >= 0);
+ }
+ pthread_mutex_unlock(&aq->mutex);
+ return (size_t)r;
+}
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif // CUBE_ARRAY_QUEUE_H
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_assert.h b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_assert.h
new file mode 100644
index 0000000000..d81a1eacaf
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_assert.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright © 2017 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_ASSERT
+#define CUBEB_ASSERT
+
+#include <stdio.h>
+#include <stdlib.h>
+
+/**
+ * This allow using an external release assert method. This file should only
+ * export a function or macro called XASSERT that aborts the program.
+ */
+
+#define XASSERT(expr) \
+ do { \
+ if (!(expr)) { \
+ fprintf(stderr, "%s:%d - fatal error: %s\n", __FILE__, __LINE__, #expr); \
+ abort(); \
+ } \
+ } while (0)
+
+#endif
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_audiotrack.c b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_audiotrack.c
new file mode 100644
index 0000000000..84ccfb80a5
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_audiotrack.c
@@ -0,0 +1,475 @@
+/*
+ * Copyright © 2013 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#if !defined(NDEBUG)
+#define NDEBUG
+#endif
+#include <android/log.h>
+#include <assert.h>
+#include <dlfcn.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "android/audiotrack_definitions.h"
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+
+#ifndef ALOG
+#if defined(DEBUG) || defined(FORCE_ALOG)
+#define ALOG(args...) \
+ __android_log_print(ANDROID_LOG_INFO, "Gecko - Cubeb", ##args)
+#else
+#define ALOG(args...)
+#endif
+#endif
+
+/**
+ * A lot of bytes for safety. It should be possible to bring this down a bit. */
+#define SIZE_AUDIOTRACK_INSTANCE 256
+
+/**
+ * call dlsym to get the symbol |mangled_name|, handle the error and store the
+ * pointer in |pointer|. Because depending on Android version, we want different
+ * symbols, not finding a symbol is not an error. */
+#define DLSYM_DLERROR(mangled_name, pointer, lib) \
+ do { \
+ pointer = dlsym(lib, mangled_name); \
+ if (!pointer) { \
+ ALOG("error while loading %stm: %stm\n", mangled_name, dlerror()); \
+ } else { \
+ ALOG("%stm: OK", mangled_name); \
+ } \
+ } while (0);
+
+static struct cubeb_ops const audiotrack_ops;
+void
+audiotrack_destroy(cubeb * context);
+void
+audiotrack_stream_destroy(cubeb_stream * stream);
+
+struct AudioTrack {
+ /* only available on ICS and later. The second int paramter is in fact of type
+ * audio_stream_type_t. */
+ /* static */ status_t (*get_min_frame_count)(int * frame_count,
+ int stream_type, uint32_t rate);
+ /* if we have a recent ctor, but can't find the above symbol, we
+ * can get the minimum frame count with this signature, and we are
+ * running gingerbread. */
+ /* static */ status_t (*get_min_frame_count_gingerbread)(int * frame_count,
+ int stream_type,
+ uint32_t rate);
+ void * (*ctor)(void * instance, int, unsigned int, int, int, int,
+ unsigned int, void (*)(int, void *, void *), void *, int, int);
+ void * (*dtor)(void * instance);
+ void (*start)(void * instance);
+ void (*pause)(void * instance);
+ uint32_t (*latency)(void * instance);
+ status_t (*check)(void * instance);
+ status_t (*get_position)(void * instance, uint32_t * position);
+ /* static */ int (*get_output_samplingrate)(int * samplerate, int stream);
+ status_t (*set_marker_position)(void * instance, unsigned int);
+ status_t (*set_volume)(void * instance, float left, float right);
+};
+
+struct cubeb {
+ struct cubeb_ops const * ops;
+ void * library;
+ struct AudioTrack klass;
+};
+
+struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
+ cubeb * context;
+ void * user_ptr;
+ /**/
+ cubeb_stream_params params;
+ cubeb_data_callback data_callback;
+ cubeb_state_callback state_callback;
+ void * instance;
+ /* Number of frames that have been passed to the AudioTrack callback */
+ long unsigned written;
+ int draining;
+};
+
+static void
+audiotrack_refill(int event, void * user, void * info)
+{
+ cubeb_stream * stream = user;
+ switch (event) {
+ case EVENT_MORE_DATA: {
+ long got = 0;
+ struct Buffer * b = (struct Buffer *)info;
+
+ if (stream->draining) {
+ return;
+ }
+
+ got = stream->data_callback(stream, stream->user_ptr, NULL, b->raw,
+ b->frameCount);
+
+ stream->written += got;
+
+ if (got != (long)b->frameCount) {
+ stream->draining = 1;
+ /* set a marker so we are notified when the are done draining, that is,
+ * when every frame has been played by android. */
+ stream->context->klass.set_marker_position(stream->instance,
+ stream->written);
+ }
+
+ break;
+ }
+ case EVENT_UNDERRUN:
+ ALOG("underrun in cubeb backend.");
+ break;
+ case EVENT_LOOP_END:
+ assert(0 && "We don't support the loop feature of audiotrack.");
+ break;
+ case EVENT_MARKER:
+ assert(stream->draining);
+ stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED);
+ break;
+ case EVENT_NEW_POS:
+ assert(
+ 0 &&
+ "We don't support the setPositionUpdatePeriod feature of audiotrack.");
+ break;
+ case EVENT_BUFFER_END:
+ assert(0 && "Should not happen.");
+ break;
+ }
+}
+
+/* We are running on gingerbread if we found the gingerbread signature for
+ * getMinFrameCount */
+static int
+audiotrack_version_is_gingerbread(cubeb * ctx)
+{
+ return ctx->klass.get_min_frame_count_gingerbread != NULL;
+}
+
+int
+audiotrack_get_min_frame_count(cubeb * ctx, cubeb_stream_params * params,
+ int * min_frame_count)
+{
+ status_t status;
+ /* Recent Android have a getMinFrameCount method. */
+ if (!audiotrack_version_is_gingerbread(ctx)) {
+ status = ctx->klass.get_min_frame_count(
+ min_frame_count, AUDIO_STREAM_TYPE_MUSIC, params->rate);
+ } else {
+ status = ctx->klass.get_min_frame_count_gingerbread(
+ min_frame_count, AUDIO_STREAM_TYPE_MUSIC, params->rate);
+ }
+ if (status != 0) {
+ ALOG("error getting the min frame count");
+ return CUBEB_ERROR;
+ }
+ return CUBEB_OK;
+}
+
+int
+audiotrack_init(cubeb ** context, char const * context_name)
+{
+ cubeb * ctx;
+ struct AudioTrack * c;
+
+ assert(context);
+ *context = NULL;
+
+ ctx = calloc(1, sizeof(*ctx));
+ assert(ctx);
+
+ /* If we use an absolute path here ("/system/lib/libmedia.so"), and on Android
+ * 2.2, the dlopen succeeds, all the dlsym succeed, but a segfault happens on
+ * the first call to a dlsym'ed function. Somehow this does not happen when
+ * using only the name of the library. */
+ ctx->library = dlopen("libmedia.so", RTLD_LAZY);
+ if (!ctx->library) {
+ ALOG("dlopen error: %s.", dlerror());
+ free(ctx);
+ return CUBEB_ERROR;
+ }
+
+ /* Recent Android first, then Gingerbread. */
+ DLSYM_DLERROR("_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_ii",
+ ctx->klass.ctor, ctx->library);
+ DLSYM_DLERROR("_ZN7android10AudioTrackD1Ev", ctx->klass.dtor, ctx->library);
+
+ DLSYM_DLERROR("_ZNK7android10AudioTrack7latencyEv", ctx->klass.latency,
+ ctx->library);
+ DLSYM_DLERROR("_ZNK7android10AudioTrack9initCheckEv", ctx->klass.check,
+ ctx->library);
+
+ DLSYM_DLERROR("_ZN7android11AudioSystem21getOutputSamplingRateEPii",
+ ctx->klass.get_output_samplingrate, ctx->library);
+
+ /* |getMinFrameCount| is available on gingerbread and ICS with different
+ * signatures. */
+ DLSYM_DLERROR(
+ "_ZN7android10AudioTrack16getMinFrameCountEPi19audio_stream_type_tj",
+ ctx->klass.get_min_frame_count, ctx->library);
+ if (!ctx->klass.get_min_frame_count) {
+ DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPiij",
+ ctx->klass.get_min_frame_count_gingerbread, ctx->library);
+ }
+
+ DLSYM_DLERROR("_ZN7android10AudioTrack5startEv", ctx->klass.start,
+ ctx->library);
+ DLSYM_DLERROR("_ZN7android10AudioTrack5pauseEv", ctx->klass.pause,
+ ctx->library);
+ DLSYM_DLERROR("_ZN7android10AudioTrack11getPositionEPj",
+ ctx->klass.get_position, ctx->library);
+ DLSYM_DLERROR("_ZN7android10AudioTrack17setMarkerPositionEj",
+ ctx->klass.set_marker_position, ctx->library);
+ DLSYM_DLERROR("_ZN7android10AudioTrack9setVolumeEff", ctx->klass.set_volume,
+ ctx->library);
+
+ /* check that we have a combination of symbol that makes sense */
+ c = &ctx->klass;
+ if (!(c->ctor && c->dtor && c->latency && c->check &&
+ /* at least one way to get the minimum frame count to request. */
+ (c->get_min_frame_count || c->get_min_frame_count_gingerbread) &&
+ c->start && c->pause && c->get_position && c->set_marker_position)) {
+ ALOG("Could not find all the symbols we need.");
+ audiotrack_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ ctx->ops = &audiotrack_ops;
+
+ *context = ctx;
+
+ return CUBEB_OK;
+}
+
+char const *
+audiotrack_get_backend_id(cubeb * context)
+{
+ return "audiotrack";
+}
+
+static int
+audiotrack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
+{
+ assert(ctx && max_channels);
+
+ /* The android mixer handles up to two channels, see
+ http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67
+ */
+ *max_channels = 2;
+
+ return CUBEB_OK;
+}
+
+static int
+audiotrack_get_min_latency(cubeb * ctx, cubeb_stream_params params,
+ uint32_t * latency_ms)
+{
+ /* We always use the lowest latency possible when using this backend (see
+ * audiotrack_stream_init), so this value is not going to be used. */
+ int r;
+
+ r = audiotrack_get_min_frame_count(ctx, &params, (int *)latency_ms);
+ if (r != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+audiotrack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
+{
+ status_t r;
+
+ r = ctx->klass.get_output_samplingrate((int32_t *)rate, 3 /* MUSIC */);
+
+ return r == 0 ? CUBEB_OK : CUBEB_ERROR;
+}
+
+void
+audiotrack_destroy(cubeb * context)
+{
+ assert(context);
+
+ dlclose(context->library);
+
+ free(context);
+}
+
+int
+audiotrack_stream_init(cubeb * ctx, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency, cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ cubeb_stream * stm;
+ int32_t channels;
+ uint32_t min_frame_count;
+
+ assert(ctx && stream);
+
+ assert(!input_stream_params && "not supported");
+ if (input_device || output_device) {
+ /* Device selection not yet implemented. */
+ return CUBEB_ERROR_DEVICE_UNAVAILABLE;
+ }
+
+ if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE ||
+ output_stream_params->format == CUBEB_SAMPLE_FLOAT32BE) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ if (audiotrack_get_min_frame_count(ctx, output_stream_params,
+ (int *)&min_frame_count)) {
+ return CUBEB_ERROR;
+ }
+
+ stm = calloc(1, sizeof(*stm));
+ assert(stm);
+
+ stm->context = ctx;
+ stm->data_callback = data_callback;
+ stm->state_callback = state_callback;
+ stm->user_ptr = user_ptr;
+ stm->params = *output_stream_params;
+
+ stm->instance = calloc(SIZE_AUDIOTRACK_INSTANCE, 1);
+ (*(uint32_t *)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) =
+ 0xbaadbaad;
+ assert(stm->instance && "cubeb: EOM");
+
+ /* gingerbread uses old channel layout enum */
+ if (audiotrack_version_is_gingerbread(ctx)) {
+ channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_Legacy
+ : AUDIO_CHANNEL_OUT_MONO_Legacy;
+ } else {
+ channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_ICS
+ : AUDIO_CHANNEL_OUT_MONO_ICS;
+ }
+
+ ctx->klass.ctor(stm->instance, AUDIO_STREAM_TYPE_MUSIC, stm->params.rate,
+ AUDIO_FORMAT_PCM_16_BIT, channels, min_frame_count, 0,
+ audiotrack_refill, stm, 0, 0);
+
+ assert((*(uint32_t *)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE -
+ 4)) == 0xbaadbaad);
+
+ if (ctx->klass.check(stm->instance)) {
+ ALOG("stream not initialized properly.");
+ audiotrack_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ *stream = stm;
+
+ return CUBEB_OK;
+}
+
+void
+audiotrack_stream_destroy(cubeb_stream * stream)
+{
+ assert(stream->context);
+
+ stream->context->klass.dtor(stream->instance);
+
+ free(stream->instance);
+ stream->instance = NULL;
+ free(stream);
+}
+
+int
+audiotrack_stream_start(cubeb_stream * stream)
+{
+ assert(stream->instance);
+
+ stream->context->klass.start(stream->instance);
+ stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STARTED);
+
+ return CUBEB_OK;
+}
+
+int
+audiotrack_stream_stop(cubeb_stream * stream)
+{
+ assert(stream->instance);
+
+ stream->context->klass.pause(stream->instance);
+ stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STOPPED);
+
+ return CUBEB_OK;
+}
+
+int
+audiotrack_stream_get_position(cubeb_stream * stream, uint64_t * position)
+{
+ uint32_t p;
+
+ assert(stream->instance && position);
+ stream->context->klass.get_position(stream->instance, &p);
+ *position = p;
+
+ return CUBEB_OK;
+}
+
+int
+audiotrack_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
+{
+ assert(stream->instance && latency);
+
+ /* Android returns the latency in ms, we want it in frames. */
+ *latency = stream->context->klass.latency(stream->instance);
+ /* with rate <= 96000, we won't overflow until 44.739 seconds of latency */
+ *latency = (*latency * stream->params.rate) / 1000;
+
+ return 0;
+}
+
+int
+audiotrack_stream_set_volume(cubeb_stream * stream, float volume)
+{
+ status_t status;
+
+ status = stream->context->klass.set_volume(stream->instance, volume, volume);
+
+ if (status) {
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static struct cubeb_ops const audiotrack_ops = {
+ .init = audiotrack_init,
+ .get_backend_id = audiotrack_get_backend_id,
+ .get_max_channel_count = audiotrack_get_max_channel_count,
+ .get_min_latency = audiotrack_get_min_latency,
+ .get_preferred_sample_rate = audiotrack_get_preferred_sample_rate,
+ .get_supported_input_processing_params = NULL,
+ .enumerate_devices = NULL,
+ .device_collection_destroy = NULL,
+ .destroy = audiotrack_destroy,
+ .stream_init = audiotrack_stream_init,
+ .stream_destroy = audiotrack_stream_destroy,
+ .stream_start = audiotrack_stream_start,
+ .stream_stop = audiotrack_stream_stop,
+ .stream_get_position = audiotrack_stream_get_position,
+ .stream_get_latency = audiotrack_stream_get_latency,
+ .stream_get_input_latency = NULL,
+ .stream_set_volume = audiotrack_stream_set_volume,
+ .stream_set_name = NULL,
+ .stream_get_current_device = NULL,
+ .stream_set_input_mute = NULL,
+ .stream_set_input_processing_params = NULL,
+ .stream_device_destroy = NULL,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL};
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_audiounit.cpp b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_audiounit.cpp
new file mode 100644
index 0000000000..0341c8d332
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_audiounit.cpp
@@ -0,0 +1,3688 @@
+/*
+ * Copyright © 2011 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#undef NDEBUG
+
+#include <AudioUnit/AudioUnit.h>
+#include <TargetConditionals.h>
+#include <assert.h>
+#include <mach/mach_time.h>
+#include <pthread.h>
+#include <stdlib.h>
+#if !TARGET_OS_IPHONE
+#include <AvailabilityMacros.h>
+#include <CoreAudio/AudioHardware.h>
+#include <CoreAudio/HostTime.h>
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_mixer.h"
+#include <AudioToolbox/AudioToolbox.h>
+#include <CoreAudio/CoreAudioTypes.h>
+#if !TARGET_OS_IPHONE
+#include "cubeb_osx_run_loop.h"
+#endif
+#include "cubeb_resampler.h"
+#include "cubeb_ring_array.h"
+#include <algorithm>
+#include <atomic>
+#include <set>
+#include <string>
+#include <sys/time.h>
+#include <vector>
+
+using namespace std;
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED < 101000
+typedef UInt32 AudioFormatFlags;
+#endif
+
+#define AU_OUT_BUS 0
+#define AU_IN_BUS 1
+
+const char * DISPATCH_QUEUE_LABEL = "org.mozilla.cubeb";
+const char * PRIVATE_AGGREGATE_DEVICE_NAME = "CubebAggregateDevice";
+
+#ifdef ALOGV
+#undef ALOGV
+#endif
+#define ALOGV(msg, ...) \
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), \
+ ^{ \
+ LOGV(msg, ##__VA_ARGS__); \
+ })
+
+#ifdef ALOG
+#undef ALOG
+#endif
+#define ALOG(msg, ...) \
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), \
+ ^{ \
+ LOG(msg, ##__VA_ARGS__); \
+ })
+
+/* Testing empirically, some headsets report a minimal latency that is very
+ * low, but this does not work in practice. Lie and say the minimum is 256
+ * frames. */
+const uint32_t SAFE_MIN_LATENCY_FRAMES = 128;
+const uint32_t SAFE_MAX_LATENCY_FRAMES = 512;
+
+const AudioObjectPropertyAddress DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS = {
+ kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+const AudioObjectPropertyAddress DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS = {
+ kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+const AudioObjectPropertyAddress DEVICE_IS_ALIVE_PROPERTY_ADDRESS = {
+ kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+const AudioObjectPropertyAddress DEVICES_PROPERTY_ADDRESS = {
+ kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+const AudioObjectPropertyAddress INPUT_DATA_SOURCE_PROPERTY_ADDRESS = {
+ kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeInput,
+ kAudioObjectPropertyElementMaster};
+
+const AudioObjectPropertyAddress OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS = {
+ kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster};
+
+typedef uint32_t device_flags_value;
+
+enum device_flags {
+ DEV_UNKNOWN = 0x00, /* Unknown */
+ DEV_INPUT = 0x01, /* Record device like mic */
+ DEV_OUTPUT = 0x02, /* Playback device like speakers */
+ DEV_SYSTEM_DEFAULT = 0x04, /* System default device */
+ DEV_SELECTED_DEFAULT =
+ 0x08, /* User selected to use the system default device */
+};
+
+void
+audiounit_stream_stop_internal(cubeb_stream * stm);
+static int
+audiounit_stream_start_internal(cubeb_stream * stm);
+static void
+audiounit_close_stream(cubeb_stream * stm);
+static int
+audiounit_setup_stream(cubeb_stream * stm);
+static vector<AudioObjectID>
+audiounit_get_devices_of_type(cubeb_device_type devtype);
+static UInt32
+audiounit_get_device_presentation_latency(AudioObjectID devid,
+ AudioObjectPropertyScope scope);
+
+#if !TARGET_OS_IPHONE
+static AudioObjectID
+audiounit_get_default_device_id(cubeb_device_type type);
+static int
+audiounit_uninstall_device_changed_callback(cubeb_stream * stm);
+static int
+audiounit_uninstall_system_changed_callback(cubeb_stream * stm);
+static void
+audiounit_reinit_stream_async(cubeb_stream * stm, device_flags_value flags);
+#endif
+
+extern cubeb_ops const audiounit_ops;
+
+struct cubeb {
+ cubeb_ops const * ops = &audiounit_ops;
+ owned_critical_section mutex;
+ int active_streams = 0;
+ uint32_t global_latency_frames = 0;
+ cubeb_device_collection_changed_callback input_collection_changed_callback =
+ nullptr;
+ void * input_collection_changed_user_ptr = nullptr;
+ cubeb_device_collection_changed_callback output_collection_changed_callback =
+ nullptr;
+ void * output_collection_changed_user_ptr = nullptr;
+ // Store list of devices to detect changes
+ vector<AudioObjectID> input_device_array;
+ vector<AudioObjectID> output_device_array;
+ // The queue should be released when it’s no longer needed.
+ dispatch_queue_t serial_queue =
+ dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL);
+ // Current used channel layout
+ atomic<cubeb_channel_layout> layout{CUBEB_LAYOUT_UNDEFINED};
+ uint32_t channels = 0;
+};
+
+static unique_ptr<AudioChannelLayout, decltype(&free)>
+make_sized_audio_channel_layout(size_t sz)
+{
+ assert(sz >= sizeof(AudioChannelLayout));
+ AudioChannelLayout * acl =
+ reinterpret_cast<AudioChannelLayout *>(calloc(1, sz));
+ assert(acl); // Assert the allocation works.
+ return unique_ptr<AudioChannelLayout, decltype(&free)>(acl, free);
+}
+
+enum class io_side {
+ INPUT,
+ OUTPUT,
+};
+
+static char const *
+to_string(io_side side)
+{
+ switch (side) {
+ case io_side::INPUT:
+ return "input";
+ case io_side::OUTPUT:
+ return "output";
+ }
+}
+
+struct device_info {
+ AudioDeviceID id = kAudioObjectUnknown;
+ device_flags_value flags = DEV_UNKNOWN;
+};
+
+struct property_listener {
+ AudioDeviceID device_id;
+ const AudioObjectPropertyAddress * property_address;
+ AudioObjectPropertyListenerProc callback;
+ cubeb_stream * stream;
+
+ property_listener(AudioDeviceID id,
+ const AudioObjectPropertyAddress * address,
+ AudioObjectPropertyListenerProc proc, cubeb_stream * stm)
+ : device_id(id), property_address(address), callback(proc), stream(stm)
+ {
+ }
+};
+
+struct cubeb_stream {
+ explicit cubeb_stream(cubeb * context);
+
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
+ cubeb * context;
+ void * user_ptr = nullptr;
+ /**/
+
+ cubeb_data_callback data_callback = nullptr;
+ cubeb_state_callback state_callback = nullptr;
+ cubeb_device_changed_callback device_changed_callback = nullptr;
+ owned_critical_section device_changed_callback_lock;
+ /* Stream creation parameters */
+ cubeb_stream_params input_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0,
+ CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE};
+ cubeb_stream_params output_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0,
+ CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE};
+ device_info input_device;
+ device_info output_device;
+ /* Format descriptions */
+ AudioStreamBasicDescription input_desc;
+ AudioStreamBasicDescription output_desc;
+ /* I/O AudioUnits */
+ AudioUnit input_unit = nullptr;
+ AudioUnit output_unit = nullptr;
+ /* I/O device sample rate */
+ Float64 input_hw_rate = 0;
+ Float64 output_hw_rate = 0;
+ /* Expected I/O thread interleave,
+ * calculated from I/O hw rate. */
+ int expected_output_callbacks_in_a_row = 0;
+ owned_critical_section mutex;
+ // Hold the input samples in every input callback iteration.
+ // Only accessed on input/output callback thread and during initial configure.
+ unique_ptr<auto_array_wrapper> input_linear_buffer;
+ /* Frame counters */
+ atomic<uint64_t> frames_played{0};
+ uint64_t frames_queued = 0;
+ // How many frames got read from the input since the stream started (includes
+ // padded silence)
+ atomic<int64_t> frames_read{0};
+ // How many frames got written to the output device since the stream started
+ atomic<int64_t> frames_written{0};
+ atomic<bool> shutdown{true};
+ atomic<bool> draining{false};
+ atomic<bool> reinit_pending{false};
+ atomic<bool> destroy_pending{false};
+ /* Latency requested by the user. */
+ uint32_t latency_frames = 0;
+ atomic<uint32_t> current_latency_frames{0};
+ atomic<uint32_t> total_output_latency_frames{0};
+ unique_ptr<cubeb_resampler, decltype(&cubeb_resampler_destroy)> resampler;
+ /* This is true if a device change callback is currently running. */
+ atomic<bool> switching_device{false};
+ atomic<bool> buffer_size_change_state{false};
+ AudioDeviceID aggregate_device_id =
+ kAudioObjectUnknown; // the aggregate device id
+ AudioObjectID plugin_id =
+ kAudioObjectUnknown; // used to create aggregate device
+ /* Mixer interface */
+ unique_ptr<cubeb_mixer, decltype(&cubeb_mixer_destroy)> mixer;
+ /* Buffer where remixing/resampling will occur when upmixing is required */
+ /* Only accessed from callback thread */
+ unique_ptr<uint8_t[]> temp_buffer;
+ size_t temp_buffer_size = 0; // size in bytes.
+ /* Listeners indicating what system events are monitored. */
+ unique_ptr<property_listener> default_input_listener;
+ unique_ptr<property_listener> default_output_listener;
+ unique_ptr<property_listener> input_alive_listener;
+ unique_ptr<property_listener> input_source_listener;
+ unique_ptr<property_listener> output_source_listener;
+};
+
+bool
+has_input(cubeb_stream * stm)
+{
+ return stm->input_stream_params.rate != 0;
+}
+
+bool
+has_output(cubeb_stream * stm)
+{
+ return stm->output_stream_params.rate != 0;
+}
+
+cubeb_channel
+channel_label_to_cubeb_channel(UInt32 label)
+{
+ switch (label) {
+ case kAudioChannelLabel_Left:
+ return CHANNEL_FRONT_LEFT;
+ case kAudioChannelLabel_Right:
+ return CHANNEL_FRONT_RIGHT;
+ case kAudioChannelLabel_Center:
+ return CHANNEL_FRONT_CENTER;
+ case kAudioChannelLabel_LFEScreen:
+ return CHANNEL_LOW_FREQUENCY;
+ case kAudioChannelLabel_LeftSurround:
+ return CHANNEL_BACK_LEFT;
+ case kAudioChannelLabel_RightSurround:
+ return CHANNEL_BACK_RIGHT;
+ case kAudioChannelLabel_LeftCenter:
+ return CHANNEL_FRONT_LEFT_OF_CENTER;
+ case kAudioChannelLabel_RightCenter:
+ return CHANNEL_FRONT_RIGHT_OF_CENTER;
+ case kAudioChannelLabel_CenterSurround:
+ return CHANNEL_BACK_CENTER;
+ case kAudioChannelLabel_LeftSurroundDirect:
+ return CHANNEL_SIDE_LEFT;
+ case kAudioChannelLabel_RightSurroundDirect:
+ return CHANNEL_SIDE_RIGHT;
+ case kAudioChannelLabel_TopCenterSurround:
+ return CHANNEL_TOP_CENTER;
+ case kAudioChannelLabel_VerticalHeightLeft:
+ return CHANNEL_TOP_FRONT_LEFT;
+ case kAudioChannelLabel_VerticalHeightCenter:
+ return CHANNEL_TOP_FRONT_CENTER;
+ case kAudioChannelLabel_VerticalHeightRight:
+ return CHANNEL_TOP_FRONT_RIGHT;
+ case kAudioChannelLabel_TopBackLeft:
+ return CHANNEL_TOP_BACK_LEFT;
+ case kAudioChannelLabel_TopBackCenter:
+ return CHANNEL_TOP_BACK_CENTER;
+ case kAudioChannelLabel_TopBackRight:
+ return CHANNEL_TOP_BACK_RIGHT;
+ default:
+ return CHANNEL_UNKNOWN;
+ }
+}
+
+AudioChannelLabel
+cubeb_channel_to_channel_label(cubeb_channel channel)
+{
+ switch (channel) {
+ case CHANNEL_FRONT_LEFT:
+ return kAudioChannelLabel_Left;
+ case CHANNEL_FRONT_RIGHT:
+ return kAudioChannelLabel_Right;
+ case CHANNEL_FRONT_CENTER:
+ return kAudioChannelLabel_Center;
+ case CHANNEL_LOW_FREQUENCY:
+ return kAudioChannelLabel_LFEScreen;
+ case CHANNEL_BACK_LEFT:
+ return kAudioChannelLabel_LeftSurround;
+ case CHANNEL_BACK_RIGHT:
+ return kAudioChannelLabel_RightSurround;
+ case CHANNEL_FRONT_LEFT_OF_CENTER:
+ return kAudioChannelLabel_LeftCenter;
+ case CHANNEL_FRONT_RIGHT_OF_CENTER:
+ return kAudioChannelLabel_RightCenter;
+ case CHANNEL_BACK_CENTER:
+ return kAudioChannelLabel_CenterSurround;
+ case CHANNEL_SIDE_LEFT:
+ return kAudioChannelLabel_LeftSurroundDirect;
+ case CHANNEL_SIDE_RIGHT:
+ return kAudioChannelLabel_RightSurroundDirect;
+ case CHANNEL_TOP_CENTER:
+ return kAudioChannelLabel_TopCenterSurround;
+ case CHANNEL_TOP_FRONT_LEFT:
+ return kAudioChannelLabel_VerticalHeightLeft;
+ case CHANNEL_TOP_FRONT_CENTER:
+ return kAudioChannelLabel_VerticalHeightCenter;
+ case CHANNEL_TOP_FRONT_RIGHT:
+ return kAudioChannelLabel_VerticalHeightRight;
+ case CHANNEL_TOP_BACK_LEFT:
+ return kAudioChannelLabel_TopBackLeft;
+ case CHANNEL_TOP_BACK_CENTER:
+ return kAudioChannelLabel_TopBackCenter;
+ case CHANNEL_TOP_BACK_RIGHT:
+ return kAudioChannelLabel_TopBackRight;
+ default:
+ return kAudioChannelLabel_Unknown;
+ }
+}
+
+#if TARGET_OS_IPHONE
+typedef UInt32 AudioDeviceID;
+typedef UInt32 AudioObjectID;
+
+#define AudioGetCurrentHostTime mach_absolute_time
+
+#endif
+
+uint64_t
+ConvertHostTimeToNanos(uint64_t host_time)
+{
+ static struct mach_timebase_info timebase_info;
+ static bool initialized = false;
+ if (!initialized) {
+ mach_timebase_info(&timebase_info);
+ initialized = true;
+ }
+
+ long double answer = host_time;
+ if (timebase_info.numer != timebase_info.denom) {
+ answer *= timebase_info.numer;
+ answer /= timebase_info.denom;
+ }
+ return (uint64_t)answer;
+}
+
+static void
+audiounit_increment_active_streams(cubeb * ctx)
+{
+ ctx->mutex.assert_current_thread_owns();
+ ctx->active_streams += 1;
+}
+
+static void
+audiounit_decrement_active_streams(cubeb * ctx)
+{
+ ctx->mutex.assert_current_thread_owns();
+ ctx->active_streams -= 1;
+}
+
+static int
+audiounit_active_streams(cubeb * ctx)
+{
+ ctx->mutex.assert_current_thread_owns();
+ return ctx->active_streams;
+}
+
+static void
+audiounit_set_global_latency(cubeb * ctx, uint32_t latency_frames)
+{
+ ctx->mutex.assert_current_thread_owns();
+ assert(audiounit_active_streams(ctx) == 1);
+ ctx->global_latency_frames = latency_frames;
+}
+
+static void
+audiounit_make_silent(AudioBuffer * ioData)
+{
+ assert(ioData);
+ assert(ioData->mData);
+ memset(ioData->mData, 0, ioData->mDataByteSize);
+}
+
+static OSStatus
+audiounit_render_input(cubeb_stream * stm, AudioUnitRenderActionFlags * flags,
+ AudioTimeStamp const * tstamp, UInt32 bus,
+ UInt32 input_frames)
+{
+ /* Create the AudioBufferList to store input. */
+ AudioBufferList input_buffer_list;
+ input_buffer_list.mBuffers[0].mDataByteSize =
+ stm->input_desc.mBytesPerFrame * input_frames;
+ input_buffer_list.mBuffers[0].mData = nullptr;
+ input_buffer_list.mBuffers[0].mNumberChannels =
+ stm->input_desc.mChannelsPerFrame;
+ input_buffer_list.mNumberBuffers = 1;
+
+ /* Render input samples */
+ OSStatus r = AudioUnitRender(stm->input_unit, flags, tstamp, bus,
+ input_frames, &input_buffer_list);
+
+ if (r != noErr) {
+ LOG("AudioUnitRender rv=%d", r);
+ if (r != kAudioUnitErr_CannotDoInCurrentContext) {
+ return r;
+ }
+ if (stm->output_unit) {
+ // kAudioUnitErr_CannotDoInCurrentContext is returned when using a BT
+ // headset and the profile is changed from A2DP to HFP/HSP. The previous
+ // output device is no longer valid and must be reset.
+ audiounit_reinit_stream_async(stm, DEV_INPUT | DEV_OUTPUT);
+ }
+ // For now state that no error occurred and feed silence, stream will be
+ // resumed once reinit has completed.
+ ALOGV("(%p) input: reinit pending feeding silence instead", stm);
+ stm->input_linear_buffer->push_silence(input_frames *
+ stm->input_desc.mChannelsPerFrame);
+ } else {
+ /* Copy input data in linear buffer. */
+ stm->input_linear_buffer->push(input_buffer_list.mBuffers[0].mData,
+ input_frames *
+ stm->input_desc.mChannelsPerFrame);
+ }
+
+ /* Advance input frame counter. */
+ assert(input_frames > 0);
+ stm->frames_read += input_frames;
+
+ ALOGV("(%p) input: buffers %u, size %u, channels %u, rendered frames %d, "
+ "total frames %lu.",
+ stm, (unsigned int)input_buffer_list.mNumberBuffers,
+ (unsigned int)input_buffer_list.mBuffers[0].mDataByteSize,
+ (unsigned int)input_buffer_list.mBuffers[0].mNumberChannels,
+ (unsigned int)input_frames,
+ stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame);
+
+ return noErr;
+}
+
+static OSStatus
+audiounit_input_callback(void * user_ptr, AudioUnitRenderActionFlags * flags,
+ AudioTimeStamp const * tstamp, UInt32 bus,
+ UInt32 input_frames, AudioBufferList * /* bufs */)
+{
+ cubeb_stream * stm = static_cast<cubeb_stream *>(user_ptr);
+
+ assert(stm->input_unit != NULL);
+ assert(AU_IN_BUS == bus);
+
+ if (stm->shutdown) {
+ ALOG("(%p) input shutdown", stm);
+ return noErr;
+ }
+
+ if (stm->draining) {
+ OSStatus r = AudioOutputUnitStop(stm->input_unit);
+ assert(r == 0);
+ // Only fire state callback in input-only stream. For duplex stream,
+ // the state callback will be fired in output callback.
+ if (stm->output_unit == NULL) {
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+ }
+ return noErr;
+ }
+
+ OSStatus r = audiounit_render_input(stm, flags, tstamp, bus, input_frames);
+ if (r != noErr) {
+ return r;
+ }
+
+ // Full Duplex. We'll call data_callback in the AudioUnit output callback.
+ if (stm->output_unit != NULL) {
+ return noErr;
+ }
+
+ /* Input only. Call the user callback through resampler.
+ Resampler will deliver input buffer in the correct rate. */
+ assert(input_frames <= stm->input_linear_buffer->length() /
+ stm->input_desc.mChannelsPerFrame);
+ long total_input_frames =
+ stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame;
+ long outframes = cubeb_resampler_fill(stm->resampler.get(),
+ stm->input_linear_buffer->data(),
+ &total_input_frames, NULL, 0);
+ if (outframes < 0) {
+ stm->shutdown = true;
+ OSStatus r = AudioOutputUnitStop(stm->input_unit);
+ assert(r == 0);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ return noErr;
+ }
+ stm->draining = outframes < total_input_frames;
+
+ // Reset input buffer
+ stm->input_linear_buffer->clear();
+
+ return noErr;
+}
+
+static void
+audiounit_mix_output_buffer(cubeb_stream * stm, size_t output_frames,
+ void * input_buffer, size_t input_buffer_size,
+ void * output_buffer, size_t output_buffer_size)
+{
+ assert(input_buffer_size >=
+ cubeb_sample_size(stm->output_stream_params.format) *
+ stm->output_stream_params.channels * output_frames);
+ assert(output_buffer_size >= stm->output_desc.mBytesPerFrame * output_frames);
+
+ int r = cubeb_mixer_mix(stm->mixer.get(), output_frames, input_buffer,
+ input_buffer_size, output_buffer, output_buffer_size);
+ if (r != 0) {
+ LOG("Remix error = %d", r);
+ }
+}
+
+// Return how many input frames (sampled at input_hw_rate) are needed to provide
+// output_frames (sampled at output_stream_params.rate)
+static int64_t
+minimum_resampling_input_frames(cubeb_stream * stm, uint32_t output_frames)
+{
+ if (stm->input_hw_rate == stm->output_stream_params.rate) {
+ // Fast path.
+ return output_frames;
+ }
+ return ceil(stm->input_hw_rate * output_frames /
+ stm->output_stream_params.rate);
+}
+
+static OSStatus
+audiounit_output_callback(void * user_ptr,
+ AudioUnitRenderActionFlags * /* flags */,
+ AudioTimeStamp const * tstamp, UInt32 bus,
+ UInt32 output_frames, AudioBufferList * outBufferList)
+{
+ assert(AU_OUT_BUS == bus);
+ assert(outBufferList->mNumberBuffers == 1);
+
+ cubeb_stream * stm = static_cast<cubeb_stream *>(user_ptr);
+
+ uint64_t now = ConvertHostTimeToNanos(mach_absolute_time());
+ uint64_t audio_output_time = ConvertHostTimeToNanos(tstamp->mHostTime);
+ uint64_t output_latency_ns = audio_output_time - now;
+
+ const int ns2s = 1e9;
+ // The total output latency is the timestamp difference + the stream latency +
+ // the hardware latency.
+ stm->total_output_latency_frames =
+ output_latency_ns * stm->output_hw_rate / ns2s +
+ stm->current_latency_frames;
+
+ ALOGV("(%p) output: buffers %u, size %u, channels %u, frames %u, total input "
+ "frames %lu.",
+ stm, (unsigned int)outBufferList->mNumberBuffers,
+ (unsigned int)outBufferList->mBuffers[0].mDataByteSize,
+ (unsigned int)outBufferList->mBuffers[0].mNumberChannels,
+ (unsigned int)output_frames,
+ has_input(stm) ? stm->input_linear_buffer->length() /
+ stm->input_desc.mChannelsPerFrame
+ : 0);
+
+ long input_frames = 0;
+ void *output_buffer = NULL, *input_buffer = NULL;
+
+ if (stm->shutdown) {
+ ALOG("(%p) output shutdown.", stm);
+ audiounit_make_silent(&outBufferList->mBuffers[0]);
+ return noErr;
+ }
+
+ if (stm->draining) {
+ OSStatus r = AudioOutputUnitStop(stm->output_unit);
+ assert(r == 0);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+ audiounit_make_silent(&outBufferList->mBuffers[0]);
+ return noErr;
+ }
+
+ /* Get output buffer. */
+ if (stm->mixer) {
+ // If remixing needs to occur, we can't directly work in our final
+ // destination buffer as data may be overwritten or too small to start with.
+ size_t size_needed = output_frames * stm->output_stream_params.channels *
+ cubeb_sample_size(stm->output_stream_params.format);
+ if (stm->temp_buffer_size < size_needed) {
+ stm->temp_buffer.reset(new uint8_t[size_needed]);
+ stm->temp_buffer_size = size_needed;
+ }
+ output_buffer = stm->temp_buffer.get();
+ } else {
+ output_buffer = outBufferList->mBuffers[0].mData;
+ }
+
+ stm->frames_written += output_frames;
+
+ /* If Full duplex get also input buffer */
+ if (stm->input_unit != NULL) {
+ /* If the output callback came first and this is a duplex stream, we need to
+ * fill in some additional silence in the resampler.
+ * Otherwise, if we had more than expected callbacks in a row, or we're
+ * currently switching, we add some silence as well to compensate for the
+ * fact that we're lacking some input data. */
+ uint32_t input_frames_needed =
+ minimum_resampling_input_frames(stm, stm->frames_written);
+ long missing_frames = input_frames_needed - stm->frames_read;
+ if (missing_frames > 0) {
+ stm->input_linear_buffer->push_silence(missing_frames *
+ stm->input_desc.mChannelsPerFrame);
+ stm->frames_read = input_frames_needed;
+
+ ALOG("(%p) %s pushed %ld frames of input silence.", stm,
+ stm->frames_read == 0 ? "Input hasn't started,"
+ : stm->switching_device ? "Device switching,"
+ : "Drop out,",
+ missing_frames);
+ }
+ input_buffer = stm->input_linear_buffer->data();
+ // Number of input frames in the buffer. It will change to actually used
+ // frames inside fill
+ input_frames =
+ stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame;
+ }
+
+ /* Call user callback through resampler. */
+ long outframes = cubeb_resampler_fill(stm->resampler.get(), input_buffer,
+ input_buffer ? &input_frames : NULL,
+ output_buffer, output_frames);
+
+ if (input_buffer) {
+ // Pop from the buffer the frames used by the the resampler.
+ stm->input_linear_buffer->pop(input_frames *
+ stm->input_desc.mChannelsPerFrame);
+ }
+
+ if (outframes < 0 || outframes > output_frames) {
+ stm->shutdown = true;
+ OSStatus r = AudioOutputUnitStop(stm->output_unit);
+ assert(r == 0);
+ if (stm->input_unit) {
+ r = AudioOutputUnitStop(stm->input_unit);
+ assert(r == 0);
+ }
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ audiounit_make_silent(&outBufferList->mBuffers[0]);
+ return noErr;
+ }
+
+ stm->draining = (UInt32)outframes < output_frames;
+ stm->frames_played = stm->frames_queued;
+ stm->frames_queued += outframes;
+
+ /* Post process output samples. */
+ if (stm->draining) {
+ /* Clear missing frames (silence) */
+ size_t channels = stm->output_stream_params.channels;
+ size_t missing_samples = (output_frames - outframes) * channels;
+ size_t size_sample = cubeb_sample_size(stm->output_stream_params.format);
+ /* number of bytes that have been filled with valid audio by the callback.
+ */
+ size_t audio_byte_count = outframes * channels * size_sample;
+ PodZero((uint8_t *)output_buffer + audio_byte_count,
+ missing_samples * size_sample);
+ }
+
+ /* Mixing */
+ if (stm->mixer) {
+ audiounit_mix_output_buffer(stm, output_frames, output_buffer,
+ stm->temp_buffer_size,
+ outBufferList->mBuffers[0].mData,
+ outBufferList->mBuffers[0].mDataByteSize);
+ }
+
+ return noErr;
+}
+
+extern "C" {
+int
+audiounit_init(cubeb ** context, char const * /* context_name */)
+{
+#if !TARGET_OS_IPHONE
+ cubeb_set_coreaudio_notification_runloop();
+#endif
+
+ *context = new cubeb;
+
+ return CUBEB_OK;
+}
+}
+
+static char const *
+audiounit_get_backend_id(cubeb * /* ctx */)
+{
+ return "audiounit";
+}
+
+#if !TARGET_OS_IPHONE
+
+static int
+audiounit_stream_get_volume(cubeb_stream * stm, float * volume);
+static int
+audiounit_stream_set_volume(cubeb_stream * stm, float volume);
+
+static int
+audiounit_set_device_info(cubeb_stream * stm, AudioDeviceID id, io_side side)
+{
+ assert(stm);
+
+ device_info * info = nullptr;
+ cubeb_device_type type = CUBEB_DEVICE_TYPE_UNKNOWN;
+
+ if (side == io_side::INPUT) {
+ info = &stm->input_device;
+ type = CUBEB_DEVICE_TYPE_INPUT;
+ } else if (side == io_side::OUTPUT) {
+ info = &stm->output_device;
+ type = CUBEB_DEVICE_TYPE_OUTPUT;
+ }
+ memset(info, 0, sizeof(device_info));
+ info->id = id;
+
+ if (side == io_side::INPUT) {
+ info->flags |= DEV_INPUT;
+ } else if (side == io_side::OUTPUT) {
+ info->flags |= DEV_OUTPUT;
+ }
+
+ AudioDeviceID default_device_id = audiounit_get_default_device_id(type);
+ if (default_device_id == kAudioObjectUnknown) {
+ return CUBEB_ERROR;
+ }
+ if (id == kAudioObjectUnknown) {
+ info->id = default_device_id;
+ info->flags |= DEV_SELECTED_DEFAULT;
+ }
+
+ if (info->id == default_device_id) {
+ info->flags |= DEV_SYSTEM_DEFAULT;
+ }
+
+ assert(info->id);
+ assert(info->flags & DEV_INPUT && !(info->flags & DEV_OUTPUT) ||
+ !(info->flags & DEV_INPUT) && info->flags & DEV_OUTPUT);
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_reinit_stream(cubeb_stream * stm, device_flags_value flags)
+{
+ auto_lock context_lock(stm->context->mutex);
+ assert((flags & DEV_INPUT && stm->input_unit) ||
+ (flags & DEV_OUTPUT && stm->output_unit));
+ if (!stm->shutdown) {
+ audiounit_stream_stop_internal(stm);
+ }
+
+ int r = audiounit_uninstall_device_changed_callback(stm);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Could not uninstall all device change listeners.", stm);
+ }
+
+ {
+ auto_lock lock(stm->mutex);
+ float volume = 0.0;
+ int vol_rv = CUBEB_ERROR;
+ if (stm->output_unit) {
+ vol_rv = audiounit_stream_get_volume(stm, &volume);
+ }
+
+ audiounit_close_stream(stm);
+
+ /* Reinit occurs in one of the following case:
+ * - When the device is not alive any more
+ * - When the default system device change.
+ * - The bluetooth device changed from A2DP to/from HFP/HSP profile
+ * We first attempt to re-use the same device id, should that fail we will
+ * default to the (potentially new) default device. */
+ AudioDeviceID input_device =
+ flags & DEV_INPUT ? stm->input_device.id : kAudioObjectUnknown;
+ if (flags & DEV_INPUT) {
+ r = audiounit_set_device_info(stm, input_device, io_side::INPUT);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Set input device info failed. This can happen when last "
+ "media device is unplugged",
+ stm);
+ return CUBEB_ERROR;
+ }
+ }
+
+ /* Always use the default output on reinit. This is not correct in every
+ * case but it is sufficient for Firefox and prevent reinit from reporting
+ * failures. It will change soon when reinit mechanism will be updated. */
+ r = audiounit_set_device_info(stm, kAudioObjectUnknown, io_side::OUTPUT);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Set output device info failed. This can happen when last media "
+ "device is unplugged",
+ stm);
+ return CUBEB_ERROR;
+ }
+
+ if (audiounit_setup_stream(stm) != CUBEB_OK) {
+ LOG("(%p) Stream reinit failed.", stm);
+ if (flags & DEV_INPUT && input_device != kAudioObjectUnknown) {
+ // Attempt to re-use the same device-id failed, so attempt again with
+ // default input device.
+ audiounit_close_stream(stm);
+ if (audiounit_set_device_info(stm, kAudioObjectUnknown,
+ io_side::INPUT) != CUBEB_OK ||
+ audiounit_setup_stream(stm) != CUBEB_OK) {
+ LOG("(%p) Second stream reinit failed.", stm);
+ return CUBEB_ERROR;
+ }
+ }
+ }
+
+ if (vol_rv == CUBEB_OK) {
+ audiounit_stream_set_volume(stm, volume);
+ }
+
+ // If the stream was running, start it again.
+ if (!stm->shutdown) {
+ r = audiounit_stream_start_internal(stm);
+ if (r != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+ }
+ }
+ return CUBEB_OK;
+}
+
+static void
+audiounit_reinit_stream_async(cubeb_stream * stm, device_flags_value flags)
+{
+ if (std::atomic_exchange(&stm->reinit_pending, true)) {
+ // A reinit task is already pending, nothing more to do.
+ ALOG("(%p) re-init stream task already pending, cancelling request", stm);
+ return;
+ }
+
+ // Use a new thread, through the queue, to avoid deadlock when calling
+ // Get/SetProperties method from inside notify callback
+ dispatch_async(stm->context->serial_queue, ^() {
+ if (stm->destroy_pending) {
+ ALOG("(%p) stream pending destroy, cancelling reinit task", stm);
+ return;
+ }
+
+ if (audiounit_reinit_stream(stm, flags) != CUBEB_OK) {
+ if (audiounit_uninstall_system_changed_callback(stm) != CUBEB_OK) {
+ LOG("(%p) Could not uninstall system changed callback", stm);
+ }
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ LOG("(%p) Could not reopen the stream after switching.", stm);
+ }
+ stm->switching_device = false;
+ stm->reinit_pending = false;
+ });
+}
+
+static char const *
+event_addr_to_string(AudioObjectPropertySelector selector)
+{
+ switch (selector) {
+ case kAudioHardwarePropertyDefaultOutputDevice:
+ return "kAudioHardwarePropertyDefaultOutputDevice";
+ case kAudioHardwarePropertyDefaultInputDevice:
+ return "kAudioHardwarePropertyDefaultInputDevice";
+ case kAudioDevicePropertyDeviceIsAlive:
+ return "kAudioDevicePropertyDeviceIsAlive";
+ case kAudioDevicePropertyDataSource:
+ return "kAudioDevicePropertyDataSource";
+ default:
+ return "Unknown";
+ }
+}
+
+static OSStatus
+audiounit_property_listener_callback(
+ AudioObjectID id, UInt32 address_count,
+ const AudioObjectPropertyAddress * addresses, void * user)
+{
+ cubeb_stream * stm = (cubeb_stream *)user;
+ if (stm->switching_device) {
+ LOG("Switching is already taking place. Skip Event %s for id=%d",
+ event_addr_to_string(addresses[0].mSelector), id);
+ return noErr;
+ }
+ stm->switching_device = true;
+
+ LOG("(%p) Audio device changed, %u events.", stm,
+ (unsigned int)address_count);
+ for (UInt32 i = 0; i < address_count; i++) {
+ switch (addresses[i].mSelector) {
+ case kAudioHardwarePropertyDefaultOutputDevice: {
+ LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultOutputDevice "
+ "for id=%d",
+ (unsigned int)i, id);
+ } break;
+ case kAudioHardwarePropertyDefaultInputDevice: {
+ LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultInputDevice "
+ "for id=%d",
+ (unsigned int)i, id);
+ } break;
+ case kAudioDevicePropertyDeviceIsAlive: {
+ LOG("Event[%u] - mSelector == kAudioDevicePropertyDeviceIsAlive for "
+ "id=%d",
+ (unsigned int)i, id);
+ // If this is the default input device ignore the event,
+ // kAudioHardwarePropertyDefaultInputDevice will take care of the switch
+ if (stm->input_device.flags & DEV_SYSTEM_DEFAULT) {
+ LOG("It's the default input device, ignore the event");
+ stm->switching_device = false;
+ return noErr;
+ }
+ } break;
+ case kAudioDevicePropertyDataSource: {
+ LOG("Event[%u] - mSelector == kAudioDevicePropertyDataSource for id=%d",
+ (unsigned int)i, id);
+ } break;
+ default:
+ LOG("Event[%u] - mSelector == Unexpected Event id %d, return",
+ (unsigned int)i, addresses[i].mSelector);
+ stm->switching_device = false;
+ return noErr;
+ }
+ }
+
+ // Allow restart to choose the new default
+ device_flags_value switch_side = DEV_UNKNOWN;
+ if (has_input(stm)) {
+ switch_side |= DEV_INPUT;
+ }
+ if (has_output(stm)) {
+ switch_side |= DEV_OUTPUT;
+ }
+
+ for (UInt32 i = 0; i < address_count; i++) {
+ switch (addresses[i].mSelector) {
+ case kAudioHardwarePropertyDefaultOutputDevice:
+ case kAudioHardwarePropertyDefaultInputDevice:
+ case kAudioDevicePropertyDeviceIsAlive:
+ /* fall through */
+ case kAudioDevicePropertyDataSource: {
+ auto_lock dev_cb_lock(stm->device_changed_callback_lock);
+ if (stm->device_changed_callback) {
+ stm->device_changed_callback(stm->user_ptr);
+ }
+ break;
+ }
+ }
+ }
+
+ audiounit_reinit_stream_async(stm, switch_side);
+
+ return noErr;
+}
+
+OSStatus
+audiounit_add_listener(const property_listener * listener)
+{
+ assert(listener);
+ return AudioObjectAddPropertyListener(listener->device_id,
+ listener->property_address,
+ listener->callback, listener->stream);
+}
+
+OSStatus
+audiounit_remove_listener(const property_listener * listener)
+{
+ assert(listener);
+ return AudioObjectRemovePropertyListener(
+ listener->device_id, listener->property_address, listener->callback,
+ listener->stream);
+}
+
+static int
+audiounit_install_device_changed_callback(cubeb_stream * stm)
+{
+ OSStatus rv;
+ int r = CUBEB_OK;
+
+ if (stm->output_unit) {
+ /* This event will notify us when the data source on the same device
+ * changes, for example when the user plugs in a normal (non-usb) headset in
+ * the headphone jack. */
+ stm->output_source_listener.reset(new property_listener(
+ stm->output_device.id, &OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS,
+ &audiounit_property_listener_callback, stm));
+ rv = audiounit_add_listener(stm->output_source_listener.get());
+ if (rv != noErr) {
+ stm->output_source_listener.reset();
+ LOG("AudioObjectAddPropertyListener/output/"
+ "kAudioDevicePropertyDataSource rv=%d, device id=%d",
+ rv, stm->output_device.id);
+ r = CUBEB_ERROR;
+ }
+ }
+
+ if (stm->input_unit) {
+ /* This event will notify us when the data source on the input device
+ * changes. */
+ stm->input_source_listener.reset(new property_listener(
+ stm->input_device.id, &INPUT_DATA_SOURCE_PROPERTY_ADDRESS,
+ &audiounit_property_listener_callback, stm));
+ rv = audiounit_add_listener(stm->input_source_listener.get());
+ if (rv != noErr) {
+ stm->input_source_listener.reset();
+ LOG("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource "
+ "rv=%d, device id=%d",
+ rv, stm->input_device.id);
+ r = CUBEB_ERROR;
+ }
+
+ /* Event to notify when the input is going away. */
+ stm->input_alive_listener.reset(new property_listener(
+ stm->input_device.id, &DEVICE_IS_ALIVE_PROPERTY_ADDRESS,
+ &audiounit_property_listener_callback, stm));
+ rv = audiounit_add_listener(stm->input_alive_listener.get());
+ if (rv != noErr) {
+ stm->input_alive_listener.reset();
+ LOG("AudioObjectAddPropertyListener/input/"
+ "kAudioDevicePropertyDeviceIsAlive rv=%d, device id =%d",
+ rv, stm->input_device.id);
+ r = CUBEB_ERROR;
+ }
+ }
+
+ return r;
+}
+
+static int
+audiounit_install_system_changed_callback(cubeb_stream * stm)
+{
+ OSStatus r;
+
+ if (stm->output_unit) {
+ /* This event will notify us when the default audio device changes,
+ * for example when the user plugs in a USB headset and the system chooses
+ * it automatically as the default, or when another device is chosen in the
+ * dropdown list. */
+ stm->default_output_listener.reset(new property_listener(
+ kAudioObjectSystemObject, &DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS,
+ &audiounit_property_listener_callback, stm));
+ r = audiounit_add_listener(stm->default_output_listener.get());
+ if (r != noErr) {
+ stm->default_output_listener.reset();
+ LOG("AudioObjectAddPropertyListener/output/"
+ "kAudioHardwarePropertyDefaultOutputDevice rv=%d",
+ r);
+ return CUBEB_ERROR;
+ }
+ }
+
+ if (stm->input_unit) {
+ /* This event will notify us when the default input device changes. */
+ stm->default_input_listener.reset(new property_listener(
+ kAudioObjectSystemObject, &DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS,
+ &audiounit_property_listener_callback, stm));
+ r = audiounit_add_listener(stm->default_input_listener.get());
+ if (r != noErr) {
+ stm->default_input_listener.reset();
+ LOG("AudioObjectAddPropertyListener/input/"
+ "kAudioHardwarePropertyDefaultInputDevice rv=%d",
+ r);
+ return CUBEB_ERROR;
+ }
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_uninstall_device_changed_callback(cubeb_stream * stm)
+{
+ OSStatus rv;
+ // Failing to uninstall listeners is not a fatal error.
+ int r = CUBEB_OK;
+
+ if (stm->output_source_listener) {
+ rv = audiounit_remove_listener(stm->output_source_listener.get());
+ if (rv != noErr) {
+ LOG("AudioObjectRemovePropertyListener/output/"
+ "kAudioDevicePropertyDataSource rv=%d, device id=%d",
+ rv, stm->output_device.id);
+ r = CUBEB_ERROR;
+ }
+ stm->output_source_listener.reset();
+ }
+
+ if (stm->input_source_listener) {
+ rv = audiounit_remove_listener(stm->input_source_listener.get());
+ if (rv != noErr) {
+ LOG("AudioObjectRemovePropertyListener/input/"
+ "kAudioDevicePropertyDataSource rv=%d, device id=%d",
+ rv, stm->input_device.id);
+ r = CUBEB_ERROR;
+ }
+ stm->input_source_listener.reset();
+ }
+
+ if (stm->input_alive_listener) {
+ rv = audiounit_remove_listener(stm->input_alive_listener.get());
+ if (rv != noErr) {
+ LOG("AudioObjectRemovePropertyListener/input/"
+ "kAudioDevicePropertyDeviceIsAlive rv=%d, device id=%d",
+ rv, stm->input_device.id);
+ r = CUBEB_ERROR;
+ }
+ stm->input_alive_listener.reset();
+ }
+
+ return r;
+}
+
+static int
+audiounit_uninstall_system_changed_callback(cubeb_stream * stm)
+{
+ OSStatus r;
+
+ if (stm->default_output_listener) {
+ r = audiounit_remove_listener(stm->default_output_listener.get());
+ if (r != noErr) {
+ return CUBEB_ERROR;
+ }
+ stm->default_output_listener.reset();
+ }
+
+ if (stm->default_input_listener) {
+ r = audiounit_remove_listener(stm->default_input_listener.get());
+ if (r != noErr) {
+ return CUBEB_ERROR;
+ }
+ stm->default_input_listener.reset();
+ }
+ return CUBEB_OK;
+}
+
+/* Get the acceptable buffer size (in frames) that this device can work with. */
+static int
+audiounit_get_acceptable_latency_range(AudioValueRange * latency_range)
+{
+ UInt32 size;
+ OSStatus r;
+ AudioDeviceID output_device_id;
+ AudioObjectPropertyAddress output_device_buffer_size_range = {
+ kAudioDevicePropertyBufferFrameSizeRange, kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster};
+
+ output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+ if (output_device_id == kAudioObjectUnknown) {
+ LOG("Could not get default output device id.");
+ return CUBEB_ERROR;
+ }
+
+ /* Get the buffer size range this device supports */
+ size = sizeof(*latency_range);
+
+ r = AudioObjectGetPropertyData(output_device_id,
+ &output_device_buffer_size_range, 0, NULL,
+ &size, latency_range);
+ if (r != noErr) {
+ LOG("AudioObjectGetPropertyData/buffer size range rv=%d", r);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+#endif /* !TARGET_OS_IPHONE */
+
+static AudioObjectID
+audiounit_get_default_device_id(cubeb_device_type type)
+{
+ const AudioObjectPropertyAddress * adr;
+ if (type == CUBEB_DEVICE_TYPE_OUTPUT) {
+ adr = &DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS;
+ } else if (type == CUBEB_DEVICE_TYPE_INPUT) {
+ adr = &DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS;
+ } else {
+ return kAudioObjectUnknown;
+ }
+
+ AudioDeviceID devid;
+ UInt32 size = sizeof(AudioDeviceID);
+ if (AudioObjectGetPropertyData(kAudioObjectSystemObject, adr, 0, NULL, &size,
+ &devid) != noErr) {
+ return kAudioObjectUnknown;
+ }
+
+ return devid;
+}
+
+int
+audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
+{
+#if TARGET_OS_IPHONE
+ // TODO: [[AVAudioSession sharedInstance] maximumOutputNumberOfChannels]
+ *max_channels = 2;
+#else
+ UInt32 size;
+ OSStatus r;
+ AudioDeviceID output_device_id;
+ AudioStreamBasicDescription stream_format;
+ AudioObjectPropertyAddress stream_format_address = {
+ kAudioDevicePropertyStreamFormat, kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster};
+
+ assert(ctx && max_channels);
+
+ output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+ if (output_device_id == kAudioObjectUnknown) {
+ return CUBEB_ERROR;
+ }
+
+ size = sizeof(stream_format);
+
+ r = AudioObjectGetPropertyData(output_device_id, &stream_format_address, 0,
+ NULL, &size, &stream_format);
+ if (r != noErr) {
+ LOG("AudioObjectPropertyAddress/StreamFormat rv=%d", r);
+ return CUBEB_ERROR;
+ }
+
+ *max_channels = stream_format.mChannelsPerFrame;
+#endif
+ return CUBEB_OK;
+}
+
+static int
+audiounit_get_min_latency(cubeb * /* ctx */, cubeb_stream_params /* params */,
+ uint32_t * latency_frames)
+{
+#if TARGET_OS_IPHONE
+ // TODO: [[AVAudioSession sharedInstance] inputLatency]
+ return CUBEB_ERROR_NOT_SUPPORTED;
+#else
+ AudioValueRange latency_range;
+ if (audiounit_get_acceptable_latency_range(&latency_range) != CUBEB_OK) {
+ LOG("Could not get acceptable latency range.");
+ return CUBEB_ERROR;
+ }
+
+ *latency_frames =
+ max<uint32_t>(latency_range.mMinimum, SAFE_MIN_LATENCY_FRAMES);
+#endif
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_get_preferred_sample_rate(cubeb * /* ctx */, uint32_t * rate)
+{
+#if TARGET_OS_IPHONE
+ // TODO
+ return CUBEB_ERROR_NOT_SUPPORTED;
+#else
+ UInt32 size;
+ OSStatus r;
+ Float64 fsamplerate;
+ AudioDeviceID output_device_id;
+ AudioObjectPropertyAddress samplerate_address = {
+ kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+ output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+ if (output_device_id == kAudioObjectUnknown) {
+ return CUBEB_ERROR;
+ }
+
+ size = sizeof(fsamplerate);
+ r = AudioObjectGetPropertyData(output_device_id, &samplerate_address, 0, NULL,
+ &size, &fsamplerate);
+
+ if (r != noErr) {
+ return CUBEB_ERROR;
+ }
+
+ *rate = static_cast<uint32_t>(fsamplerate);
+#endif
+ return CUBEB_OK;
+}
+
+static cubeb_channel_layout
+audiounit_convert_channel_layout(AudioChannelLayout * layout)
+{
+ // 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 CUBEB_LAYOUT_MONO;
+ } else if (layout->mNumberChannelDescriptions == 2) {
+ return CUBEB_LAYOUT_STEREO;
+ }
+
+ if (layout->mChannelLayoutTag !=
+ kAudioChannelLayoutTag_UseChannelDescriptions) {
+ // kAudioChannelLayoutTag_UseChannelBitmap
+ // kAudioChannelLayoutTag_Mono
+ // kAudioChannelLayoutTag_Stereo
+ // ....
+ LOG("Only handle UseChannelDescriptions for now.\n");
+ return CUBEB_LAYOUT_UNDEFINED;
+ }
+
+ cubeb_channel_layout cl = 0;
+ for (UInt32 i = 0; i < layout->mNumberChannelDescriptions; ++i) {
+ cubeb_channel cc = channel_label_to_cubeb_channel(
+ layout->mChannelDescriptions[i].mChannelLabel);
+ if (cc == CHANNEL_UNKNOWN) {
+ return CUBEB_LAYOUT_UNDEFINED;
+ }
+ cl |= cc;
+ }
+
+ return cl;
+}
+
+static cubeb_channel_layout
+audiounit_get_preferred_channel_layout(AudioUnit output_unit)
+{
+ OSStatus rv = noErr;
+ UInt32 size = 0;
+ rv = AudioUnitGetPropertyInfo(
+ output_unit, kAudioDevicePropertyPreferredChannelLayout,
+ kAudioUnitScope_Output, AU_OUT_BUS, &size, nullptr);
+ if (rv != noErr) {
+ LOG("AudioUnitGetPropertyInfo/kAudioDevicePropertyPreferredChannelLayout "
+ "rv=%d",
+ rv);
+ return CUBEB_LAYOUT_UNDEFINED;
+ }
+ assert(size > 0);
+
+ auto layout = make_sized_audio_channel_layout(size);
+ rv = AudioUnitGetProperty(
+ output_unit, kAudioDevicePropertyPreferredChannelLayout,
+ kAudioUnitScope_Output, AU_OUT_BUS, layout.get(), &size);
+ if (rv != noErr) {
+ LOG("AudioUnitGetProperty/kAudioDevicePropertyPreferredChannelLayout rv=%d",
+ rv);
+ return CUBEB_LAYOUT_UNDEFINED;
+ }
+
+ return audiounit_convert_channel_layout(layout.get());
+}
+
+static cubeb_channel_layout
+audiounit_get_current_channel_layout(AudioUnit output_unit)
+{
+ OSStatus rv = noErr;
+ UInt32 size = 0;
+ rv = AudioUnitGetPropertyInfo(
+ output_unit, kAudioUnitProperty_AudioChannelLayout,
+ kAudioUnitScope_Output, AU_OUT_BUS, &size, nullptr);
+ if (rv != noErr) {
+ LOG("AudioUnitGetPropertyInfo/kAudioUnitProperty_AudioChannelLayout rv=%d",
+ rv);
+ // This property isn't known before macOS 10.12, attempt another method.
+ return audiounit_get_preferred_channel_layout(output_unit);
+ }
+ assert(size > 0);
+
+ auto layout = make_sized_audio_channel_layout(size);
+ rv = AudioUnitGetProperty(output_unit, kAudioUnitProperty_AudioChannelLayout,
+ kAudioUnitScope_Output, AU_OUT_BUS, layout.get(),
+ &size);
+ if (rv != noErr) {
+ LOG("AudioUnitGetProperty/kAudioUnitProperty_AudioChannelLayout rv=%d", rv);
+ return CUBEB_LAYOUT_UNDEFINED;
+ }
+
+ return audiounit_convert_channel_layout(layout.get());
+}
+
+static int
+audiounit_create_unit(AudioUnit * unit, device_info * device);
+
+static OSStatus
+audiounit_remove_device_listener(cubeb * context, cubeb_device_type devtype);
+
+static void
+audiounit_destroy(cubeb * ctx)
+{
+ {
+ auto_lock lock(ctx->mutex);
+
+ // Disabling this assert for bug 1083664 -- we seem to leak a stream
+ // assert(ctx->active_streams == 0);
+ if (audiounit_active_streams(ctx) > 0) {
+ LOG("(%p) API misuse, %d streams active when context destroyed!", ctx,
+ audiounit_active_streams(ctx));
+ }
+
+ // Destroying a cubeb context with device collection callbacks registered
+ // is misuse of the API, assert then attempt to clean up.
+ assert(!ctx->input_collection_changed_callback &&
+ !ctx->input_collection_changed_user_ptr &&
+ !ctx->output_collection_changed_callback &&
+ !ctx->output_collection_changed_user_ptr);
+
+ /* Unregister the callback if necessary. */
+ if (ctx->input_collection_changed_callback) {
+ audiounit_remove_device_listener(ctx, CUBEB_DEVICE_TYPE_INPUT);
+ }
+ if (ctx->output_collection_changed_callback) {
+ audiounit_remove_device_listener(ctx, CUBEB_DEVICE_TYPE_OUTPUT);
+ }
+ }
+
+ dispatch_release(ctx->serial_queue);
+
+ delete ctx;
+}
+
+static void
+audiounit_stream_destroy(cubeb_stream * stm);
+
+static int
+audio_stream_desc_init(AudioStreamBasicDescription * ss,
+ const cubeb_stream_params * stream_params)
+{
+ switch (stream_params->format) {
+ case CUBEB_SAMPLE_S16LE:
+ ss->mBitsPerChannel = 16;
+ ss->mFormatFlags = kAudioFormatFlagIsSignedInteger;
+ break;
+ case CUBEB_SAMPLE_S16BE:
+ ss->mBitsPerChannel = 16;
+ ss->mFormatFlags =
+ kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsBigEndian;
+ break;
+ case CUBEB_SAMPLE_FLOAT32LE:
+ ss->mBitsPerChannel = 32;
+ ss->mFormatFlags = kAudioFormatFlagIsFloat;
+ break;
+ case CUBEB_SAMPLE_FLOAT32BE:
+ ss->mBitsPerChannel = 32;
+ ss->mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsBigEndian;
+ break;
+ default:
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ ss->mFormatID = kAudioFormatLinearPCM;
+ ss->mFormatFlags |= kLinearPCMFormatFlagIsPacked;
+ ss->mSampleRate = stream_params->rate;
+ ss->mChannelsPerFrame = stream_params->channels;
+
+ ss->mBytesPerFrame = (ss->mBitsPerChannel / 8) * ss->mChannelsPerFrame;
+ ss->mFramesPerPacket = 1;
+ ss->mBytesPerPacket = ss->mBytesPerFrame * ss->mFramesPerPacket;
+
+ ss->mReserved = 0;
+
+ return CUBEB_OK;
+}
+
+void
+audiounit_init_mixer(cubeb_stream * stm)
+{
+ // We can't rely on macOS' AudioUnit to properly downmix (or upmix) the audio
+ // data, it silently drop the channels so we need to remix the
+ // audio data by ourselves to keep all the information.
+ stm->mixer.reset(cubeb_mixer_create(
+ stm->output_stream_params.format, stm->output_stream_params.channels,
+ stm->output_stream_params.layout, stm->context->channels,
+ stm->context->layout));
+ assert(stm->mixer);
+}
+
+static int
+audiounit_set_channel_layout(AudioUnit unit, io_side side,
+ cubeb_channel_layout layout)
+{
+ if (side != io_side::OUTPUT) {
+ return CUBEB_ERROR;
+ }
+
+ if (layout == CUBEB_LAYOUT_UNDEFINED) {
+ // We leave everything as-is...
+ return CUBEB_OK;
+ }
+
+ OSStatus r;
+ uint32_t nb_channels = cubeb_channel_layout_nb_channels(layout);
+
+ // We do not use CoreAudio standard layout for lack of documentation on what
+ // the actual channel orders are. So we set a custom layout.
+ size_t size = offsetof(AudioChannelLayout, mChannelDescriptions[nb_channels]);
+ auto au_layout = make_sized_audio_channel_layout(size);
+ au_layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
+ au_layout->mNumberChannelDescriptions = nb_channels;
+
+ uint32_t channels = 0;
+ cubeb_channel_layout channelMap = layout;
+ for (uint32_t i = 0; channelMap != 0; ++i) {
+ XASSERT(channels < nb_channels);
+ uint32_t channel = (channelMap & 1) << i;
+ if (channel != 0) {
+ au_layout->mChannelDescriptions[channels].mChannelLabel =
+ cubeb_channel_to_channel_label(static_cast<cubeb_channel>(channel));
+ au_layout->mChannelDescriptions[channels].mChannelFlags =
+ kAudioChannelFlags_AllOff;
+ channels++;
+ }
+ channelMap = channelMap >> 1;
+ }
+
+ r = AudioUnitSetProperty(unit, kAudioUnitProperty_AudioChannelLayout,
+ kAudioUnitScope_Input, AU_OUT_BUS, au_layout.get(),
+ size);
+ if (r != noErr) {
+ LOG("AudioUnitSetProperty/%s/kAudioUnitProperty_AudioChannelLayout rv=%d",
+ to_string(side), r);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+void
+audiounit_layout_init(cubeb_stream * stm, io_side side)
+{
+ // We currently don't support the input layout setting.
+ if (side == io_side::INPUT) {
+ return;
+ }
+
+ stm->context->layout = audiounit_get_current_channel_layout(stm->output_unit);
+
+ audiounit_set_channel_layout(stm->output_unit, io_side::OUTPUT,
+ stm->context->layout);
+}
+
+static vector<AudioObjectID>
+audiounit_get_sub_devices(AudioDeviceID device_id)
+{
+ vector<AudioDeviceID> sub_devices;
+ AudioObjectPropertyAddress property_address = {
+ kAudioAggregateDevicePropertyActiveSubDeviceList,
+ kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
+ UInt32 size = 0;
+ OSStatus rv = AudioObjectGetPropertyDataSize(device_id, &property_address, 0,
+ nullptr, &size);
+
+ if (rv != noErr) {
+ sub_devices.push_back(device_id);
+ return sub_devices;
+ }
+
+ uint32_t count = static_cast<uint32_t>(size / sizeof(AudioObjectID));
+ sub_devices.resize(count);
+ rv = AudioObjectGetPropertyData(device_id, &property_address, 0, nullptr,
+ &size, sub_devices.data());
+ if (rv != noErr) {
+ sub_devices.clear();
+ sub_devices.push_back(device_id);
+ } else {
+ LOG("Found %u sub-devices", count);
+ }
+ return sub_devices;
+}
+
+static int
+audiounit_create_blank_aggregate_device(AudioObjectID * plugin_id,
+ AudioDeviceID * aggregate_device_id)
+{
+ AudioObjectPropertyAddress address_plugin_bundle_id = {
+ kAudioHardwarePropertyPlugInForBundleID, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ UInt32 size = 0;
+ OSStatus r = AudioObjectGetPropertyDataSize(
+ kAudioObjectSystemObject, &address_plugin_bundle_id, 0, NULL, &size);
+ if (r != noErr) {
+ LOG("AudioObjectGetPropertyDataSize/"
+ "kAudioHardwarePropertyPlugInForBundleID, rv=%d",
+ r);
+ return CUBEB_ERROR;
+ }
+
+ AudioValueTranslation translation_value;
+ CFStringRef in_bundle_ref = CFSTR("com.apple.audio.CoreAudio");
+ translation_value.mInputData = &in_bundle_ref;
+ translation_value.mInputDataSize = sizeof(in_bundle_ref);
+ translation_value.mOutputData = plugin_id;
+ translation_value.mOutputDataSize = sizeof(*plugin_id);
+
+ r = AudioObjectGetPropertyData(kAudioObjectSystemObject,
+ &address_plugin_bundle_id, 0, nullptr, &size,
+ &translation_value);
+ if (r != noErr) {
+ LOG("AudioObjectGetPropertyData/kAudioHardwarePropertyPlugInForBundleID, "
+ "rv=%d",
+ r);
+ return CUBEB_ERROR;
+ }
+
+ AudioObjectPropertyAddress create_aggregate_device_address = {
+ kAudioPlugInCreateAggregateDevice, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ r = AudioObjectGetPropertyDataSize(
+ *plugin_id, &create_aggregate_device_address, 0, nullptr, &size);
+ if (r != noErr) {
+ LOG("AudioObjectGetPropertyDataSize/kAudioPlugInCreateAggregateDevice, "
+ "rv=%d",
+ r);
+ return CUBEB_ERROR;
+ }
+
+ CFMutableDictionaryRef aggregate_device_dict = CFDictionaryCreateMutable(
+ kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ struct timeval timestamp;
+ gettimeofday(&timestamp, NULL);
+ long long int time_id = timestamp.tv_sec * 1000000LL + timestamp.tv_usec;
+ CFStringRef aggregate_device_name = CFStringCreateWithFormat(
+ NULL, NULL, CFSTR("%s_%llx"), PRIVATE_AGGREGATE_DEVICE_NAME, time_id);
+ CFDictionaryAddValue(aggregate_device_dict,
+ CFSTR(kAudioAggregateDeviceNameKey),
+ aggregate_device_name);
+ CFRelease(aggregate_device_name);
+
+ CFStringRef aggregate_device_UID =
+ CFStringCreateWithFormat(NULL, NULL, CFSTR("org.mozilla.%s_%llx"),
+ PRIVATE_AGGREGATE_DEVICE_NAME, time_id);
+ CFDictionaryAddValue(aggregate_device_dict,
+ CFSTR(kAudioAggregateDeviceUIDKey),
+ aggregate_device_UID);
+ CFRelease(aggregate_device_UID);
+
+ int private_value = 1;
+ CFNumberRef aggregate_device_private_key =
+ CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &private_value);
+ CFDictionaryAddValue(aggregate_device_dict,
+ CFSTR(kAudioAggregateDeviceIsPrivateKey),
+ aggregate_device_private_key);
+ CFRelease(aggregate_device_private_key);
+
+ int stacked_value = 0;
+ CFNumberRef aggregate_device_stacked_key =
+ CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &stacked_value);
+ CFDictionaryAddValue(aggregate_device_dict,
+ CFSTR(kAudioAggregateDeviceIsStackedKey),
+ aggregate_device_stacked_key);
+ CFRelease(aggregate_device_stacked_key);
+
+ r = AudioObjectGetPropertyData(*plugin_id, &create_aggregate_device_address,
+ sizeof(aggregate_device_dict),
+ &aggregate_device_dict, &size,
+ aggregate_device_id);
+ CFRelease(aggregate_device_dict);
+ if (r != noErr) {
+ LOG("AudioObjectGetPropertyData/kAudioPlugInCreateAggregateDevice, rv=%d",
+ r);
+ return CUBEB_ERROR;
+ }
+ LOG("New aggregate device %u", *aggregate_device_id);
+
+ return CUBEB_OK;
+}
+
+// The returned CFStringRef object needs to be released (via CFRelease)
+// if it's not NULL, since the reference count of the returned CFStringRef
+// object is increased.
+static CFStringRef
+get_device_name(AudioDeviceID id)
+{
+ UInt32 size = sizeof(CFStringRef);
+ CFStringRef UIname = nullptr;
+ AudioObjectPropertyAddress address_uuid = {kAudioDevicePropertyDeviceUID,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ OSStatus err =
+ AudioObjectGetPropertyData(id, &address_uuid, 0, nullptr, &size, &UIname);
+ return (err == noErr) ? UIname : NULL;
+}
+
+static int
+audiounit_set_aggregate_sub_device_list(AudioDeviceID aggregate_device_id,
+ AudioDeviceID input_device_id,
+ AudioDeviceID output_device_id)
+{
+ LOG("Add devices input %u and output %u into aggregate device %u",
+ input_device_id, output_device_id, aggregate_device_id);
+ const vector<AudioDeviceID> output_sub_devices =
+ audiounit_get_sub_devices(output_device_id);
+ const vector<AudioDeviceID> input_sub_devices =
+ audiounit_get_sub_devices(input_device_id);
+
+ CFMutableArrayRef aggregate_sub_devices_array =
+ CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ /* The order of the items in the array is significant and is used to determine
+ the order of the streams of the AudioAggregateDevice. */
+ for (UInt32 i = 0; i < output_sub_devices.size(); i++) {
+ CFStringRef ref = get_device_name(output_sub_devices[i]);
+ if (ref == NULL) {
+ CFRelease(aggregate_sub_devices_array);
+ return CUBEB_ERROR;
+ }
+ CFArrayAppendValue(aggregate_sub_devices_array, ref);
+ CFRelease(ref);
+ }
+ for (UInt32 i = 0; i < input_sub_devices.size(); i++) {
+ CFStringRef ref = get_device_name(input_sub_devices[i]);
+ if (ref == NULL) {
+ CFRelease(aggregate_sub_devices_array);
+ return CUBEB_ERROR;
+ }
+ CFArrayAppendValue(aggregate_sub_devices_array, ref);
+ CFRelease(ref);
+ }
+
+ AudioObjectPropertyAddress aggregate_sub_device_list = {
+ kAudioAggregateDevicePropertyFullSubDeviceList,
+ kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
+ UInt32 size = sizeof(CFMutableArrayRef);
+ OSStatus rv = AudioObjectSetPropertyData(
+ aggregate_device_id, &aggregate_sub_device_list, 0, nullptr, size,
+ &aggregate_sub_devices_array);
+ CFRelease(aggregate_sub_devices_array);
+ if (rv != noErr) {
+ LOG("AudioObjectSetPropertyData/"
+ "kAudioAggregateDevicePropertyFullSubDeviceList, rv=%d",
+ rv);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_set_master_aggregate_device(const AudioDeviceID aggregate_device_id)
+{
+ assert(aggregate_device_id != kAudioObjectUnknown);
+ AudioObjectPropertyAddress master_aggregate_sub_device = {
+ kAudioAggregateDevicePropertyMasterSubDevice,
+ kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
+
+ // Master become the 1st output sub device
+ AudioDeviceID output_device_id =
+ audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+ const vector<AudioDeviceID> output_sub_devices =
+ audiounit_get_sub_devices(output_device_id);
+ CFStringRef master_sub_device = get_device_name(output_sub_devices[0]);
+
+ UInt32 size = sizeof(CFStringRef);
+ OSStatus rv = AudioObjectSetPropertyData(aggregate_device_id,
+ &master_aggregate_sub_device, 0,
+ NULL, size, &master_sub_device);
+ if (master_sub_device) {
+ CFRelease(master_sub_device);
+ }
+ if (rv != noErr) {
+ LOG("AudioObjectSetPropertyData/"
+ "kAudioAggregateDevicePropertyMasterSubDevice, rv=%d",
+ rv);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_activate_clock_drift_compensation(
+ const AudioDeviceID aggregate_device_id)
+{
+ assert(aggregate_device_id != kAudioObjectUnknown);
+ AudioObjectPropertyAddress address_owned = {
+ kAudioObjectPropertyOwnedObjects, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+ UInt32 qualifier_data_size = sizeof(AudioObjectID);
+ AudioClassID class_id = kAudioSubDeviceClassID;
+ void * qualifier_data = &class_id;
+ UInt32 size = 0;
+ OSStatus rv = AudioObjectGetPropertyDataSize(
+ aggregate_device_id, &address_owned, qualifier_data_size, qualifier_data,
+ &size);
+ if (rv != noErr) {
+ LOG("AudioObjectGetPropertyDataSize/kAudioObjectPropertyOwnedObjects, "
+ "rv=%d",
+ rv);
+ return CUBEB_ERROR;
+ }
+
+ UInt32 subdevices_num = 0;
+ subdevices_num = size / sizeof(AudioObjectID);
+ AudioObjectID sub_devices[subdevices_num];
+ size = sizeof(sub_devices);
+
+ rv = AudioObjectGetPropertyData(aggregate_device_id, &address_owned,
+ qualifier_data_size, qualifier_data, &size,
+ sub_devices);
+ if (rv != noErr) {
+ LOG("AudioObjectGetPropertyData/kAudioObjectPropertyOwnedObjects, rv=%d",
+ rv);
+ return CUBEB_ERROR;
+ }
+
+ AudioObjectPropertyAddress address_drift = {
+ kAudioSubDevicePropertyDriftCompensation, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+ // Start from the second device since the first is the master clock
+ for (UInt32 i = 1; i < subdevices_num; ++i) {
+ UInt32 drift_compensation_value = 1;
+ rv = AudioObjectSetPropertyData(sub_devices[i], &address_drift, 0, nullptr,
+ sizeof(UInt32), &drift_compensation_value);
+ if (rv != noErr) {
+ LOG("AudioObjectSetPropertyData/"
+ "kAudioSubDevicePropertyDriftCompensation, rv=%d",
+ rv);
+ return CUBEB_OK;
+ }
+ }
+ return CUBEB_OK;
+}
+
+static int
+audiounit_destroy_aggregate_device(AudioObjectID plugin_id,
+ AudioDeviceID * aggregate_device_id);
+static void
+audiounit_get_available_samplerate(AudioObjectID devid,
+ AudioObjectPropertyScope scope,
+ uint32_t * min, uint32_t * max,
+ uint32_t * def);
+static int
+audiounit_create_device_from_hwdev(cubeb_device_info * dev_info,
+ AudioObjectID devid, cubeb_device_type type);
+static void
+audiounit_device_destroy(cubeb_device_info * device);
+
+static void
+audiounit_workaround_for_airpod(cubeb_stream * stm)
+{
+ cubeb_device_info input_device_info;
+ audiounit_create_device_from_hwdev(&input_device_info, stm->input_device.id,
+ CUBEB_DEVICE_TYPE_INPUT);
+
+ cubeb_device_info output_device_info;
+ audiounit_create_device_from_hwdev(&output_device_info, stm->output_device.id,
+ CUBEB_DEVICE_TYPE_OUTPUT);
+
+ std::string input_name_str(input_device_info.friendly_name);
+ std::string output_name_str(output_device_info.friendly_name);
+
+ if (input_name_str.find("AirPods") != std::string::npos &&
+ output_name_str.find("AirPods") != std::string::npos) {
+ uint32_t input_min_rate = 0;
+ uint32_t input_max_rate = 0;
+ uint32_t input_nominal_rate = 0;
+ audiounit_get_available_samplerate(
+ stm->input_device.id, kAudioObjectPropertyScopeGlobal, &input_min_rate,
+ &input_max_rate, &input_nominal_rate);
+ LOG("(%p) Input device %u, name: %s, min: %u, max: %u, nominal rate: %u",
+ stm, stm->input_device.id, input_device_info.friendly_name,
+ input_min_rate, input_max_rate, input_nominal_rate);
+ uint32_t output_min_rate = 0;
+ uint32_t output_max_rate = 0;
+ uint32_t output_nominal_rate = 0;
+ audiounit_get_available_samplerate(
+ stm->output_device.id, kAudioObjectPropertyScopeGlobal,
+ &output_min_rate, &output_max_rate, &output_nominal_rate);
+ LOG("(%p) Output device %u, name: %s, min: %u, max: %u, nominal rate: %u",
+ stm, stm->output_device.id, output_device_info.friendly_name,
+ output_min_rate, output_max_rate, output_nominal_rate);
+
+ Float64 rate = input_nominal_rate;
+ AudioObjectPropertyAddress addr = {kAudioDevicePropertyNominalSampleRate,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+ OSStatus rv = AudioObjectSetPropertyData(stm->aggregate_device_id, &addr, 0,
+ nullptr, sizeof(Float64), &rate);
+ if (rv != noErr) {
+ LOG("Non fatal error, "
+ "AudioObjectSetPropertyData/kAudioDevicePropertyNominalSampleRate, "
+ "rv=%d",
+ rv);
+ }
+ }
+ audiounit_device_destroy(&input_device_info);
+ audiounit_device_destroy(&output_device_info);
+}
+
+/*
+ * Aggregate Device is a virtual audio interface which utilizes inputs and
+ * outputs of one or more physical audio interfaces. It is possible to use the
+ * clock of one of the devices as a master clock for all the combined devices
+ * and enable drift compensation for the devices that are not designated clock
+ * master.
+ *
+ * Creating a new aggregate device programmatically requires [0][1]:
+ * 1. Locate the base plug-in ("com.apple.audio.CoreAudio")
+ * 2. Create a dictionary that describes the aggregate device
+ * (don't add sub-devices in that step, prone to fail [0])
+ * 3. Ask the base plug-in to create the aggregate device (blank)
+ * 4. Add the array of sub-devices.
+ * 5. Set the master device (1st output device in our case)
+ * 6. Enable drift compensation for the non-master devices
+ *
+ * [0] https://lists.apple.com/archives/coreaudio-api/2006/Apr/msg00092.html
+ * [1] https://lists.apple.com/archives/coreaudio-api/2005/Jul/msg00150.html
+ * [2] CoreAudio.framework/Headers/AudioHardware.h
+ * */
+static int
+audiounit_create_aggregate_device(cubeb_stream * stm)
+{
+ int r = audiounit_create_blank_aggregate_device(&stm->plugin_id,
+ &stm->aggregate_device_id);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Failed to create blank aggregate device", stm);
+ return CUBEB_ERROR;
+ }
+
+ r = audiounit_set_aggregate_sub_device_list(
+ stm->aggregate_device_id, stm->input_device.id, stm->output_device.id);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Failed to set aggregate sub-device list", stm);
+ audiounit_destroy_aggregate_device(stm->plugin_id,
+ &stm->aggregate_device_id);
+ return CUBEB_ERROR;
+ }
+
+ r = audiounit_set_master_aggregate_device(stm->aggregate_device_id);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Failed to set master sub-device for aggregate device", stm);
+ audiounit_destroy_aggregate_device(stm->plugin_id,
+ &stm->aggregate_device_id);
+ return CUBEB_ERROR;
+ }
+
+ r = audiounit_activate_clock_drift_compensation(stm->aggregate_device_id);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Failed to activate clock drift compensation for aggregate device",
+ stm);
+ audiounit_destroy_aggregate_device(stm->plugin_id,
+ &stm->aggregate_device_id);
+ return CUBEB_ERROR;
+ }
+
+ audiounit_workaround_for_airpod(stm);
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_destroy_aggregate_device(AudioObjectID plugin_id,
+ AudioDeviceID * aggregate_device_id)
+{
+ assert(aggregate_device_id && *aggregate_device_id != kAudioDeviceUnknown &&
+ plugin_id != kAudioObjectUnknown);
+ AudioObjectPropertyAddress destroy_aggregate_device_addr = {
+ kAudioPlugInDestroyAggregateDevice, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ UInt32 size;
+ OSStatus rv = AudioObjectGetPropertyDataSize(
+ plugin_id, &destroy_aggregate_device_addr, 0, NULL, &size);
+ if (rv != noErr) {
+ LOG("AudioObjectGetPropertyDataSize/kAudioPlugInDestroyAggregateDevice, "
+ "rv=%d",
+ rv);
+ return CUBEB_ERROR;
+ }
+
+ rv = AudioObjectGetPropertyData(plugin_id, &destroy_aggregate_device_addr, 0,
+ NULL, &size, aggregate_device_id);
+ if (rv != noErr) {
+ LOG("AudioObjectGetPropertyData/kAudioPlugInDestroyAggregateDevice, rv=%d",
+ rv);
+ return CUBEB_ERROR;
+ }
+
+ LOG("Destroyed aggregate device %d", *aggregate_device_id);
+ *aggregate_device_id = kAudioObjectUnknown;
+ return CUBEB_OK;
+}
+
+static int
+audiounit_new_unit_instance(AudioUnit * unit, device_info * device)
+{
+ AudioComponentDescription desc;
+ AudioComponent comp;
+ OSStatus rv;
+
+ desc.componentType = kAudioUnitType_Output;
+#if TARGET_OS_IPHONE
+ desc.componentSubType = kAudioUnitSubType_RemoteIO;
+#else
+ // Use the DefaultOutputUnit for output when no device is specified
+ // so we retain automatic output device switching when the default
+ // changes. Once we have complete support for device notifications
+ // and switching, we can use the AUHAL for everything.
+ if ((device->flags & DEV_SYSTEM_DEFAULT) && (device->flags & DEV_OUTPUT)) {
+ desc.componentSubType = kAudioUnitSubType_DefaultOutput;
+ } else {
+ desc.componentSubType = kAudioUnitSubType_HALOutput;
+ }
+#endif
+ desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+ desc.componentFlags = 0;
+ desc.componentFlagsMask = 0;
+ comp = AudioComponentFindNext(NULL, &desc);
+ if (comp == NULL) {
+ LOG("Could not find matching audio hardware.");
+ return CUBEB_ERROR;
+ }
+
+ rv = AudioComponentInstanceNew(comp, unit);
+ if (rv != noErr) {
+ LOG("AudioComponentInstanceNew rv=%d", rv);
+ return CUBEB_ERROR;
+ }
+ return CUBEB_OK;
+}
+
+enum enable_state {
+ DISABLE,
+ ENABLE,
+};
+
+static int
+audiounit_enable_unit_scope(AudioUnit * unit, io_side side, enable_state state)
+{
+ OSStatus rv;
+ UInt32 enable = state;
+ rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_EnableIO,
+ (side == io_side::INPUT) ? kAudioUnitScope_Input
+ : kAudioUnitScope_Output,
+ (side == io_side::INPUT) ? AU_IN_BUS : AU_OUT_BUS,
+ &enable, sizeof(UInt32));
+ if (rv != noErr) {
+ LOG("AudioUnitSetProperty/kAudioOutputUnitProperty_EnableIO rv=%d", rv);
+ return CUBEB_ERROR;
+ }
+ return CUBEB_OK;
+}
+
+static int
+audiounit_create_unit(AudioUnit * unit, device_info * device)
+{
+ assert(*unit == nullptr);
+ assert(device);
+
+ OSStatus rv;
+ int r;
+
+ r = audiounit_new_unit_instance(unit, device);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+ assert(*unit);
+
+ if ((device->flags & DEV_SYSTEM_DEFAULT) && (device->flags & DEV_OUTPUT)) {
+ return CUBEB_OK;
+ }
+
+ if (device->flags & DEV_INPUT) {
+ r = audiounit_enable_unit_scope(unit, io_side::INPUT, ENABLE);
+ if (r != CUBEB_OK) {
+ LOG("Failed to enable audiounit input scope");
+ return r;
+ }
+ r = audiounit_enable_unit_scope(unit, io_side::OUTPUT, DISABLE);
+ if (r != CUBEB_OK) {
+ LOG("Failed to disable audiounit output scope");
+ return r;
+ }
+ } else if (device->flags & DEV_OUTPUT) {
+ r = audiounit_enable_unit_scope(unit, io_side::OUTPUT, ENABLE);
+ if (r != CUBEB_OK) {
+ LOG("Failed to enable audiounit output scope");
+ return r;
+ }
+ r = audiounit_enable_unit_scope(unit, io_side::INPUT, DISABLE);
+ if (r != CUBEB_OK) {
+ LOG("Failed to disable audiounit input scope");
+ return r;
+ }
+ } else {
+ assert(false);
+ }
+
+ rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global, 0, &device->id,
+ sizeof(AudioDeviceID));
+ if (rv != noErr) {
+ LOG("AudioUnitSetProperty/kAudioOutputUnitProperty_CurrentDevice rv=%d",
+ rv);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_init_input_linear_buffer(cubeb_stream * stream, uint32_t capacity)
+{
+ uint32_t size =
+ capacity * stream->latency_frames * stream->input_desc.mChannelsPerFrame;
+ if (stream->input_desc.mFormatFlags & kAudioFormatFlagIsSignedInteger) {
+ stream->input_linear_buffer.reset(new auto_array_wrapper_impl<short>(size));
+ } else {
+ stream->input_linear_buffer.reset(new auto_array_wrapper_impl<float>(size));
+ }
+ assert(stream->input_linear_buffer->length() == 0);
+
+ return CUBEB_OK;
+}
+
+static uint32_t
+audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames)
+{
+ // For the 1st stream set anything within safe min-max
+ assert(audiounit_active_streams(stm->context) > 0);
+ if (audiounit_active_streams(stm->context) == 1) {
+ return max(min<uint32_t>(latency_frames, SAFE_MAX_LATENCY_FRAMES),
+ SAFE_MIN_LATENCY_FRAMES);
+ }
+ assert(stm->output_unit);
+
+ // If more than one stream operates in parallel
+ // allow only lower values of latency
+ int r;
+ UInt32 output_buffer_size = 0;
+ UInt32 size = sizeof(output_buffer_size);
+ if (stm->output_unit) {
+ r = AudioUnitGetProperty(
+ stm->output_unit, kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Output, AU_OUT_BUS, &output_buffer_size, &size);
+ if (r != noErr) {
+ LOG("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize "
+ "rv=%d",
+ r);
+ return 0;
+ }
+
+ output_buffer_size =
+ max(min<uint32_t>(output_buffer_size, SAFE_MAX_LATENCY_FRAMES),
+ SAFE_MIN_LATENCY_FRAMES);
+ }
+
+ UInt32 input_buffer_size = 0;
+ if (stm->input_unit) {
+ r = AudioUnitGetProperty(
+ stm->input_unit, kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Input, AU_IN_BUS, &input_buffer_size, &size);
+ if (r != noErr) {
+ LOG("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize "
+ "rv=%d",
+ r);
+ return 0;
+ }
+
+ input_buffer_size =
+ max(min<uint32_t>(input_buffer_size, SAFE_MAX_LATENCY_FRAMES),
+ SAFE_MIN_LATENCY_FRAMES);
+ }
+
+ // Every following active streams can only set smaller latency
+ UInt32 upper_latency_limit = 0;
+ if (input_buffer_size != 0 && output_buffer_size != 0) {
+ upper_latency_limit = min<uint32_t>(input_buffer_size, output_buffer_size);
+ } else if (input_buffer_size != 0) {
+ upper_latency_limit = input_buffer_size;
+ } else if (output_buffer_size != 0) {
+ upper_latency_limit = output_buffer_size;
+ } else {
+ upper_latency_limit = SAFE_MAX_LATENCY_FRAMES;
+ }
+
+ return max(min<uint32_t>(latency_frames, upper_latency_limit),
+ SAFE_MIN_LATENCY_FRAMES);
+}
+
+/*
+ * Change buffer size is prone to deadlock thus we change it
+ * following the steps:
+ * - register a listener for the buffer size property
+ * - change the property
+ * - wait until the listener is executed
+ * - property has changed, remove the listener
+ * */
+static void
+buffer_size_changed_callback(void * inClientData, AudioUnit inUnit,
+ AudioUnitPropertyID inPropertyID,
+ AudioUnitScope inScope, AudioUnitElement inElement)
+{
+ cubeb_stream * stm = (cubeb_stream *)inClientData;
+
+ AudioUnit au = inUnit;
+ AudioUnitScope au_scope = kAudioUnitScope_Input;
+ AudioUnitElement au_element = inElement;
+ char const * au_type = "output";
+
+ if (AU_IN_BUS == inElement) {
+ au_scope = kAudioUnitScope_Output;
+ au_type = "input";
+ }
+
+ switch (inPropertyID) {
+
+ case kAudioDevicePropertyBufferFrameSize: {
+ if (inScope != au_scope) {
+ break;
+ }
+ UInt32 new_buffer_size;
+ UInt32 outSize = sizeof(UInt32);
+ OSStatus r =
+ AudioUnitGetProperty(au, kAudioDevicePropertyBufferFrameSize, au_scope,
+ au_element, &new_buffer_size, &outSize);
+ if (r != noErr) {
+ LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: Cannot get current "
+ "buffer size",
+ stm);
+ } else {
+ LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: New %s buffer size "
+ "= %d for scope %d",
+ stm, au_type, new_buffer_size, inScope);
+ }
+ stm->buffer_size_change_state = true;
+ break;
+ }
+ }
+}
+
+static int
+audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames,
+ io_side side)
+{
+ AudioUnit au = stm->output_unit;
+ AudioUnitScope au_scope = kAudioUnitScope_Input;
+ AudioUnitElement au_element = AU_OUT_BUS;
+
+ if (side == io_side::INPUT) {
+ au = stm->input_unit;
+ au_scope = kAudioUnitScope_Output;
+ au_element = AU_IN_BUS;
+ }
+
+ uint32_t buffer_frames = 0;
+ UInt32 size = sizeof(buffer_frames);
+ int r = AudioUnitGetProperty(au, kAudioDevicePropertyBufferFrameSize,
+ au_scope, au_element, &buffer_frames, &size);
+ if (r != noErr) {
+ LOG("AudioUnitGetProperty/%s/kAudioDevicePropertyBufferFrameSize rv=%d",
+ to_string(side), r);
+ return CUBEB_ERROR;
+ }
+
+ if (new_size_frames == buffer_frames) {
+ LOG("(%p) No need to update %s buffer size already %u frames", stm,
+ to_string(side), buffer_frames);
+ return CUBEB_OK;
+ }
+
+ r = AudioUnitAddPropertyListener(au, kAudioDevicePropertyBufferFrameSize,
+ buffer_size_changed_callback, stm);
+ if (r != noErr) {
+ LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize "
+ "rv=%d",
+ to_string(side), r);
+ return CUBEB_ERROR;
+ }
+
+ stm->buffer_size_change_state = false;
+
+ r = AudioUnitSetProperty(au, kAudioDevicePropertyBufferFrameSize, au_scope,
+ au_element, &new_size_frames,
+ sizeof(new_size_frames));
+ if (r != noErr) {
+ LOG("AudioUnitSetProperty/%s/kAudioDevicePropertyBufferFrameSize rv=%d",
+ to_string(side), r);
+
+ r = AudioUnitRemovePropertyListenerWithUserData(
+ au, kAudioDevicePropertyBufferFrameSize, buffer_size_changed_callback,
+ stm);
+ if (r != noErr) {
+ LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize "
+ "rv=%d",
+ to_string(side), r);
+ }
+
+ return CUBEB_ERROR;
+ }
+
+ int count = 0;
+ while (!stm->buffer_size_change_state && count++ < 30) {
+ struct timespec req, rem;
+ req.tv_sec = 0;
+ req.tv_nsec = 100000000L; // 0.1 sec
+ if (nanosleep(&req, &rem) < 0) {
+ LOG("(%p) Warning: nanosleep call failed or interrupted. Remaining time "
+ "%ld nano secs \n",
+ stm, rem.tv_nsec);
+ }
+ LOG("(%p) audiounit_set_buffer_size : wait count = %d", stm, count);
+ }
+
+ r = AudioUnitRemovePropertyListenerWithUserData(
+ au, kAudioDevicePropertyBufferFrameSize, buffer_size_changed_callback,
+ stm);
+ if (r != noErr) {
+ LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize "
+ "rv=%d",
+ to_string(side), r);
+ return CUBEB_ERROR;
+ }
+
+ if (!stm->buffer_size_change_state && count >= 30) {
+ LOG("(%p) Error, did not get buffer size change callback ...", stm);
+ return CUBEB_ERROR;
+ }
+
+ LOG("(%p) %s buffer size changed to %u frames.", stm, to_string(side),
+ new_size_frames);
+ return CUBEB_OK;
+}
+
+static int
+audiounit_configure_input(cubeb_stream * stm)
+{
+ assert(stm && stm->input_unit);
+
+ int r = 0;
+ UInt32 size;
+ AURenderCallbackStruct aurcbs_in;
+
+ LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in "
+ "frames %u.",
+ stm, stm->input_stream_params.rate, stm->input_stream_params.channels,
+ stm->input_stream_params.format, stm->latency_frames);
+
+ /* Get input device sample rate. */
+ AudioStreamBasicDescription input_hw_desc;
+ size = sizeof(AudioStreamBasicDescription);
+ r = AudioUnitGetProperty(stm->input_unit, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input, AU_IN_BUS, &input_hw_desc,
+ &size);
+ if (r != noErr) {
+ LOG("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat rv=%d", r);
+ return CUBEB_ERROR;
+ }
+ stm->input_hw_rate = input_hw_desc.mSampleRate;
+ LOG("(%p) Input device sampling rate: %.2f", stm, stm->input_hw_rate);
+
+ /* Set format description according to the input params. */
+ r = audio_stream_desc_init(&stm->input_desc, &stm->input_stream_params);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Setting format description for input failed.", stm);
+ return r;
+ }
+
+ // Use latency to set buffer size
+ r = audiounit_set_buffer_size(stm, stm->latency_frames, io_side::INPUT);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Error in change input buffer size.", stm);
+ return CUBEB_ERROR;
+ }
+
+ AudioStreamBasicDescription src_desc = stm->input_desc;
+ /* Input AudioUnit must be configured with device's sample rate.
+ we will resample inside input callback. */
+ src_desc.mSampleRate = stm->input_hw_rate;
+
+ r = AudioUnitSetProperty(stm->input_unit, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Output, AU_IN_BUS, &src_desc,
+ sizeof(AudioStreamBasicDescription));
+ if (r != noErr) {
+ LOG("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat rv=%d", r);
+ return CUBEB_ERROR;
+ }
+
+ /* Frames per buffer in the input callback. */
+ r = AudioUnitSetProperty(
+ stm->input_unit, kAudioUnitProperty_MaximumFramesPerSlice,
+ kAudioUnitScope_Global, AU_IN_BUS, &stm->latency_frames, sizeof(UInt32));
+ if (r != noErr) {
+ LOG("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice "
+ "rv=%d",
+ r);
+ return CUBEB_ERROR;
+ }
+
+ // Input only capacity
+ unsigned int array_capacity = 1;
+ if (has_output(stm)) {
+ // Full-duplex increase capacity
+ array_capacity = 8;
+ }
+ if (audiounit_init_input_linear_buffer(stm, array_capacity) != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+
+ aurcbs_in.inputProc = audiounit_input_callback;
+ aurcbs_in.inputProcRefCon = stm;
+
+ r = AudioUnitSetProperty(
+ stm->input_unit, kAudioOutputUnitProperty_SetInputCallback,
+ kAudioUnitScope_Global, AU_OUT_BUS, &aurcbs_in, sizeof(aurcbs_in));
+ if (r != noErr) {
+ LOG("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback "
+ "rv=%d",
+ r);
+ return CUBEB_ERROR;
+ }
+
+ stm->frames_read = 0;
+
+ LOG("(%p) Input audiounit init successfully.", stm);
+
+ return CUBEB_OK;
+}
+
+static int
+audiounit_configure_output(cubeb_stream * stm)
+{
+ assert(stm && stm->output_unit);
+
+ int r;
+ AURenderCallbackStruct aurcbs_out;
+ UInt32 size;
+
+ LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in "
+ "frames %u.",
+ stm, stm->output_stream_params.rate, stm->output_stream_params.channels,
+ stm->output_stream_params.format, stm->latency_frames);
+
+ r = audio_stream_desc_init(&stm->output_desc, &stm->output_stream_params);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Could not initialize the audio stream description.", stm);
+ return r;
+ }
+
+ /* Get output device sample rate. */
+ AudioStreamBasicDescription output_hw_desc;
+ size = sizeof(AudioStreamBasicDescription);
+ memset(&output_hw_desc, 0, size);
+ r = AudioUnitGetProperty(stm->output_unit, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Output, AU_OUT_BUS, &output_hw_desc,
+ &size);
+ if (r != noErr) {
+ LOG("AudioUnitGetProperty/output/kAudioUnitProperty_StreamFormat rv=%d", r);
+ return CUBEB_ERROR;
+ }
+ stm->output_hw_rate = output_hw_desc.mSampleRate;
+ LOG("(%p) Output device sampling rate: %.2f", stm,
+ output_hw_desc.mSampleRate);
+ stm->context->channels = output_hw_desc.mChannelsPerFrame;
+
+ // Set the input layout to match the output device layout.
+ audiounit_layout_init(stm, io_side::OUTPUT);
+ if (stm->context->channels != stm->output_stream_params.channels ||
+ stm->context->layout != stm->output_stream_params.layout) {
+ LOG("Incompatible channel layouts detected, setting up remixer");
+ audiounit_init_mixer(stm);
+ // We will be remixing the data before it reaches the output device.
+ // We need to adjust the number of channels and other
+ // AudioStreamDescription details.
+ stm->output_desc.mChannelsPerFrame = stm->context->channels;
+ stm->output_desc.mBytesPerFrame = (stm->output_desc.mBitsPerChannel / 8) *
+ stm->output_desc.mChannelsPerFrame;
+ stm->output_desc.mBytesPerPacket =
+ stm->output_desc.mBytesPerFrame * stm->output_desc.mFramesPerPacket;
+ } else {
+ stm->mixer = nullptr;
+ }
+
+ r = AudioUnitSetProperty(stm->output_unit, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input, AU_OUT_BUS, &stm->output_desc,
+ sizeof(AudioStreamBasicDescription));
+ if (r != noErr) {
+ LOG("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat rv=%d", r);
+ return CUBEB_ERROR;
+ }
+
+ r = audiounit_set_buffer_size(stm, stm->latency_frames, io_side::OUTPUT);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Error in change output buffer size.", stm);
+ return CUBEB_ERROR;
+ }
+
+ /* Frames per buffer in the input callback. */
+ r = AudioUnitSetProperty(
+ stm->output_unit, kAudioUnitProperty_MaximumFramesPerSlice,
+ kAudioUnitScope_Global, AU_OUT_BUS, &stm->latency_frames, sizeof(UInt32));
+ if (r != noErr) {
+ LOG("AudioUnitSetProperty/output/kAudioUnitProperty_MaximumFramesPerSlice "
+ "rv=%d",
+ r);
+ return CUBEB_ERROR;
+ }
+
+ aurcbs_out.inputProc = audiounit_output_callback;
+ aurcbs_out.inputProcRefCon = stm;
+ r = AudioUnitSetProperty(
+ stm->output_unit, kAudioUnitProperty_SetRenderCallback,
+ kAudioUnitScope_Global, AU_OUT_BUS, &aurcbs_out, sizeof(aurcbs_out));
+ if (r != noErr) {
+ LOG("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback "
+ "rv=%d",
+ r);
+ return CUBEB_ERROR;
+ }
+
+ stm->frames_written = 0;
+
+ LOG("(%p) Output audiounit init successfully.", stm);
+ return CUBEB_OK;
+}
+
+static int
+audiounit_setup_stream(cubeb_stream * stm)
+{
+ stm->mutex.assert_current_thread_owns();
+
+ if ((stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) ||
+ (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK)) {
+ LOG("(%p) Loopback not supported for audiounit.", stm);
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ int r = 0;
+
+ device_info in_dev_info = stm->input_device;
+ device_info out_dev_info = stm->output_device;
+
+ if (has_input(stm) && has_output(stm) &&
+ stm->input_device.id != stm->output_device.id) {
+ r = audiounit_create_aggregate_device(stm);
+ if (r != CUBEB_OK) {
+ stm->aggregate_device_id = kAudioObjectUnknown;
+ LOG("(%p) Create aggregate devices failed.", stm);
+ // !!!NOTE: It is not necessary to return here. If it does not
+ // return it will fallback to the old implementation. The intention
+ // is to investigate how often it fails. I plan to remove
+ // it after a couple of weeks.
+ return r;
+ } else {
+ in_dev_info.id = out_dev_info.id = stm->aggregate_device_id;
+ in_dev_info.flags = DEV_INPUT;
+ out_dev_info.flags = DEV_OUTPUT;
+ }
+ }
+
+ if (has_input(stm)) {
+ r = audiounit_create_unit(&stm->input_unit, &in_dev_info);
+ if (r != CUBEB_OK) {
+ LOG("(%p) AudioUnit creation for input failed.", stm);
+ return r;
+ }
+ }
+
+ if (has_output(stm)) {
+ r = audiounit_create_unit(&stm->output_unit, &out_dev_info);
+ if (r != CUBEB_OK) {
+ LOG("(%p) AudioUnit creation for output failed.", stm);
+ return r;
+ }
+ }
+
+ /* Latency cannot change if another stream is operating in parallel. In this
+ * case latency is set to the other stream value. */
+ if (audiounit_active_streams(stm->context) > 1) {
+ LOG("(%p) More than one active stream, use global latency.", stm);
+ stm->latency_frames = stm->context->global_latency_frames;
+ } else {
+ /* Silently clamp the latency down to the platform default, because we
+ * synthetize the clock from the callbacks, and we want the clock to update
+ * often. */
+ stm->latency_frames = audiounit_clamp_latency(stm, stm->latency_frames);
+ assert(stm->latency_frames); // Ugly error check
+ audiounit_set_global_latency(stm->context, stm->latency_frames);
+ }
+
+ /* Configure I/O stream */
+ if (has_input(stm)) {
+ r = audiounit_configure_input(stm);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Configure audiounit input failed.", stm);
+ return r;
+ }
+ }
+
+ if (has_output(stm)) {
+ r = audiounit_configure_output(stm);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Configure audiounit output failed.", stm);
+ return r;
+ }
+ }
+
+ // Setting the latency doesn't work well for USB headsets (eg. plantronics).
+ // Keep the default latency for now.
+#if 0
+ buffer_size = latency;
+
+ /* Get the range of latency this particular device can work with, and clamp
+ * the requested latency to this acceptable range. */
+#if !TARGET_OS_IPHONE
+ if (audiounit_get_acceptable_latency_range(&latency_range) != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+
+ if (buffer_size < (unsigned int) latency_range.mMinimum) {
+ buffer_size = (unsigned int) latency_range.mMinimum;
+ } else if (buffer_size > (unsigned int) latency_range.mMaximum) {
+ buffer_size = (unsigned int) latency_range.mMaximum;
+ }
+
+ /**
+ * Get the default buffer size. If our latency request is below the default,
+ * set it. Otherwise, use the default latency.
+ **/
+ size = sizeof(default_buffer_size);
+ if (AudioUnitGetProperty(stm->output_unit, kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Output, 0, &default_buffer_size, &size) != 0) {
+ return CUBEB_ERROR;
+ }
+
+ if (buffer_size < default_buffer_size) {
+ /* Set the maximum number of frame that the render callback will ask for,
+ * effectively setting the latency of the stream. This is process-wide. */
+ if (AudioUnitSetProperty(stm->output_unit, kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Output, 0, &buffer_size, sizeof(buffer_size)) != 0) {
+ return CUBEB_ERROR;
+ }
+ }
+#else // TARGET_OS_IPHONE
+ //TODO: [[AVAudioSession sharedInstance] inputLatency]
+ // http://stackoverflow.com/questions/13157523/kaudiodevicepropertybufferframesize-replacement-for-ios
+#endif
+#endif
+
+ /* We use a resampler because input AudioUnit operates
+ * reliable only in the capture device sample rate.
+ * Resampler will convert it to the user sample rate
+ * and deliver it to the callback. */
+ uint32_t target_sample_rate;
+ if (has_input(stm)) {
+ target_sample_rate = stm->input_stream_params.rate;
+ } else {
+ assert(has_output(stm));
+ target_sample_rate = stm->output_stream_params.rate;
+ }
+
+ cubeb_stream_params input_unconverted_params;
+ if (has_input(stm)) {
+ input_unconverted_params = stm->input_stream_params;
+ /* Use the rate of the input device. */
+ input_unconverted_params.rate = stm->input_hw_rate;
+ }
+
+ /* Create resampler. Output params are unchanged
+ * because we do not need conversion on the output. */
+ stm->resampler.reset(cubeb_resampler_create(
+ stm, has_input(stm) ? &input_unconverted_params : NULL,
+ has_output(stm) ? &stm->output_stream_params : NULL, target_sample_rate,
+ stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP,
+ CUBEB_RESAMPLER_RECLOCK_NONE));
+ if (!stm->resampler) {
+ LOG("(%p) Could not create resampler.", stm);
+ return CUBEB_ERROR;
+ }
+
+ if (stm->input_unit != NULL) {
+ r = AudioUnitInitialize(stm->input_unit);
+ if (r != noErr) {
+ LOG("AudioUnitInitialize/input rv=%d", r);
+ return CUBEB_ERROR;
+ }
+ }
+
+ if (stm->output_unit != NULL) {
+ r = AudioUnitInitialize(stm->output_unit);
+ if (r != noErr) {
+ LOG("AudioUnitInitialize/output rv=%d", r);
+ return CUBEB_ERROR;
+ }
+
+ stm->current_latency_frames = audiounit_get_device_presentation_latency(
+ stm->output_device.id, kAudioDevicePropertyScopeOutput);
+
+ Float64 unit_s;
+ UInt32 size = sizeof(unit_s);
+ if (AudioUnitGetProperty(stm->output_unit, kAudioUnitProperty_Latency,
+ kAudioUnitScope_Global, 0, &unit_s,
+ &size) == noErr) {
+ stm->current_latency_frames +=
+ static_cast<uint32_t>(unit_s * stm->output_desc.mSampleRate);
+ }
+ }
+
+ if (stm->input_unit && stm->output_unit) {
+ // According to the I/O hardware rate it is expected a specific pattern of
+ // callbacks for example is input is 44100 and output is 48000 we expected
+ // no more than 2 out callback in a row.
+ stm->expected_output_callbacks_in_a_row =
+ ceilf(stm->output_hw_rate / stm->input_hw_rate);
+ }
+
+ r = audiounit_install_device_changed_callback(stm);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Could not install all device change callback.", stm);
+ }
+
+ return CUBEB_OK;
+}
+
+cubeb_stream::cubeb_stream(cubeb * context)
+ : context(context), resampler(nullptr, cubeb_resampler_destroy),
+ mixer(nullptr, cubeb_mixer_destroy)
+{
+ PodZero(&input_desc, 1);
+ PodZero(&output_desc, 1);
+}
+
+static void
+audiounit_stream_destroy_internal(cubeb_stream * stm);
+
+static int
+audiounit_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * /* stream_name */, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ assert(context);
+ auto_lock context_lock(context->mutex);
+ audiounit_increment_active_streams(context);
+ unique_ptr<cubeb_stream, decltype(&audiounit_stream_destroy)> stm(
+ new cubeb_stream(context), audiounit_stream_destroy_internal);
+ int r;
+ *stream = NULL;
+ assert(latency_frames > 0);
+
+ /* These could be different in the future if we have both
+ * full-duplex stream and different devices for input vs output. */
+ stm->data_callback = data_callback;
+ stm->state_callback = state_callback;
+ stm->user_ptr = user_ptr;
+ stm->latency_frames = latency_frames;
+
+ if ((input_device && !input_stream_params) ||
+ (output_device && !output_stream_params)) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+ if (input_stream_params) {
+ stm->input_stream_params = *input_stream_params;
+ r = audiounit_set_device_info(
+ stm.get(), reinterpret_cast<uintptr_t>(input_device), io_side::INPUT);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Fail to set device info for input.", stm.get());
+ return r;
+ }
+ }
+ if (output_stream_params) {
+ stm->output_stream_params = *output_stream_params;
+ r = audiounit_set_device_info(
+ stm.get(), reinterpret_cast<uintptr_t>(output_device), io_side::OUTPUT);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Fail to set device info for output.", stm.get());
+ return r;
+ }
+ }
+
+ {
+ // It's not critical to lock here, because no other thread has been started
+ // yet, but it allows to assert that the lock has been taken in
+ // `audiounit_setup_stream`.
+ auto_lock lock(stm->mutex);
+ r = audiounit_setup_stream(stm.get());
+ }
+
+ if (r != CUBEB_OK) {
+ LOG("(%p) Could not setup the audiounit stream.", stm.get());
+ return r;
+ }
+
+ r = audiounit_install_system_changed_callback(stm.get());
+ if (r != CUBEB_OK) {
+ LOG("(%p) Could not install the device change callback.", stm.get());
+ return r;
+ }
+
+ *stream = stm.release();
+ LOG("(%p) Cubeb stream init successful.", *stream);
+ return CUBEB_OK;
+}
+
+static void
+audiounit_close_stream(cubeb_stream * stm)
+{
+ stm->mutex.assert_current_thread_owns();
+
+ if (stm->input_unit) {
+ AudioUnitUninitialize(stm->input_unit);
+ AudioComponentInstanceDispose(stm->input_unit);
+ stm->input_unit = nullptr;
+ }
+
+ stm->input_linear_buffer.reset();
+
+ if (stm->output_unit) {
+ AudioUnitUninitialize(stm->output_unit);
+ AudioComponentInstanceDispose(stm->output_unit);
+ stm->output_unit = nullptr;
+ }
+
+ stm->resampler.reset();
+ stm->mixer.reset();
+
+ if (stm->aggregate_device_id != kAudioObjectUnknown) {
+ audiounit_destroy_aggregate_device(stm->plugin_id,
+ &stm->aggregate_device_id);
+ stm->aggregate_device_id = kAudioObjectUnknown;
+ }
+}
+
+static void
+audiounit_stream_destroy_internal(cubeb_stream * stm)
+{
+ stm->context->mutex.assert_current_thread_owns();
+
+ int r = audiounit_uninstall_system_changed_callback(stm);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Could not uninstall the device changed callback", stm);
+ }
+ r = audiounit_uninstall_device_changed_callback(stm);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Could not uninstall all device change listeners", stm);
+ }
+
+ auto_lock lock(stm->mutex);
+ audiounit_close_stream(stm);
+ assert(audiounit_active_streams(stm->context) >= 1);
+ audiounit_decrement_active_streams(stm->context);
+}
+
+static void
+audiounit_stream_destroy(cubeb_stream * stm)
+{
+ int r = audiounit_uninstall_system_changed_callback(stm);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Could not uninstall the device changed callback", stm);
+ }
+ r = audiounit_uninstall_device_changed_callback(stm);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Could not uninstall all device change listeners", stm);
+ }
+
+ if (!stm->shutdown.load()) {
+ auto_lock context_lock(stm->context->mutex);
+ audiounit_stream_stop_internal(stm);
+ stm->shutdown = true;
+ }
+
+ stm->destroy_pending = true;
+ // Execute close in serial queue to avoid collision
+ // with reinit when un/plug devices
+ dispatch_sync(stm->context->serial_queue, ^() {
+ auto_lock context_lock(stm->context->mutex);
+ audiounit_stream_destroy_internal(stm);
+ });
+
+ LOG("Cubeb stream (%p) destroyed successful.", stm);
+ delete stm;
+}
+
+static int
+audiounit_stream_start_internal(cubeb_stream * stm)
+{
+ OSStatus r;
+ if (stm->input_unit != NULL) {
+ r = AudioOutputUnitStart(stm->input_unit);
+ if (r != noErr) {
+ LOG("AudioOutputUnitStart (input) rv=%d", r);
+ return CUBEB_ERROR;
+ }
+ }
+ if (stm->output_unit != NULL) {
+ r = AudioOutputUnitStart(stm->output_unit);
+ if (r != noErr) {
+ LOG("AudioOutputUnitStart (output) rv=%d", r);
+ return CUBEB_ERROR;
+ }
+ }
+ return CUBEB_OK;
+}
+
+static int
+audiounit_stream_start(cubeb_stream * stm)
+{
+ auto_lock context_lock(stm->context->mutex);
+ stm->shutdown = false;
+ stm->draining = false;
+
+ int r = audiounit_stream_start_internal(stm);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
+
+ LOG("Cubeb stream (%p) started successfully.", stm);
+ return CUBEB_OK;
+}
+
+void
+audiounit_stream_stop_internal(cubeb_stream * stm)
+{
+ OSStatus r;
+ if (stm->input_unit != NULL) {
+ r = AudioOutputUnitStop(stm->input_unit);
+ assert(r == 0);
+ }
+ if (stm->output_unit != NULL) {
+ r = AudioOutputUnitStop(stm->output_unit);
+ assert(r == 0);
+ }
+}
+
+static int
+audiounit_stream_stop(cubeb_stream * stm)
+{
+ auto_lock context_lock(stm->context->mutex);
+ stm->shutdown = true;
+
+ audiounit_stream_stop_internal(stm);
+
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
+
+ LOG("Cubeb stream (%p) stopped successfully.", stm);
+ return CUBEB_OK;
+}
+
+static int
+audiounit_stream_get_position(cubeb_stream * stm, uint64_t * position)
+{
+ assert(stm);
+ if (stm->current_latency_frames > stm->frames_played) {
+ *position = 0;
+ } else {
+ *position = stm->frames_played - stm->current_latency_frames;
+ }
+ return CUBEB_OK;
+}
+
+int
+audiounit_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
+{
+#if TARGET_OS_IPHONE
+ // TODO
+ return CUBEB_ERROR_NOT_SUPPORTED;
+#else
+ *latency = stm->total_output_latency_frames;
+ return CUBEB_OK;
+#endif
+}
+
+static int
+audiounit_stream_get_volume(cubeb_stream * stm, float * volume)
+{
+ assert(stm->output_unit);
+ OSStatus r = AudioUnitGetParameter(stm->output_unit, kHALOutputParam_Volume,
+ kAudioUnitScope_Global, 0, volume);
+ if (r != noErr) {
+ LOG("AudioUnitGetParameter/kHALOutputParam_Volume rv=%d", r);
+ return CUBEB_ERROR;
+ }
+ return CUBEB_OK;
+}
+
+static int
+audiounit_stream_set_volume(cubeb_stream * stm, float volume)
+{
+ assert(stm->output_unit);
+ OSStatus r;
+ r = AudioUnitSetParameter(stm->output_unit, kHALOutputParam_Volume,
+ kAudioUnitScope_Global, 0, volume, 0);
+
+ if (r != noErr) {
+ LOG("AudioUnitSetParameter/kHALOutputParam_Volume rv=%d", r);
+ return CUBEB_ERROR;
+ }
+ return CUBEB_OK;
+}
+
+unique_ptr<char[]>
+convert_uint32_into_string(UInt32 data)
+{
+ // Simply create an empty string if no data.
+ size_t size = data == 0 ? 0 : 4; // 4 bytes for uint32.
+ auto str = unique_ptr<char[]>{new char[size + 1]}; // + 1 for '\0'.
+ str[size] = '\0';
+ if (size < 4) {
+ return str;
+ }
+
+ // Reverse 0xWXYZ into 0xZYXW.
+ str[0] = (char)(data >> 24);
+ str[1] = (char)(data >> 16);
+ str[2] = (char)(data >> 8);
+ str[3] = (char)(data);
+ return str;
+}
+
+int
+audiounit_get_default_device_datasource(cubeb_device_type type, UInt32 * data)
+{
+ AudioDeviceID id = audiounit_get_default_device_id(type);
+ if (id == kAudioObjectUnknown) {
+ return CUBEB_ERROR;
+ }
+
+ UInt32 size = sizeof(*data);
+ /* This fails with some USB headsets (e.g., Plantronic .Audio 628). */
+ OSStatus r = AudioObjectGetPropertyData(
+ id,
+ type == CUBEB_DEVICE_TYPE_INPUT ? &INPUT_DATA_SOURCE_PROPERTY_ADDRESS
+ : &OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS,
+ 0, NULL, &size, data);
+ if (r != noErr) {
+ *data = 0;
+ }
+
+ return CUBEB_OK;
+}
+
+int
+audiounit_get_default_device_name(cubeb_stream * stm,
+ cubeb_device * const device,
+ cubeb_device_type type)
+{
+ assert(stm);
+ assert(device);
+
+ UInt32 data;
+ int r = audiounit_get_default_device_datasource(type, &data);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+ char ** name = type == CUBEB_DEVICE_TYPE_INPUT ? &device->input_name
+ : &device->output_name;
+ *name = convert_uint32_into_string(data).release();
+ if (!strlen(*name)) { // empty string.
+ LOG("(%p) name of %s device is empty!", stm,
+ type == CUBEB_DEVICE_TYPE_INPUT ? "input" : "output");
+ }
+ return CUBEB_OK;
+}
+
+int
+audiounit_stream_get_current_device(cubeb_stream * stm,
+ cubeb_device ** const device)
+{
+#if TARGET_OS_IPHONE
+ // TODO
+ return CUBEB_ERROR_NOT_SUPPORTED;
+#else
+ *device = new cubeb_device;
+ if (!*device) {
+ return CUBEB_ERROR;
+ }
+ PodZero(*device, 1);
+
+ int r =
+ audiounit_get_default_device_name(stm, *device, CUBEB_DEVICE_TYPE_OUTPUT);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+
+ r = audiounit_get_default_device_name(stm, *device, CUBEB_DEVICE_TYPE_INPUT);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+
+ return CUBEB_OK;
+#endif
+}
+
+int
+audiounit_stream_device_destroy(cubeb_stream * /* stream */,
+ cubeb_device * device)
+{
+ delete[] device->output_name;
+ delete[] device->input_name;
+ delete device;
+ return CUBEB_OK;
+}
+
+int
+audiounit_stream_register_device_changed_callback(
+ cubeb_stream * stream,
+ cubeb_device_changed_callback device_changed_callback)
+{
+ auto_lock dev_cb_lock(stream->device_changed_callback_lock);
+ /* Note: second register without unregister first causes 'nope' error.
+ * Current implementation requires unregister before register a new cb. */
+ assert(!device_changed_callback || !stream->device_changed_callback);
+ stream->device_changed_callback = device_changed_callback;
+ return CUBEB_OK;
+}
+
+static char *
+audiounit_strref_to_cstr_utf8(CFStringRef strref)
+{
+ CFIndex len, size;
+ char * ret;
+ if (strref == NULL) {
+ return NULL;
+ }
+
+ len = CFStringGetLength(strref);
+ // Add 1 to size to allow for '\0' termination character.
+ size = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8) + 1;
+ ret = new char[size];
+
+ if (!CFStringGetCString(strref, ret, size, kCFStringEncodingUTF8)) {
+ delete[] ret;
+ ret = NULL;
+ }
+
+ return ret;
+}
+
+static uint32_t
+audiounit_get_channel_count(AudioObjectID devid, AudioObjectPropertyScope scope)
+{
+ AudioObjectPropertyAddress adr = {0, scope,
+ kAudioObjectPropertyElementMaster};
+ UInt32 size = 0;
+ uint32_t i, ret = 0;
+
+ adr.mSelector = kAudioDevicePropertyStreamConfiguration;
+
+ if (AudioObjectGetPropertyDataSize(devid, &adr, 0, NULL, &size) == noErr &&
+ size > 0) {
+ AudioBufferList * list = static_cast<AudioBufferList *>(alloca(size));
+ if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, list) ==
+ noErr) {
+ for (i = 0; i < list->mNumberBuffers; i++)
+ ret += list->mBuffers[i].mNumberChannels;
+ }
+ }
+
+ return ret;
+}
+
+static void
+audiounit_get_available_samplerate(AudioObjectID devid,
+ AudioObjectPropertyScope scope,
+ uint32_t * min, uint32_t * max,
+ uint32_t * def)
+{
+ AudioObjectPropertyAddress adr = {0, scope,
+ kAudioObjectPropertyElementMaster};
+
+ adr.mSelector = kAudioDevicePropertyNominalSampleRate;
+ if (AudioObjectHasProperty(devid, &adr)) {
+ UInt32 size = sizeof(Float64);
+ Float64 fvalue = 0.0;
+ if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &fvalue) ==
+ noErr) {
+ *def = fvalue;
+ }
+ }
+
+ adr.mSelector = kAudioDevicePropertyAvailableNominalSampleRates;
+ UInt32 size = 0;
+ AudioValueRange range;
+ if (AudioObjectHasProperty(devid, &adr) &&
+ AudioObjectGetPropertyDataSize(devid, &adr, 0, NULL, &size) == noErr) {
+ uint32_t count = size / sizeof(AudioValueRange);
+ vector<AudioValueRange> ranges(count);
+ range.mMinimum = 9999999999.0;
+ range.mMaximum = 0.0;
+ if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size,
+ ranges.data()) == noErr) {
+ for (uint32_t i = 0; i < count; i++) {
+ if (ranges[i].mMaximum > range.mMaximum)
+ range.mMaximum = ranges[i].mMaximum;
+ if (ranges[i].mMinimum < range.mMinimum)
+ range.mMinimum = ranges[i].mMinimum;
+ }
+ }
+ *max = static_cast<uint32_t>(range.mMaximum);
+ *min = static_cast<uint32_t>(range.mMinimum);
+ } else {
+ *min = *max = 0;
+ }
+}
+
+static UInt32
+audiounit_get_device_presentation_latency(AudioObjectID devid,
+ AudioObjectPropertyScope scope)
+{
+ AudioObjectPropertyAddress adr = {0, scope,
+ kAudioObjectPropertyElementMaster};
+ UInt32 size, dev, stream = 0;
+ AudioStreamID sid[1];
+
+ adr.mSelector = kAudioDevicePropertyLatency;
+ size = sizeof(UInt32);
+ if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &dev) != noErr) {
+ dev = 0;
+ }
+
+ adr.mSelector = kAudioDevicePropertyStreams;
+ size = sizeof(sid);
+ if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, sid) == noErr) {
+ adr.mSelector = kAudioStreamPropertyLatency;
+ size = sizeof(UInt32);
+ AudioObjectGetPropertyData(sid[0], &adr, 0, NULL, &size, &stream);
+ }
+
+ return dev + stream;
+}
+
+static int
+audiounit_create_device_from_hwdev(cubeb_device_info * dev_info,
+ AudioObjectID devid, cubeb_device_type type)
+{
+ AudioObjectPropertyAddress adr = {0, 0, kAudioObjectPropertyElementMaster};
+ UInt32 size;
+
+ if (type == CUBEB_DEVICE_TYPE_OUTPUT) {
+ adr.mScope = kAudioDevicePropertyScopeOutput;
+ } else if (type == CUBEB_DEVICE_TYPE_INPUT) {
+ adr.mScope = kAudioDevicePropertyScopeInput;
+ } else {
+ return CUBEB_ERROR;
+ }
+
+ UInt32 ch = audiounit_get_channel_count(devid, adr.mScope);
+ if (ch == 0) {
+ return CUBEB_ERROR;
+ }
+
+ PodZero(dev_info, 1);
+
+ CFStringRef device_id_str = nullptr;
+ size = sizeof(CFStringRef);
+ adr.mSelector = kAudioDevicePropertyDeviceUID;
+ OSStatus ret =
+ AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &device_id_str);
+ if (ret == noErr && device_id_str != NULL) {
+ dev_info->device_id = audiounit_strref_to_cstr_utf8(device_id_str);
+ static_assert(sizeof(cubeb_devid) >= sizeof(decltype(devid)),
+ "cubeb_devid can't represent devid");
+ dev_info->devid = reinterpret_cast<cubeb_devid>(devid);
+ dev_info->group_id = dev_info->device_id;
+ CFRelease(device_id_str);
+ }
+
+ CFStringRef friendly_name_str = nullptr;
+ UInt32 ds;
+ size = sizeof(UInt32);
+ adr.mSelector = kAudioDevicePropertyDataSource;
+ ret = AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &ds);
+ if (ret == noErr) {
+ AudioValueTranslation trl = {&ds, sizeof(ds), &friendly_name_str,
+ sizeof(CFStringRef)};
+ adr.mSelector = kAudioDevicePropertyDataSourceNameForIDCFString;
+ size = sizeof(AudioValueTranslation);
+ AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &trl);
+ }
+
+ // If there is no datasource for this device, fall back to the
+ // device name.
+ if (!friendly_name_str) {
+ size = sizeof(CFStringRef);
+ adr.mSelector = kAudioObjectPropertyName;
+ AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &friendly_name_str);
+ }
+
+ if (friendly_name_str) {
+ dev_info->friendly_name = audiounit_strref_to_cstr_utf8(friendly_name_str);
+ CFRelease(friendly_name_str);
+ } else {
+ // Couldn't get a datasource name nor a device name, return a
+ // valid string of length 0.
+ char * fallback_name = new char[1];
+ fallback_name[0] = '\0';
+ dev_info->friendly_name = fallback_name;
+ }
+
+ CFStringRef vendor_name_str = nullptr;
+ size = sizeof(CFStringRef);
+ adr.mSelector = kAudioObjectPropertyManufacturer;
+ ret =
+ AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &vendor_name_str);
+ if (ret == noErr && vendor_name_str != NULL) {
+ dev_info->vendor_name = audiounit_strref_to_cstr_utf8(vendor_name_str);
+ CFRelease(vendor_name_str);
+ }
+
+ dev_info->type = type;
+ dev_info->state = CUBEB_DEVICE_STATE_ENABLED;
+ dev_info->preferred = (devid == audiounit_get_default_device_id(type))
+ ? CUBEB_DEVICE_PREF_ALL
+ : CUBEB_DEVICE_PREF_NONE;
+
+ dev_info->max_channels = ch;
+ dev_info->format =
+ (cubeb_device_fmt)CUBEB_DEVICE_FMT_ALL; /* CoreAudio supports All! */
+ /* kAudioFormatFlagsAudioUnitCanonical is deprecated, prefer floating point */
+ dev_info->default_format = CUBEB_DEVICE_FMT_F32NE;
+ audiounit_get_available_samplerate(devid, adr.mScope, &dev_info->min_rate,
+ &dev_info->max_rate,
+ &dev_info->default_rate);
+
+ UInt32 latency = audiounit_get_device_presentation_latency(devid, adr.mScope);
+
+ AudioValueRange range;
+ adr.mSelector = kAudioDevicePropertyBufferFrameSizeRange;
+ size = sizeof(AudioValueRange);
+ ret = AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &range);
+ if (ret == noErr) {
+ dev_info->latency_lo = latency + range.mMinimum;
+ dev_info->latency_hi = latency + range.mMaximum;
+ } else {
+ dev_info->latency_lo =
+ 10 * dev_info->default_rate / 1000; /* Default to 10ms */
+ dev_info->latency_hi =
+ 100 * dev_info->default_rate / 1000; /* Default to 100ms */
+ }
+
+ return CUBEB_OK;
+}
+
+bool
+is_aggregate_device(cubeb_device_info * device_info)
+{
+ assert(device_info->friendly_name);
+ return !strncmp(device_info->friendly_name, PRIVATE_AGGREGATE_DEVICE_NAME,
+ strlen(PRIVATE_AGGREGATE_DEVICE_NAME));
+}
+
+static int
+audiounit_enumerate_devices(cubeb * /* context */, cubeb_device_type type,
+ cubeb_device_collection * collection)
+{
+ vector<AudioObjectID> input_devs;
+ vector<AudioObjectID> output_devs;
+
+ // Count number of input and output devices. This is not
+ // necessarily the same as the count of raw devices supported by the
+ // system since, for example, with Soundflower installed, some
+ // devices may report as being both input *and* output and cubeb
+ // separates those into two different devices.
+
+ if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
+ output_devs = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT);
+ }
+
+ if (type & CUBEB_DEVICE_TYPE_INPUT) {
+ input_devs = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT);
+ }
+
+ auto devices = new cubeb_device_info[output_devs.size() + input_devs.size()];
+ collection->count = 0;
+
+ if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
+ for (auto dev : output_devs) {
+ auto device = &devices[collection->count];
+ auto err = audiounit_create_device_from_hwdev(device, dev,
+ CUBEB_DEVICE_TYPE_OUTPUT);
+ if (err != CUBEB_OK || is_aggregate_device(device)) {
+ continue;
+ }
+ collection->count += 1;
+ }
+ }
+
+ if (type & CUBEB_DEVICE_TYPE_INPUT) {
+ for (auto dev : input_devs) {
+ auto device = &devices[collection->count];
+ auto err = audiounit_create_device_from_hwdev(device, dev,
+ CUBEB_DEVICE_TYPE_INPUT);
+ if (err != CUBEB_OK || is_aggregate_device(device)) {
+ continue;
+ }
+ collection->count += 1;
+ }
+ }
+
+ if (collection->count > 0) {
+ collection->device = devices;
+ } else {
+ delete[] devices;
+ collection->device = NULL;
+ }
+
+ return CUBEB_OK;
+}
+
+static void
+audiounit_device_destroy(cubeb_device_info * device)
+{
+ delete[] device->device_id;
+ delete[] device->friendly_name;
+ delete[] device->vendor_name;
+}
+
+static int
+audiounit_device_collection_destroy(cubeb * /* context */,
+ cubeb_device_collection * collection)
+{
+ for (size_t i = 0; i < collection->count; i++) {
+ audiounit_device_destroy(&collection->device[i]);
+ }
+ delete[] collection->device;
+
+ return CUBEB_OK;
+}
+
+static vector<AudioObjectID>
+audiounit_get_devices_of_type(cubeb_device_type devtype)
+{
+ UInt32 size = 0;
+ OSStatus ret = AudioObjectGetPropertyDataSize(
+ kAudioObjectSystemObject, &DEVICES_PROPERTY_ADDRESS, 0, NULL, &size);
+ if (ret != noErr) {
+ return vector<AudioObjectID>();
+ }
+ vector<AudioObjectID> devices(size / sizeof(AudioObjectID));
+ ret = AudioObjectGetPropertyData(kAudioObjectSystemObject,
+ &DEVICES_PROPERTY_ADDRESS, 0, NULL, &size,
+ devices.data());
+ if (ret != noErr) {
+ return vector<AudioObjectID>();
+ }
+
+ // Remove the aggregate device from the list of devices (if any).
+ for (auto it = devices.begin(); it != devices.end();) {
+ CFStringRef name = get_device_name(*it);
+ if (name && CFStringFind(name, CFSTR("CubebAggregateDevice"), 0).location !=
+ kCFNotFound) {
+ it = devices.erase(it);
+ } else {
+ it++;
+ }
+ if (name) {
+ CFRelease(name);
+ }
+ }
+
+ /* Expected sorted but did not find anything in the docs. */
+ sort(devices.begin(), devices.end(),
+ [](AudioObjectID a, AudioObjectID b) { return a < b; });
+
+ if (devtype == (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) {
+ return devices;
+ }
+
+ AudioObjectPropertyScope scope = (devtype == CUBEB_DEVICE_TYPE_INPUT)
+ ? kAudioDevicePropertyScopeInput
+ : kAudioDevicePropertyScopeOutput;
+
+ vector<AudioObjectID> devices_in_scope;
+ for (uint32_t i = 0; i < devices.size(); ++i) {
+ /* For device in the given scope channel must be > 0. */
+ if (audiounit_get_channel_count(devices[i], scope) > 0) {
+ devices_in_scope.push_back(devices[i]);
+ }
+ }
+
+ return devices_in_scope;
+}
+
+static OSStatus
+audiounit_collection_changed_callback(
+ AudioObjectID /* inObjectID */, UInt32 /* inNumberAddresses */,
+ const AudioObjectPropertyAddress * /* inAddresses */, void * inClientData)
+{
+ cubeb * context = static_cast<cubeb *>(inClientData);
+
+ // This can be called from inside an AudioUnit function, dispatch to another
+ // queue.
+ dispatch_async(context->serial_queue, ^() {
+ auto_lock lock(context->mutex);
+ if (!context->input_collection_changed_callback &&
+ !context->output_collection_changed_callback) {
+ /* Listener removed while waiting in mutex, abort. */
+ return;
+ }
+ if (context->input_collection_changed_callback) {
+ vector<AudioObjectID> devices =
+ audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT);
+ /* Elements in the vector expected sorted. */
+ if (context->input_device_array != devices) {
+ context->input_device_array = devices;
+ context->input_collection_changed_callback(
+ context, context->input_collection_changed_user_ptr);
+ }
+ }
+ if (context->output_collection_changed_callback) {
+ vector<AudioObjectID> devices =
+ audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT);
+ /* Elements in the vector expected sorted. */
+ if (context->output_device_array != devices) {
+ context->output_device_array = devices;
+ context->output_collection_changed_callback(
+ context, context->output_collection_changed_user_ptr);
+ }
+ }
+ });
+ return noErr;
+}
+
+static OSStatus
+audiounit_add_device_listener(
+ cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback collection_changed_callback,
+ void * user_ptr)
+{
+ context->mutex.assert_current_thread_owns();
+ assert(devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT));
+ /* Note: second register without unregister first causes 'nope' error.
+ * Current implementation requires unregister before register a new cb. */
+ assert((devtype & CUBEB_DEVICE_TYPE_INPUT) &&
+ !context->input_collection_changed_callback ||
+ (devtype & CUBEB_DEVICE_TYPE_OUTPUT) &&
+ !context->output_collection_changed_callback);
+
+ if (!context->input_collection_changed_callback &&
+ !context->output_collection_changed_callback) {
+ OSStatus ret = AudioObjectAddPropertyListener(
+ kAudioObjectSystemObject, &DEVICES_PROPERTY_ADDRESS,
+ audiounit_collection_changed_callback, context);
+ if (ret != noErr) {
+ return ret;
+ }
+ }
+ if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
+ /* Expected empty after unregister. */
+ assert(context->input_device_array.empty());
+ context->input_device_array =
+ audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT);
+ context->input_collection_changed_callback = collection_changed_callback;
+ context->input_collection_changed_user_ptr = user_ptr;
+ }
+ if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
+ /* Expected empty after unregister. */
+ assert(context->output_device_array.empty());
+ context->output_device_array =
+ audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT);
+ context->output_collection_changed_callback = collection_changed_callback;
+ context->output_collection_changed_user_ptr = user_ptr;
+ }
+ return noErr;
+}
+
+static OSStatus
+audiounit_remove_device_listener(cubeb * context, cubeb_device_type devtype)
+{
+ context->mutex.assert_current_thread_owns();
+
+ if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
+ context->input_collection_changed_callback = nullptr;
+ context->input_collection_changed_user_ptr = nullptr;
+ context->input_device_array.clear();
+ }
+ if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
+ context->output_collection_changed_callback = nullptr;
+ context->output_collection_changed_user_ptr = nullptr;
+ context->output_device_array.clear();
+ }
+
+ if (context->input_collection_changed_callback ||
+ context->output_collection_changed_callback) {
+ return noErr;
+ }
+ /* Note: unregister a non registered cb is not a problem, not checking. */
+ return AudioObjectRemovePropertyListener(
+ kAudioObjectSystemObject, &DEVICES_PROPERTY_ADDRESS,
+ audiounit_collection_changed_callback, context);
+}
+
+int
+audiounit_register_device_collection_changed(
+ cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback collection_changed_callback,
+ void * user_ptr)
+{
+ if (devtype == CUBEB_DEVICE_TYPE_UNKNOWN) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+ OSStatus ret;
+ auto_lock lock(context->mutex);
+ if (collection_changed_callback) {
+ ret = audiounit_add_device_listener(context, devtype,
+ collection_changed_callback, user_ptr);
+ } else {
+ ret = audiounit_remove_device_listener(context, devtype);
+ }
+ return (ret == noErr) ? CUBEB_OK : CUBEB_ERROR;
+}
+
+cubeb_ops const audiounit_ops = {
+ /*.init =*/audiounit_init,
+ /*.get_backend_id =*/audiounit_get_backend_id,
+ /*.get_max_channel_count =*/audiounit_get_max_channel_count,
+ /*.get_min_latency =*/audiounit_get_min_latency,
+ /*.get_preferred_sample_rate =*/audiounit_get_preferred_sample_rate,
+ /*.get_supported_input_processing_params =*/NULL,
+ /*.enumerate_devices =*/audiounit_enumerate_devices,
+ /*.device_collection_destroy =*/audiounit_device_collection_destroy,
+ /*.destroy =*/audiounit_destroy,
+ /*.stream_init =*/audiounit_stream_init,
+ /*.stream_destroy =*/audiounit_stream_destroy,
+ /*.stream_start =*/audiounit_stream_start,
+ /*.stream_stop =*/audiounit_stream_stop,
+ /*.stream_get_position =*/audiounit_stream_get_position,
+ /*.stream_get_latency =*/audiounit_stream_get_latency,
+ /*.stream_get_input_latency =*/NULL,
+ /*.stream_set_volume =*/audiounit_stream_set_volume,
+ /*.stream_set_name =*/NULL,
+ /*.stream_get_current_device =*/audiounit_stream_get_current_device,
+ /*.stream_set_input_mute =*/NULL,
+ /*.stream_set_input_processing_params =*/NULL,
+ /*.stream_device_destroy =*/audiounit_stream_device_destroy,
+ /*.stream_register_device_changed_callback =*/
+ audiounit_stream_register_device_changed_callback,
+ /*.register_device_collection_changed =*/
+ audiounit_register_device_collection_changed};
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_jack.cpp b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_jack.cpp
new file mode 100644
index 0000000000..b417078fc3
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_jack.cpp
@@ -0,0 +1,1183 @@
+/*
+ * Copyright © 2012 David Richards
+ * Copyright © 2013 Sebastien Alaiwan
+ * Copyright © 2016 Damien Zammit
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#define _DEFAULT_SOURCE
+#define _BSD_SOURCE
+#if !defined(__FreeBSD__) && !defined(__NetBSD__)
+#define _POSIX_SOURCE
+#endif
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_resampler.h"
+#include "cubeb_utils.h"
+#include <dlfcn.h>
+#include <limits.h>
+#include <math.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <jack/jack.h>
+#include <jack/statistics.h>
+
+#ifdef DISABLE_LIBJACK_DLOPEN
+#define WRAP(x) x
+#else
+#define WRAP(x) (*api_##x)
+#define JACK_API_VISIT(X) \
+ X(jack_activate) \
+ X(jack_client_close) \
+ X(jack_client_open) \
+ X(jack_connect) \
+ X(jack_free) \
+ X(jack_get_ports) \
+ X(jack_get_sample_rate) \
+ X(jack_get_xrun_delayed_usecs) \
+ X(jack_get_buffer_size) \
+ X(jack_port_get_buffer) \
+ X(jack_port_name) \
+ X(jack_port_register) \
+ X(jack_port_unregister) \
+ X(jack_port_get_latency_range) \
+ X(jack_set_process_callback) \
+ X(jack_set_xrun_callback) \
+ X(jack_set_graph_order_callback) \
+ X(jack_set_error_function) \
+ X(jack_set_info_function)
+
+#define IMPORT_FUNC(x) static decltype(x) * api_##x;
+JACK_API_VISIT(IMPORT_FUNC);
+#undef IMPORT_FUNC
+#endif
+
+#define JACK_DEFAULT_IN "JACK capture"
+#define JACK_DEFAULT_OUT "JACK playback"
+
+static const int MAX_STREAMS = 16;
+static const int MAX_CHANNELS = 8;
+static const int FIFO_SIZE = 4096 * sizeof(float);
+
+enum devstream {
+ NONE = 0,
+ IN_ONLY,
+ OUT_ONLY,
+ DUPLEX,
+};
+
+enum cbjack_connect_ports_options {
+ CBJACK_CP_OPTIONS_NONE = 0x0,
+ CBJACK_CP_OPTIONS_SKIP_OUTPUT = 0x1,
+ CBJACK_CP_OPTIONS_SKIP_INPUT = 0x2,
+};
+
+static void
+s16ne_to_float(float * dst, const int16_t * src, size_t n)
+{
+ for (size_t i = 0; i < n; i++)
+ *(dst++) = (float)((float)*(src++) / 32767.0f);
+}
+
+static void
+float_to_s16ne(int16_t * dst, float * src, size_t n)
+{
+ for (size_t i = 0; i < n; i++) {
+ if (*src > 1.f)
+ *src = 1.f;
+ if (*src < -1.f)
+ *src = -1.f;
+ *(dst++) = (int16_t)((int16_t)(*(src++) * 32767));
+ }
+}
+
+extern "C" {
+/*static*/ int
+jack_init(cubeb ** context, char const * context_name);
+}
+static char const *
+cbjack_get_backend_id(cubeb * context);
+static int
+cbjack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels);
+static int
+cbjack_get_min_latency(cubeb * ctx, cubeb_stream_params params,
+ uint32_t * latency_frames);
+static int
+cbjack_get_latency(cubeb_stream * stm, unsigned int * latency_frames);
+static int
+cbjack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate);
+static void
+cbjack_destroy(cubeb * context);
+static void
+cbjack_interleave_capture(cubeb_stream * stream, float ** in,
+ jack_nframes_t nframes, bool format_mismatch);
+static void
+cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream,
+ short ** bufs_in, float ** bufs_out,
+ jack_nframes_t nframes);
+static void
+cbjack_deinterleave_playback_refill_float(cubeb_stream * stream,
+ float ** bufs_in, float ** bufs_out,
+ jack_nframes_t nframes);
+static int
+cbjack_stream_device_destroy(cubeb_stream * stream, cubeb_device * device);
+static int
+cbjack_stream_get_current_device(cubeb_stream * stm,
+ cubeb_device ** const device);
+static int
+cbjack_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection);
+static int
+cbjack_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection);
+static int
+cbjack_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr);
+static void
+cbjack_stream_destroy(cubeb_stream * stream);
+static int
+cbjack_stream_start(cubeb_stream * stream);
+static int
+cbjack_stream_stop(cubeb_stream * stream);
+static int
+cbjack_stream_get_position(cubeb_stream * stream, uint64_t * position);
+static int
+cbjack_stream_set_volume(cubeb_stream * stm, float volume);
+
+static struct cubeb_ops const cbjack_ops = {
+ .init = jack_init,
+ .get_backend_id = cbjack_get_backend_id,
+ .get_max_channel_count = cbjack_get_max_channel_count,
+ .get_min_latency = cbjack_get_min_latency,
+ .get_preferred_sample_rate = cbjack_get_preferred_sample_rate,
+ .get_supported_input_processing_params = NULL,
+ .enumerate_devices = cbjack_enumerate_devices,
+ .device_collection_destroy = cbjack_device_collection_destroy,
+ .destroy = cbjack_destroy,
+ .stream_init = cbjack_stream_init,
+ .stream_destroy = cbjack_stream_destroy,
+ .stream_start = cbjack_stream_start,
+ .stream_stop = cbjack_stream_stop,
+ .stream_get_position = cbjack_stream_get_position,
+ .stream_get_latency = cbjack_get_latency,
+ .stream_get_input_latency = NULL,
+ .stream_set_volume = cbjack_stream_set_volume,
+ .stream_set_name = NULL,
+ .stream_get_current_device = cbjack_stream_get_current_device,
+ .stream_set_input_mute = NULL,
+ .stream_set_input_processing_params = NULL,
+ .stream_device_destroy = cbjack_stream_device_destroy,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL};
+
+struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
+ cubeb * context;
+ void * user_ptr;
+ /**/
+
+ /**< Mutex for each stream */
+ pthread_mutex_t mutex;
+
+ bool in_use; /**< Set to false iff the stream is free */
+ bool ports_ready; /**< Set to true iff the JACK ports are ready */
+
+ cubeb_data_callback data_callback;
+ cubeb_state_callback state_callback;
+ cubeb_stream_params in_params;
+ cubeb_stream_params out_params;
+
+ cubeb_resampler * resampler;
+
+ uint64_t position;
+ bool pause;
+ float ratio;
+ enum devstream devs;
+ char stream_name[256];
+ jack_port_t * output_ports[MAX_CHANNELS];
+ jack_port_t * input_ports[MAX_CHANNELS];
+ float volume;
+};
+
+struct cubeb {
+ struct cubeb_ops const * ops;
+ void * libjack;
+
+ /**< Mutex for whole context */
+ pthread_mutex_t mutex;
+
+ /**< Audio buffers, converted to float */
+ float in_float_interleaved_buffer[FIFO_SIZE * MAX_CHANNELS];
+ float out_float_interleaved_buffer[FIFO_SIZE * MAX_CHANNELS];
+
+ /**< Audio buffer, at the sampling rate of the output */
+ float in_resampled_interleaved_buffer_float[FIFO_SIZE * MAX_CHANNELS * 3];
+ int16_t in_resampled_interleaved_buffer_s16ne[FIFO_SIZE * MAX_CHANNELS * 3];
+ float out_resampled_interleaved_buffer_float[FIFO_SIZE * MAX_CHANNELS * 3];
+ int16_t out_resampled_interleaved_buffer_s16ne[FIFO_SIZE * MAX_CHANNELS * 3];
+
+ cubeb_stream streams[MAX_STREAMS];
+ unsigned int active_streams;
+
+ cubeb_device_collection_changed_callback collection_changed_callback;
+
+ bool active;
+ unsigned int jack_sample_rate;
+ unsigned int jack_latency;
+ unsigned int jack_xruns;
+ unsigned int jack_buffer_size;
+ unsigned int fragment_size;
+ unsigned int output_bytes_per_frame;
+ jack_client_t * jack_client;
+};
+
+static int
+load_jack_lib(cubeb * context)
+{
+#ifndef DISABLE_LIBJACK_DLOPEN
+#ifdef __APPLE__
+ context->libjack = dlopen("libjack.0.dylib", RTLD_LAZY);
+ context->libjack = dlopen("/usr/local/lib/libjack.0.dylib", RTLD_LAZY);
+#elif defined(__WIN32__)
+#ifdef _WIN64
+ context->libjack = LoadLibrary("libjack64.dll");
+#else
+ context->libjack = LoadLibrary("libjack.dll");
+#endif
+#else
+ context->libjack = dlopen("libjack.so.0", RTLD_LAZY);
+ if (!context->libjack) {
+ context->libjack = dlopen("libjack.so", RTLD_LAZY);
+ }
+#endif
+ if (!context->libjack) {
+ return CUBEB_ERROR;
+ }
+
+#define LOAD(x) \
+ { \
+ api_##x = (decltype(x) *)dlsym(context->libjack, #x); \
+ if (!api_##x) { \
+ dlclose(context->libjack); \
+ return CUBEB_ERROR; \
+ } \
+ }
+
+ JACK_API_VISIT(LOAD);
+#undef LOAD
+#endif
+ return CUBEB_OK;
+}
+
+static void
+cbjack_connect_port_out(cubeb_stream * stream, const size_t out_port,
+ const char * const phys_in_port)
+{
+ const char * src_port = WRAP(jack_port_name)(stream->output_ports[out_port]);
+
+ WRAP(jack_connect)(stream->context->jack_client, src_port, phys_in_port);
+}
+
+static void
+cbjack_connect_port_in(cubeb_stream * stream, const char * const phys_out_port,
+ size_t in_port)
+{
+ const char * src_port = WRAP(jack_port_name)(stream->input_ports[in_port]);
+
+ WRAP(jack_connect)(stream->context->jack_client, phys_out_port, src_port);
+}
+
+static int
+cbjack_connect_ports(cubeb_stream * stream,
+ enum cbjack_connect_ports_options options)
+{
+ int r = CUBEB_ERROR;
+ const char ** phys_in_ports =
+ WRAP(jack_get_ports)(stream->context->jack_client, NULL, NULL,
+ JackPortIsInput | JackPortIsPhysical);
+ const char ** phys_out_ports =
+ WRAP(jack_get_ports)(stream->context->jack_client, NULL, NULL,
+ JackPortIsOutput | JackPortIsPhysical);
+
+ if (phys_in_ports == NULL || *phys_in_ports == NULL ||
+ options & CBJACK_CP_OPTIONS_SKIP_OUTPUT) {
+ goto skipplayback;
+ }
+
+ // Connect outputs to playback
+ for (unsigned int c = 0;
+ c < stream->out_params.channels && phys_in_ports[c] != NULL; c++) {
+ cbjack_connect_port_out(stream, c, phys_in_ports[c]);
+ }
+
+ // Special case playing mono source in stereo
+ if (stream->out_params.channels == 1 && phys_in_ports[1] != NULL) {
+ cbjack_connect_port_out(stream, 0, phys_in_ports[1]);
+ }
+
+ r = CUBEB_OK;
+
+skipplayback:
+ if (phys_out_ports == NULL || *phys_out_ports == NULL ||
+ options & CBJACK_CP_OPTIONS_SKIP_INPUT) {
+ goto end;
+ }
+ // Connect inputs to capture
+ for (unsigned int c = 0;
+ c < stream->in_params.channels && phys_out_ports[c] != NULL; c++) {
+ cbjack_connect_port_in(stream, phys_out_ports[c], c);
+ }
+ r = CUBEB_OK;
+end:
+ if (phys_out_ports) {
+ WRAP(jack_free)(phys_out_ports);
+ }
+ if (phys_in_ports) {
+ WRAP(jack_free)(phys_in_ports);
+ }
+ return r;
+}
+
+static int
+cbjack_xrun_callback(void * arg)
+{
+ cubeb * ctx = (cubeb *)arg;
+
+ float delay = WRAP(jack_get_xrun_delayed_usecs)(ctx->jack_client);
+ float fragments = ceilf(((delay / 1000000.0) * ctx->jack_sample_rate) /
+ ctx->jack_buffer_size);
+
+ ctx->jack_xruns += (unsigned int)fragments;
+ return 0;
+}
+
+static int
+cbjack_graph_order_callback(void * arg)
+{
+ cubeb * ctx = (cubeb *)arg;
+ int i;
+ jack_latency_range_t latency_range;
+ jack_nframes_t port_latency, max_latency = 0;
+
+ for (int j = 0; j < MAX_STREAMS; j++) {
+ cubeb_stream * stm = &ctx->streams[j];
+
+ if (!stm->in_use)
+ continue;
+ if (!stm->ports_ready)
+ continue;
+
+ for (i = 0; i < (int)stm->out_params.channels; ++i) {
+ WRAP(jack_port_get_latency_range)
+ (stm->output_ports[i], JackPlaybackLatency, &latency_range);
+ port_latency = latency_range.max;
+ if (port_latency > max_latency)
+ max_latency = port_latency;
+ }
+ /* Cap minimum latency to 128 frames */
+ if (max_latency < 128)
+ max_latency = 128;
+ }
+
+ ctx->jack_latency = max_latency;
+
+ return 0;
+}
+
+static int
+cbjack_process(jack_nframes_t nframes, void * arg)
+{
+ cubeb * ctx = (cubeb *)arg;
+ unsigned int t_jack_xruns = ctx->jack_xruns;
+ int i;
+
+ ctx->jack_xruns = 0;
+
+ for (int j = 0; j < MAX_STREAMS; j++) {
+ cubeb_stream * stm = &ctx->streams[j];
+ float * bufs_out[stm->out_params.channels];
+ float * bufs_in[stm->in_params.channels];
+
+ if (!stm->in_use)
+ continue;
+
+ // handle xruns by skipping audio that should have been played
+ stm->position += t_jack_xruns * ctx->fragment_size * stm->ratio;
+
+ if (!stm->ports_ready)
+ continue;
+
+ if (stm->devs & OUT_ONLY) {
+ // get jack output buffers
+ for (i = 0; i < (int)stm->out_params.channels; i++)
+ bufs_out[i] =
+ (float *)WRAP(jack_port_get_buffer)(stm->output_ports[i], nframes);
+ }
+ if (stm->devs & IN_ONLY) {
+ // get jack input buffers
+ for (i = 0; i < (int)stm->in_params.channels; i++)
+ bufs_in[i] =
+ (float *)WRAP(jack_port_get_buffer)(stm->input_ports[i], nframes);
+ }
+ if (stm->pause) {
+ // paused, play silence on output
+ if (stm->devs & OUT_ONLY) {
+ for (unsigned int c = 0; c < stm->out_params.channels; c++) {
+ float * buffer_out = bufs_out[c];
+ if (buffer_out) {
+ for (long f = 0; f < nframes; f++) {
+ buffer_out[f] = 0.f;
+ }
+ }
+ }
+ }
+ if (stm->devs & IN_ONLY) {
+ // paused, capture silence
+ for (unsigned int c = 0; c < stm->in_params.channels; c++) {
+ float * buffer_in = bufs_in[c];
+ if (buffer_in) {
+ for (long f = 0; f < nframes; f++) {
+ buffer_in[f] = 0.f;
+ }
+ }
+ }
+ }
+ } else {
+
+ // try to lock stream mutex
+ if (pthread_mutex_trylock(&stm->mutex) == 0) {
+
+ int16_t * in_s16ne =
+ stm->context->in_resampled_interleaved_buffer_s16ne;
+ float * in_float = stm->context->in_resampled_interleaved_buffer_float;
+
+ // unpaused, play audio
+ if (stm->devs == DUPLEX) {
+ if (stm->out_params.format == CUBEB_SAMPLE_S16NE) {
+ cbjack_interleave_capture(stm, bufs_in, nframes, true);
+ cbjack_deinterleave_playback_refill_s16ne(stm, &in_s16ne, bufs_out,
+ nframes);
+ } else if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) {
+ cbjack_interleave_capture(stm, bufs_in, nframes, false);
+ cbjack_deinterleave_playback_refill_float(stm, &in_float, bufs_out,
+ nframes);
+ }
+ } else if (stm->devs == IN_ONLY) {
+ if (stm->in_params.format == CUBEB_SAMPLE_S16NE) {
+ cbjack_interleave_capture(stm, bufs_in, nframes, true);
+ cbjack_deinterleave_playback_refill_s16ne(stm, &in_s16ne, nullptr,
+ nframes);
+ } else if (stm->in_params.format == CUBEB_SAMPLE_FLOAT32NE) {
+ cbjack_interleave_capture(stm, bufs_in, nframes, false);
+ cbjack_deinterleave_playback_refill_float(stm, &in_float, nullptr,
+ nframes);
+ }
+ } else if (stm->devs == OUT_ONLY) {
+ if (stm->out_params.format == CUBEB_SAMPLE_S16NE) {
+ cbjack_deinterleave_playback_refill_s16ne(stm, nullptr, bufs_out,
+ nframes);
+ } else if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) {
+ cbjack_deinterleave_playback_refill_float(stm, nullptr, bufs_out,
+ nframes);
+ }
+ }
+ // unlock stream mutex
+ pthread_mutex_unlock(&stm->mutex);
+
+ } else {
+ // could not lock mutex
+ // output silence
+ if (stm->devs & OUT_ONLY) {
+ for (unsigned int c = 0; c < stm->out_params.channels; c++) {
+ float * buffer_out = bufs_out[c];
+ if (buffer_out) {
+ for (long f = 0; f < nframes; f++) {
+ buffer_out[f] = 0.f;
+ }
+ }
+ }
+ }
+ if (stm->devs & IN_ONLY) {
+ // capture silence
+ for (unsigned int c = 0; c < stm->in_params.channels; c++) {
+ float * buffer_in = bufs_in[c];
+ if (buffer_in) {
+ for (long f = 0; f < nframes; f++) {
+ buffer_in[f] = 0.f;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+static void
+cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in,
+ float ** bufs_out,
+ jack_nframes_t nframes)
+{
+ float * out_interleaved_buffer = nullptr;
+
+ float * inptr = (in != NULL) ? *in : nullptr;
+ float * outptr = (bufs_out != NULL) ? *bufs_out : nullptr;
+
+ long needed_frames = (bufs_out != NULL) ? nframes : 0;
+ long done_frames = 0;
+ long input_frames_count = (in != NULL) ? nframes : 0;
+
+ done_frames = cubeb_resampler_fill(
+ stream->resampler, inptr, &input_frames_count,
+ (bufs_out != NULL)
+ ? stream->context->out_resampled_interleaved_buffer_float
+ : NULL,
+ needed_frames);
+
+ out_interleaved_buffer =
+ stream->context->out_resampled_interleaved_buffer_float;
+
+ if (outptr) {
+ // convert interleaved output buffers to contiguous buffers
+ for (unsigned int c = 0; c < stream->out_params.channels; c++) {
+ float * buffer = bufs_out[c];
+ for (long f = 0; f < done_frames; f++) {
+ if (buffer) {
+ buffer[f] =
+ out_interleaved_buffer[(f * stream->out_params.channels) + c] *
+ stream->volume;
+ }
+ }
+ if (done_frames < needed_frames) {
+ // draining
+ for (long f = done_frames; f < needed_frames; f++) {
+ if (buffer) {
+ buffer[f] = 0.f;
+ }
+ }
+ }
+ if (done_frames == 0) {
+ // stop, but first zero out the existing buffer
+ for (long f = 0; f < needed_frames; f++) {
+ if (buffer) {
+ buffer[f] = 0.f;
+ }
+ }
+ }
+ }
+ }
+
+ if (done_frames >= 0 && done_frames < needed_frames) {
+ // set drained
+ stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED);
+ // stop stream
+ cbjack_stream_stop(stream);
+ }
+ if (done_frames > 0 && done_frames <= needed_frames) {
+ // advance stream position
+ stream->position += done_frames * stream->ratio;
+ }
+ if (done_frames < 0 || done_frames > needed_frames) {
+ // stream error
+ stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_ERROR);
+ }
+}
+
+static void
+cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short ** in,
+ float ** bufs_out,
+ jack_nframes_t nframes)
+{
+ float * out_interleaved_buffer = nullptr;
+
+ short * inptr = (in != NULL) ? *in : nullptr;
+ float * outptr = (bufs_out != NULL) ? *bufs_out : nullptr;
+
+ long needed_frames = (bufs_out != NULL) ? nframes : 0;
+ long done_frames = 0;
+ long input_frames_count = (in != NULL) ? nframes : 0;
+
+ done_frames = cubeb_resampler_fill(
+ stream->resampler, inptr, &input_frames_count,
+ (bufs_out != NULL)
+ ? stream->context->out_resampled_interleaved_buffer_s16ne
+ : NULL,
+ needed_frames);
+
+ s16ne_to_float(stream->context->out_resampled_interleaved_buffer_float,
+ stream->context->out_resampled_interleaved_buffer_s16ne,
+ done_frames * stream->out_params.channels);
+
+ out_interleaved_buffer =
+ stream->context->out_resampled_interleaved_buffer_float;
+
+ if (outptr) {
+ // convert interleaved output buffers to contiguous buffers
+ for (unsigned int c = 0; c < stream->out_params.channels; c++) {
+ float * buffer = bufs_out[c];
+ for (long f = 0; f < done_frames; f++) {
+ buffer[f] =
+ out_interleaved_buffer[(f * stream->out_params.channels) + c] *
+ stream->volume;
+ }
+ if (done_frames < needed_frames) {
+ // draining
+ for (long f = done_frames; f < needed_frames; f++) {
+ buffer[f] = 0.f;
+ }
+ }
+ if (done_frames == 0) {
+ // stop, but first zero out the existing buffer
+ for (long f = 0; f < needed_frames; f++) {
+ buffer[f] = 0.f;
+ }
+ }
+ }
+ }
+
+ if (done_frames >= 0 && done_frames < needed_frames) {
+ // set drained
+ stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED);
+ // stop stream
+ cbjack_stream_stop(stream);
+ }
+ if (done_frames > 0 && done_frames <= needed_frames) {
+ // advance stream position
+ stream->position += done_frames * stream->ratio;
+ }
+ if (done_frames < 0 || done_frames > needed_frames) {
+ // stream error
+ stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_ERROR);
+ }
+}
+
+static void
+cbjack_interleave_capture(cubeb_stream * stream, float ** in,
+ jack_nframes_t nframes, bool format_mismatch)
+{
+ float * in_buffer = stream->context->in_float_interleaved_buffer;
+
+ for (unsigned int c = 0; c < stream->in_params.channels; c++) {
+ for (long f = 0; f < nframes; f++) {
+ in_buffer[(f * stream->in_params.channels) + c] =
+ in[c][f] * stream->volume;
+ }
+ }
+ if (format_mismatch) {
+ float_to_s16ne(stream->context->in_resampled_interleaved_buffer_s16ne,
+ in_buffer, nframes * stream->in_params.channels);
+ } else {
+ memset(stream->context->in_resampled_interleaved_buffer_float, 0,
+ (FIFO_SIZE * MAX_CHANNELS * 3) * sizeof(float));
+ memcpy(stream->context->in_resampled_interleaved_buffer_float, in_buffer,
+ (FIFO_SIZE * MAX_CHANNELS * 2) * sizeof(float));
+ }
+}
+
+static void
+silent_jack_error_callback(char const * /*msg*/)
+{
+}
+
+/*static*/ int
+jack_init(cubeb ** context, char const * context_name)
+{
+ int r;
+
+ *context = NULL;
+
+ cubeb * ctx = (cubeb *)calloc(1, sizeof(*ctx));
+ if (ctx == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ r = load_jack_lib(ctx);
+ if (r != 0) {
+ cbjack_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ WRAP(jack_set_error_function)(silent_jack_error_callback);
+ WRAP(jack_set_info_function)(silent_jack_error_callback);
+
+ ctx->ops = &cbjack_ops;
+
+ ctx->mutex = PTHREAD_MUTEX_INITIALIZER;
+ for (r = 0; r < MAX_STREAMS; r++) {
+ ctx->streams[r].mutex = PTHREAD_MUTEX_INITIALIZER;
+ }
+
+ const char * jack_client_name = "cubeb";
+ if (context_name)
+ jack_client_name = context_name;
+
+ ctx->jack_client =
+ WRAP(jack_client_open)(jack_client_name, JackNoStartServer, NULL);
+
+ if (ctx->jack_client == NULL) {
+ cbjack_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ ctx->jack_xruns = 0;
+
+ WRAP(jack_set_process_callback)(ctx->jack_client, cbjack_process, ctx);
+ WRAP(jack_set_xrun_callback)(ctx->jack_client, cbjack_xrun_callback, ctx);
+ WRAP(jack_set_graph_order_callback)
+ (ctx->jack_client, cbjack_graph_order_callback, ctx);
+
+ if (WRAP(jack_activate)(ctx->jack_client)) {
+ cbjack_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ ctx->jack_sample_rate = WRAP(jack_get_sample_rate)(ctx->jack_client);
+ ctx->jack_latency = 128 * 1000 / ctx->jack_sample_rate;
+
+ ctx->active = true;
+ *context = ctx;
+
+ return CUBEB_OK;
+}
+
+static char const *
+cbjack_get_backend_id(cubeb * /*context*/)
+{
+ return "jack";
+}
+
+static int
+cbjack_get_max_channel_count(cubeb * /*ctx*/, uint32_t * max_channels)
+{
+ *max_channels = MAX_CHANNELS;
+ return CUBEB_OK;
+}
+
+static int
+cbjack_get_latency(cubeb_stream * stm, unsigned int * latency_ms)
+{
+ *latency_ms = stm->context->jack_latency;
+ return CUBEB_OK;
+}
+
+static int
+cbjack_get_min_latency(cubeb * ctx, cubeb_stream_params /*params*/,
+ uint32_t * latency_ms)
+{
+ *latency_ms = ctx->jack_latency;
+ return CUBEB_OK;
+}
+
+static int
+cbjack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
+{
+ if (!ctx->jack_client) {
+ jack_client_t * testclient =
+ WRAP(jack_client_open)("test-samplerate", JackNoStartServer, NULL);
+ if (!testclient) {
+ return CUBEB_ERROR;
+ }
+
+ *rate = WRAP(jack_get_sample_rate)(testclient);
+ WRAP(jack_client_close)(testclient);
+
+ } else {
+ *rate = WRAP(jack_get_sample_rate)(ctx->jack_client);
+ }
+ return CUBEB_OK;
+}
+
+static void
+cbjack_destroy(cubeb * context)
+{
+ context->active = false;
+
+ if (context->jack_client != NULL)
+ WRAP(jack_client_close)(context->jack_client);
+#ifndef DISABLE_LIBJACK_DLOPEN
+ if (context->libjack)
+ dlclose(context->libjack);
+#endif
+ free(context);
+}
+
+static cubeb_stream *
+context_alloc_stream(cubeb * context, char const * stream_name)
+{
+ for (int i = 0; i < MAX_STREAMS; i++) {
+ if (!context->streams[i].in_use) {
+ cubeb_stream * stm = &context->streams[i];
+ stm->in_use = true;
+ snprintf(stm->stream_name, 255, "%s_%u", stream_name, i);
+ return stm;
+ }
+ }
+ return NULL;
+}
+
+static int
+cbjack_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int /*latency_frames*/,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ int stream_actual_rate = 0;
+ int jack_rate = WRAP(jack_get_sample_rate)(context->jack_client);
+
+ if (output_stream_params &&
+ (output_stream_params->format != CUBEB_SAMPLE_FLOAT32NE &&
+ output_stream_params->format != CUBEB_SAMPLE_S16NE)) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ if (input_stream_params &&
+ (input_stream_params->format != CUBEB_SAMPLE_FLOAT32NE &&
+ input_stream_params->format != CUBEB_SAMPLE_S16NE)) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ if ((input_device && input_device != JACK_DEFAULT_IN) ||
+ (output_device && output_device != JACK_DEFAULT_OUT)) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ // Loopback is unsupported
+ if ((input_stream_params &&
+ (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK)) ||
+ (output_stream_params &&
+ (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ *stream = NULL;
+
+ // Find a free stream.
+ pthread_mutex_lock(&context->mutex);
+ cubeb_stream * stm = context_alloc_stream(context, stream_name);
+
+ // No free stream?
+ if (stm == NULL) {
+ pthread_mutex_unlock(&context->mutex);
+ return CUBEB_ERROR;
+ }
+
+ // unlock context mutex
+ pthread_mutex_unlock(&context->mutex);
+
+ // Lock active stream
+ pthread_mutex_lock(&stm->mutex);
+
+ stm->ports_ready = false;
+ stm->user_ptr = user_ptr;
+ stm->context = context;
+ stm->devs = NONE;
+ if (output_stream_params && !input_stream_params) {
+ stm->out_params = *output_stream_params;
+ stream_actual_rate = stm->out_params.rate;
+ stm->out_params.rate = jack_rate;
+ stm->devs = OUT_ONLY;
+ if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) {
+ context->output_bytes_per_frame = sizeof(float);
+ } else {
+ context->output_bytes_per_frame = sizeof(short);
+ }
+ }
+ if (input_stream_params && output_stream_params) {
+ stm->in_params = *input_stream_params;
+ stm->out_params = *output_stream_params;
+ stream_actual_rate = stm->out_params.rate;
+ stm->in_params.rate = jack_rate;
+ stm->out_params.rate = jack_rate;
+ stm->devs = DUPLEX;
+ if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) {
+ context->output_bytes_per_frame = sizeof(float);
+ stm->in_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ } else {
+ context->output_bytes_per_frame = sizeof(short);
+ stm->in_params.format = CUBEB_SAMPLE_S16NE;
+ }
+ } else if (input_stream_params && !output_stream_params) {
+ stm->in_params = *input_stream_params;
+ stream_actual_rate = stm->in_params.rate;
+ stm->in_params.rate = jack_rate;
+ stm->devs = IN_ONLY;
+ if (stm->in_params.format == CUBEB_SAMPLE_FLOAT32NE) {
+ context->output_bytes_per_frame = sizeof(float);
+ } else {
+ context->output_bytes_per_frame = sizeof(short);
+ }
+ }
+
+ stm->ratio = (float)stream_actual_rate / (float)jack_rate;
+
+ stm->data_callback = data_callback;
+ stm->state_callback = state_callback;
+ stm->position = 0;
+ stm->volume = 1.0f;
+ context->jack_buffer_size = WRAP(jack_get_buffer_size)(context->jack_client);
+ context->fragment_size = context->jack_buffer_size;
+
+ if (stm->devs == NONE) {
+ pthread_mutex_unlock(&stm->mutex);
+ return CUBEB_ERROR;
+ }
+
+ stm->resampler = NULL;
+
+ if (stm->devs == DUPLEX) {
+ stm->resampler = cubeb_resampler_create(
+ stm, &stm->in_params, &stm->out_params, stream_actual_rate,
+ stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP,
+ CUBEB_RESAMPLER_RECLOCK_NONE);
+ } else if (stm->devs == IN_ONLY) {
+ stm->resampler = cubeb_resampler_create(
+ stm, &stm->in_params, nullptr, stream_actual_rate, stm->data_callback,
+ stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP,
+ CUBEB_RESAMPLER_RECLOCK_NONE);
+ } else if (stm->devs == OUT_ONLY) {
+ stm->resampler = cubeb_resampler_create(
+ stm, nullptr, &stm->out_params, stream_actual_rate, stm->data_callback,
+ stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP,
+ CUBEB_RESAMPLER_RECLOCK_NONE);
+ }
+
+ if (!stm->resampler) {
+ stm->in_use = false;
+ pthread_mutex_unlock(&stm->mutex);
+ return CUBEB_ERROR;
+ }
+
+ if (stm->devs == DUPLEX || stm->devs == OUT_ONLY) {
+ for (unsigned int c = 0; c < stm->out_params.channels; c++) {
+ char portname[256];
+ snprintf(portname, 255, "%s_out_%d", stm->stream_name, c);
+ stm->output_ports[c] = WRAP(jack_port_register)(
+ stm->context->jack_client, portname, JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput, 0);
+ if (!(output_stream_params->prefs &
+ CUBEB_STREAM_PREF_JACK_NO_AUTO_CONNECT)) {
+ if (cbjack_connect_ports(stm, CBJACK_CP_OPTIONS_SKIP_INPUT) !=
+ CUBEB_OK) {
+ pthread_mutex_unlock(&stm->mutex);
+ cbjack_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+ }
+ }
+ }
+
+ if (stm->devs == DUPLEX || stm->devs == IN_ONLY) {
+ for (unsigned int c = 0; c < stm->in_params.channels; c++) {
+ char portname[256];
+ snprintf(portname, 255, "%s_in_%d", stm->stream_name, c);
+ stm->input_ports[c] =
+ WRAP(jack_port_register)(stm->context->jack_client, portname,
+ JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
+ if (!(input_stream_params->prefs &
+ CUBEB_STREAM_PREF_JACK_NO_AUTO_CONNECT)) {
+ if (cbjack_connect_ports(stm, CBJACK_CP_OPTIONS_SKIP_OUTPUT) !=
+ CUBEB_OK) {
+ pthread_mutex_unlock(&stm->mutex);
+ cbjack_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+ }
+ }
+ }
+
+ *stream = stm;
+
+ stm->ports_ready = true;
+ stm->pause = true;
+ pthread_mutex_unlock(&stm->mutex);
+
+ return CUBEB_OK;
+}
+
+static void
+cbjack_stream_destroy(cubeb_stream * stream)
+{
+ pthread_mutex_lock(&stream->mutex);
+ stream->ports_ready = false;
+
+ if (stream->devs == DUPLEX || stream->devs == OUT_ONLY) {
+ for (unsigned int c = 0; c < stream->out_params.channels; c++) {
+ if (stream->output_ports[c]) {
+ WRAP(jack_port_unregister)
+ (stream->context->jack_client, stream->output_ports[c]);
+ stream->output_ports[c] = NULL;
+ }
+ }
+ }
+
+ if (stream->devs == DUPLEX || stream->devs == IN_ONLY) {
+ for (unsigned int c = 0; c < stream->in_params.channels; c++) {
+ if (stream->input_ports[c]) {
+ WRAP(jack_port_unregister)
+ (stream->context->jack_client, stream->input_ports[c]);
+ stream->input_ports[c] = NULL;
+ }
+ }
+ }
+
+ if (stream->resampler) {
+ cubeb_resampler_destroy(stream->resampler);
+ stream->resampler = NULL;
+ }
+ stream->in_use = false;
+ pthread_mutex_unlock(&stream->mutex);
+}
+
+static int
+cbjack_stream_start(cubeb_stream * stream)
+{
+ stream->pause = false;
+ stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STARTED);
+ return CUBEB_OK;
+}
+
+static int
+cbjack_stream_stop(cubeb_stream * stream)
+{
+ stream->pause = true;
+ stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STOPPED);
+ return CUBEB_OK;
+}
+
+static int
+cbjack_stream_get_position(cubeb_stream * stream, uint64_t * position)
+{
+ *position = stream->position;
+ return CUBEB_OK;
+}
+
+static int
+cbjack_stream_set_volume(cubeb_stream * stm, float volume)
+{
+ stm->volume = volume;
+ return CUBEB_OK;
+}
+
+static int
+cbjack_stream_get_current_device(cubeb_stream * stm,
+ cubeb_device ** const device)
+{
+ *device = (cubeb_device *)calloc(1, sizeof(cubeb_device));
+ if (*device == NULL)
+ return CUBEB_ERROR;
+
+ const char * j_in = JACK_DEFAULT_IN;
+ const char * j_out = JACK_DEFAULT_OUT;
+ const char * empty = "";
+
+ if (stm->devs == DUPLEX) {
+ (*device)->input_name = strdup(j_in);
+ (*device)->output_name = strdup(j_out);
+ } else if (stm->devs == IN_ONLY) {
+ (*device)->input_name = strdup(j_in);
+ (*device)->output_name = strdup(empty);
+ } else if (stm->devs == OUT_ONLY) {
+ (*device)->input_name = strdup(empty);
+ (*device)->output_name = strdup(j_out);
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+cbjack_stream_device_destroy(cubeb_stream * /*stream*/, cubeb_device * device)
+{
+ if (device->input_name)
+ free(device->input_name);
+ if (device->output_name)
+ free(device->output_name);
+ free(device);
+ return CUBEB_OK;
+}
+
+static int
+cbjack_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection)
+{
+ if (!context)
+ return CUBEB_ERROR;
+
+ uint32_t rate;
+ cbjack_get_preferred_sample_rate(context, &rate);
+
+ cubeb_device_info * devices = new cubeb_device_info[2];
+ if (!devices)
+ return CUBEB_ERROR;
+ PodZero(devices, 2);
+ collection->count = 0;
+
+ if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
+ cubeb_device_info * cur = &devices[collection->count];
+ cur->device_id = JACK_DEFAULT_OUT;
+ cur->devid = (cubeb_devid)cur->device_id;
+ cur->friendly_name = JACK_DEFAULT_OUT;
+ cur->group_id = JACK_DEFAULT_OUT;
+ cur->vendor_name = JACK_DEFAULT_OUT;
+ cur->type = CUBEB_DEVICE_TYPE_OUTPUT;
+ cur->state = CUBEB_DEVICE_STATE_ENABLED;
+ cur->preferred = CUBEB_DEVICE_PREF_ALL;
+ cur->format = CUBEB_DEVICE_FMT_F32NE;
+ cur->default_format = CUBEB_DEVICE_FMT_F32NE;
+ cur->max_channels = MAX_CHANNELS;
+ cur->min_rate = rate;
+ cur->max_rate = rate;
+ cur->default_rate = rate;
+ cur->latency_lo = 0;
+ cur->latency_hi = 0;
+ collection->count += 1;
+ }
+
+ if (type & CUBEB_DEVICE_TYPE_INPUT) {
+ cubeb_device_info * cur = &devices[collection->count];
+ cur->device_id = JACK_DEFAULT_IN;
+ cur->devid = (cubeb_devid)cur->device_id;
+ cur->friendly_name = JACK_DEFAULT_IN;
+ cur->group_id = JACK_DEFAULT_IN;
+ cur->vendor_name = JACK_DEFAULT_IN;
+ cur->type = CUBEB_DEVICE_TYPE_INPUT;
+ cur->state = CUBEB_DEVICE_STATE_ENABLED;
+ cur->preferred = CUBEB_DEVICE_PREF_ALL;
+ cur->format = CUBEB_DEVICE_FMT_F32NE;
+ cur->default_format = CUBEB_DEVICE_FMT_F32NE;
+ cur->max_channels = MAX_CHANNELS;
+ cur->min_rate = rate;
+ cur->max_rate = rate;
+ cur->default_rate = rate;
+ cur->latency_lo = 0;
+ cur->latency_hi = 0;
+ collection->count += 1;
+ }
+
+ collection->device = devices;
+
+ return CUBEB_OK;
+}
+
+static int
+cbjack_device_collection_destroy(cubeb * /*ctx*/,
+ cubeb_device_collection * collection)
+{
+ XASSERT(collection);
+ delete[] collection->device;
+ return CUBEB_OK;
+}
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_kai.c b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_kai.c
new file mode 100644
index 0000000000..4bc6c7afa7
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_kai.c
@@ -0,0 +1,372 @@
+/*
+ * Copyright © 2015 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/fmutex.h>
+
+#include <kai.h>
+
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+
+/* We don't support more than 2 channels in KAI */
+#define MAX_CHANNELS 2
+
+#define NBUFS 2
+#define FRAME_SIZE 2048
+
+struct cubeb_stream_item {
+ cubeb_stream * stream;
+};
+
+static struct cubeb_ops const kai_ops;
+
+struct cubeb {
+ struct cubeb_ops const * ops;
+};
+
+struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
+ cubeb * context;
+ void * user_ptr;
+ /**/
+ cubeb_stream_params params;
+ cubeb_data_callback data_callback;
+ cubeb_state_callback state_callback;
+
+ HKAI hkai;
+ KAISPEC spec;
+ uint64_t total_frames;
+ float soft_volume;
+ _fmutex mutex;
+ float float_buffer[FRAME_SIZE * MAX_CHANNELS];
+};
+
+static inline long
+frames_to_bytes(long frames, cubeb_stream_params params)
+{
+ return frames * 2 * params.channels; /* 2 bytes per frame */
+}
+
+static inline long
+bytes_to_frames(long bytes, cubeb_stream_params params)
+{
+ return bytes / 2 / params.channels; /* 2 bytes per frame */
+}
+
+static void
+kai_destroy(cubeb * ctx);
+
+/*static*/ int
+kai_init(cubeb ** context, char const * context_name)
+{
+ cubeb * ctx;
+
+ XASSERT(context);
+ *context = NULL;
+
+ if (kaiInit(KAIM_AUTO))
+ return CUBEB_ERROR;
+
+ ctx = calloc(1, sizeof(*ctx));
+ XASSERT(ctx);
+
+ ctx->ops = &kai_ops;
+
+ *context = ctx;
+
+ return CUBEB_OK;
+}
+
+static char const *
+kai_get_backend_id(cubeb * ctx)
+{
+ return "kai";
+}
+
+static void
+kai_destroy(cubeb * ctx)
+{
+ kaiDone();
+
+ free(ctx);
+}
+
+static void
+float_to_s16ne(int16_t * dst, float * src, size_t n)
+{
+ long l;
+
+ while (n--) {
+ l = lrintf(*src++ * 0x8000);
+ if (l > 32767)
+ l = 32767;
+ if (l < -32768)
+ l = -32768;
+ *dst++ = (int16_t)l;
+ }
+}
+
+static ULONG APIENTRY
+kai_callback(PVOID cbdata, PVOID buffer, ULONG len)
+{
+ cubeb_stream * stm = cbdata;
+ void * p;
+ long wanted_frames;
+ long frames;
+ float soft_volume;
+ int elements = len / sizeof(int16_t);
+
+ p = stm->params.format == CUBEB_SAMPLE_FLOAT32NE ? stm->float_buffer : buffer;
+
+ wanted_frames = bytes_to_frames(len, stm->params);
+ frames = stm->data_callback(stm, stm->user_ptr, NULL, p, wanted_frames);
+
+ _fmutex_request(&stm->mutex, 0);
+ stm->total_frames += frames;
+ soft_volume = stm->soft_volume;
+ _fmutex_release(&stm->mutex);
+
+ if (frames < wanted_frames)
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+
+ if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE)
+ float_to_s16ne(buffer, p, elements);
+
+ if (soft_volume != -1.0f) {
+ int16_t * b = buffer;
+ int i;
+
+ for (i = 0; i < elements; i++)
+ *b++ *= soft_volume;
+ }
+
+ return frames_to_bytes(frames, stm->params);
+}
+
+static void
+kai_stream_destroy(cubeb_stream * stm);
+
+static int
+kai_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency, cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ cubeb_stream * stm;
+ KAISPEC wanted_spec;
+
+ XASSERT(!input_stream_params && "not supported.");
+ if (input_device || output_device) {
+ /* Device selection not yet implemented. */
+ return CUBEB_ERROR_DEVICE_UNAVAILABLE;
+ }
+
+ if (!output_stream_params)
+ return CUBEB_ERROR_INVALID_PARAMETER;
+
+ // Loopback is unsupported
+ if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ if (output_stream_params->channels < 1 ||
+ output_stream_params->channels > MAX_CHANNELS)
+ return CUBEB_ERROR_INVALID_FORMAT;
+
+ XASSERT(context);
+ XASSERT(stream);
+
+ *stream = NULL;
+
+ stm = calloc(1, sizeof(*stm));
+ XASSERT(stm);
+
+ stm->context = context;
+ stm->params = *output_stream_params;
+ stm->data_callback = data_callback;
+ stm->state_callback = state_callback;
+ stm->user_ptr = user_ptr;
+ stm->soft_volume = -1.0f;
+
+ if (_fmutex_create(&stm->mutex, 0)) {
+ free(stm);
+ return CUBEB_ERROR;
+ }
+
+ wanted_spec.usDeviceIndex = 0;
+ wanted_spec.ulType = KAIT_PLAY;
+ wanted_spec.ulBitsPerSample = BPS_16;
+ wanted_spec.ulSamplingRate = stm->params.rate;
+ wanted_spec.ulDataFormat = MCI_WAVE_FORMAT_PCM;
+ wanted_spec.ulChannels = stm->params.channels;
+ wanted_spec.ulNumBuffers = NBUFS;
+ wanted_spec.ulBufferSize = frames_to_bytes(FRAME_SIZE, stm->params);
+ wanted_spec.fShareable = TRUE;
+ wanted_spec.pfnCallBack = kai_callback;
+ wanted_spec.pCallBackData = stm;
+
+ if (kaiOpen(&wanted_spec, &stm->spec, &stm->hkai)) {
+ _fmutex_close(&stm->mutex);
+ free(stm);
+ return CUBEB_ERROR;
+ }
+
+ *stream = stm;
+
+ return CUBEB_OK;
+}
+
+static void
+kai_stream_destroy(cubeb_stream * stm)
+{
+ kaiClose(stm->hkai);
+ _fmutex_close(&stm->mutex);
+ free(stm);
+}
+
+static int
+kai_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
+{
+ XASSERT(ctx && max_channels);
+
+ *max_channels = MAX_CHANNELS;
+
+ return CUBEB_OK;
+}
+
+static int
+kai_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency)
+{
+ /* We have at least two buffers. One is being played, the other one is being
+ filled. So there is as much latency as one buffer. */
+ *latency = FRAME_SIZE;
+
+ return CUBEB_OK;
+}
+
+static int
+kai_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
+{
+ cubeb_stream_params params;
+ KAISPEC wanted_spec;
+ KAISPEC spec;
+ HKAI hkai;
+
+ params.format = CUBEB_SAMPLE_S16NE;
+ params.rate = 48000;
+ params.channels = 2;
+
+ wanted_spec.usDeviceIndex = 0;
+ wanted_spec.ulType = KAIT_PLAY;
+ wanted_spec.ulBitsPerSample = BPS_16;
+ wanted_spec.ulSamplingRate = params.rate;
+ wanted_spec.ulDataFormat = MCI_WAVE_FORMAT_PCM;
+ wanted_spec.ulChannels = params.channels;
+ wanted_spec.ulNumBuffers = NBUFS;
+ wanted_spec.ulBufferSize = frames_to_bytes(FRAME_SIZE, params);
+ wanted_spec.fShareable = TRUE;
+ wanted_spec.pfnCallBack = kai_callback;
+ wanted_spec.pCallBackData = NULL;
+
+ /* Test 48KHz */
+ if (kaiOpen(&wanted_spec, &spec, &hkai)) {
+ /* Not supported. Fall back to 44.1KHz */
+ params.rate = 44100;
+ } else {
+ /* Supported. Use 48KHz */
+ kaiClose(hkai);
+ }
+
+ *rate = params.rate;
+
+ return CUBEB_OK;
+}
+
+static int
+kai_stream_start(cubeb_stream * stm)
+{
+ if (kaiPlay(stm->hkai))
+ return CUBEB_ERROR;
+
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
+
+ return CUBEB_OK;
+}
+
+static int
+kai_stream_stop(cubeb_stream * stm)
+{
+ if (kaiStop(stm->hkai))
+ return CUBEB_ERROR;
+
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
+
+ return CUBEB_OK;
+}
+
+static int
+kai_stream_get_position(cubeb_stream * stm, uint64_t * position)
+{
+ _fmutex_request(&stm->mutex, 0);
+ *position = stm->total_frames;
+ _fmutex_release(&stm->mutex);
+
+ return CUBEB_OK;
+}
+
+static int
+kai_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
+{
+ /* Out of buffers, one is being played, the others are being filled.
+ So there is as much latency as total buffers - 1. */
+ *latency = bytes_to_frames(stm->spec.ulBufferSize, stm->params) *
+ (stm->spec.ulNumBuffers - 1);
+
+ return CUBEB_OK;
+}
+
+static int
+kai_stream_set_volume(cubeb_stream * stm, float volume)
+{
+ _fmutex_request(&stm->mutex, 0);
+ stm->soft_volume = volume;
+ _fmutex_release(&stm->mutex);
+
+ return CUBEB_OK;
+}
+
+static struct cubeb_ops const kai_ops = {
+ /*.init =*/kai_init,
+ /*.get_backend_id =*/kai_get_backend_id,
+ /*.get_max_channel_count=*/kai_get_max_channel_count,
+ /*.get_min_latency=*/kai_get_min_latency,
+ /*.get_preferred_sample_rate =*/kai_get_preferred_sample_rate,
+ /*.get_preferred_channel_layout =*/NULL,
+ /*.get_supported_input_processing_params =*/NULL,
+ /*.enumerate_devices =*/NULL,
+ /*.device_collection_destroy =*/NULL,
+ /*.destroy =*/kai_destroy,
+ /*.stream_init =*/kai_stream_init,
+ /*.stream_destroy =*/kai_stream_destroy,
+ /*.stream_start =*/kai_stream_start,
+ /*.stream_stop =*/kai_stream_stop,
+ /*.stream_get_position =*/kai_stream_get_position,
+ /*.stream_get_latency = */ kai_stream_get_latency,
+ /*.stream_get_input_latency = */ NULL,
+ /*.stream_set_volume =*/kai_stream_set_volume,
+ /*.stream_set_name =*/NULL,
+ /*.stream_get_current_device =*/NULL,
+ /*.stream_set_input_mute =*/NULL,
+ /*.stream_set_input_processing_params =*/NULL,
+ /*.stream_device_destroy =*/NULL,
+ /*.stream_register_device_changed_callback=*/NULL,
+ /*.register_device_collection_changed=*/NULL};
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_log.cpp b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_log.cpp
new file mode 100644
index 0000000000..73b568f5cd
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_log.cpp
@@ -0,0 +1,233 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#define NOMINMAX
+
+#include "cubeb_log.h"
+#include "cubeb_ringbuffer.h"
+#include "cubeb_tracing.h"
+#include <cstdarg>
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <time.h>
+#endif
+
+std::atomic<cubeb_log_level> g_cubeb_log_level;
+std::atomic<cubeb_log_callback> g_cubeb_log_callback;
+
+/** The maximum size of a log message, after having been formatted. */
+const size_t CUBEB_LOG_MESSAGE_MAX_SIZE = 256;
+/** The maximum number of log messages that can be queued before dropping
+ * messages. */
+const size_t CUBEB_LOG_MESSAGE_QUEUE_DEPTH = 40;
+/** Number of milliseconds to wait before dequeuing log messages. */
+const size_t CUBEB_LOG_BATCH_PRINT_INTERVAL_MS = 10;
+
+void
+cubeb_noop_log_callback(char const * /* fmt */, ...)
+{
+}
+
+/**
+ * This wraps an inline buffer, that represents a log message, that must be
+ * null-terminated.
+ * This class should not use system calls or other potentially blocking code.
+ */
+class cubeb_log_message {
+public:
+ cubeb_log_message() { *storage = '\0'; }
+ cubeb_log_message(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE])
+ {
+ size_t length = strlen(str);
+ /* paranoia against malformed message */
+ assert(length < CUBEB_LOG_MESSAGE_MAX_SIZE);
+ if (length > CUBEB_LOG_MESSAGE_MAX_SIZE - 1) {
+ return;
+ }
+ PodCopy(storage, str, length);
+ storage[length] = '\0';
+ }
+ char const * get() { return storage; }
+
+private:
+ char storage[CUBEB_LOG_MESSAGE_MAX_SIZE]{};
+};
+
+/** Lock-free asynchronous logger, made so that logging from a
+ * real-time audio callback does not block the audio thread. */
+class cubeb_async_logger {
+public:
+ /* This is thread-safe since C++11 */
+ static cubeb_async_logger & get()
+ {
+ static cubeb_async_logger instance;
+ return instance;
+ }
+ void push(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE])
+ {
+ cubeb_log_message msg(str);
+ auto * owned_queue = msg_queue.load();
+ // Check if the queue is being deallocated. If not, grab ownership. If yes,
+ // return, the message won't be logged.
+ if (!owned_queue ||
+ !msg_queue.compare_exchange_strong(owned_queue, nullptr)) {
+ return;
+ }
+ owned_queue->enqueue(msg);
+ // Return ownership.
+ msg_queue.store(owned_queue);
+ }
+ void run()
+ {
+ assert(logging_thread.get_id() == std::thread::id());
+ logging_thread = std::thread([this]() {
+ CUBEB_REGISTER_THREAD("cubeb_log");
+ while (!shutdown_thread) {
+ cubeb_log_message msg;
+ while (msg_queue_consumer.load()->dequeue(&msg, 1)) {
+ cubeb_log_internal_no_format(msg.get());
+ }
+ std::this_thread::sleep_for(
+ std::chrono::milliseconds(CUBEB_LOG_BATCH_PRINT_INTERVAL_MS));
+ }
+ CUBEB_UNREGISTER_THREAD();
+ });
+ }
+ // Tell the underlying queue the producer thread has changed, so it does not
+ // assert in debug. This should be called with the thread stopped.
+ void reset_producer_thread()
+ {
+ if (msg_queue) {
+ msg_queue.load()->reset_thread_ids();
+ }
+ }
+ void start()
+ {
+ auto * queue =
+ new lock_free_queue<cubeb_log_message>(CUBEB_LOG_MESSAGE_QUEUE_DEPTH);
+ msg_queue.store(queue);
+ msg_queue_consumer.store(queue);
+ shutdown_thread = false;
+ run();
+ }
+ void stop()
+ {
+ assert(((g_cubeb_log_callback == cubeb_noop_log_callback) ||
+ !g_cubeb_log_callback) &&
+ "Only call stop after logging has been disabled.");
+ shutdown_thread = true;
+ if (logging_thread.get_id() != std::thread::id()) {
+ logging_thread.join();
+ logging_thread = std::thread();
+ auto * owned_queue = msg_queue.load();
+ // Check if the queue is being used. If not, grab ownership. If yes,
+ // try again shortly. At this point, the logging thread has been joined,
+ // so nothing is going to dequeue.
+ // If there is a valid pointer here, then the real-time audio thread that
+ // logs won't attempt to write into the queue, and instead drop the
+ // message.
+ while (!msg_queue.compare_exchange_weak(owned_queue, nullptr)) {
+ }
+ delete owned_queue;
+ msg_queue_consumer.store(nullptr);
+ }
+ }
+
+private:
+ cubeb_async_logger() {}
+ ~cubeb_async_logger()
+ {
+ assert(logging_thread.get_id() == std::thread::id() &&
+ (g_cubeb_log_callback == cubeb_noop_log_callback ||
+ !g_cubeb_log_callback));
+ if (msg_queue.load()) {
+ delete msg_queue.load();
+ }
+ }
+ /** This is quite a big data structure, but is only instantiated if the
+ * asynchronous logger is used. The two pointers point to the same object, but
+ * the first one can be temporarily null when a message is being enqueued. */
+ std::atomic<lock_free_queue<cubeb_log_message> *> msg_queue = {nullptr};
+
+ std::atomic<lock_free_queue<cubeb_log_message> *> msg_queue_consumer = {
+ nullptr};
+ std::atomic<bool> shutdown_thread = {false};
+ std::thread logging_thread;
+};
+
+void
+cubeb_log_internal(char const * file, uint32_t line, char const * fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ char msg[CUBEB_LOG_MESSAGE_MAX_SIZE];
+ vsnprintf(msg, CUBEB_LOG_MESSAGE_MAX_SIZE, fmt, args);
+ va_end(args);
+ g_cubeb_log_callback.load()("%s:%d:%s", file, line, msg);
+}
+
+void
+cubeb_log_internal_no_format(const char * msg)
+{
+ g_cubeb_log_callback.load()(msg);
+}
+
+void
+cubeb_async_log(char const * fmt, ...)
+{
+ // This is going to copy a 256 bytes array around, which is fine.
+ // We don't want to allocate memory here, because this is made to
+ // be called from a real-time callback.
+ va_list args;
+ va_start(args, fmt);
+ char msg[CUBEB_LOG_MESSAGE_MAX_SIZE];
+ vsnprintf(msg, CUBEB_LOG_MESSAGE_MAX_SIZE, fmt, args);
+ cubeb_async_logger::get().push(msg);
+ va_end(args);
+}
+
+void
+cubeb_async_log_reset_threads(void)
+{
+ if (!g_cubeb_log_callback) {
+ return;
+ }
+ cubeb_async_logger::get().reset_producer_thread();
+}
+
+void
+cubeb_log_set(cubeb_log_level log_level, cubeb_log_callback log_callback)
+{
+ g_cubeb_log_level = log_level;
+ // Once a callback has a been set, `g_cubeb_log_callback` is never set back to
+ // nullptr, to prevent a TOCTOU race between checking the pointer
+ if (log_callback && log_level != CUBEB_LOG_DISABLED) {
+ g_cubeb_log_callback = log_callback;
+ cubeb_async_logger::get().start();
+ } else if (!log_callback || CUBEB_LOG_DISABLED) {
+ g_cubeb_log_callback = cubeb_noop_log_callback;
+ // This returns once the thread has joined.
+ cubeb_async_logger::get().stop();
+ } else {
+ assert(false && "Incorrect parameters passed to cubeb_log_set");
+ }
+}
+
+cubeb_log_level
+cubeb_log_get_level()
+{
+ return g_cubeb_log_level;
+}
+
+cubeb_log_callback
+cubeb_log_get_callback()
+{
+ if (g_cubeb_log_callback == cubeb_noop_log_callback) {
+ return nullptr;
+ }
+ return g_cubeb_log_callback;
+}
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_log.h b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_log.h
new file mode 100644
index 0000000000..fb3b719b17
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_log.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_LOG
+#define CUBEB_LOG
+
+#include "cubeb/cubeb.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(__GNUC__) || defined(__clang__)
+#define PRINTF_FORMAT(fmt, args) __attribute__((format(printf, fmt, args)))
+#if defined(__FILE_NAME__)
+#define __FILENAME__ __FILE_NAME__
+#else
+#define __FILENAME__ \
+ (__builtin_strrchr(__FILE__, '/') ? __builtin_strrchr(__FILE__, '/') + 1 \
+ : __FILE__)
+#endif
+#else
+#define PRINTF_FORMAT(fmt, args)
+#include <string.h>
+#define __FILENAME__ \
+ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
+#endif
+
+void
+cubeb_log_set(cubeb_log_level log_level, cubeb_log_callback log_callback);
+cubeb_log_level
+cubeb_log_get_level(void);
+cubeb_log_callback
+cubeb_log_get_callback(void);
+void
+cubeb_log_internal_no_format(const char * msg);
+void
+cubeb_log_internal(const char * filename, uint32_t line, const char * fmt, ...);
+void
+cubeb_async_log(const char * fmt, ...);
+void
+cubeb_async_log_reset_threads(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#define LOGV(msg, ...) LOG_INTERNAL(CUBEB_LOG_VERBOSE, msg, ##__VA_ARGS__)
+#define LOG(msg, ...) LOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__)
+
+#define LOG_INTERNAL(level, fmt, ...) \
+ do { \
+ if (cubeb_log_get_level() >= level && cubeb_log_get_callback()) { \
+ cubeb_log_internal(__FILENAME__, __LINE__, fmt, ##__VA_ARGS__); \
+ } \
+ } while (0)
+
+#define ALOG_INTERNAL(level, fmt, ...) \
+ do { \
+ if (cubeb_log_get_level() >= level && cubeb_log_get_callback()) { \
+ cubeb_async_log(fmt, ##__VA_ARGS__); \
+ } \
+ } while (0)
+
+/* Asynchronous logging macros to log in real-time callbacks. */
+/* Should not be used on android due to the use of global/static variables. */
+#define ALOGV(msg, ...) ALOG_INTERNAL(CUBEB_LOG_VERBOSE, msg, ##__VA_ARGS__)
+#define ALOG(msg, ...) ALOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__)
+
+#endif // CUBEB_LOG
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_mixer.cpp b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_mixer.cpp
new file mode 100644
index 0000000000..7f87571f56
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_mixer.cpp
@@ -0,0 +1,621 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ *
+ * Adapted from code based on libswresample's rematrix.c
+ */
+
+#define NOMINMAX
+
+#include "cubeb_mixer.h"
+#include "cubeb-internal.h"
+#include "cubeb_utils.h"
+#include <algorithm>
+#include <cassert>
+#include <climits>
+#include <cmath>
+#include <cstdlib>
+#include <memory>
+#include <type_traits>
+
+#ifndef FF_ARRAY_ELEMS
+#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0]))
+#endif
+
+#define CHANNELS_MAX 32
+#define FRONT_LEFT 0
+#define FRONT_RIGHT 1
+#define FRONT_CENTER 2
+#define LOW_FREQUENCY 3
+#define BACK_LEFT 4
+#define BACK_RIGHT 5
+#define FRONT_LEFT_OF_CENTER 6
+#define FRONT_RIGHT_OF_CENTER 7
+#define BACK_CENTER 8
+#define SIDE_LEFT 9
+#define SIDE_RIGHT 10
+#define TOP_CENTER 11
+#define TOP_FRONT_LEFT 12
+#define TOP_FRONT_CENTER 13
+#define TOP_FRONT_RIGHT 14
+#define TOP_BACK_LEFT 15
+#define TOP_BACK_CENTER 16
+#define TOP_BACK_RIGHT 17
+#define NUM_NAMED_CHANNELS 18
+
+#ifndef M_SQRT1_2
+#define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */
+#endif
+#ifndef M_SQRT2
+#define M_SQRT2 1.41421356237309504880 /* sqrt(2) */
+#endif
+#define SQRT3_2 1.22474487139158904909 /* sqrt(3/2) */
+
+#define C30DB M_SQRT2
+#define C15DB 1.189207115
+#define C__0DB 1.0
+#define C_15DB 0.840896415
+#define C_30DB M_SQRT1_2
+#define C_45DB 0.594603558
+#define C_60DB 0.5
+
+static cubeb_channel_layout
+cubeb_channel_layout_check(cubeb_channel_layout l, uint32_t c)
+{
+ if (l == CUBEB_LAYOUT_UNDEFINED) {
+ switch (c) {
+ case 1:
+ return CUBEB_LAYOUT_MONO;
+ case 2:
+ return CUBEB_LAYOUT_STEREO;
+ }
+ }
+ return l;
+}
+
+unsigned int
+cubeb_channel_layout_nb_channels(cubeb_channel_layout x)
+{
+#if __GNUC__ || __clang__
+ return __builtin_popcount(x);
+#else
+ x -= (x >> 1) & 0x55555555;
+ x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
+ x = (x + (x >> 4)) & 0x0F0F0F0F;
+ x += x >> 8;
+ return (x + (x >> 16)) & 0x3F;
+#endif
+}
+
+struct MixerContext {
+ MixerContext(cubeb_sample_format f, uint32_t in_channels,
+ cubeb_channel_layout in, uint32_t out_channels,
+ cubeb_channel_layout out)
+ : _format(f), _in_ch_layout(cubeb_channel_layout_check(in, in_channels)),
+ _out_ch_layout(cubeb_channel_layout_check(out, out_channels)),
+ _in_ch_count(in_channels), _out_ch_count(out_channels)
+ {
+ if (in_channels != cubeb_channel_layout_nb_channels(in) ||
+ out_channels != cubeb_channel_layout_nb_channels(out)) {
+ // Mismatch between channels and layout, aborting.
+ return;
+ }
+ _valid = init() >= 0;
+ }
+
+ static bool even(cubeb_channel_layout layout)
+ {
+ if (!layout) {
+ return true;
+ }
+ if (layout & (layout - 1)) {
+ return true;
+ }
+ return false;
+ }
+
+ // Ensure that the layout is sane (that is have symmetrical left/right
+ // channels), if not, layout will be treated as mono.
+ static cubeb_channel_layout clean_layout(cubeb_channel_layout layout)
+ {
+ if (layout && layout != CHANNEL_FRONT_LEFT && !(layout & (layout - 1))) {
+ LOG("Treating layout as mono");
+ return CHANNEL_FRONT_CENTER;
+ }
+
+ return layout;
+ }
+
+ static bool sane_layout(cubeb_channel_layout layout)
+ {
+ if (!(layout & CUBEB_LAYOUT_3F)) { // at least 1 front speaker
+ return false;
+ }
+ if (!even(layout & (CHANNEL_FRONT_LEFT |
+ CHANNEL_FRONT_RIGHT))) { // no asymetric front
+ return false;
+ }
+ if (!even(layout &
+ (CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT))) { // no asymetric side
+ return false;
+ }
+ if (!even(layout & (CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT))) {
+ return false;
+ }
+ if (!even(layout &
+ (CHANNEL_FRONT_LEFT_OF_CENTER | CHANNEL_FRONT_RIGHT_OF_CENTER))) {
+ return false;
+ }
+ if (cubeb_channel_layout_nb_channels(layout) >= CHANNELS_MAX) {
+ return false;
+ }
+ return true;
+ }
+
+ int auto_matrix();
+ int init();
+
+ const cubeb_sample_format _format;
+ const cubeb_channel_layout _in_ch_layout; ///< input channel layout
+ const cubeb_channel_layout _out_ch_layout; ///< output channel layout
+ const uint32_t _in_ch_count; ///< input channel count
+ const uint32_t _out_ch_count; ///< output channel count
+ const float _surround_mix_level = C_30DB; ///< surround mixing level
+ const float _center_mix_level = C_30DB; ///< center mixing level
+ const float _lfe_mix_level = 1; ///< LFE mixing level
+ double _matrix[CHANNELS_MAX][CHANNELS_MAX] = {
+ {0}}; ///< floating point rematrixing coefficients
+ float _matrix_flt[CHANNELS_MAX][CHANNELS_MAX] = {
+ {0}}; ///< single precision floating point rematrixing coefficients
+ int32_t _matrix32[CHANNELS_MAX][CHANNELS_MAX] = {
+ {0}}; ///< 17.15 fixed point rematrixing coefficients
+ uint8_t _matrix_ch[CHANNELS_MAX][CHANNELS_MAX + 1] = {
+ {0}}; ///< Lists of input channels per output channel that have non zero
+ ///< rematrixing coefficients
+ bool _clipping = false; ///< Set to true if clipping detection is required
+ bool _valid = false; ///< Set to true if context is valid.
+};
+
+int
+MixerContext::auto_matrix()
+{
+ double matrix[NUM_NAMED_CHANNELS][NUM_NAMED_CHANNELS] = {{0}};
+ double maxcoef = 0;
+ double maxval;
+
+ cubeb_channel_layout in_ch_layout = clean_layout(_in_ch_layout);
+ cubeb_channel_layout out_ch_layout = clean_layout(_out_ch_layout);
+
+ if (!sane_layout(in_ch_layout)) {
+ // Channel Not Supported
+ LOG("Input Layout %x is not supported", _in_ch_layout);
+ return -1;
+ }
+
+ if (!sane_layout(out_ch_layout)) {
+ LOG("Output Layout %x is not supported", _out_ch_layout);
+ return -1;
+ }
+
+ for (uint32_t i = 0; i < FF_ARRAY_ELEMS(matrix); i++) {
+ if (in_ch_layout & out_ch_layout & (1U << i)) {
+ matrix[i][i] = 1.0;
+ }
+ }
+
+ cubeb_channel_layout unaccounted = in_ch_layout & ~out_ch_layout;
+
+ // Rematrixing is done via a matrix of coefficient that should be applied to
+ // all channels. Channels are treated as pair and must be symmetrical (if a
+ // left channel exists, the corresponding right should exist too) unless the
+ // output layout has similar layout. Channels are then mixed toward the front
+ // center or back center if they exist with a slight bias toward the front.
+
+ if (unaccounted & CHANNEL_FRONT_CENTER) {
+ if ((out_ch_layout & CUBEB_LAYOUT_STEREO) == CUBEB_LAYOUT_STEREO) {
+ if (in_ch_layout & CUBEB_LAYOUT_STEREO) {
+ matrix[FRONT_LEFT][FRONT_CENTER] += _center_mix_level;
+ matrix[FRONT_RIGHT][FRONT_CENTER] += _center_mix_level;
+ } else {
+ matrix[FRONT_LEFT][FRONT_CENTER] += M_SQRT1_2;
+ matrix[FRONT_RIGHT][FRONT_CENTER] += M_SQRT1_2;
+ }
+ }
+ }
+ if (unaccounted & CUBEB_LAYOUT_STEREO) {
+ if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][FRONT_LEFT] += M_SQRT1_2;
+ matrix[FRONT_CENTER][FRONT_RIGHT] += M_SQRT1_2;
+ if (in_ch_layout & CHANNEL_FRONT_CENTER)
+ matrix[FRONT_CENTER][FRONT_CENTER] = _center_mix_level * M_SQRT2;
+ }
+ }
+
+ if (unaccounted & CHANNEL_BACK_CENTER) {
+ if (out_ch_layout & CHANNEL_BACK_LEFT) {
+ matrix[BACK_LEFT][BACK_CENTER] += M_SQRT1_2;
+ matrix[BACK_RIGHT][BACK_CENTER] += M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_SIDE_LEFT) {
+ matrix[SIDE_LEFT][BACK_CENTER] += M_SQRT1_2;
+ matrix[SIDE_RIGHT][BACK_CENTER] += M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][BACK_CENTER] += _surround_mix_level * M_SQRT1_2;
+ matrix[FRONT_RIGHT][BACK_CENTER] += _surround_mix_level * M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][BACK_CENTER] += _surround_mix_level * M_SQRT1_2;
+ }
+ }
+ if (unaccounted & CHANNEL_BACK_LEFT) {
+ if (out_ch_layout & CHANNEL_BACK_CENTER) {
+ matrix[BACK_CENTER][BACK_LEFT] += M_SQRT1_2;
+ matrix[BACK_CENTER][BACK_RIGHT] += M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_SIDE_LEFT) {
+ if (in_ch_layout & CHANNEL_SIDE_LEFT) {
+ matrix[SIDE_LEFT][BACK_LEFT] += M_SQRT1_2;
+ matrix[SIDE_RIGHT][BACK_RIGHT] += M_SQRT1_2;
+ } else {
+ matrix[SIDE_LEFT][BACK_LEFT] += 1.0;
+ matrix[SIDE_RIGHT][BACK_RIGHT] += 1.0;
+ }
+ } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][BACK_LEFT] += _surround_mix_level;
+ matrix[FRONT_RIGHT][BACK_RIGHT] += _surround_mix_level;
+ } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][BACK_LEFT] += _surround_mix_level * M_SQRT1_2;
+ matrix[FRONT_CENTER][BACK_RIGHT] += _surround_mix_level * M_SQRT1_2;
+ }
+ }
+
+ if (unaccounted & CHANNEL_SIDE_LEFT) {
+ if (out_ch_layout & CHANNEL_BACK_LEFT) {
+ /* if back channels do not exist in the input, just copy side
+ channels to back channels, otherwise mix side into back */
+ if (in_ch_layout & CHANNEL_BACK_LEFT) {
+ matrix[BACK_LEFT][SIDE_LEFT] += M_SQRT1_2;
+ matrix[BACK_RIGHT][SIDE_RIGHT] += M_SQRT1_2;
+ } else {
+ matrix[BACK_LEFT][SIDE_LEFT] += 1.0;
+ matrix[BACK_RIGHT][SIDE_RIGHT] += 1.0;
+ }
+ } else if (out_ch_layout & CHANNEL_BACK_CENTER) {
+ matrix[BACK_CENTER][SIDE_LEFT] += M_SQRT1_2;
+ matrix[BACK_CENTER][SIDE_RIGHT] += M_SQRT1_2;
+ } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][SIDE_LEFT] += _surround_mix_level;
+ matrix[FRONT_RIGHT][SIDE_RIGHT] += _surround_mix_level;
+ } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][SIDE_LEFT] += _surround_mix_level * M_SQRT1_2;
+ matrix[FRONT_CENTER][SIDE_RIGHT] += _surround_mix_level * M_SQRT1_2;
+ }
+ }
+
+ if (unaccounted & CHANNEL_FRONT_LEFT_OF_CENTER) {
+ if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][FRONT_LEFT_OF_CENTER] += 1.0;
+ matrix[FRONT_RIGHT][FRONT_RIGHT_OF_CENTER] += 1.0;
+ } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][FRONT_LEFT_OF_CENTER] += M_SQRT1_2;
+ matrix[FRONT_CENTER][FRONT_RIGHT_OF_CENTER] += M_SQRT1_2;
+ }
+ }
+ /* mix LFE into front left/right or center */
+ if (unaccounted & CHANNEL_LOW_FREQUENCY) {
+ if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+ matrix[FRONT_CENTER][LOW_FREQUENCY] += _lfe_mix_level;
+ } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+ matrix[FRONT_LEFT][LOW_FREQUENCY] += _lfe_mix_level * M_SQRT1_2;
+ matrix[FRONT_RIGHT][LOW_FREQUENCY] += _lfe_mix_level * M_SQRT1_2;
+ }
+ }
+
+ // Normalize the conversion matrix.
+ for (uint32_t out_i = 0, i = 0; i < CHANNELS_MAX; i++) {
+ double sum = 0;
+ int in_i = 0;
+ if ((out_ch_layout & (1U << i)) == 0) {
+ continue;
+ }
+ for (uint32_t j = 0; j < CHANNELS_MAX; j++) {
+ if ((in_ch_layout & (1U << j)) == 0) {
+ continue;
+ }
+ if (i < FF_ARRAY_ELEMS(matrix) && j < FF_ARRAY_ELEMS(matrix[0])) {
+ _matrix[out_i][in_i] = matrix[i][j];
+ } else {
+ _matrix[out_i][in_i] =
+ i == j && (in_ch_layout & out_ch_layout & (1U << i));
+ }
+ sum += fabs(_matrix[out_i][in_i]);
+ in_i++;
+ }
+ maxcoef = std::max(maxcoef, sum);
+ out_i++;
+ }
+
+ if (_format == CUBEB_SAMPLE_S16NE) {
+ maxval = 1.0;
+ } else {
+ maxval = INT_MAX;
+ }
+
+ // Normalize matrix if needed.
+ if (maxcoef > maxval) {
+ maxcoef /= maxval;
+ for (uint32_t i = 0; i < CHANNELS_MAX; i++)
+ for (uint32_t j = 0; j < CHANNELS_MAX; j++) {
+ _matrix[i][j] /= maxcoef;
+ }
+ }
+
+ if (_format == CUBEB_SAMPLE_FLOAT32NE) {
+ for (uint32_t i = 0; i < FF_ARRAY_ELEMS(_matrix); i++) {
+ for (uint32_t j = 0; j < FF_ARRAY_ELEMS(_matrix[0]); j++) {
+ _matrix_flt[i][j] = _matrix[i][j];
+ }
+ }
+ }
+
+ return 0;
+}
+
+int
+MixerContext::init()
+{
+ int r = auto_matrix();
+ if (r) {
+ return r;
+ }
+
+ // Determine if matrix operation would overflow
+ if (_format == CUBEB_SAMPLE_S16NE) {
+ int maxsum = 0;
+ for (uint32_t i = 0; i < _out_ch_count; i++) {
+ double rem = 0;
+ int sum = 0;
+
+ for (uint32_t j = 0; j < _in_ch_count; j++) {
+ double target = _matrix[i][j] * 32768 + rem;
+ int value = lrintf(target);
+ rem += target - value;
+ sum += std::abs(value);
+ }
+ maxsum = std::max(maxsum, sum);
+ }
+ if (maxsum > 32768) {
+ _clipping = true;
+ }
+ }
+
+ // FIXME quantize for integers
+ for (uint32_t i = 0; i < CHANNELS_MAX; i++) {
+ int ch_in = 0;
+ for (uint32_t j = 0; j < CHANNELS_MAX; j++) {
+ _matrix32[i][j] = lrintf(_matrix[i][j] * 32768);
+ if (_matrix[i][j]) {
+ _matrix_ch[i][++ch_in] = j;
+ }
+ }
+ _matrix_ch[i][0] = ch_in;
+ }
+
+ return 0;
+}
+
+template <typename TYPE_SAMPLE, typename TYPE_COEFF, typename F>
+void
+sum2(TYPE_SAMPLE * out, uint32_t stride_out, const TYPE_SAMPLE * in1,
+ const TYPE_SAMPLE * in2, uint32_t stride_in, TYPE_COEFF coeff1,
+ TYPE_COEFF coeff2, F && operand, uint32_t frames)
+{
+ static_assert(
+ std::is_same<TYPE_COEFF, decltype(operand(coeff1))>::value,
+ "function must return the same type as used by coeff1 and coeff2");
+ for (uint32_t i = 0; i < frames; i++) {
+ *out = operand(coeff1 * *in1 + coeff2 * *in2);
+ out += stride_out;
+ in1 += stride_in;
+ in2 += stride_in;
+ }
+}
+
+template <typename TYPE_SAMPLE, typename TYPE_COEFF, typename F>
+void
+copy(TYPE_SAMPLE * out, uint32_t stride_out, const TYPE_SAMPLE * in,
+ uint32_t stride_in, TYPE_COEFF coeff, F && operand, uint32_t frames)
+{
+ static_assert(std::is_same<TYPE_COEFF, decltype(operand(coeff))>::value,
+ "function must return the same type as used by coeff");
+ for (uint32_t i = 0; i < frames; i++) {
+ *out = operand(coeff * *in);
+ out += stride_out;
+ in += stride_in;
+ }
+}
+
+template <typename TYPE, typename TYPE_COEFF, size_t COLS, typename F>
+static int
+rematrix(const MixerContext * s, TYPE * aOut, const TYPE * aIn,
+ const TYPE_COEFF (&matrix_coeff)[COLS][COLS], F && aF, uint32_t frames)
+{
+ static_assert(
+ std::is_same<TYPE_COEFF, decltype(aF(matrix_coeff[0][0]))>::value,
+ "function must return the same type as used by matrix_coeff");
+
+ for (uint32_t out_i = 0; out_i < s->_out_ch_count; out_i++) {
+ TYPE * out = aOut + out_i;
+ switch (s->_matrix_ch[out_i][0]) {
+ case 0:
+ for (uint32_t i = 0; i < frames; i++) {
+ out[i * s->_out_ch_count] = 0;
+ }
+ break;
+ case 1: {
+ int in_i = s->_matrix_ch[out_i][1];
+ copy(out, s->_out_ch_count, aIn + in_i, s->_in_ch_count,
+ matrix_coeff[out_i][in_i], aF, frames);
+ } break;
+ case 2:
+ sum2(out, s->_out_ch_count, aIn + s->_matrix_ch[out_i][1],
+ aIn + s->_matrix_ch[out_i][2], s->_in_ch_count,
+ matrix_coeff[out_i][s->_matrix_ch[out_i][1]],
+ matrix_coeff[out_i][s->_matrix_ch[out_i][2]], aF, frames);
+ break;
+ default:
+ for (uint32_t i = 0; i < frames; i++) {
+ TYPE_COEFF v = 0;
+ for (uint32_t j = 0; j < s->_matrix_ch[out_i][0]; j++) {
+ uint32_t in_i = s->_matrix_ch[out_i][1 + j];
+ v += *(aIn + in_i + i * s->_in_ch_count) * matrix_coeff[out_i][in_i];
+ }
+ out[i * s->_out_ch_count] = aF(v);
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+struct cubeb_mixer {
+ cubeb_mixer(cubeb_sample_format format, uint32_t in_channels,
+ cubeb_channel_layout in_layout, uint32_t out_channels,
+ cubeb_channel_layout out_layout)
+ : _context(format, in_channels, in_layout, out_channels, out_layout)
+ {
+ }
+
+ template <typename T>
+ void copy_and_trunc(size_t frames, const T * input_buffer,
+ T * output_buffer) const
+ {
+ if (_context._in_ch_count <= _context._out_ch_count) {
+ // Not enough channels to copy, fill the gaps with silence.
+ if (_context._in_ch_count == 1 && _context._out_ch_count >= 2) {
+ // Special case for upmixing mono input to stereo and more. We will
+ // duplicate the mono channel to the first two channels. On most system,
+ // the first two channels are for left and right. It is commonly
+ // expected that mono will on both left+right channels
+ for (uint32_t i = 0; i < frames; i++) {
+ output_buffer[0] = output_buffer[1] = *input_buffer;
+ PodZero(output_buffer + 2, _context._out_ch_count - 2);
+ output_buffer += _context._out_ch_count;
+ input_buffer++;
+ }
+ return;
+ }
+ for (uint32_t i = 0; i < frames; i++) {
+ PodCopy(output_buffer, input_buffer, _context._in_ch_count);
+ output_buffer += _context._in_ch_count;
+ input_buffer += _context._in_ch_count;
+ PodZero(output_buffer, _context._out_ch_count - _context._in_ch_count);
+ output_buffer += _context._out_ch_count - _context._in_ch_count;
+ }
+ } else {
+ for (uint32_t i = 0; i < frames; i++) {
+ PodCopy(output_buffer, input_buffer, _context._out_ch_count);
+ output_buffer += _context._out_ch_count;
+ input_buffer += _context._in_ch_count;
+ }
+ }
+ }
+
+ int mix(size_t frames, const void * input_buffer, size_t input_buffer_size,
+ void * output_buffer, size_t output_buffer_size) const
+ {
+ if (frames <= 0 || _context._out_ch_count == 0) {
+ return 0;
+ }
+
+ // Check if output buffer is of sufficient size.
+ size_t size_read_needed =
+ frames * _context._in_ch_count * cubeb_sample_size(_context._format);
+ if (input_buffer_size < size_read_needed) {
+ // We don't have enough data to read!
+ return -1;
+ }
+ if (output_buffer_size * _context._in_ch_count <
+ size_read_needed * _context._out_ch_count) {
+ return -1;
+ }
+
+ if (!valid()) {
+ // The channel layouts were invalid or unsupported, instead we will simply
+ // either drop the extra channels, or fill with silence the missing ones
+ if (_context._format == CUBEB_SAMPLE_FLOAT32NE) {
+ copy_and_trunc(frames, static_cast<const float *>(input_buffer),
+ static_cast<float *>(output_buffer));
+ } else {
+ assert(_context._format == CUBEB_SAMPLE_S16NE);
+ copy_and_trunc(frames, static_cast<const int16_t *>(input_buffer),
+ reinterpret_cast<int16_t *>(output_buffer));
+ }
+ return 0;
+ }
+
+ switch (_context._format) {
+ case CUBEB_SAMPLE_FLOAT32NE: {
+ auto f = [](float x) { return x; };
+ return rematrix(&_context, static_cast<float *>(output_buffer),
+ static_cast<const float *>(input_buffer),
+ _context._matrix_flt, f, frames);
+ }
+ case CUBEB_SAMPLE_S16NE:
+ if (_context._clipping) {
+ auto f = [](int x) {
+ int y = (x + 16384) >> 15;
+ // clip the signed integer value into the -32768,32767 range.
+ if ((y + 0x8000U) & ~0xFFFF) {
+ return (y >> 31) ^ 0x7FFF;
+ }
+ return y;
+ };
+ return rematrix(&_context, static_cast<int16_t *>(output_buffer),
+ static_cast<const int16_t *>(input_buffer),
+ _context._matrix32, f, frames);
+ } else {
+ auto f = [](int x) { return (x + 16384) >> 15; };
+ return rematrix(&_context, static_cast<int16_t *>(output_buffer),
+ static_cast<const int16_t *>(input_buffer),
+ _context._matrix32, f, frames);
+ }
+ break;
+ default:
+ assert(false);
+ break;
+ }
+
+ return -1;
+ }
+
+ // Return false if any of the input or ouput layout were invalid.
+ bool valid() const { return _context._valid; }
+
+ virtual ~cubeb_mixer(){};
+
+ MixerContext _context;
+};
+
+cubeb_mixer *
+cubeb_mixer_create(cubeb_sample_format format, uint32_t in_channels,
+ cubeb_channel_layout in_layout, uint32_t out_channels,
+ cubeb_channel_layout out_layout)
+{
+ return new cubeb_mixer(format, in_channels, in_layout, out_channels,
+ out_layout);
+}
+
+void
+cubeb_mixer_destroy(cubeb_mixer * mixer)
+{
+ delete mixer;
+}
+
+int
+cubeb_mixer_mix(cubeb_mixer * mixer, size_t frames, const void * input_buffer,
+ size_t input_buffer_size, void * output_buffer,
+ size_t output_buffer_size)
+{
+ return mixer->mix(frames, input_buffer, input_buffer_size, output_buffer,
+ output_buffer_size);
+}
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_mixer.h b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_mixer.h
new file mode 100644
index 0000000000..1859dab467
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_mixer.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_MIXER
+#define CUBEB_MIXER
+
+#include "cubeb/cubeb.h" // for cubeb_channel_layout and cubeb_stream_params.
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct cubeb_mixer cubeb_mixer;
+cubeb_mixer *
+cubeb_mixer_create(cubeb_sample_format format, uint32_t in_channels,
+ cubeb_channel_layout in_layout, uint32_t out_channels,
+ cubeb_channel_layout out_layout);
+void
+cubeb_mixer_destroy(cubeb_mixer * mixer);
+int
+cubeb_mixer_mix(cubeb_mixer * mixer, size_t frames, const void * input_buffer,
+ size_t input_buffer_size, void * output_buffer,
+ size_t output_buffer_size);
+
+unsigned int
+cubeb_channel_layout_nb_channels(cubeb_channel_layout channel_layout);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif // CUBEB_MIXER
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_opensl.cpp b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_opensl.cpp
new file mode 100644
index 0000000000..f9914ea04c
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_opensl.cpp
@@ -0,0 +1,1955 @@
+/*
+ * Copyright © 2012 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#undef NDEBUG
+#include <SLES/OpenSLES.h>
+#include <assert.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <math.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <time.h>
+#include <vector>
+#if defined(__ANDROID__)
+#include "android/sles_definitions.h"
+#include <SLES/OpenSLES_Android.h>
+#include <android/api-level.h>
+#include <android/log.h>
+#include <dlfcn.h>
+#include <sys/system_properties.h>
+#endif
+#include "android/cubeb-output-latency.h"
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_android.h"
+#include "cubeb_array_queue.h"
+#include "cubeb_resampler.h"
+
+#define ANDROID_VERSION_GINGERBREAD_MR1 10
+#define ANDROID_VERSION_JELLY_BEAN 18
+#define ANDROID_VERSION_LOLLIPOP 21
+#define ANDROID_VERSION_MARSHMALLOW 23
+#define ANDROID_VERSION_N_MR1 25
+
+#define DEFAULT_SAMPLE_RATE 48000
+#define DEFAULT_NUM_OF_FRAMES 480
+
+extern cubeb_ops const opensl_ops;
+
+struct cubeb {
+ struct cubeb_ops const * ops;
+ void * lib;
+ SLInterfaceID SL_IID_BUFFERQUEUE;
+ SLInterfaceID SL_IID_PLAY;
+#if defined(__ANDROID__)
+ SLInterfaceID SL_IID_ANDROIDCONFIGURATION;
+ SLInterfaceID SL_IID_ANDROIDSIMPLEBUFFERQUEUE;
+#endif
+ SLInterfaceID SL_IID_VOLUME;
+ SLInterfaceID SL_IID_RECORD;
+ SLObjectItf engObj;
+ SLEngineItf eng;
+ SLObjectItf outmixObj;
+ output_latency_function * p_output_latency_function;
+};
+
+#define NELEMS(A) (sizeof(A) / sizeof(A)[0])
+#define NBUFS 2
+
+struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
+ cubeb * context;
+ void * user_ptr;
+ /**/
+ pthread_mutex_t mutex;
+ SLObjectItf playerObj;
+ SLPlayItf play;
+ SLBufferQueueItf bufq;
+ SLVolumeItf volume;
+ void ** queuebuf;
+ uint32_t queuebuf_capacity;
+ uint32_t queuebuf_idx;
+ long queuebuf_len;
+ long bytespersec;
+ uint32_t framesize;
+ /* Total number of played frames.
+ * Synchronized by stream::mutex lock. */
+ long written;
+ /* Flag indicating draining. Synchronized
+ * by stream::mutex lock. */
+ int draining;
+ /* Flags to determine in/out.*/
+ uint32_t input_enabled;
+ uint32_t output_enabled;
+ /* Recorder abstract object. */
+ SLObjectItf recorderObj;
+ /* Recorder Itf for input capture. */
+ SLRecordItf recorderItf;
+ /* Buffer queue for input capture. */
+ SLAndroidSimpleBufferQueueItf recorderBufferQueueItf;
+ /* Store input buffers. */
+ void ** input_buffer_array;
+ /* The capacity of the array.
+ * On capture only can be small (4).
+ * On full duplex is calculated to
+ * store 1 sec of data buffers. */
+ uint32_t input_array_capacity;
+ /* Current filled index of input buffer array.
+ * It is initiated to -1 indicating buffering
+ * have not started yet. */
+ int input_buffer_index;
+ /* Length of input buffer.*/
+ uint32_t input_buffer_length;
+ /* Input frame size */
+ uint32_t input_frame_size;
+ /* Device sampling rate. If user rate is not
+ * accepted an compatible rate is set. If it is
+ * accepted this is equal to params.rate. */
+ uint32_t input_device_rate;
+ /* Exchange input buffers between input
+ * and full duplex threads. */
+ array_queue * input_queue;
+ /* Silent input buffer used on full duplex. */
+ void * input_silent_buffer;
+ /* Number of input frames from the start of the stream*/
+ uint32_t input_total_frames;
+ /* Flag to stop the execution of user callback and
+ * close all working threads. Synchronized by
+ * stream::mutex lock. */
+ uint32_t shutdown;
+ /* Store user callback. */
+ cubeb_data_callback data_callback;
+ /* Store state callback. */
+ cubeb_state_callback state_callback;
+
+ cubeb_resampler * resampler;
+ unsigned int user_output_rate;
+ unsigned int output_configured_rate;
+ unsigned int buffer_size_frames;
+ // Audio output latency used in cubeb_stream_get_position().
+ unsigned int output_latency_ms;
+ int64_t lastPosition;
+ int64_t lastPositionTimeStamp;
+ int64_t lastCompensativePosition;
+ int voice_input;
+ int voice_output;
+ std::unique_ptr<cubeb_stream_params> input_params;
+ std::unique_ptr<cubeb_stream_params> output_params;
+ // A non-empty buffer means that f32 -> int16 conversion need to happen
+ std::vector<float> conversion_buffer_output;
+ std::vector<float> conversion_buffer_input;
+};
+
+/* Forward declaration. */
+static int
+opensl_stop_player(cubeb_stream * stm);
+static int
+opensl_stop_recorder(cubeb_stream * stm);
+
+static int
+opensl_get_draining(cubeb_stream * stm)
+{
+#ifdef DEBUG
+ int r = pthread_mutex_trylock(&stm->mutex);
+ assert((r == EDEADLK || r == EBUSY) &&
+ "get_draining: mutex should be locked but it's not.");
+#endif
+ return stm->draining;
+}
+
+static void
+opensl_set_draining(cubeb_stream * stm, int value)
+{
+#ifdef DEBUG
+ int r = pthread_mutex_trylock(&stm->mutex);
+ LOG("set draining try r = %d", r);
+ assert((r == EDEADLK || r == EBUSY) &&
+ "set_draining: mutex should be locked but it's not.");
+#endif
+ assert(value == 0 || value == 1);
+ stm->draining = value;
+}
+
+static void
+opensl_notify_drained(cubeb_stream * stm)
+{
+ assert(stm);
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int draining = opensl_get_draining(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ if (draining) {
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+ if (stm->play) {
+ LOG("stop player in play_callback");
+ r = opensl_stop_player(stm);
+ assert(r == CUBEB_OK);
+ }
+ if (stm->recorderItf) {
+ r = opensl_stop_recorder(stm);
+ assert(r == CUBEB_OK);
+ }
+ }
+}
+
+static uint32_t
+opensl_get_shutdown(cubeb_stream * stm)
+{
+#ifdef DEBUG
+ int r = pthread_mutex_trylock(&stm->mutex);
+ assert((r == EDEADLK || r == EBUSY) &&
+ "get_shutdown: mutex should be locked but it's not.");
+#endif
+ return stm->shutdown;
+}
+
+static void
+opensl_set_shutdown(cubeb_stream * stm, uint32_t value)
+{
+#ifdef DEBUG
+ int r = pthread_mutex_trylock(&stm->mutex);
+ LOG("set shutdown try r = %d", r);
+ assert((r == EDEADLK || r == EBUSY) &&
+ "set_shutdown: mutex should be locked but it's not.");
+#endif
+ assert(value == 0 || value == 1);
+ stm->shutdown = value;
+}
+
+static void
+play_callback(SLPlayItf caller, void * user_ptr, SLuint32 event)
+{
+ cubeb_stream * stm = static_cast<cubeb_stream *>(user_ptr);
+ assert(stm);
+ switch (event) {
+ case SL_PLAYEVENT_HEADATMARKER:
+ opensl_notify_drained(stm);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+recorder_marker_callback(SLRecordItf caller, void * pContext, SLuint32 event)
+{
+ cubeb_stream * stm = static_cast<cubeb_stream *>(pContext);
+ assert(stm);
+
+ if (event == SL_RECORDEVENT_HEADATMARKER) {
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int draining = opensl_get_draining(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ if (draining) {
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+ if (stm->recorderItf) {
+ r = opensl_stop_recorder(stm);
+ assert(r == CUBEB_OK);
+ }
+ if (stm->play) {
+ r = opensl_stop_player(stm);
+ assert(r == CUBEB_OK);
+ }
+ }
+ }
+}
+
+// Returns a buffer suitable to write output data to.
+void *
+get_output_buffer(cubeb_stream * stm, void * output_buffer,
+ uint32_t sample_count)
+{
+ if (stm->conversion_buffer_output.empty()) {
+ return output_buffer;
+ }
+ if (stm->conversion_buffer_output.size() < sample_count) {
+ stm->conversion_buffer_output.resize(sample_count);
+ }
+ return stm->conversion_buffer_output.data();
+}
+
+void *
+release_output_buffer(cubeb_stream * stm, void * original_output_buffer,
+ uint32_t sample_count)
+{
+ if (stm->conversion_buffer_output.empty()) {
+ return original_output_buffer;
+ }
+ int16_t * int16_buf = reinterpret_cast<int16_t *>(original_output_buffer);
+ for (uint32_t i = 0; i < sample_count; i++) {
+ float v = stm->conversion_buffer_output[i] * 32768.0f;
+ float clamped = std::max(-32768.0f, std::min(32767.0f, v));
+ int16_buf[i] = static_cast<int16_t>(clamped);
+ }
+ return original_output_buffer;
+}
+
+static void
+bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr)
+{
+ cubeb_stream * stm = static_cast<cubeb_stream *>(user_ptr);
+ assert(stm);
+ SLBufferQueueState state;
+ SLresult res;
+ long written = 0;
+
+ res = (*stm->bufq)->GetState(stm->bufq, &state);
+ assert(res == SL_RESULT_SUCCESS);
+
+ if (state.count > 1) {
+ return;
+ }
+
+ void * buf = stm->queuebuf[stm->queuebuf_idx];
+ void * buf_original_ptr = buf;
+ uint32_t sample_count =
+ stm->output_params->channels * stm->queuebuf_len / stm->framesize;
+ written = 0;
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int draining = opensl_get_draining(stm);
+ uint32_t shutdown = opensl_get_shutdown(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ if (!draining && !shutdown) {
+
+ buf = get_output_buffer(stm, buf, sample_count);
+
+ written = cubeb_resampler_fill(stm->resampler, nullptr, nullptr, buf,
+ stm->queuebuf_len / stm->framesize);
+
+ buf = release_output_buffer(stm, buf_original_ptr, sample_count);
+
+ ALOGV("bufferqueue_callback: resampler fill returned %ld frames", written);
+ if (written < 0 ||
+ written * stm->framesize > static_cast<uint32_t>(stm->queuebuf_len)) {
+ ALOGV("bufferqueue_callback: error, shutting down", written);
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_shutdown(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ opensl_stop_player(stm);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ return;
+ }
+ }
+
+ // Keep sending silent data even in draining mode to prevent the audio
+ // back-end from being stopped automatically by OpenSL/ES.
+ assert(static_cast<uint32_t>(stm->queuebuf_len) >= written * stm->framesize);
+ memset(reinterpret_cast<uint8_t *>(buf) + written * stm->framesize, 0,
+ stm->queuebuf_len - written * stm->framesize);
+ res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->queuebuf_len);
+ assert(res == SL_RESULT_SUCCESS);
+ stm->queuebuf_idx = (stm->queuebuf_idx + 1) % stm->queuebuf_capacity;
+
+ if (written > 0) {
+ pthread_mutex_lock(&stm->mutex);
+ stm->written += written;
+ pthread_mutex_unlock(&stm->mutex);
+ }
+
+ if (!draining &&
+ written * stm->framesize < static_cast<uint32_t>(stm->queuebuf_len)) {
+ LOG("bufferqueue_callback draining");
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int64_t written_duration =
+ INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec;
+ opensl_set_draining(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (written_duration == 0) {
+ // since we didn't write any sample, it's not possible to reach the marker
+ // time and trigger the callback. We should initiative notify drained.
+ opensl_notify_drained(stm);
+ } else {
+ // Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf
+ // to make sure all the data has been processed.
+ (*stm->play)
+ ->SetMarkerPosition(stm->play, (SLmillisecond)written_duration);
+ }
+ return;
+ }
+}
+
+static int
+opensl_enqueue_recorder(cubeb_stream * stm, void ** last_filled_buffer)
+{
+ assert(stm);
+
+ int current_index = stm->input_buffer_index;
+ void * last_buffer = nullptr;
+
+ if (current_index < 0) {
+ // This is the first enqueue
+ current_index = 0;
+ } else {
+ // The current index hold the last filled buffer get it before advance
+ // index.
+ last_buffer = stm->input_buffer_array[current_index];
+ // Advance to get next available buffer
+ current_index =
+ static_cast<int>((current_index + 1) % stm->input_array_capacity);
+ }
+ // enqueue next empty buffer to be filled by the recorder
+ SLresult res = (*stm->recorderBufferQueueItf)
+ ->Enqueue(stm->recorderBufferQueueItf,
+ stm->input_buffer_array[current_index],
+ stm->input_buffer_length);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Enqueue recorder failed. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ // All good, update buffer and index.
+ stm->input_buffer_index = current_index;
+ if (last_filled_buffer) {
+ *last_filled_buffer = last_buffer;
+ }
+ return CUBEB_OK;
+}
+
+// If necessary, convert and returns an input buffer.
+// Otherwise, just returns the pointer that has been passed in.
+void *
+convert_input_buffer_if_needed(cubeb_stream * stm, void * input_buffer,
+ uint32_t sample_count)
+{
+ // Perform conversion if needed
+ if (stm->conversion_buffer_input.empty()) {
+ return input_buffer;
+ }
+ if (stm->conversion_buffer_input.size() < sample_count) {
+ stm->conversion_buffer_input.resize(sample_count);
+ }
+ int16_t * int16_buf = reinterpret_cast<int16_t *>(input_buffer);
+ for (uint32_t i = 0; i < sample_count; i++) {
+ stm->conversion_buffer_input[i] =
+ static_cast<float>(int16_buf[i]) / 32768.f;
+ }
+ return stm->conversion_buffer_input.data();
+}
+
+// input data callback
+void
+recorder_callback(SLAndroidSimpleBufferQueueItf bq, void * context)
+{
+ assert(context);
+ cubeb_stream * stm = static_cast<cubeb_stream *>(context);
+ assert(stm->recorderBufferQueueItf);
+
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ uint32_t shutdown = opensl_get_shutdown(stm);
+ int draining = opensl_get_draining(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (shutdown || draining) {
+ // According to the OpenSL ES 1.1 Specification, 8.14 SLBufferQueueItf
+ // page 184, on transition to the SL_RECORDSTATE_STOPPED state,
+ // the application should continue to enqueue buffers onto the queue
+ // to retrieve the residual recorded data in the system.
+ r = opensl_enqueue_recorder(stm, nullptr);
+ assert(r == CUBEB_OK);
+ return;
+ }
+
+ // Enqueue next available buffer and get the last filled buffer.
+ void * input_buffer = nullptr;
+ r = opensl_enqueue_recorder(stm, &input_buffer);
+ assert(r == CUBEB_OK);
+ assert(input_buffer);
+
+ long input_frame_count = stm->input_buffer_length / stm->input_frame_size;
+ uint32_t sample_count = input_frame_count * stm->input_params->channels;
+
+ input_buffer =
+ convert_input_buffer_if_needed(stm, input_buffer, sample_count);
+
+ // Fill resampler with last input
+ long got = cubeb_resampler_fill(stm->resampler, input_buffer,
+ &input_frame_count, nullptr, 0);
+ // Error case
+ if (got < 0 || got > input_frame_count) {
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_shutdown(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ r = opensl_stop_recorder(stm);
+ assert(r == CUBEB_OK);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ }
+
+ // Advance total stream frames
+ stm->input_total_frames += got;
+
+ if (got < input_frame_count) {
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_draining(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ int64_t duration =
+ INT64_C(1000) * stm->input_total_frames / stm->input_device_rate;
+ (*stm->recorderItf)
+ ->SetMarkerPosition(stm->recorderItf, (SLmillisecond)duration);
+ return;
+ }
+}
+
+void
+recorder_fullduplex_callback(SLAndroidSimpleBufferQueueItf bq, void * context)
+{
+ assert(context);
+ cubeb_stream * stm = static_cast<cubeb_stream *>(context);
+ assert(stm->recorderBufferQueueItf);
+
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int draining = opensl_get_draining(stm);
+ uint32_t shutdown = opensl_get_shutdown(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (shutdown || draining) {
+ /* On draining and shutdown the recorder should have been stoped from
+ * the one set the flags. Accordint to the doc, on transition to
+ * the SL_RECORDSTATE_STOPPED state, the application should
+ * continue to enqueue buffers onto the queue to retrieve the residual
+ * recorded data in the system. */
+ LOG("Input shutdown %d or drain %d", shutdown, draining);
+ int r = opensl_enqueue_recorder(stm, nullptr);
+ assert(r == CUBEB_OK);
+ return;
+ }
+
+ // Enqueue next available buffer and get the last filled buffer.
+ void * input_buffer = nullptr;
+ r = opensl_enqueue_recorder(stm, &input_buffer);
+ assert(r == CUBEB_OK);
+ assert(input_buffer);
+
+ assert(stm->input_queue);
+ r = array_queue_push(stm->input_queue, input_buffer);
+ if (r == -1) {
+ LOG("Input queue is full, drop input ...");
+ return;
+ }
+
+ LOG("Input pushed in the queue, input array %zu",
+ array_queue_get_size(stm->input_queue));
+}
+
+static void
+player_fullduplex_callback(SLBufferQueueItf caller, void * user_ptr)
+{
+ cubeb_stream * stm = static_cast<cubeb_stream *>(user_ptr);
+ assert(stm);
+ SLresult res;
+
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int draining = opensl_get_draining(stm);
+ uint32_t shutdown = opensl_get_shutdown(stm);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ // Get output
+ void * output_buffer = nullptr;
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ output_buffer = stm->queuebuf[stm->queuebuf_idx];
+ void * output_buffer_original_ptr = output_buffer;
+ // Advance the output buffer queue index
+ stm->queuebuf_idx = (stm->queuebuf_idx + 1) % stm->queuebuf_capacity;
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (shutdown || draining) {
+ LOG("Shutdown/draining, send silent");
+ // Set silent on buffer
+ memset(output_buffer, 0, stm->queuebuf_len);
+
+ // Enqueue data in player buffer queue
+ res = (*stm->bufq)->Enqueue(stm->bufq, output_buffer, stm->queuebuf_len);
+ assert(res == SL_RESULT_SUCCESS);
+ return;
+ }
+
+ // Get input.
+ void * input_buffer = array_queue_pop(stm->input_queue);
+ long input_frame_count = stm->input_buffer_length / stm->input_frame_size;
+ long sample_count = input_frame_count * stm->input_params->channels;
+ long frames_needed = stm->queuebuf_len / stm->framesize;
+ uint32_t output_sample_count =
+ stm->output_params->channels * stm->queuebuf_len / stm->framesize;
+
+ if (!input_buffer) {
+ LOG("Input hole set silent input buffer");
+ input_buffer = stm->input_silent_buffer;
+ }
+
+ input_buffer =
+ convert_input_buffer_if_needed(stm, input_buffer, sample_count);
+
+ output_buffer = get_output_buffer(stm, output_buffer, output_sample_count);
+
+ long written = 0;
+ // Trigger user callback through resampler
+ written =
+ cubeb_resampler_fill(stm->resampler, input_buffer, &input_frame_count,
+ output_buffer, frames_needed);
+
+ output_buffer =
+ release_output_buffer(stm, output_buffer_original_ptr, sample_count);
+
+ LOG("Fill: written %ld, frames_needed %ld, input array size %zu", written,
+ frames_needed, array_queue_get_size(stm->input_queue));
+
+ if (written < 0 || written > frames_needed) {
+ // Error case
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_shutdown(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+ opensl_stop_player(stm);
+ opensl_stop_recorder(stm);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ memset(output_buffer, 0, stm->queuebuf_len);
+
+ // Enqueue data in player buffer queue
+ res = (*stm->bufq)->Enqueue(stm->bufq, output_buffer, stm->queuebuf_len);
+ assert(res == SL_RESULT_SUCCESS);
+ return;
+ }
+
+ // Advance total out written frames counter
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ stm->written += written;
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (written < frames_needed) {
+ r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ int64_t written_duration =
+ INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec;
+ opensl_set_draining(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ // Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf
+ // to make sure all the data has been processed.
+ (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)written_duration);
+ }
+
+ // Keep sending silent data even in draining mode to prevent the audio
+ // back-end from being stopped automatically by OpenSL/ES.
+ memset((uint8_t *)output_buffer + written * stm->framesize, 0,
+ stm->queuebuf_len - written * stm->framesize);
+
+ // Enqueue data in player buffer queue
+ res = (*stm->bufq)->Enqueue(stm->bufq, output_buffer, stm->queuebuf_len);
+ assert(res == SL_RESULT_SUCCESS);
+}
+
+static void
+opensl_destroy(cubeb * ctx);
+
+#if defined(__ANDROID__)
+#if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
+using system_property_get = int(const char *, char *);
+
+static int
+wrap_system_property_get(const char * name, char * value)
+{
+ void * libc = dlopen("libc.so", RTLD_LAZY);
+ if (!libc) {
+ LOG("Failed to open libc.so");
+ return -1;
+ }
+ system_property_get * func =
+ (system_property_get *)dlsym(libc, "__system_property_get");
+ int ret = -1;
+ if (func) {
+ ret = func(name, value);
+ }
+ dlclose(libc);
+ return ret;
+}
+#endif
+
+static int
+get_android_version(void)
+{
+ char version_string[PROP_VALUE_MAX];
+
+ memset(version_string, 0, PROP_VALUE_MAX);
+
+#if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
+ int len = wrap_system_property_get("ro.build.version.sdk", version_string);
+#else
+ int len = __system_property_get("ro.build.version.sdk", version_string);
+#endif
+ if (len <= 0) {
+ LOG("Failed to get Android version!\n");
+ return len;
+ }
+
+ int version = (int)strtol(version_string, nullptr, 10);
+ LOG("Android version %d", version);
+ return version;
+}
+#endif
+
+extern "C" {
+int
+opensl_init(cubeb ** context, char const * context_name)
+{
+ cubeb * ctx;
+
+#if defined(__ANDROID__)
+ int android_version = get_android_version();
+ if (android_version > 0 &&
+ android_version <= ANDROID_VERSION_GINGERBREAD_MR1) {
+ // Don't even attempt to run on Gingerbread and lower
+ LOG("Error: Android version too old, exiting.");
+ return CUBEB_ERROR;
+ }
+#endif
+
+ *context = nullptr;
+
+ ctx = static_cast<cubeb *>(calloc(1, sizeof(*ctx)));
+ assert(ctx);
+
+ ctx->ops = &opensl_ops;
+
+ ctx->lib = dlopen("libOpenSLES.so", RTLD_LAZY);
+ if (!ctx->lib) {
+ LOG("Error: Couldn't find libOpenSLES.so, exiting");
+ free(ctx);
+ return CUBEB_ERROR;
+ }
+
+ typedef SLresult (*slCreateEngine_t)(
+ SLObjectItf *, SLuint32, const SLEngineOption *, SLuint32,
+ const SLInterfaceID *, const SLboolean *);
+ slCreateEngine_t f_slCreateEngine =
+ (slCreateEngine_t)dlsym(ctx->lib, "slCreateEngine");
+ SLInterfaceID SL_IID_ENGINE =
+ *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ENGINE");
+ SLInterfaceID SL_IID_OUTPUTMIX =
+ *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_OUTPUTMIX");
+ ctx->SL_IID_VOLUME = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_VOLUME");
+ ctx->SL_IID_BUFFERQUEUE =
+ *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_BUFFERQUEUE");
+#if defined(__ANDROID__)
+ ctx->SL_IID_ANDROIDCONFIGURATION =
+ *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDCONFIGURATION");
+ ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE =
+ *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDSIMPLEBUFFERQUEUE");
+#endif
+ ctx->SL_IID_PLAY = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_PLAY");
+ ctx->SL_IID_RECORD = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_RECORD");
+
+ if (!f_slCreateEngine || !SL_IID_ENGINE || !SL_IID_OUTPUTMIX ||
+ !ctx->SL_IID_BUFFERQUEUE ||
+#if defined(__ANDROID__)
+ !ctx->SL_IID_ANDROIDCONFIGURATION ||
+ !ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE ||
+#endif
+ !ctx->SL_IID_PLAY || !ctx->SL_IID_RECORD) {
+ LOG("Error: didn't find required symbols, exiting.");
+ opensl_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ const SLEngineOption opt[] = {{SL_ENGINEOPTION_THREADSAFE, SL_BOOLEAN_TRUE}};
+
+ SLresult res;
+ res = f_slCreateEngine(&ctx->engObj, 1, opt, 0, nullptr, nullptr);
+
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Error: slCreateEngine failure, exiting.");
+ opensl_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ res = (*ctx->engObj)->Realize(ctx->engObj, SL_BOOLEAN_FALSE);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Error: engine realization failure, exiting.");
+ opensl_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ res = (*ctx->engObj)->GetInterface(ctx->engObj, SL_IID_ENGINE, &ctx->eng);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Error: GetInterface(..., SL_IID_ENGINE, ...), exiting.");
+ opensl_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ const SLInterfaceID idsom[] = {SL_IID_OUTPUTMIX};
+ const SLboolean reqom[] = {SL_BOOLEAN_TRUE};
+ res =
+ (*ctx->eng)->CreateOutputMix(ctx->eng, &ctx->outmixObj, 1, idsom, reqom);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Error: CreateOutputMix failure, exiting.");
+ opensl_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ res = (*ctx->outmixObj)->Realize(ctx->outmixObj, SL_BOOLEAN_FALSE);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Error: Output mix object failure, exiting.");
+ opensl_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ ctx->p_output_latency_function =
+ cubeb_output_latency_load_method(android_version);
+ if (!cubeb_output_latency_method_is_loaded(ctx->p_output_latency_function)) {
+ LOG("Warning: output latency is not available, cubeb_stream_get_position() "
+ "is not supported");
+ }
+
+ *context = ctx;
+
+ LOG("Cubeb init (%p) success", ctx);
+ return CUBEB_OK;
+}
+}
+
+static char const *
+opensl_get_backend_id(cubeb * ctx)
+{
+ return "opensl";
+}
+
+static int
+opensl_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
+{
+ assert(ctx && max_channels);
+ /* The android mixer handles up to two channels, see
+ http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67
+ */
+ *max_channels = 2;
+
+ return CUBEB_OK;
+}
+
+static void
+opensl_destroy(cubeb * ctx)
+{
+ if (ctx->outmixObj) {
+ (*ctx->outmixObj)->Destroy(ctx->outmixObj);
+ }
+ if (ctx->engObj) {
+ (*ctx->engObj)->Destroy(ctx->engObj);
+ }
+ dlclose(ctx->lib);
+ if (ctx->p_output_latency_function) {
+ cubeb_output_latency_unload_method(ctx->p_output_latency_function);
+ }
+ free(ctx);
+}
+
+static void
+opensl_stream_destroy(cubeb_stream * stm);
+
+#if defined(__ANDROID__) && (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
+static int
+opensl_set_format_ext(SLAndroidDataFormat_PCM_EX * format,
+ cubeb_stream_params * params)
+{
+ assert(format);
+ assert(params);
+
+ format->formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
+ format->numChannels = params->channels;
+ // sampleRate is in milliHertz
+ format->sampleRate = params->rate * 1000;
+ format->channelMask = params->channels == 1
+ ? SL_SPEAKER_FRONT_CENTER
+ : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
+
+ switch (params->format) {
+ case CUBEB_SAMPLE_S16LE:
+ format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
+ format->endianness = SL_BYTEORDER_LITTLEENDIAN;
+ break;
+ case CUBEB_SAMPLE_S16BE:
+ format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
+ format->endianness = SL_BYTEORDER_BIGENDIAN;
+ break;
+ case CUBEB_SAMPLE_FLOAT32LE:
+ format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32;
+ format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_32;
+ format->representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
+ format->endianness = SL_BYTEORDER_LITTLEENDIAN;
+ break;
+ case CUBEB_SAMPLE_FLOAT32BE:
+ format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32;
+ format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_32;
+ format->representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
+ format->endianness = SL_BYTEORDER_BIGENDIAN;
+ break;
+ default:
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ return CUBEB_OK;
+}
+#endif
+
+static int
+opensl_set_format(SLDataFormat_PCM * format, cubeb_stream_params * params)
+{
+ assert(format);
+ assert(params);
+
+ // If this function is called, this backend has been compiled with an older
+ // version of Android, that doesn't support floating point audio IO.
+ // The stream is configured with int16 of the proper endianess, and conversion
+ // will happen during playback.
+
+ format->formatType = SL_DATAFORMAT_PCM;
+ format->numChannels = params->channels;
+ // samplesPerSec is in milliHertz
+ format->samplesPerSec = params->rate * 1000;
+ format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format->channelMask = params->channels == 1
+ ? SL_SPEAKER_FRONT_CENTER
+ : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
+
+ switch (params->format) {
+ case CUBEB_SAMPLE_S16LE:
+ case CUBEB_SAMPLE_FLOAT32LE:
+ format->endianness = SL_BYTEORDER_LITTLEENDIAN;
+ break;
+ case CUBEB_SAMPLE_S16BE:
+ case CUBEB_SAMPLE_FLOAT32BE:
+ format->endianness = SL_BYTEORDER_BIGENDIAN;
+ break;
+ default:
+ assert(false && "unhandled value");
+ }
+ return CUBEB_OK;
+}
+
+template <typename Function>
+int
+initialize_with_format(cubeb_stream * stm, cubeb_stream_params * params,
+ Function func)
+{
+ void * format = nullptr;
+ bool using_floats = false;
+ uint32_t * format_sample_rate;
+#if defined(__ANDROID__) && (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
+ SLAndroidDataFormat_PCM_EX pcm_ext_format;
+ if (get_android_version() >= ANDROID_VERSION_LOLLIPOP) {
+ if (opensl_set_format_ext(&pcm_ext_format, params) != CUBEB_OK) {
+ LOG("opensl_set_format_ext: error, exiting");
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ format = &pcm_ext_format;
+ format_sample_rate = &pcm_ext_format.sampleRate;
+ using_floats =
+ pcm_ext_format.representation == SL_ANDROID_PCM_REPRESENTATION_FLOAT;
+ }
+#endif
+
+ SLDataFormat_PCM pcm_format;
+ if (!format) {
+ if (opensl_set_format(&pcm_format, params) != CUBEB_OK) {
+ LOG("opensl_set_format: error, exiting");
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ format = &pcm_format;
+ format_sample_rate = &pcm_format.samplesPerSec;
+ }
+
+ return func(format, format_sample_rate, using_floats);
+}
+
+static int
+opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params)
+{
+ assert(stm);
+ assert(params);
+
+ /* For now set device rate to params rate. */
+ stm->input_device_rate = params->rate;
+
+ int rv = initialize_with_format(
+ stm, params,
+ [=](void * format, uint32_t * format_sample_rate,
+ bool using_floats) -> int {
+ SLDataLocator_AndroidSimpleBufferQueue lDataLocatorOut;
+ lDataLocatorOut.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
+ lDataLocatorOut.numBuffers = NBUFS;
+
+ SLDataSink dataSink;
+ dataSink.pLocator = &lDataLocatorOut;
+ dataSink.pFormat = format;
+
+ SLDataLocator_IODevice dataLocatorIn;
+ dataLocatorIn.locatorType = SL_DATALOCATOR_IODEVICE;
+ dataLocatorIn.deviceType = SL_IODEVICE_AUDIOINPUT;
+ dataLocatorIn.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
+ dataLocatorIn.device = nullptr;
+
+ SLDataSource dataSource;
+ dataSource.pLocator = &dataLocatorIn;
+ dataSource.pFormat = nullptr;
+
+ const SLInterfaceID lSoundRecorderIIDs[] = {
+ stm->context->SL_IID_RECORD,
+ stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ stm->context->SL_IID_ANDROIDCONFIGURATION};
+
+ const SLboolean lSoundRecorderReqs[] = {
+ SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
+ // create the audio recorder abstract object
+ SLresult res =
+ (*stm->context->eng)
+ ->CreateAudioRecorder(stm->context->eng, &stm->recorderObj,
+ &dataSource, &dataSink,
+ NELEMS(lSoundRecorderIIDs),
+ lSoundRecorderIIDs, lSoundRecorderReqs);
+ // Sample rate not supported. Try again with default sample rate!
+ if (res == SL_RESULT_CONTENT_UNSUPPORTED) {
+ if (stm->output_enabled && stm->output_configured_rate != 0) {
+ // Set the same with the player. Since there is no
+ // api for input device this is a safe choice.
+ stm->input_device_rate = stm->output_configured_rate;
+ } else {
+ // The output preferred rate is used for an input only scenario.
+ // The default rate expected to be supported from all android
+ // devices.
+ stm->input_device_rate = DEFAULT_SAMPLE_RATE;
+ }
+ *format_sample_rate = stm->input_device_rate * 1000;
+ res = (*stm->context->eng)
+ ->CreateAudioRecorder(
+ stm->context->eng, &stm->recorderObj, &dataSource,
+ &dataSink, NELEMS(lSoundRecorderIIDs),
+ lSoundRecorderIIDs, lSoundRecorderReqs);
+ }
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to create recorder, not trying other input"
+ " rate. Error code: %lu",
+ res);
+ return CUBEB_ERROR;
+ }
+ // It's always possible to use int16 regardless of the Android version.
+ // However if compiling for older Android version, it's possible to
+ // request f32 audio, but Android only supports int16, in which case a
+ // conversion need to happen.
+ if ((params->format == CUBEB_SAMPLE_FLOAT32NE ||
+ params->format == CUBEB_SAMPLE_FLOAT32BE) &&
+ !using_floats) {
+ // setup conversion from f32 to int16
+ LOG("Input stream configured for using float, but not supported: a "
+ "conversion will be performed");
+ stm->conversion_buffer_input.resize(1);
+ }
+ return CUBEB_OK;
+ });
+
+ if (rv != CUBEB_OK) {
+ LOG("Could not initialize recorder.");
+ return rv;
+ }
+
+ SLresult res;
+ if (get_android_version() > ANDROID_VERSION_JELLY_BEAN) {
+ SLAndroidConfigurationItf recorderConfig;
+ res = (*stm->recorderObj)
+ ->GetInterface(stm->recorderObj,
+ stm->context->SL_IID_ANDROIDCONFIGURATION,
+ &recorderConfig);
+
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to get the android configuration interface for recorder. "
+ "Error "
+ "code: %lu",
+ res);
+ return CUBEB_ERROR;
+ }
+
+ // Voice recognition is the lowest latency, according to the docs. Camcorder
+ // uses a microphone that is in the same direction as the camera.
+ SLint32 streamType = stm->voice_input
+ ? SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION
+ : SL_ANDROID_RECORDING_PRESET_CAMCORDER;
+
+ res =
+ (*recorderConfig)
+ ->SetConfiguration(recorderConfig, SL_ANDROID_KEY_RECORDING_PRESET,
+ &streamType, sizeof(SLint32));
+
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to set the android configuration to VOICE for the recorder. "
+ "Error code: %lu",
+ res);
+ return CUBEB_ERROR;
+ }
+ }
+ // realize the audio recorder
+ res = (*stm->recorderObj)->Realize(stm->recorderObj, SL_BOOLEAN_FALSE);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to realize recorder. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ // get the record interface
+ res = (*stm->recorderObj)
+ ->GetInterface(stm->recorderObj, stm->context->SL_IID_RECORD,
+ &stm->recorderItf);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to get recorder interface. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ res = (*stm->recorderItf)
+ ->RegisterCallback(stm->recorderItf, recorder_marker_callback, stm);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to register recorder marker callback. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ (*stm->recorderItf)->SetMarkerPosition(stm->recorderItf, (SLmillisecond)0);
+
+ res = (*stm->recorderItf)
+ ->SetCallbackEventsMask(stm->recorderItf,
+ (SLuint32)SL_RECORDEVENT_HEADATMARKER);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to set headatmarker event mask. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ // get the simple android buffer queue interface
+ res = (*stm->recorderObj)
+ ->GetInterface(stm->recorderObj,
+ stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ &stm->recorderBufferQueueItf);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to get recorder (android) buffer queue interface. Error code: "
+ "%lu",
+ res);
+ return CUBEB_ERROR;
+ }
+
+ // register callback on record (input) buffer queue
+ slAndroidSimpleBufferQueueCallback rec_callback = recorder_callback;
+ if (stm->output_enabled) {
+ // Register full duplex callback instead.
+ rec_callback = recorder_fullduplex_callback;
+ }
+ res = (*stm->recorderBufferQueueItf)
+ ->RegisterCallback(stm->recorderBufferQueueItf, rec_callback, stm);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to register recorder buffer queue callback. Error code: %lu",
+ res);
+ return CUBEB_ERROR;
+ }
+
+ // Calculate length of input buffer according to requested latency
+ uint32_t sample_size = 0;
+ if (params->format == CUBEB_SAMPLE_FLOAT32BE ||
+ params->format == CUBEB_SAMPLE_FLOAT32NE) {
+ sample_size = sizeof(float);
+ } else {
+ sample_size = sizeof(int16_t);
+ }
+ stm->input_frame_size = params->channels * sample_size;
+ stm->input_buffer_length = (stm->input_frame_size * stm->buffer_size_frames);
+
+ // Calculate the capacity of input array
+ stm->input_array_capacity = NBUFS;
+ if (stm->output_enabled) {
+ // Full duplex, update capacity to hold 1 sec of data
+ stm->input_array_capacity =
+ 1 * stm->input_device_rate / stm->input_buffer_length;
+ }
+ // Allocate input array
+ stm->input_buffer_array =
+ (void **)calloc(1, sizeof(void *) * stm->input_array_capacity);
+ // Buffering has not started yet.
+ stm->input_buffer_index = -1;
+ // Prepare input buffers
+ for (uint32_t i = 0; i < stm->input_array_capacity; ++i) {
+ stm->input_buffer_array[i] = calloc(1, stm->input_buffer_length);
+ }
+
+ // On full duplex allocate input queue and silent buffer
+ if (stm->output_enabled) {
+ stm->input_queue = array_queue_create(stm->input_array_capacity);
+ assert(stm->input_queue);
+ stm->input_silent_buffer = calloc(1, stm->input_buffer_length);
+ assert(stm->input_silent_buffer);
+ }
+
+ // Enqueue buffer to start rolling once recorder started
+ rv = opensl_enqueue_recorder(stm, nullptr);
+ if (rv != CUBEB_OK) {
+ return rv;
+ }
+
+ LOG("Cubeb stream init recorder success");
+
+ return CUBEB_OK;
+}
+
+static int
+opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params)
+{
+ assert(stm);
+ assert(params);
+
+ stm->user_output_rate = params->rate;
+ stm->lastPosition = -1;
+ stm->lastPositionTimeStamp = 0;
+ stm->lastCompensativePosition = -1;
+
+ int rv = initialize_with_format(
+ stm, params,
+ [=](void * format, uint32_t * format_sample_rate, bool using_floats) {
+ SLDataLocator_BufferQueue loc_bufq;
+ loc_bufq.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
+ loc_bufq.numBuffers = NBUFS;
+ SLDataSource source;
+ source.pLocator = &loc_bufq;
+ source.pFormat = format;
+ SLDataLocator_OutputMix loc_outmix;
+ loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
+ loc_outmix.outputMix = stm->context->outmixObj;
+
+ SLDataSink sink;
+ sink.pLocator = &loc_outmix;
+ sink.pFormat = nullptr;
+
+#if defined(__ANDROID__)
+ const SLInterfaceID ids[] = {stm->context->SL_IID_BUFFERQUEUE,
+ stm->context->SL_IID_VOLUME,
+ stm->context->SL_IID_ANDROIDCONFIGURATION};
+ const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE,
+ SL_BOOLEAN_TRUE};
+#else
+ const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE,
+ ctx->SL_IID_VOLUME};
+ const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
+#endif
+ assert(NELEMS(ids) == NELEMS(req));
+
+ uint32_t preferred_sampling_rate = stm->user_output_rate;
+ SLresult res = SL_RESULT_CONTENT_UNSUPPORTED;
+ if (preferred_sampling_rate) {
+ res = (*stm->context->eng)
+ ->CreateAudioPlayer(stm->context->eng, &stm->playerObj,
+ &source, &sink, NELEMS(ids), ids, req);
+ }
+
+ // Sample rate not supported? Try again with primary sample rate!
+ if (res == SL_RESULT_CONTENT_UNSUPPORTED &&
+ preferred_sampling_rate != DEFAULT_SAMPLE_RATE) {
+ preferred_sampling_rate = DEFAULT_SAMPLE_RATE;
+ *format_sample_rate = preferred_sampling_rate * 1000;
+ res = (*stm->context->eng)
+ ->CreateAudioPlayer(stm->context->eng, &stm->playerObj,
+ &source, &sink, NELEMS(ids), ids, req);
+ }
+
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to create audio player. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ stm->output_configured_rate = preferred_sampling_rate;
+
+ // It's always possible to use int16 regardless of the Android version.
+ // However if compiling for older Android version, it's possible to
+ // request f32 audio, but Android only supports int16, in which case a
+ // conversion need to happen.
+ if ((params->format == CUBEB_SAMPLE_FLOAT32NE ||
+ params->format == CUBEB_SAMPLE_FLOAT32BE) &&
+ !using_floats) {
+ // setup conversion from f32 to int16
+ LOG("Input stream configured for using float, but not supported: a "
+ "conversion will be performed");
+ stm->conversion_buffer_output.resize(1);
+ }
+
+ if (!using_floats) {
+ stm->framesize = params->channels * sizeof(int16_t);
+ } else {
+ stm->framesize = params->channels * sizeof(float);
+ }
+ return CUBEB_OK;
+ });
+
+ if (rv != CUBEB_OK) {
+ LOG("Couldn't set format on sink or source");
+ return rv;
+ }
+
+ stm->bytespersec = stm->output_configured_rate * stm->framesize;
+ stm->queuebuf_len = stm->framesize * stm->buffer_size_frames;
+
+ // Calculate the capacity of input array
+ stm->queuebuf_capacity = NBUFS;
+ // Allocate input arrays
+ stm->queuebuf = (void **)calloc(1, sizeof(void *) * stm->queuebuf_capacity);
+ for (uint32_t i = 0; i < stm->queuebuf_capacity; ++i) {
+ stm->queuebuf[i] = calloc(1, stm->queuebuf_len);
+ assert(stm->queuebuf[i]);
+ }
+
+ SLAndroidConfigurationItf playerConfig = nullptr;
+
+ SLresult res;
+ if (get_android_version() >= ANDROID_VERSION_N_MR1) {
+ res = (*stm->playerObj)
+ ->GetInterface(stm->playerObj,
+ stm->context->SL_IID_ANDROIDCONFIGURATION,
+ &playerConfig);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to get Android configuration interface. Error code: %lu",
+ res);
+ return CUBEB_ERROR;
+ }
+
+ SLint32 streamType = SL_ANDROID_STREAM_MEDIA;
+ if (stm->voice_output) {
+ streamType = SL_ANDROID_STREAM_VOICE;
+ }
+ res = (*playerConfig)
+ ->SetConfiguration(playerConfig, SL_ANDROID_KEY_STREAM_TYPE,
+ &streamType, sizeof(streamType));
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to set Android configuration to %d Error code: %lu",
+ streamType, res);
+ }
+
+ SLuint32 performanceMode = SL_ANDROID_PERFORMANCE_LATENCY;
+ if (stm->buffer_size_frames > POWERSAVE_LATENCY_FRAMES_THRESHOLD) {
+ LOG("Audio stream configured for power saving");
+ performanceMode = SL_ANDROID_PERFORMANCE_POWER_SAVING;
+ }
+
+ res = (*playerConfig)
+ ->SetConfiguration(playerConfig, SL_ANDROID_KEY_PERFORMANCE_MODE,
+ &performanceMode, sizeof(performanceMode));
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to set Android performance mode to %d Error code: %lu. This "
+ "is not fatal.",
+ performanceMode, res);
+ }
+ }
+
+ res = (*stm->playerObj)->Realize(stm->playerObj, SL_BOOLEAN_FALSE);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to realize player object. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ // There are two ways of getting the audio output latency:
+ // - a configuration value, only available on some devices (notably devices
+ // running FireOS)
+ // - A Java method, that we call using JNI.
+ //
+ // The first method is prefered, if available, because it can account for more
+ // latency causes, and is more precise.
+
+ // Latency has to be queried after the realization of the interface, when
+ // using SL_IID_ANDROIDCONFIGURATION.
+ SLuint32 audioLatency = 0;
+ SLuint32 paramSize = sizeof(SLuint32);
+ // The reported latency is in milliseconds.
+ if (playerConfig) {
+ res = (*playerConfig)
+ ->GetConfiguration(playerConfig,
+ (const SLchar *)"androidGetAudioLatency",
+ &paramSize, &audioLatency);
+ if (res == SL_RESULT_SUCCESS) {
+ LOG("Got playback latency using android configuration extension");
+ stm->output_latency_ms = audioLatency;
+ }
+ }
+ // `playerConfig` is available, but the above failed, or `playerConfig` is not
+ // available. In both cases, we need to acquire the output latency by an other
+ // mean.
+ if ((playerConfig && res != SL_RESULT_SUCCESS) || !playerConfig) {
+ if (cubeb_output_latency_method_is_loaded(
+ stm->context->p_output_latency_function)) {
+ LOG("Got playback latency using JNI");
+ stm->output_latency_ms =
+ cubeb_get_output_latency(stm->context->p_output_latency_function);
+ } else {
+ LOG("No alternate latency querying method loaded, A/V sync will be off.");
+ stm->output_latency_ms = 0;
+ }
+ }
+
+ LOG("Audio output latency: %dms", stm->output_latency_ms);
+
+ res =
+ (*stm->playerObj)
+ ->GetInterface(stm->playerObj, stm->context->SL_IID_PLAY, &stm->play);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to get play interface. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ res = (*stm->playerObj)
+ ->GetInterface(stm->playerObj, stm->context->SL_IID_BUFFERQUEUE,
+ &stm->bufq);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to get bufferqueue interface. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ res = (*stm->playerObj)
+ ->GetInterface(stm->playerObj, stm->context->SL_IID_VOLUME,
+ &stm->volume);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to get volume interface. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ res = (*stm->play)->RegisterCallback(stm->play, play_callback, stm);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to register play callback. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ // Work around wilhelm/AudioTrack badness, bug 1221228
+ (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)0);
+
+ res = (*stm->play)
+ ->SetCallbackEventsMask(stm->play,
+ (SLuint32)SL_PLAYEVENT_HEADATMARKER);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to set headatmarker event mask. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ slBufferQueueCallback player_callback = bufferqueue_callback;
+ if (stm->input_enabled) {
+ player_callback = player_fullduplex_callback;
+ }
+ res = (*stm->bufq)->RegisterCallback(stm->bufq, player_callback, stm);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to register bufferqueue callback. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ {
+ // Enqueue a silent frame so once the player becomes playing, the frame
+ // will be consumed and kick off the buffer queue callback.
+ // Note the duration of a single frame is less than 1ms. We don't bother
+ // adjusting the playback position.
+ uint8_t * buf =
+ reinterpret_cast<uint8_t *>(stm->queuebuf[stm->queuebuf_idx++]);
+ memset(buf, 0, stm->framesize);
+ res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->framesize);
+ assert(res == SL_RESULT_SUCCESS);
+ }
+
+ LOG("Cubeb stream init playback success");
+ return CUBEB_OK;
+}
+
+static int
+opensl_validate_stream_param(cubeb_stream_params * stream_params)
+{
+ if ((stream_params &&
+ (stream_params->channels < 1 || stream_params->channels > 32))) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ if ((stream_params && (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) {
+ LOG("Loopback is not supported");
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+ return CUBEB_OK;
+}
+
+int
+has_pref_set(cubeb_stream_params * input_params,
+ cubeb_stream_params * output_params, cubeb_stream_prefs pref)
+{
+ return (input_params && input_params->prefs & pref) ||
+ (output_params && output_params->prefs & pref);
+}
+
+static int
+opensl_stream_init(cubeb * ctx, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ cubeb_stream * stm = nullptr;
+ cubeb_async_log_reset_threads();
+
+ assert(ctx);
+ if (input_device || output_device) {
+ LOG("Device selection is not supported in Android. The default will be "
+ "used");
+ }
+
+ *stream = nullptr;
+
+ int r = opensl_validate_stream_param(output_stream_params);
+ if (r != CUBEB_OK) {
+ LOG("Output stream params not valid");
+ return r;
+ }
+ r = opensl_validate_stream_param(input_stream_params);
+ if (r != CUBEB_OK) {
+ LOG("Input stream params not valid");
+ return r;
+ }
+
+ stm = reinterpret_cast<cubeb_stream *>(calloc(1, sizeof(*stm)));
+ assert(stm);
+
+ if (input_stream_params) {
+ stm->input_params =
+ std::make_unique<cubeb_stream_params>(*input_stream_params);
+ }
+ if (output_stream_params) {
+ stm->output_params =
+ std::make_unique<cubeb_stream_params>(*output_stream_params);
+ }
+
+ stm->context = ctx;
+ stm->data_callback = data_callback;
+ stm->state_callback = state_callback;
+ stm->user_ptr = user_ptr;
+ stm->buffer_size_frames =
+ latency_frames ? latency_frames : DEFAULT_NUM_OF_FRAMES;
+ stm->input_enabled = (input_stream_params) ? 1 : 0;
+ stm->output_enabled = (output_stream_params) ? 1 : 0;
+ stm->shutdown = 1;
+ stm->voice_input =
+ has_pref_set(input_stream_params, nullptr, CUBEB_STREAM_PREF_VOICE);
+ stm->voice_output =
+ has_pref_set(nullptr, output_stream_params, CUBEB_STREAM_PREF_VOICE);
+
+ LOG("cubeb stream prefs: voice_input: %s voice_output: %s",
+ stm->voice_input ? "true" : "false",
+ stm->voice_output ? "true" : "false");
+
+#ifdef DEBUG
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
+ r = pthread_mutex_init(&stm->mutex, &attr);
+#else
+ r = pthread_mutex_init(&stm->mutex, nullptr);
+#endif
+ assert(r == 0);
+
+ if (output_stream_params) {
+ LOG("Playback params: Rate %d, channels %d, format %d, latency in frames "
+ "%d.",
+ output_stream_params->rate, output_stream_params->channels,
+ output_stream_params->format, stm->buffer_size_frames);
+ r = opensl_configure_playback(stm, output_stream_params);
+ if (r != CUBEB_OK) {
+ LOG("Error: playback-side configuration error, exiting.");
+ opensl_stream_destroy(stm);
+ return r;
+ }
+ }
+
+ if (input_stream_params) {
+ LOG("Capture params: Rate %d, channels %d, format %d, latency in frames "
+ "%d.",
+ input_stream_params->rate, input_stream_params->channels,
+ input_stream_params->format, stm->buffer_size_frames);
+ r = opensl_configure_capture(stm, input_stream_params);
+ if (r != CUBEB_OK) {
+ LOG("Error: record-side configuration error, exiting.");
+ opensl_stream_destroy(stm);
+ return r;
+ }
+ }
+
+ /* Configure resampler*/
+ uint32_t target_sample_rate;
+ if (input_stream_params) {
+ target_sample_rate = input_stream_params->rate;
+ } else {
+ assert(output_stream_params);
+ target_sample_rate = output_stream_params->rate;
+ }
+
+ // Use the actual configured rates for input
+ // and output.
+ cubeb_stream_params input_params;
+ if (input_stream_params) {
+ input_params = *input_stream_params;
+ input_params.rate = stm->input_device_rate;
+ }
+ cubeb_stream_params output_params;
+ if (output_stream_params) {
+ output_params = *output_stream_params;
+ output_params.rate = stm->output_configured_rate;
+ }
+
+ stm->resampler = cubeb_resampler_create(
+ stm, input_stream_params ? &input_params : nullptr,
+ output_stream_params ? &output_params : nullptr, target_sample_rate,
+ data_callback, user_ptr, CUBEB_RESAMPLER_QUALITY_DEFAULT,
+ CUBEB_RESAMPLER_RECLOCK_NONE);
+ if (!stm->resampler) {
+ LOG("Failed to create resampler");
+ opensl_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ *stream = stm;
+ LOG("Cubeb stream (%p) init success", stm);
+ return CUBEB_OK;
+}
+
+static int
+opensl_start_player(cubeb_stream * stm)
+{
+ assert(stm->playerObj);
+ SLuint32 playerState;
+ (*stm->playerObj)->GetState(stm->playerObj, &playerState);
+ if (playerState == SL_OBJECT_STATE_REALIZED) {
+ SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PLAYING);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to start player. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ }
+ return CUBEB_OK;
+}
+
+static int
+opensl_start_recorder(cubeb_stream * stm)
+{
+ assert(stm->recorderObj);
+ SLuint32 recorderState;
+ (*stm->recorderObj)->GetState(stm->recorderObj, &recorderState);
+ if (recorderState == SL_OBJECT_STATE_REALIZED) {
+ SLresult res =
+ (*stm->recorderItf)
+ ->SetRecordState(stm->recorderItf, SL_RECORDSTATE_RECORDING);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to start recorder. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ }
+ return CUBEB_OK;
+}
+
+static int
+opensl_stream_start(cubeb_stream * stm)
+{
+ assert(stm);
+
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_shutdown(stm, 0);
+ opensl_set_draining(stm, 0);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (stm->playerObj) {
+ r = opensl_start_player(stm);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+ }
+
+ if (stm->recorderObj) {
+ int r = opensl_start_recorder(stm);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+ }
+
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
+ LOG("Cubeb stream (%p) started", stm);
+ return CUBEB_OK;
+}
+
+static int
+opensl_stop_player(cubeb_stream * stm)
+{
+ assert(stm->playerObj);
+ assert(stm->shutdown || stm->draining);
+
+ SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to stop player. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+opensl_stop_recorder(cubeb_stream * stm)
+{
+ assert(stm->recorderObj);
+ assert(stm->shutdown || stm->draining);
+
+ SLresult res = (*stm->recorderItf)
+ ->SetRecordState(stm->recorderItf, SL_RECORDSTATE_PAUSED);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to stop recorder. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+opensl_stream_stop(cubeb_stream * stm)
+{
+ assert(stm);
+
+ int r = pthread_mutex_lock(&stm->mutex);
+ assert(r == 0);
+ opensl_set_shutdown(stm, 1);
+ r = pthread_mutex_unlock(&stm->mutex);
+ assert(r == 0);
+
+ if (stm->playerObj) {
+ r = opensl_stop_player(stm);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+ }
+
+ if (stm->recorderObj) {
+ int r = opensl_stop_recorder(stm);
+ if (r != CUBEB_OK) {
+ return r;
+ }
+ }
+
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
+ LOG("Cubeb stream (%p) stopped", stm);
+ return CUBEB_OK;
+}
+
+static int
+opensl_destroy_recorder(cubeb_stream * stm)
+{
+ assert(stm);
+ assert(stm->recorderObj);
+
+ if (stm->recorderBufferQueueItf) {
+ SLresult res =
+ (*stm->recorderBufferQueueItf)->Clear(stm->recorderBufferQueueItf);
+ if (res != SL_RESULT_SUCCESS) {
+ LOG("Failed to clear recorder buffer queue. Error code: %lu", res);
+ return CUBEB_ERROR;
+ }
+ stm->recorderBufferQueueItf = nullptr;
+ for (uint32_t i = 0; i < stm->input_array_capacity; ++i) {
+ free(stm->input_buffer_array[i]);
+ }
+ }
+
+ (*stm->recorderObj)->Destroy(stm->recorderObj);
+ stm->recorderObj = nullptr;
+ stm->recorderItf = nullptr;
+
+ if (stm->input_queue) {
+ array_queue_destroy(stm->input_queue);
+ }
+ free(stm->input_silent_buffer);
+
+ return CUBEB_OK;
+}
+
+static void
+opensl_stream_destroy(cubeb_stream * stm)
+{
+ assert(stm->draining || stm->shutdown);
+
+ // If we're still draining at stream destroy time, pause the streams now so we
+ // can destroy them safely.
+ if (stm->draining) {
+ opensl_stream_stop(stm);
+ }
+ // Sleep for 10ms to give active streams time to pause so that no further
+ // buffer callbacks occur. Inspired by the same workaround (sleepBeforeClose)
+ // in liboboe.
+ usleep(10 * 1000);
+
+ if (stm->playerObj) {
+ (*stm->playerObj)->Destroy(stm->playerObj);
+ stm->playerObj = nullptr;
+ stm->play = nullptr;
+ stm->bufq = nullptr;
+ for (uint32_t i = 0; i < stm->queuebuf_capacity; ++i) {
+ free(stm->queuebuf[i]);
+ }
+ }
+
+ if (stm->recorderObj) {
+ int r = opensl_destroy_recorder(stm);
+ assert(r == CUBEB_OK);
+ }
+
+ if (stm->resampler) {
+ cubeb_resampler_destroy(stm->resampler);
+ }
+
+ pthread_mutex_destroy(&stm->mutex);
+
+ LOG("Cubeb stream (%p) destroyed", stm);
+ free(stm);
+}
+
+static int
+opensl_stream_get_position(cubeb_stream * stm, uint64_t * position)
+{
+ SLmillisecond msec;
+ uint32_t compensation_msec = 0;
+ SLresult res;
+
+ res = (*stm->play)->GetPosition(stm->play, &msec);
+ if (res != SL_RESULT_SUCCESS) {
+ return CUBEB_ERROR;
+ }
+
+ timespec t{};
+ clock_gettime(CLOCK_MONOTONIC, &t);
+ if (stm->lastPosition == msec) {
+ compensation_msec =
+ (t.tv_sec * 1000000000LL + t.tv_nsec - stm->lastPositionTimeStamp) /
+ 1000000;
+ } else {
+ stm->lastPositionTimeStamp = t.tv_sec * 1000000000LL + t.tv_nsec;
+ stm->lastPosition = msec;
+ }
+
+ uint64_t samplerate = stm->user_output_rate;
+ uint32_t output_latency = stm->output_latency_ms;
+
+ pthread_mutex_lock(&stm->mutex);
+ int64_t maximum_position = stm->written * (int64_t)stm->user_output_rate /
+ stm->output_configured_rate;
+ pthread_mutex_unlock(&stm->mutex);
+ assert(maximum_position >= 0);
+
+ if (msec > output_latency) {
+ int64_t unadjusted_position;
+ if (stm->lastCompensativePosition > msec + compensation_msec) {
+ // Over compensation, use lastCompensativePosition.
+ unadjusted_position =
+ samplerate * (stm->lastCompensativePosition - output_latency) / 1000;
+ } else {
+ unadjusted_position =
+ samplerate * (msec - output_latency + compensation_msec) / 1000;
+ stm->lastCompensativePosition = msec + compensation_msec;
+ }
+ *position = unadjusted_position < maximum_position ? unadjusted_position
+ : maximum_position;
+ } else {
+ *position = 0;
+ }
+ return CUBEB_OK;
+}
+
+static int
+opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
+{
+ assert(stm);
+ assert(latency);
+
+ uint32_t stream_latency_frames =
+ stm->user_output_rate * stm->output_latency_ms / 1000;
+
+ *latency = static_cast<int>(stream_latency_frames +
+ cubeb_resampler_latency(stm->resampler));
+
+ return CUBEB_OK;
+}
+
+int
+opensl_stream_set_volume(cubeb_stream * stm, float volume)
+{
+ SLresult res;
+ SLmillibel max_level, millibels;
+ float unclamped_millibels;
+
+ res = (*stm->volume)->GetMaxVolumeLevel(stm->volume, &max_level);
+
+ if (res != SL_RESULT_SUCCESS) {
+ return CUBEB_ERROR;
+ }
+
+ /* millibels are 100*dB, so the conversion from the volume's linear amplitude
+ * is 100 * 20 * log(volume). However we clamp the resulting value before
+ * passing it to lroundf() in order to prevent it from silently returning an
+ * erroneous value when the unclamped value exceeds the size of a long. */
+ unclamped_millibels = 100.0f * 20.0f * log10f(fmaxf(volume, 0.0f));
+ unclamped_millibels = fmaxf(unclamped_millibels, SL_MILLIBEL_MIN);
+ unclamped_millibels = fminf(unclamped_millibels, max_level);
+
+ millibels = static_cast<SLmillibel>(lroundf(unclamped_millibels));
+
+ res = (*stm->volume)->SetVolumeLevel(stm->volume, millibels);
+
+ if (res != SL_RESULT_SUCCESS) {
+ return CUBEB_ERROR;
+ }
+ return CUBEB_OK;
+}
+
+struct cubeb_ops const opensl_ops = {
+ .init = opensl_init,
+ .get_backend_id = opensl_get_backend_id,
+ .get_max_channel_count = opensl_get_max_channel_count,
+ .get_min_latency = nullptr,
+ .get_preferred_sample_rate = nullptr,
+ .get_supported_input_processing_params = nullptr,
+ .enumerate_devices = nullptr,
+ .device_collection_destroy = nullptr,
+ .destroy = opensl_destroy,
+ .stream_init = opensl_stream_init,
+ .stream_destroy = opensl_stream_destroy,
+ .stream_start = opensl_stream_start,
+ .stream_stop = opensl_stream_stop,
+ .stream_get_position = opensl_stream_get_position,
+ .stream_get_latency = opensl_stream_get_latency,
+ .stream_get_input_latency = nullptr,
+ .stream_set_volume = opensl_stream_set_volume,
+ .stream_set_name = nullptr,
+ .stream_get_current_device = nullptr,
+ .stream_set_input_mute = nullptr,
+ .stream_set_input_processing_params = nullptr,
+ .stream_device_destroy = nullptr,
+ .stream_register_device_changed_callback = nullptr,
+ .register_device_collection_changed = nullptr};
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_oss.c b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_oss.c
new file mode 100644
index 0000000000..3d09ba4db2
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_oss.c
@@ -0,0 +1,1356 @@
+/*
+ * Copyright © 2019-2020 Nia Alarie <nia@NetBSD.org>
+ * Copyright © 2020 Ka Ho Ng <khng300@gmail.com>
+ * Copyright © 2020 The FreeBSD Foundation
+ *
+ * Portions of this software were developed by Ka Ho Ng
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_mixer.h"
+#include "cubeb_strings.h"
+#include "cubeb_tracing.h"
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <poll.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/soundcard.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+/* Supported well by most hardware. */
+#ifndef OSS_PREFER_RATE
+#define OSS_PREFER_RATE (48000)
+#endif
+
+/* Standard acceptable minimum. */
+#ifndef OSS_LATENCY_MS
+#define OSS_LATENCY_MS (8)
+#endif
+
+#ifndef OSS_NFRAGS
+#define OSS_NFRAGS (4)
+#endif
+
+#ifndef OSS_DEFAULT_DEVICE
+#define OSS_DEFAULT_DEVICE "/dev/dsp"
+#endif
+
+#ifndef OSS_DEFAULT_MIXER
+#define OSS_DEFAULT_MIXER "/dev/mixer"
+#endif
+
+#define ENV_AUDIO_DEVICE "AUDIO_DEVICE"
+
+#ifndef OSS_MAX_CHANNELS
+#if defined(__FreeBSD__) || defined(__DragonFly__)
+/*
+ * The current maximum number of channels supported
+ * on FreeBSD is 8.
+ *
+ * Reference: FreeBSD 12.1-RELEASE
+ */
+#define OSS_MAX_CHANNELS (8)
+#elif defined(__sun__)
+/*
+ * The current maximum number of channels supported
+ * on Illumos is 16.
+ *
+ * Reference: PSARC 2008/318
+ */
+#define OSS_MAX_CHANNELS (16)
+#else
+#define OSS_MAX_CHANNELS (2)
+#endif
+#endif
+
+#if defined(__FreeBSD__) || defined(__DragonFly__)
+#define SNDSTAT_BEGIN_STR "Installed devices:"
+#define SNDSTAT_USER_BEGIN_STR "Installed devices from userspace:"
+#define SNDSTAT_FV_BEGIN_STR "File Versions:"
+#endif
+
+static struct cubeb_ops const oss_ops;
+
+struct cubeb {
+ struct cubeb_ops const * ops;
+
+ /* Our intern string store */
+ pthread_mutex_t mutex; /* protects devid_strs */
+ cubeb_strings * devid_strs;
+};
+
+struct oss_stream {
+ oss_devnode_t name;
+ int fd;
+ void * buf;
+ unsigned int bufframes;
+ unsigned int maxframes;
+
+ struct stream_info {
+ int channels;
+ int sample_rate;
+ int fmt;
+ int precision;
+ } info;
+
+ unsigned int frame_size; /* precision in bytes * channels */
+ bool floating;
+};
+
+struct cubeb_stream {
+ struct cubeb * context;
+ void * user_ptr;
+ pthread_t thread;
+ bool doorbell; /* (m) */
+ pthread_cond_t doorbell_cv; /* (m) */
+ pthread_cond_t stopped_cv; /* (m) */
+ pthread_mutex_t mtx; /* Members protected by this should be marked (m) */
+ bool thread_created; /* (m) */
+ bool running; /* (m) */
+ bool destroying; /* (m) */
+ cubeb_state state; /* (m) */
+ float volume /* (m) */;
+ struct oss_stream play;
+ struct oss_stream record;
+ cubeb_data_callback data_cb;
+ cubeb_state_callback state_cb;
+ uint64_t frames_written /* (m) */;
+};
+
+static char const *
+oss_cubeb_devid_intern(cubeb * context, char const * devid)
+{
+ char const * is;
+ pthread_mutex_lock(&context->mutex);
+ is = cubeb_strings_intern(context->devid_strs, devid);
+ pthread_mutex_unlock(&context->mutex);
+ return is;
+}
+
+int
+oss_init(cubeb ** context, char const * context_name)
+{
+ cubeb * c;
+
+ (void)context_name;
+ if ((c = calloc(1, sizeof(cubeb))) == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ if (cubeb_strings_init(&c->devid_strs) == CUBEB_ERROR) {
+ goto fail;
+ }
+
+ if (pthread_mutex_init(&c->mutex, NULL) != 0) {
+ goto fail;
+ }
+
+ c->ops = &oss_ops;
+ *context = c;
+ return CUBEB_OK;
+
+fail:
+ cubeb_strings_destroy(c->devid_strs);
+ free(c);
+ return CUBEB_ERROR;
+}
+
+static void
+oss_destroy(cubeb * context)
+{
+ pthread_mutex_destroy(&context->mutex);
+ cubeb_strings_destroy(context->devid_strs);
+ free(context);
+}
+
+static char const *
+oss_get_backend_id(cubeb * context)
+{
+ return "oss";
+}
+
+static int
+oss_get_preferred_sample_rate(cubeb * context, uint32_t * rate)
+{
+ (void)context;
+
+ *rate = OSS_PREFER_RATE;
+ return CUBEB_OK;
+}
+
+static int
+oss_get_max_channel_count(cubeb * context, uint32_t * max_channels)
+{
+ (void)context;
+
+ *max_channels = OSS_MAX_CHANNELS;
+ return CUBEB_OK;
+}
+
+static int
+oss_get_min_latency(cubeb * context, cubeb_stream_params params,
+ uint32_t * latency_frames)
+{
+ (void)context;
+
+ *latency_frames = (OSS_LATENCY_MS * params.rate) / 1000;
+ return CUBEB_OK;
+}
+
+static void
+oss_free_cubeb_device_info_strings(cubeb_device_info * cdi)
+{
+ free((char *)cdi->device_id);
+ free((char *)cdi->friendly_name);
+ free((char *)cdi->group_id);
+ cdi->device_id = NULL;
+ cdi->friendly_name = NULL;
+ cdi->group_id = NULL;
+}
+
+#if defined(__FreeBSD__) || defined(__DragonFly__)
+/*
+ * Check if the specified DSP is okay for the purpose specified
+ * in type. Here type can only specify one operation each time
+ * this helper is called.
+ *
+ * Return 0 if OK, otherwise 1.
+ */
+static int
+oss_probe_open(const char * dsppath, cubeb_device_type type, int * fdp,
+ oss_audioinfo * resai)
+{
+ oss_audioinfo ai;
+ int error;
+ int oflags = (type == CUBEB_DEVICE_TYPE_INPUT) ? O_RDONLY : O_WRONLY;
+ int dspfd = open(dsppath, oflags);
+ if (dspfd == -1)
+ return 1;
+
+ ai.dev = -1;
+ error = ioctl(dspfd, SNDCTL_AUDIOINFO, &ai);
+ if (error < 0) {
+ close(dspfd);
+ return 1;
+ }
+
+ if (resai)
+ *resai = ai;
+ if (fdp)
+ *fdp = dspfd;
+ else
+ close(dspfd);
+ return 0;
+}
+
+struct sndstat_info {
+ oss_devnode_t devname;
+ const char * desc;
+ cubeb_device_type type;
+ int preferred;
+};
+
+static int
+oss_sndstat_line_parse(char * line, int is_ud, struct sndstat_info * sinfo)
+{
+ char *matchptr = line, *n = NULL;
+ struct sndstat_info res;
+
+ memset(&res, 0, sizeof(res));
+
+ n = strchr(matchptr, ':');
+ if (n == NULL)
+ goto fail;
+ if (is_ud == 0) {
+ unsigned int devunit;
+
+ if (sscanf(matchptr, "pcm%u: ", &devunit) < 1)
+ goto fail;
+
+ if (snprintf(res.devname, sizeof(res.devname), "/dev/dsp%u", devunit) < 1)
+ goto fail;
+ } else {
+ if (n - matchptr >= (ssize_t)(sizeof(res.devname) - strlen("/dev/")))
+ goto fail;
+
+ strlcpy(res.devname, "/dev/", sizeof(res.devname));
+ strncat(res.devname, matchptr, n - matchptr);
+ }
+ matchptr = n + 1;
+
+ n = strchr(matchptr, '<');
+ if (n == NULL)
+ goto fail;
+ matchptr = n + 1;
+ n = strrchr(matchptr, '>');
+ if (n == NULL)
+ goto fail;
+ *n = 0;
+ res.desc = matchptr;
+ matchptr = n + 1;
+
+ n = strchr(matchptr, '(');
+ if (n == NULL)
+ goto fail;
+ matchptr = n + 1;
+ n = strrchr(matchptr, ')');
+ if (n == NULL)
+ goto fail;
+ *n = 0;
+ if (!isdigit(matchptr[0])) {
+ if (strstr(matchptr, "play") != NULL)
+ res.type |= CUBEB_DEVICE_TYPE_OUTPUT;
+ if (strstr(matchptr, "rec") != NULL)
+ res.type |= CUBEB_DEVICE_TYPE_INPUT;
+ } else {
+ int p, r;
+ if (sscanf(matchptr, "%dp:%*dv/%dr:%*dv", &p, &r) != 2)
+ goto fail;
+ if (p > 0)
+ res.type |= CUBEB_DEVICE_TYPE_OUTPUT;
+ if (r > 0)
+ res.type |= CUBEB_DEVICE_TYPE_INPUT;
+ }
+ matchptr = n + 1;
+ if (strstr(matchptr, "default") != NULL)
+ res.preferred = 1;
+
+ *sinfo = res;
+ return 0;
+
+fail:
+ return 1;
+}
+
+/*
+ * XXX: On FreeBSD we have to rely on SNDCTL_CARDINFO to get all
+ * the usable audio devices currently, as SNDCTL_AUDIOINFO will
+ * never return directly usable audio device nodes.
+ */
+static int
+oss_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection)
+{
+ cubeb_device_info * devinfop = NULL;
+ char * line = NULL;
+ size_t linecap = 0;
+ FILE * sndstatfp = NULL;
+ int collection_cnt = 0;
+ int is_ud = 0;
+ int skipall = 0;
+
+ devinfop = calloc(1, sizeof(cubeb_device_info));
+ if (devinfop == NULL)
+ goto fail;
+
+ sndstatfp = fopen("/dev/sndstat", "r");
+ if (sndstatfp == NULL)
+ goto fail;
+ while (getline(&line, &linecap, sndstatfp) > 0) {
+ const char * devid = NULL;
+ struct sndstat_info sinfo;
+ oss_audioinfo ai;
+
+ if (!strncmp(line, SNDSTAT_FV_BEGIN_STR, strlen(SNDSTAT_FV_BEGIN_STR))) {
+ skipall = 1;
+ continue;
+ }
+ if (!strncmp(line, SNDSTAT_BEGIN_STR, strlen(SNDSTAT_BEGIN_STR))) {
+ is_ud = 0;
+ skipall = 0;
+ continue;
+ }
+ if (!strncmp(line, SNDSTAT_USER_BEGIN_STR,
+ strlen(SNDSTAT_USER_BEGIN_STR))) {
+ is_ud = 1;
+ skipall = 0;
+ continue;
+ }
+ if (skipall || isblank(line[0]))
+ continue;
+
+ if (oss_sndstat_line_parse(line, is_ud, &sinfo))
+ continue;
+
+ devinfop[collection_cnt].type = 0;
+ switch (sinfo.type) {
+ case CUBEB_DEVICE_TYPE_INPUT:
+ if (type & CUBEB_DEVICE_TYPE_OUTPUT)
+ continue;
+ break;
+ case CUBEB_DEVICE_TYPE_OUTPUT:
+ if (type & CUBEB_DEVICE_TYPE_INPUT)
+ continue;
+ break;
+ case 0:
+ continue;
+ }
+
+ if (oss_probe_open(sinfo.devname, type, NULL, &ai))
+ continue;
+
+ devid = oss_cubeb_devid_intern(context, sinfo.devname);
+ if (devid == NULL)
+ continue;
+
+ devinfop[collection_cnt].device_id = strdup(sinfo.devname);
+ asprintf((char **)&devinfop[collection_cnt].friendly_name, "%s: %s",
+ sinfo.devname, sinfo.desc);
+ devinfop[collection_cnt].group_id = strdup(sinfo.devname);
+ devinfop[collection_cnt].vendor_name = NULL;
+ if (devinfop[collection_cnt].device_id == NULL ||
+ devinfop[collection_cnt].friendly_name == NULL ||
+ devinfop[collection_cnt].group_id == NULL) {
+ oss_free_cubeb_device_info_strings(&devinfop[collection_cnt]);
+ continue;
+ }
+
+ devinfop[collection_cnt].type = type;
+ devinfop[collection_cnt].devid = devid;
+ devinfop[collection_cnt].state = CUBEB_DEVICE_STATE_ENABLED;
+ devinfop[collection_cnt].preferred =
+ (sinfo.preferred) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
+ devinfop[collection_cnt].format = CUBEB_DEVICE_FMT_S16NE;
+ devinfop[collection_cnt].default_format = CUBEB_DEVICE_FMT_S16NE;
+ devinfop[collection_cnt].max_channels = ai.max_channels;
+ devinfop[collection_cnt].default_rate = OSS_PREFER_RATE;
+ devinfop[collection_cnt].max_rate = ai.max_rate;
+ devinfop[collection_cnt].min_rate = ai.min_rate;
+ devinfop[collection_cnt].latency_lo = 0;
+ devinfop[collection_cnt].latency_hi = 0;
+
+ collection_cnt++;
+
+ void * newp =
+ reallocarray(devinfop, collection_cnt + 1, sizeof(cubeb_device_info));
+ if (newp == NULL)
+ goto fail;
+ devinfop = newp;
+ }
+
+ free(line);
+ fclose(sndstatfp);
+
+ collection->count = collection_cnt;
+ collection->device = devinfop;
+
+ return CUBEB_OK;
+
+fail:
+ free(line);
+ if (sndstatfp)
+ fclose(sndstatfp);
+ free(devinfop);
+ return CUBEB_ERROR;
+}
+
+#else
+
+static int
+oss_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection)
+{
+ oss_sysinfo si;
+ int error, i;
+ cubeb_device_info * devinfop = NULL;
+ int collection_cnt = 0;
+ int mixer_fd = -1;
+
+ mixer_fd = open(OSS_DEFAULT_MIXER, O_RDWR);
+ if (mixer_fd == -1) {
+ LOG("Failed to open mixer %s. errno: %d", OSS_DEFAULT_MIXER, errno);
+ return CUBEB_ERROR;
+ }
+
+ error = ioctl(mixer_fd, SNDCTL_SYSINFO, &si);
+ if (error) {
+ LOG("Failed to run SNDCTL_SYSINFO on mixer %s. errno: %d",
+ OSS_DEFAULT_MIXER, errno);
+ goto fail;
+ }
+
+ devinfop = calloc(si.numaudios, sizeof(cubeb_device_info));
+ if (devinfop == NULL)
+ goto fail;
+
+ collection->count = 0;
+ for (i = 0; i < si.numaudios; i++) {
+ oss_audioinfo ai;
+ cubeb_device_info cdi = {0};
+ const char * devid = NULL;
+
+ ai.dev = i;
+ error = ioctl(mixer_fd, SNDCTL_AUDIOINFO, &ai);
+ if (error)
+ goto fail;
+
+ assert(ai.dev < si.numaudios);
+ if (!ai.enabled)
+ continue;
+
+ cdi.type = 0;
+ switch (ai.caps & DSP_CAP_DUPLEX) {
+ case DSP_CAP_INPUT:
+ if (type & CUBEB_DEVICE_TYPE_OUTPUT)
+ continue;
+ break;
+ case DSP_CAP_OUTPUT:
+ if (type & CUBEB_DEVICE_TYPE_INPUT)
+ continue;
+ break;
+ case 0:
+ continue;
+ }
+ cdi.type = type;
+
+ devid = oss_cubeb_devid_intern(context, ai.devnode);
+ cdi.device_id = strdup(ai.name);
+ cdi.friendly_name = strdup(ai.name);
+ cdi.group_id = strdup(ai.name);
+ if (devid == NULL || cdi.device_id == NULL || cdi.friendly_name == NULL ||
+ cdi.group_id == NULL) {
+ oss_free_cubeb_device_info_strings(&cdi);
+ continue;
+ }
+
+ cdi.devid = devid;
+ cdi.vendor_name = NULL;
+ cdi.state = CUBEB_DEVICE_STATE_ENABLED;
+ cdi.preferred = CUBEB_DEVICE_PREF_NONE;
+ cdi.format = CUBEB_DEVICE_FMT_S16NE;
+ cdi.default_format = CUBEB_DEVICE_FMT_S16NE;
+ cdi.max_channels = ai.max_channels;
+ cdi.default_rate = OSS_PREFER_RATE;
+ cdi.max_rate = ai.max_rate;
+ cdi.min_rate = ai.min_rate;
+ cdi.latency_lo = 0;
+ cdi.latency_hi = 0;
+
+ devinfop[collection_cnt++] = cdi;
+ }
+
+ collection->count = collection_cnt;
+ collection->device = devinfop;
+
+ if (mixer_fd != -1)
+ close(mixer_fd);
+ return CUBEB_OK;
+
+fail:
+ if (mixer_fd != -1)
+ close(mixer_fd);
+ free(devinfop);
+ return CUBEB_ERROR;
+}
+
+#endif
+
+static int
+oss_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection)
+{
+ size_t i;
+ for (i = 0; i < collection->count; i++) {
+ oss_free_cubeb_device_info_strings(&collection->device[i]);
+ }
+ free(collection->device);
+ collection->device = NULL;
+ collection->count = 0;
+ return 0;
+}
+
+static unsigned int
+oss_chn_from_cubeb(cubeb_channel chn)
+{
+ switch (chn) {
+ case CHANNEL_FRONT_LEFT:
+ return CHID_L;
+ case CHANNEL_FRONT_RIGHT:
+ return CHID_R;
+ case CHANNEL_FRONT_CENTER:
+ return CHID_C;
+ case CHANNEL_LOW_FREQUENCY:
+ return CHID_LFE;
+ case CHANNEL_BACK_LEFT:
+ return CHID_LR;
+ case CHANNEL_BACK_RIGHT:
+ return CHID_RR;
+ case CHANNEL_SIDE_LEFT:
+ return CHID_LS;
+ case CHANNEL_SIDE_RIGHT:
+ return CHID_RS;
+ default:
+ return CHID_UNDEF;
+ }
+}
+
+static unsigned long long
+oss_cubeb_layout_to_chnorder(cubeb_channel_layout layout)
+{
+ unsigned int i, nchns = 0;
+ unsigned long long chnorder = 0;
+
+ for (i = 0; layout; i++, layout >>= 1) {
+ unsigned long long chid = oss_chn_from_cubeb((layout & 1) << i);
+ if (chid == CHID_UNDEF)
+ continue;
+
+ chnorder |= (chid & 0xf) << nchns * 4;
+ nchns++;
+ }
+
+ return chnorder;
+}
+
+static int
+oss_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params,
+ struct stream_info * sinfo)
+{
+ unsigned long long chnorder;
+
+ sinfo->channels = params->channels;
+ sinfo->sample_rate = params->rate;
+ switch (params->format) {
+ case CUBEB_SAMPLE_S16LE:
+ sinfo->fmt = AFMT_S16_LE;
+ sinfo->precision = 16;
+ break;
+ case CUBEB_SAMPLE_S16BE:
+ sinfo->fmt = AFMT_S16_BE;
+ sinfo->precision = 16;
+ break;
+ case CUBEB_SAMPLE_FLOAT32NE:
+ sinfo->fmt = AFMT_S32_NE;
+ sinfo->precision = 32;
+ break;
+ default:
+ LOG("Unsupported format");
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+ if (ioctl(fd, SNDCTL_DSP_CHANNELS, &sinfo->channels) == -1) {
+ return CUBEB_ERROR;
+ }
+ if (ioctl(fd, SNDCTL_DSP_SETFMT, &sinfo->fmt) == -1) {
+ return CUBEB_ERROR;
+ }
+ if (ioctl(fd, SNDCTL_DSP_SPEED, &sinfo->sample_rate) == -1) {
+ return CUBEB_ERROR;
+ }
+ /* Mono layout is an exception */
+ if (params->layout != CUBEB_LAYOUT_UNDEFINED &&
+ params->layout != CUBEB_LAYOUT_MONO) {
+ chnorder = oss_cubeb_layout_to_chnorder(params->layout);
+ if (ioctl(fd, SNDCTL_DSP_SET_CHNORDER, &chnorder) == -1)
+ LOG("Non-fatal error %d occured when setting channel order.", errno);
+ }
+ return CUBEB_OK;
+}
+
+static int
+oss_stream_stop(cubeb_stream * s)
+{
+ pthread_mutex_lock(&s->mtx);
+ if (s->thread_created && s->running) {
+ s->running = false;
+ s->doorbell = false;
+ pthread_cond_wait(&s->stopped_cv, &s->mtx);
+ }
+ if (s->state != CUBEB_STATE_STOPPED) {
+ s->state = CUBEB_STATE_STOPPED;
+ pthread_mutex_unlock(&s->mtx);
+ s->state_cb(s, s->user_ptr, CUBEB_STATE_STOPPED);
+ } else {
+ pthread_mutex_unlock(&s->mtx);
+ }
+ return CUBEB_OK;
+}
+
+static void
+oss_stream_destroy(cubeb_stream * s)
+{
+ pthread_mutex_lock(&s->mtx);
+ if (s->thread_created) {
+ s->destroying = true;
+ s->doorbell = true;
+ pthread_cond_signal(&s->doorbell_cv);
+ }
+ pthread_mutex_unlock(&s->mtx);
+ pthread_join(s->thread, NULL);
+
+ pthread_cond_destroy(&s->doorbell_cv);
+ pthread_cond_destroy(&s->stopped_cv);
+ pthread_mutex_destroy(&s->mtx);
+ if (s->play.fd != -1) {
+ close(s->play.fd);
+ }
+ if (s->record.fd != -1) {
+ close(s->record.fd);
+ }
+ free(s->play.buf);
+ free(s->record.buf);
+ free(s);
+}
+
+static void
+oss_float_to_linear32(void * buf, unsigned sample_count, float vol)
+{
+ float * in = buf;
+ int32_t * out = buf;
+ int32_t * tail = out + sample_count;
+
+ while (out < tail) {
+ int64_t f = *(in++) * vol * 0x80000000LL;
+ if (f < -INT32_MAX)
+ f = -INT32_MAX;
+ else if (f > INT32_MAX)
+ f = INT32_MAX;
+ *(out++) = f;
+ }
+}
+
+static void
+oss_linear32_to_float(void * buf, unsigned sample_count)
+{
+ int32_t * in = buf;
+ float * out = buf;
+ float * tail = out + sample_count;
+
+ while (out < tail) {
+ *(out++) = (1.0 / 0x80000000LL) * *(in++);
+ }
+}
+
+static void
+oss_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol)
+{
+ unsigned i;
+ int32_t multiplier = vol * 0x8000;
+
+ for (i = 0; i < sample_count; ++i) {
+ buf[i] = (buf[i] * multiplier) >> 15;
+ }
+}
+
+static int
+oss_get_rec_frames(cubeb_stream * s, unsigned int nframes)
+{
+ size_t rem = nframes * s->record.frame_size;
+ size_t read_ofs = 0;
+ while (rem > 0) {
+ ssize_t n;
+ if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs, rem)) <
+ 0) {
+ if (errno == EINTR)
+ continue;
+ return CUBEB_ERROR;
+ }
+ read_ofs += n;
+ rem -= n;
+ }
+ return 0;
+}
+
+static int
+oss_put_play_frames(cubeb_stream * s, unsigned int nframes)
+{
+ size_t rem = nframes * s->play.frame_size;
+ size_t write_ofs = 0;
+ while (rem > 0) {
+ ssize_t n;
+ if ((n = write(s->play.fd, (uint8_t *)s->play.buf + write_ofs, rem)) < 0) {
+ if (errno == EINTR)
+ continue;
+ return CUBEB_ERROR;
+ }
+ pthread_mutex_lock(&s->mtx);
+ s->frames_written += n / s->play.frame_size;
+ pthread_mutex_unlock(&s->mtx);
+ write_ofs += n;
+ rem -= n;
+ }
+ return 0;
+}
+
+static int
+oss_wait_fds_for_space(cubeb_stream * s, long * nfrp)
+{
+ audio_buf_info bi;
+ struct pollfd pfds[2];
+ long nfr, tnfr;
+ int i;
+
+ assert(s->play.fd != -1 || s->record.fd != -1);
+ pfds[0].events = POLLOUT | POLLHUP;
+ pfds[0].revents = 0;
+ pfds[0].fd = s->play.fd;
+ pfds[1].events = POLLIN | POLLHUP;
+ pfds[1].revents = 0;
+ pfds[1].fd = s->record.fd;
+
+retry:
+ nfr = LONG_MAX;
+
+ if (poll(pfds, 2, 1000) == -1) {
+ return CUBEB_ERROR;
+ }
+
+ for (i = 0; i < 2; i++) {
+ if (pfds[i].revents & POLLHUP) {
+ return CUBEB_ERROR;
+ }
+ }
+
+ if (s->play.fd != -1) {
+ if (ioctl(s->play.fd, SNDCTL_DSP_GETOSPACE, &bi) == -1) {
+ return CUBEB_STATE_ERROR;
+ }
+ tnfr = bi.bytes / s->play.frame_size;
+ if (tnfr <= 0) {
+ /* too little space - stop polling record, if any */
+ pfds[0].fd = s->play.fd;
+ pfds[1].fd = -1;
+ goto retry;
+ } else if (tnfr > (long)s->play.maxframes) {
+ /* too many frames available - limit */
+ tnfr = (long)s->play.maxframes;
+ }
+ if (nfr > tnfr) {
+ nfr = tnfr;
+ }
+ }
+ if (s->record.fd != -1) {
+ if (ioctl(s->record.fd, SNDCTL_DSP_GETISPACE, &bi) == -1) {
+ return CUBEB_STATE_ERROR;
+ }
+ tnfr = bi.bytes / s->record.frame_size;
+ if (tnfr <= 0) {
+ /* too little space - stop polling playback, if any */
+ pfds[0].fd = -1;
+ pfds[1].fd = s->record.fd;
+ goto retry;
+ } else if (tnfr > (long)s->record.maxframes) {
+ /* too many frames available - limit */
+ tnfr = (long)s->record.maxframes;
+ }
+ if (nfr > tnfr) {
+ nfr = tnfr;
+ }
+ }
+
+ *nfrp = nfr;
+ return 0;
+}
+
+/* 1 - Stopped by cubeb_stream_stop, otherwise 0 */
+static int
+oss_audio_loop(cubeb_stream * s, cubeb_state * new_state)
+{
+ cubeb_state state = CUBEB_STATE_STOPPED;
+ int trig = 0, drain = 0;
+ const bool play_on = s->play.fd != -1, record_on = s->record.fd != -1;
+ long nfr = 0;
+
+ if (record_on) {
+ if (ioctl(s->record.fd, SNDCTL_DSP_SETTRIGGER, &trig)) {
+ LOG("Error %d occured when setting trigger on record fd", errno);
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+
+ trig |= PCM_ENABLE_INPUT;
+ memset(s->record.buf, 0, s->record.bufframes * s->record.frame_size);
+
+ if (ioctl(s->record.fd, SNDCTL_DSP_SETTRIGGER, &trig) == -1) {
+ LOG("Error %d occured when setting trigger on record fd", errno);
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+ }
+
+ if (!play_on && !record_on) {
+ /*
+ * Stop here if the stream is not play & record stream,
+ * play-only stream or record-only stream
+ */
+
+ goto breakdown;
+ }
+
+ while (1) {
+ pthread_mutex_lock(&s->mtx);
+ if (!s->running || s->destroying) {
+ pthread_mutex_unlock(&s->mtx);
+ break;
+ }
+ pthread_mutex_unlock(&s->mtx);
+
+ long got = 0;
+ if (nfr > 0) {
+ if (record_on) {
+ if (oss_get_rec_frames(s, nfr) == CUBEB_ERROR) {
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+ if (s->record.floating) {
+ oss_linear32_to_float(s->record.buf, s->record.info.channels * nfr);
+ }
+ }
+
+ got = s->data_cb(s, s->user_ptr, s->record.buf, s->play.buf, nfr);
+ if (got == CUBEB_ERROR) {
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+ if (got < nfr) {
+ if (s->play.fd != -1) {
+ drain = 1;
+ } else {
+ /*
+ * This is a record-only stream and number of frames
+ * returned from data_cb() is smaller than number
+ * of frames required to read. Stop here.
+ */
+ state = CUBEB_STATE_STOPPED;
+ goto breakdown;
+ }
+ }
+
+ if (got > 0 && play_on) {
+ float vol;
+
+ pthread_mutex_lock(&s->mtx);
+ vol = s->volume;
+ pthread_mutex_unlock(&s->mtx);
+
+ if (s->play.floating) {
+ oss_float_to_linear32(s->play.buf, s->play.info.channels * got, vol);
+ } else {
+ oss_linear16_set_vol((int16_t *)s->play.buf,
+ s->play.info.channels * got, vol);
+ }
+ if (oss_put_play_frames(s, got) == CUBEB_ERROR) {
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+ }
+ if (drain) {
+ state = CUBEB_STATE_DRAINED;
+ goto breakdown;
+ }
+ }
+
+ if (oss_wait_fds_for_space(s, &nfr) != 0) {
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+ }
+
+ return 1;
+
+breakdown:
+ pthread_mutex_lock(&s->mtx);
+ *new_state = s->state = state;
+ s->running = false;
+ pthread_mutex_unlock(&s->mtx);
+ return 0;
+}
+
+static void *
+oss_io_routine(void * arg)
+{
+ cubeb_stream * s = arg;
+ cubeb_state new_state;
+ int stopped;
+
+ CUBEB_REGISTER_THREAD("cubeb rendering thread");
+
+ do {
+ pthread_mutex_lock(&s->mtx);
+ if (s->destroying) {
+ pthread_mutex_unlock(&s->mtx);
+ break;
+ }
+ pthread_mutex_unlock(&s->mtx);
+
+ stopped = oss_audio_loop(s, &new_state);
+ if (s->record.fd != -1)
+ ioctl(s->record.fd, SNDCTL_DSP_HALT_INPUT, NULL);
+ if (!stopped)
+ s->state_cb(s, s->user_ptr, new_state);
+
+ pthread_mutex_lock(&s->mtx);
+ pthread_cond_signal(&s->stopped_cv);
+ if (s->destroying) {
+ pthread_mutex_unlock(&s->mtx);
+ break;
+ }
+ while (!s->doorbell) {
+ pthread_cond_wait(&s->doorbell_cv, &s->mtx);
+ }
+ s->doorbell = false;
+ pthread_mutex_unlock(&s->mtx);
+ } while (1);
+
+ pthread_mutex_lock(&s->mtx);
+ s->thread_created = false;
+ pthread_mutex_unlock(&s->mtx);
+
+ CUBEB_UNREGISTER_THREAD();
+
+ return NULL;
+}
+
+static inline int
+oss_calc_frag_shift(unsigned int frames, unsigned int frame_size)
+{
+ int n = 4;
+ int blksize = frames * frame_size;
+ while ((1 << n) < blksize) {
+ n++;
+ }
+ return n;
+}
+
+static inline int
+oss_get_frag_params(unsigned int shift)
+{
+ return (OSS_NFRAGS << 16) | shift;
+}
+
+static int
+oss_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames, cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ int ret = CUBEB_OK;
+ cubeb_stream * s = NULL;
+ const char * defdsp;
+
+ if (!(defdsp = getenv(ENV_AUDIO_DEVICE)) || *defdsp == '\0')
+ defdsp = OSS_DEFAULT_DEVICE;
+
+ (void)stream_name;
+ if ((s = calloc(1, sizeof(cubeb_stream))) == NULL) {
+ ret = CUBEB_ERROR;
+ goto error;
+ }
+ s->state = CUBEB_STATE_STOPPED;
+ s->record.fd = s->play.fd = -1;
+ if (input_device != NULL) {
+ strlcpy(s->record.name, input_device, sizeof(s->record.name));
+ } else {
+ strlcpy(s->record.name, defdsp, sizeof(s->record.name));
+ }
+ if (output_device != NULL) {
+ strlcpy(s->play.name, output_device, sizeof(s->play.name));
+ } else {
+ strlcpy(s->play.name, defdsp, sizeof(s->play.name));
+ }
+ if (input_stream_params != NULL) {
+ unsigned int nb_channels;
+ uint32_t minframes;
+
+ if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ LOG("Loopback not supported");
+ ret = CUBEB_ERROR_NOT_SUPPORTED;
+ goto error;
+ }
+ nb_channels = cubeb_channel_layout_nb_channels(input_stream_params->layout);
+ if (input_stream_params->layout != CUBEB_LAYOUT_UNDEFINED &&
+ nb_channels != input_stream_params->channels) {
+ LOG("input_stream_params->layout does not match "
+ "input_stream_params->channels");
+ ret = CUBEB_ERROR_INVALID_PARAMETER;
+ goto error;
+ }
+ if ((s->record.fd = open(s->record.name, O_RDONLY)) == -1) {
+ LOG("Audio device \"%s\" could not be opened as read-only",
+ s->record.name);
+ ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
+ goto error;
+ }
+ if ((ret = oss_copy_params(s->record.fd, s, input_stream_params,
+ &s->record.info)) != CUBEB_OK) {
+ LOG("Setting record params failed");
+ goto error;
+ }
+ s->record.floating =
+ (input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
+ s->record.frame_size =
+ s->record.info.channels * (s->record.info.precision / 8);
+ s->record.bufframes = latency_frames;
+
+ oss_get_min_latency(context, *input_stream_params, &minframes);
+ if (s->record.bufframes < minframes) {
+ s->record.bufframes = minframes;
+ }
+ }
+ if (output_stream_params != NULL) {
+ unsigned int nb_channels;
+ uint32_t minframes;
+
+ if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ LOG("Loopback not supported");
+ ret = CUBEB_ERROR_NOT_SUPPORTED;
+ goto error;
+ }
+ nb_channels =
+ cubeb_channel_layout_nb_channels(output_stream_params->layout);
+ if (output_stream_params->layout != CUBEB_LAYOUT_UNDEFINED &&
+ nb_channels != output_stream_params->channels) {
+ LOG("output_stream_params->layout does not match "
+ "output_stream_params->channels");
+ ret = CUBEB_ERROR_INVALID_PARAMETER;
+ goto error;
+ }
+ if ((s->play.fd = open(s->play.name, O_WRONLY)) == -1) {
+ LOG("Audio device \"%s\" could not be opened as write-only",
+ s->play.name);
+ ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
+ goto error;
+ }
+ if ((ret = oss_copy_params(s->play.fd, s, output_stream_params,
+ &s->play.info)) != CUBEB_OK) {
+ LOG("Setting play params failed");
+ goto error;
+ }
+ s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
+ s->play.frame_size = s->play.info.channels * (s->play.info.precision / 8);
+ s->play.bufframes = latency_frames;
+
+ oss_get_min_latency(context, *output_stream_params, &minframes);
+ if (s->play.bufframes < minframes) {
+ s->play.bufframes = minframes;
+ }
+ }
+ if (s->play.fd != -1) {
+ int frag = oss_get_frag_params(
+ oss_calc_frag_shift(s->play.bufframes, s->play.frame_size));
+ if (ioctl(s->play.fd, SNDCTL_DSP_SETFRAGMENT, &frag))
+ LOG("Failed to set play fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x",
+ frag);
+ audio_buf_info bi;
+ if (ioctl(s->play.fd, SNDCTL_DSP_GETOSPACE, &bi))
+ LOG("Failed to get play fd's buffer info.");
+ else {
+ s->play.bufframes = (bi.fragsize * bi.fragstotal) / s->play.frame_size;
+ }
+ int lw;
+
+ /*
+ * Force 32 ms service intervals at most, or when recording is
+ * active, use the recording service intervals as a reference.
+ */
+ s->play.maxframes = (32 * output_stream_params->rate) / 1000;
+ if (s->record.fd != -1 || s->play.maxframes >= s->play.bufframes) {
+ lw = s->play.frame_size; /* Feed data when possible. */
+ s->play.maxframes = s->play.bufframes;
+ } else {
+ lw = (s->play.bufframes - s->play.maxframes) * s->play.frame_size;
+ }
+ if (ioctl(s->play.fd, SNDCTL_DSP_LOW_WATER, &lw))
+ LOG("Audio device \"%s\" (play) could not set trigger threshold",
+ s->play.name);
+ }
+ if (s->record.fd != -1) {
+ int frag = oss_get_frag_params(
+ oss_calc_frag_shift(s->record.bufframes, s->record.frame_size));
+ if (ioctl(s->record.fd, SNDCTL_DSP_SETFRAGMENT, &frag))
+ LOG("Failed to set record fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x",
+ frag);
+ audio_buf_info bi;
+ if (ioctl(s->record.fd, SNDCTL_DSP_GETISPACE, &bi))
+ LOG("Failed to get record fd's buffer info.");
+ else {
+ s->record.bufframes =
+ (bi.fragsize * bi.fragstotal) / s->record.frame_size;
+ }
+
+ s->record.maxframes = s->record.bufframes;
+ int lw = s->record.frame_size;
+ if (ioctl(s->record.fd, SNDCTL_DSP_LOW_WATER, &lw))
+ LOG("Audio device \"%s\" (record) could not set trigger threshold",
+ s->record.name);
+ }
+ s->context = context;
+ s->volume = 1.0;
+ s->state_cb = state_callback;
+ s->data_cb = data_callback;
+ s->user_ptr = user_ptr;
+
+ if (pthread_mutex_init(&s->mtx, NULL) != 0) {
+ LOG("Failed to create mutex");
+ goto error;
+ }
+ if (pthread_cond_init(&s->doorbell_cv, NULL) != 0) {
+ LOG("Failed to create cv");
+ goto error;
+ }
+ if (pthread_cond_init(&s->stopped_cv, NULL) != 0) {
+ LOG("Failed to create cv");
+ goto error;
+ }
+ s->doorbell = false;
+
+ if (s->play.fd != -1) {
+ if ((s->play.buf = calloc(s->play.bufframes, s->play.frame_size)) == NULL) {
+ ret = CUBEB_ERROR;
+ goto error;
+ }
+ }
+ if (s->record.fd != -1) {
+ if ((s->record.buf = calloc(s->record.bufframes, s->record.frame_size)) ==
+ NULL) {
+ ret = CUBEB_ERROR;
+ goto error;
+ }
+ }
+
+ *stream = s;
+ return CUBEB_OK;
+error:
+ if (s != NULL) {
+ oss_stream_destroy(s);
+ }
+ return ret;
+}
+
+static int
+oss_stream_thr_create(cubeb_stream * s)
+{
+ if (s->thread_created) {
+ s->doorbell = true;
+ pthread_cond_signal(&s->doorbell_cv);
+ return CUBEB_OK;
+ }
+
+ if (pthread_create(&s->thread, NULL, oss_io_routine, s) != 0) {
+ LOG("Couldn't create thread");
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+static int
+oss_stream_start(cubeb_stream * s)
+{
+ s->state_cb(s, s->user_ptr, CUBEB_STATE_STARTED);
+ pthread_mutex_lock(&s->mtx);
+ /* Disallow starting an already started stream */
+ assert(!s->running && s->state != CUBEB_STATE_STARTED);
+ if (oss_stream_thr_create(s) != CUBEB_OK) {
+ pthread_mutex_unlock(&s->mtx);
+ s->state_cb(s, s->user_ptr, CUBEB_STATE_ERROR);
+ return CUBEB_ERROR;
+ }
+ s->state = CUBEB_STATE_STARTED;
+ s->thread_created = true;
+ s->running = true;
+ pthread_mutex_unlock(&s->mtx);
+ return CUBEB_OK;
+}
+
+static int
+oss_stream_get_position(cubeb_stream * s, uint64_t * position)
+{
+ pthread_mutex_lock(&s->mtx);
+ *position = s->frames_written;
+ pthread_mutex_unlock(&s->mtx);
+ return CUBEB_OK;
+}
+
+static int
+oss_stream_get_latency(cubeb_stream * s, uint32_t * latency)
+{
+ int delay;
+
+ if (ioctl(s->play.fd, SNDCTL_DSP_GETODELAY, &delay) == -1) {
+ return CUBEB_ERROR;
+ }
+
+ /* Return number of frames there */
+ *latency = delay / s->play.frame_size;
+ return CUBEB_OK;
+}
+
+static int
+oss_stream_set_volume(cubeb_stream * stream, float volume)
+{
+ if (volume < 0.0)
+ volume = 0.0;
+ else if (volume > 1.0)
+ volume = 1.0;
+ pthread_mutex_lock(&stream->mtx);
+ stream->volume = volume;
+ pthread_mutex_unlock(&stream->mtx);
+ return CUBEB_OK;
+}
+
+static int
+oss_get_current_device(cubeb_stream * stream, cubeb_device ** const device)
+{
+ *device = calloc(1, sizeof(cubeb_device));
+ if (*device == NULL) {
+ return CUBEB_ERROR;
+ }
+ (*device)->input_name =
+ stream->record.fd != -1 ? strdup(stream->record.name) : NULL;
+ (*device)->output_name =
+ stream->play.fd != -1 ? strdup(stream->play.name) : NULL;
+ return CUBEB_OK;
+}
+
+static int
+oss_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
+{
+ (void)stream;
+ free(device->input_name);
+ free(device->output_name);
+ free(device);
+ return CUBEB_OK;
+}
+
+static struct cubeb_ops const oss_ops = {
+ .init = oss_init,
+ .get_backend_id = oss_get_backend_id,
+ .get_max_channel_count = oss_get_max_channel_count,
+ .get_min_latency = oss_get_min_latency,
+ .get_preferred_sample_rate = oss_get_preferred_sample_rate,
+ .get_supported_input_processing_params = NULL,
+ .enumerate_devices = oss_enumerate_devices,
+ .device_collection_destroy = oss_device_collection_destroy,
+ .destroy = oss_destroy,
+ .stream_init = oss_stream_init,
+ .stream_destroy = oss_stream_destroy,
+ .stream_start = oss_stream_start,
+ .stream_stop = oss_stream_stop,
+ .stream_get_position = oss_stream_get_position,
+ .stream_get_latency = oss_stream_get_latency,
+ .stream_get_input_latency = NULL,
+ .stream_set_volume = oss_stream_set_volume,
+ .stream_set_name = NULL,
+ .stream_get_current_device = oss_get_current_device,
+ .stream_set_input_mute = NULL,
+ .stream_set_input_processing_params = NULL,
+ .stream_device_destroy = oss_stream_device_destroy,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL};
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_osx_run_loop.cpp b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_osx_run_loop.cpp
new file mode 100644
index 0000000000..5828db6e95
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_osx_run_loop.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#include "cubeb_osx_run_loop.h"
+#include "cubeb_log.h"
+#include <AudioUnit/AudioUnit.h>
+#include <CoreAudio/AudioHardware.h>
+#include <CoreAudio/HostTime.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <cubeb/cubeb.h>
+
+void
+cubeb_set_coreaudio_notification_runloop()
+{
+ /* This is needed so that AudioUnit listeners get called on this thread, and
+ * not the main thread. If we don't do that, they are not called, or a crash
+ * occur, depending on the OSX version. */
+ AudioObjectPropertyAddress runloop_address = {
+ kAudioHardwarePropertyRunLoop, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+ CFRunLoopRef run_loop = nullptr;
+
+ OSStatus r;
+ r = AudioObjectSetPropertyData(kAudioObjectSystemObject, &runloop_address, 0,
+ NULL, sizeof(CFRunLoopRef), &run_loop);
+ if (r != noErr) {
+ LOG("Could not make global CoreAudio notifications use their own thread.");
+ }
+}
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_osx_run_loop.h b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_osx_run_loop.h
new file mode 100644
index 0000000000..8d88a37140
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_osx_run_loop.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright © 2014 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* On OSX 10.6 and after, the notification callbacks from the audio hardware are
+ * called on the main thread. Setting the kAudioHardwarePropertyRunLoop property
+ * to null tells the OSX to use a separate thread for that.
+ *
+ * This has to be called only once per process, so it is in a separate header
+ * for easy integration in other code bases. */
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+void
+cubeb_set_coreaudio_notification_runloop();
+
+#if defined(__cplusplus)
+}
+#endif
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_pulse.c b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_pulse.c
new file mode 100644
index 0000000000..c761487e4c
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_pulse.c
@@ -0,0 +1,1712 @@
+/*
+ * Copyright © 2011 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#undef NDEBUG
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_mixer.h"
+#include "cubeb_strings.h"
+#include <assert.h>
+#include <dlfcn.h>
+#include <pulse/pulseaudio.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef DISABLE_LIBPULSE_DLOPEN
+#define WRAP(x) x
+#else
+#define WRAP(x) (*cubeb_##x)
+#define LIBPULSE_API_VISIT(X) \
+ X(pa_channel_map_can_balance) \
+ X(pa_channel_map_init) \
+ X(pa_context_connect) \
+ X(pa_context_disconnect) \
+ X(pa_context_drain) \
+ X(pa_context_get_server_info) \
+ X(pa_context_get_sink_info_by_name) \
+ X(pa_context_get_sink_info_list) \
+ X(pa_context_get_sink_input_info) \
+ X(pa_context_get_source_info_list) \
+ X(pa_context_get_state) \
+ X(pa_context_new) \
+ X(pa_context_rttime_new) \
+ X(pa_context_set_sink_input_volume) \
+ X(pa_context_set_state_callback) \
+ X(pa_context_unref) \
+ X(pa_cvolume_set) \
+ X(pa_cvolume_set_balance) \
+ X(pa_frame_size) \
+ X(pa_operation_get_state) \
+ X(pa_operation_unref) \
+ X(pa_proplist_gets) \
+ X(pa_rtclock_now) \
+ X(pa_stream_begin_write) \
+ X(pa_stream_cancel_write) \
+ X(pa_stream_connect_playback) \
+ X(pa_stream_cork) \
+ X(pa_stream_disconnect) \
+ X(pa_stream_get_channel_map) \
+ X(pa_stream_get_index) \
+ X(pa_stream_get_latency) \
+ X(pa_stream_get_sample_spec) \
+ X(pa_stream_get_state) \
+ X(pa_stream_get_time) \
+ X(pa_stream_new) \
+ X(pa_stream_set_state_callback) \
+ X(pa_stream_set_write_callback) \
+ X(pa_stream_unref) \
+ X(pa_stream_update_timing_info) \
+ X(pa_stream_write) \
+ X(pa_sw_volume_from_linear) \
+ X(pa_threaded_mainloop_free) \
+ X(pa_threaded_mainloop_get_api) \
+ X(pa_threaded_mainloop_in_thread) \
+ X(pa_threaded_mainloop_lock) \
+ X(pa_threaded_mainloop_new) \
+ X(pa_threaded_mainloop_signal) \
+ X(pa_threaded_mainloop_start) \
+ X(pa_threaded_mainloop_stop) \
+ X(pa_threaded_mainloop_unlock) \
+ X(pa_threaded_mainloop_wait) \
+ X(pa_usec_to_bytes) \
+ X(pa_stream_set_read_callback) \
+ X(pa_stream_connect_record) \
+ X(pa_stream_readable_size) \
+ X(pa_stream_writable_size) \
+ X(pa_stream_peek) \
+ X(pa_stream_drop) \
+ X(pa_stream_get_buffer_attr) \
+ X(pa_stream_get_device_name) \
+ X(pa_context_set_subscribe_callback) \
+ X(pa_context_subscribe) \
+ X(pa_mainloop_api_once) \
+ X(pa_get_library_version) \
+ X(pa_channel_map_init_auto) \
+ X(pa_stream_set_name)
+
+#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
+LIBPULSE_API_VISIT(MAKE_TYPEDEF);
+#undef MAKE_TYPEDEF
+#endif
+
+#if PA_CHECK_VERSION(2, 0, 0)
+static int has_pulse_v2 = 0;
+#endif
+
+static struct cubeb_ops const pulse_ops;
+
+struct cubeb_default_sink_info {
+ pa_channel_map channel_map;
+ uint32_t sample_spec_rate;
+ pa_sink_flags_t flags;
+};
+
+struct cubeb {
+ struct cubeb_ops const * ops;
+ void * libpulse;
+ pa_threaded_mainloop * mainloop;
+ pa_context * context;
+ struct cubeb_default_sink_info * default_sink_info;
+ char * context_name;
+ int error;
+ cubeb_device_collection_changed_callback output_collection_changed_callback;
+ void * output_collection_changed_user_ptr;
+ cubeb_device_collection_changed_callback input_collection_changed_callback;
+ void * input_collection_changed_user_ptr;
+ cubeb_strings * device_ids;
+};
+
+struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
+ cubeb * context;
+ void * user_ptr;
+ /**/
+ pa_stream * output_stream;
+ pa_stream * input_stream;
+ cubeb_data_callback data_callback;
+ cubeb_state_callback state_callback;
+ pa_time_event * drain_timer;
+ pa_sample_spec output_sample_spec;
+ pa_sample_spec input_sample_spec;
+ int shutdown;
+ float volume;
+ cubeb_state state;
+};
+
+static const float PULSE_NO_GAIN = -1.0;
+
+enum cork_state { UNCORK = 0, CORK = 1 << 0, NOTIFY = 1 << 1 };
+
+static int
+intern_device_id(cubeb * ctx, char const ** id)
+{
+ char const * interned;
+
+ assert(ctx);
+ assert(id);
+
+ interned = cubeb_strings_intern(ctx->device_ids, *id);
+ if (!interned) {
+ return CUBEB_ERROR;
+ }
+
+ *id = interned;
+
+ return CUBEB_OK;
+}
+
+static void
+sink_info_callback(pa_context * context, const pa_sink_info * info, int eol,
+ void * u)
+{
+ (void)context;
+ cubeb * ctx = u;
+ if (!eol) {
+ free(ctx->default_sink_info);
+ ctx->default_sink_info = malloc(sizeof(struct cubeb_default_sink_info));
+ memcpy(&ctx->default_sink_info->channel_map, &info->channel_map,
+ sizeof(pa_channel_map));
+ ctx->default_sink_info->sample_spec_rate = info->sample_spec.rate;
+ ctx->default_sink_info->flags = info->flags;
+ }
+ WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
+}
+
+static void
+server_info_callback(pa_context * context, const pa_server_info * info,
+ void * u)
+{
+ pa_operation * o;
+ o = WRAP(pa_context_get_sink_info_by_name)(context, info->default_sink_name,
+ sink_info_callback, u);
+ if (o) {
+ WRAP(pa_operation_unref)(o);
+ }
+}
+
+static void
+context_state_callback(pa_context * c, void * u)
+{
+ cubeb * ctx = u;
+ if (!PA_CONTEXT_IS_GOOD(WRAP(pa_context_get_state)(c))) {
+ ctx->error = 1;
+ }
+ WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
+}
+
+static void
+context_notify_callback(pa_context * c, void * u)
+{
+ (void)c;
+ cubeb * ctx = u;
+ WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
+}
+
+static void
+stream_success_callback(pa_stream * s, int success, void * u)
+{
+ (void)s;
+ (void)success;
+ cubeb_stream * stm = u;
+ WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
+}
+
+static void
+stream_state_change_callback(cubeb_stream * stm, cubeb_state s)
+{
+ stm->state = s;
+ stm->state_callback(stm, stm->user_ptr, s);
+}
+
+static void
+stream_drain_callback(pa_mainloop_api * a, pa_time_event * e,
+ struct timeval const * tv, void * u)
+{
+ (void)a;
+ (void)tv;
+ cubeb_stream * stm = u;
+ assert(stm->drain_timer == e);
+ stream_state_change_callback(stm, CUBEB_STATE_DRAINED);
+ /* there's no pa_rttime_free, so use this instead. */
+ a->time_free(stm->drain_timer);
+ stm->drain_timer = NULL;
+ WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
+}
+
+static void
+stream_state_callback(pa_stream * s, void * u)
+{
+ cubeb_stream * stm = u;
+ if (!PA_STREAM_IS_GOOD(WRAP(pa_stream_get_state)(s))) {
+ stream_state_change_callback(stm, CUBEB_STATE_ERROR);
+ }
+ WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
+}
+
+static void
+trigger_user_callback(pa_stream * s, void const * input_data, size_t nbytes,
+ cubeb_stream * stm)
+{
+ void * buffer;
+ size_t size;
+ int r;
+ long got;
+ size_t towrite, read_offset;
+ size_t frame_size;
+
+ frame_size = WRAP(pa_frame_size)(&stm->output_sample_spec);
+ assert(nbytes % frame_size == 0);
+
+ towrite = nbytes;
+ read_offset = 0;
+ while (towrite) {
+ size = towrite;
+ r = WRAP(pa_stream_begin_write)(s, &buffer, &size);
+ // Note: this has failed running under rr on occassion - needs
+ // investigation.
+ assert(r == 0);
+ assert(size > 0);
+ assert(size % frame_size == 0);
+
+ LOGV("Trigger user callback with output buffer size=%zd, read_offset=%zd",
+ size, read_offset);
+ got = stm->data_callback(stm, stm->user_ptr,
+ (uint8_t const *)input_data + read_offset, buffer,
+ size / frame_size);
+ if (got < 0) {
+ WRAP(pa_stream_cancel_write)(s);
+ stm->shutdown = 1;
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ return;
+ }
+ // If more iterations move offset of read buffer
+ if (input_data) {
+ size_t in_frame_size = WRAP(pa_frame_size)(&stm->input_sample_spec);
+ read_offset += (size / frame_size) * in_frame_size;
+ }
+
+ if (stm->volume != PULSE_NO_GAIN) {
+ uint32_t samples = size * stm->output_sample_spec.channels / frame_size;
+
+ if (stm->output_sample_spec.format == PA_SAMPLE_S16BE ||
+ stm->output_sample_spec.format == PA_SAMPLE_S16LE) {
+ short * b = buffer;
+ for (uint32_t i = 0; i < samples; i++) {
+ b[i] *= stm->volume;
+ }
+ } else {
+ float * b = buffer;
+ for (uint32_t i = 0; i < samples; i++) {
+ b[i] *= stm->volume;
+ }
+ }
+ }
+
+ r = WRAP(pa_stream_write)(s, buffer, got * frame_size, NULL, 0,
+ PA_SEEK_RELATIVE);
+ assert(r == 0);
+
+ if ((size_t)got < size / frame_size) {
+ pa_usec_t latency = 0;
+ r = WRAP(pa_stream_get_latency)(s, &latency, NULL);
+ if (r == -PA_ERR_NODATA) {
+ /* this needs a better guess. */
+ latency = 100 * PA_USEC_PER_MSEC;
+ }
+ assert(r == 0 || r == -PA_ERR_NODATA);
+ /* pa_stream_drain is useless, see PA bug# 866. this is a workaround. */
+ /* arbitrary safety margin: double the current latency. */
+ assert(!stm->drain_timer);
+ stm->drain_timer = WRAP(pa_context_rttime_new)(
+ stm->context->context, WRAP(pa_rtclock_now)() + 2 * latency,
+ stream_drain_callback, stm);
+ stm->shutdown = 1;
+ return;
+ }
+
+ towrite -= size;
+ }
+
+ assert(towrite == 0);
+}
+
+static int
+read_from_input(pa_stream * s, void const ** buffer, size_t * size)
+{
+ size_t readable_size = WRAP(pa_stream_readable_size)(s);
+ if (readable_size > 0) {
+ if (WRAP(pa_stream_peek)(s, buffer, size) < 0) {
+ return -1;
+ }
+ }
+ return readable_size;
+}
+
+static void
+stream_write_callback(pa_stream * s, size_t nbytes, void * u)
+{
+ LOGV("Output callback to be written buffer size %zd", nbytes);
+ cubeb_stream * stm = u;
+ if (stm->shutdown || stm->state != CUBEB_STATE_STARTED) {
+ return;
+ }
+
+ if (!stm->input_stream) {
+ // Output/playback only operation.
+ // Write directly to output
+ assert(!stm->input_stream && stm->output_stream);
+ trigger_user_callback(s, NULL, nbytes, stm);
+ }
+}
+
+static void
+stream_read_callback(pa_stream * s, size_t nbytes, void * u)
+{
+ LOGV("Input callback buffer size %zd", nbytes);
+ cubeb_stream * stm = u;
+ if (stm->shutdown) {
+ return;
+ }
+
+ void const * read_data = NULL;
+ size_t read_size;
+ while (read_from_input(s, &read_data, &read_size) > 0) {
+ /* read_data can be NULL in case of a hole. */
+ if (read_data) {
+ size_t in_frame_size = WRAP(pa_frame_size)(&stm->input_sample_spec);
+ size_t read_frames = read_size / in_frame_size;
+
+ if (stm->output_stream) {
+ // input/capture + output/playback operation
+ size_t out_frame_size = WRAP(pa_frame_size)(&stm->output_sample_spec);
+ size_t write_size = read_frames * out_frame_size;
+ // Offer full duplex data for writing
+ trigger_user_callback(stm->output_stream, read_data, write_size, stm);
+ } else {
+ // input/capture only operation. Call callback directly
+ long got = stm->data_callback(stm, stm->user_ptr, read_data, NULL,
+ read_frames);
+ if (got < 0 || (size_t)got != read_frames) {
+ WRAP(pa_stream_cancel_write)(s);
+ stm->shutdown = 1;
+ if (got < 0) {
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ }
+ break;
+ }
+ }
+ }
+ if (read_size > 0) {
+ WRAP(pa_stream_drop)(s);
+ }
+
+ if (stm->shutdown) {
+ return;
+ }
+ }
+}
+
+static int
+wait_until_context_ready(cubeb * ctx)
+{
+ for (;;) {
+ pa_context_state_t state = WRAP(pa_context_get_state)(ctx->context);
+ if (!PA_CONTEXT_IS_GOOD(state))
+ return -1;
+ if (state == PA_CONTEXT_READY)
+ break;
+ WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
+ }
+ return 0;
+}
+
+static int
+wait_until_io_stream_ready(pa_stream * stream, pa_threaded_mainloop * mainloop)
+{
+ if (!stream || !mainloop) {
+ return -1;
+ }
+ for (;;) {
+ pa_stream_state_t state = WRAP(pa_stream_get_state)(stream);
+ if (!PA_STREAM_IS_GOOD(state))
+ return -1;
+ if (state == PA_STREAM_READY)
+ break;
+ WRAP(pa_threaded_mainloop_wait)(mainloop);
+ }
+ return 0;
+}
+
+static int
+wait_until_stream_ready(cubeb_stream * stm)
+{
+ if (stm->output_stream &&
+ wait_until_io_stream_ready(stm->output_stream, stm->context->mainloop) ==
+ -1) {
+ return -1;
+ }
+ if (stm->input_stream &&
+ wait_until_io_stream_ready(stm->input_stream, stm->context->mainloop) ==
+ -1) {
+ return -1;
+ }
+ return 0;
+}
+
+static int
+operation_wait(cubeb * ctx, pa_stream * stream, pa_operation * o)
+{
+ while (WRAP(pa_operation_get_state)(o) == PA_OPERATION_RUNNING) {
+ WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
+ if (!PA_CONTEXT_IS_GOOD(WRAP(pa_context_get_state)(ctx->context))) {
+ return -1;
+ }
+ if (stream && !PA_STREAM_IS_GOOD(WRAP(pa_stream_get_state)(stream))) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static void
+cork_io_stream(cubeb_stream * stm, pa_stream * io_stream, enum cork_state state)
+{
+ pa_operation * o;
+ if (!io_stream) {
+ return;
+ }
+ o = WRAP(pa_stream_cork)(io_stream, state & CORK, stream_success_callback,
+ stm);
+ if (o) {
+ operation_wait(stm->context, io_stream, o);
+ WRAP(pa_operation_unref)(o);
+ }
+}
+
+static void
+stream_cork(cubeb_stream * stm, enum cork_state state)
+{
+ WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
+ cork_io_stream(stm, stm->output_stream, state);
+ cork_io_stream(stm, stm->input_stream, state);
+ WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
+
+ if (state & NOTIFY) {
+ stream_state_change_callback(stm, state & CORK ? CUBEB_STATE_STOPPED
+ : CUBEB_STATE_STARTED);
+ }
+}
+
+static int
+stream_update_timing_info(cubeb_stream * stm)
+{
+ int r = -1;
+ pa_operation * o = NULL;
+ if (stm->output_stream) {
+ o = WRAP(pa_stream_update_timing_info)(stm->output_stream,
+ stream_success_callback, stm);
+ if (o) {
+ r = operation_wait(stm->context, stm->output_stream, o);
+ WRAP(pa_operation_unref)(o);
+ }
+ if (r != 0) {
+ return r;
+ }
+ }
+
+ if (stm->input_stream) {
+ o = WRAP(pa_stream_update_timing_info)(stm->input_stream,
+ stream_success_callback, stm);
+ if (o) {
+ r = operation_wait(stm->context, stm->input_stream, o);
+ WRAP(pa_operation_unref)(o);
+ }
+ }
+
+ return r;
+}
+
+static pa_channel_position_t
+cubeb_channel_to_pa_channel(cubeb_channel channel)
+{
+ switch (channel) {
+ case CHANNEL_FRONT_LEFT:
+ return PA_CHANNEL_POSITION_FRONT_LEFT;
+ case CHANNEL_FRONT_RIGHT:
+ return PA_CHANNEL_POSITION_FRONT_RIGHT;
+ case CHANNEL_FRONT_CENTER:
+ return PA_CHANNEL_POSITION_FRONT_CENTER;
+ case CHANNEL_LOW_FREQUENCY:
+ return PA_CHANNEL_POSITION_LFE;
+ case CHANNEL_BACK_LEFT:
+ return PA_CHANNEL_POSITION_REAR_LEFT;
+ case CHANNEL_BACK_RIGHT:
+ return PA_CHANNEL_POSITION_REAR_RIGHT;
+ case CHANNEL_FRONT_LEFT_OF_CENTER:
+ return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
+ case CHANNEL_FRONT_RIGHT_OF_CENTER:
+ return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
+ case CHANNEL_BACK_CENTER:
+ return PA_CHANNEL_POSITION_REAR_CENTER;
+ case CHANNEL_SIDE_LEFT:
+ return PA_CHANNEL_POSITION_SIDE_LEFT;
+ case CHANNEL_SIDE_RIGHT:
+ return PA_CHANNEL_POSITION_SIDE_RIGHT;
+ case CHANNEL_TOP_CENTER:
+ return PA_CHANNEL_POSITION_TOP_CENTER;
+ case CHANNEL_TOP_FRONT_LEFT:
+ return PA_CHANNEL_POSITION_TOP_FRONT_LEFT;
+ case CHANNEL_TOP_FRONT_CENTER:
+ return PA_CHANNEL_POSITION_TOP_FRONT_CENTER;
+ case CHANNEL_TOP_FRONT_RIGHT:
+ return PA_CHANNEL_POSITION_TOP_FRONT_RIGHT;
+ case CHANNEL_TOP_BACK_LEFT:
+ return PA_CHANNEL_POSITION_TOP_REAR_LEFT;
+ case CHANNEL_TOP_BACK_CENTER:
+ return PA_CHANNEL_POSITION_TOP_REAR_CENTER;
+ case CHANNEL_TOP_BACK_RIGHT:
+ return PA_CHANNEL_POSITION_TOP_REAR_RIGHT;
+ default:
+ return PA_CHANNEL_POSITION_INVALID;
+ }
+}
+
+static void
+layout_to_channel_map(cubeb_channel_layout layout, pa_channel_map * cm)
+{
+ assert(cm && layout != CUBEB_LAYOUT_UNDEFINED);
+
+ WRAP(pa_channel_map_init)(cm);
+
+ uint32_t channels = 0;
+ cubeb_channel_layout channelMap = layout;
+ for (uint32_t i = 0; channelMap != 0; ++i) {
+ uint32_t channel = (channelMap & 1) << i;
+ if (channel != 0) {
+ cm->map[channels] = cubeb_channel_to_pa_channel(channel);
+ channels++;
+ }
+ channelMap = channelMap >> 1;
+ }
+ unsigned int channels_from_layout = cubeb_channel_layout_nb_channels(layout);
+ assert(channels_from_layout <= UINT8_MAX);
+ cm->channels = (uint8_t)channels_from_layout;
+
+ // Special case single channel center mapping as mono.
+ if (cm->channels == 1 && cm->map[0] == PA_CHANNEL_POSITION_FRONT_CENTER) {
+ cm->map[0] = PA_CHANNEL_POSITION_MONO;
+ }
+}
+
+static void
+pulse_context_destroy(cubeb * ctx);
+static void
+pulse_destroy(cubeb * ctx);
+
+static int
+pulse_context_init(cubeb * ctx)
+{
+ int r;
+
+ if (ctx->context) {
+ assert(ctx->error == 1);
+ pulse_context_destroy(ctx);
+ }
+
+ ctx->context = WRAP(pa_context_new)(
+ WRAP(pa_threaded_mainloop_get_api)(ctx->mainloop), ctx->context_name);
+ if (!ctx->context) {
+ return -1;
+ }
+ WRAP(pa_context_set_state_callback)
+ (ctx->context, context_state_callback, ctx);
+
+ WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
+ r = WRAP(pa_context_connect)(ctx->context, NULL, 0, NULL);
+
+ if (r < 0 || wait_until_context_ready(ctx) != 0) {
+ WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
+ pulse_context_destroy(ctx);
+ ctx->context = NULL;
+ return -1;
+ }
+
+ WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
+
+ ctx->error = 0;
+
+ return 0;
+}
+
+static int
+pulse_subscribe_notifications(cubeb * context, pa_subscription_mask_t mask);
+
+/*static*/ int
+pulse_init(cubeb ** context, char const * context_name)
+{
+ void * libpulse = NULL;
+ cubeb * ctx;
+ pa_operation * o;
+
+ *context = NULL;
+
+#ifndef DISABLE_LIBPULSE_DLOPEN
+ libpulse = dlopen("libpulse.so.0", RTLD_LAZY);
+ if (!libpulse) {
+ libpulse = dlopen("libpulse.so", RTLD_LAZY);
+ if (!libpulse) {
+ return CUBEB_ERROR;
+ }
+ }
+
+#define LOAD(x) \
+ { \
+ cubeb_##x = dlsym(libpulse, #x); \
+ if (!cubeb_##x) { \
+ dlclose(libpulse); \
+ return CUBEB_ERROR; \
+ } \
+ }
+
+ LIBPULSE_API_VISIT(LOAD);
+#undef LOAD
+#endif
+
+#if PA_CHECK_VERSION(2, 0, 0)
+ const char * version = WRAP(pa_get_library_version)();
+ has_pulse_v2 = strtol(version, NULL, 10) >= 2;
+#endif
+
+ ctx = calloc(1, sizeof(*ctx));
+ assert(ctx);
+
+ ctx->ops = &pulse_ops;
+ ctx->libpulse = libpulse;
+ if (cubeb_strings_init(&ctx->device_ids) != CUBEB_OK) {
+ pulse_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ ctx->mainloop = WRAP(pa_threaded_mainloop_new)();
+ ctx->default_sink_info = NULL;
+
+ WRAP(pa_threaded_mainloop_start)(ctx->mainloop);
+
+ ctx->context_name = context_name ? strdup(context_name) : NULL;
+ if (pulse_context_init(ctx) != 0) {
+ pulse_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ /* server_info_callback performs a second async query, which is
+ responsible for initializing default_sink_info and signalling the
+ mainloop to end the wait. */
+ WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
+ o = WRAP(pa_context_get_server_info)(ctx->context, server_info_callback, ctx);
+ if (o) {
+ operation_wait(ctx, NULL, o);
+ WRAP(pa_operation_unref)(o);
+ }
+ WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
+
+ /* Update `default_sink_info` when the default device changes. */
+ pulse_subscribe_notifications(ctx, PA_SUBSCRIPTION_MASK_SERVER);
+
+ *context = ctx;
+
+ return CUBEB_OK;
+}
+
+static char const *
+pulse_get_backend_id(cubeb * ctx)
+{
+ (void)ctx;
+ return "pulse";
+}
+
+static int
+pulse_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
+{
+ (void)ctx;
+ assert(ctx && max_channels);
+
+ if (!ctx->default_sink_info)
+ return CUBEB_ERROR;
+
+ *max_channels = ctx->default_sink_info->channel_map.channels;
+
+ return CUBEB_OK;
+}
+
+static int
+pulse_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
+{
+ assert(ctx && rate);
+ (void)ctx;
+
+ if (!ctx->default_sink_info)
+ return CUBEB_ERROR;
+
+ *rate = ctx->default_sink_info->sample_spec_rate;
+
+ return CUBEB_OK;
+}
+
+static int
+pulse_get_min_latency(cubeb * ctx, cubeb_stream_params params,
+ uint32_t * latency_frames)
+{
+ (void)ctx;
+ // According to PulseAudio developers, this is a safe minimum.
+ *latency_frames = 25 * params.rate / 1000;
+
+ return CUBEB_OK;
+}
+
+static void
+pulse_context_destroy(cubeb * ctx)
+{
+ pa_operation * o;
+
+ WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
+ o = WRAP(pa_context_drain)(ctx->context, context_notify_callback, ctx);
+ if (o) {
+ operation_wait(ctx, NULL, o);
+ WRAP(pa_operation_unref)(o);
+ }
+ WRAP(pa_context_set_state_callback)(ctx->context, NULL, NULL);
+ WRAP(pa_context_disconnect)(ctx->context);
+ WRAP(pa_context_unref)(ctx->context);
+ WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
+}
+
+static void
+pulse_destroy(cubeb * ctx)
+{
+ assert(!ctx->input_collection_changed_callback &&
+ !ctx->input_collection_changed_user_ptr &&
+ !ctx->output_collection_changed_callback &&
+ !ctx->output_collection_changed_user_ptr);
+ free(ctx->context_name);
+ if (ctx->context) {
+ pulse_context_destroy(ctx);
+ }
+
+ if (ctx->mainloop) {
+ WRAP(pa_threaded_mainloop_stop)(ctx->mainloop);
+ WRAP(pa_threaded_mainloop_free)(ctx->mainloop);
+ }
+
+ if (ctx->device_ids) {
+ cubeb_strings_destroy(ctx->device_ids);
+ }
+#ifndef DISABLE_LIBPULSE_DLOPEN
+ if (ctx->libpulse) {
+ dlclose(ctx->libpulse);
+ }
+#endif
+ free(ctx->default_sink_info);
+ free(ctx);
+}
+
+static void
+pulse_stream_destroy(cubeb_stream * stm);
+
+static pa_sample_format_t
+to_pulse_format(cubeb_sample_format format)
+{
+ switch (format) {
+ case CUBEB_SAMPLE_S16LE:
+ return PA_SAMPLE_S16LE;
+ case CUBEB_SAMPLE_S16BE:
+ return PA_SAMPLE_S16BE;
+ case CUBEB_SAMPLE_FLOAT32LE:
+ return PA_SAMPLE_FLOAT32LE;
+ case CUBEB_SAMPLE_FLOAT32BE:
+ return PA_SAMPLE_FLOAT32BE;
+ default:
+ return PA_SAMPLE_INVALID;
+ }
+}
+
+static cubeb_channel_layout
+pulse_default_layout_for_channels(uint32_t ch)
+{
+ assert(ch > 0 && ch <= 8);
+ switch (ch) {
+ case 1:
+ return CUBEB_LAYOUT_MONO;
+ case 2:
+ return CUBEB_LAYOUT_STEREO;
+ case 3:
+ return CUBEB_LAYOUT_3F;
+ case 4:
+ return CUBEB_LAYOUT_QUAD;
+ case 5:
+ return CUBEB_LAYOUT_3F2;
+ case 6:
+ return CUBEB_LAYOUT_3F_LFE | CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT;
+ case 7:
+ return CUBEB_LAYOUT_3F3R_LFE;
+ case 8:
+ return CUBEB_LAYOUT_3F4_LFE;
+ }
+ // Never get here!
+ return CUBEB_LAYOUT_UNDEFINED;
+}
+
+static int
+create_pa_stream(cubeb_stream * stm, pa_stream ** pa_stm,
+ cubeb_stream_params * stream_params, char const * stream_name)
+{
+ assert(stm && stream_params);
+ assert(&stm->input_stream == pa_stm ||
+ (&stm->output_stream == pa_stm &&
+ (stream_params->layout == CUBEB_LAYOUT_UNDEFINED ||
+ (stream_params->layout != CUBEB_LAYOUT_UNDEFINED &&
+ cubeb_channel_layout_nb_channels(stream_params->layout) ==
+ stream_params->channels))));
+ if (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+ *pa_stm = NULL;
+ pa_sample_spec ss;
+ ss.format = to_pulse_format(stream_params->format);
+ if (ss.format == PA_SAMPLE_INVALID)
+ return CUBEB_ERROR_INVALID_FORMAT;
+ ss.rate = stream_params->rate;
+ if (stream_params->channels > UINT8_MAX)
+ return CUBEB_ERROR_INVALID_FORMAT;
+ ss.channels = (uint8_t)stream_params->channels;
+
+ if (stream_params->layout == CUBEB_LAYOUT_UNDEFINED) {
+ pa_channel_map cm;
+ if (stream_params->channels <= 8 &&
+ !WRAP(pa_channel_map_init_auto)(&cm, stream_params->channels,
+ PA_CHANNEL_MAP_DEFAULT)) {
+ LOG("Layout undefined and PulseAudio's default layout has not been "
+ "configured, guess one.");
+ layout_to_channel_map(
+ pulse_default_layout_for_channels(stream_params->channels), &cm);
+ *pa_stm =
+ WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, &cm);
+ } else {
+ LOG("Layout undefined, PulseAudio will use its default.");
+ *pa_stm =
+ WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL);
+ }
+ } else {
+ pa_channel_map cm;
+ layout_to_channel_map(stream_params->layout, &cm);
+ *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, &cm);
+ }
+ return (*pa_stm == NULL) ? CUBEB_ERROR : CUBEB_OK;
+}
+
+static pa_buffer_attr
+set_buffering_attribute(unsigned int latency_frames,
+ pa_sample_spec * sample_spec)
+{
+ pa_buffer_attr battr;
+ battr.maxlength = -1;
+ battr.prebuf = -1;
+ battr.tlength = latency_frames * WRAP(pa_frame_size)(sample_spec);
+ battr.minreq = battr.tlength / 4;
+ battr.fragsize = battr.minreq;
+
+ LOG("Requested buffer attributes maxlength %u, tlength %u, prebuf %u, minreq "
+ "%u, fragsize %u",
+ battr.maxlength, battr.tlength, battr.prebuf, battr.minreq,
+ battr.fragsize);
+
+ return battr;
+}
+
+static int
+pulse_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ cubeb_stream * stm;
+ pa_buffer_attr battr;
+ int r;
+
+ assert(context);
+
+ // If the connection failed for some reason, try to reconnect
+ if (context->error == 1 && pulse_context_init(context) != 0) {
+ return CUBEB_ERROR;
+ }
+
+ *stream = NULL;
+
+ stm = calloc(1, sizeof(*stm));
+ assert(stm);
+
+ stm->context = context;
+ stm->data_callback = data_callback;
+ stm->state_callback = state_callback;
+ stm->user_ptr = user_ptr;
+ stm->volume = PULSE_NO_GAIN;
+ stm->state = -1;
+ assert(stm->shutdown == 0);
+
+ WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
+ if (output_stream_params) {
+ r = create_pa_stream(stm, &stm->output_stream, output_stream_params,
+ stream_name);
+ if (r != CUBEB_OK) {
+ WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
+ pulse_stream_destroy(stm);
+ return r;
+ }
+
+ stm->output_sample_spec =
+ *(WRAP(pa_stream_get_sample_spec)(stm->output_stream));
+
+ WRAP(pa_stream_set_state_callback)
+ (stm->output_stream, stream_state_callback, stm);
+ WRAP(pa_stream_set_write_callback)
+ (stm->output_stream, stream_write_callback, stm);
+
+ battr = set_buffering_attribute(latency_frames, &stm->output_sample_spec);
+ WRAP(pa_stream_connect_playback)
+ (stm->output_stream, (char const *)output_device, &battr,
+ PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING |
+ PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY,
+ NULL, NULL);
+ }
+
+ // Set up input stream
+ if (input_stream_params) {
+ r = create_pa_stream(stm, &stm->input_stream, input_stream_params,
+ stream_name);
+ if (r != CUBEB_OK) {
+ WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
+ pulse_stream_destroy(stm);
+ return r;
+ }
+
+ stm->input_sample_spec =
+ *(WRAP(pa_stream_get_sample_spec)(stm->input_stream));
+
+ WRAP(pa_stream_set_state_callback)
+ (stm->input_stream, stream_state_callback, stm);
+ WRAP(pa_stream_set_read_callback)
+ (stm->input_stream, stream_read_callback, stm);
+
+ battr = set_buffering_attribute(latency_frames, &stm->input_sample_spec);
+ WRAP(pa_stream_connect_record)
+ (stm->input_stream, (char const *)input_device, &battr,
+ PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING |
+ PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY);
+ }
+
+ r = wait_until_stream_ready(stm);
+ if (r == 0) {
+ /* force a timing update now, otherwise timing info does not become valid
+ until some point after initialization has completed. */
+ r = stream_update_timing_info(stm);
+ }
+
+ WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
+
+ if (r != 0) {
+ pulse_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ if (cubeb_log_get_level()) {
+ if (output_stream_params) {
+ const pa_buffer_attr * output_att;
+ output_att = WRAP(pa_stream_get_buffer_attr)(stm->output_stream);
+ LOG("Output buffer attributes maxlength %u, tlength %u, prebuf %u, "
+ "minreq %u, fragsize %u",
+ output_att->maxlength, output_att->tlength, output_att->prebuf,
+ output_att->minreq, output_att->fragsize);
+ }
+
+ if (input_stream_params) {
+ const pa_buffer_attr * input_att;
+ input_att = WRAP(pa_stream_get_buffer_attr)(stm->input_stream);
+ LOG("Input buffer attributes maxlength %u, tlength %u, prebuf %u, minreq "
+ "%u, fragsize %u",
+ input_att->maxlength, input_att->tlength, input_att->prebuf,
+ input_att->minreq, input_att->fragsize);
+ }
+ }
+
+ *stream = stm;
+ LOG("Cubeb stream (%p) init successful.", *stream);
+
+ return CUBEB_OK;
+}
+
+static void
+pulse_stream_destroy(cubeb_stream * stm)
+{
+ stream_cork(stm, CORK);
+
+ WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
+ if (stm->output_stream) {
+
+ if (stm->drain_timer) {
+ /* there's no pa_rttime_free, so use this instead. */
+ WRAP(pa_threaded_mainloop_get_api)
+ (stm->context->mainloop)->time_free(stm->drain_timer);
+ }
+
+ WRAP(pa_stream_set_state_callback)(stm->output_stream, NULL, NULL);
+ WRAP(pa_stream_set_write_callback)(stm->output_stream, NULL, NULL);
+ WRAP(pa_stream_disconnect)(stm->output_stream);
+ WRAP(pa_stream_unref)(stm->output_stream);
+ }
+
+ if (stm->input_stream) {
+ WRAP(pa_stream_set_state_callback)(stm->input_stream, NULL, NULL);
+ WRAP(pa_stream_set_read_callback)(stm->input_stream, NULL, NULL);
+ WRAP(pa_stream_disconnect)(stm->input_stream);
+ WRAP(pa_stream_unref)(stm->input_stream);
+ }
+ WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
+
+ LOG("Cubeb stream (%p) destroyed successfully.", stm);
+ free(stm);
+}
+
+static void
+pulse_defer_event_cb(pa_mainloop_api * a, void * userdata)
+{
+ (void)a;
+ cubeb_stream * stm = userdata;
+ if (stm->shutdown) {
+ return;
+ }
+ size_t writable_size = WRAP(pa_stream_writable_size)(stm->output_stream);
+ trigger_user_callback(stm->output_stream, NULL, writable_size, stm);
+}
+
+static int
+pulse_stream_start(cubeb_stream * stm)
+{
+ stm->shutdown = 0;
+ stream_cork(stm, UNCORK | NOTIFY);
+
+ if (stm->output_stream && !stm->input_stream) {
+ /* On output only case need to manually call user cb once in order to make
+ * things roll. This is done via a defer event in order to execute it
+ * from PA server thread. */
+ WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
+ WRAP(pa_mainloop_api_once)
+ (WRAP(pa_threaded_mainloop_get_api)(stm->context->mainloop),
+ pulse_defer_event_cb, stm);
+ WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
+ }
+
+ LOG("Cubeb stream (%p) started successfully.", stm);
+ return CUBEB_OK;
+}
+
+static int
+pulse_stream_stop(cubeb_stream * stm)
+{
+ WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
+ stm->shutdown = 1;
+ // If draining is taking place wait to finish
+ while (stm->drain_timer) {
+ WRAP(pa_threaded_mainloop_wait)(stm->context->mainloop);
+ }
+ WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
+
+ stream_cork(stm, CORK | NOTIFY);
+ LOG("Cubeb stream (%p) stopped successfully.", stm);
+ return CUBEB_OK;
+}
+
+static int
+pulse_stream_get_position(cubeb_stream * stm, uint64_t * position)
+{
+ int r, in_thread;
+ pa_usec_t r_usec;
+ uint64_t bytes;
+
+ if (!stm || !stm->output_stream) {
+ return CUBEB_ERROR;
+ }
+
+ in_thread = WRAP(pa_threaded_mainloop_in_thread)(stm->context->mainloop);
+
+ if (!in_thread) {
+ WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
+ }
+ r = WRAP(pa_stream_get_time)(stm->output_stream, &r_usec);
+ if (!in_thread) {
+ WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
+ }
+
+ if (r != 0) {
+ return CUBEB_ERROR;
+ }
+
+ bytes = WRAP(pa_usec_to_bytes)(r_usec, &stm->output_sample_spec);
+ *position = bytes / WRAP(pa_frame_size)(&stm->output_sample_spec);
+
+ return CUBEB_OK;
+}
+
+static int
+pulse_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
+{
+ pa_usec_t r_usec;
+ int negative, r;
+
+ if (!stm || !stm->output_stream) {
+ return CUBEB_ERROR;
+ }
+
+ r = WRAP(pa_stream_get_latency)(stm->output_stream, &r_usec, &negative);
+ assert(!negative);
+ if (r) {
+ return CUBEB_ERROR;
+ }
+
+ *latency = r_usec * stm->output_sample_spec.rate / PA_USEC_PER_SEC;
+ return CUBEB_OK;
+}
+
+static void
+volume_success(pa_context * c, int success, void * userdata)
+{
+ (void)success;
+ (void)c;
+ cubeb_stream * stream = userdata;
+ assert(success);
+ WRAP(pa_threaded_mainloop_signal)(stream->context->mainloop, 0);
+}
+
+static void
+rename_success(pa_stream * s, int success, void * userdata)
+{
+ cubeb_stream * stream = userdata;
+ assert(success);
+ WRAP(pa_threaded_mainloop_signal)(stream->context->mainloop, 0);
+}
+
+static int
+pulse_stream_set_volume(cubeb_stream * stm, float volume)
+{
+ uint32_t index;
+ pa_operation * op;
+ pa_volume_t vol;
+ pa_cvolume cvol;
+ const pa_sample_spec * ss;
+ cubeb * ctx;
+
+ if (!stm->output_stream) {
+ return CUBEB_ERROR;
+ }
+
+ WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
+
+ /* if the pulse daemon is configured to use flat volumes,
+ * apply our own gain instead of changing the input volume on the sink. */
+ ctx = stm->context;
+ if (ctx->default_sink_info &&
+ (ctx->default_sink_info->flags & PA_SINK_FLAT_VOLUME)) {
+ stm->volume = volume;
+ } else {
+ ss = WRAP(pa_stream_get_sample_spec)(stm->output_stream);
+
+ vol = WRAP(pa_sw_volume_from_linear)(volume);
+ WRAP(pa_cvolume_set)(&cvol, ss->channels, vol);
+
+ index = WRAP(pa_stream_get_index)(stm->output_stream);
+
+ op = WRAP(pa_context_set_sink_input_volume)(ctx->context, index, &cvol,
+ volume_success, stm);
+ if (op) {
+ operation_wait(ctx, stm->output_stream, op);
+ WRAP(pa_operation_unref)(op);
+ }
+ }
+
+ WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
+
+ return CUBEB_OK;
+}
+
+static int
+pulse_stream_set_name(cubeb_stream * stm, char const * stream_name)
+{
+ if (!stm || !stm->output_stream) {
+ return CUBEB_ERROR;
+ }
+
+ WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
+
+ pa_operation * op = WRAP(pa_stream_set_name)(stm->output_stream, stream_name,
+ rename_success, stm);
+
+ if (op) {
+ operation_wait(stm->context, stm->output_stream, op);
+ WRAP(pa_operation_unref)(op);
+ }
+
+ WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
+
+ return CUBEB_OK;
+}
+
+typedef struct {
+ char * default_sink_name;
+ char * default_source_name;
+
+ cubeb_device_info * devinfo;
+ uint32_t max;
+ uint32_t count;
+ cubeb * context;
+} pulse_dev_list_data;
+
+static cubeb_device_fmt
+pulse_format_to_cubeb_format(pa_sample_format_t format)
+{
+ switch (format) {
+ case PA_SAMPLE_S16LE:
+ return CUBEB_DEVICE_FMT_S16LE;
+ case PA_SAMPLE_S16BE:
+ return CUBEB_DEVICE_FMT_S16BE;
+ case PA_SAMPLE_FLOAT32LE:
+ return CUBEB_DEVICE_FMT_F32LE;
+ case PA_SAMPLE_FLOAT32BE:
+ return CUBEB_DEVICE_FMT_F32BE;
+ default:
+ return CUBEB_DEVICE_FMT_F32NE;
+ }
+}
+
+static void
+pulse_ensure_dev_list_data_list_size(pulse_dev_list_data * list_data)
+{
+ if (list_data->count == list_data->max) {
+ list_data->max += 8;
+ list_data->devinfo =
+ realloc(list_data->devinfo, sizeof(cubeb_device_info) * list_data->max);
+ }
+}
+
+static cubeb_device_state
+pulse_get_state_from_sink_port(pa_sink_port_info * info)
+{
+ if (info != NULL) {
+#if PA_CHECK_VERSION(2, 0, 0)
+ if (has_pulse_v2 && info->available == PA_PORT_AVAILABLE_NO)
+ return CUBEB_DEVICE_STATE_UNPLUGGED;
+ else /*if (info->available == PA_PORT_AVAILABLE_YES) + UNKNOWN */
+#endif
+ return CUBEB_DEVICE_STATE_ENABLED;
+ }
+
+ return CUBEB_DEVICE_STATE_ENABLED;
+}
+
+static void
+pulse_sink_info_cb(pa_context * context, const pa_sink_info * info, int eol,
+ void * user_data)
+{
+ pulse_dev_list_data * list_data = user_data;
+ cubeb_device_info * devinfo;
+ char const * prop = NULL;
+ char const * device_id = NULL;
+
+ (void)context;
+
+ if (eol) {
+ WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
+ return;
+ }
+
+ if (info == NULL)
+ return;
+
+ device_id = info->name;
+ if (intern_device_id(list_data->context, &device_id) != CUBEB_OK) {
+ assert(NULL);
+ return;
+ }
+
+ pulse_ensure_dev_list_data_list_size(list_data);
+ devinfo = &list_data->devinfo[list_data->count];
+ memset(devinfo, 0, sizeof(cubeb_device_info));
+
+ devinfo->device_id = device_id;
+ devinfo->devid = (cubeb_devid)devinfo->device_id;
+ devinfo->friendly_name = strdup(info->description);
+ prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path");
+ if (prop)
+ devinfo->group_id = strdup(prop);
+ prop = WRAP(pa_proplist_gets)(info->proplist, "device.vendor.name");
+ if (prop)
+ devinfo->vendor_name = strdup(prop);
+
+ devinfo->type = CUBEB_DEVICE_TYPE_OUTPUT;
+ devinfo->state = pulse_get_state_from_sink_port(info->active_port);
+ devinfo->preferred = (strcmp(info->name, list_data->default_sink_name) == 0)
+ ? CUBEB_DEVICE_PREF_ALL
+ : CUBEB_DEVICE_PREF_NONE;
+
+ devinfo->format = CUBEB_DEVICE_FMT_ALL;
+ devinfo->default_format =
+ pulse_format_to_cubeb_format(info->sample_spec.format);
+ devinfo->max_channels = info->channel_map.channels;
+ devinfo->min_rate = 1;
+ devinfo->max_rate = PA_RATE_MAX;
+ devinfo->default_rate = info->sample_spec.rate;
+
+ devinfo->latency_lo = 0;
+ devinfo->latency_hi = 0;
+
+ list_data->count += 1;
+}
+
+static cubeb_device_state
+pulse_get_state_from_source_port(pa_source_port_info * info)
+{
+ if (info != NULL) {
+#if PA_CHECK_VERSION(2, 0, 0)
+ if (has_pulse_v2 && info->available == PA_PORT_AVAILABLE_NO)
+ return CUBEB_DEVICE_STATE_UNPLUGGED;
+ else /*if (info->available == PA_PORT_AVAILABLE_YES) + UNKNOWN */
+#endif
+ return CUBEB_DEVICE_STATE_ENABLED;
+ }
+
+ return CUBEB_DEVICE_STATE_ENABLED;
+}
+
+static void
+pulse_source_info_cb(pa_context * context, const pa_source_info * info, int eol,
+ void * user_data)
+{
+ pulse_dev_list_data * list_data = user_data;
+ cubeb_device_info * devinfo;
+ char const * prop = NULL;
+ char const * device_id = NULL;
+
+ (void)context;
+
+ if (eol) {
+ WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
+ return;
+ }
+
+ device_id = info->name;
+ if (intern_device_id(list_data->context, &device_id) != CUBEB_OK) {
+ assert(NULL);
+ return;
+ }
+
+ pulse_ensure_dev_list_data_list_size(list_data);
+ devinfo = &list_data->devinfo[list_data->count];
+ memset(devinfo, 0, sizeof(cubeb_device_info));
+
+ devinfo->device_id = device_id;
+ devinfo->devid = (cubeb_devid)devinfo->device_id;
+ devinfo->friendly_name = strdup(info->description);
+ prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path");
+ if (prop)
+ devinfo->group_id = strdup(prop);
+ prop = WRAP(pa_proplist_gets)(info->proplist, "device.vendor.name");
+ if (prop)
+ devinfo->vendor_name = strdup(prop);
+
+ devinfo->type = CUBEB_DEVICE_TYPE_INPUT;
+ devinfo->state = pulse_get_state_from_source_port(info->active_port);
+ devinfo->preferred = (strcmp(info->name, list_data->default_source_name) == 0)
+ ? CUBEB_DEVICE_PREF_ALL
+ : CUBEB_DEVICE_PREF_NONE;
+
+ devinfo->format = CUBEB_DEVICE_FMT_ALL;
+ devinfo->default_format =
+ pulse_format_to_cubeb_format(info->sample_spec.format);
+ devinfo->max_channels = info->channel_map.channels;
+ devinfo->min_rate = 1;
+ devinfo->max_rate = PA_RATE_MAX;
+ devinfo->default_rate = info->sample_spec.rate;
+
+ devinfo->latency_lo = 0;
+ devinfo->latency_hi = 0;
+
+ list_data->count += 1;
+}
+
+static void
+pulse_server_info_cb(pa_context * c, const pa_server_info * i, void * userdata)
+{
+ pulse_dev_list_data * list_data = userdata;
+
+ (void)c;
+
+ free(list_data->default_sink_name);
+ free(list_data->default_source_name);
+ list_data->default_sink_name =
+ i->default_sink_name ? strdup(i->default_sink_name) : NULL;
+ list_data->default_source_name =
+ i->default_source_name ? strdup(i->default_source_name) : NULL;
+
+ WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
+}
+
+static int
+pulse_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection)
+{
+ pulse_dev_list_data user_data = {NULL, NULL, NULL, 0, 0, context};
+ pa_operation * o;
+
+ WRAP(pa_threaded_mainloop_lock)(context->mainloop);
+
+ o = WRAP(pa_context_get_server_info)(context->context, pulse_server_info_cb,
+ &user_data);
+ if (o) {
+ operation_wait(context, NULL, o);
+ WRAP(pa_operation_unref)(o);
+ }
+
+ if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
+ o = WRAP(pa_context_get_sink_info_list)(context->context,
+ pulse_sink_info_cb, &user_data);
+ if (o) {
+ operation_wait(context, NULL, o);
+ WRAP(pa_operation_unref)(o);
+ }
+ }
+
+ if (type & CUBEB_DEVICE_TYPE_INPUT) {
+ o = WRAP(pa_context_get_source_info_list)(context->context,
+ pulse_source_info_cb, &user_data);
+ if (o) {
+ operation_wait(context, NULL, o);
+ WRAP(pa_operation_unref)(o);
+ }
+ }
+
+ WRAP(pa_threaded_mainloop_unlock)(context->mainloop);
+
+ collection->device = user_data.devinfo;
+ collection->count = user_data.count;
+
+ free(user_data.default_sink_name);
+ free(user_data.default_source_name);
+ return CUBEB_OK;
+}
+
+static int
+pulse_device_collection_destroy(cubeb * ctx,
+ cubeb_device_collection * collection)
+{
+ size_t n;
+
+ for (n = 0; n < collection->count; n++) {
+ free((void *)collection->device[n].friendly_name);
+ free((void *)collection->device[n].vendor_name);
+ free((void *)collection->device[n].group_id);
+ }
+
+ free(collection->device);
+ return CUBEB_OK;
+}
+
+static int
+pulse_stream_get_current_device(cubeb_stream * stm,
+ cubeb_device ** const device)
+{
+#if PA_CHECK_VERSION(0, 9, 8)
+ *device = calloc(1, sizeof(cubeb_device));
+ if (*device == NULL)
+ return CUBEB_ERROR;
+
+ if (stm->input_stream) {
+ const char * name = WRAP(pa_stream_get_device_name)(stm->input_stream);
+ (*device)->input_name = (name == NULL) ? NULL : strdup(name);
+ }
+
+ if (stm->output_stream) {
+ const char * name = WRAP(pa_stream_get_device_name)(stm->output_stream);
+ (*device)->output_name = (name == NULL) ? NULL : strdup(name);
+ }
+
+ return CUBEB_OK;
+#else
+ return CUBEB_ERROR_NOT_SUPPORTED;
+#endif
+}
+
+static int
+pulse_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
+{
+ (void)stream;
+ free(device->input_name);
+ free(device->output_name);
+ free(device);
+ return CUBEB_OK;
+}
+
+static void
+pulse_subscribe_callback(pa_context * ctx, pa_subscription_event_type_t t,
+ uint32_t index, void * userdata)
+{
+ (void)ctx;
+ cubeb * context = userdata;
+
+ switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
+ case PA_SUBSCRIPTION_EVENT_SERVER:
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) {
+ LOG("Server changed %d", index);
+ WRAP(pa_context_get_server_info)
+ (context->context, server_info_callback, context);
+ }
+ break;
+ case PA_SUBSCRIPTION_EVENT_SOURCE:
+ case PA_SUBSCRIPTION_EVENT_SINK:
+
+ if (cubeb_log_get_level()) {
+ if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) ==
+ PA_SUBSCRIPTION_EVENT_SOURCE &&
+ (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
+ PA_SUBSCRIPTION_EVENT_REMOVE) {
+ LOG("Removing source index %d", index);
+ } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) ==
+ PA_SUBSCRIPTION_EVENT_SOURCE &&
+ (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
+ PA_SUBSCRIPTION_EVENT_NEW) {
+ LOG("Adding source index %d", index);
+ }
+ if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) ==
+ PA_SUBSCRIPTION_EVENT_SINK &&
+ (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
+ PA_SUBSCRIPTION_EVENT_REMOVE) {
+ LOG("Removing sink index %d", index);
+ } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) ==
+ PA_SUBSCRIPTION_EVENT_SINK &&
+ (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
+ PA_SUBSCRIPTION_EVENT_NEW) {
+ LOG("Adding sink index %d", index);
+ }
+ }
+
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE ||
+ (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
+ if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) ==
+ PA_SUBSCRIPTION_EVENT_SOURCE) {
+ context->input_collection_changed_callback(
+ context, context->input_collection_changed_user_ptr);
+ }
+ if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) ==
+ PA_SUBSCRIPTION_EVENT_SINK) {
+ context->output_collection_changed_callback(
+ context, context->output_collection_changed_user_ptr);
+ }
+ }
+ break;
+ }
+}
+
+static void
+subscribe_success(pa_context * c, int success, void * userdata)
+{
+ (void)c;
+ cubeb * context = userdata;
+ assert(success);
+ WRAP(pa_threaded_mainloop_signal)(context->mainloop, 0);
+}
+
+static int
+pulse_subscribe_notifications(cubeb * context, pa_subscription_mask_t mask)
+{
+ WRAP(pa_threaded_mainloop_lock)(context->mainloop);
+
+ WRAP(pa_context_set_subscribe_callback)
+ (context->context, pulse_subscribe_callback, context);
+
+ pa_operation * o;
+ o = WRAP(pa_context_subscribe)(context->context, mask, subscribe_success,
+ context);
+ if (o == NULL) {
+ WRAP(pa_threaded_mainloop_unlock)(context->mainloop);
+ LOG("Context subscribe failed");
+ return CUBEB_ERROR;
+ }
+ operation_wait(context, NULL, o);
+ WRAP(pa_operation_unref)(o);
+
+ WRAP(pa_threaded_mainloop_unlock)(context->mainloop);
+
+ return CUBEB_OK;
+}
+
+static int
+pulse_register_device_collection_changed(
+ cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback collection_changed_callback,
+ void * user_ptr)
+{
+ if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
+ context->input_collection_changed_callback = collection_changed_callback;
+ context->input_collection_changed_user_ptr = user_ptr;
+ }
+ if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
+ context->output_collection_changed_callback = collection_changed_callback;
+ context->output_collection_changed_user_ptr = user_ptr;
+ }
+
+ pa_subscription_mask_t mask = PA_SUBSCRIPTION_MASK_NULL;
+ if (context->input_collection_changed_callback) {
+ /* Input added or removed */
+ mask |= PA_SUBSCRIPTION_MASK_SOURCE;
+ }
+ if (context->output_collection_changed_callback) {
+ /* Output added or removed */
+ mask |= PA_SUBSCRIPTION_MASK_SINK;
+ }
+ /* Default device changed, this is always registered in order to update the
+ * `default_sink_info` when the default device changes. */
+ mask |= PA_SUBSCRIPTION_MASK_SERVER;
+
+ return pulse_subscribe_notifications(context, mask);
+}
+
+static struct cubeb_ops const pulse_ops = {
+ .init = pulse_init,
+ .get_backend_id = pulse_get_backend_id,
+ .get_max_channel_count = pulse_get_max_channel_count,
+ .get_min_latency = pulse_get_min_latency,
+ .get_preferred_sample_rate = pulse_get_preferred_sample_rate,
+ .get_supported_input_processing_params = NULL,
+ .enumerate_devices = pulse_enumerate_devices,
+ .device_collection_destroy = pulse_device_collection_destroy,
+ .destroy = pulse_destroy,
+ .stream_init = pulse_stream_init,
+ .stream_destroy = pulse_stream_destroy,
+ .stream_start = pulse_stream_start,
+ .stream_stop = pulse_stream_stop,
+ .stream_get_position = pulse_stream_get_position,
+ .stream_get_latency = pulse_stream_get_latency,
+ .stream_get_input_latency = NULL,
+ .stream_set_volume = pulse_stream_set_volume,
+ .stream_set_name = pulse_stream_set_name,
+ .stream_get_current_device = pulse_stream_get_current_device,
+ .stream_set_input_mute = NULL,
+ .stream_set_input_processing_params = NULL,
+ .stream_device_destroy = pulse_stream_device_destroy,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed =
+ pulse_register_device_collection_changed};
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_resampler.cpp b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_resampler.cpp
new file mode 100644
index 0000000000..c31944b826
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_resampler.cpp
@@ -0,0 +1,373 @@
+/*
+ * Copyright © 2014 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif // NOMINMAX
+
+#include "cubeb_resampler.h"
+#include "cubeb-speex-resampler.h"
+#include "cubeb_resampler_internal.h"
+#include "cubeb_utils.h"
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstddef>
+#include <cstdio>
+#include <cstring>
+
+int
+to_speex_quality(cubeb_resampler_quality q)
+{
+ switch (q) {
+ case CUBEB_RESAMPLER_QUALITY_VOIP:
+ return SPEEX_RESAMPLER_QUALITY_VOIP;
+ case CUBEB_RESAMPLER_QUALITY_DEFAULT:
+ return SPEEX_RESAMPLER_QUALITY_DEFAULT;
+ case CUBEB_RESAMPLER_QUALITY_DESKTOP:
+ return SPEEX_RESAMPLER_QUALITY_DESKTOP;
+ default:
+ assert(false);
+ return 0XFFFFFFFF;
+ }
+}
+
+uint32_t
+min_buffered_audio_frame(uint32_t sample_rate)
+{
+ return sample_rate / 20;
+}
+
+template <typename T>
+passthrough_resampler<T>::passthrough_resampler(cubeb_stream * s,
+ cubeb_data_callback cb,
+ void * ptr,
+ uint32_t input_channels,
+ uint32_t sample_rate)
+ : processor(input_channels), stream(s), data_callback(cb), user_ptr(ptr),
+ sample_rate(sample_rate)
+{
+}
+
+template <typename T>
+long
+passthrough_resampler<T>::fill(void * input_buffer, long * input_frames_count,
+ void * output_buffer, long output_frames)
+{
+ if (input_buffer) {
+ assert(input_frames_count);
+ }
+ assert((input_buffer && output_buffer) ||
+ (output_buffer && !input_buffer &&
+ (!input_frames_count || *input_frames_count == 0)) ||
+ (input_buffer && !output_buffer && output_frames == 0));
+
+ // When we have no pending input data and exactly as much input
+ // as output data, we don't need to copy it into the internal buffer
+ // and can directly forward it to the callback.
+ void * in_buf = input_buffer;
+ unsigned long pop_input_count = 0u;
+ if (input_buffer && !output_buffer) {
+ output_frames = *input_frames_count;
+ } else if (input_buffer) {
+ if (internal_input_buffer.length() != 0 ||
+ *input_frames_count < output_frames) {
+ // If we have pending input data left and have to first append the input
+ // so we can pass it as one pointer to the callback. Or this is a glitch.
+ // It can happen when system's performance is poor. Audible silence is
+ // being pushed at the end of the short input buffer. An improvement for
+ // the future is to resample to the output number of frames, when that
+ // happens.
+ internal_input_buffer.push(static_cast<T *>(input_buffer),
+ frames_to_samples(*input_frames_count));
+ if (internal_input_buffer.length() < frames_to_samples(output_frames)) {
+ // This is unxpected but it can happen when a glitch occurs. Fill the
+ // buffer with silence. First keep the actual number of input samples
+ // used without the silence.
+ pop_input_count = internal_input_buffer.length();
+ internal_input_buffer.push_silence(frames_to_samples(output_frames) -
+ internal_input_buffer.length());
+ } else {
+ pop_input_count = frames_to_samples(output_frames);
+ }
+ in_buf = internal_input_buffer.data();
+ } else if (*input_frames_count > output_frames) {
+ // In this case we have more input that we need output and
+ // fill the overflowing input into internal_input_buffer
+ // Since we have no other pending data, we can nonetheless
+ // pass the current input data directly to the callback
+ assert(pop_input_count == 0);
+ unsigned long samples_off = frames_to_samples(output_frames);
+ internal_input_buffer.push(
+ static_cast<T *>(input_buffer) + samples_off,
+ frames_to_samples(*input_frames_count - output_frames));
+ }
+ }
+
+ long rv =
+ data_callback(stream, user_ptr, in_buf, output_buffer, output_frames);
+
+ if (input_buffer) {
+ if (pop_input_count) {
+ internal_input_buffer.pop(nullptr, pop_input_count);
+ *input_frames_count = samples_to_frames(pop_input_count);
+ } else {
+ *input_frames_count = output_frames;
+ }
+ drop_audio_if_needed();
+ }
+
+ return rv;
+}
+
+// Explicit instantiation of template class.
+template class passthrough_resampler<float>;
+template class passthrough_resampler<short>;
+
+template <typename T, typename InputProcessor, typename OutputProcessor>
+cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::
+ cubeb_resampler_speex(InputProcessor * input_processor,
+ OutputProcessor * output_processor, cubeb_stream * s,
+ cubeb_data_callback cb, void * ptr)
+ : input_processor(input_processor), output_processor(output_processor),
+ stream(s), data_callback(cb), user_ptr(ptr)
+{
+ if (input_processor && output_processor) {
+ fill_internal = &cubeb_resampler_speex::fill_internal_duplex;
+ } else if (input_processor) {
+ fill_internal = &cubeb_resampler_speex::fill_internal_input;
+ } else if (output_processor) {
+ fill_internal = &cubeb_resampler_speex::fill_internal_output;
+ }
+}
+
+template <typename T, typename InputProcessor, typename OutputProcessor>
+cubeb_resampler_speex<T, InputProcessor,
+ OutputProcessor>::~cubeb_resampler_speex()
+{
+}
+
+template <typename T, typename InputProcessor, typename OutputProcessor>
+long
+cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::fill(
+ void * input_buffer, long * input_frames_count, void * output_buffer,
+ long output_frames_needed)
+{
+ /* Input and output buffers, typed */
+ T * in_buffer = reinterpret_cast<T *>(input_buffer);
+ T * out_buffer = reinterpret_cast<T *>(output_buffer);
+ return (this->*fill_internal)(in_buffer, input_frames_count, out_buffer,
+ output_frames_needed);
+}
+
+template <typename T, typename InputProcessor, typename OutputProcessor>
+long
+cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::fill_internal_output(
+ T * input_buffer, long * input_frames_count, T * output_buffer,
+ long output_frames_needed)
+{
+ assert(!input_buffer && (!input_frames_count || *input_frames_count == 0) &&
+ output_buffer && output_frames_needed);
+
+ if (!draining) {
+ long got = 0;
+ T * out_unprocessed = nullptr;
+ long output_frames_before_processing = 0;
+
+ /* fill directly the input buffer of the output processor to save a copy */
+ output_frames_before_processing =
+ output_processor->input_needed_for_output(output_frames_needed);
+
+ out_unprocessed =
+ output_processor->input_buffer(output_frames_before_processing);
+
+ got = data_callback(stream, user_ptr, nullptr, out_unprocessed,
+ output_frames_before_processing);
+
+ if (got < output_frames_before_processing) {
+ draining = true;
+
+ if (got < 0) {
+ return got;
+ }
+ }
+
+ output_processor->written(got);
+ }
+
+ /* Process the output. If not enough frames have been returned from the
+ * callback, drain the processors. */
+ return output_processor->output(output_buffer, output_frames_needed);
+}
+
+template <typename T, typename InputProcessor, typename OutputProcessor>
+long
+cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::fill_internal_input(
+ T * input_buffer, long * input_frames_count, T * output_buffer,
+ long /*output_frames_needed*/)
+{
+ assert(input_buffer && input_frames_count && *input_frames_count &&
+ !output_buffer);
+
+ /* The input data, after eventual resampling. This is passed to the callback.
+ */
+ T * resampled_input = nullptr;
+ uint32_t resampled_frame_count =
+ input_processor->output_for_input(*input_frames_count);
+
+ /* process the input, and present exactly `output_frames_needed` in the
+ * callback. */
+ input_processor->input(input_buffer, *input_frames_count);
+
+ /* resampled_frame_count == 0 happens if the resampler
+ * doesn't have enough input frames buffered to produce 1 resampled frame. */
+ if (resampled_frame_count == 0) {
+ return *input_frames_count;
+ }
+
+ size_t frames_resampled = 0;
+ resampled_input =
+ input_processor->output(resampled_frame_count, &frames_resampled);
+ *input_frames_count = frames_resampled;
+
+ long got = data_callback(stream, user_ptr, resampled_input, nullptr,
+ resampled_frame_count);
+
+ /* Return the number of initial input frames or part of it.
+ * Since output_frames_needed == 0 in input scenario, the only
+ * available number outside resampler is the initial number of frames. */
+ return (*input_frames_count) * (got / resampled_frame_count);
+}
+
+template <typename T, typename InputProcessor, typename OutputProcessor>
+long
+cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::fill_internal_duplex(
+ T * in_buffer, long * input_frames_count, T * out_buffer,
+ long output_frames_needed)
+{
+ if (draining) {
+ // discard input and drain any signal remaining in the resampler.
+ return output_processor->output(out_buffer, output_frames_needed);
+ }
+
+ /* The input data, after eventual resampling. This is passed to the callback.
+ */
+ T * resampled_input = nullptr;
+ /* The output buffer passed down in the callback, that might be resampled. */
+ T * out_unprocessed = nullptr;
+ long output_frames_before_processing = 0;
+ /* The number of frames returned from the callback. */
+ long got = 0;
+
+ /* We need to determine how much frames to present to the consumer.
+ * - If we have a two way stream, but we're only resampling input, we resample
+ * the input to the number of output frames.
+ * - If we have a two way stream, but we're only resampling the output, we
+ * resize the input buffer of the output resampler to the number of input
+ * frames, and we resample it afterwards.
+ * - If we resample both ways, we resample the input to the number of frames
+ * we would need to pass down to the consumer (before resampling the output),
+ * get the output data, and resample it to the number of frames needed by the
+ * caller. */
+
+ output_frames_before_processing =
+ output_processor->input_needed_for_output(output_frames_needed);
+ /* fill directly the input buffer of the output processor to save a copy */
+ out_unprocessed =
+ output_processor->input_buffer(output_frames_before_processing);
+
+ if (in_buffer) {
+ /* process the input, and present exactly `output_frames_needed` in the
+ * callback. */
+ input_processor->input(in_buffer, *input_frames_count);
+
+ size_t frames_resampled = 0;
+ resampled_input = input_processor->output(output_frames_before_processing,
+ &frames_resampled);
+ *input_frames_count = frames_resampled;
+ } else {
+ resampled_input = nullptr;
+ }
+
+ got = data_callback(stream, user_ptr, resampled_input, out_unprocessed,
+ output_frames_before_processing);
+
+ if (got < output_frames_before_processing) {
+ draining = true;
+
+ if (got < 0) {
+ return got;
+ }
+ }
+
+ output_processor->written(got);
+
+ input_processor->drop_audio_if_needed();
+
+ /* Process the output. If not enough frames have been returned from the
+ * callback, drain the processors. */
+ got = output_processor->output(out_buffer, output_frames_needed);
+
+ output_processor->drop_audio_if_needed();
+
+ return got;
+}
+
+/* Resampler C API */
+
+cubeb_resampler *
+cubeb_resampler_create(cubeb_stream * stream,
+ cubeb_stream_params * input_params,
+ cubeb_stream_params * output_params,
+ unsigned int target_rate, cubeb_data_callback callback,
+ void * user_ptr, cubeb_resampler_quality quality,
+ cubeb_resampler_reclock reclock)
+{
+ cubeb_sample_format format;
+
+ assert(input_params || output_params);
+
+ if (input_params) {
+ format = input_params->format;
+ } else {
+ format = output_params->format;
+ }
+
+ switch (format) {
+ case CUBEB_SAMPLE_S16NE:
+ return cubeb_resampler_create_internal<short>(
+ stream, input_params, output_params, target_rate, callback, user_ptr,
+ quality, reclock);
+ case CUBEB_SAMPLE_FLOAT32NE:
+ return cubeb_resampler_create_internal<float>(
+ stream, input_params, output_params, target_rate, callback, user_ptr,
+ quality, reclock);
+ default:
+ assert(false);
+ return nullptr;
+ }
+}
+
+long
+cubeb_resampler_fill(cubeb_resampler * resampler, void * input_buffer,
+ long * input_frames_count, void * output_buffer,
+ long output_frames_needed)
+{
+ return resampler->fill(input_buffer, input_frames_count, output_buffer,
+ output_frames_needed);
+}
+
+void
+cubeb_resampler_destroy(cubeb_resampler * resampler)
+{
+ delete resampler;
+}
+
+long
+cubeb_resampler_latency(cubeb_resampler * resampler)
+{
+ return resampler->latency();
+}
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_resampler.h b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_resampler.h
new file mode 100644
index 0000000000..711a3771d4
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_resampler.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright © 2014 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#ifndef CUBEB_RESAMPLER_H
+#define CUBEB_RESAMPLER_H
+
+#include "cubeb/cubeb.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct cubeb_resampler cubeb_resampler;
+
+typedef enum {
+ CUBEB_RESAMPLER_QUALITY_VOIP,
+ CUBEB_RESAMPLER_QUALITY_DEFAULT,
+ CUBEB_RESAMPLER_QUALITY_DESKTOP
+} cubeb_resampler_quality;
+
+typedef enum {
+ CUBEB_RESAMPLER_RECLOCK_NONE,
+ CUBEB_RESAMPLER_RECLOCK_INPUT
+} cubeb_resampler_reclock;
+
+/**
+ * Create a resampler to adapt the requested sample rate into something that
+ * is accepted by the audio backend.
+ * @param stream A cubeb_stream instance supplied to the data callback.
+ * @param input_params Used to calculate bytes per frame and buffer size for
+ * resampling of the input side of the stream. NULL if input should not be
+ * resampled.
+ * @param output_params Used to calculate bytes per frame and buffer size for
+ * resampling of the output side of the stream. NULL if output should not be
+ * resampled.
+ * @param target_rate The sampling rate after resampling for the input side of
+ * the stream, and/or the sampling rate prior to resampling of the output side
+ * of the stream.
+ * @param callback A callback to request data for resampling.
+ * @param user_ptr User data supplied to the data callback.
+ * @param quality Quality of the resampler.
+ * @retval A non-null pointer if success.
+ */
+cubeb_resampler *
+cubeb_resampler_create(cubeb_stream * stream,
+ cubeb_stream_params * input_params,
+ cubeb_stream_params * output_params,
+ unsigned int target_rate, cubeb_data_callback callback,
+ void * user_ptr, cubeb_resampler_quality quality,
+ cubeb_resampler_reclock reclock);
+
+/**
+ * Fill the buffer with frames acquired using the data callback. Resampling will
+ * happen if necessary.
+ * @param resampler A cubeb_resampler instance.
+ * @param input_buffer A buffer of input samples
+ * @param input_frame_count The size of the buffer. Returns the number of frames
+ * consumed.
+ * @param output_buffer The buffer to be filled.
+ * @param output_frames_needed Number of frames that should be produced.
+ * @retval Number of frames that are actually produced.
+ * @retval CUBEB_ERROR on error.
+ */
+long
+cubeb_resampler_fill(cubeb_resampler * resampler, void * input_buffer,
+ long * input_frame_count, void * output_buffer,
+ long output_frames_needed);
+
+/**
+ * Destroy a cubeb_resampler.
+ * @param resampler A cubeb_resampler instance.
+ */
+void
+cubeb_resampler_destroy(cubeb_resampler * resampler);
+
+/**
+ * Returns the latency, in frames, of the resampler.
+ * @param resampler A cubeb resampler instance.
+ * @retval The latency, in frames, induced by the resampler.
+ */
+long
+cubeb_resampler_latency(cubeb_resampler * resampler);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* CUBEB_RESAMPLER_H */
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_resampler_internal.h b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_resampler_internal.h
new file mode 100644
index 0000000000..285f24dd0b
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_resampler_internal.h
@@ -0,0 +1,591 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#if !defined(CUBEB_RESAMPLER_INTERNAL)
+#define CUBEB_RESAMPLER_INTERNAL
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <memory>
+#ifdef CUBEB_GECKO_BUILD
+#include "mozilla/UniquePtr.h"
+// In libc++, symbols such as std::unique_ptr may be defined in std::__1.
+// The _LIBCPP_BEGIN_NAMESPACE_STD and _LIBCPP_END_NAMESPACE_STD macros
+// will expand to the correct namespace.
+#ifdef _LIBCPP_BEGIN_NAMESPACE_STD
+#define MOZ_BEGIN_STD_NAMESPACE _LIBCPP_BEGIN_NAMESPACE_STD
+#define MOZ_END_STD_NAMESPACE _LIBCPP_END_NAMESPACE_STD
+#else
+#define MOZ_BEGIN_STD_NAMESPACE namespace std {
+#define MOZ_END_STD_NAMESPACE }
+#endif
+MOZ_BEGIN_STD_NAMESPACE
+using mozilla::DefaultDelete;
+using mozilla::UniquePtr;
+#define default_delete DefaultDelete
+#define unique_ptr UniquePtr
+MOZ_END_STD_NAMESPACE
+#endif
+#include "cubeb-speex-resampler.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_log.h"
+#include "cubeb_resampler.h"
+#include "cubeb_utils.h"
+#include <stdio.h>
+
+/* This header file contains the internal C++ API of the resamplers, for
+ * testing. */
+
+// When dropping audio input frames to prevent building
+// an input delay, this function returns the number of frames
+// to keep in the buffer.
+// @parameter sample_rate The sample rate of the stream.
+// @return A number of frames to keep.
+uint32_t
+min_buffered_audio_frame(uint32_t sample_rate);
+
+int
+to_speex_quality(cubeb_resampler_quality q);
+
+struct cubeb_resampler {
+ virtual long fill(void * input_buffer, long * input_frames_count,
+ void * output_buffer, long frames_needed) = 0;
+ virtual long latency() = 0;
+ virtual ~cubeb_resampler() {}
+};
+
+/** Base class for processors. This is just used to share methods for now. */
+class processor {
+public:
+ explicit processor(uint32_t channels) : channels(channels) {}
+
+protected:
+ size_t frames_to_samples(size_t frames) const { return frames * channels; }
+ size_t samples_to_frames(size_t samples) const
+ {
+ assert(!(samples % channels));
+ return samples / channels;
+ }
+ /** The number of channel of the audio buffers to be resampled. */
+ const uint32_t channels;
+};
+
+template <typename T>
+class passthrough_resampler : public cubeb_resampler, public processor {
+public:
+ passthrough_resampler(cubeb_stream * s, cubeb_data_callback cb, void * ptr,
+ uint32_t input_channels, uint32_t sample_rate);
+
+ virtual long fill(void * input_buffer, long * input_frames_count,
+ void * output_buffer, long output_frames);
+
+ virtual long latency() { return 0; }
+
+ void drop_audio_if_needed()
+ {
+ uint32_t to_keep = min_buffered_audio_frame(sample_rate);
+ uint32_t available = samples_to_frames(internal_input_buffer.length());
+ if (available > to_keep) {
+ ALOGV("Dropping %u frames", available - to_keep);
+ internal_input_buffer.pop(nullptr,
+ frames_to_samples(available - to_keep));
+ }
+ }
+
+private:
+ cubeb_stream * const stream;
+ const cubeb_data_callback data_callback;
+ void * const user_ptr;
+ /* This allows to buffer some input to account for the fact that we buffer
+ * some inputs. */
+ auto_array<T> internal_input_buffer;
+ uint32_t sample_rate;
+};
+
+/** Bidirectional resampler, can resample an input and an output stream, or just
+ * an input stream or output stream. In this case a delay is inserted in the
+ * opposite direction to keep the streams synchronized. */
+template <typename T, typename InputProcessing, typename OutputProcessing>
+class cubeb_resampler_speex : public cubeb_resampler {
+public:
+ cubeb_resampler_speex(InputProcessing * input_processor,
+ OutputProcessing * output_processor, cubeb_stream * s,
+ cubeb_data_callback cb, void * ptr);
+
+ virtual ~cubeb_resampler_speex();
+
+ virtual long fill(void * input_buffer, long * input_frames_count,
+ void * output_buffer, long output_frames_needed);
+
+ virtual long latency()
+ {
+ if (input_processor && output_processor) {
+ assert(input_processor->latency() == output_processor->latency());
+ return input_processor->latency();
+ } else if (input_processor) {
+ return input_processor->latency();
+ } else {
+ return output_processor->latency();
+ }
+ }
+
+private:
+ typedef long (cubeb_resampler_speex::*processing_callback)(
+ T * input_buffer, long * input_frames_count, T * output_buffer,
+ long output_frames_needed);
+
+ long fill_internal_duplex(T * input_buffer, long * input_frames_count,
+ T * output_buffer, long output_frames_needed);
+ long fill_internal_input(T * input_buffer, long * input_frames_count,
+ T * output_buffer, long output_frames_needed);
+ long fill_internal_output(T * input_buffer, long * input_frames_count,
+ T * output_buffer, long output_frames_needed);
+
+ std::unique_ptr<InputProcessing> input_processor;
+ std::unique_ptr<OutputProcessing> output_processor;
+ processing_callback fill_internal;
+ cubeb_stream * const stream;
+ const cubeb_data_callback data_callback;
+ void * const user_ptr;
+ bool draining = false;
+};
+
+/** Handles one way of a (possibly) duplex resampler, working on interleaved
+ * audio buffers of type T. This class is designed so that the number of frames
+ * coming out of the resampler can be precisely controled. It manages its own
+ * input buffer, and can use the caller's output buffer, or allocate its own. */
+template <typename T> class cubeb_resampler_speex_one_way : public processor {
+public:
+ /** The sample type of this resampler, either 16-bit integers or 32-bit
+ * floats. */
+ typedef T sample_type;
+ /** Construct a resampler resampling from #source_rate to #target_rate, that
+ * can be arbitrary, strictly positive number.
+ * @parameter channels The number of channels this resampler will resample.
+ * @parameter source_rate The sample-rate of the audio input.
+ * @parameter target_rate The sample-rate of the audio output.
+ * @parameter quality A number between 0 (fast, low quality) and 10 (slow,
+ * high quality). */
+ cubeb_resampler_speex_one_way(uint32_t channels, uint32_t source_rate,
+ uint32_t target_rate, int quality)
+ : processor(channels),
+ resampling_ratio(static_cast<float>(source_rate) / target_rate),
+ source_rate(source_rate), additional_latency(0), leftover_samples(0)
+ {
+ int r;
+ speex_resampler =
+ speex_resampler_init(channels, source_rate, target_rate, quality, &r);
+ assert(r == RESAMPLER_ERR_SUCCESS && "resampler allocation failure");
+
+ uint32_t input_latency = speex_resampler_get_input_latency(speex_resampler);
+ const size_t LATENCY_SAMPLES = 8192;
+ T input_buffer[LATENCY_SAMPLES] = {};
+ T output_buffer[LATENCY_SAMPLES] = {};
+ uint32_t input_frame_count = input_latency;
+ uint32_t output_frame_count = LATENCY_SAMPLES;
+ assert(input_latency * channels <= LATENCY_SAMPLES);
+ speex_resample(input_buffer, &input_frame_count, output_buffer,
+ &output_frame_count);
+ }
+
+ /** Destructor, deallocate the resampler */
+ virtual ~cubeb_resampler_speex_one_way()
+ {
+ speex_resampler_destroy(speex_resampler);
+ }
+
+ /* Fill the resampler with `input_frame_count` frames. */
+ void input(T * input_buffer, size_t input_frame_count)
+ {
+ resampling_in_buffer.push(input_buffer,
+ frames_to_samples(input_frame_count));
+ }
+
+ /** Outputs exactly `output_frame_count` into `output_buffer`.
+ * `output_buffer` has to be at least `output_frame_count` long. */
+ size_t output(T * output_buffer, size_t output_frame_count)
+ {
+ uint32_t in_len = samples_to_frames(resampling_in_buffer.length());
+ uint32_t out_len = output_frame_count;
+
+ speex_resample(resampling_in_buffer.data(), &in_len, output_buffer,
+ &out_len);
+
+ /* This shifts back any unresampled samples to the beginning of the input
+ buffer. */
+ resampling_in_buffer.pop(nullptr, frames_to_samples(in_len));
+
+ return out_len;
+ }
+
+ size_t output_for_input(uint32_t input_frames)
+ {
+ return (size_t)floorf(
+ (input_frames + samples_to_frames(resampling_in_buffer.length())) /
+ resampling_ratio);
+ }
+
+ /** Returns a buffer containing exactly `output_frame_count` resampled frames.
+ * The consumer should not hold onto the pointer. */
+ T * output(size_t output_frame_count, size_t * input_frames_used)
+ {
+ if (resampling_out_buffer.capacity() <
+ frames_to_samples(output_frame_count)) {
+ resampling_out_buffer.reserve(frames_to_samples(output_frame_count));
+ }
+
+ uint32_t in_len = samples_to_frames(resampling_in_buffer.length());
+ uint32_t out_len = output_frame_count;
+
+ speex_resample(resampling_in_buffer.data(), &in_len,
+ resampling_out_buffer.data(), &out_len);
+
+ if (out_len < output_frame_count) {
+ LOGV("underrun during resampling: got %u frames, expected %zu",
+ (unsigned)out_len, output_frame_count);
+ // silence the rightmost part
+ T * data = resampling_out_buffer.data();
+ for (uint32_t i = frames_to_samples(out_len);
+ i < frames_to_samples(output_frame_count); i++) {
+ data[i] = 0;
+ }
+ }
+
+ /* This shifts back any unresampled samples to the beginning of the input
+ buffer. */
+ resampling_in_buffer.pop(nullptr, frames_to_samples(in_len));
+ *input_frames_used = in_len;
+
+ return resampling_out_buffer.data();
+ }
+
+ /** Get the latency of the resampler, in output frames. */
+ uint32_t latency() const
+ {
+ /* The documentation of the resampler talks about "samples" here, but it
+ * only consider a single channel here so it's the same number of frames. */
+ int latency = 0;
+
+ latency = speex_resampler_get_output_latency(speex_resampler) +
+ additional_latency;
+
+ assert(latency >= 0);
+
+ return latency;
+ }
+
+ /** Returns the number of frames to pass in the input of the resampler to have
+ * exactly `output_frame_count` resampled frames. This can return a number
+ * slightly bigger than what is strictly necessary, but it guaranteed that the
+ * number of output frames will be exactly equal. */
+ uint32_t input_needed_for_output(int32_t output_frame_count) const
+ {
+ assert(output_frame_count >= 0); // Check overflow
+ int32_t unresampled_frames_left =
+ samples_to_frames(resampling_in_buffer.length());
+ int32_t resampled_frames_left =
+ samples_to_frames(resampling_out_buffer.length());
+ float input_frames_needed =
+ (output_frame_count - unresampled_frames_left) * resampling_ratio -
+ resampled_frames_left;
+ if (input_frames_needed < 0) {
+ return 0;
+ }
+ return (uint32_t)ceilf(input_frames_needed);
+ }
+
+ /** Returns a pointer to the input buffer, that contains empty space for at
+ * least `frame_count` elements. This is useful so that consumer can directly
+ * write into the input buffer of the resampler. The pointer returned is
+ * adjusted so that leftover data are not overwritten.
+ */
+ T * input_buffer(size_t frame_count)
+ {
+ leftover_samples = resampling_in_buffer.length();
+ resampling_in_buffer.reserve(leftover_samples +
+ frames_to_samples(frame_count));
+ return resampling_in_buffer.data() + leftover_samples;
+ }
+
+ /** This method works with `input_buffer`, and allows to inform the processor
+ how much frames have been written in the provided buffer. */
+ void written(size_t written_frames)
+ {
+ resampling_in_buffer.set_length(leftover_samples +
+ frames_to_samples(written_frames));
+ }
+
+ void drop_audio_if_needed()
+ {
+ // Keep at most 100ms buffered.
+ uint32_t available = samples_to_frames(resampling_in_buffer.length());
+ uint32_t to_keep = min_buffered_audio_frame(source_rate);
+ if (available > to_keep) {
+ ALOGV("Dropping %u frames", available - to_keep);
+ resampling_in_buffer.pop(nullptr, frames_to_samples(available - to_keep));
+ }
+ }
+
+private:
+ /** Wrapper for the speex resampling functions to have a typed
+ * interface. */
+ void speex_resample(float * input_buffer, uint32_t * input_frame_count,
+ float * output_buffer, uint32_t * output_frame_count)
+ {
+#ifndef NDEBUG
+ int rv;
+ rv =
+#endif
+ speex_resampler_process_interleaved_float(
+ speex_resampler, input_buffer, input_frame_count, output_buffer,
+ output_frame_count);
+ assert(rv == RESAMPLER_ERR_SUCCESS);
+ }
+
+ void speex_resample(short * input_buffer, uint32_t * input_frame_count,
+ short * output_buffer, uint32_t * output_frame_count)
+ {
+#ifndef NDEBUG
+ int rv;
+ rv =
+#endif
+ speex_resampler_process_interleaved_int(
+ speex_resampler, input_buffer, input_frame_count, output_buffer,
+ output_frame_count);
+ assert(rv == RESAMPLER_ERR_SUCCESS);
+ }
+ /** The state for the speex resampler used internaly. */
+ SpeexResamplerState * speex_resampler;
+ /** Source rate / target rate. */
+ const float resampling_ratio;
+ const uint32_t source_rate;
+ /** Storage for the input frames, to be resampled. Also contains
+ * any unresampled frames after resampling. */
+ auto_array<T> resampling_in_buffer;
+ /* Storage for the resampled frames, to be passed back to the caller. */
+ auto_array<T> resampling_out_buffer;
+ /** Additional latency inserted into the pipeline for synchronisation. */
+ uint32_t additional_latency;
+ /** When `input_buffer` is called, this allows tracking the number of samples
+ that were in the buffer. */
+ uint32_t leftover_samples;
+};
+
+/** This class allows delaying an audio stream by `frames` frames. */
+template <typename T> class delay_line : public processor {
+public:
+ /** Constructor
+ * @parameter frames the number of frames of delay.
+ * @parameter channels the number of channels of this delay line.
+ * @parameter sample_rate sample-rate of the audio going through this delay
+ * line */
+ delay_line(uint32_t frames, uint32_t channels, uint32_t sample_rate)
+ : processor(channels), length(frames), leftover_samples(0),
+ sample_rate(sample_rate)
+ {
+ /* Fill the delay line with some silent frames to add latency. */
+ delay_input_buffer.push_silence(frames * channels);
+ }
+ /** Push some frames into the delay line.
+ * @parameter buffer the frames to push.
+ * @parameter frame_count the number of frames in #buffer. */
+ void input(T * buffer, uint32_t frame_count)
+ {
+ delay_input_buffer.push(buffer, frames_to_samples(frame_count));
+ }
+ /** Pop some frames from the internal buffer, into a internal output buffer.
+ * @parameter frames_needed the number of frames to be returned.
+ * @return a buffer containing the delayed frames. The consumer should not
+ * hold onto the pointer. */
+ T * output(uint32_t frames_needed, size_t * input_frames_used)
+ {
+ if (delay_output_buffer.capacity() < frames_to_samples(frames_needed)) {
+ delay_output_buffer.reserve(frames_to_samples(frames_needed));
+ }
+
+ delay_output_buffer.clear();
+ delay_output_buffer.push(delay_input_buffer.data(),
+ frames_to_samples(frames_needed));
+ delay_input_buffer.pop(nullptr, frames_to_samples(frames_needed));
+ *input_frames_used = frames_needed;
+
+ return delay_output_buffer.data();
+ }
+ /** Get a pointer to the first writable location in the input buffer>
+ * @parameter frames_needed the number of frames the user needs to write into
+ * the buffer.
+ * @returns a pointer to a location in the input buffer where #frames_needed
+ * can be writen. */
+ T * input_buffer(uint32_t frames_needed)
+ {
+ leftover_samples = delay_input_buffer.length();
+ delay_input_buffer.reserve(leftover_samples +
+ frames_to_samples(frames_needed));
+ return delay_input_buffer.data() + leftover_samples;
+ }
+ /** This method works with `input_buffer`, and allows to inform the processor
+ how much frames have been written in the provided buffer. */
+ void written(size_t frames_written)
+ {
+ delay_input_buffer.set_length(leftover_samples +
+ frames_to_samples(frames_written));
+ }
+ /** Drains the delay line, emptying the buffer.
+ * @parameter output_buffer the buffer in which the frames are written.
+ * @parameter frames_needed the maximum number of frames to write.
+ * @return the actual number of frames written. */
+ size_t output(T * output_buffer, uint32_t frames_needed)
+ {
+ uint32_t in_len = samples_to_frames(delay_input_buffer.length());
+ uint32_t out_len = frames_needed;
+
+ uint32_t to_pop = std::min(in_len, out_len);
+
+ delay_input_buffer.pop(output_buffer, frames_to_samples(to_pop));
+
+ return to_pop;
+ }
+ /** Returns the number of frames one needs to input into the delay line to get
+ * #frames_needed frames back.
+ * @parameter frames_needed the number of frames one want to write into the
+ * delay_line
+ * @returns the number of frames one will get. */
+ uint32_t input_needed_for_output(int32_t frames_needed) const
+ {
+ assert(frames_needed >= 0); // Check overflow
+ return frames_needed;
+ }
+ /** Returns the number of frames produces for `input_frames` frames in input
+ */
+ size_t output_for_input(uint32_t input_frames) { return input_frames; }
+ /** The number of frames this delay line delays the stream by.
+ * @returns The number of frames of delay. */
+ size_t latency() { return length; }
+
+ void drop_audio_if_needed()
+ {
+ size_t available = samples_to_frames(delay_input_buffer.length());
+ uint32_t to_keep = min_buffered_audio_frame(sample_rate);
+ if (available > to_keep) {
+ ALOGV("Dropping %u frames", available - to_keep);
+ delay_input_buffer.pop(nullptr, frames_to_samples(available - to_keep));
+ }
+ }
+
+private:
+ /** The length, in frames, of this delay line */
+ uint32_t length;
+ /** When `input_buffer` is called, this allows tracking the number of samples
+ that where in the buffer. */
+ uint32_t leftover_samples;
+ /** The input buffer, where the delay is applied. */
+ auto_array<T> delay_input_buffer;
+ /** The output buffer. This is only ever used if using the ::output with a
+ * single argument. */
+ auto_array<T> delay_output_buffer;
+ uint32_t sample_rate;
+};
+
+/** This sits behind the C API and is more typed. */
+template <typename T>
+cubeb_resampler *
+cubeb_resampler_create_internal(cubeb_stream * stream,
+ cubeb_stream_params * input_params,
+ cubeb_stream_params * output_params,
+ unsigned int target_rate,
+ cubeb_data_callback callback, void * user_ptr,
+ cubeb_resampler_quality quality,
+ cubeb_resampler_reclock reclock)
+{
+ std::unique_ptr<cubeb_resampler_speex_one_way<T>> input_resampler = nullptr;
+ std::unique_ptr<cubeb_resampler_speex_one_way<T>> output_resampler = nullptr;
+ std::unique_ptr<delay_line<T>> input_delay = nullptr;
+ std::unique_ptr<delay_line<T>> output_delay = nullptr;
+
+ assert((input_params || output_params) &&
+ "need at least one valid parameter pointer.");
+
+ /* All the streams we have have a sample rate that matches the target
+ sample rate, use a no-op resampler, that simply forwards the buffers to the
+ callback. */
+ if (((input_params && input_params->rate == target_rate) &&
+ (output_params && output_params->rate == target_rate)) ||
+ (input_params && !output_params && (input_params->rate == target_rate)) ||
+ (output_params && !input_params &&
+ (output_params->rate == target_rate))) {
+ LOG("Input and output sample-rate match, target rate of %dHz", target_rate);
+ return new passthrough_resampler<T>(
+ stream, callback, user_ptr, input_params ? input_params->channels : 0,
+ target_rate);
+ }
+
+ /* Determine if we need to resampler one or both directions, and create the
+ resamplers. */
+ if (output_params && (output_params->rate != target_rate)) {
+ output_resampler.reset(new cubeb_resampler_speex_one_way<T>(
+ output_params->channels, target_rate, output_params->rate,
+ to_speex_quality(quality)));
+ if (!output_resampler) {
+ return NULL;
+ }
+ }
+
+ if (input_params && (input_params->rate != target_rate)) {
+ input_resampler.reset(new cubeb_resampler_speex_one_way<T>(
+ input_params->channels, input_params->rate, target_rate,
+ to_speex_quality(quality)));
+ if (!input_resampler) {
+ return NULL;
+ }
+ }
+
+ /* If we resample only one direction but we have a duplex stream, insert a
+ * delay line with a length equal to the resampler latency of the
+ * other direction so that the streams are synchronized. */
+ if (input_resampler && !output_resampler && input_params && output_params) {
+ output_delay.reset(new delay_line<T>(input_resampler->latency(),
+ output_params->channels,
+ output_params->rate));
+ if (!output_delay) {
+ return NULL;
+ }
+ } else if (output_resampler && !input_resampler && input_params &&
+ output_params) {
+ input_delay.reset(new delay_line<T>(output_resampler->latency(),
+ input_params->channels,
+ output_params->rate));
+ if (!input_delay) {
+ return NULL;
+ }
+ }
+
+ if (input_resampler && output_resampler) {
+ LOG("Resampling input (%d) and output (%d) to target rate of %dHz",
+ input_params->rate, output_params->rate, target_rate);
+ return new cubeb_resampler_speex<T, cubeb_resampler_speex_one_way<T>,
+ cubeb_resampler_speex_one_way<T>>(
+ input_resampler.release(), output_resampler.release(), stream, callback,
+ user_ptr);
+ } else if (input_resampler) {
+ LOG("Resampling input (%d) to target and output rate of %dHz",
+ input_params->rate, target_rate);
+ return new cubeb_resampler_speex<T, cubeb_resampler_speex_one_way<T>,
+ delay_line<T>>(input_resampler.release(),
+ output_delay.release(),
+ stream, callback, user_ptr);
+ } else {
+ LOG("Resampling output (%dHz) to target and input rate of %dHz",
+ output_params->rate, target_rate);
+ return new cubeb_resampler_speex<T, delay_line<T>,
+ cubeb_resampler_speex_one_way<T>>(
+ input_delay.release(), output_resampler.release(), stream, callback,
+ user_ptr);
+ }
+}
+
+#endif /* CUBEB_RESAMPLER_INTERNAL */
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_ring_array.h b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_ring_array.h
new file mode 100644
index 0000000000..331d0471c5
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_ring_array.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_RING_ARRAY_H
+#define CUBEB_RING_ARRAY_H
+
+#include "cubeb_utils.h"
+#include <CoreAudio/CoreAudioTypes.h>
+
+/** Ring array of pointers is used to hold buffers. In case that
+ asynchronous producer/consumer callbacks do not arrive in a
+ repeated order the ring array stores the buffers and fetch
+ them in the correct order. */
+
+typedef struct {
+ AudioBuffer * buffer_array; /**< Array that hold pointers of the allocated
+ space for the buffers. */
+ unsigned int tail; /**< Index of the last element (first to deliver). */
+ unsigned int count; /**< Number of elements in the array. */
+ unsigned int capacity; /**< Total length of the array. */
+} ring_array;
+
+static int
+single_audiobuffer_init(AudioBuffer * buffer, uint32_t bytesPerFrame,
+ uint32_t channelsPerFrame, uint32_t frames)
+{
+ assert(buffer);
+ assert(bytesPerFrame > 0 && channelsPerFrame && frames > 0);
+
+ size_t size = bytesPerFrame * frames;
+ buffer->mData = operator new(size);
+ if (buffer->mData == NULL) {
+ return CUBEB_ERROR;
+ }
+ PodZero(static_cast<char *>(buffer->mData), size);
+
+ buffer->mNumberChannels = channelsPerFrame;
+ buffer->mDataByteSize = size;
+
+ return CUBEB_OK;
+}
+
+/** Initialize the ring array.
+ @param ra The ring_array pointer of allocated structure.
+ @retval 0 on success. */
+static int
+ring_array_init(ring_array * ra, uint32_t capacity, uint32_t bytesPerFrame,
+ uint32_t channelsPerFrame, uint32_t framesPerBuffer)
+{
+ assert(ra);
+ if (capacity == 0 || bytesPerFrame == 0 || channelsPerFrame == 0 ||
+ framesPerBuffer == 0) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+ ra->capacity = capacity;
+ ra->tail = 0;
+ ra->count = 0;
+
+ ra->buffer_array = new AudioBuffer[ra->capacity];
+ PodZero(ra->buffer_array, ra->capacity);
+ if (ra->buffer_array == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ for (unsigned int i = 0; i < ra->capacity; ++i) {
+ if (single_audiobuffer_init(&ra->buffer_array[i], bytesPerFrame,
+ channelsPerFrame,
+ framesPerBuffer) != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+ }
+
+ return CUBEB_OK;
+}
+
+/** Destroy the ring array.
+ @param ra The ring_array pointer.*/
+static void
+ring_array_destroy(ring_array * ra)
+{
+ assert(ra);
+ if (ra->buffer_array == NULL) {
+ return;
+ }
+ for (unsigned int i = 0; i < ra->capacity; ++i) {
+ if (ra->buffer_array[i].mData) {
+ operator delete(ra->buffer_array[i].mData);
+ }
+ }
+ delete[] ra->buffer_array;
+}
+
+/** Get the allocated buffer to be stored with fresh data.
+ @param ra The ring_array pointer.
+ @retval Pointer of the allocated space to be stored with fresh data or NULL
+ if full. */
+static AudioBuffer *
+ring_array_get_free_buffer(ring_array * ra)
+{
+ assert(ra && ra->buffer_array);
+ assert(ra->buffer_array[0].mData != NULL);
+ if (ra->count == ra->capacity) {
+ return NULL;
+ }
+
+ assert(ra->count == 0 || (ra->tail + ra->count) % ra->capacity != ra->tail);
+ AudioBuffer * ret = &ra->buffer_array[(ra->tail + ra->count) % ra->capacity];
+
+ ++ra->count;
+ assert(ra->count <= ra->capacity);
+
+ return ret;
+}
+
+/** Get the next available buffer with data.
+ @param ra The ring_array pointer.
+ @retval Pointer of the next in order data buffer or NULL if empty. */
+static AudioBuffer *
+ring_array_get_data_buffer(ring_array * ra)
+{
+ assert(ra && ra->buffer_array);
+ assert(ra->buffer_array[0].mData != NULL);
+
+ if (ra->count == 0) {
+ return NULL;
+ }
+ AudioBuffer * ret = &ra->buffer_array[ra->tail];
+
+ ra->tail = (ra->tail + 1) % ra->capacity;
+ assert(ra->tail < ra->capacity);
+
+ assert(ra->count > 0);
+ --ra->count;
+
+ return ret;
+}
+
+#endif // CUBEB_RING_ARRAY_H
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_ringbuffer.h b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_ringbuffer.h
new file mode 100644
index 0000000000..f020351566
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_ringbuffer.h
@@ -0,0 +1,468 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_RING_BUFFER_H
+#define CUBEB_RING_BUFFER_H
+
+#include "cubeb_utils.h"
+#include <algorithm>
+#include <atomic>
+#include <cstdint>
+#include <memory>
+#include <thread>
+
+/**
+ * Single producer single consumer lock-free and wait-free ring buffer.
+ *
+ * This data structure allows producing data from one thread, and consuming it
+ * on another thread, safely and without explicit synchronization. If used on
+ * two threads, this data structure uses atomics for thread safety. It is
+ * possible to disable the use of atomics at compile time and only use this data
+ * structure on one thread.
+ *
+ * The role for the producer and the consumer must be constant, i.e., the
+ * producer should always be on one thread and the consumer should always be on
+ * another thread.
+ *
+ * Some words about the inner workings of this class:
+ * - Capacity is fixed. Only one allocation is performed, in the constructor.
+ * When reading and writing, the return value of the method allows checking if
+ * the ring buffer is empty or full.
+ * - We always keep the read index at least one element ahead of the write
+ * index, so we can distinguish between an empty and a full ring buffer: an
+ * empty ring buffer is when the write index is at the same position as the
+ * read index. A full buffer is when the write index is exactly one position
+ * before the read index.
+ * - We synchronize updates to the read index after having read the data, and
+ * the write index after having written the data. This means that the each
+ * thread can only touch a portion of the buffer that is not touched by the
+ * other thread.
+ * - Callers are expected to provide buffers. When writing to the queue,
+ * elements are copied into the internal storage from the buffer passed in.
+ * When reading from the queue, the user is expected to provide a buffer.
+ * Because this is a ring buffer, data might not be contiguous in memory,
+ * providing an external buffer to copy into is an easy way to have linear
+ * data for further processing.
+ */
+template <typename T> class ring_buffer_base {
+public:
+ /**
+ * Constructor for a ring buffer.
+ *
+ * This performs an allocation, but is the only allocation that will happen
+ * for the life time of a `ring_buffer_base`.
+ *
+ * @param capacity The maximum number of element this ring buffer will hold.
+ */
+ ring_buffer_base(int capacity)
+ /* One more element to distinguish from empty and full buffer. */
+ : capacity_(capacity + 1)
+ {
+ assert(storage_capacity() < std::numeric_limits<int>::max() / 2 &&
+ "buffer too large for the type of index used.");
+ assert(capacity_ > 0);
+
+ data_.reset(new T[storage_capacity()]);
+ /* If this queue is using atomics, initializing those members as the last
+ * action in the constructor acts as a full barrier, and allow capacity() to
+ * be thread-safe. */
+ write_index_ = 0;
+ read_index_ = 0;
+ }
+ /**
+ * Push `count` zero or default constructed elements in the array.
+ *
+ * Only safely called on the producer thread.
+ *
+ * @param count The number of elements to enqueue.
+ * @return The number of element enqueued.
+ */
+ int enqueue_default(int count) { return enqueue(nullptr, count); }
+ /**
+ * @brief Put an element in the queue
+ *
+ * Only safely called on the producer thread.
+ *
+ * @param element The element to put in the queue.
+ *
+ * @return 1 if the element was inserted, 0 otherwise.
+ */
+ int enqueue(T & element) { return enqueue(&element, 1); }
+ /**
+ * Push `count` elements in the ring buffer.
+ *
+ * Only safely called on the producer thread.
+ *
+ * @param elements a pointer to a buffer containing at least `count` elements.
+ * If `elements` is nullptr, zero or default constructed elements are
+ * enqueued.
+ * @param count The number of elements to read from `elements`
+ * @return The number of elements successfully coped from `elements` and
+ * inserted into the ring buffer.
+ */
+ int enqueue(T * elements, int count)
+ {
+#ifndef NDEBUG
+ assert_correct_thread(producer_id);
+#endif
+
+ int wr_idx = write_index_.load(std::memory_order_relaxed);
+ int rd_idx = read_index_.load(std::memory_order_acquire);
+
+ if (full_internal(rd_idx, wr_idx)) {
+ return 0;
+ }
+
+ int to_write = std::min(available_write_internal(rd_idx, wr_idx), count);
+
+ /* First part, from the write index to the end of the array. */
+ int first_part = std::min(storage_capacity() - wr_idx, to_write);
+ /* Second part, from the beginning of the array */
+ int second_part = to_write - first_part;
+
+ if (elements) {
+ Copy(data_.get() + wr_idx, elements, first_part);
+ Copy(data_.get(), elements + first_part, second_part);
+ } else {
+ ConstructDefault(data_.get() + wr_idx, first_part);
+ ConstructDefault(data_.get(), second_part);
+ }
+
+ write_index_.store(increment_index(wr_idx, to_write),
+ std::memory_order_release);
+
+ return to_write;
+ }
+ /**
+ * Retrieve at most `count` elements from the ring buffer, and copy them to
+ * `elements`, if non-null.
+ *
+ * Only safely called on the consumer side.
+ *
+ * @param elements A pointer to a buffer with space for at least `count`
+ * elements. If `elements` is `nullptr`, `count` element will be discarded.
+ * @param count The maximum number of elements to dequeue.
+ * @return The number of elements written to `elements`.
+ */
+ int dequeue(T * elements, int count)
+ {
+#ifndef NDEBUG
+ assert_correct_thread(consumer_id);
+#endif
+
+ int rd_idx = read_index_.load(std::memory_order_relaxed);
+ int wr_idx = write_index_.load(std::memory_order_acquire);
+
+ if (empty_internal(rd_idx, wr_idx)) {
+ return 0;
+ }
+
+ int to_read = std::min(available_read_internal(rd_idx, wr_idx), count);
+
+ int first_part = std::min(storage_capacity() - rd_idx, to_read);
+ int second_part = to_read - first_part;
+
+ if (elements) {
+ Copy(elements, data_.get() + rd_idx, first_part);
+ Copy(elements + first_part, data_.get(), second_part);
+ }
+
+ read_index_.store(increment_index(rd_idx, to_read),
+ std::memory_order_release);
+
+ return to_read;
+ }
+ /**
+ * Get the number of available element for consuming.
+ *
+ * Only safely called on the consumer thread.
+ *
+ * @return The number of available elements for reading.
+ */
+ int available_read() const
+ {
+#ifndef NDEBUG
+ assert_correct_thread(consumer_id);
+#endif
+ return available_read_internal(
+ read_index_.load(std::memory_order_relaxed),
+ write_index_.load(std::memory_order_acquire));
+ }
+ /**
+ * Get the number of available elements for consuming.
+ *
+ * Only safely called on the producer thread.
+ *
+ * @return The number of empty slots in the buffer, available for writing.
+ */
+ int available_write() const
+ {
+#ifndef NDEBUG
+ assert_correct_thread(producer_id);
+#endif
+ return available_write_internal(
+ read_index_.load(std::memory_order_acquire),
+ write_index_.load(std::memory_order_relaxed));
+ }
+ /**
+ * Get the total capacity, for this ring buffer.
+ *
+ * Can be called safely on any thread.
+ *
+ * @return The maximum capacity of this ring buffer.
+ */
+ int capacity() const { return storage_capacity() - 1; }
+ /**
+ * Reset the consumer and producer thread identifier, in case the thread are
+ * being changed. This has to be externally synchronized. This is no-op when
+ * asserts are disabled.
+ */
+ void reset_thread_ids()
+ {
+#ifndef NDEBUG
+ consumer_id = producer_id = std::thread::id();
+#endif
+ }
+
+private:
+ /** Return true if the ring buffer is empty.
+ *
+ * @param read_index the read index to consider
+ * @param write_index the write index to consider
+ * @return true if the ring buffer is empty, false otherwise.
+ **/
+ bool empty_internal(int read_index, int write_index) const
+ {
+ return write_index == read_index;
+ }
+ /** Return true if the ring buffer is full.
+ *
+ * This happens if the write index is exactly one element behind the read
+ * index.
+ *
+ * @param read_index the read index to consider
+ * @param write_index the write index to consider
+ * @return true if the ring buffer is full, false otherwise.
+ **/
+ bool full_internal(int read_index, int write_index) const
+ {
+ return (write_index + 1) % storage_capacity() == read_index;
+ }
+ /**
+ * Return the size of the storage. It is one more than the number of elements
+ * that can be stored in the buffer.
+ *
+ * @return the number of elements that can be stored in the buffer.
+ */
+ int storage_capacity() const { return capacity_; }
+ /**
+ * Returns the number of elements available for reading.
+ *
+ * @return the number of available elements for reading.
+ */
+ int available_read_internal(int read_index, int write_index) const
+ {
+ if (write_index >= read_index) {
+ return write_index - read_index;
+ } else {
+ return write_index + storage_capacity() - read_index;
+ }
+ }
+ /**
+ * Returns the number of empty elements, available for writing.
+ *
+ * @return the number of elements that can be written into the array.
+ */
+ int available_write_internal(int read_index, int write_index) const
+ {
+ /* We substract one element here to always keep at least one sample
+ * free in the buffer, to distinguish between full and empty array. */
+ int rv = read_index - write_index - 1;
+ if (write_index >= read_index) {
+ rv += storage_capacity();
+ }
+ return rv;
+ }
+ /**
+ * Increments an index, wrapping it around the storage.
+ *
+ * @param index a reference to the index to increment.
+ * @param increment the number by which `index` is incremented.
+ * @return the new index.
+ */
+ int increment_index(int index, int increment) const
+ {
+ assert(increment >= 0);
+ return (index + increment) % storage_capacity();
+ }
+ /**
+ * @brief This allows checking that enqueue (resp. dequeue) are always called
+ * by the right thread.
+ *
+ * @param id the id of the thread that has called the calling method first.
+ */
+#ifndef NDEBUG
+ static void assert_correct_thread(std::thread::id & id)
+ {
+ if (id == std::thread::id()) {
+ id = std::this_thread::get_id();
+ return;
+ }
+ assert(id == std::this_thread::get_id());
+ }
+#endif
+ /** Index at which the oldest element is at, in samples. */
+ std::atomic<int> read_index_;
+ /** Index at which to write new elements. `write_index` is always at
+ * least one element ahead of `read_index_`. */
+ std::atomic<int> write_index_;
+ /** Maximum number of elements that can be stored in the ring buffer. */
+ const int capacity_;
+ /** Data storage */
+ std::unique_ptr<T[]> data_;
+#ifndef NDEBUG
+ /** The id of the only thread that is allowed to read from the queue. */
+ mutable std::thread::id consumer_id;
+ /** The id of the only thread that is allowed to write from the queue. */
+ mutable std::thread::id producer_id;
+#endif
+};
+
+/**
+ * Adapter for `ring_buffer_base` that exposes an interface in frames.
+ */
+template <typename T> class audio_ring_buffer_base {
+public:
+ /**
+ * @brief Constructor.
+ *
+ * @param channel_count Number of channels.
+ * @param capacity_in_frames The capacity in frames.
+ */
+ audio_ring_buffer_base(int channel_count, int capacity_in_frames)
+ : channel_count(channel_count),
+ ring_buffer(frames_to_samples(capacity_in_frames))
+ {
+ assert(channel_count > 0);
+ }
+ /**
+ * @brief Enqueue silence.
+ *
+ * Only safely called on the producer thread.
+ *
+ * @param frame_count The number of frames of silence to enqueue.
+ * @return The number of frames of silence actually written to the queue.
+ */
+ int enqueue_default(int frame_count)
+ {
+ return samples_to_frames(
+ ring_buffer.enqueue(nullptr, frames_to_samples(frame_count)));
+ }
+ /**
+ * @brief Enqueue `frames_count` frames of audio.
+ *
+ * Only safely called from the producer thread.
+ *
+ * @param [in] frames If non-null, the frames to enqueue.
+ * Otherwise, silent frames are enqueued.
+ * @param frame_count The number of frames to enqueue.
+ *
+ * @return The number of frames enqueued
+ */
+
+ int enqueue(T * frames, int frame_count)
+ {
+ return samples_to_frames(
+ ring_buffer.enqueue(frames, frames_to_samples(frame_count)));
+ }
+
+ /**
+ * @brief Removes `frame_count` frames from the buffer, and
+ * write them to `frames` if it is non-null.
+ *
+ * Only safely called on the consumer thread.
+ *
+ * @param frames If non-null, the frames are copied to `frames`.
+ * Otherwise, they are dropped.
+ * @param frame_count The number of frames to remove.
+ *
+ * @return The number of frames actually dequeud.
+ */
+ int dequeue(T * frames, int frame_count)
+ {
+ return samples_to_frames(
+ ring_buffer.dequeue(frames, frames_to_samples(frame_count)));
+ }
+ /**
+ * Get the number of available frames of audio for consuming.
+ *
+ * Only safely called on the consumer thread.
+ *
+ * @return The number of available frames of audio for reading.
+ */
+ int available_read() const
+ {
+ return samples_to_frames(ring_buffer.available_read());
+ }
+ /**
+ * Get the number of available frames of audio for consuming.
+ *
+ * Only safely called on the producer thread.
+ *
+ * @return The number of empty slots in the buffer, available for writing.
+ */
+ int available_write() const
+ {
+ return samples_to_frames(ring_buffer.available_write());
+ }
+ /**
+ * Get the total capacity, for this ring buffer.
+ *
+ * Can be called safely on any thread.
+ *
+ * @return The maximum capacity of this ring buffer.
+ */
+ int capacity() const { return samples_to_frames(ring_buffer.capacity()); }
+
+private:
+ /**
+ * @brief Frames to samples conversion.
+ *
+ * @param frames The number of frames.
+ *
+ * @return A number of samples.
+ */
+ int frames_to_samples(int frames) const { return frames * channel_count; }
+ /**
+ * @brief Samples to frames conversion.
+ *
+ * @param samples The number of samples.
+ *
+ * @return A number of frames.
+ */
+ int samples_to_frames(int samples) const { return samples / channel_count; }
+ /** Number of channels of audio that will stream through this ring buffer. */
+ int channel_count;
+ /** The underlying ring buffer that is used to store the data. */
+ ring_buffer_base<T> ring_buffer;
+};
+
+/**
+ * Lock-free instantiation of the `ring_buffer_base` type. This is safe to use
+ * from two threads, one producer, one consumer (that never change role),
+ * without explicit synchronization.
+ */
+template <typename T> using lock_free_queue = ring_buffer_base<T>;
+/**
+ * Lock-free instantiation of the `audio_ring_buffer` type. This is safe to use
+ * from two threads, one producer, one consumer (that never change role),
+ * without explicit synchronization.
+ */
+template <typename T>
+using lock_free_audio_ring_buffer = audio_ring_buffer_base<T>;
+
+#endif // CUBEB_RING_BUFFER_H
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_sndio.c b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_sndio.c
new file mode 100644
index 0000000000..cd295bff56
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_sndio.c
@@ -0,0 +1,687 @@
+/*
+ * Copyright (c) 2011 Alexandre Ratchov <alex@caoua.org>
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_tracing.h"
+#include <assert.h>
+#include <dlfcn.h>
+#include <inttypes.h>
+#include <math.h>
+#include <poll.h>
+#include <pthread.h>
+#include <sndio.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if defined(CUBEB_SNDIO_DEBUG)
+#define DPR(...) fprintf(stderr, __VA_ARGS__);
+#else
+#define DPR(...) \
+ do { \
+ } while (0)
+#endif
+
+#ifdef DISABLE_LIBSNDIO_DLOPEN
+#define WRAP(x) x
+#else
+#define WRAP(x) (*cubeb_##x)
+#define LIBSNDIO_API_VISIT(X) \
+ X(sio_close) \
+ X(sio_eof) \
+ X(sio_getpar) \
+ X(sio_initpar) \
+ X(sio_nfds) \
+ X(sio_onmove) \
+ X(sio_open) \
+ X(sio_pollfd) \
+ X(sio_read) \
+ X(sio_revents) \
+ X(sio_setpar) \
+ X(sio_start) \
+ X(sio_stop) \
+ X(sio_write)
+
+#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
+LIBSNDIO_API_VISIT(MAKE_TYPEDEF);
+#undef MAKE_TYPEDEF
+#endif
+
+static struct cubeb_ops const sndio_ops;
+
+struct cubeb {
+ struct cubeb_ops const * ops;
+ void * libsndio;
+};
+
+struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
+ cubeb * context;
+ void * arg; /* user arg to {data,state}_cb */
+ /**/
+ pthread_t th; /* to run real-time audio i/o */
+ pthread_mutex_t mtx; /* protects hdl and pos */
+ struct sio_hdl * hdl; /* link us to sndio */
+ int mode; /* bitmap of SIO_{PLAY,REC} */
+ int active; /* cubec_start() called */
+ int conv; /* need float->s24 conversion */
+ unsigned char * rbuf; /* rec data consumed from here */
+ unsigned char * pbuf; /* play data is prepared here */
+ unsigned int nfr; /* number of frames in ibuf and obuf */
+ unsigned int rbpf; /* rec bytes per frame */
+ unsigned int pbpf; /* play bytes per frame */
+ unsigned int rchan; /* number of rec channels */
+ unsigned int pchan; /* number of play channels */
+ unsigned int nblks; /* number of blocks in the buffer */
+ uint64_t hwpos; /* frame number Joe hears right now */
+ uint64_t swpos; /* number of frames produced/consumed */
+ cubeb_data_callback data_cb; /* cb to preapare data */
+ cubeb_state_callback state_cb; /* cb to notify about state changes */
+ float volume; /* current volume */
+};
+
+static void
+s16_setvol(void * ptr, long nsamp, float volume)
+{
+ int16_t * dst = ptr;
+ int32_t mult = volume * 32768;
+ int32_t s;
+
+ while (nsamp-- > 0) {
+ s = *dst;
+ s = (s * mult) >> 15;
+ *(dst++) = s;
+ }
+}
+
+static void
+float_to_s24(void * ptr, long nsamp, float volume)
+{
+ int32_t * dst = ptr;
+ float * src = ptr;
+ float mult = volume * 8388608;
+ int s;
+
+ while (nsamp-- > 0) {
+ s = lrintf(*(src++) * mult);
+ if (s < -8388608)
+ s = -8388608;
+ else if (s > 8388607)
+ s = 8388607;
+ *(dst++) = s;
+ }
+}
+
+static void
+s24_to_float(void * ptr, long nsamp)
+{
+ int32_t * src = ptr;
+ float * dst = ptr;
+
+ src += nsamp;
+ dst += nsamp;
+ while (nsamp-- > 0)
+ *(--dst) = (1. / 8388608) * *(--src);
+}
+
+static const char *
+sndio_get_device()
+{
+#ifdef __linux__
+ /*
+ * On other platforms default to sndio devices,
+ * so cubebs other backends can be used instead.
+ */
+ const char * dev = getenv("AUDIODEVICE");
+ if (dev == NULL || *dev == '\0')
+ return "snd/0";
+ return dev;
+#else
+ return SIO_DEVANY;
+#endif
+}
+
+static void
+sndio_onmove(void * arg, int delta)
+{
+ cubeb_stream * s = (cubeb_stream *)arg;
+
+ s->hwpos += delta;
+}
+
+static void *
+sndio_mainloop(void * arg)
+{
+ struct pollfd * pfds;
+ cubeb_stream * s = arg;
+ int n, eof = 0, prime, nfds, events, revents, state = CUBEB_STATE_STARTED;
+ size_t pstart = 0, pend = 0, rstart = 0, rend = 0;
+ long nfr;
+
+ CUBEB_REGISTER_THREAD("cubeb rendering thread");
+
+ nfds = WRAP(sio_nfds)(s->hdl);
+ pfds = calloc(nfds, sizeof(struct pollfd));
+ if (pfds == NULL) {
+ CUBEB_UNREGISTER_THREAD();
+ return NULL;
+ }
+
+ DPR("sndio_mainloop()\n");
+ s->state_cb(s, s->arg, CUBEB_STATE_STARTED);
+ pthread_mutex_lock(&s->mtx);
+ if (!WRAP(sio_start)(s->hdl)) {
+ pthread_mutex_unlock(&s->mtx);
+ free(pfds);
+ CUBEB_UNREGISTER_THREAD();
+ return NULL;
+ }
+ DPR("sndio_mainloop(), started\n");
+
+ if (s->mode & SIO_PLAY) {
+ pstart = pend = s->nfr * s->pbpf;
+ prime = s->nblks;
+ if (s->mode & SIO_REC) {
+ memset(s->rbuf, 0, s->nfr * s->rbpf);
+ rstart = rend = s->nfr * s->rbpf;
+ }
+ } else {
+ prime = 0;
+ rstart = 0;
+ rend = s->nfr * s->rbpf;
+ }
+
+ for (;;) {
+ if (!s->active) {
+ DPR("sndio_mainloop() stopped\n");
+ state = CUBEB_STATE_STOPPED;
+ break;
+ }
+
+ /* do we have a complete block? */
+ if ((!(s->mode & SIO_PLAY) || pstart == pend) &&
+ (!(s->mode & SIO_REC) || rstart == rend)) {
+
+ if (eof) {
+ DPR("sndio_mainloop() drained\n");
+ state = CUBEB_STATE_DRAINED;
+ break;
+ }
+
+ if ((s->mode & SIO_REC) && s->conv)
+ s24_to_float(s->rbuf, s->nfr * s->rchan);
+
+ /* invoke call-back, it returns less that s->nfr if done */
+ pthread_mutex_unlock(&s->mtx);
+ nfr = s->data_cb(s, s->arg, s->rbuf, s->pbuf, s->nfr);
+ pthread_mutex_lock(&s->mtx);
+ if (nfr < 0) {
+ DPR("sndio_mainloop() cb err\n");
+ state = CUBEB_STATE_ERROR;
+ break;
+ }
+ s->swpos += nfr;
+
+ /* was this last call-back invocation (aka end-of-stream) ? */
+ if (nfr < s->nfr) {
+
+ if (!(s->mode & SIO_PLAY) || nfr == 0) {
+ state = CUBEB_STATE_DRAINED;
+ break;
+ }
+
+ /* need to write (aka drain) the partial play block we got */
+ pend = nfr * s->pbpf;
+ eof = 1;
+ }
+
+ if (prime > 0)
+ prime--;
+
+ if (s->mode & SIO_PLAY) {
+ if (s->conv)
+ float_to_s24(s->pbuf, nfr * s->pchan, s->volume);
+ else
+ s16_setvol(s->pbuf, nfr * s->pchan, s->volume);
+ }
+
+ if (s->mode & SIO_REC)
+ rstart = 0;
+ if (s->mode & SIO_PLAY)
+ pstart = 0;
+ }
+
+ events = 0;
+ if ((s->mode & SIO_REC) && rstart < rend && prime == 0)
+ events |= POLLIN;
+ if ((s->mode & SIO_PLAY) && pstart < pend)
+ events |= POLLOUT;
+ nfds = WRAP(sio_pollfd)(s->hdl, pfds, events);
+
+ if (nfds > 0) {
+ pthread_mutex_unlock(&s->mtx);
+ n = poll(pfds, nfds, -1);
+ pthread_mutex_lock(&s->mtx);
+ if (n < 0)
+ continue;
+ }
+
+ revents = WRAP(sio_revents)(s->hdl, pfds);
+
+ if (revents & POLLHUP) {
+ state = CUBEB_STATE_ERROR;
+ break;
+ }
+
+ if (revents & POLLOUT) {
+ n = WRAP(sio_write)(s->hdl, s->pbuf + pstart, pend - pstart);
+ if (n == 0 && WRAP(sio_eof)(s->hdl)) {
+ DPR("sndio_mainloop() werr\n");
+ state = CUBEB_STATE_ERROR;
+ break;
+ }
+ pstart += n;
+ }
+
+ if (revents & POLLIN) {
+ n = WRAP(sio_read)(s->hdl, s->rbuf + rstart, rend - rstart);
+ if (n == 0 && WRAP(sio_eof)(s->hdl)) {
+ DPR("sndio_mainloop() rerr\n");
+ state = CUBEB_STATE_ERROR;
+ break;
+ }
+ rstart += n;
+ }
+
+ /* skip rec block, if not recording (yet) */
+ if (prime > 0 && (s->mode & SIO_REC))
+ rstart = rend;
+ }
+ WRAP(sio_stop)(s->hdl);
+ s->hwpos = s->swpos;
+ pthread_mutex_unlock(&s->mtx);
+ s->state_cb(s, s->arg, state);
+ free(pfds);
+ CUBEB_UNREGISTER_THREAD();
+ return NULL;
+}
+
+/*static*/ int
+sndio_init(cubeb ** context, char const * context_name)
+{
+ void * libsndio = NULL;
+ struct sio_hdl * hdl;
+
+ assert(context);
+
+#ifndef DISABLE_LIBSNDIO_DLOPEN
+ libsndio = dlopen("libsndio.so.7.0", RTLD_LAZY);
+ if (!libsndio) {
+ libsndio = dlopen("libsndio.so", RTLD_LAZY);
+ if (!libsndio) {
+ DPR("sndio_init(%s) failed dlopen(libsndio.so)\n", context_name);
+ return CUBEB_ERROR;
+ }
+ }
+
+#define LOAD(x) \
+ { \
+ cubeb_##x = dlsym(libsndio, #x); \
+ if (!cubeb_##x) { \
+ DPR("sndio_init(%s) failed dlsym(%s)\n", context_name, #x); \
+ dlclose(libsndio); \
+ return CUBEB_ERROR; \
+ } \
+ }
+
+ LIBSNDIO_API_VISIT(LOAD);
+#undef LOAD
+#endif
+
+ /* test if sndio works */
+ hdl = WRAP(sio_open)(sndio_get_device(), SIO_PLAY, 1);
+ if (hdl == NULL) {
+ return CUBEB_ERROR;
+ }
+ WRAP(sio_close)(hdl);
+
+ DPR("sndio_init(%s)\n", context_name);
+ *context = malloc(sizeof(**context));
+ if (*context == NULL)
+ return CUBEB_ERROR;
+ (*context)->libsndio = libsndio;
+ (*context)->ops = &sndio_ops;
+ (void)context_name;
+ return CUBEB_OK;
+}
+
+static char const *
+sndio_get_backend_id(cubeb * context)
+{
+ return "sndio";
+}
+
+static void
+sndio_destroy(cubeb * context)
+{
+ DPR("sndio_destroy()\n");
+#ifndef DISABLE_LIBSNDIO_DLOPEN
+ if (context->libsndio)
+ dlclose(context->libsndio);
+#endif
+ free(context);
+}
+
+static int
+sndio_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ cubeb_stream * s;
+ struct sio_par wpar, rpar;
+ cubeb_sample_format format;
+ int rate;
+ size_t bps;
+
+ DPR("sndio_stream_init(%s)\n", stream_name);
+
+ s = malloc(sizeof(cubeb_stream));
+ if (s == NULL)
+ return CUBEB_ERROR;
+ memset(s, 0, sizeof(cubeb_stream));
+ s->mode = 0;
+ if (input_stream_params) {
+ if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ DPR("sndio_stream_init(), loopback not supported\n");
+ goto err;
+ }
+ s->mode |= SIO_REC;
+ format = input_stream_params->format;
+ rate = input_stream_params->rate;
+ }
+ if (output_stream_params) {
+ if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ DPR("sndio_stream_init(), loopback not supported\n");
+ goto err;
+ }
+ s->mode |= SIO_PLAY;
+ format = output_stream_params->format;
+ rate = output_stream_params->rate;
+ }
+ if (s->mode == 0) {
+ DPR("sndio_stream_init(), neither playing nor recording\n");
+ goto err;
+ }
+ s->context = context;
+ s->hdl = WRAP(sio_open)(sndio_get_device(), s->mode, 1);
+ if (s->hdl == NULL) {
+ DPR("sndio_stream_init(), sio_open() failed\n");
+ goto err;
+ }
+ WRAP(sio_initpar)(&wpar);
+ wpar.sig = 1;
+ switch (format) {
+ case CUBEB_SAMPLE_S16LE:
+ wpar.le = 1;
+ wpar.bits = 16;
+ break;
+ case CUBEB_SAMPLE_S16BE:
+ wpar.le = 0;
+ wpar.bits = 16;
+ break;
+ case CUBEB_SAMPLE_FLOAT32NE:
+ wpar.le = SIO_LE_NATIVE;
+ wpar.bits = 24;
+ wpar.msb = 0;
+ break;
+ default:
+ DPR("sndio_stream_init() unsupported format\n");
+ goto err;
+ }
+ wpar.bps = SIO_BPS(wpar.bits);
+ wpar.rate = rate;
+ if (s->mode & SIO_REC)
+ wpar.rchan = input_stream_params->channels;
+ if (s->mode & SIO_PLAY)
+ wpar.pchan = output_stream_params->channels;
+ wpar.appbufsz = latency_frames;
+ if (!WRAP(sio_setpar)(s->hdl, &wpar) || !WRAP(sio_getpar)(s->hdl, &rpar)) {
+ DPR("sndio_stream_init(), sio_setpar() failed\n");
+ goto err;
+ }
+ if (rpar.bits != wpar.bits || rpar.le != wpar.le || rpar.sig != wpar.sig ||
+ rpar.bps != wpar.bps ||
+ (wpar.bits < 8 * wpar.bps && rpar.msb != wpar.msb) ||
+ rpar.rate != wpar.rate ||
+ ((s->mode & SIO_REC) && rpar.rchan != wpar.rchan) ||
+ ((s->mode & SIO_PLAY) && rpar.pchan != wpar.pchan)) {
+ DPR("sndio_stream_init() unsupported params\n");
+ goto err;
+ }
+ WRAP(sio_onmove)(s->hdl, sndio_onmove, s);
+ s->active = 0;
+ s->nfr = rpar.round;
+ s->rbpf = rpar.bps * rpar.rchan;
+ s->pbpf = rpar.bps * rpar.pchan;
+ s->rchan = rpar.rchan;
+ s->pchan = rpar.pchan;
+ s->nblks = rpar.bufsz / rpar.round;
+ s->data_cb = data_callback;
+ s->state_cb = state_callback;
+ s->arg = user_ptr;
+ s->mtx = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
+ s->hwpos = s->swpos = 0;
+ if (format == CUBEB_SAMPLE_FLOAT32LE) {
+ s->conv = 1;
+ bps = sizeof(float);
+ } else {
+ s->conv = 0;
+ bps = rpar.bps;
+ }
+ if (s->mode & SIO_PLAY) {
+ s->pbuf = malloc(bps * rpar.pchan * rpar.round);
+ if (s->pbuf == NULL)
+ goto err;
+ }
+ if (s->mode & SIO_REC) {
+ s->rbuf = malloc(bps * rpar.rchan * rpar.round);
+ if (s->rbuf == NULL)
+ goto err;
+ }
+ s->volume = 1.;
+ *stream = s;
+ DPR("sndio_stream_init() end, ok\n");
+ (void)context;
+ (void)stream_name;
+ return CUBEB_OK;
+err:
+ if (s->hdl)
+ WRAP(sio_close)(s->hdl);
+ if (s->pbuf)
+ free(s->pbuf);
+ if (s->rbuf)
+ free(s->pbuf);
+ free(s);
+ return CUBEB_ERROR;
+}
+
+static int
+sndio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
+{
+ assert(ctx && max_channels);
+
+ *max_channels = 8;
+
+ return CUBEB_OK;
+}
+
+static int
+sndio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
+{
+ /*
+ * We've no device-independent prefered rate; any rate will work if
+ * sndiod is running. If it isn't, 48kHz is what is most likely to
+ * work as most (but not all) devices support it.
+ */
+ *rate = 48000;
+ return CUBEB_OK;
+}
+
+static int
+sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params,
+ uint32_t * latency_frames)
+{
+ /*
+ * We've no device-independent minimum latency.
+ */
+ *latency_frames = 2048;
+
+ return CUBEB_OK;
+}
+
+static void
+sndio_stream_destroy(cubeb_stream * s)
+{
+ DPR("sndio_stream_destroy()\n");
+ WRAP(sio_close)(s->hdl);
+ if (s->mode & SIO_PLAY)
+ free(s->pbuf);
+ if (s->mode & SIO_REC)
+ free(s->rbuf);
+ free(s);
+}
+
+static int
+sndio_stream_start(cubeb_stream * s)
+{
+ int err;
+
+ DPR("sndio_stream_start()\n");
+ s->active = 1;
+ err = pthread_create(&s->th, NULL, sndio_mainloop, s);
+ if (err) {
+ s->active = 0;
+ return CUBEB_ERROR;
+ }
+ return CUBEB_OK;
+}
+
+static int
+sndio_stream_stop(cubeb_stream * s)
+{
+ void * dummy;
+
+ DPR("sndio_stream_stop()\n");
+ if (s->active) {
+ s->active = 0;
+ pthread_join(s->th, &dummy);
+ }
+ return CUBEB_OK;
+}
+
+static int
+sndio_stream_get_position(cubeb_stream * s, uint64_t * p)
+{
+ pthread_mutex_lock(&s->mtx);
+ DPR("sndio_stream_get_position() %" PRId64 "\n", s->hwpos);
+ *p = s->hwpos;
+ pthread_mutex_unlock(&s->mtx);
+ return CUBEB_OK;
+}
+
+static int
+sndio_stream_set_volume(cubeb_stream * s, float volume)
+{
+ DPR("sndio_stream_set_volume(%f)\n", volume);
+ pthread_mutex_lock(&s->mtx);
+ if (volume < 0.)
+ volume = 0.;
+ else if (volume > 1.0)
+ volume = 1.;
+ s->volume = volume;
+ pthread_mutex_unlock(&s->mtx);
+ return CUBEB_OK;
+}
+
+int
+sndio_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
+{
+ // http://www.openbsd.org/cgi-bin/man.cgi?query=sio_open
+ // in the "Measuring the latency and buffers usage" paragraph.
+ *latency = stm->swpos - stm->hwpos;
+ return CUBEB_OK;
+}
+
+static int
+sndio_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection)
+{
+ static char dev[] = SIO_DEVANY;
+ cubeb_device_info * device;
+
+ device = malloc(sizeof(cubeb_device_info));
+ if (device == NULL)
+ return CUBEB_ERROR;
+
+ device->devid = dev; /* passed to stream_init() */
+ device->device_id = dev; /* printable in UI */
+ device->friendly_name = dev; /* same, but friendly */
+ device->group_id = dev; /* actual device if full-duplex */
+ device->vendor_name = NULL; /* may be NULL */
+ device->type = type; /* Input/Output */
+ device->state = CUBEB_DEVICE_STATE_ENABLED;
+ device->preferred = CUBEB_DEVICE_PREF_ALL;
+ device->format = CUBEB_DEVICE_FMT_S16NE;
+ device->default_format = CUBEB_DEVICE_FMT_S16NE;
+ device->max_channels = (type == CUBEB_DEVICE_TYPE_INPUT) ? 2 : 8;
+ device->default_rate = 48000;
+ device->min_rate = 4000;
+ device->max_rate = 192000;
+ device->latency_lo = 480;
+ device->latency_hi = 9600;
+ collection->device = device;
+ collection->count = 1;
+ return CUBEB_OK;
+}
+
+static int
+sndio_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection)
+{
+ free(collection->device);
+ return CUBEB_OK;
+}
+
+static struct cubeb_ops const sndio_ops = {
+ .init = sndio_init,
+ .get_backend_id = sndio_get_backend_id,
+ .get_max_channel_count = sndio_get_max_channel_count,
+ .get_min_latency = sndio_get_min_latency,
+ .get_preferred_sample_rate = sndio_get_preferred_sample_rate,
+ .get_supported_input_processing_params = NULL,
+ .enumerate_devices = sndio_enumerate_devices,
+ .device_collection_destroy = sndio_device_collection_destroy,
+ .destroy = sndio_destroy,
+ .stream_init = sndio_stream_init,
+ .stream_destroy = sndio_stream_destroy,
+ .stream_start = sndio_stream_start,
+ .stream_stop = sndio_stream_stop,
+ .stream_get_position = sndio_stream_get_position,
+ .stream_get_latency = sndio_stream_get_latency,
+ .stream_set_volume = sndio_stream_set_volume,
+ .stream_set_name = NULL,
+ .stream_get_current_device = NULL,
+ .stream_set_input_mute = NULL,
+ .stream_set_input_processing_params = NULL,
+ .stream_device_destroy = NULL,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL};
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_strings.c b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_strings.c
new file mode 100644
index 0000000000..5fe5b791e2
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_strings.c
@@ -0,0 +1,154 @@
+/*
+ * Copyright © 2011 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#include "cubeb_strings.h"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define CUBEB_STRINGS_INLINE_COUNT 4
+
+struct cubeb_strings {
+ uint32_t size;
+ uint32_t count;
+ char ** data;
+ char * small_store[CUBEB_STRINGS_INLINE_COUNT];
+};
+
+int
+cubeb_strings_init(cubeb_strings ** strings)
+{
+ cubeb_strings * strs = NULL;
+
+ if (!strings) {
+ return CUBEB_ERROR;
+ }
+
+ strs = calloc(1, sizeof(cubeb_strings));
+ assert(strs);
+
+ if (!strs) {
+ return CUBEB_ERROR;
+ }
+
+ strs->size = sizeof(strs->small_store) / sizeof(strs->small_store[0]);
+ strs->count = 0;
+ strs->data = strs->small_store;
+
+ *strings = strs;
+
+ return CUBEB_OK;
+}
+
+void
+cubeb_strings_destroy(cubeb_strings * strings)
+{
+ char ** sp = NULL;
+ char ** se = NULL;
+
+ if (!strings) {
+ return;
+ }
+
+ sp = strings->data;
+ se = sp + strings->count;
+
+ for (; sp != se; sp++) {
+ if (*sp) {
+ free(*sp);
+ }
+ }
+
+ if (strings->data != strings->small_store) {
+ free(strings->data);
+ }
+
+ free(strings);
+}
+
+/** Look for string in string storage.
+ @param strings Opaque pointer to interned string storage.
+ @param s String to look up.
+ @retval Read-only string or NULL if not found. */
+static char const *
+cubeb_strings_lookup(cubeb_strings * strings, char const * s)
+{
+ char ** sp = NULL;
+ char ** se = NULL;
+
+ if (!strings || !s) {
+ return NULL;
+ }
+
+ sp = strings->data;
+ se = sp + strings->count;
+
+ for (; sp != se; sp++) {
+ if (*sp && strcmp(*sp, s) == 0) {
+ return *sp;
+ }
+ }
+
+ return NULL;
+}
+
+static char const *
+cubeb_strings_push(cubeb_strings * strings, char const * s)
+{
+ char * is = NULL;
+
+ if (strings->count == strings->size) {
+ char ** new_data;
+ uint32_t value_size = sizeof(char const *);
+ uint32_t new_size = strings->size * 2;
+ if (!new_size || value_size > (uint32_t)-1 / new_size) {
+ // overflow
+ return NULL;
+ }
+
+ if (strings->small_store == strings->data) {
+ // First time heap allocation.
+ new_data = malloc(new_size * value_size);
+ if (new_data) {
+ memcpy(new_data, strings->small_store, sizeof(strings->small_store));
+ }
+ } else {
+ new_data = realloc(strings->data, new_size * value_size);
+ }
+
+ if (!new_data) {
+ // out of memory
+ return NULL;
+ }
+
+ strings->size = new_size;
+ strings->data = new_data;
+ }
+
+ is = strdup(s);
+ strings->data[strings->count++] = is;
+
+ return is;
+}
+
+char const *
+cubeb_strings_intern(cubeb_strings * strings, char const * s)
+{
+ char const * is = NULL;
+
+ if (!strings || !s) {
+ return NULL;
+ }
+
+ is = cubeb_strings_lookup(strings, s);
+ if (is) {
+ return is;
+ }
+
+ return cubeb_strings_push(strings, s);
+}
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_strings.h b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_strings.h
new file mode 100644
index 0000000000..cfffbbc68a
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_strings.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright © 2011 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_STRINGS_H
+#define CUBEB_STRINGS_H
+
+#include "cubeb/cubeb.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/** Opaque handle referencing interned string storage. */
+typedef struct cubeb_strings cubeb_strings;
+
+/** Initialize an interned string structure.
+ @param strings An out param where an opaque pointer to the
+ interned string storage will be returned.
+ @retval CUBEB_OK in case of success.
+ @retval CUBEB_ERROR in case of error. */
+CUBEB_EXPORT int
+cubeb_strings_init(cubeb_strings ** strings);
+
+/** Destroy an interned string structure freeing all associated memory.
+ @param strings An opaque pointer to the interned string storage to
+ destroy. */
+CUBEB_EXPORT void
+cubeb_strings_destroy(cubeb_strings * strings);
+
+/** Add string to internal storage.
+ @param strings Opaque pointer to interned string storage.
+ @param s String to add to storage.
+ @retval CUBEB_OK
+ @retval CUBEB_ERROR
+ */
+CUBEB_EXPORT char const *
+cubeb_strings_intern(cubeb_strings * strings, char const * s);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif // !CUBEB_STRINGS_H
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_sun.c b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_sun.c
new file mode 100644
index 0000000000..cae9eefe20
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_sun.c
@@ -0,0 +1,740 @@
+/*
+ * Copyright © 2019-2020 Nia Alarie <nia@NetBSD.org>
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_tracing.h"
+#include <fcntl.h>
+#include <limits.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/audioio.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+/* Default to 4 + 1 for the default device. */
+#ifndef SUN_DEVICE_COUNT
+#define SUN_DEVICE_COUNT (5)
+#endif
+
+/* Supported well by most hardware. */
+#ifndef SUN_PREFER_RATE
+#define SUN_PREFER_RATE (48000)
+#endif
+
+/* Standard acceptable minimum. */
+#ifndef SUN_LATENCY_MS
+#define SUN_LATENCY_MS (40)
+#endif
+
+#ifndef SUN_DEFAULT_DEVICE
+#define SUN_DEFAULT_DEVICE "/dev/audio"
+#endif
+
+#ifndef SUN_BUFFER_FRAMES
+#define SUN_BUFFER_FRAMES (32)
+#endif
+
+/*
+ * Supported on NetBSD regardless of hardware.
+ */
+
+#ifndef SUN_MAX_CHANNELS
+#ifdef __NetBSD__
+#define SUN_MAX_CHANNELS (12)
+#else
+#define SUN_MAX_CHANNELS (2)
+#endif
+#endif
+
+#ifndef SUN_MIN_RATE
+#define SUN_MIN_RATE (1000)
+#endif
+
+#ifndef SUN_MAX_RATE
+#define SUN_MAX_RATE (192000)
+#endif
+
+static struct cubeb_ops const sun_ops;
+
+struct cubeb {
+ struct cubeb_ops const * ops;
+};
+
+struct sun_stream {
+ char name[32];
+ int fd;
+ void * buf;
+ struct audio_info info;
+ unsigned frame_size; /* precision in bytes * channels */
+ bool floating;
+};
+
+struct cubeb_stream {
+ struct cubeb * context;
+ void * user_ptr;
+ pthread_t thread;
+ pthread_mutex_t mutex; /* protects running, volume, frames_written */
+ bool running;
+ float volume;
+ struct sun_stream play;
+ struct sun_stream record;
+ cubeb_data_callback data_cb;
+ cubeb_state_callback state_cb;
+ uint64_t frames_written;
+ uint64_t blocks_written;
+};
+
+int
+sun_init(cubeb ** context, char const * context_name)
+{
+ cubeb * c;
+
+ (void)context_name;
+ if ((c = calloc(1, sizeof(cubeb))) == NULL) {
+ return CUBEB_ERROR;
+ }
+ c->ops = &sun_ops;
+ *context = c;
+ return CUBEB_OK;
+}
+
+static void
+sun_destroy(cubeb * context)
+{
+ free(context);
+}
+
+static char const *
+sun_get_backend_id(cubeb * context)
+{
+ return "sun";
+}
+
+static int
+sun_get_preferred_sample_rate(cubeb * context, uint32_t * rate)
+{
+ (void)context;
+
+ *rate = SUN_PREFER_RATE;
+ return CUBEB_OK;
+}
+
+static int
+sun_get_max_channel_count(cubeb * context, uint32_t * max_channels)
+{
+ (void)context;
+
+ *max_channels = SUN_MAX_CHANNELS;
+ return CUBEB_OK;
+}
+
+static int
+sun_get_min_latency(cubeb * context, cubeb_stream_params params,
+ uint32_t * latency_frames)
+{
+ (void)context;
+
+ *latency_frames = SUN_LATENCY_MS * params.rate / 1000;
+ return CUBEB_OK;
+}
+
+static int
+sun_get_hwinfo(const char * device, struct audio_info * format, int * props,
+ struct audio_device * dev)
+{
+ int fd = -1;
+
+ if ((fd = open(device, O_RDONLY)) == -1) {
+ goto error;
+ }
+#ifdef AUDIO_GETFORMAT
+ if (ioctl(fd, AUDIO_GETFORMAT, format) != 0) {
+ goto error;
+ }
+#endif
+#ifdef AUDIO_GETPROPS
+ if (ioctl(fd, AUDIO_GETPROPS, props) != 0) {
+ goto error;
+ }
+#endif
+ if (ioctl(fd, AUDIO_GETDEV, dev) != 0) {
+ goto error;
+ }
+ close(fd);
+ return CUBEB_OK;
+error:
+ if (fd != -1) {
+ close(fd);
+ }
+ return CUBEB_ERROR;
+}
+
+/*
+ * XXX: PR kern/54264
+ */
+static int
+sun_prinfo_verify_sanity(struct audio_prinfo * prinfo)
+{
+ return prinfo->precision >= 8 && prinfo->precision <= 32 &&
+ prinfo->channels >= 1 && prinfo->channels < SUN_MAX_CHANNELS &&
+ prinfo->sample_rate < SUN_MAX_RATE &&
+ prinfo->sample_rate > SUN_MIN_RATE;
+}
+
+static int
+sun_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection)
+{
+ unsigned i;
+ cubeb_device_info device = {0};
+ char dev[16] = SUN_DEFAULT_DEVICE;
+ char dev_friendly[64];
+ struct audio_info hwfmt;
+ struct audio_device hwname;
+ struct audio_prinfo * prinfo = NULL;
+ int hwprops;
+
+ collection->device = calloc(SUN_DEVICE_COUNT, sizeof(cubeb_device_info));
+ if (collection->device == NULL) {
+ return CUBEB_ERROR;
+ }
+ collection->count = 0;
+
+ for (i = 0; i < SUN_DEVICE_COUNT; ++i) {
+ if (i > 0) {
+ (void)snprintf(dev, sizeof(dev), "/dev/audio%u", i - 1);
+ }
+ if (sun_get_hwinfo(dev, &hwfmt, &hwprops, &hwname) != CUBEB_OK) {
+ continue;
+ }
+#ifdef AUDIO_GETPROPS
+ device.type = 0;
+ if ((hwprops & AUDIO_PROP_CAPTURE) != 0 &&
+ sun_prinfo_verify_sanity(&hwfmt.record)) {
+ /* the device supports recording, probably */
+ device.type |= CUBEB_DEVICE_TYPE_INPUT;
+ }
+ if ((hwprops & AUDIO_PROP_PLAYBACK) != 0 &&
+ sun_prinfo_verify_sanity(&hwfmt.play)) {
+ /* the device supports playback, probably */
+ device.type |= CUBEB_DEVICE_TYPE_OUTPUT;
+ }
+ switch (device.type) {
+ case 0:
+ /* device doesn't do input or output, aliens probably involved */
+ continue;
+ case CUBEB_DEVICE_TYPE_INPUT:
+ if ((type & CUBEB_DEVICE_TYPE_INPUT) == 0) {
+ /* this device is input only, not scanning for those, skip it */
+ continue;
+ }
+ break;
+ case CUBEB_DEVICE_TYPE_OUTPUT:
+ if ((type & CUBEB_DEVICE_TYPE_OUTPUT) == 0) {
+ /* this device is output only, not scanning for those, skip it */
+ continue;
+ }
+ break;
+ }
+ if ((type & CUBEB_DEVICE_TYPE_INPUT) != 0) {
+ prinfo = &hwfmt.record;
+ }
+ if ((type & CUBEB_DEVICE_TYPE_OUTPUT) != 0) {
+ prinfo = &hwfmt.play;
+ }
+#endif
+ if (i > 0) {
+ (void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (%d)",
+ hwname.name, hwname.version, hwname.config, i - 1);
+ } else {
+ (void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (default)",
+ hwname.name, hwname.version, hwname.config);
+ }
+ device.devid = (void *)(uintptr_t)i;
+ device.device_id = strdup(dev);
+ device.friendly_name = strdup(dev_friendly);
+ device.group_id = strdup(dev);
+ device.vendor_name = strdup(hwname.name);
+ device.type = type;
+ device.state = CUBEB_DEVICE_STATE_ENABLED;
+ device.preferred =
+ (i == 0) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
+#ifdef AUDIO_GETFORMAT
+ device.max_channels = prinfo->channels;
+ device.default_rate = prinfo->sample_rate;
+#else
+ device.max_channels = 2;
+ device.default_rate = SUN_PREFER_RATE;
+#endif
+ device.default_format = CUBEB_DEVICE_FMT_S16NE;
+ device.format = CUBEB_DEVICE_FMT_S16NE;
+ device.min_rate = SUN_MIN_RATE;
+ device.max_rate = SUN_MAX_RATE;
+ device.latency_lo = SUN_LATENCY_MS * SUN_MIN_RATE / 1000;
+ device.latency_hi = SUN_LATENCY_MS * SUN_MAX_RATE / 1000;
+ collection->device[collection->count++] = device;
+ }
+ return CUBEB_OK;
+}
+
+static int
+sun_device_collection_destroy(cubeb * context,
+ cubeb_device_collection * collection)
+{
+ unsigned i;
+
+ for (i = 0; i < collection->count; ++i) {
+ free((char *)collection->device[i].device_id);
+ free((char *)collection->device[i].friendly_name);
+ free((char *)collection->device[i].group_id);
+ free((char *)collection->device[i].vendor_name);
+ }
+ free(collection->device);
+ return CUBEB_OK;
+}
+
+static int
+sun_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params,
+ struct audio_info * info, struct audio_prinfo * prinfo)
+{
+ prinfo->channels = params->channels;
+ prinfo->sample_rate = params->rate;
+#ifdef AUDIO_ENCODING_SLINEAR_LE
+ switch (params->format) {
+ case CUBEB_SAMPLE_S16LE:
+ prinfo->encoding = AUDIO_ENCODING_SLINEAR_LE;
+ prinfo->precision = 16;
+ break;
+ case CUBEB_SAMPLE_S16BE:
+ prinfo->encoding = AUDIO_ENCODING_SLINEAR_BE;
+ prinfo->precision = 16;
+ break;
+ case CUBEB_SAMPLE_FLOAT32NE:
+ prinfo->encoding = AUDIO_ENCODING_SLINEAR;
+ prinfo->precision = 32;
+ break;
+ default:
+ LOG("Unsupported format");
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+#else
+ switch (params->format) {
+ case CUBEB_SAMPLE_S16NE:
+ prinfo->encoding = AUDIO_ENCODING_LINEAR;
+ prinfo->precision = 16;
+ break;
+ case CUBEB_SAMPLE_FLOAT32NE:
+ prinfo->encoding = AUDIO_ENCODING_LINEAR;
+ prinfo->precision = 32;
+ break;
+ default:
+ LOG("Unsupported format");
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+#endif
+ if (ioctl(fd, AUDIO_SETINFO, info) == -1) {
+ return CUBEB_ERROR;
+ }
+ if (ioctl(fd, AUDIO_GETINFO, info) == -1) {
+ return CUBEB_ERROR;
+ }
+ return CUBEB_OK;
+}
+
+static int
+sun_stream_stop(cubeb_stream * s)
+{
+ pthread_mutex_lock(&s->mutex);
+ if (s->running) {
+ s->running = false;
+ pthread_mutex_unlock(&s->mutex);
+ pthread_join(s->thread, NULL);
+ } else {
+ pthread_mutex_unlock(&s->mutex);
+ }
+ return CUBEB_OK;
+}
+
+static void
+sun_stream_destroy(cubeb_stream * s)
+{
+ sun_stream_stop(s);
+ pthread_mutex_destroy(&s->mutex);
+ if (s->play.fd != -1) {
+ close(s->play.fd);
+ }
+ if (s->record.fd != -1) {
+ close(s->record.fd);
+ }
+ free(s->play.buf);
+ free(s->record.buf);
+ free(s);
+}
+
+static void
+sun_float_to_linear32(void * buf, unsigned sample_count, float vol)
+{
+ float * in = buf;
+ int32_t * out = buf;
+ int32_t * tail = out + sample_count;
+
+ while (out < tail) {
+ float f = *(in++) * vol;
+ if (f < -1.0)
+ f = -1.0;
+ else if (f > 1.0)
+ f = 1.0;
+ *(out++) = f * (float)INT32_MAX;
+ }
+}
+
+static void
+sun_linear32_to_float(void * buf, unsigned sample_count)
+{
+ int32_t * in = buf;
+ float * out = buf;
+ float * tail = out + sample_count;
+
+ while (out < tail) {
+ *(out++) = (1.0 / 0x80000000) * *(in++);
+ }
+}
+
+static void
+sun_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol)
+{
+ unsigned i;
+ int32_t multiplier = vol * 0x8000;
+
+ for (i = 0; i < sample_count; ++i) {
+ buf[i] = (buf[i] * multiplier) >> 15;
+ }
+}
+
+static void *
+sun_io_routine(void * arg)
+{
+ cubeb_stream * s = arg;
+ cubeb_state state = CUBEB_STATE_STARTED;
+ size_t to_read = 0;
+ long to_write = 0;
+ size_t write_ofs = 0;
+ size_t read_ofs = 0;
+ int drain = 0;
+
+ CUBEB_REGISTER_THREAD("cubeb rendering thread");
+
+ s->state_cb(s, s->user_ptr, CUBEB_STATE_STARTED);
+ while (state != CUBEB_STATE_ERROR) {
+ pthread_mutex_lock(&s->mutex);
+ if (!s->running) {
+ pthread_mutex_unlock(&s->mutex);
+ state = CUBEB_STATE_STOPPED;
+ break;
+ }
+ pthread_mutex_unlock(&s->mutex);
+ if (s->record.fd != -1 && s->record.floating) {
+ sun_linear32_to_float(s->record.buf,
+ s->record.info.record.channels * SUN_BUFFER_FRAMES);
+ }
+ to_write = s->data_cb(s, s->user_ptr, s->record.buf, s->play.buf,
+ SUN_BUFFER_FRAMES);
+ if (to_write == CUBEB_ERROR) {
+ state = CUBEB_STATE_ERROR;
+ break;
+ }
+ if (s->play.fd != -1) {
+ float vol;
+
+ pthread_mutex_lock(&s->mutex);
+ vol = s->volume;
+ pthread_mutex_unlock(&s->mutex);
+
+ if (s->play.floating) {
+ sun_float_to_linear32(s->play.buf,
+ s->play.info.play.channels * to_write, vol);
+ } else {
+ sun_linear16_set_vol(s->play.buf, s->play.info.play.channels * to_write,
+ vol);
+ }
+ }
+ if (to_write < SUN_BUFFER_FRAMES) {
+ drain = 1;
+ }
+ to_write = s->play.fd != -1 ? to_write : 0;
+ to_read = s->record.fd != -1 ? SUN_BUFFER_FRAMES : 0;
+ write_ofs = 0;
+ read_ofs = 0;
+ while (to_write > 0 || to_read > 0) {
+ size_t bytes;
+ ssize_t n, frames;
+
+ if (to_write > 0) {
+ bytes = to_write * s->play.frame_size;
+ if ((n = write(s->play.fd, (uint8_t *)s->play.buf + write_ofs, bytes)) <
+ 0) {
+ state = CUBEB_STATE_ERROR;
+ break;
+ }
+ frames = n / s->play.frame_size;
+ pthread_mutex_lock(&s->mutex);
+ s->frames_written += frames;
+ pthread_mutex_unlock(&s->mutex);
+ to_write -= frames;
+ write_ofs += n;
+ }
+ if (to_read > 0) {
+ bytes = to_read * s->record.frame_size;
+ if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs,
+ bytes)) < 0) {
+ state = CUBEB_STATE_ERROR;
+ break;
+ }
+ frames = n / s->record.frame_size;
+ to_read -= frames;
+ read_ofs += n;
+ }
+ }
+ if (drain && state != CUBEB_STATE_ERROR) {
+ state = CUBEB_STATE_DRAINED;
+ break;
+ }
+ }
+ s->state_cb(s, s->user_ptr, state);
+ CUBEB_UNREGISTER_THREAD();
+ return NULL;
+}
+
+static int
+sun_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned latency_frames, cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ int ret = CUBEB_OK;
+ cubeb_stream * s = NULL;
+
+ (void)stream_name;
+ (void)latency_frames;
+ if ((s = calloc(1, sizeof(cubeb_stream))) == NULL) {
+ ret = CUBEB_ERROR;
+ goto error;
+ }
+ s->record.fd = -1;
+ s->play.fd = -1;
+ if (input_device != 0) {
+ snprintf(s->record.name, sizeof(s->record.name), "/dev/audio%zu",
+ (uintptr_t)input_device - 1);
+ } else {
+ snprintf(s->record.name, sizeof(s->record.name), "%s", SUN_DEFAULT_DEVICE);
+ }
+ if (output_device != 0) {
+ snprintf(s->play.name, sizeof(s->play.name), "/dev/audio%zu",
+ (uintptr_t)output_device - 1);
+ } else {
+ snprintf(s->play.name, sizeof(s->play.name), "%s", SUN_DEFAULT_DEVICE);
+ }
+ if (input_stream_params != NULL) {
+ if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ LOG("Loopback not supported");
+ ret = CUBEB_ERROR_NOT_SUPPORTED;
+ goto error;
+ }
+ if (s->record.fd == -1) {
+ if ((s->record.fd = open(s->record.name, O_RDONLY)) == -1) {
+ LOG("Audio device could not be opened as read-only");
+ ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
+ goto error;
+ }
+ }
+ AUDIO_INITINFO(&s->record.info);
+#ifdef AUMODE_RECORD
+ s->record.info.mode = AUMODE_RECORD;
+#endif
+ if ((ret = sun_copy_params(s->record.fd, s, input_stream_params,
+ &s->record.info, &s->record.info.record)) !=
+ CUBEB_OK) {
+ LOG("Setting record params failed");
+ goto error;
+ }
+ s->record.floating =
+ (input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
+ }
+ if (output_stream_params != NULL) {
+ if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ LOG("Loopback not supported");
+ ret = CUBEB_ERROR_NOT_SUPPORTED;
+ goto error;
+ }
+ if (s->play.fd == -1) {
+ if ((s->play.fd = open(s->play.name, O_WRONLY)) == -1) {
+ LOG("Audio device could not be opened as write-only");
+ ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
+ goto error;
+ }
+ }
+ AUDIO_INITINFO(&s->play.info);
+#ifdef AUMODE_PLAY
+ s->play.info.mode = AUMODE_PLAY;
+#endif
+ if ((ret = sun_copy_params(s->play.fd, s, output_stream_params,
+ &s->play.info, &s->play.info.play)) !=
+ CUBEB_OK) {
+ LOG("Setting play params failed");
+ goto error;
+ }
+ s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
+ }
+ s->context = context;
+ s->volume = 1.0;
+ s->state_cb = state_callback;
+ s->data_cb = data_callback;
+ s->user_ptr = user_ptr;
+ if (pthread_mutex_init(&s->mutex, NULL) != 0) {
+ LOG("Failed to create mutex");
+ goto error;
+ }
+ s->play.frame_size =
+ s->play.info.play.channels * (s->play.info.play.precision / 8);
+ if (s->play.fd != -1 &&
+ (s->play.buf = calloc(SUN_BUFFER_FRAMES, s->play.frame_size)) == NULL) {
+ ret = CUBEB_ERROR;
+ goto error;
+ }
+ s->record.frame_size =
+ s->record.info.record.channels * (s->record.info.record.precision / 8);
+ if (s->record.fd != -1 &&
+ (s->record.buf = calloc(SUN_BUFFER_FRAMES, s->record.frame_size)) ==
+ NULL) {
+ ret = CUBEB_ERROR;
+ goto error;
+ }
+ *stream = s;
+ return CUBEB_OK;
+error:
+ if (s != NULL) {
+ sun_stream_destroy(s);
+ }
+ return ret;
+}
+
+static int
+sun_stream_start(cubeb_stream * s)
+{
+ s->running = true;
+ if (pthread_create(&s->thread, NULL, sun_io_routine, s) != 0) {
+ LOG("Couldn't create thread");
+ return CUBEB_ERROR;
+ }
+ return CUBEB_OK;
+}
+
+static int
+sun_stream_get_position(cubeb_stream * s, uint64_t * position)
+{
+#ifdef AUDIO_GETOOFFS
+ struct audio_offset offset;
+
+ if (ioctl(s->play.fd, AUDIO_GETOOFFS, &offset) == -1) {
+ return CUBEB_ERROR;
+ }
+ s->blocks_written += offset.deltablks;
+ *position = (s->blocks_written * s->play.info.blocksize) / s->play.frame_size;
+ return CUBEB_OK;
+#else
+ pthread_mutex_lock(&s->mutex);
+ *position = s->frames_written;
+ pthread_mutex_unlock(&s->mutex);
+ return CUBEB_OK;
+#endif
+}
+
+static int
+sun_stream_get_latency(cubeb_stream * s, uint32_t * latency)
+{
+#ifdef AUDIO_GETBUFINFO
+ struct audio_info info;
+
+ if (ioctl(s->play.fd, AUDIO_GETBUFINFO, &info) == -1) {
+ return CUBEB_ERROR;
+ }
+
+ *latency = (info.play.seek + info.blocksize) / s->play.frame_size;
+ return CUBEB_OK;
+#else
+ cubeb_stream_params params;
+
+ params.rate = s->play.info.play.sample_rate;
+
+ return sun_get_min_latency(NULL, params, latency);
+#endif
+}
+
+static int
+sun_stream_set_volume(cubeb_stream * stream, float volume)
+{
+ pthread_mutex_lock(&stream->mutex);
+ stream->volume = volume;
+ pthread_mutex_unlock(&stream->mutex);
+ return CUBEB_OK;
+}
+
+static int
+sun_get_current_device(cubeb_stream * stream, cubeb_device ** const device)
+{
+ *device = calloc(1, sizeof(cubeb_device));
+ if (*device == NULL) {
+ return CUBEB_ERROR;
+ }
+ (*device)->input_name =
+ stream->record.fd != -1 ? strdup(stream->record.name) : NULL;
+ (*device)->output_name =
+ stream->play.fd != -1 ? strdup(stream->play.name) : NULL;
+ return CUBEB_OK;
+}
+
+static int
+sun_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
+{
+ (void)stream;
+ free(device->input_name);
+ free(device->output_name);
+ free(device);
+ return CUBEB_OK;
+}
+
+static struct cubeb_ops const sun_ops = {
+ .init = sun_init,
+ .get_backend_id = sun_get_backend_id,
+ .get_max_channel_count = sun_get_max_channel_count,
+ .get_min_latency = sun_get_min_latency,
+ .get_preferred_sample_rate = sun_get_preferred_sample_rate,
+ .get_supported_input_processing_params = NULL,
+ .enumerate_devices = sun_enumerate_devices,
+ .device_collection_destroy = sun_device_collection_destroy,
+ .destroy = sun_destroy,
+ .stream_init = sun_stream_init,
+ .stream_destroy = sun_stream_destroy,
+ .stream_start = sun_stream_start,
+ .stream_stop = sun_stream_stop,
+ .stream_get_position = sun_stream_get_position,
+ .stream_get_latency = sun_stream_get_latency,
+ .stream_get_input_latency = NULL,
+ .stream_set_volume = sun_stream_set_volume,
+ .stream_set_name = NULL,
+ .stream_get_current_device = sun_get_current_device,
+ .stream_set_input_mute = NULL,
+ .stream_set_input_processing_params = NULL,
+ .stream_device_destroy = sun_stream_device_destroy,
+ .stream_register_device_changed_callback = NULL,
+ .register_device_collection_changed = NULL};
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_tracing.h b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_tracing.h
new file mode 100644
index 0000000000..49944cdf67
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_tracing.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright © 2022 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_TRACING_H
+#define CUBEB_TRACING_H
+
+/* Empty header to allow hooking up a frame profiler. */
+
+// To be called once on a thread to register for tracing.
+#define CUBEB_REGISTER_THREAD(name)
+// To be called once before a registered threads exits.
+#define CUBEB_UNREGISTER_THREAD()
+// Insert a tracing marker, with a particular name.
+// Phase can be 'x': instant marker, start time but no duration
+// 'b': beginning of a marker with a duration
+// 'e': end of a marker with a duration
+#define CUBEB_TRACE(name, phase)
+
+#endif // CUBEB_TRACING_H
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_triple_buffer.h b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_triple_buffer.h
new file mode 100644
index 0000000000..a5a5978fb4
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_triple_buffer.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright © 2022 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/**
+ * Adapted and ported to C++ from https://crates.io/crates/triple_buffer
+ */
+
+#ifndef CUBEB_TRIPLE_BUFFER
+#define CUBEB_TRIPLE_BUFFER
+
+#include <atomic>
+
+// Single producer / single consumer wait-free triple buffering
+// implementation, for when a producer wants to publish data to a consumer
+// without blocking, but when a queue is wastefull, because it's OK for the
+// consumer to miss data updates.
+template <typename T> class triple_buffer {
+public:
+ // Write a new value into the triple buffer. Returns true if a value was
+ // overwritten.
+ // Producer-side only.
+ bool write(T & input)
+ {
+ storage[input_idx] = input;
+ return publish();
+ }
+ // Get the latest value from the triple buffer.
+ // Consumer-side only.
+ T & read()
+ {
+ update();
+ return storage[output_idx];
+ }
+ // Returns true if a new value has been published by the consumer without
+ // having been consumed yet.
+ // Consumer-side only.
+ bool updated()
+ {
+ return (shared_state.load(std::memory_order_relaxed) & BACK_DIRTY_BIT) != 0;
+ }
+
+private:
+ // Publish a value to the consumer. Returns true if the data was overwritten
+ // without having been read.
+ bool publish()
+ {
+ auto former_back_idx = shared_state.exchange(input_idx | BACK_DIRTY_BIT,
+ std::memory_order_acq_rel);
+ input_idx = former_back_idx & BACK_INDEX_MASK;
+ return (former_back_idx & BACK_DIRTY_BIT) != 0;
+ }
+ // Get a new value from the producer, if a new value has been produced.
+ bool update()
+ {
+ bool was_updated = updated();
+ if (was_updated) {
+ auto former_back_idx =
+ shared_state.exchange(output_idx, std::memory_order_acq_rel);
+ output_idx = former_back_idx & BACK_INDEX_MASK;
+ }
+ return was_updated;
+ }
+ T storage[3];
+ // Mask used to extract back-buffer index
+ const uint8_t BACK_INDEX_MASK = 0b11;
+ // Bit set by producer to signal updates
+ const uint8_t BACK_DIRTY_BIT = 0b100;
+ // Shared state: a dirty bit, and an index.
+ std::atomic<uint8_t> shared_state = {0};
+ // Output index, private to the consumer.
+ uint8_t output_idx = 1;
+ // Input index, private to the producer.
+ uint8_t input_idx = 2;
+};
+
+#endif // CUBEB_TRIPLE_BUFFER
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_utils.cpp b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_utils.cpp
new file mode 100644
index 0000000000..dd1aef7b82
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_utils.cpp
@@ -0,0 +1,25 @@
+/*
+ * Copyright © 2018 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#include "cubeb_utils.h"
+
+size_t
+cubeb_sample_size(cubeb_sample_format format)
+{
+ switch (format) {
+ case CUBEB_SAMPLE_S16LE:
+ case CUBEB_SAMPLE_S16BE:
+ return sizeof(int16_t);
+ case CUBEB_SAMPLE_FLOAT32LE:
+ case CUBEB_SAMPLE_FLOAT32BE:
+ return sizeof(float);
+ default:
+ // should never happen as all cases are handled above.
+ assert(false);
+ return 0;
+ }
+}
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_utils.h b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_utils.h
new file mode 100644
index 0000000000..851d24de2c
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_utils.h
@@ -0,0 +1,318 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#if !defined(CUBEB_UTILS)
+#define CUBEB_UTILS
+
+#include "cubeb/cubeb.h"
+
+#ifdef __cplusplus
+
+#include <assert.h>
+#include <mutex>
+#include <stdint.h>
+#include <string.h>
+#include <type_traits>
+#if defined(_WIN32)
+#include "cubeb_utils_win.h"
+#else
+#include "cubeb_utils_unix.h"
+#endif
+
+/** Similar to memcpy, but accounts for the size of an element. */
+template <typename T>
+void
+PodCopy(T * destination, const T * source, size_t count)
+{
+ static_assert(std::is_trivial<T>::value, "Requires trivial type");
+ assert(destination && source);
+ memcpy(destination, source, count * sizeof(T));
+}
+
+/** Similar to memmove, but accounts for the size of an element. */
+template <typename T>
+void
+PodMove(T * destination, const T * source, size_t count)
+{
+ static_assert(std::is_trivial<T>::value, "Requires trivial type");
+ assert(destination && source);
+ memmove(destination, source, count * sizeof(T));
+}
+
+/** Similar to a memset to zero, but accounts for the size of an element. */
+template <typename T>
+void
+PodZero(T * destination, size_t count)
+{
+ static_assert(std::is_trivial<T>::value, "Requires trivial type");
+ assert(destination);
+ memset(destination, 0, count * sizeof(T));
+}
+
+namespace {
+template <typename T, typename Trait>
+void
+Copy(T * destination, const T * source, size_t count, Trait)
+{
+ for (size_t i = 0; i < count; i++) {
+ destination[i] = source[i];
+ }
+}
+
+template <typename T>
+void
+Copy(T * destination, const T * source, size_t count, std::true_type)
+{
+ PodCopy(destination, source, count);
+}
+} // namespace
+
+/**
+ * This allows copying a number of elements from a `source` pointer to a
+ * `destination` pointer, using `memcpy` if it is safe to do so, or a loop that
+ * calls the constructors and destructors otherwise.
+ */
+template <typename T>
+void
+Copy(T * destination, const T * source, size_t count)
+{
+ assert(destination && source);
+ Copy(destination, source, count, typename std::is_trivial<T>::type());
+}
+
+namespace {
+template <typename T, typename Trait>
+void
+ConstructDefault(T * destination, size_t count, Trait)
+{
+ for (size_t i = 0; i < count; i++) {
+ destination[i] = T();
+ }
+}
+
+template <typename T>
+void
+ConstructDefault(T * destination, size_t count, std::true_type)
+{
+ PodZero(destination, count);
+}
+} // namespace
+
+/**
+ * This allows zeroing (using memset) or default-constructing a number of
+ * elements calling the constructors and destructors if necessary.
+ */
+template <typename T>
+void
+ConstructDefault(T * destination, size_t count)
+{
+ assert(destination);
+ ConstructDefault(destination, count, typename std::is_arithmetic<T>::type());
+}
+
+template <typename T> class auto_array {
+public:
+ explicit auto_array(uint32_t capacity = 0)
+ : data_(capacity ? new T[capacity] : nullptr), capacity_(capacity),
+ length_(0)
+ {
+ }
+
+ ~auto_array() { delete[] data_; }
+
+ /** Get a constant pointer to the underlying data. */
+ T * data() const { return data_; }
+
+ T * end() const { return data_ + length_; }
+
+ const T & at(size_t index) const
+ {
+ assert(index < length_ && "out of range");
+ return data_[index];
+ }
+
+ T & at(size_t index)
+ {
+ assert(index < length_ && "out of range");
+ return data_[index];
+ }
+
+ /** Get how much underlying storage this auto_array has. */
+ size_t capacity() const { return capacity_; }
+
+ /** Get how much elements this auto_array contains. */
+ size_t length() const { return length_; }
+
+ /** Keeps the storage, but removes all the elements from the array. */
+ void clear() { length_ = 0; }
+
+ /** Change the storage of this auto array, copying the elements to the new
+ * storage.
+ * @returns true in case of success
+ * @returns false if the new capacity is not big enough to accomodate for the
+ * elements in the array.
+ */
+ bool reserve(size_t new_capacity)
+ {
+ if (new_capacity < length_) {
+ return false;
+ }
+ T * new_data = new T[new_capacity];
+ if (data_ && length_) {
+ PodCopy(new_data, data_, length_);
+ }
+ capacity_ = new_capacity;
+ delete[] data_;
+ data_ = new_data;
+
+ return true;
+ }
+
+ /** Append `length` elements to the end of the array, resizing the array if
+ * needed.
+ * @parameter elements the elements to append to the array.
+ * @parameter length the number of elements to append to the array.
+ */
+ void push(const T * elements, size_t length)
+ {
+ if (length_ + length > capacity_) {
+ reserve(length_ + length);
+ }
+ if (data_) {
+ PodCopy(data_ + length_, elements, length);
+ }
+ length_ += length;
+ }
+
+ /** Append `length` zero-ed elements to the end of the array, resizing the
+ * array if needed.
+ * @parameter length the number of elements to append to the array.
+ */
+ void push_silence(size_t length)
+ {
+ if (length_ + length > capacity_) {
+ reserve(length + length_);
+ }
+ if (data_) {
+ PodZero(data_ + length_, length);
+ }
+ length_ += length;
+ }
+
+ /** Prepend `length` zero-ed elements to the front of the array, resizing and
+ * shifting the array if needed.
+ * @parameter length the number of elements to prepend to the array.
+ */
+ void push_front_silence(size_t length)
+ {
+ if (length_ + length > capacity_) {
+ reserve(length + length_);
+ }
+ if (data_) {
+ PodMove(data_ + length, data_, length_);
+ PodZero(data_, length);
+ }
+ length_ += length;
+ }
+
+ /** Return the number of free elements in the array. */
+ size_t available() const { return capacity_ - length_; }
+
+ /** Copies `length` elements to `elements` if it is not null, and shift
+ * the remaining elements of the `auto_array` to the beginning.
+ * @parameter elements a buffer to copy the elements to, or nullptr.
+ * @parameter length the number of elements to copy.
+ * @returns true in case of success.
+ * @returns false if the auto_array contains less than `length` elements. */
+ bool pop(T * elements, size_t length)
+ {
+ if (length > length_) {
+ return false;
+ }
+ if (!data_) {
+ return true;
+ }
+ if (elements) {
+ PodCopy(elements, data_, length);
+ }
+ PodMove(data_, data_ + length, length_ - length);
+
+ length_ -= length;
+
+ return true;
+ }
+
+ void set_length(size_t length)
+ {
+ assert(length <= capacity_);
+ length_ = length;
+ }
+
+private:
+ /** The underlying storage */
+ T * data_;
+ /** The size, in number of elements, of the storage. */
+ size_t capacity_;
+ /** The number of elements the array contains. */
+ size_t length_;
+};
+
+struct auto_array_wrapper {
+ virtual void push(void * elements, size_t length) = 0;
+ virtual size_t length() = 0;
+ virtual void push_silence(size_t length) = 0;
+ virtual bool pop(size_t length) = 0;
+ virtual void * data() = 0;
+ virtual void * end() = 0;
+ virtual void clear() = 0;
+ virtual bool reserve(size_t capacity) = 0;
+ virtual void set_length(size_t length) = 0;
+ virtual ~auto_array_wrapper() {}
+};
+
+template <typename T>
+struct auto_array_wrapper_impl : public auto_array_wrapper {
+ auto_array_wrapper_impl() {}
+
+ explicit auto_array_wrapper_impl(uint32_t size) : ar(size) {}
+
+ void push(void * elements, size_t length) override
+ {
+ ar.push(static_cast<T *>(elements), length);
+ }
+
+ size_t length() override { return ar.length(); }
+
+ void push_silence(size_t length) override { ar.push_silence(length); }
+
+ bool pop(size_t length) override { return ar.pop(nullptr, length); }
+
+ void * data() override { return ar.data(); }
+
+ void * end() override { return ar.end(); }
+
+ void clear() override { ar.clear(); }
+
+ bool reserve(size_t capacity) override { return ar.reserve(capacity); }
+
+ void set_length(size_t length) override { ar.set_length(length); }
+
+ ~auto_array_wrapper_impl() { ar.clear(); }
+
+private:
+ auto_array<T> ar;
+};
+
+extern "C" {
+size_t
+cubeb_sample_size(cubeb_sample_format format);
+}
+
+using auto_lock = std::lock_guard<owned_critical_section>;
+#endif // __cplusplus
+
+#endif /* CUBEB_UTILS */
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_utils_unix.h b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_utils_unix.h
new file mode 100644
index 0000000000..b6618ca45e
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_utils_unix.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#if !defined(CUBEB_UTILS_UNIX)
+#define CUBEB_UTILS_UNIX
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdio.h>
+
+/* This wraps a critical section to track the owner in debug mode. */
+class owned_critical_section {
+public:
+ owned_critical_section()
+ {
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+#ifndef NDEBUG
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
+#else
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
+#endif
+
+#ifndef NDEBUG
+ int r =
+#endif
+ pthread_mutex_init(&mutex, &attr);
+#ifndef NDEBUG
+ assert(r == 0);
+#endif
+
+ pthread_mutexattr_destroy(&attr);
+ }
+
+ ~owned_critical_section()
+ {
+#ifndef NDEBUG
+ int r =
+#endif
+ pthread_mutex_destroy(&mutex);
+#ifndef NDEBUG
+ assert(r == 0);
+#endif
+ }
+
+ void lock()
+ {
+#ifndef NDEBUG
+ int r =
+#endif
+ pthread_mutex_lock(&mutex);
+#ifndef NDEBUG
+ assert(r == 0 && "Deadlock");
+#endif
+ }
+
+ void unlock()
+ {
+#ifndef NDEBUG
+ int r =
+#endif
+ pthread_mutex_unlock(&mutex);
+#ifndef NDEBUG
+ assert(r == 0 && "Unlocking unlocked mutex");
+#endif
+ }
+
+ void assert_current_thread_owns()
+ {
+#ifndef NDEBUG
+ int r = pthread_mutex_lock(&mutex);
+ assert(r == EDEADLK);
+#endif
+ }
+
+private:
+ pthread_mutex_t mutex;
+
+ // Disallow copy and assignment because pthread_mutex_t cannot be copied.
+ owned_critical_section(const owned_critical_section &);
+ owned_critical_section & operator=(const owned_critical_section &);
+};
+
+#endif /* CUBEB_UTILS_UNIX */
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_utils_win.h b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_utils_win.h
new file mode 100644
index 0000000000..48e7b1b6d0
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_utils_win.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#if !defined(CUBEB_UTILS_WIN)
+#define CUBEB_UTILS_WIN
+
+#include "cubeb-internal.h"
+#include <windows.h>
+
+/* This wraps an SRWLock to track the owner in debug mode, adapted from
+ NSPR and http://blogs.msdn.com/b/oldnewthing/archive/2013/07/12/10433554.aspx
+ */
+class owned_critical_section {
+public:
+ owned_critical_section()
+ : srwlock(SRWLOCK_INIT)
+#ifndef NDEBUG
+ ,
+ owner(0)
+#endif
+ {
+ }
+
+ void lock()
+ {
+ AcquireSRWLockExclusive(&srwlock);
+#ifndef NDEBUG
+ XASSERT(owner != GetCurrentThreadId() && "recursive locking");
+ owner = GetCurrentThreadId();
+#endif
+ }
+
+ void unlock()
+ {
+#ifndef NDEBUG
+ /* GetCurrentThreadId cannot return 0: it is not a the valid thread id */
+ owner = 0;
+#endif
+ ReleaseSRWLockExclusive(&srwlock);
+ }
+
+ /* This is guaranteed to have the good behaviour if it succeeds. The behaviour
+ is undefined otherwise. */
+ void assert_current_thread_owns()
+ {
+#ifndef NDEBUG
+ /* This implies owner != 0, because GetCurrentThreadId cannot return 0. */
+ XASSERT(owner == GetCurrentThreadId());
+#endif
+ }
+
+private:
+ SRWLOCK srwlock;
+#ifndef NDEBUG
+ DWORD owner;
+#endif
+
+ // Disallow copy and assignment because SRWLock cannot be copied.
+ owned_critical_section(const owned_critical_section &);
+ owned_critical_section & operator=(const owned_critical_section &);
+};
+
+#endif /* CUBEB_UTILS_WIN */
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_wasapi.cpp b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_wasapi.cpp
new file mode 100644
index 0000000000..01417a52e3
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_wasapi.cpp
@@ -0,0 +1,3585 @@
+/*
+ * Copyright © 2013 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#define _WIN32_WINNT 0x0603
+#define NOMINMAX
+
+#include <algorithm>
+#include <atomic>
+#include <audioclient.h>
+#include <avrt.h>
+#include <cmath>
+#include <devicetopology.h>
+#include <initguid.h>
+#include <limits>
+#include <memory>
+#include <mmdeviceapi.h>
+#include <process.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <vector>
+#include <windef.h>
+#include <windows.h>
+/* clang-format off */
+/* These need to be included after windows.h */
+#include <mmsystem.h>
+/* clang-format on */
+
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_mixer.h"
+#include "cubeb_resampler.h"
+#include "cubeb_strings.h"
+#include "cubeb_tracing.h"
+#include "cubeb_utils.h"
+
+// Windows 10 exposes the IAudioClient3 interface to create low-latency streams.
+// Copy the interface definition from audioclient.h here to make the code
+// simpler and so that we can still access IAudioClient3 via COM if cubeb was
+// compiled against an older SDK.
+#ifndef __IAudioClient3_INTERFACE_DEFINED__
+#define __IAudioClient3_INTERFACE_DEFINED__
+MIDL_INTERFACE("7ED4EE07-8E67-4CD4-8C1A-2B7A5987AD42")
+IAudioClient3 : public IAudioClient
+{
+public:
+ virtual HRESULT STDMETHODCALLTYPE GetSharedModeEnginePeriod(
+ /* [annotation][in] */
+ _In_ const WAVEFORMATEX * pFormat,
+ /* [annotation][out] */
+ _Out_ UINT32 * pDefaultPeriodInFrames,
+ /* [annotation][out] */
+ _Out_ UINT32 * pFundamentalPeriodInFrames,
+ /* [annotation][out] */
+ _Out_ UINT32 * pMinPeriodInFrames,
+ /* [annotation][out] */
+ _Out_ UINT32 * pMaxPeriodInFrames) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE GetCurrentSharedModeEnginePeriod(
+ /* [unique][annotation][out] */
+ _Out_ WAVEFORMATEX * *ppFormat,
+ /* [annotation][out] */
+ _Out_ UINT32 * pCurrentPeriodInFrames) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE InitializeSharedAudioStream(
+ /* [annotation][in] */
+ _In_ DWORD StreamFlags,
+ /* [annotation][in] */
+ _In_ UINT32 PeriodInFrames,
+ /* [annotation][in] */
+ _In_ const WAVEFORMATEX * pFormat,
+ /* [annotation][in] */
+ _In_opt_ LPCGUID AudioSessionGuid) = 0;
+};
+#ifdef __CRT_UUID_DECL
+// Required for MinGW
+__CRT_UUID_DECL(IAudioClient3, 0x7ED4EE07, 0x8E67, 0x4CD4, 0x8C, 0x1A, 0x2B,
+ 0x7A, 0x59, 0x87, 0xAD, 0x42)
+#endif
+#endif
+// Copied from audioclient.h in the Windows 10 SDK
+#ifndef AUDCLNT_E_ENGINE_PERIODICITY_LOCKED
+#define AUDCLNT_E_ENGINE_PERIODICITY_LOCKED AUDCLNT_ERR(0x028)
+#endif
+
+#ifndef PKEY_Device_FriendlyName
+DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,
+ 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0,
+ 14); // DEVPROP_TYPE_STRING
+#endif
+#ifndef PKEY_Device_InstanceId
+DEFINE_PROPERTYKEY(PKEY_Device_InstanceId, 0x78c34fc8, 0x104a, 0x4aca, 0x9e,
+ 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57,
+ 0x00000100); // VT_LPWSTR
+#endif
+
+namespace {
+
+const int64_t LATENCY_NOT_AVAILABLE_YET = -1;
+
+const DWORD DEVICE_CHANGE_DEBOUNCE_MS = 250;
+
+struct com_heap_ptr_deleter {
+ void operator()(void * ptr) const noexcept { CoTaskMemFree(ptr); }
+};
+
+template <typename T>
+using com_heap_ptr = std::unique_ptr<T, com_heap_ptr_deleter>;
+
+template <typename T, size_t N>
+constexpr size_t
+ARRAY_LENGTH(T (&)[N])
+{
+ return N;
+}
+
+template <typename T> class no_addref_release : public T {
+ ULONG STDMETHODCALLTYPE AddRef() = 0;
+ ULONG STDMETHODCALLTYPE Release() = 0;
+};
+
+template <typename T> class com_ptr {
+public:
+ com_ptr() noexcept = default;
+
+ com_ptr(com_ptr const & other) noexcept = delete;
+ com_ptr & operator=(com_ptr const & other) noexcept = delete;
+ T ** operator&() const noexcept = delete;
+
+ ~com_ptr() noexcept { release(); }
+
+ com_ptr(com_ptr && other) noexcept : ptr(other.ptr) { other.ptr = nullptr; }
+
+ com_ptr & operator=(com_ptr && other) noexcept
+ {
+ if (ptr != other.ptr) {
+ release();
+ ptr = other.ptr;
+ other.ptr = nullptr;
+ }
+ return *this;
+ }
+
+ explicit operator bool() const noexcept { return nullptr != ptr; }
+
+ no_addref_release<T> * operator->() const noexcept
+ {
+ return static_cast<no_addref_release<T> *>(ptr);
+ }
+
+ T * get() const noexcept { return ptr; }
+
+ T ** receive() noexcept
+ {
+ XASSERT(ptr == nullptr);
+ return &ptr;
+ }
+
+ void ** receive_vpp() noexcept
+ {
+ return reinterpret_cast<void **>(receive());
+ }
+
+ com_ptr & operator=(std::nullptr_t) noexcept
+ {
+ release();
+ return *this;
+ }
+
+ void reset(T * p = nullptr) noexcept
+ {
+ release();
+ ptr = p;
+ }
+
+private:
+ void release() noexcept
+ {
+ T * temp = ptr;
+
+ if (temp) {
+ ptr = nullptr;
+ temp->Release();
+ }
+ }
+
+ T * ptr = nullptr;
+};
+
+LONG
+wasapi_stream_add_ref(cubeb_stream * stm);
+LONG
+wasapi_stream_release(cubeb_stream * stm);
+
+struct auto_stream_ref {
+ auto_stream_ref(cubeb_stream * stm_) : stm(stm_)
+ {
+ wasapi_stream_add_ref(stm);
+ }
+ ~auto_stream_ref() { wasapi_stream_release(stm); }
+ cubeb_stream * stm;
+};
+
+extern cubeb_ops const wasapi_ops;
+
+static com_heap_ptr<wchar_t>
+wasapi_get_default_device_id(EDataFlow flow, ERole role,
+ IMMDeviceEnumerator * enumerator);
+
+struct wasapi_default_devices {
+ wasapi_default_devices(IMMDeviceEnumerator * enumerator)
+ : render_console_id(
+ wasapi_get_default_device_id(eRender, eConsole, enumerator)),
+ render_comms_id(
+ wasapi_get_default_device_id(eRender, eCommunications, enumerator)),
+ capture_console_id(
+ wasapi_get_default_device_id(eCapture, eConsole, enumerator)),
+ capture_comms_id(
+ wasapi_get_default_device_id(eCapture, eCommunications, enumerator))
+ {
+ }
+
+ bool is_default(EDataFlow flow, ERole role, wchar_t const * id)
+ {
+ wchar_t const * default_id = nullptr;
+ if (flow == eRender && role == eConsole) {
+ default_id = this->render_console_id.get();
+ } else if (flow == eRender && role == eCommunications) {
+ default_id = this->render_comms_id.get();
+ } else if (flow == eCapture && role == eConsole) {
+ default_id = this->capture_console_id.get();
+ } else if (flow == eCapture && role == eCommunications) {
+ default_id = this->capture_comms_id.get();
+ }
+
+ return default_id && wcscmp(id, default_id) == 0;
+ }
+
+private:
+ com_heap_ptr<wchar_t> render_console_id;
+ com_heap_ptr<wchar_t> render_comms_id;
+ com_heap_ptr<wchar_t> capture_console_id;
+ com_heap_ptr<wchar_t> capture_comms_id;
+};
+
+struct AutoRegisterThread {
+ AutoRegisterThread(const char * name) { CUBEB_REGISTER_THREAD(name); }
+ ~AutoRegisterThread() { CUBEB_UNREGISTER_THREAD(); }
+};
+
+int
+wasapi_stream_stop(cubeb_stream * stm);
+int
+wasapi_stream_start(cubeb_stream * stm);
+void
+close_wasapi_stream(cubeb_stream * stm);
+int
+setup_wasapi_stream(cubeb_stream * stm);
+ERole
+pref_to_role(cubeb_stream_prefs param);
+int
+wasapi_create_device(cubeb * ctx, cubeb_device_info & ret,
+ IMMDeviceEnumerator * enumerator, IMMDevice * dev,
+ wasapi_default_devices * defaults);
+void
+wasapi_destroy_device(cubeb_device_info * device_info);
+static int
+wasapi_enumerate_devices_internal(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * out,
+ DWORD state_mask);
+static int
+wasapi_device_collection_destroy(cubeb * ctx,
+ cubeb_device_collection * collection);
+static char const *
+wstr_to_utf8(wchar_t const * str);
+static std::unique_ptr<wchar_t const[]>
+utf8_to_wstr(char const * str);
+
+} // namespace
+
+class wasapi_collection_notification_client;
+class monitor_device_notifications;
+
+struct cubeb {
+ cubeb_ops const * ops = &wasapi_ops;
+ owned_critical_section lock;
+ cubeb_strings * device_ids;
+ /* Device enumerator to get notifications when the
+ device collection change. */
+ com_ptr<IMMDeviceEnumerator> device_collection_enumerator;
+ com_ptr<wasapi_collection_notification_client> collection_notification_client;
+ /* Collection changed for input (capture) devices. */
+ cubeb_device_collection_changed_callback input_collection_changed_callback =
+ nullptr;
+ void * input_collection_changed_user_ptr = nullptr;
+ /* Collection changed for output (render) devices. */
+ cubeb_device_collection_changed_callback output_collection_changed_callback =
+ nullptr;
+ void * output_collection_changed_user_ptr = nullptr;
+ UINT64 performance_counter_frequency;
+};
+
+class wasapi_endpoint_notification_client;
+
+/* We have three possible callbacks we can use with a stream:
+ * - input only
+ * - output only
+ * - synchronized input and output
+ *
+ * Returns true when we should continue to play, false otherwise.
+ */
+typedef bool (*wasapi_refill_callback)(cubeb_stream * stm);
+
+struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
+ cubeb * context = nullptr;
+ void * user_ptr = nullptr;
+ /**/
+
+ /* Mixer pameters. We need to convert the input stream to this
+ samplerate/channel layout, as WASAPI does not resample nor upmix
+ itself. */
+ cubeb_stream_params input_mix_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0,
+ CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE};
+ cubeb_stream_params output_mix_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0,
+ CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE};
+ /* Stream parameters. This is what the client requested,
+ * and what will be presented in the callback. */
+ cubeb_stream_params input_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0,
+ CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE};
+ cubeb_stream_params output_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0,
+ CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE};
+ /* A MMDevice role for this stream: either communication or console here. */
+ ERole role;
+ /* True if this stream will transport voice-data. */
+ bool voice;
+ /* True if the input device of this stream is using bluetooth handsfree. */
+ bool input_bluetooth_handsfree;
+ /* The input and output device, or NULL for default. */
+ std::unique_ptr<const wchar_t[]> input_device_id;
+ std::unique_ptr<const wchar_t[]> output_device_id;
+ com_ptr<IMMDevice> input_device;
+ com_ptr<IMMDevice> output_device;
+ /* The latency initially requested for this stream, in frames. */
+ unsigned latency = 0;
+ cubeb_state_callback state_callback = nullptr;
+ cubeb_data_callback data_callback = nullptr;
+ wasapi_refill_callback refill_callback = nullptr;
+ /* True when a loopback device is requested with no output device. In this
+ case a dummy output device is opened to drive the loopback, but should not
+ be exposed. */
+ bool has_dummy_output = false;
+ /* Lifetime considerations:
+ - client, render_client, audio_clock and audio_stream_volume are interface
+ pointer to the IAudioClient.
+ - The lifetime for device_enumerator and notification_client, resampler,
+ mix_buffer are the same as the cubeb_stream instance. */
+
+ /* Main handle on the WASAPI stream. */
+ com_ptr<IAudioClient> output_client;
+ /* Interface pointer to use the event-driven interface. */
+ com_ptr<IAudioRenderClient> render_client;
+#ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
+ /* Interface pointer to use the volume facilities. */
+ com_ptr<IAudioStreamVolume> audio_stream_volume;
+#endif
+ /* Interface pointer to use the stream audio clock. */
+ com_ptr<IAudioClock> audio_clock;
+ /* Frames written to the stream since it was opened. Reset on device
+ change. Uses mix_params.rate. */
+ UINT64 frames_written = 0;
+ /* Frames written to the (logical) stream since it was first
+ created. Updated on device change. Uses stream_params.rate. */
+ UINT64 total_frames_written = 0;
+ /* Last valid reported stream position. Used to ensure the position
+ reported by stream_get_position increases monotonically. */
+ UINT64 prev_position = 0;
+ /* Device enumerator to be able to be notified when the default
+ device change. */
+ com_ptr<IMMDeviceEnumerator> device_enumerator;
+ /* Device notification client, to be able to be notified when the default
+ audio device changes and route the audio to the new default audio output
+ device */
+ com_ptr<wasapi_endpoint_notification_client> notification_client;
+ /* Main andle to the WASAPI capture stream. */
+ com_ptr<IAudioClient> input_client;
+ /* Interface to use the event driven capture interface */
+ com_ptr<IAudioCaptureClient> capture_client;
+ /* This event is set by the stream_destroy function, so the render loop can
+ exit properly. */
+ HANDLE shutdown_event = 0;
+ /* Set by OnDefaultDeviceChanged when a stream reconfiguration is required.
+ The reconfiguration is handled by the render loop thread. */
+ HANDLE reconfigure_event = 0;
+ /* This is set by WASAPI when we should refill the stream. */
+ HANDLE refill_event = 0;
+ /* This is set by WASAPI when we should read from the input stream. In
+ * practice, we read from the input stream in the output callback, so
+ * this is not used, but it is necessary to start getting input data. */
+ HANDLE input_available_event = 0;
+ /* Each cubeb_stream has its own thread. */
+ HANDLE thread = 0;
+ /* The lock protects all members that are touched by the render thread or
+ change during a device reset, including: audio_clock, audio_stream_volume,
+ client, frames_written, mix_params, total_frames_written, prev_position. */
+ owned_critical_section stream_reset_lock;
+ /* Maximum number of frames that can be passed down in a callback. */
+ uint32_t input_buffer_frame_count = 0;
+ /* Maximum number of frames that can be requested in a callback. */
+ uint32_t output_buffer_frame_count = 0;
+ /* Resampler instance. Resampling will only happen if necessary. */
+ std::unique_ptr<cubeb_resampler, decltype(&cubeb_resampler_destroy)>
+ resampler = {nullptr, cubeb_resampler_destroy};
+ /* Mixer interfaces */
+ std::unique_ptr<cubeb_mixer, decltype(&cubeb_mixer_destroy)> output_mixer = {
+ nullptr, cubeb_mixer_destroy};
+ std::unique_ptr<cubeb_mixer, decltype(&cubeb_mixer_destroy)> input_mixer = {
+ nullptr, cubeb_mixer_destroy};
+ /* A buffer for up/down mixing multi-channel audio output. */
+ std::vector<BYTE> mix_buffer;
+ /* WASAPI input works in "packets". We re-linearize the audio packets
+ * into this buffer before handing it to the resampler. */
+ std::unique_ptr<auto_array_wrapper> linear_input_buffer;
+ /* Bytes per sample. This multiplied by the number of channels is the number
+ * of bytes per frame. */
+ size_t bytes_per_sample = 0;
+ /* WAVEFORMATEXTENSIBLE sub-format: either PCM or float. */
+ GUID waveformatextensible_sub_format = GUID_NULL;
+ /* Stream volume. Set via stream_set_volume and used to reset volume on
+ device changes. */
+ float volume = 1.0;
+ /* True if the stream is draining. */
+ bool draining = false;
+ /* This needs an active audio input stream to be known, and is updated in the
+ * first audio input callback. */
+ std::atomic<int64_t> input_latency_hns{LATENCY_NOT_AVAILABLE_YET};
+ /* Those attributes count the number of frames requested (resp. received) by
+ the OS, to be able to detect drifts. This is only used for logging for now. */
+ size_t total_input_frames = 0;
+ size_t total_output_frames = 0;
+ /* This is set by the render loop thread once it has obtained a reference to
+ * COM and this stream object. */
+ HANDLE thread_ready_event = 0;
+ /* Keep a ref count on this stream object. After both stream_destroy has been
+ * called and the render loop thread has exited, destroy this stream object.
+ */
+ LONG ref_count = 0;
+
+ /* True if the stream is active, false if inactive. */
+ bool active = false;
+};
+
+class monitor_device_notifications {
+public:
+ monitor_device_notifications(cubeb * context) : cubeb_context(context)
+ {
+ create_thread();
+ }
+
+ ~monitor_device_notifications()
+ {
+ SetEvent(begin_shutdown);
+ WaitForSingleObject(shutdown_complete, INFINITE);
+ CloseHandle(thread);
+
+ CloseHandle(input_changed);
+ CloseHandle(output_changed);
+ CloseHandle(begin_shutdown);
+ CloseHandle(shutdown_complete);
+ }
+
+ void notify(EDataFlow flow)
+ {
+ XASSERT(cubeb_context);
+ if (flow == eCapture && cubeb_context->input_collection_changed_callback) {
+ bool res = SetEvent(input_changed);
+ if (!res) {
+ LOG("Failed to set input changed event");
+ }
+ return;
+ }
+ if (flow == eRender && cubeb_context->output_collection_changed_callback) {
+ bool res = SetEvent(output_changed);
+ if (!res) {
+ LOG("Failed to set output changed event");
+ }
+ }
+ }
+
+private:
+ static unsigned int __stdcall thread_proc(LPVOID args)
+ {
+ AutoRegisterThread raii("WASAPI device notification thread");
+ XASSERT(args);
+ auto mdn = static_cast<monitor_device_notifications *>(args);
+ mdn->notification_thread_loop();
+ SetEvent(mdn->shutdown_complete);
+ return 0;
+ }
+
+ void notification_thread_loop()
+ {
+ struct auto_com {
+ auto_com()
+ {
+ HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+ XASSERT(SUCCEEDED(hr));
+ }
+ ~auto_com() { CoUninitialize(); }
+ } com;
+
+ HANDLE wait_array[3] = {
+ input_changed,
+ output_changed,
+ begin_shutdown,
+ };
+
+ while (true) {
+ Sleep(200);
+
+ DWORD wait_result = WaitForMultipleObjects(ARRAY_LENGTH(wait_array),
+ wait_array, FALSE, INFINITE);
+ if (wait_result == WAIT_OBJECT_0) { // input changed
+ cubeb_context->input_collection_changed_callback(
+ cubeb_context, cubeb_context->input_collection_changed_user_ptr);
+ } else if (wait_result == WAIT_OBJECT_0 + 1) { // output changed
+ cubeb_context->output_collection_changed_callback(
+ cubeb_context, cubeb_context->output_collection_changed_user_ptr);
+ } else if (wait_result == WAIT_OBJECT_0 + 2) { // shutdown
+ break;
+ } else {
+ LOG("Unexpected result %lu", wait_result);
+ }
+ } // loop
+ }
+
+ void create_thread()
+ {
+ output_changed = CreateEvent(nullptr, 0, 0, nullptr);
+ if (!output_changed) {
+ LOG("Failed to create output changed event.");
+ return;
+ }
+
+ input_changed = CreateEvent(nullptr, 0, 0, nullptr);
+ if (!input_changed) {
+ LOG("Failed to create input changed event.");
+ return;
+ }
+
+ begin_shutdown = CreateEvent(nullptr, 0, 0, nullptr);
+ if (!begin_shutdown) {
+ LOG("Failed to create begin_shutdown event.");
+ return;
+ }
+
+ shutdown_complete = CreateEvent(nullptr, 0, 0, nullptr);
+ if (!shutdown_complete) {
+ LOG("Failed to create shutdown_complete event.");
+ return;
+ }
+
+ thread = (HANDLE)_beginthreadex(nullptr, 256 * 1024, thread_proc, this,
+ STACK_SIZE_PARAM_IS_A_RESERVATION, nullptr);
+ if (!thread) {
+ LOG("Failed to create thread.");
+ return;
+ }
+ }
+
+ HANDLE thread = INVALID_HANDLE_VALUE;
+ HANDLE output_changed = INVALID_HANDLE_VALUE;
+ HANDLE input_changed = INVALID_HANDLE_VALUE;
+ HANDLE begin_shutdown = INVALID_HANDLE_VALUE;
+ HANDLE shutdown_complete = INVALID_HANDLE_VALUE;
+
+ cubeb * cubeb_context = nullptr;
+};
+
+class wasapi_collection_notification_client : public IMMNotificationClient {
+public:
+ /* The implementation of MSCOM was copied from MSDN. */
+ ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&ref_count); }
+
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ ULONG ulRef = InterlockedDecrement(&ref_count);
+ if (0 == ulRef) {
+ delete this;
+ }
+ return ulRef;
+ }
+
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID ** ppvInterface)
+ {
+ if (__uuidof(IUnknown) == riid) {
+ AddRef();
+ *ppvInterface = (IUnknown *)this;
+ } else if (__uuidof(IMMNotificationClient) == riid) {
+ AddRef();
+ *ppvInterface = (IMMNotificationClient *)this;
+ } else {
+ *ppvInterface = NULL;
+ return E_NOINTERFACE;
+ }
+ return S_OK;
+ }
+
+ wasapi_collection_notification_client(cubeb * context)
+ : ref_count(1), cubeb_context(context), monitor_notifications(context)
+ {
+ XASSERT(cubeb_context);
+ }
+
+ virtual ~wasapi_collection_notification_client() {}
+
+ HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role,
+ LPCWSTR device_id)
+ {
+ LOG("collection: Audio device default changed, id = %S.", device_id);
+ return S_OK;
+ }
+
+ /* The remaining methods are not implemented, they simply log when called (if
+ log is enabled), for debugging. */
+ HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id)
+ {
+ LOG("collection: Audio device added.");
+ return S_OK;
+ };
+
+ HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device_id)
+ {
+ LOG("collection: Audio device removed.");
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR device_id,
+ DWORD new_state)
+ {
+ XASSERT(cubeb_context->output_collection_changed_callback ||
+ cubeb_context->input_collection_changed_callback);
+ LOG("collection: Audio device state changed, id = %S, state = %lu.",
+ device_id, new_state);
+ EDataFlow flow;
+ HRESULT hr = GetDataFlow(device_id, &flow);
+ if (FAILED(hr)) {
+ return hr;
+ }
+ monitor_notifications.notify(flow);
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR device_id,
+ const PROPERTYKEY key)
+ {
+ // Audio device property value changed.
+ return S_OK;
+ }
+
+private:
+ HRESULT GetDataFlow(LPCWSTR device_id, EDataFlow * flow)
+ {
+ com_ptr<IMMDevice> device;
+ com_ptr<IMMEndpoint> endpoint;
+
+ HRESULT hr = cubeb_context->device_collection_enumerator->GetDevice(
+ device_id, device.receive());
+ if (FAILED(hr)) {
+ LOG("collection: Could not get device: %lx", hr);
+ return hr;
+ }
+
+ hr = device->QueryInterface(IID_PPV_ARGS(endpoint.receive()));
+ if (FAILED(hr)) {
+ LOG("collection: Could not get endpoint: %lx", hr);
+ return hr;
+ }
+
+ return endpoint->GetDataFlow(flow);
+ }
+
+ /* refcount for this instance, necessary to implement MSCOM semantics. */
+ LONG ref_count;
+
+ cubeb * cubeb_context = nullptr;
+ monitor_device_notifications monitor_notifications;
+};
+
+class wasapi_endpoint_notification_client : public IMMNotificationClient {
+public:
+ /* The implementation of MSCOM was copied from MSDN. */
+ ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&ref_count); }
+
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ ULONG ulRef = InterlockedDecrement(&ref_count);
+ if (0 == ulRef) {
+ delete this;
+ }
+ return ulRef;
+ }
+
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID ** ppvInterface)
+ {
+ if (__uuidof(IUnknown) == riid) {
+ AddRef();
+ *ppvInterface = (IUnknown *)this;
+ } else if (__uuidof(IMMNotificationClient) == riid) {
+ AddRef();
+ *ppvInterface = (IMMNotificationClient *)this;
+ } else {
+ *ppvInterface = NULL;
+ return E_NOINTERFACE;
+ }
+ return S_OK;
+ }
+
+ wasapi_endpoint_notification_client(HANDLE event, ERole role)
+ : ref_count(1), reconfigure_event(event), role(role),
+ last_device_change(timeGetTime())
+ {
+ }
+
+ virtual ~wasapi_endpoint_notification_client() {}
+
+ HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role,
+ LPCWSTR device_id)
+ {
+ LOG("endpoint: Audio device default changed flow=%d role=%d "
+ "new_device_id=%ws.",
+ flow, role, device_id);
+
+ /* we only support a single stream type for now. */
+ if (flow != eRender || role != this->role) {
+ return S_OK;
+ }
+
+ DWORD last_change_ms = timeGetTime() - last_device_change;
+ bool same_device = default_device_id && device_id &&
+ wcscmp(default_device_id.get(), device_id) == 0;
+ LOG("endpoint: Audio device default changed last_change=%u same_device=%d",
+ last_change_ms, same_device);
+ if (last_change_ms > DEVICE_CHANGE_DEBOUNCE_MS || !same_device) {
+ if (device_id) {
+ default_device_id.reset(_wcsdup(device_id));
+ } else {
+ default_device_id.reset();
+ }
+ BOOL ok = SetEvent(reconfigure_event);
+ LOG("endpoint: Audio device default changed: trigger reconfig");
+ if (!ok) {
+ LOG("endpoint: SetEvent on reconfigure_event failed: %lx",
+ GetLastError());
+ }
+ }
+
+ return S_OK;
+ }
+
+ /* The remaining methods are not implemented, they simply log when called (if
+ log is enabled), for debugging. */
+ HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id)
+ {
+ LOG("endpoint: Audio device added.");
+ return S_OK;
+ };
+
+ HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device_id)
+ {
+ LOG("endpoint: Audio device removed.");
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR device_id,
+ DWORD new_state)
+ {
+ LOG("endpoint: Audio device state changed.");
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR device_id,
+ const PROPERTYKEY key)
+ {
+ // Audio device property value changed.
+ return S_OK;
+ }
+
+private:
+ /* refcount for this instance, necessary to implement MSCOM semantics. */
+ LONG ref_count;
+ HANDLE reconfigure_event;
+ ERole role;
+ std::unique_ptr<const wchar_t[]> default_device_id;
+ DWORD last_device_change;
+};
+
+namespace {
+
+long
+wasapi_data_callback(cubeb_stream * stm, void * user_ptr,
+ void const * input_buffer, void * output_buffer,
+ long nframes)
+{
+ return stm->data_callback(stm, user_ptr, input_buffer, output_buffer,
+ nframes);
+}
+
+void
+wasapi_state_callback(cubeb_stream * stm, void * user_ptr, cubeb_state state)
+{
+ return stm->state_callback(stm, user_ptr, state);
+}
+
+char const *
+intern_device_id(cubeb * ctx, wchar_t const * id)
+{
+ XASSERT(id);
+
+ auto_lock lock(ctx->lock);
+
+ char const * tmp = wstr_to_utf8(id);
+ if (!tmp) {
+ return nullptr;
+ }
+
+ char const * interned = cubeb_strings_intern(ctx->device_ids, tmp);
+
+ free((void *)tmp);
+
+ return interned;
+}
+
+bool
+has_input(cubeb_stream * stm)
+{
+ return stm->input_stream_params.rate != 0;
+}
+
+bool
+has_output(cubeb_stream * stm)
+{
+ return stm->output_stream_params.rate != 0;
+}
+
+double
+stream_to_mix_samplerate_ratio(cubeb_stream_params & stream,
+ cubeb_stream_params & mixer)
+{
+ return double(stream.rate) / mixer.rate;
+}
+
+/* Convert the channel layout into the corresponding KSAUDIO_CHANNEL_CONFIG.
+ See more:
+ https://msdn.microsoft.com/en-us/library/windows/hardware/ff537083(v=vs.85).aspx
+ */
+
+cubeb_channel_layout
+mask_to_channel_layout(WAVEFORMATEX const * fmt)
+{
+ cubeb_channel_layout mask = 0;
+
+ if (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
+ WAVEFORMATEXTENSIBLE const * ext =
+ reinterpret_cast<WAVEFORMATEXTENSIBLE const *>(fmt);
+ mask = ext->dwChannelMask;
+ } else if (fmt->wFormatTag == WAVE_FORMAT_PCM ||
+ fmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
+ if (fmt->nChannels == 1) {
+ mask = CHANNEL_FRONT_CENTER;
+ } else if (fmt->nChannels == 2) {
+ mask = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT;
+ }
+ }
+ return mask;
+}
+
+uint32_t
+get_rate(cubeb_stream * stm)
+{
+ return has_input(stm) ? stm->input_stream_params.rate
+ : stm->output_stream_params.rate;
+}
+
+uint32_t
+hns_to_frames(uint32_t rate, REFERENCE_TIME hns)
+{
+ return std::ceil((hns - 1) / 10000000.0 * rate);
+}
+
+uint32_t
+hns_to_frames(cubeb_stream * stm, REFERENCE_TIME hns)
+{
+ return hns_to_frames(get_rate(stm), hns);
+}
+
+REFERENCE_TIME
+frames_to_hns(uint32_t rate, uint32_t frames)
+{
+ return std::ceil(frames * 10000000.0 / rate);
+}
+
+/* This returns the size of a frame in the stream, before the eventual upmix
+ occurs. */
+static size_t
+frames_to_bytes_before_mix(cubeb_stream * stm, size_t frames)
+{
+ // This is called only when we has a output client.
+ XASSERT(has_output(stm));
+ return stm->output_stream_params.channels * stm->bytes_per_sample * frames;
+}
+
+/* This function handles the processing of the input and output audio,
+ * converting it to rate and channel layout specified at initialization.
+ * It then calls the data callback, via the resampler. */
+long
+refill(cubeb_stream * stm, void * input_buffer, long input_frames_count,
+ void * output_buffer, long output_frames_needed)
+{
+ XASSERT(!stm->draining);
+ /* If we need to upmix after resampling, resample into the mix buffer to
+ avoid a copy. Avoid exposing output if it is a dummy stream. */
+ void * dest = nullptr;
+ if (has_output(stm) && !stm->has_dummy_output) {
+ if (stm->output_mixer) {
+ dest = stm->mix_buffer.data();
+ } else {
+ dest = output_buffer;
+ }
+ }
+
+ long out_frames =
+ cubeb_resampler_fill(stm->resampler.get(), input_buffer,
+ &input_frames_count, dest, output_frames_needed);
+ if (out_frames < 0) {
+ ALOGV("Callback refill error: %d", out_frames);
+ wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ return out_frames;
+ }
+
+ float volume = 1.0;
+ {
+ auto_lock lock(stm->stream_reset_lock);
+ stm->frames_written += out_frames;
+ volume = stm->volume;
+ }
+
+ /* Go in draining mode if we got fewer frames than requested. If the stream
+ has no output we still expect the callback to return number of frames read
+ from input, otherwise we stop. */
+ if ((out_frames < output_frames_needed) ||
+ (!has_output(stm) && out_frames < input_frames_count)) {
+ LOG("start draining.");
+ stm->draining = true;
+ }
+
+ /* If this is not true, there will be glitches.
+ It is alright to have produced less frames if we are draining, though. */
+ XASSERT(out_frames == output_frames_needed || stm->draining ||
+ !has_output(stm) || stm->has_dummy_output);
+
+#ifndef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
+ if (has_output(stm) && !stm->has_dummy_output && volume != 1.0) {
+ // Adjust the output volume.
+ // Note: This could be integrated with the remixing below.
+ long out_samples = out_frames * stm->output_stream_params.channels;
+ if (volume == 0.0) {
+ memset(dest, 0, out_samples * stm->bytes_per_sample);
+ } else {
+ switch (stm->output_stream_params.format) {
+ case CUBEB_SAMPLE_FLOAT32NE: {
+ float * buf = static_cast<float *>(dest);
+ for (long i = 0; i < out_samples; ++i) {
+ buf[i] *= volume;
+ }
+ break;
+ }
+ case CUBEB_SAMPLE_S16NE: {
+ short * buf = static_cast<short *>(dest);
+ for (long i = 0; i < out_samples; ++i) {
+ buf[i] = static_cast<short>(static_cast<float>(buf[i]) * volume);
+ }
+ break;
+ }
+ default:
+ XASSERT(false);
+ }
+ }
+ }
+#endif
+
+ // We don't bother mixing dummy output as it will be silenced, otherwise mix
+ // output if needed
+ if (!stm->has_dummy_output && has_output(stm) && stm->output_mixer) {
+ XASSERT(dest == stm->mix_buffer.data());
+ size_t dest_size =
+ out_frames * stm->output_stream_params.channels * stm->bytes_per_sample;
+ XASSERT(dest_size <= stm->mix_buffer.size());
+ size_t output_buffer_size =
+ out_frames * stm->output_mix_params.channels * stm->bytes_per_sample;
+ int ret = cubeb_mixer_mix(stm->output_mixer.get(), out_frames, dest,
+ dest_size, output_buffer, output_buffer_size);
+ if (ret < 0) {
+ LOG("Error remixing content (%d)", ret);
+ }
+ }
+
+ return out_frames;
+}
+
+bool
+trigger_async_reconfigure(cubeb_stream * stm)
+{
+ XASSERT(stm && stm->reconfigure_event);
+ LOG("Try reconfiguring the stream");
+ BOOL ok = SetEvent(stm->reconfigure_event);
+ if (!ok) {
+ LOG("SetEvent on reconfigure_event failed: %lx", GetLastError());
+ }
+ return static_cast<bool>(ok);
+}
+
+/* This helper grabs all the frames available from a capture client, put them in
+ * the linear_input_buffer. This helper does not work with exclusive mode
+ * streams. */
+bool
+get_input_buffer(cubeb_stream * stm)
+{
+ XASSERT(has_input(stm));
+
+ HRESULT hr;
+ BYTE * input_packet = NULL;
+ DWORD flags;
+ UINT64 dev_pos;
+ UINT64 pc_position;
+ UINT32 next;
+ /* Get input packets until we have captured enough frames, and put them in a
+ * contiguous buffer. */
+ uint32_t offset = 0;
+ // If the input stream is event driven we should only ever expect to read a
+ // single packet each time. However, if we're pulling from the stream we may
+ // need to grab multiple packets worth of frames that have accumulated (so
+ // need a loop).
+ for (hr = stm->capture_client->GetNextPacketSize(&next); next > 0;
+ hr = stm->capture_client->GetNextPacketSize(&next)) {
+ if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
+ // Application can recover from this error. More info
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd316605(v=vs.85).aspx
+ LOG("Input device invalidated error");
+ // No need to reset device if user asks to use particular device, or
+ // switching is disabled.
+ if (stm->input_device_id ||
+ (stm->input_stream_params.prefs &
+ CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING) ||
+ !trigger_async_reconfigure(stm)) {
+ wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ return false;
+ }
+ return true;
+ }
+
+ if (FAILED(hr)) {
+ LOG("cannot get next packet size: %lx", hr);
+ return false;
+ }
+
+ UINT32 frames;
+ hr = stm->capture_client->GetBuffer(&input_packet, &frames, &flags,
+ &dev_pos, &pc_position);
+
+ if (FAILED(hr)) {
+ LOG("GetBuffer failed for capture: %lx", hr);
+ return false;
+ }
+ XASSERT(frames == next);
+
+ if (stm->context->performance_counter_frequency) {
+ LARGE_INTEGER now;
+ UINT64 now_hns;
+ // See
+ // https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudiocaptureclient-getbuffer,
+ // section "Remarks".
+ QueryPerformanceCounter(&now);
+ now_hns =
+ 10000000 * now.QuadPart / stm->context->performance_counter_frequency;
+ if (now_hns >= pc_position) {
+ stm->input_latency_hns = now_hns - pc_position;
+ }
+ }
+
+ stm->total_input_frames += frames;
+
+ UINT32 input_stream_samples = frames * stm->input_stream_params.channels;
+ // We do not explicitly handle the AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY
+ // flag. There a two primary (non exhaustive) scenarios we anticipate this
+ // flag being set in:
+ // - The first GetBuffer after Start has this flag undefined. In this
+ // case the flag may be set but is meaningless and can be ignored.
+ // - If a glitch is introduced into the input. This should not happen
+ // for event based inputs, and should be mitigated by using a dummy
+ // stream to drive input in the case of input only loopback. Without
+ // a dummy output, input only loopback would glitch on silence. However,
+ // the dummy input should push silence to the loopback and prevent
+ // discontinuities. See
+ // https://blogs.msdn.microsoft.com/matthew_van_eerde/2008/12/16/sample-wasapi-loopback-capture-record-what-you-hear/
+ // As the first scenario can be ignored, and we anticipate the second
+ // scenario is mitigated, we ignore the flag.
+ // For more info:
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd370859(v=vs.85).aspx,
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd371458(v=vs.85).aspx
+ if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
+ LOG("insert silence: ps=%u", frames);
+ stm->linear_input_buffer->push_silence(input_stream_samples);
+ } else {
+ if (stm->input_mixer) {
+ bool ok = stm->linear_input_buffer->reserve(
+ stm->linear_input_buffer->length() + input_stream_samples);
+ XASSERT(ok);
+ size_t input_packet_size =
+ frames * stm->input_mix_params.channels *
+ cubeb_sample_size(stm->input_mix_params.format);
+ size_t linear_input_buffer_size =
+ input_stream_samples *
+ cubeb_sample_size(stm->input_stream_params.format);
+ cubeb_mixer_mix(stm->input_mixer.get(), frames, input_packet,
+ input_packet_size, stm->linear_input_buffer->end(),
+ linear_input_buffer_size);
+ stm->linear_input_buffer->set_length(
+ stm->linear_input_buffer->length() + input_stream_samples);
+ } else {
+ stm->linear_input_buffer->push(input_packet, input_stream_samples);
+ }
+ }
+ hr = stm->capture_client->ReleaseBuffer(frames);
+ if (FAILED(hr)) {
+ LOG("FAILED to release intput buffer");
+ return false;
+ }
+ offset += input_stream_samples;
+ }
+
+ ALOGV("get_input_buffer: got %d frames", offset);
+
+ XASSERT(stm->linear_input_buffer->length() >= offset);
+
+ return true;
+}
+
+/* Get an output buffer from the render_client. It has to be released before
+ * exiting the callback. */
+bool
+get_output_buffer(cubeb_stream * stm, void *& buffer, size_t & frame_count)
+{
+ UINT32 padding_out;
+ HRESULT hr;
+
+ XASSERT(has_output(stm));
+
+ hr = stm->output_client->GetCurrentPadding(&padding_out);
+ if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
+ // Application can recover from this error. More info
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd316605(v=vs.85).aspx
+ LOG("Output device invalidated error");
+ // No need to reset device if user asks to use particular device, or
+ // switching is disabled.
+ if (stm->output_device_id ||
+ (stm->output_stream_params.prefs &
+ CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING) ||
+ !trigger_async_reconfigure(stm)) {
+ wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ return false;
+ }
+ return true;
+ }
+
+ if (FAILED(hr)) {
+ LOG("Failed to get padding: %lx", hr);
+ return false;
+ }
+
+ XASSERT(padding_out <= stm->output_buffer_frame_count);
+
+ if (stm->draining) {
+ if (padding_out == 0) {
+ LOG("Draining finished.");
+ wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+ return false;
+ }
+ LOG("Draining.");
+ return true;
+ }
+
+ frame_count = stm->output_buffer_frame_count - padding_out;
+ BYTE * output_buffer;
+
+ hr = stm->render_client->GetBuffer(frame_count, &output_buffer);
+ if (FAILED(hr)) {
+ LOG("cannot get render buffer");
+ return false;
+ }
+
+ buffer = output_buffer;
+
+ return true;
+}
+
+/**
+ * This function gets input data from a input device, and pass it along with an
+ * output buffer to the resamplers. */
+bool
+refill_callback_duplex(cubeb_stream * stm)
+{
+ HRESULT hr;
+ void * output_buffer = nullptr;
+ size_t output_frames = 0;
+ size_t input_frames;
+ bool rv;
+
+ XASSERT(has_input(stm) && has_output(stm));
+
+ if (stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ HRESULT rv = get_input_buffer(stm);
+ if (FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ input_frames =
+ stm->linear_input_buffer->length() / stm->input_stream_params.channels;
+
+ rv = get_output_buffer(stm, output_buffer, output_frames);
+ if (!rv) {
+ hr = stm->render_client->ReleaseBuffer(output_frames, 0);
+ return rv;
+ }
+
+ /* This can only happen when debugging, and having breakpoints set in the
+ * callback in a way that it makes the stream underrun. */
+ if (output_frames == 0) {
+ return true;
+ }
+
+ /* Wait for draining is not important on duplex. */
+ if (stm->draining) {
+ return false;
+ }
+
+ stm->total_output_frames += output_frames;
+
+ ALOGV("in: %zu, out: %zu, missing: %ld, ratio: %f", stm->total_input_frames,
+ stm->total_output_frames,
+ static_cast<long>(stm->total_output_frames) - stm->total_input_frames,
+ static_cast<float>(stm->total_output_frames) / stm->total_input_frames);
+
+ long got;
+ if (stm->has_dummy_output) {
+ ALOGV(
+ "Duplex callback (dummy output): input frames: %Iu, output frames: %Iu",
+ input_frames, output_frames);
+
+ // We don't want to expose the dummy output to the callback so don't pass
+ // the output buffer (it will be released later with silence in it)
+ got =
+ refill(stm, stm->linear_input_buffer->data(), input_frames, nullptr, 0);
+
+ } else {
+ ALOGV("Duplex callback: input frames: %Iu, output frames: %Iu",
+ input_frames, output_frames);
+
+ got = refill(stm, stm->linear_input_buffer->data(), input_frames,
+ output_buffer, output_frames);
+ }
+
+ stm->linear_input_buffer->clear();
+
+ if (stm->has_dummy_output) {
+ // If output is a dummy output, make sure it's silent
+ hr = stm->render_client->ReleaseBuffer(output_frames,
+ AUDCLNT_BUFFERFLAGS_SILENT);
+ } else {
+ hr = stm->render_client->ReleaseBuffer(output_frames, 0);
+ }
+ if (FAILED(hr)) {
+ LOG("failed to release buffer: %lx", hr);
+ return false;
+ }
+ if (got < 0) {
+ return false;
+ }
+ return true;
+}
+
+bool
+refill_callback_input(cubeb_stream * stm)
+{
+ bool rv;
+ size_t input_frames;
+
+ XASSERT(has_input(stm) && !has_output(stm));
+
+ rv = get_input_buffer(stm);
+ if (!rv) {
+ return rv;
+ }
+
+ input_frames =
+ stm->linear_input_buffer->length() / stm->input_stream_params.channels;
+ if (!input_frames) {
+ return true;
+ }
+
+ ALOGV("Input callback: input frames: %Iu", input_frames);
+
+ long read =
+ refill(stm, stm->linear_input_buffer->data(), input_frames, nullptr, 0);
+ if (read < 0) {
+ return false;
+ }
+
+ stm->linear_input_buffer->clear();
+
+ return !stm->draining;
+}
+
+bool
+refill_callback_output(cubeb_stream * stm)
+{
+ bool rv;
+ HRESULT hr;
+ void * output_buffer = nullptr;
+ size_t output_frames = 0;
+
+ XASSERT(!has_input(stm) && has_output(stm));
+
+ rv = get_output_buffer(stm, output_buffer, output_frames);
+ if (!rv) {
+ return rv;
+ }
+
+ if (stm->draining || output_frames == 0) {
+ return true;
+ }
+
+ long got = refill(stm, nullptr, 0, output_buffer, output_frames);
+
+ ALOGV("Output callback: output frames requested: %Iu, got %ld", output_frames,
+ got);
+ if (got < 0) {
+ return false;
+ }
+ XASSERT(size_t(got) == output_frames || stm->draining);
+
+ hr = stm->render_client->ReleaseBuffer(got, 0);
+ if (FAILED(hr)) {
+ LOG("failed to release buffer: %lx", hr);
+ return false;
+ }
+
+ return size_t(got) == output_frames || stm->draining;
+}
+
+void
+wasapi_stream_destroy(cubeb_stream * stm);
+
+static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
+{
+ AutoRegisterThread raii("cubeb rendering thread");
+ cubeb_stream * stm = static_cast<cubeb_stream *>(stream);
+
+ auto_stream_ref stream_ref(stm);
+ struct auto_com {
+ auto_com()
+ {
+ HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+ XASSERT(SUCCEEDED(hr));
+ }
+ ~auto_com() { CoUninitialize(); }
+ } com;
+
+ bool is_playing = true;
+ HANDLE wait_array[4] = {stm->shutdown_event, stm->reconfigure_event,
+ stm->refill_event, stm->input_available_event};
+ HANDLE mmcss_handle = NULL;
+ HRESULT hr = 0;
+ DWORD mmcss_task_index = 0;
+
+ // Signal wasapi_stream_start that we've initialized COM and incremented
+ // the stream's ref_count.
+ BOOL ok = SetEvent(stm->thread_ready_event);
+ if (!ok) {
+ LOG("thread_ready SetEvent failed: %lx", GetLastError());
+ return 0;
+ }
+
+ /* We could consider using "Pro Audio" here for WebAudio and
+ maybe WebRTC. */
+ mmcss_handle = AvSetMmThreadCharacteristicsA("Audio", &mmcss_task_index);
+ if (!mmcss_handle) {
+ /* This is not fatal, but we might glitch under heavy load. */
+ LOG("Unable to use mmcss to bump the render thread priority: %lx",
+ GetLastError());
+ }
+
+ while (is_playing) {
+ DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array),
+ wait_array, FALSE, INFINITE);
+ switch (waitResult) {
+ case WAIT_OBJECT_0: { /* shutdown */
+ is_playing = false;
+ /* We don't check if the drain is actually finished here, we just want to
+ shutdown. */
+ if (stm->draining) {
+ wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+ }
+ continue;
+ }
+ case WAIT_OBJECT_0 + 1: { /* reconfigure */
+ auto_lock lock(stm->stream_reset_lock);
+ if (!stm->active) {
+ /* Avoid reconfiguring, stream start will handle it. */
+ LOG("Stream is not active, ignoring reconfigure.");
+ continue;
+ }
+ XASSERT(stm->output_client || stm->input_client);
+ LOG("Reconfiguring the stream");
+ /* Close the stream */
+ bool was_running = false;
+ if (stm->output_client) {
+ was_running = stm->output_client->Stop() == S_OK;
+ LOG("Output stopped.");
+ }
+ if (stm->input_client) {
+ was_running = stm->input_client->Stop() == S_OK;
+ LOG("Input stopped.");
+ }
+ close_wasapi_stream(stm);
+ LOG("Stream closed.");
+ /* Reopen a stream and start it immediately. This will automatically
+ pick the new default device for this role. */
+ int r = setup_wasapi_stream(stm);
+ if (r != CUBEB_OK) {
+ LOG("Error setting up the stream during reconfigure.");
+ /* Don't destroy the stream here, since we expect the caller to do
+ so after the error has propagated via the state callback. */
+ is_playing = false;
+ hr = E_FAIL;
+ continue;
+ }
+ LOG("Stream setup successfuly.");
+ XASSERT(stm->output_client || stm->input_client);
+ if (was_running && stm->output_client) {
+ hr = stm->output_client->Start();
+ if (FAILED(hr)) {
+ LOG("Error starting output after reconfigure, error: %lx", hr);
+ is_playing = false;
+ continue;
+ }
+ LOG("Output started after reconfigure.");
+ }
+ if (was_running && stm->input_client) {
+ hr = stm->input_client->Start();
+ if (FAILED(hr)) {
+ LOG("Error starting input after reconfiguring, error: %lx", hr);
+ is_playing = false;
+ continue;
+ }
+ LOG("Input started after reconfigure.");
+ }
+ break;
+ }
+ case WAIT_OBJECT_0 + 2: /* refill */
+ XASSERT((has_input(stm) && has_output(stm)) ||
+ (!has_input(stm) && has_output(stm)));
+ is_playing = stm->refill_callback(stm);
+ break;
+ case WAIT_OBJECT_0 + 3: { /* input available */
+ HRESULT rv = get_input_buffer(stm);
+ if (FAILED(rv)) {
+ is_playing = false;
+ continue;
+ }
+
+ if (!has_output(stm)) {
+ is_playing = stm->refill_callback(stm);
+ }
+
+ break;
+ }
+ default:
+ LOG("case %lu not handled in render loop.", waitResult);
+ XASSERT(false);
+ }
+ }
+
+ // Stop audio clients since this thread will no longer service
+ // the events.
+ if (stm->output_client) {
+ stm->output_client->Stop();
+ }
+ if (stm->input_client) {
+ stm->input_client->Stop();
+ }
+
+ if (mmcss_handle) {
+ AvRevertMmThreadCharacteristics(mmcss_handle);
+ }
+
+ if (FAILED(hr)) {
+ wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ }
+
+ return 0;
+}
+
+void
+wasapi_destroy(cubeb * context);
+
+HRESULT
+register_notification_client(cubeb_stream * stm)
+{
+ XASSERT(stm->device_enumerator && !stm->notification_client);
+
+ stm->notification_client.reset(new wasapi_endpoint_notification_client(
+ stm->reconfigure_event, stm->role));
+
+ HRESULT hr = stm->device_enumerator->RegisterEndpointNotificationCallback(
+ stm->notification_client.get());
+ if (FAILED(hr)) {
+ LOG("Could not register endpoint notification callback: %lx", hr);
+ stm->notification_client = nullptr;
+ }
+
+ return hr;
+}
+
+HRESULT
+unregister_notification_client(cubeb_stream * stm)
+{
+ XASSERT(stm->device_enumerator && stm->notification_client);
+
+ HRESULT hr = stm->device_enumerator->UnregisterEndpointNotificationCallback(
+ stm->notification_client.get());
+ if (FAILED(hr)) {
+ // We can't really do anything here, we'll probably leak the
+ // notification client.
+ return S_OK;
+ }
+
+ stm->notification_client = nullptr;
+
+ return S_OK;
+}
+
+HRESULT
+get_endpoint(com_ptr<IMMDevice> & device, LPCWSTR devid)
+{
+ com_ptr<IMMDeviceEnumerator> enumerator;
+ HRESULT hr =
+ CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(enumerator.receive()));
+ if (FAILED(hr)) {
+ LOG("Could not get device enumerator: %lx", hr);
+ return hr;
+ }
+
+ hr = enumerator->GetDevice(devid, device.receive());
+ if (FAILED(hr)) {
+ LOG("Could not get device: %lx", hr);
+ return hr;
+ }
+
+ return S_OK;
+}
+
+HRESULT
+register_collection_notification_client(cubeb * context)
+{
+ context->lock.assert_current_thread_owns();
+ XASSERT(!context->device_collection_enumerator &&
+ !context->collection_notification_client);
+ HRESULT hr = CoCreateInstance(
+ __uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(context->device_collection_enumerator.receive()));
+ if (FAILED(hr)) {
+ LOG("Could not get device enumerator: %lx", hr);
+ return hr;
+ }
+
+ context->collection_notification_client.reset(
+ new wasapi_collection_notification_client(context));
+
+ hr = context->device_collection_enumerator
+ ->RegisterEndpointNotificationCallback(
+ context->collection_notification_client.get());
+ if (FAILED(hr)) {
+ LOG("Could not register endpoint notification callback: %lx", hr);
+ context->collection_notification_client.reset();
+ context->device_collection_enumerator.reset();
+ }
+
+ return hr;
+}
+
+HRESULT
+unregister_collection_notification_client(cubeb * context)
+{
+ context->lock.assert_current_thread_owns();
+ XASSERT(context->device_collection_enumerator &&
+ context->collection_notification_client);
+ HRESULT hr = context->device_collection_enumerator
+ ->UnregisterEndpointNotificationCallback(
+ context->collection_notification_client.get());
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ context->collection_notification_client = nullptr;
+ context->device_collection_enumerator = nullptr;
+
+ return hr;
+}
+
+HRESULT
+get_default_endpoint(com_ptr<IMMDevice> & device, EDataFlow direction,
+ ERole role)
+{
+ com_ptr<IMMDeviceEnumerator> enumerator;
+ HRESULT hr =
+ CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(enumerator.receive()));
+ if (FAILED(hr)) {
+ LOG("Could not get device enumerator: %lx", hr);
+ return hr;
+ }
+ hr = enumerator->GetDefaultAudioEndpoint(direction, role, device.receive());
+ if (FAILED(hr)) {
+ LOG("Could not get default audio endpoint: %lx", hr);
+ return hr;
+ }
+
+ return ERROR_SUCCESS;
+}
+
+double
+current_stream_delay(cubeb_stream * stm)
+{
+ stm->stream_reset_lock.assert_current_thread_owns();
+
+ /* If the default audio endpoint went away during playback and we weren't
+ able to configure a new one, it's possible the caller may call this
+ before the error callback has propogated back. */
+ if (!stm->audio_clock) {
+ return 0;
+ }
+
+ UINT64 freq;
+ HRESULT hr = stm->audio_clock->GetFrequency(&freq);
+ if (FAILED(hr)) {
+ LOG("GetFrequency failed: %lx", hr);
+ return 0;
+ }
+
+ UINT64 pos;
+ hr = stm->audio_clock->GetPosition(&pos, NULL);
+ if (FAILED(hr)) {
+ LOG("GetPosition failed: %lx", hr);
+ return 0;
+ }
+
+ double cur_pos = static_cast<double>(pos) / freq;
+ double max_pos =
+ static_cast<double>(stm->frames_written) / stm->output_mix_params.rate;
+ double delay = std::max(max_pos - cur_pos, 0.0);
+
+ return delay;
+}
+
+#ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
+int
+stream_set_volume(cubeb_stream * stm, float volume)
+{
+ stm->stream_reset_lock.assert_current_thread_owns();
+
+ if (!stm->audio_stream_volume) {
+ return CUBEB_ERROR;
+ }
+
+ uint32_t channels;
+ HRESULT hr = stm->audio_stream_volume->GetChannelCount(&channels);
+ if (FAILED(hr)) {
+ LOG("could not get the channel count: %lx", hr);
+ return CUBEB_ERROR;
+ }
+
+ /* up to 9.1 for now */
+ if (channels > 10) {
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ float volumes[10];
+ for (uint32_t i = 0; i < channels; i++) {
+ volumes[i] = volume;
+ }
+
+ hr = stm->audio_stream_volume->SetAllVolumes(channels, volumes);
+ if (FAILED(hr)) {
+ LOG("could not set the channels volume: %lx", hr);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+#endif
+} // namespace
+
+extern "C" {
+int
+wasapi_init(cubeb ** context, char const * context_name)
+{
+ /* We don't use the device yet, but need to make sure we can initialize one
+ so that this backend is not incorrectly enabled on platforms that don't
+ support WASAPI. */
+ com_ptr<IMMDevice> device;
+ HRESULT hr = get_default_endpoint(device, eRender, eConsole);
+ if (FAILED(hr)) {
+ XASSERT(hr != CO_E_NOTINITIALIZED);
+ LOG("It wasn't able to find a default rendering device: %lx", hr);
+ hr = get_default_endpoint(device, eCapture, eConsole);
+ if (FAILED(hr)) {
+ LOG("It wasn't able to find a default capture device: %lx", hr);
+ return CUBEB_ERROR;
+ }
+ }
+
+ cubeb * ctx = new cubeb();
+
+ ctx->ops = &wasapi_ops;
+ auto_lock lock(ctx->lock);
+ if (cubeb_strings_init(&ctx->device_ids) != CUBEB_OK) {
+ delete ctx;
+ return CUBEB_ERROR;
+ }
+
+ LARGE_INTEGER frequency;
+ if (QueryPerformanceFrequency(&frequency)) {
+ ctx->performance_counter_frequency = frequency.QuadPart;
+ } else {
+ LOG("Failed getting performance counter frequency, latency reporting will "
+ "be inacurate");
+ ctx->performance_counter_frequency = 0;
+ }
+
+ *context = ctx;
+
+ return CUBEB_OK;
+}
+}
+
+namespace {
+enum ShutdownPhase { OnStop, OnDestroy };
+
+bool
+stop_and_join_render_thread(cubeb_stream * stm)
+{
+ LOG("%p: Stop and join render thread: %p", stm, stm->thread);
+ if (!stm->thread) {
+ return true;
+ }
+
+ BOOL ok = SetEvent(stm->shutdown_event);
+ if (!ok) {
+ LOG("stop_and_join_render_thread: SetEvent failed: %lx", GetLastError());
+ return false;
+ }
+
+ /* Wait five seconds for the rendering thread to return. It's supposed to
+ * check its event loop very often, five seconds is rather conservative.
+ * Note: 5*1s loop to work around timer sleep issues on pre-Windows 8. */
+ DWORD r;
+ for (int i = 0; i < 5; ++i) {
+ r = WaitForSingleObject(stm->thread, 1000);
+ if (r == WAIT_OBJECT_0) {
+ break;
+ }
+ }
+ if (r != WAIT_OBJECT_0) {
+ LOG("stop_and_join_render_thread: WaitForSingleObject on thread failed: "
+ "%lx, %lx",
+ r, GetLastError());
+ return false;
+ }
+
+ return true;
+}
+
+void
+wasapi_destroy(cubeb * context)
+{
+ {
+ auto_lock lock(context->lock);
+ XASSERT(!context->device_collection_enumerator &&
+ !context->collection_notification_client);
+
+ if (context->device_ids) {
+ cubeb_strings_destroy(context->device_ids);
+ }
+ }
+
+ delete context;
+}
+
+char const *
+wasapi_get_backend_id(cubeb * context)
+{
+ return "wasapi";
+}
+
+int
+wasapi_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
+{
+ XASSERT(ctx && max_channels);
+
+ com_ptr<IMMDevice> device;
+ HRESULT hr = get_default_endpoint(device, eRender, eConsole);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+
+ com_ptr<IAudioClient> client;
+ hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL,
+ client.receive_vpp());
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+
+ WAVEFORMATEX * tmp = nullptr;
+ hr = client->GetMixFormat(&tmp);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+ com_heap_ptr<WAVEFORMATEX> mix_format(tmp);
+
+ *max_channels = mix_format->nChannels;
+
+ return CUBEB_OK;
+}
+
+int
+wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params,
+ uint32_t * latency_frames)
+{
+ if (params.format != CUBEB_SAMPLE_FLOAT32NE &&
+ params.format != CUBEB_SAMPLE_S16NE) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ ERole role = pref_to_role(params.prefs);
+
+ com_ptr<IMMDevice> device;
+ HRESULT hr = get_default_endpoint(device, eRender, role);
+ if (FAILED(hr)) {
+ LOG("Could not get default endpoint: %lx", hr);
+ return CUBEB_ERROR;
+ }
+
+ com_ptr<IAudioClient> client;
+ hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL,
+ client.receive_vpp());
+ if (FAILED(hr)) {
+ LOG("Could not activate device for latency: %lx", hr);
+ return CUBEB_ERROR;
+ }
+
+ REFERENCE_TIME minimum_period;
+ REFERENCE_TIME default_period;
+ hr = client->GetDevicePeriod(&default_period, &minimum_period);
+ if (FAILED(hr)) {
+ LOG("Could not get device period: %lx", hr);
+ return CUBEB_ERROR;
+ }
+
+ LOG("default device period: %I64d, minimum device period: %I64d",
+ default_period, minimum_period);
+
+ /* If we're on Windows 10, we can use IAudioClient3 to get minimal latency.
+ Otherwise, according to the docs, the best latency we can achieve is by
+ synchronizing the stream and the engine.
+ http://msdn.microsoft.com/en-us/library/windows/desktop/dd370871%28v=vs.85%29.aspx
+ */
+
+ // #ifdef _WIN32_WINNT_WIN10
+#if 0
+ *latency_frames = hns_to_frames(params.rate, minimum_period);
+#else
+ *latency_frames = hns_to_frames(params.rate, default_period);
+#endif
+
+ LOG("Minimum latency in frames: %u", *latency_frames);
+
+ return CUBEB_OK;
+}
+
+int
+wasapi_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
+{
+ com_ptr<IMMDevice> device;
+ HRESULT hr = get_default_endpoint(device, eRender, eConsole);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+
+ com_ptr<IAudioClient> client;
+ hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL,
+ client.receive_vpp());
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+
+ WAVEFORMATEX * tmp = nullptr;
+ hr = client->GetMixFormat(&tmp);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+ com_heap_ptr<WAVEFORMATEX> mix_format(tmp);
+
+ *rate = mix_format->nSamplesPerSec;
+
+ LOG("Preferred sample rate for output: %u", *rate);
+
+ return CUBEB_OK;
+}
+
+static void
+waveformatex_update_derived_properties(WAVEFORMATEX * format)
+{
+ format->nBlockAlign = format->wBitsPerSample * format->nChannels / 8;
+ format->nAvgBytesPerSec = format->nSamplesPerSec * format->nBlockAlign;
+ if (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
+ WAVEFORMATEXTENSIBLE * format_pcm =
+ reinterpret_cast<WAVEFORMATEXTENSIBLE *>(format);
+ format_pcm->Samples.wValidBitsPerSample = format->wBitsPerSample;
+ }
+}
+
+/* Based on the mix format and the stream format, try to find a way to play
+ what the user requested. */
+static void
+handle_channel_layout(cubeb_stream * stm, EDataFlow direction,
+ com_heap_ptr<WAVEFORMATEX> & mix_format,
+ const cubeb_stream_params * stream_params)
+{
+ com_ptr<IAudioClient> & audio_client =
+ (direction == eRender) ? stm->output_client : stm->input_client;
+ XASSERT(audio_client);
+ /* The docs say that GetMixFormat is always of type WAVEFORMATEXTENSIBLE [1],
+ so the reinterpret_cast below should be safe. In practice, this is not
+ true, and we just want to bail out and let the rest of the code find a good
+ conversion path instead of trying to make WASAPI do it by itself.
+ [1]:
+ http://msdn.microsoft.com/en-us/library/windows/desktop/dd370811%28v=vs.85%29.aspx*/
+ if (mix_format->wFormatTag != WAVE_FORMAT_EXTENSIBLE) {
+ return;
+ }
+
+ WAVEFORMATEXTENSIBLE * format_pcm =
+ reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get());
+
+ /* Stash a copy of the original mix format in case we need to restore it
+ * later. */
+ WAVEFORMATEXTENSIBLE hw_mix_format = *format_pcm;
+
+ /* Get the channel mask by the channel layout.
+ If the layout is not supported, we will get a closest settings below. */
+ format_pcm->dwChannelMask = stream_params->layout;
+ mix_format->nChannels = stream_params->channels;
+ waveformatex_update_derived_properties(mix_format.get());
+
+ /* Check if wasapi will accept our channel layout request. */
+ WAVEFORMATEX * tmp = nullptr;
+ HRESULT hr = audio_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED,
+ mix_format.get(), &tmp);
+ com_heap_ptr<WAVEFORMATEX> closest(tmp);
+ if (hr == S_FALSE) {
+ /* Channel layout not supported, but WASAPI gives us a suggestion. Use it,
+ and handle the eventual upmix/downmix ourselves. Ignore the subformat of
+ the suggestion, since it seems to always be IEEE_FLOAT. */
+ LOG("Using WASAPI suggested format: channels: %d", closest->nChannels);
+ XASSERT(closest->wFormatTag == WAVE_FORMAT_EXTENSIBLE);
+ WAVEFORMATEXTENSIBLE * closest_pcm =
+ reinterpret_cast<WAVEFORMATEXTENSIBLE *>(closest.get());
+ format_pcm->dwChannelMask = closest_pcm->dwChannelMask;
+ mix_format->nChannels = closest->nChannels;
+ waveformatex_update_derived_properties(mix_format.get());
+ } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) {
+ /* Not supported, no suggestion. This should not happen, but it does in the
+ field with some sound cards. We restore the mix format, and let the rest
+ of the code figure out the right conversion path. */
+ XASSERT(mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE);
+ *reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get()) = hw_mix_format;
+ } else if (hr == S_OK) {
+ LOG("Requested format accepted by WASAPI.");
+ } else {
+ LOG("IsFormatSupported unhandled error: %lx", hr);
+ }
+}
+
+static int
+initialize_iaudioclient2(com_ptr<IAudioClient> & audio_client)
+{
+ com_ptr<IAudioClient2> audio_client2;
+ audio_client->QueryInterface<IAudioClient2>(audio_client2.receive());
+ if (!audio_client2) {
+ LOG("Could not get IAudioClient2 interface, not setting "
+ "AUDCLNT_STREAMOPTIONS_RAW.");
+ return CUBEB_OK;
+ }
+ AudioClientProperties properties = {0};
+ properties.cbSize = sizeof(AudioClientProperties);
+#ifndef __MINGW32__
+ properties.Options |= AUDCLNT_STREAMOPTIONS_RAW;
+#endif
+ HRESULT hr = audio_client2->SetClientProperties(&properties);
+ if (FAILED(hr)) {
+ LOG("IAudioClient2::SetClientProperties error: %lx", GetLastError());
+ return CUBEB_ERROR;
+ }
+ return CUBEB_OK;
+}
+
+#if 0
+bool
+initialize_iaudioclient3(com_ptr<IAudioClient> & audio_client,
+ cubeb_stream * stm,
+ const com_heap_ptr<WAVEFORMATEX> & mix_format,
+ DWORD flags, EDataFlow direction)
+{
+ com_ptr<IAudioClient3> audio_client3;
+ audio_client->QueryInterface<IAudioClient3>(audio_client3.receive());
+ if (!audio_client3) {
+ LOG("Could not get IAudioClient3 interface");
+ return false;
+ }
+
+ if (flags & AUDCLNT_STREAMFLAGS_LOOPBACK) {
+ // IAudioClient3 doesn't work with loopback streams, and will return error
+ // 88890021: AUDCLNT_E_INVALID_STREAM_FLAG
+ LOG("Audio stream is loopback, not using IAudioClient3");
+ return false;
+ }
+
+ // Some people have reported glitches with capture streams:
+ // http://blog.nirbheek.in/2018/03/low-latency-audio-on-windows-with.html
+ if (direction == eCapture) {
+ LOG("Audio stream is capture, not using IAudioClient3");
+ return false;
+ }
+
+ // Possibly initialize a shared-mode stream using IAudioClient3. Initializing
+ // a stream this way lets you request lower latencies, but also locks the
+ // global WASAPI engine at that latency.
+ // - If we request a shared-mode stream, streams created with IAudioClient
+ // will
+ // have their latency adjusted to match. When the shared-mode stream is
+ // closed, they'll go back to normal.
+ // - If there's already a shared-mode stream running, then we cannot request
+ // the engine change to a different latency - we have to match it.
+ // - It's antisocial to lock the WASAPI engine at its default latency. If we
+ // would do this, then stop and use IAudioClient instead.
+
+ HRESULT hr;
+ uint32_t default_period = 0, fundamental_period = 0, min_period = 0,
+ max_period = 0;
+ hr = audio_client3->GetSharedModeEnginePeriod(
+ mix_format.get(), &default_period, &fundamental_period, &min_period,
+ &max_period);
+ if (FAILED(hr)) {
+ LOG("Could not get shared mode engine period: error: %lx", hr);
+ return false;
+ }
+ uint32_t requested_latency = stm->latency;
+ if (requested_latency >= default_period) {
+ LOG("Requested latency %i greater than default latency %i, not using "
+ "IAudioClient3",
+ requested_latency, default_period);
+ return false;
+ }
+ LOG("Got shared mode engine period: default=%i fundamental=%i min=%i max=%i",
+ default_period, fundamental_period, min_period, max_period);
+ // Snap requested latency to a valid value
+ uint32_t old_requested_latency = requested_latency;
+ if (requested_latency < min_period) {
+ requested_latency = min_period;
+ }
+ requested_latency -= (requested_latency - min_period) % fundamental_period;
+ if (requested_latency != old_requested_latency) {
+ LOG("Requested latency %i was adjusted to %i", old_requested_latency,
+ requested_latency);
+ }
+
+ hr = audio_client3->InitializeSharedAudioStream(flags, requested_latency,
+ mix_format.get(), NULL);
+ if (SUCCEEDED(hr)) {
+ return true;
+ } else if (hr == AUDCLNT_E_ENGINE_PERIODICITY_LOCKED) {
+ LOG("Got AUDCLNT_E_ENGINE_PERIODICITY_LOCKED, adjusting latency request");
+ } else {
+ LOG("Could not initialize shared stream with IAudioClient3: error: %lx",
+ hr);
+ return false;
+ }
+
+ uint32_t current_period = 0;
+ WAVEFORMATEX * current_format = nullptr;
+ // We have to pass a valid WAVEFORMATEX** and not nullptr, otherwise
+ // GetCurrentSharedModeEnginePeriod will return E_POINTER
+ hr = audio_client3->GetCurrentSharedModeEnginePeriod(&current_format,
+ &current_period);
+ CoTaskMemFree(current_format);
+ if (FAILED(hr)) {
+ LOG("Could not get current shared mode engine period: error: %lx", hr);
+ return false;
+ }
+
+ if (current_period >= default_period) {
+ LOG("Current shared mode engine period %i too high, not using IAudioClient",
+ current_period);
+ return false;
+ }
+
+ hr = audio_client3->InitializeSharedAudioStream(flags, current_period,
+ mix_format.get(), NULL);
+ if (SUCCEEDED(hr)) {
+ LOG("Current shared mode engine period is %i instead of requested %i",
+ current_period, requested_latency);
+ return true;
+ }
+
+ LOG("Could not initialize shared stream with IAudioClient3: error: %lx", hr);
+ return false;
+}
+#endif
+
+#define DIRECTION_NAME (direction == eCapture ? "capture" : "render")
+
+template <typename T>
+int
+setup_wasapi_stream_one_side(cubeb_stream * stm,
+ cubeb_stream_params * stream_params,
+ wchar_t const * devid, EDataFlow direction,
+ REFIID riid, com_ptr<IAudioClient> & audio_client,
+ uint32_t * buffer_frame_count, HANDLE & event,
+ T & render_or_capture_client,
+ cubeb_stream_params * mix_params,
+ com_ptr<IMMDevice> & device)
+{
+ XASSERT(direction == eCapture || direction == eRender);
+
+ HRESULT hr;
+ bool is_loopback = stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK;
+ if (is_loopback && direction != eCapture) {
+ LOG("Loopback pref can only be used with capture streams!\n");
+ return CUBEB_ERROR;
+ }
+
+ stm->stream_reset_lock.assert_current_thread_owns();
+ // If user doesn't specify a particular device, we can choose another one when
+ // the given devid is unavailable.
+ bool allow_fallback =
+ direction == eCapture ? !stm->input_device_id : !stm->output_device_id;
+ bool try_again = false;
+ // This loops until we find a device that works, or we've exhausted all
+ // possibilities.
+ do {
+ if (devid) {
+ hr = get_endpoint(device, devid);
+ if (FAILED(hr)) {
+ LOG("Could not get %s endpoint, error: %lx\n", DIRECTION_NAME, hr);
+ return CUBEB_ERROR;
+ }
+ } else {
+ // If caller has requested loopback but not specified a device, look for
+ // the default render device. Otherwise look for the default device
+ // appropriate to the direction.
+ hr = get_default_endpoint(device, is_loopback ? eRender : direction,
+ pref_to_role(stream_params->prefs));
+ if (FAILED(hr)) {
+ if (is_loopback) {
+ LOG("Could not get default render endpoint for loopback, error: "
+ "%lx\n",
+ hr);
+ } else {
+ LOG("Could not get default %s endpoint, error: %lx\n", DIRECTION_NAME,
+ hr);
+ }
+ return CUBEB_ERROR;
+ }
+ }
+
+ /* Get a client. We will get all other interfaces we need from
+ * this pointer. */
+#if 0 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1590902
+ hr = device->Activate(__uuidof(IAudioClient3),
+ CLSCTX_INPROC_SERVER,
+ NULL, audio_client.receive_vpp());
+ if (hr == E_NOINTERFACE) {
+#endif
+ hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL,
+ audio_client.receive_vpp());
+#if 0
+ }
+#endif
+
+ if (FAILED(hr)) {
+ LOG("Could not activate the device to get an audio"
+ " client for %s: error: %lx\n",
+ DIRECTION_NAME, hr);
+ // A particular device can't be activated because it has been
+ // unplugged, try fall back to the default audio device.
+ if (devid && hr == AUDCLNT_E_DEVICE_INVALIDATED && allow_fallback) {
+ LOG("Trying again with the default %s audio device.", DIRECTION_NAME);
+ devid = nullptr;
+ device = nullptr;
+ try_again = true;
+ } else {
+ return CUBEB_ERROR;
+ }
+ } else {
+ try_again = false;
+ }
+ } while (try_again);
+
+ /* We have to distinguish between the format the mixer uses,
+ * and the format the stream we want to play uses. */
+ WAVEFORMATEX * tmp = nullptr;
+ hr = audio_client->GetMixFormat(&tmp);
+ if (FAILED(hr)) {
+ LOG("Could not fetch current mix format from the audio"
+ " client for %s: error: %lx",
+ DIRECTION_NAME, hr);
+ return CUBEB_ERROR;
+ }
+ com_heap_ptr<WAVEFORMATEX> mix_format(tmp);
+
+ mix_format->wBitsPerSample = stm->bytes_per_sample * 8;
+ if (mix_format->wFormatTag == WAVE_FORMAT_PCM ||
+ mix_format->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
+ switch (mix_format->wBitsPerSample) {
+ case 8:
+ case 16:
+ mix_format->wFormatTag = WAVE_FORMAT_PCM;
+ break;
+ case 32:
+ mix_format->wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
+ break;
+ default:
+ LOG("%u bits per sample is incompatible with PCM wave formats",
+ mix_format->wBitsPerSample);
+ return CUBEB_ERROR;
+ }
+ }
+
+ if (mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
+ WAVEFORMATEXTENSIBLE * format_pcm =
+ reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get());
+ format_pcm->SubFormat = stm->waveformatextensible_sub_format;
+ }
+ waveformatex_update_derived_properties(mix_format.get());
+
+ /* Set channel layout only when there're more than two channels. Otherwise,
+ * use the default setting retrieved from the stream format of the audio
+ * engine's internal processing by GetMixFormat. */
+ if (mix_format->nChannels > 2) {
+ handle_channel_layout(stm, direction, mix_format, stream_params);
+ }
+
+ mix_params->format = stream_params->format;
+ mix_params->rate = mix_format->nSamplesPerSec;
+ mix_params->channels = mix_format->nChannels;
+ mix_params->layout = mask_to_channel_layout(mix_format.get());
+
+ LOG("Setup requested=[f=%d r=%u c=%u l=%u] mix=[f=%d r=%u c=%u l=%u]",
+ stream_params->format, stream_params->rate, stream_params->channels,
+ stream_params->layout, mix_params->format, mix_params->rate,
+ mix_params->channels, mix_params->layout);
+
+ DWORD flags = 0;
+
+ // Check if a loopback device should be requested. Note that event callbacks
+ // do not work with loopback devices, so only request these if not looping.
+ if (is_loopback) {
+ flags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
+ } else {
+ flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
+ }
+
+ REFERENCE_TIME latency_hns = frames_to_hns(stream_params->rate, stm->latency);
+
+ // Adjust input latency and check if input is using bluetooth handsfree
+ // protocol.
+ if (direction == eCapture) {
+ stm->input_bluetooth_handsfree = false;
+
+ wasapi_default_devices default_devices(stm->device_enumerator.get());
+ cubeb_device_info device_info;
+ if (wasapi_create_device(stm->context, device_info,
+ stm->device_enumerator.get(), device.get(),
+ &default_devices) == CUBEB_OK) {
+ if (device_info.latency_hi == 0) {
+ LOG("Input: could not query latency_hi to guess safe latency");
+ wasapi_destroy_device(&device_info);
+ return CUBEB_ERROR;
+ }
+ // This multiplicator has been found empirically.
+ uint32_t latency_frames = device_info.latency_hi * 8;
+ LOG("Input: latency increased to %u frames from a default of %u",
+ latency_frames, device_info.latency_hi);
+ latency_hns = frames_to_hns(device_info.default_rate, latency_frames);
+
+ const char * HANDSFREE_TAG = "BTHHFENUM";
+ size_t len = sizeof(HANDSFREE_TAG);
+ if (strlen(device_info.group_id) >= len &&
+ strncmp(device_info.group_id, HANDSFREE_TAG, len) == 0) {
+ LOG("Input device is using bluetooth handsfree protocol");
+ stm->input_bluetooth_handsfree = true;
+ }
+
+ wasapi_destroy_device(&device_info);
+ } else {
+ LOG("Could not get cubeb_device_info. Skip customizing input settings");
+ }
+ }
+
+ if (stream_params->prefs & CUBEB_STREAM_PREF_RAW) {
+ if (initialize_iaudioclient2(audio_client) != CUBEB_OK) {
+ LOG("Can't initialize an IAudioClient2, error: %lx", GetLastError());
+ // This is not fatal.
+ }
+ }
+
+#if 0 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1590902
+ if (initialize_iaudioclient3(audio_client, stm, mix_format, flags, direction)) {
+ LOG("Initialized with IAudioClient3");
+ } else {
+#endif
+ hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, flags, latency_hns, 0,
+ mix_format.get(), NULL);
+#if 0
+ }
+#endif
+ if (FAILED(hr)) {
+ LOG("Unable to initialize audio client for %s: %lx.", DIRECTION_NAME, hr);
+ return CUBEB_ERROR;
+ }
+
+ hr = audio_client->GetBufferSize(buffer_frame_count);
+ if (FAILED(hr)) {
+ LOG("Could not get the buffer size from the client"
+ " for %s %lx.",
+ DIRECTION_NAME, hr);
+ return CUBEB_ERROR;
+ }
+
+ LOG("Buffer size is: %d for %s\n", *buffer_frame_count, DIRECTION_NAME);
+
+ // Events are used if not looping back
+ if (!is_loopback) {
+ hr = audio_client->SetEventHandle(event);
+ if (FAILED(hr)) {
+ LOG("Could set the event handle for the %s client %lx.", DIRECTION_NAME,
+ hr);
+ return CUBEB_ERROR;
+ }
+ }
+
+ hr = audio_client->GetService(riid, render_or_capture_client.receive_vpp());
+ if (FAILED(hr)) {
+ LOG("Could not get the %s client %lx.", DIRECTION_NAME, hr);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+#undef DIRECTION_NAME
+
+// Returns a non-null cubeb_devid if we find a matched device, or nullptr
+// otherwise.
+cubeb_devid
+wasapi_find_bt_handsfree_output_device(cubeb_stream * stm)
+{
+ HRESULT hr;
+ cubeb_device_info * input_device = nullptr;
+ cubeb_device_collection collection;
+
+ // Only try to match to an output device if the input device is a bluetooth
+ // device that is using the handsfree protocol
+ if (!stm->input_bluetooth_handsfree) {
+ return nullptr;
+ }
+
+ wchar_t * tmp = nullptr;
+ hr = stm->input_device->GetId(&tmp);
+ if (FAILED(hr)) {
+ LOG("Couldn't get input device id in "
+ "wasapi_find_bt_handsfree_output_device");
+ return nullptr;
+ }
+ com_heap_ptr<wchar_t> device_id(tmp);
+ cubeb_devid input_device_id = reinterpret_cast<cubeb_devid>(
+ intern_device_id(stm->context, device_id.get()));
+ if (!input_device_id) {
+ return nullptr;
+ }
+
+ int rv = wasapi_enumerate_devices_internal(
+ stm->context,
+ (cubeb_device_type)(CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT),
+ &collection, DEVICE_STATE_ACTIVE);
+ if (rv != CUBEB_OK) {
+ return nullptr;
+ }
+
+ // Find the input device, and then find the output device with the same group
+ // id and the same rate.
+ for (uint32_t i = 0; i < collection.count; i++) {
+ if (collection.device[i].devid == input_device_id) {
+ input_device = &collection.device[i];
+ break;
+ }
+ }
+
+ cubeb_devid matched_output = nullptr;
+
+ if (input_device) {
+ for (uint32_t i = 0; i < collection.count; i++) {
+ cubeb_device_info & dev = collection.device[i];
+ if (dev.type == CUBEB_DEVICE_TYPE_OUTPUT && dev.group_id &&
+ !strcmp(dev.group_id, input_device->group_id) &&
+ dev.default_rate == input_device->default_rate) {
+ LOG("Found matching device for %s: %s", input_device->friendly_name,
+ dev.friendly_name);
+ matched_output = dev.devid;
+ break;
+ }
+ }
+ }
+
+ wasapi_device_collection_destroy(stm->context, &collection);
+ return matched_output;
+}
+
+std::unique_ptr<wchar_t[]>
+copy_wide_string(const wchar_t * src)
+{
+ XASSERT(src);
+ size_t len = wcslen(src);
+ std::unique_ptr<wchar_t[]> copy(new wchar_t[len + 1]);
+ if (wcsncpy_s(copy.get(), len + 1, src, len) != 0) {
+ return nullptr;
+ }
+ return copy;
+}
+
+int
+setup_wasapi_stream(cubeb_stream * stm)
+{
+ int rv;
+
+ stm->stream_reset_lock.assert_current_thread_owns();
+
+ XASSERT((!stm->output_client || !stm->input_client) &&
+ "WASAPI stream already setup, close it first.");
+
+ std::unique_ptr<const wchar_t[]> selected_output_device_id;
+ if (stm->output_device_id) {
+ if (std::unique_ptr<wchar_t[]> tmp =
+ copy_wide_string(stm->output_device_id.get())) {
+ selected_output_device_id = std::move(tmp);
+ } else {
+ LOG("Failed to copy output device identifier.");
+ return CUBEB_ERROR;
+ }
+ }
+
+ if (has_input(stm)) {
+ LOG("(%p) Setup capture: device=%p", stm, stm->input_device_id.get());
+ rv = setup_wasapi_stream_one_side(
+ stm, &stm->input_stream_params, stm->input_device_id.get(), eCapture,
+ __uuidof(IAudioCaptureClient), stm->input_client,
+ &stm->input_buffer_frame_count, stm->input_available_event,
+ stm->capture_client, &stm->input_mix_params, stm->input_device);
+ if (rv != CUBEB_OK) {
+ LOG("Failure to open the input side.");
+ return rv;
+ }
+
+ // We initializing an input stream, buffer ahead two buffers worth of
+ // silence. This delays the input side slightly, but allow to not glitch
+ // when no input is available when calling into the resampler to call the
+ // callback: the input refill event will be set shortly after to compensate
+ // for this lack of data. In debug, four buffers are used, to avoid tripping
+ // up assertions down the line.
+#if !defined(DEBUG)
+ const int silent_buffer_count = 2;
+#else
+ const int silent_buffer_count = 6;
+#endif
+ stm->linear_input_buffer->push_silence(stm->input_buffer_frame_count *
+ stm->input_stream_params.channels *
+ silent_buffer_count);
+
+ // If this is a bluetooth device, and the output device is the default
+ // device, and the default device is the same bluetooth device, pick the
+ // right output device, running at the same rate and with the same protocol
+ // as the input.
+ if (!selected_output_device_id) {
+ cubeb_devid matched = wasapi_find_bt_handsfree_output_device(stm);
+ if (matched) {
+ selected_output_device_id =
+ utf8_to_wstr(reinterpret_cast<char const *>(matched));
+ }
+ }
+ }
+
+ // If we don't have an output device but are requesting a loopback device,
+ // we attempt to open that same device in output mode in order to drive the
+ // loopback via the output events.
+ stm->has_dummy_output = false;
+ if (!has_output(stm) &&
+ stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ stm->output_stream_params.rate = stm->input_stream_params.rate;
+ stm->output_stream_params.channels = stm->input_stream_params.channels;
+ stm->output_stream_params.layout = stm->input_stream_params.layout;
+ if (stm->input_device_id) {
+ if (std::unique_ptr<wchar_t[]> tmp =
+ copy_wide_string(stm->input_device_id.get())) {
+ XASSERT(!selected_output_device_id);
+ selected_output_device_id = std::move(tmp);
+ } else {
+ LOG("Failed to copy device identifier while copying input stream "
+ "configuration to output stream configuration to drive loopback.");
+ return CUBEB_ERROR;
+ }
+ }
+ stm->has_dummy_output = true;
+ }
+
+ if (has_output(stm)) {
+ LOG("(%p) Setup render: device=%p", stm, selected_output_device_id.get());
+ rv = setup_wasapi_stream_one_side(
+ stm, &stm->output_stream_params, selected_output_device_id.get(),
+ eRender, __uuidof(IAudioRenderClient), stm->output_client,
+ &stm->output_buffer_frame_count, stm->refill_event, stm->render_client,
+ &stm->output_mix_params, stm->output_device);
+ if (rv != CUBEB_OK) {
+ LOG("Failure to open the output side.");
+ return rv;
+ }
+
+ HRESULT hr = 0;
+#ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
+ hr = stm->output_client->GetService(__uuidof(IAudioStreamVolume),
+ stm->audio_stream_volume.receive_vpp());
+ if (FAILED(hr)) {
+ LOG("Could not get the IAudioStreamVolume: %lx", hr);
+ return CUBEB_ERROR;
+ }
+#endif
+
+ XASSERT(stm->frames_written == 0);
+ hr = stm->output_client->GetService(__uuidof(IAudioClock),
+ stm->audio_clock.receive_vpp());
+ if (FAILED(hr)) {
+ LOG("Could not get the IAudioClock: %lx", hr);
+ return CUBEB_ERROR;
+ }
+
+#ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
+ /* Restore the stream volume over a device change. */
+ if (stream_set_volume(stm, stm->volume) != CUBEB_OK) {
+ LOG("Could not set the volume.");
+ return CUBEB_ERROR;
+ }
+#endif
+ }
+
+ /* If we have both input and output, we resample to
+ * the highest sample rate available. */
+ int32_t target_sample_rate;
+ if (has_input(stm) && has_output(stm)) {
+ XASSERT(stm->input_stream_params.rate == stm->output_stream_params.rate);
+ target_sample_rate = stm->input_stream_params.rate;
+ } else if (has_input(stm)) {
+ target_sample_rate = stm->input_stream_params.rate;
+ } else {
+ XASSERT(has_output(stm));
+ target_sample_rate = stm->output_stream_params.rate;
+ }
+
+ LOG("Target sample rate: %d", target_sample_rate);
+
+ /* If we are playing/capturing a mono stream, we only resample one channel,
+ and copy it over, so we are always resampling the number
+ of channels of the stream, not the number of channels
+ that WASAPI wants. */
+ cubeb_stream_params input_params = stm->input_mix_params;
+ input_params.channels = stm->input_stream_params.channels;
+ cubeb_stream_params output_params = stm->output_mix_params;
+ output_params.channels = stm->output_stream_params.channels;
+
+ stm->resampler.reset(cubeb_resampler_create(
+ stm, has_input(stm) ? &input_params : nullptr,
+ has_output(stm) && !stm->has_dummy_output ? &output_params : nullptr,
+ target_sample_rate, wasapi_data_callback, stm->user_ptr,
+ stm->voice ? CUBEB_RESAMPLER_QUALITY_VOIP
+ : CUBEB_RESAMPLER_QUALITY_DESKTOP,
+ CUBEB_RESAMPLER_RECLOCK_NONE));
+ if (!stm->resampler) {
+ LOG("Could not get a resampler");
+ return CUBEB_ERROR;
+ }
+
+ XASSERT(has_input(stm) || has_output(stm));
+
+ if (has_input(stm) && has_output(stm)) {
+ stm->refill_callback = refill_callback_duplex;
+ } else if (has_input(stm)) {
+ stm->refill_callback = refill_callback_input;
+ } else if (has_output(stm)) {
+ stm->refill_callback = refill_callback_output;
+ }
+
+ // Create input mixer.
+ if (has_input(stm) &&
+ ((stm->input_mix_params.layout != CUBEB_LAYOUT_UNDEFINED &&
+ stm->input_mix_params.layout != stm->input_stream_params.layout) ||
+ (stm->input_mix_params.channels != stm->input_stream_params.channels))) {
+ if (stm->input_mix_params.layout == CUBEB_LAYOUT_UNDEFINED) {
+ LOG("Input stream using undefined layout! Any mixing may be "
+ "unpredictable!\n");
+ }
+ stm->input_mixer.reset(cubeb_mixer_create(
+ stm->input_stream_params.format, stm->input_mix_params.channels,
+ stm->input_mix_params.layout, stm->input_stream_params.channels,
+ stm->input_stream_params.layout));
+ assert(stm->input_mixer);
+ }
+
+ // Create output mixer.
+ if (has_output(stm) &&
+ stm->output_mix_params.layout != stm->output_stream_params.layout) {
+ if (stm->output_mix_params.layout == CUBEB_LAYOUT_UNDEFINED) {
+ LOG("Output stream using undefined layout! Any mixing may be "
+ "unpredictable!\n");
+ }
+ stm->output_mixer.reset(cubeb_mixer_create(
+ stm->output_stream_params.format, stm->output_stream_params.channels,
+ stm->output_stream_params.layout, stm->output_mix_params.channels,
+ stm->output_mix_params.layout));
+ assert(stm->output_mixer);
+ // Input is up/down mixed when depacketized in get_input_buffer.
+ stm->mix_buffer.resize(
+ frames_to_bytes_before_mix(stm, stm->output_buffer_frame_count));
+ }
+
+ return CUBEB_OK;
+}
+
+ERole
+pref_to_role(cubeb_stream_prefs prefs)
+{
+ if (prefs & CUBEB_STREAM_PREF_VOICE) {
+ return eCommunications;
+ }
+
+ return eConsole;
+}
+
+int
+wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ int rv;
+
+ XASSERT(context && stream && (input_stream_params || output_stream_params));
+
+ if (output_stream_params && input_stream_params &&
+ output_stream_params->format != input_stream_params->format) {
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ cubeb_stream * stm = new cubeb_stream();
+ auto_stream_ref stream_ref(stm);
+
+ stm->context = context;
+ stm->data_callback = data_callback;
+ stm->state_callback = state_callback;
+ stm->user_ptr = user_ptr;
+ stm->role = eConsole;
+ stm->input_bluetooth_handsfree = false;
+
+ HRESULT hr =
+ CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(stm->device_enumerator.receive()));
+ if (FAILED(hr)) {
+ LOG("Could not get device enumerator: %lx", hr);
+ return hr;
+ }
+
+ if (input_stream_params) {
+ stm->input_stream_params = *input_stream_params;
+ stm->input_device_id =
+ utf8_to_wstr(reinterpret_cast<char const *>(input_device));
+ }
+ if (output_stream_params) {
+ stm->output_stream_params = *output_stream_params;
+ stm->output_device_id =
+ utf8_to_wstr(reinterpret_cast<char const *>(output_device));
+ }
+
+ if (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_VOICE ||
+ stm->input_stream_params.prefs & CUBEB_STREAM_PREF_VOICE) {
+ stm->voice = true;
+ } else {
+ stm->voice = false;
+ }
+
+ switch (output_stream_params ? output_stream_params->format
+ : input_stream_params->format) {
+ case CUBEB_SAMPLE_S16NE:
+ stm->bytes_per_sample = sizeof(short);
+ stm->waveformatextensible_sub_format = KSDATAFORMAT_SUBTYPE_PCM;
+ stm->linear_input_buffer.reset(new auto_array_wrapper_impl<short>);
+ break;
+ case CUBEB_SAMPLE_FLOAT32NE:
+ stm->bytes_per_sample = sizeof(float);
+ stm->waveformatextensible_sub_format = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+ stm->linear_input_buffer.reset(new auto_array_wrapper_impl<float>);
+ break;
+ default:
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ stm->latency = latency_frames;
+
+ stm->reconfigure_event = CreateEvent(NULL, 0, 0, NULL);
+ if (!stm->reconfigure_event) {
+ LOG("Can't create the reconfigure event, error: %lx", GetLastError());
+ return CUBEB_ERROR;
+ }
+
+ /* Unconditionally create the two events so that the wait logic is simpler. */
+ stm->refill_event = CreateEvent(NULL, 0, 0, NULL);
+ if (!stm->refill_event) {
+ LOG("Can't create the refill event, error: %lx", GetLastError());
+ return CUBEB_ERROR;
+ }
+
+ stm->input_available_event = CreateEvent(NULL, 0, 0, NULL);
+ if (!stm->input_available_event) {
+ LOG("Can't create the input available event , error: %lx", GetLastError());
+ return CUBEB_ERROR;
+ }
+
+ stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL);
+ if (!stm->shutdown_event) {
+ LOG("Can't create the shutdown event, error: %lx", GetLastError());
+ return CUBEB_ERROR;
+ }
+
+ stm->thread_ready_event = CreateEvent(NULL, 0, 0, NULL);
+ if (!stm->thread_ready_event) {
+ LOG("Can't create the thread ready event, error: %lx", GetLastError());
+ return CUBEB_ERROR;
+ }
+
+ {
+ /* Locking here is not strictly necessary, because we don't have a
+ notification client that can reset the stream yet, but it lets us
+ assert that the lock is held in the function. */
+ auto_lock lock(stm->stream_reset_lock);
+ rv = setup_wasapi_stream(stm);
+ }
+ if (rv != CUBEB_OK) {
+ return rv;
+ }
+
+ // Follow the system default devices when not specifying devices explicitly
+ // and CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING is not set.
+ if ((!input_device && input_stream_params &&
+ !(input_stream_params->prefs &
+ CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING)) ||
+ (!output_device && output_stream_params &&
+ !(output_stream_params->prefs &
+ CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING))) {
+ LOG("Follow the system default input or/and output devices");
+ HRESULT hr = register_notification_client(stm);
+ if (FAILED(hr)) {
+ /* this is not fatal, we can still play audio, but we won't be able
+ to keep using the default audio endpoint if it changes. */
+ LOG("failed to register notification client, %lx", hr);
+ }
+ }
+
+ cubeb_async_log_reset_threads();
+ stm->thread =
+ (HANDLE)_beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm,
+ STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
+ if (stm->thread == NULL) {
+ LOG("could not create WASAPI render thread.");
+ return CUBEB_ERROR;
+ }
+
+ // Wait for the wasapi_stream_render_loop thread to signal that COM has been
+ // initialized and the stream's ref_count has been incremented.
+ hr = WaitForSingleObject(stm->thread_ready_event, INFINITE);
+ XASSERT(hr == WAIT_OBJECT_0);
+ CloseHandle(stm->thread_ready_event);
+ stm->thread_ready_event = 0;
+
+ wasapi_stream_add_ref(stm);
+ *stream = stm;
+
+ LOG("Stream init successful (%p)", *stream);
+ return CUBEB_OK;
+}
+
+void
+close_wasapi_stream(cubeb_stream * stm)
+{
+ XASSERT(stm);
+
+ stm->stream_reset_lock.assert_current_thread_owns();
+
+#ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
+ stm->audio_stream_volume = nullptr;
+#endif
+ stm->audio_clock = nullptr;
+ stm->render_client = nullptr;
+ stm->output_client = nullptr;
+ stm->output_device = nullptr;
+
+ stm->capture_client = nullptr;
+ stm->input_client = nullptr;
+ stm->input_device = nullptr;
+
+ stm->total_frames_written += static_cast<UINT64>(
+ round(stm->frames_written *
+ stream_to_mix_samplerate_ratio(stm->output_stream_params,
+ stm->output_mix_params)));
+ stm->frames_written = 0;
+
+ stm->resampler.reset();
+ stm->output_mixer.reset();
+ stm->input_mixer.reset();
+ stm->mix_buffer.clear();
+ if (stm->linear_input_buffer) {
+ stm->linear_input_buffer->clear();
+ }
+}
+
+LONG
+wasapi_stream_add_ref(cubeb_stream * stm)
+{
+ XASSERT(stm);
+ LONG result = InterlockedIncrement(&stm->ref_count);
+ LOGV("Stream ref count incremented = %i (%p)", result, stm);
+ return result;
+}
+
+LONG
+wasapi_stream_release(cubeb_stream * stm)
+{
+ XASSERT(stm);
+
+ LONG result = InterlockedDecrement(&stm->ref_count);
+ LOGV("Stream ref count decremented = %i (%p)", result, stm);
+ if (result == 0) {
+ LOG("Stream ref count hit zero, destroying (%p)", stm);
+
+ if (stm->notification_client) {
+ unregister_notification_client(stm);
+ }
+
+ CloseHandle(stm->shutdown_event);
+ CloseHandle(stm->reconfigure_event);
+ CloseHandle(stm->refill_event);
+ CloseHandle(stm->input_available_event);
+
+ CloseHandle(stm->thread);
+
+ // The variables intialized in wasapi_stream_init,
+ // must be destroyed in wasapi_stream_release.
+ stm->linear_input_buffer.reset();
+
+ {
+ auto_lock lock(stm->stream_reset_lock);
+ close_wasapi_stream(stm);
+ }
+
+ delete stm;
+ }
+
+ return result;
+}
+
+void
+wasapi_stream_destroy(cubeb_stream * stm)
+{
+ XASSERT(stm);
+ LOG("Stream destroy called, decrementing ref count (%p)", stm);
+
+ stop_and_join_render_thread(stm);
+ wasapi_stream_release(stm);
+}
+
+enum StreamDirection { OUTPUT, INPUT };
+
+int
+stream_start_one_side(cubeb_stream * stm, StreamDirection dir)
+{
+ XASSERT(stm);
+ XASSERT((dir == OUTPUT && stm->output_client) ||
+ (dir == INPUT && stm->input_client));
+
+ HRESULT hr =
+ dir == OUTPUT ? stm->output_client->Start() : stm->input_client->Start();
+ if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
+ LOG("audioclient invalidated for %s device, reconfiguring",
+ dir == OUTPUT ? "output" : "input");
+
+ BOOL ok = ResetEvent(stm->reconfigure_event);
+ if (!ok) {
+ LOG("resetting reconfig event failed for %s stream: %lx",
+ dir == OUTPUT ? "output" : "input", GetLastError());
+ }
+
+ close_wasapi_stream(stm);
+ int r = setup_wasapi_stream(stm);
+ if (r != CUBEB_OK) {
+ LOG("reconfigure failed");
+ return r;
+ }
+
+ HRESULT hr2 = dir == OUTPUT ? stm->output_client->Start()
+ : stm->input_client->Start();
+ if (FAILED(hr2)) {
+ LOG("could not start the %s stream after reconfig: %lx",
+ dir == OUTPUT ? "output" : "input", hr);
+ return CUBEB_ERROR;
+ }
+ } else if (FAILED(hr)) {
+ LOG("could not start the %s stream: %lx.",
+ dir == OUTPUT ? "output" : "input", hr);
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+}
+
+int
+wasapi_stream_start(cubeb_stream * stm)
+{
+ auto_lock lock(stm->stream_reset_lock);
+
+ XASSERT(stm);
+ XASSERT(stm->output_client || stm->input_client);
+
+ if (stm->output_client) {
+ int rv = stream_start_one_side(stm, OUTPUT);
+ if (rv != CUBEB_OK) {
+ return rv;
+ }
+ }
+
+ if (stm->input_client) {
+ int rv = stream_start_one_side(stm, INPUT);
+ if (rv != CUBEB_OK) {
+ return rv;
+ }
+ }
+
+ stm->active = true;
+
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
+
+ return CUBEB_OK;
+}
+
+int
+wasapi_stream_stop(cubeb_stream * stm)
+{
+ XASSERT(stm);
+ HRESULT hr;
+
+ {
+ auto_lock lock(stm->stream_reset_lock);
+
+ if (stm->output_client) {
+ hr = stm->output_client->Stop();
+ if (FAILED(hr)) {
+ LOG("could not stop AudioClient (output)");
+ return CUBEB_ERROR;
+ }
+ }
+
+ if (stm->input_client) {
+ hr = stm->input_client->Stop();
+ if (FAILED(hr)) {
+ LOG("could not stop AudioClient (input)");
+ return CUBEB_ERROR;
+ }
+ }
+
+ stm->active = false;
+
+ wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
+ }
+
+ return CUBEB_OK;
+}
+
+int
+wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position)
+{
+ XASSERT(stm && position);
+ auto_lock lock(stm->stream_reset_lock);
+
+ if (!has_output(stm)) {
+ return CUBEB_ERROR;
+ }
+
+ /* Calculate how far behind the current stream head the playback cursor is. */
+ uint64_t stream_delay = static_cast<uint64_t>(current_stream_delay(stm) *
+ stm->output_stream_params.rate);
+
+ /* Calculate the logical stream head in frames at the stream sample rate. */
+ uint64_t max_pos =
+ stm->total_frames_written +
+ static_cast<uint64_t>(
+ round(stm->frames_written *
+ stream_to_mix_samplerate_ratio(stm->output_stream_params,
+ stm->output_mix_params)));
+
+ *position = max_pos;
+ if (stream_delay <= *position) {
+ *position -= stream_delay;
+ }
+
+ if (*position < stm->prev_position) {
+ *position = stm->prev_position;
+ }
+ stm->prev_position = *position;
+
+ return CUBEB_OK;
+}
+
+int
+wasapi_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
+{
+ XASSERT(stm && latency);
+
+ if (!has_output(stm)) {
+ return CUBEB_ERROR;
+ }
+
+ auto_lock lock(stm->stream_reset_lock);
+
+ /* The GetStreamLatency method only works if the
+ AudioClient has been initialized. */
+ if (!stm->output_client) {
+ LOG("get_latency: No output_client.");
+ return CUBEB_ERROR;
+ }
+
+ REFERENCE_TIME latency_hns;
+ HRESULT hr = stm->output_client->GetStreamLatency(&latency_hns);
+ if (FAILED(hr)) {
+ LOG("GetStreamLatency failed %lx.", hr);
+ return CUBEB_ERROR;
+ }
+ // This happens on windows 10: no error, but always 0 for latency.
+ if (latency_hns == 0) {
+ LOG("GetStreamLatency returned 0, using workaround.");
+ double delay_s = current_stream_delay(stm);
+ // convert to sample-frames
+ *latency = delay_s * stm->output_stream_params.rate;
+ } else {
+ *latency = hns_to_frames(stm, latency_hns);
+ }
+
+ LOG("Output latency %u frames.", *latency);
+
+ return CUBEB_OK;
+}
+
+int
+wasapi_stream_get_input_latency(cubeb_stream * stm, uint32_t * latency)
+{
+ XASSERT(stm && latency);
+
+ if (!has_input(stm)) {
+ LOG("Input latency queried on an output-only stream.");
+ return CUBEB_ERROR;
+ }
+
+ auto_lock lock(stm->stream_reset_lock);
+
+ if (stm->input_latency_hns == LATENCY_NOT_AVAILABLE_YET) {
+ LOG("Input latency not available yet.");
+ return CUBEB_ERROR;
+ }
+
+ *latency = hns_to_frames(stm, stm->input_latency_hns);
+
+ return CUBEB_OK;
+}
+
+int
+wasapi_stream_set_volume(cubeb_stream * stm, float volume)
+{
+ auto_lock lock(stm->stream_reset_lock);
+
+ if (!has_output(stm)) {
+ return CUBEB_ERROR;
+ }
+
+#ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
+ if (stream_set_volume(stm, volume) != CUBEB_OK) {
+ return CUBEB_ERROR;
+ }
+#endif
+
+ stm->volume = volume;
+
+ return CUBEB_OK;
+}
+
+static char const *
+wstr_to_utf8(LPCWSTR str)
+{
+ int size = ::WideCharToMultiByte(CP_UTF8, 0, str, -1, nullptr, 0, NULL, NULL);
+ if (size <= 0) {
+ return nullptr;
+ }
+
+ char * ret = static_cast<char *>(malloc(size));
+ ::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, size, NULL, NULL);
+ return ret;
+}
+
+static std::unique_ptr<wchar_t const[]>
+utf8_to_wstr(char const * str)
+{
+ int size = ::MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0);
+ if (size <= 0) {
+ return nullptr;
+ }
+
+ std::unique_ptr<wchar_t[]> ret(new wchar_t[size]);
+ ::MultiByteToWideChar(CP_UTF8, 0, str, -1, ret.get(), size);
+ return ret;
+}
+
+static com_ptr<IMMDevice>
+wasapi_get_device_node(IMMDeviceEnumerator * enumerator, IMMDevice * dev)
+{
+ com_ptr<IMMDevice> ret;
+ com_ptr<IDeviceTopology> devtopo;
+ com_ptr<IConnector> connector;
+
+ if (SUCCEEDED(dev->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL,
+ devtopo.receive_vpp())) &&
+ SUCCEEDED(devtopo->GetConnector(0, connector.receive()))) {
+ wchar_t * tmp = nullptr;
+ if (SUCCEEDED(connector->GetDeviceIdConnectedTo(&tmp))) {
+ com_heap_ptr<wchar_t> filterid(tmp);
+ if (FAILED(enumerator->GetDevice(filterid.get(), ret.receive())))
+ ret = NULL;
+ }
+ }
+
+ return ret;
+}
+
+static com_heap_ptr<wchar_t>
+wasapi_get_default_device_id(EDataFlow flow, ERole role,
+ IMMDeviceEnumerator * enumerator)
+{
+ com_ptr<IMMDevice> dev;
+
+ HRESULT hr = enumerator->GetDefaultAudioEndpoint(flow, role, dev.receive());
+ if (SUCCEEDED(hr)) {
+ wchar_t * tmp = nullptr;
+ if (SUCCEEDED(dev->GetId(&tmp))) {
+ com_heap_ptr<wchar_t> devid(tmp);
+ return devid;
+ }
+ }
+
+ return nullptr;
+}
+
+/* `ret` must be deallocated with `wasapi_destroy_device`, iff the return value
+ * of this function is `CUBEB_OK`. */
+int
+wasapi_create_device(cubeb * ctx, cubeb_device_info & ret,
+ IMMDeviceEnumerator * enumerator, IMMDevice * dev,
+ wasapi_default_devices * defaults)
+{
+ com_ptr<IMMEndpoint> endpoint;
+ com_ptr<IMMDevice> devnode;
+ com_ptr<IAudioClient> client;
+ EDataFlow flow;
+ DWORD state = DEVICE_STATE_NOTPRESENT;
+ com_ptr<IPropertyStore> propstore;
+ REFERENCE_TIME def_period, min_period;
+ HRESULT hr;
+
+ XASSERT(enumerator && dev && defaults);
+
+ // zero-out to be able to safely delete the pointers to friendly_name and
+ // group_id at all time in this function.
+ PodZero(&ret, 1);
+
+ struct prop_variant : public PROPVARIANT {
+ prop_variant() { PropVariantInit(this); }
+ ~prop_variant() { PropVariantClear(this); }
+ prop_variant(prop_variant const &) = delete;
+ prop_variant & operator=(prop_variant const &) = delete;
+ };
+
+ hr = dev->QueryInterface(IID_PPV_ARGS(endpoint.receive()));
+ if (FAILED(hr)) {
+ wasapi_destroy_device(&ret);
+ return CUBEB_ERROR;
+ }
+
+ hr = endpoint->GetDataFlow(&flow);
+ if (FAILED(hr)) {
+ wasapi_destroy_device(&ret);
+ return CUBEB_ERROR;
+ }
+
+ wchar_t * tmp = nullptr;
+ hr = dev->GetId(&tmp);
+ if (FAILED(hr)) {
+ wasapi_destroy_device(&ret);
+ return CUBEB_ERROR;
+ }
+ com_heap_ptr<wchar_t> device_id(tmp);
+
+ char const * device_id_intern = intern_device_id(ctx, device_id.get());
+ if (!device_id_intern) {
+ wasapi_destroy_device(&ret);
+ return CUBEB_ERROR;
+ }
+
+ hr = dev->OpenPropertyStore(STGM_READ, propstore.receive());
+ if (FAILED(hr)) {
+ wasapi_destroy_device(&ret);
+ return CUBEB_ERROR;
+ }
+
+ hr = dev->GetState(&state);
+ if (FAILED(hr)) {
+ wasapi_destroy_device(&ret);
+ return CUBEB_ERROR;
+ }
+
+ ret.device_id = device_id_intern;
+ ret.devid = reinterpret_cast<cubeb_devid>(ret.device_id);
+ prop_variant namevar;
+ hr = propstore->GetValue(PKEY_Device_FriendlyName, &namevar);
+ if (SUCCEEDED(hr) && namevar.vt == VT_LPWSTR) {
+ ret.friendly_name = wstr_to_utf8(namevar.pwszVal);
+ }
+ if (!ret.friendly_name) {
+ // This is not fatal, but a valid string is expected in all cases.
+ char * empty = new char[1];
+ empty[0] = '\0';
+ ret.friendly_name = empty;
+ }
+
+ devnode = wasapi_get_device_node(enumerator, dev);
+ if (devnode) {
+ com_ptr<IPropertyStore> ps;
+ hr = devnode->OpenPropertyStore(STGM_READ, ps.receive());
+ if (FAILED(hr)) {
+ wasapi_destroy_device(&ret);
+ return CUBEB_ERROR;
+ }
+
+ prop_variant instancevar;
+ hr = ps->GetValue(PKEY_Device_InstanceId, &instancevar);
+ if (SUCCEEDED(hr) && instancevar.vt == VT_LPWSTR) {
+ ret.group_id = wstr_to_utf8(instancevar.pwszVal);
+ }
+ }
+
+ if (!ret.group_id) {
+ // This is not fatal, but a valid string is expected in all cases.
+ char * empty = new char[1];
+ empty[0] = '\0';
+ ret.group_id = empty;
+ }
+
+ ret.preferred = CUBEB_DEVICE_PREF_NONE;
+ if (defaults->is_default(flow, eConsole, device_id.get())) {
+ ret.preferred =
+ (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_MULTIMEDIA |
+ CUBEB_DEVICE_PREF_NOTIFICATION);
+ } else if (defaults->is_default(flow, eCommunications, device_id.get())) {
+ ret.preferred =
+ (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_VOICE);
+ }
+
+ if (flow == eRender) {
+ ret.type = CUBEB_DEVICE_TYPE_OUTPUT;
+ } else if (flow == eCapture) {
+ ret.type = CUBEB_DEVICE_TYPE_INPUT;
+ }
+
+ switch (state) {
+ case DEVICE_STATE_ACTIVE:
+ ret.state = CUBEB_DEVICE_STATE_ENABLED;
+ break;
+ case DEVICE_STATE_UNPLUGGED:
+ ret.state = CUBEB_DEVICE_STATE_UNPLUGGED;
+ break;
+ default:
+ ret.state = CUBEB_DEVICE_STATE_DISABLED;
+ break;
+ };
+
+ ret.format = static_cast<cubeb_device_fmt>(CUBEB_DEVICE_FMT_F32NE |
+ CUBEB_DEVICE_FMT_S16NE);
+ ret.default_format = CUBEB_DEVICE_FMT_F32NE;
+ prop_variant fmtvar;
+ hr = propstore->GetValue(PKEY_AudioEngine_DeviceFormat, &fmtvar);
+ if (SUCCEEDED(hr) && fmtvar.vt == VT_BLOB) {
+ if (fmtvar.blob.cbSize == sizeof(PCMWAVEFORMAT)) {
+ const PCMWAVEFORMAT * pcm =
+ reinterpret_cast<const PCMWAVEFORMAT *>(fmtvar.blob.pBlobData);
+
+ ret.max_rate = ret.min_rate = ret.default_rate = pcm->wf.nSamplesPerSec;
+ ret.max_channels = pcm->wf.nChannels;
+ } else if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX)) {
+ WAVEFORMATEX * wfx =
+ reinterpret_cast<WAVEFORMATEX *>(fmtvar.blob.pBlobData);
+
+ if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX) + wfx->cbSize ||
+ wfx->wFormatTag == WAVE_FORMAT_PCM) {
+ ret.max_rate = ret.min_rate = ret.default_rate = wfx->nSamplesPerSec;
+ ret.max_channels = wfx->nChannels;
+ }
+ }
+ }
+
+ if (SUCCEEDED(dev->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER,
+ NULL, client.receive_vpp())) &&
+ SUCCEEDED(client->GetDevicePeriod(&def_period, &min_period))) {
+ ret.latency_lo = hns_to_frames(ret.default_rate, min_period);
+ ret.latency_hi = hns_to_frames(ret.default_rate, def_period);
+ } else {
+ ret.latency_lo = 0;
+ ret.latency_hi = 0;
+ }
+
+ XASSERT(ret.friendly_name && ret.group_id);
+
+ return CUBEB_OK;
+}
+
+void
+wasapi_destroy_device(cubeb_device_info * device)
+{
+ delete[] device->friendly_name;
+ delete[] device->group_id;
+}
+
+static int
+wasapi_enumerate_devices_internal(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * out,
+ DWORD state_mask)
+{
+ com_ptr<IMMDeviceEnumerator> enumerator;
+ com_ptr<IMMDeviceCollection> collection;
+ HRESULT hr;
+ UINT cc, i;
+ EDataFlow flow;
+
+ hr =
+ CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(enumerator.receive()));
+ if (FAILED(hr)) {
+ LOG("Could not get device enumerator: %lx", hr);
+ return CUBEB_ERROR;
+ }
+
+ wasapi_default_devices default_devices(enumerator.get());
+
+ if (type == CUBEB_DEVICE_TYPE_OUTPUT) {
+ flow = eRender;
+ } else if (type == CUBEB_DEVICE_TYPE_INPUT) {
+ flow = eCapture;
+ } else if (type & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) {
+ flow = eAll;
+ } else {
+ return CUBEB_ERROR;
+ }
+
+ hr = enumerator->EnumAudioEndpoints(flow, state_mask, collection.receive());
+ if (FAILED(hr)) {
+ LOG("Could not enumerate audio endpoints: %lx", hr);
+ return CUBEB_ERROR;
+ }
+
+ hr = collection->GetCount(&cc);
+ if (FAILED(hr)) {
+ LOG("IMMDeviceCollection::GetCount() failed: %lx", hr);
+ return CUBEB_ERROR;
+ }
+ cubeb_device_info * devices = new cubeb_device_info[cc];
+ if (!devices)
+ return CUBEB_ERROR;
+
+ PodZero(devices, cc);
+ out->count = 0;
+ for (i = 0; i < cc; i++) {
+ com_ptr<IMMDevice> dev;
+ hr = collection->Item(i, dev.receive());
+ if (FAILED(hr)) {
+ LOG("IMMDeviceCollection::Item(%u) failed: %lx", i - 1, hr);
+ continue;
+ }
+ if (wasapi_create_device(context, devices[out->count], enumerator.get(),
+ dev.get(), &default_devices) == CUBEB_OK) {
+ out->count += 1;
+ }
+ }
+
+ out->device = devices;
+ return CUBEB_OK;
+}
+
+static int
+wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * out)
+{
+ return wasapi_enumerate_devices_internal(
+ context, type, out,
+ DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED | DEVICE_STATE_UNPLUGGED);
+}
+
+static int
+wasapi_device_collection_destroy(cubeb * /*ctx*/,
+ cubeb_device_collection * collection)
+{
+ XASSERT(collection);
+
+ for (size_t n = 0; n < collection->count; n++) {
+ cubeb_device_info & dev = collection->device[n];
+ wasapi_destroy_device(&dev);
+ }
+
+ delete[] collection->device;
+ return CUBEB_OK;
+}
+
+static int
+wasapi_register_device_collection_changed(
+ cubeb * context, cubeb_device_type devtype,
+ cubeb_device_collection_changed_callback collection_changed_callback,
+ void * user_ptr)
+{
+ auto_lock lock(context->lock);
+ if (devtype == CUBEB_DEVICE_TYPE_UNKNOWN) {
+ return CUBEB_ERROR_INVALID_PARAMETER;
+ }
+
+ if (collection_changed_callback) {
+ // Make sure it has been unregistered first.
+ XASSERT(((devtype & CUBEB_DEVICE_TYPE_INPUT) &&
+ !context->input_collection_changed_callback) ||
+ ((devtype & CUBEB_DEVICE_TYPE_OUTPUT) &&
+ !context->output_collection_changed_callback));
+
+ // Stop the notification client. Notifications arrive on
+ // a separate thread. We stop them here to avoid
+ // synchronization issues during the update.
+ if (context->device_collection_enumerator.get()) {
+ HRESULT hr = unregister_collection_notification_client(context);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+ }
+
+ if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
+ context->input_collection_changed_callback = collection_changed_callback;
+ context->input_collection_changed_user_ptr = user_ptr;
+ }
+ if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
+ context->output_collection_changed_callback = collection_changed_callback;
+ context->output_collection_changed_user_ptr = user_ptr;
+ }
+
+ HRESULT hr = register_collection_notification_client(context);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+ } else {
+ if (!context->device_collection_enumerator.get()) {
+ // Already unregistered, ignore it.
+ return CUBEB_OK;
+ }
+
+ HRESULT hr = unregister_collection_notification_client(context);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+ if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
+ context->input_collection_changed_callback = nullptr;
+ context->input_collection_changed_user_ptr = nullptr;
+ }
+ if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
+ context->output_collection_changed_callback = nullptr;
+ context->output_collection_changed_user_ptr = nullptr;
+ }
+
+ // If after the updates we still have registered
+ // callbacks restart the notification client.
+ if (context->input_collection_changed_callback ||
+ context->output_collection_changed_callback) {
+ hr = register_collection_notification_client(context);
+ if (FAILED(hr)) {
+ return CUBEB_ERROR;
+ }
+ }
+ }
+
+ return CUBEB_OK;
+}
+
+cubeb_ops const wasapi_ops = {
+ /*.init =*/wasapi_init,
+ /*.get_backend_id =*/wasapi_get_backend_id,
+ /*.get_max_channel_count =*/wasapi_get_max_channel_count,
+ /*.get_min_latency =*/wasapi_get_min_latency,
+ /*.get_preferred_sample_rate =*/wasapi_get_preferred_sample_rate,
+ /*.get_supported_input_processing_params =*/NULL,
+ /*.enumerate_devices =*/wasapi_enumerate_devices,
+ /*.device_collection_destroy =*/wasapi_device_collection_destroy,
+ /*.destroy =*/wasapi_destroy,
+ /*.stream_init =*/wasapi_stream_init,
+ /*.stream_destroy =*/wasapi_stream_destroy,
+ /*.stream_start =*/wasapi_stream_start,
+ /*.stream_stop =*/wasapi_stream_stop,
+ /*.stream_get_position =*/wasapi_stream_get_position,
+ /*.stream_get_latency =*/wasapi_stream_get_latency,
+ /*.stream_get_input_latency =*/wasapi_stream_get_input_latency,
+ /*.stream_set_volume =*/wasapi_stream_set_volume,
+ /*.stream_set_name =*/NULL,
+ /*.stream_get_current_device =*/NULL,
+ /*.stream_set_input_mute =*/NULL,
+ /*.stream_set_input_processing_params =*/NULL,
+ /*.stream_device_destroy =*/NULL,
+ /*.stream_register_device_changed_callback =*/NULL,
+ /*.register_device_collection_changed =*/
+ wasapi_register_device_collection_changed,
+};
+} // namespace
diff --git a/third_party/rust/cubeb-sys/libcubeb/src/cubeb_winmm.c b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_winmm.c
new file mode 100644
index 0000000000..b2234a9b52
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/src/cubeb_winmm.c
@@ -0,0 +1,1213 @@
+/*
+ * Copyright © 2011 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#undef WINVER
+#define WINVER 0x0501
+#undef WIN32_LEAN_AND_MEAN
+
+#include "cubeb-internal.h"
+#include "cubeb/cubeb.h"
+#include <malloc.h>
+#include <math.h>
+#include <process.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <windows.h>
+
+/* clang-format off */
+/* These need to be included after windows.h */
+#include <mmreg.h>
+#include <mmsystem.h>
+/* clang-format on */
+
+/* This is missing from the MinGW headers. Use a safe fallback. */
+#if !defined(MEMORY_ALLOCATION_ALIGNMENT)
+#define MEMORY_ALLOCATION_ALIGNMENT 16
+#endif
+
+/**This is also missing from the MinGW headers. It also appears to be
+ * undocumented by Microsoft.*/
+#ifndef WAVE_FORMAT_48M08
+#define WAVE_FORMAT_48M08 0x00001000 /* 48 kHz, Mono, 8-bit */
+#endif
+#ifndef WAVE_FORMAT_48M16
+#define WAVE_FORMAT_48M16 0x00002000 /* 48 kHz, Mono, 16-bit */
+#endif
+#ifndef WAVE_FORMAT_48S08
+#define WAVE_FORMAT_48S08 0x00004000 /* 48 kHz, Stereo, 8-bit */
+#endif
+#ifndef WAVE_FORMAT_48S16
+#define WAVE_FORMAT_48S16 0x00008000 /* 48 kHz, Stereo, 16-bit */
+#endif
+#ifndef WAVE_FORMAT_96M08
+#define WAVE_FORMAT_96M08 0x00010000 /* 96 kHz, Mono, 8-bit */
+#endif
+#ifndef WAVE_FORMAT_96M16
+#define WAVE_FORMAT_96M16 0x00020000 /* 96 kHz, Mono, 16-bit */
+#endif
+#ifndef WAVE_FORMAT_96S08
+#define WAVE_FORMAT_96S08 0x00040000 /* 96 kHz, Stereo, 8-bit */
+#endif
+#ifndef WAVE_FORMAT_96S16
+#define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */
+#endif
+
+/**Taken from winbase.h, also not in MinGW.*/
+#ifndef STACK_SIZE_PARAM_IS_A_RESERVATION
+#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 // Threads only
+#endif
+
+#ifndef DRVM_MAPPER
+#define DRVM_MAPPER (0x2000)
+#endif
+#ifndef DRVM_MAPPER_PREFERRED_GET
+#define DRVM_MAPPER_PREFERRED_GET (DRVM_MAPPER + 21)
+#endif
+#ifndef DRVM_MAPPER_CONSOLEVOICECOM_GET
+#define DRVM_MAPPER_CONSOLEVOICECOM_GET (DRVM_MAPPER + 23)
+#endif
+
+#define CUBEB_STREAM_MAX 32
+#define NBUFS 4
+
+struct cubeb_stream_item {
+ SLIST_ENTRY head;
+ cubeb_stream * stream;
+};
+
+static struct cubeb_ops const winmm_ops;
+
+struct cubeb {
+ struct cubeb_ops const * ops;
+ HANDLE event;
+ HANDLE thread;
+ int shutdown;
+ PSLIST_HEADER work;
+ CRITICAL_SECTION lock;
+ unsigned int active_streams;
+ unsigned int minimum_latency_ms;
+};
+
+struct cubeb_stream {
+ /* Note: Must match cubeb_stream layout in cubeb.c. */
+ cubeb * context;
+ void * user_ptr;
+ /**/
+ cubeb_stream_params params;
+ cubeb_data_callback data_callback;
+ cubeb_state_callback state_callback;
+ WAVEHDR buffers[NBUFS];
+ size_t buffer_size;
+ int next_buffer;
+ int free_buffers;
+ int shutdown;
+ int draining;
+ int error;
+ HANDLE event;
+ HWAVEOUT waveout;
+ CRITICAL_SECTION lock;
+ uint64_t written;
+ /* number of frames written during preroll */
+ uint64_t position_base;
+ float soft_volume;
+ /* For position wrap-around handling: */
+ size_t frame_size;
+ DWORD prev_pos_lo_dword;
+ DWORD pos_hi_dword;
+};
+
+static size_t
+bytes_per_frame(cubeb_stream_params params)
+{
+ size_t bytes;
+
+ switch (params.format) {
+ case CUBEB_SAMPLE_S16LE:
+ bytes = sizeof(signed short);
+ break;
+ case CUBEB_SAMPLE_FLOAT32LE:
+ bytes = sizeof(float);
+ break;
+ default:
+ XASSERT(0);
+ }
+
+ return bytes * params.channels;
+}
+
+static WAVEHDR *
+winmm_get_next_buffer(cubeb_stream * stm)
+{
+ WAVEHDR * hdr = NULL;
+
+ XASSERT(stm->free_buffers > 0 && stm->free_buffers <= NBUFS);
+ hdr = &stm->buffers[stm->next_buffer];
+ XASSERT(hdr->dwFlags & WHDR_PREPARED ||
+ (hdr->dwFlags & WHDR_DONE && !(hdr->dwFlags & WHDR_INQUEUE)));
+ stm->next_buffer = (stm->next_buffer + 1) % NBUFS;
+ stm->free_buffers -= 1;
+
+ return hdr;
+}
+
+static long
+preroll_callback(cubeb_stream * stream, void * user, const void * inputbuffer,
+ void * outputbuffer, long nframes)
+{
+ memset((uint8_t *)outputbuffer, 0, nframes * bytes_per_frame(stream->params));
+ return nframes;
+}
+
+static void
+winmm_refill_stream(cubeb_stream * stm)
+{
+ WAVEHDR * hdr;
+ long got;
+ long wanted;
+ MMRESULT r;
+
+ ALOG("winmm_refill_stream");
+
+ EnterCriticalSection(&stm->lock);
+ if (stm->error) {
+ LeaveCriticalSection(&stm->lock);
+ return;
+ }
+ stm->free_buffers += 1;
+ XASSERT(stm->free_buffers > 0 && stm->free_buffers <= NBUFS);
+
+ if (stm->draining) {
+ LeaveCriticalSection(&stm->lock);
+ if (stm->free_buffers == NBUFS) {
+ ALOG("winmm_refill_stream draining");
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+ }
+ SetEvent(stm->event);
+ return;
+ }
+
+ if (stm->shutdown) {
+ LeaveCriticalSection(&stm->lock);
+ SetEvent(stm->event);
+ return;
+ }
+
+ hdr = winmm_get_next_buffer(stm);
+
+ wanted = (DWORD)stm->buffer_size / bytes_per_frame(stm->params);
+
+ /* It is assumed that the caller is holding this lock. It must be dropped
+ during the callback to avoid deadlocks. */
+ LeaveCriticalSection(&stm->lock);
+ got = stm->data_callback(stm, stm->user_ptr, NULL, hdr->lpData, wanted);
+ EnterCriticalSection(&stm->lock);
+ if (got < 0) {
+ stm->error = 1;
+ LeaveCriticalSection(&stm->lock);
+ SetEvent(stm->event);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ return;
+ } else if (got < wanted) {
+ stm->draining = 1;
+ }
+ stm->written += got;
+
+ XASSERT(hdr->dwFlags & WHDR_PREPARED);
+
+ hdr->dwBufferLength = got * bytes_per_frame(stm->params);
+ XASSERT(hdr->dwBufferLength <= stm->buffer_size);
+
+ if (stm->soft_volume != -1.0) {
+ if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) {
+ float * b = (float *)hdr->lpData;
+ uint32_t i;
+ for (i = 0; i < got * stm->params.channels; i++) {
+ b[i] *= stm->soft_volume;
+ }
+ } else {
+ short * b = (short *)hdr->lpData;
+ uint32_t i;
+ for (i = 0; i < got * stm->params.channels; i++) {
+ b[i] = (short)(b[i] * stm->soft_volume);
+ }
+ }
+ }
+
+ r = waveOutWrite(stm->waveout, hdr, sizeof(*hdr));
+ if (r != MMSYSERR_NOERROR) {
+ LeaveCriticalSection(&stm->lock);
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+ return;
+ }
+
+ ALOG("winmm_refill_stream %ld frames", got);
+
+ LeaveCriticalSection(&stm->lock);
+}
+
+static unsigned __stdcall winmm_buffer_thread(void * user_ptr)
+{
+ cubeb * ctx = (cubeb *)user_ptr;
+ XASSERT(ctx);
+
+ for (;;) {
+ DWORD r;
+ PSLIST_ENTRY item;
+
+ r = WaitForSingleObject(ctx->event, INFINITE);
+ XASSERT(r == WAIT_OBJECT_0);
+
+ /* Process work items in batches so that a single stream can't
+ starve the others by continuously adding new work to the top of
+ the work item stack. */
+ item = InterlockedFlushSList(ctx->work);
+ while (item != NULL) {
+ PSLIST_ENTRY tmp = item;
+ winmm_refill_stream(((struct cubeb_stream_item *)tmp)->stream);
+ item = item->Next;
+ _aligned_free(tmp);
+ }
+
+ if (ctx->shutdown) {
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static void CALLBACK
+winmm_buffer_callback(HWAVEOUT waveout, UINT msg, DWORD_PTR user_ptr,
+ DWORD_PTR p1, DWORD_PTR p2)
+{
+ cubeb_stream * stm = (cubeb_stream *)user_ptr;
+ struct cubeb_stream_item * item;
+
+ if (msg != WOM_DONE) {
+ return;
+ }
+
+ item = _aligned_malloc(sizeof(struct cubeb_stream_item),
+ MEMORY_ALLOCATION_ALIGNMENT);
+ XASSERT(item);
+ item->stream = stm;
+ InterlockedPushEntrySList(stm->context->work, &item->head);
+
+ SetEvent(stm->context->event);
+}
+
+static unsigned int
+calculate_minimum_latency(void)
+{
+ OSVERSIONINFOEX osvi;
+ DWORDLONG mask;
+
+ /* Running under Terminal Services results in underruns with low latency. */
+ if (GetSystemMetrics(SM_REMOTESESSION) == TRUE) {
+ return 500;
+ }
+
+ /* Vista's WinMM implementation underruns when less than 200ms of audio is
+ * buffered. */
+ memset(&osvi, 0, sizeof(OSVERSIONINFOEX));
+ osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+ osvi.dwMajorVersion = 6;
+ osvi.dwMinorVersion = 0;
+
+ mask = 0;
+ VER_SET_CONDITION(mask, VER_MAJORVERSION, VER_EQUAL);
+ VER_SET_CONDITION(mask, VER_MINORVERSION, VER_EQUAL);
+
+ if (VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, mask) !=
+ 0) {
+ return 200;
+ }
+
+ return 100;
+}
+
+static void
+winmm_destroy(cubeb * ctx);
+
+/*static*/ int
+winmm_init(cubeb ** context, char const * context_name)
+{
+ cubeb * ctx;
+
+ XASSERT(context);
+ *context = NULL;
+
+ /* Don't initialize a context if there are no devices available. */
+ if (waveOutGetNumDevs() == 0) {
+ return CUBEB_ERROR;
+ }
+
+ ctx = calloc(1, sizeof(*ctx));
+ XASSERT(ctx);
+
+ ctx->ops = &winmm_ops;
+
+ ctx->work = _aligned_malloc(sizeof(*ctx->work), MEMORY_ALLOCATION_ALIGNMENT);
+ XASSERT(ctx->work);
+ InitializeSListHead(ctx->work);
+
+ ctx->event = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (!ctx->event) {
+ winmm_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ ctx->thread =
+ (HANDLE)_beginthreadex(NULL, 256 * 1024, winmm_buffer_thread, ctx,
+ STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
+ if (!ctx->thread) {
+ winmm_destroy(ctx);
+ return CUBEB_ERROR;
+ }
+
+ SetThreadPriority(ctx->thread, THREAD_PRIORITY_TIME_CRITICAL);
+
+ InitializeCriticalSection(&ctx->lock);
+ ctx->active_streams = 0;
+
+ ctx->minimum_latency_ms = calculate_minimum_latency();
+
+ *context = ctx;
+
+ return CUBEB_OK;
+}
+
+static char const *
+winmm_get_backend_id(cubeb * ctx)
+{
+ return "winmm";
+}
+
+static void
+winmm_destroy(cubeb * ctx)
+{
+ DWORD r;
+
+ XASSERT(ctx->active_streams == 0);
+ XASSERT(!InterlockedPopEntrySList(ctx->work));
+
+ DeleteCriticalSection(&ctx->lock);
+
+ if (ctx->thread) {
+ ctx->shutdown = 1;
+ SetEvent(ctx->event);
+ r = WaitForSingleObject(ctx->thread, INFINITE);
+ XASSERT(r == WAIT_OBJECT_0);
+ CloseHandle(ctx->thread);
+ }
+
+ if (ctx->event) {
+ CloseHandle(ctx->event);
+ }
+
+ _aligned_free(ctx->work);
+
+ free(ctx);
+}
+
+static void
+winmm_stream_destroy(cubeb_stream * stm);
+
+static int
+winmm_stream_init(cubeb * context, cubeb_stream ** stream,
+ char const * stream_name, cubeb_devid input_device,
+ cubeb_stream_params * input_stream_params,
+ cubeb_devid output_device,
+ cubeb_stream_params * output_stream_params,
+ unsigned int latency_frames,
+ cubeb_data_callback data_callback,
+ cubeb_state_callback state_callback, void * user_ptr)
+{
+ MMRESULT r;
+ WAVEFORMATEXTENSIBLE wfx;
+ cubeb_stream * stm;
+ int i;
+ size_t bufsz;
+
+ XASSERT(context);
+ XASSERT(stream);
+ XASSERT(output_stream_params);
+
+ if (input_stream_params) {
+ /* Capture support not yet implemented. */
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ if (input_device || output_device) {
+ /* Device selection not yet implemented. */
+ return CUBEB_ERROR_DEVICE_UNAVAILABLE;
+ }
+
+ if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
+ /* Loopback is not supported */
+ return CUBEB_ERROR_NOT_SUPPORTED;
+ }
+
+ *stream = NULL;
+
+ memset(&wfx, 0, sizeof(wfx));
+ if (output_stream_params->channels > 2) {
+ wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ wfx.Format.cbSize = sizeof(wfx) - sizeof(wfx.Format);
+ } else {
+ wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
+ if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE) {
+ wfx.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
+ }
+ wfx.Format.cbSize = 0;
+ }
+ wfx.Format.nChannels = output_stream_params->channels;
+ wfx.Format.nSamplesPerSec = output_stream_params->rate;
+
+ /* XXX fix channel mappings */
+ wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
+
+ switch (output_stream_params->format) {
+ case CUBEB_SAMPLE_S16LE:
+ wfx.Format.wBitsPerSample = 16;
+ wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+ break;
+ case CUBEB_SAMPLE_FLOAT32LE:
+ wfx.Format.wBitsPerSample = 32;
+ wfx.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+ break;
+ default:
+ return CUBEB_ERROR_INVALID_FORMAT;
+ }
+
+ wfx.Format.nBlockAlign =
+ (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8;
+ wfx.Format.nAvgBytesPerSec =
+ wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
+ wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample;
+
+ EnterCriticalSection(&context->lock);
+ /* CUBEB_STREAM_MAX is a horrible hack to avoid a situation where, when
+ many streams are active at once, a subset of them will not consume (via
+ playback) or release (via waveOutReset) their buffers. */
+ if (context->active_streams >= CUBEB_STREAM_MAX) {
+ LeaveCriticalSection(&context->lock);
+ return CUBEB_ERROR;
+ }
+ context->active_streams += 1;
+ LeaveCriticalSection(&context->lock);
+
+ stm = calloc(1, sizeof(*stm));
+ XASSERT(stm);
+
+ stm->context = context;
+
+ stm->params = *output_stream_params;
+
+ // Data callback is set to the user-provided data callback after
+ // the initialization and potential preroll callback calls are done, because
+ // cubeb users don't expect the data callback to be called during
+ // initialization.
+ stm->data_callback = preroll_callback;
+ stm->state_callback = state_callback;
+ stm->user_ptr = user_ptr;
+ stm->written = 0;
+
+ uint32_t latency_ms = latency_frames * 1000 / output_stream_params->rate;
+
+ if (latency_ms < context->minimum_latency_ms) {
+ latency_ms = context->minimum_latency_ms;
+ }
+
+ bufsz = (size_t)(stm->params.rate / 1000.0 * latency_ms *
+ bytes_per_frame(stm->params) / NBUFS);
+ if (bufsz % bytes_per_frame(stm->params) != 0) {
+ bufsz +=
+ bytes_per_frame(stm->params) - (bufsz % bytes_per_frame(stm->params));
+ }
+ XASSERT(bufsz % bytes_per_frame(stm->params) == 0);
+
+ stm->buffer_size = bufsz;
+
+ InitializeCriticalSection(&stm->lock);
+
+ stm->event = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (!stm->event) {
+ winmm_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ stm->soft_volume = -1.0;
+
+ /* winmm_buffer_callback will be called during waveOutOpen, so all
+ other initialization must be complete before calling it. */
+ r = waveOutOpen(&stm->waveout, WAVE_MAPPER, &wfx.Format,
+ (DWORD_PTR)winmm_buffer_callback, (DWORD_PTR)stm,
+ CALLBACK_FUNCTION);
+ if (r != MMSYSERR_NOERROR) {
+ winmm_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ r = waveOutPause(stm->waveout);
+ if (r != MMSYSERR_NOERROR) {
+ winmm_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ for (i = 0; i < NBUFS; ++i) {
+ WAVEHDR * hdr = &stm->buffers[i];
+
+ hdr->lpData = calloc(1, bufsz);
+ XASSERT(hdr->lpData);
+ hdr->dwBufferLength = bufsz;
+ hdr->dwFlags = 0;
+
+ r = waveOutPrepareHeader(stm->waveout, hdr, sizeof(*hdr));
+ if (r != MMSYSERR_NOERROR) {
+ winmm_stream_destroy(stm);
+ return CUBEB_ERROR;
+ }
+
+ winmm_refill_stream(stm);
+ }
+
+ stm->frame_size = bytes_per_frame(stm->params);
+ stm->prev_pos_lo_dword = 0;
+ stm->pos_hi_dword = 0;
+ // Set the user data callback now that preroll has finished.
+ stm->data_callback = data_callback;
+ stm->position_base = 0;
+
+ // Offset the position by the number of frames written during preroll.
+ stm->position_base = stm->written;
+ stm->written = 0;
+
+ *stream = stm;
+
+ LOG("winmm_stream_init OK");
+
+ return CUBEB_OK;
+}
+
+static void
+winmm_stream_destroy(cubeb_stream * stm)
+{
+ int i;
+
+ if (stm->waveout) {
+ MMTIME time;
+ MMRESULT r;
+ int device_valid;
+ int enqueued;
+
+ EnterCriticalSection(&stm->lock);
+ stm->shutdown = 1;
+
+ waveOutReset(stm->waveout);
+
+ /* Don't need this value, we just want the result to detect invalid
+ handle/no device errors than waveOutReset doesn't seem to report. */
+ time.wType = TIME_SAMPLES;
+ r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
+ device_valid = !(r == MMSYSERR_INVALHANDLE || r == MMSYSERR_NODRIVER);
+
+ enqueued = NBUFS - stm->free_buffers;
+ LeaveCriticalSection(&stm->lock);
+
+ /* Wait for all blocks to complete. */
+ while (device_valid && enqueued > 0 && !stm->error) {
+ DWORD rv = WaitForSingleObject(stm->event, INFINITE);
+ XASSERT(rv == WAIT_OBJECT_0);
+
+ EnterCriticalSection(&stm->lock);
+ enqueued = NBUFS - stm->free_buffers;
+ LeaveCriticalSection(&stm->lock);
+ }
+
+ EnterCriticalSection(&stm->lock);
+
+ for (i = 0; i < NBUFS; ++i) {
+ if (stm->buffers[i].dwFlags & WHDR_PREPARED) {
+ waveOutUnprepareHeader(stm->waveout, &stm->buffers[i],
+ sizeof(stm->buffers[i]));
+ }
+ }
+
+ waveOutClose(stm->waveout);
+
+ LeaveCriticalSection(&stm->lock);
+ }
+
+ if (stm->event) {
+ CloseHandle(stm->event);
+ }
+
+ DeleteCriticalSection(&stm->lock);
+
+ for (i = 0; i < NBUFS; ++i) {
+ free(stm->buffers[i].lpData);
+ }
+
+ EnterCriticalSection(&stm->context->lock);
+ XASSERT(stm->context->active_streams >= 1);
+ stm->context->active_streams -= 1;
+ LeaveCriticalSection(&stm->context->lock);
+
+ free(stm);
+}
+
+static int
+winmm_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
+{
+ XASSERT(ctx && max_channels);
+
+ /* We don't support more than two channels in this backend. */
+ *max_channels = 2;
+
+ return CUBEB_OK;
+}
+
+static int
+winmm_get_min_latency(cubeb * ctx, cubeb_stream_params params,
+ uint32_t * latency)
+{
+ // 100ms minimum, if we are not in a bizarre configuration.
+ *latency = ctx->minimum_latency_ms * params.rate / 1000;
+
+ return CUBEB_OK;
+}
+
+static int
+winmm_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
+{
+ WAVEOUTCAPS woc;
+ MMRESULT r;
+
+ r = waveOutGetDevCaps(WAVE_MAPPER, &woc, sizeof(WAVEOUTCAPS));
+ if (r != MMSYSERR_NOERROR) {
+ return CUBEB_ERROR;
+ }
+
+ /* Check if we support 48kHz, but not 44.1kHz. */
+ if (!(woc.dwFormats & WAVE_FORMAT_4S16) &&
+ woc.dwFormats & WAVE_FORMAT_48S16) {
+ *rate = 48000;
+ return CUBEB_OK;
+ }
+ /* Prefer 44.1kHz between 44.1kHz and 48kHz. */
+ *rate = 44100;
+
+ return CUBEB_OK;
+}
+
+static int
+winmm_stream_start(cubeb_stream * stm)
+{
+ MMRESULT r;
+
+ EnterCriticalSection(&stm->lock);
+ r = waveOutRestart(stm->waveout);
+ LeaveCriticalSection(&stm->lock);
+
+ if (r != MMSYSERR_NOERROR) {
+ return CUBEB_ERROR;
+ }
+
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
+
+ return CUBEB_OK;
+}
+
+static int
+winmm_stream_stop(cubeb_stream * stm)
+{
+ MMRESULT r;
+
+ EnterCriticalSection(&stm->lock);
+ r = waveOutPause(stm->waveout);
+ LeaveCriticalSection(&stm->lock);
+
+ if (r != MMSYSERR_NOERROR) {
+ return CUBEB_ERROR;
+ }
+
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
+
+ return CUBEB_OK;
+}
+
+/*
+Microsoft wave audio docs say "samples are the preferred time format in which
+to represent the current position", but relying on this causes problems on
+Windows XP, the only OS cubeb_winmm is used on.
+
+While the wdmaud.sys driver internally tracks a 64-bit position and ensures no
+backward movement, the WinMM API limits the position returned from
+waveOutGetPosition() to a 32-bit DWORD (this applies equally to XP x64). The
+higher 32 bits are chopped off, and to an API consumer the position can appear
+to move backward.
+
+In theory, even a 32-bit TIME_SAMPLES position should provide plenty of
+playback time for typical use cases before this pseudo wrap-around, e.g:
+ (2^32 - 1)/48000 = ~24:51:18 for 48.0 kHz stereo;
+ (2^32 - 1)/44100 = ~27:03:12 for 44.1 kHz stereo.
+In reality, wdmaud.sys doesn't provide a TIME_SAMPLES position at all, only a
+32-bit TIME_BYTES position, from which wdmaud.drv derives TIME_SAMPLES:
+ SamplePos = (BytePos * 8) / BitsPerFrame,
+ where BitsPerFrame = Channels * BitsPerSample,
+Per dom\media\AudioSampleFormat.h, desktop builds always use 32-bit FLOAT32
+samples, so the maximum for TIME_SAMPLES should be:
+ (2^29 - 1)/48000 = ~03:06:25;
+ (2^29 - 1)/44100 = ~03:22:54.
+This might still be OK for typical browser usage, but there's also a bug in the
+formula above: BytePos * 8 (BytePos << 3) is done on a 32-bit BytePos, without
+first casting it to 64 bits, so the highest 3 bits, if set, would get shifted
+out, and the maximum possible TIME_SAMPLES drops unacceptably low:
+ (2^26 - 1)/48000 = ~00:23:18;
+ (2^26 - 1)/44100 = ~00:25:22.
+
+To work around these limitations, we just get the position in TIME_BYTES,
+recover the 64-bit value, and do our own conversion to samples.
+*/
+
+/* Convert chopped 32-bit waveOutGetPosition() into 64-bit true position. */
+static uint64_t
+update_64bit_position(cubeb_stream * stm, DWORD pos_lo_dword)
+{
+ /* Caller should be holding stm->lock. */
+ if (pos_lo_dword < stm->prev_pos_lo_dword) {
+ stm->pos_hi_dword++;
+ LOG("waveOutGetPosition() has wrapped around: %#lx -> %#lx",
+ stm->prev_pos_lo_dword, pos_lo_dword);
+ LOG("Wrap-around count = %#lx", stm->pos_hi_dword);
+ LOG("Current 64-bit position = %#llx",
+ (((uint64_t)stm->pos_hi_dword) << 32) | ((uint64_t)pos_lo_dword));
+ }
+ stm->prev_pos_lo_dword = pos_lo_dword;
+
+ return (((uint64_t)stm->pos_hi_dword) << 32) | ((uint64_t)pos_lo_dword);
+}
+
+static int
+winmm_stream_get_position(cubeb_stream * stm, uint64_t * position)
+{
+ MMRESULT r;
+ MMTIME time;
+
+ EnterCriticalSection(&stm->lock);
+ /* See the long comment above for why not just use TIME_SAMPLES here. */
+ time.wType = TIME_BYTES;
+ r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
+
+ if (r != MMSYSERR_NOERROR || time.wType != TIME_BYTES) {
+ LeaveCriticalSection(&stm->lock);
+ return CUBEB_ERROR;
+ }
+
+ uint64_t position_not_adjusted =
+ update_64bit_position(stm, time.u.cb) / stm->frame_size;
+
+ // Subtract the number of frames that were written while prerolling, during
+ // initialization.
+ if (position_not_adjusted < stm->position_base) {
+ *position = 0;
+ } else {
+ *position = position_not_adjusted - stm->position_base;
+ }
+
+ LeaveCriticalSection(&stm->lock);
+
+ return CUBEB_OK;
+}
+
+static int
+winmm_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
+{
+ MMRESULT r;
+ MMTIME time;
+ uint64_t written, position;
+
+ int rv = winmm_stream_get_position(stm, &position);
+ if (rv != CUBEB_OK) {
+ return rv;
+ }
+
+ EnterCriticalSection(&stm->lock);
+ written = stm->written;
+ LeaveCriticalSection(&stm->lock);
+
+ XASSERT((written - (position / stm->frame_size)) <= UINT32_MAX);
+ *latency = (uint32_t)(written - (position / stm->frame_size));
+
+ return CUBEB_OK;
+}
+
+static int
+winmm_stream_set_volume(cubeb_stream * stm, float volume)
+{
+ EnterCriticalSection(&stm->lock);
+ stm->soft_volume = volume;
+ LeaveCriticalSection(&stm->lock);
+ return CUBEB_OK;
+}
+
+#define MM_11025HZ_MASK \
+ (WAVE_FORMAT_1M08 | WAVE_FORMAT_1M16 | WAVE_FORMAT_1S08 | WAVE_FORMAT_1S16)
+#define MM_22050HZ_MASK \
+ (WAVE_FORMAT_2M08 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S08 | WAVE_FORMAT_2S16)
+#define MM_44100HZ_MASK \
+ (WAVE_FORMAT_4M08 | WAVE_FORMAT_4M16 | WAVE_FORMAT_4S08 | WAVE_FORMAT_4S16)
+#define MM_48000HZ_MASK \
+ (WAVE_FORMAT_48M08 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S08 | \
+ WAVE_FORMAT_48S16)
+#define MM_96000HZ_MASK \
+ (WAVE_FORMAT_96M08 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S08 | \
+ WAVE_FORMAT_96S16)
+static void
+winmm_calculate_device_rate(cubeb_device_info * info, DWORD formats)
+{
+ if (formats & MM_11025HZ_MASK) {
+ info->min_rate = 11025;
+ info->default_rate = 11025;
+ info->max_rate = 11025;
+ }
+ if (formats & MM_22050HZ_MASK) {
+ if (info->min_rate == 0)
+ info->min_rate = 22050;
+ info->max_rate = 22050;
+ info->default_rate = 22050;
+ }
+ if (formats & MM_44100HZ_MASK) {
+ if (info->min_rate == 0)
+ info->min_rate = 44100;
+ info->max_rate = 44100;
+ info->default_rate = 44100;
+ }
+ if (formats & MM_48000HZ_MASK) {
+ if (info->min_rate == 0)
+ info->min_rate = 48000;
+ info->max_rate = 48000;
+ info->default_rate = 48000;
+ }
+ if (formats & MM_96000HZ_MASK) {
+ if (info->min_rate == 0) {
+ info->min_rate = 96000;
+ info->default_rate = 96000;
+ }
+ info->max_rate = 96000;
+ }
+}
+
+#define MM_S16_MASK \
+ (WAVE_FORMAT_1M16 | WAVE_FORMAT_1S16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S16 | \
+ WAVE_FORMAT_4M16 | WAVE_FORMAT_4S16 | WAVE_FORMAT_48M16 | \
+ WAVE_FORMAT_48S16 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S16)
+static int
+winmm_query_supported_formats(UINT devid, DWORD formats,
+ cubeb_device_fmt * supfmt,
+ cubeb_device_fmt * deffmt)
+{
+ WAVEFORMATEXTENSIBLE wfx;
+
+ if (formats & MM_S16_MASK)
+ *deffmt = *supfmt = CUBEB_DEVICE_FMT_S16LE;
+ else
+ *deffmt = *supfmt = 0;
+
+ ZeroMemory(&wfx, sizeof(WAVEFORMATEXTENSIBLE));
+ wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ wfx.Format.nChannels = 2;
+ wfx.Format.nSamplesPerSec = 44100;
+ wfx.Format.wBitsPerSample = 32;
+ wfx.Format.nBlockAlign =
+ (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8;
+ wfx.Format.nAvgBytesPerSec =
+ wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
+ wfx.Format.cbSize = 22;
+ wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample;
+ wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
+ wfx.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+ if (waveOutOpen(NULL, devid, &wfx.Format, 0, 0, WAVE_FORMAT_QUERY) ==
+ MMSYSERR_NOERROR)
+ *supfmt = (cubeb_device_fmt)(*supfmt | CUBEB_DEVICE_FMT_F32LE);
+
+ return (*deffmt != 0) ? CUBEB_OK : CUBEB_ERROR;
+}
+
+static char *
+guid_to_cstr(LPGUID guid)
+{
+ char * ret = malloc(40);
+ if (!ret) {
+ return NULL;
+ }
+ _snprintf_s(ret, 40, _TRUNCATE,
+ "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", guid->Data1,
+ guid->Data2, guid->Data3, guid->Data4[0], guid->Data4[1],
+ guid->Data4[2], guid->Data4[3], guid->Data4[4], guid->Data4[5],
+ guid->Data4[6], guid->Data4[7]);
+ return ret;
+}
+
+static cubeb_device_pref
+winmm_query_preferred_out_device(UINT devid)
+{
+ DWORD mmpref = WAVE_MAPPER, compref = WAVE_MAPPER, status;
+ cubeb_device_pref ret = CUBEB_DEVICE_PREF_NONE;
+
+ if (waveOutMessage((HWAVEOUT)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET,
+ (DWORD_PTR)&mmpref,
+ (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
+ devid == mmpref)
+ ret |= CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION;
+
+ if (waveOutMessage((HWAVEOUT)WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET,
+ (DWORD_PTR)&compref,
+ (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
+ devid == compref)
+ ret |= CUBEB_DEVICE_PREF_VOICE;
+
+ return ret;
+}
+
+static char *
+device_id_idx(UINT devid)
+{
+ char * ret = malloc(16);
+ if (!ret) {
+ return NULL;
+ }
+ _snprintf_s(ret, 16, _TRUNCATE, "%u", devid);
+ return ret;
+}
+
+static void
+winmm_create_device_from_outcaps2(cubeb_device_info * ret, LPWAVEOUTCAPS2A caps,
+ UINT devid)
+{
+ XASSERT(ret);
+ ret->devid = (cubeb_devid)devid;
+ ret->device_id = device_id_idx(devid);
+ ret->friendly_name = _strdup(caps->szPname);
+ ret->group_id = guid_to_cstr(&caps->ProductGuid);
+ ret->vendor_name = guid_to_cstr(&caps->ManufacturerGuid);
+
+ ret->type = CUBEB_DEVICE_TYPE_OUTPUT;
+ ret->state = CUBEB_DEVICE_STATE_ENABLED;
+ ret->preferred = winmm_query_preferred_out_device(devid);
+
+ ret->max_channels = caps->wChannels;
+ winmm_calculate_device_rate(ret, caps->dwFormats);
+ winmm_query_supported_formats(devid, caps->dwFormats, &ret->format,
+ &ret->default_format);
+
+ /* Hardcoded latency estimates... */
+ ret->latency_lo = 100 * ret->default_rate / 1000;
+ ret->latency_hi = 200 * ret->default_rate / 1000;
+}
+
+static void
+winmm_create_device_from_outcaps(cubeb_device_info * ret, LPWAVEOUTCAPSA caps,
+ UINT devid)
+{
+ XASSERT(ret);
+ ret->devid = (cubeb_devid)devid;
+ ret->device_id = device_id_idx(devid);
+ ret->friendly_name = _strdup(caps->szPname);
+ ret->group_id = NULL;
+ ret->vendor_name = NULL;
+
+ ret->type = CUBEB_DEVICE_TYPE_OUTPUT;
+ ret->state = CUBEB_DEVICE_STATE_ENABLED;
+ ret->preferred = winmm_query_preferred_out_device(devid);
+
+ ret->max_channels = caps->wChannels;
+ winmm_calculate_device_rate(ret, caps->dwFormats);
+ winmm_query_supported_formats(devid, caps->dwFormats, &ret->format,
+ &ret->default_format);
+
+ /* Hardcoded latency estimates... */
+ ret->latency_lo = 100 * ret->default_rate / 1000;
+ ret->latency_hi = 200 * ret->default_rate / 1000;
+}
+
+static cubeb_device_pref
+winmm_query_preferred_in_device(UINT devid)
+{
+ DWORD mmpref = WAVE_MAPPER, compref = WAVE_MAPPER, status;
+ cubeb_device_pref ret = CUBEB_DEVICE_PREF_NONE;
+
+ if (waveInMessage((HWAVEIN)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET,
+ (DWORD_PTR)&mmpref,
+ (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
+ devid == mmpref)
+ ret |= CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION;
+
+ if (waveInMessage((HWAVEIN)WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET,
+ (DWORD_PTR)&compref,
+ (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
+ devid == compref)
+ ret |= CUBEB_DEVICE_PREF_VOICE;
+
+ return ret;
+}
+
+static void
+winmm_create_device_from_incaps2(cubeb_device_info * ret, LPWAVEINCAPS2A caps,
+ UINT devid)
+{
+ XASSERT(ret);
+ ret->devid = (cubeb_devid)devid;
+ ret->device_id = device_id_idx(devid);
+ ret->friendly_name = _strdup(caps->szPname);
+ ret->group_id = guid_to_cstr(&caps->ProductGuid);
+ ret->vendor_name = guid_to_cstr(&caps->ManufacturerGuid);
+
+ ret->type = CUBEB_DEVICE_TYPE_INPUT;
+ ret->state = CUBEB_DEVICE_STATE_ENABLED;
+ ret->preferred = winmm_query_preferred_in_device(devid);
+
+ ret->max_channels = caps->wChannels;
+ winmm_calculate_device_rate(ret, caps->dwFormats);
+ winmm_query_supported_formats(devid, caps->dwFormats, &ret->format,
+ &ret->default_format);
+
+ /* Hardcoded latency estimates... */
+ ret->latency_lo = 100 * ret->default_rate / 1000;
+ ret->latency_hi = 200 * ret->default_rate / 1000;
+}
+
+static void
+winmm_create_device_from_incaps(cubeb_device_info * ret, LPWAVEINCAPSA caps,
+ UINT devid)
+{
+ XASSERT(ret);
+ ret->devid = (cubeb_devid)devid;
+ ret->device_id = device_id_idx(devid);
+ ret->friendly_name = _strdup(caps->szPname);
+ ret->group_id = NULL;
+ ret->vendor_name = NULL;
+
+ ret->type = CUBEB_DEVICE_TYPE_INPUT;
+ ret->state = CUBEB_DEVICE_STATE_ENABLED;
+ ret->preferred = winmm_query_preferred_in_device(devid);
+
+ ret->max_channels = caps->wChannels;
+ winmm_calculate_device_rate(ret, caps->dwFormats);
+ winmm_query_supported_formats(devid, caps->dwFormats, &ret->format,
+ &ret->default_format);
+
+ /* Hardcoded latency estimates... */
+ ret->latency_lo = 100 * ret->default_rate / 1000;
+ ret->latency_hi = 200 * ret->default_rate / 1000;
+}
+
+static int
+winmm_enumerate_devices(cubeb * context, cubeb_device_type type,
+ cubeb_device_collection * collection)
+{
+ UINT i, incount, outcount, total;
+ cubeb_device_info * devices;
+ cubeb_device_info * dev;
+
+ outcount = waveOutGetNumDevs();
+ incount = waveInGetNumDevs();
+ total = outcount + incount;
+
+ devices = calloc(total, sizeof(cubeb_device_info));
+ collection->count = 0;
+
+ if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
+ WAVEOUTCAPSA woc;
+ WAVEOUTCAPS2A woc2;
+
+ ZeroMemory(&woc, sizeof(woc));
+ ZeroMemory(&woc2, sizeof(woc2));
+
+ for (i = 0; i < outcount; i++) {
+ dev = &devices[collection->count];
+ if (waveOutGetDevCapsA(i, (LPWAVEOUTCAPSA)&woc2, sizeof(woc2)) ==
+ MMSYSERR_NOERROR) {
+ winmm_create_device_from_outcaps2(dev, &woc2, i);
+ collection->count += 1;
+ } else if (waveOutGetDevCapsA(i, &woc, sizeof(woc)) == MMSYSERR_NOERROR) {
+ winmm_create_device_from_outcaps(dev, &woc, i);
+ collection->count += 1;
+ }
+ }
+ }
+
+ if (type & CUBEB_DEVICE_TYPE_INPUT) {
+ WAVEINCAPSA wic;
+ WAVEINCAPS2A wic2;
+
+ ZeroMemory(&wic, sizeof(wic));
+ ZeroMemory(&wic2, sizeof(wic2));
+
+ for (i = 0; i < incount; i++) {
+ dev = &devices[collection->count];
+ if (waveInGetDevCapsA(i, (LPWAVEINCAPSA)&wic2, sizeof(wic2)) ==
+ MMSYSERR_NOERROR) {
+ winmm_create_device_from_incaps2(dev, &wic2, i);
+ collection->count += 1;
+ } else if (waveInGetDevCapsA(i, &wic, sizeof(wic)) == MMSYSERR_NOERROR) {
+ winmm_create_device_from_incaps(dev, &wic, i);
+ collection->count += 1;
+ }
+ }
+ }
+
+ collection->device = devices;
+
+ return CUBEB_OK;
+}
+
+static int
+winmm_device_collection_destroy(cubeb * ctx,
+ cubeb_device_collection * collection)
+{
+ uint32_t i;
+ XASSERT(collection);
+
+ (void)ctx;
+
+ for (i = 0; i < collection->count; i++) {
+ free((void *)collection->device[i].device_id);
+ free((void *)collection->device[i].friendly_name);
+ free((void *)collection->device[i].group_id);
+ free((void *)collection->device[i].vendor_name);
+ }
+
+ free(collection->device);
+ return CUBEB_OK;
+}
+
+static struct cubeb_ops const winmm_ops = {
+ /*.init =*/winmm_init,
+ /*.get_backend_id =*/winmm_get_backend_id,
+ /*.get_max_channel_count=*/winmm_get_max_channel_count,
+ /*.get_min_latency=*/winmm_get_min_latency,
+ /*.get_preferred_sample_rate =*/winmm_get_preferred_sample_rate,
+ /*.get_supported_input_processing_params =*/NULL,
+ /*.enumerate_devices =*/winmm_enumerate_devices,
+ /*.device_collection_destroy =*/winmm_device_collection_destroy,
+ /*.destroy =*/winmm_destroy,
+ /*.stream_init =*/winmm_stream_init,
+ /*.stream_destroy =*/winmm_stream_destroy,
+ /*.stream_start =*/winmm_stream_start,
+ /*.stream_stop =*/winmm_stream_stop,
+ /*.stream_get_position =*/winmm_stream_get_position,
+ /*.stream_get_latency = */ winmm_stream_get_latency,
+ /*.stream_get_input_latency = */ NULL,
+ /*.stream_set_volume =*/winmm_stream_set_volume,
+ /*.stream_set_name =*/NULL,
+ /*.stream_get_current_device =*/NULL,
+ /*.stream_set_input_mute =*/NULL,
+ /*.stream_set_input_processing_params =*/NULL,
+ /*.stream_device_destroy =*/NULL,
+ /*.stream_register_device_changed_callback=*/NULL,
+ /*.register_device_collection_changed =*/NULL};
diff --git a/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/arch.h b/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/arch.h
new file mode 100644
index 0000000000..73a45a069f
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/arch.h
@@ -0,0 +1,235 @@
+/* Copyright (C) 2003 Jean-Marc Valin */
+/**
+ @file arch.h
+ @brief Various architecture definitions Speex
+*/
+/*
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ - Neither the name of the Xiph.org Foundation nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef ARCH_H
+#define ARCH_H
+
+/* A couple test to catch stupid option combinations */
+#ifdef FIXED_POINT
+
+#ifdef FLOATING_POINT
+#error You cannot compile as floating point and fixed point at the same time
+#endif
+#ifdef _USE_SSE
+#error SSE is only for floating-point
+#endif
+#if ((defined (ARM4_ASM)||defined (ARM4_ASM)) && defined(BFIN_ASM)) || (defined (ARM4_ASM)&&defined(ARM5E_ASM))
+#error Make up your mind. What CPU do you have?
+#endif
+#ifdef VORBIS_PSYCHO
+#error Vorbis-psy model currently not implemented in fixed-point
+#endif
+
+#else
+
+#ifndef FLOATING_POINT
+#error You now need to define either FIXED_POINT or FLOATING_POINT
+#endif
+#if defined (ARM4_ASM) || defined(ARM5E_ASM) || defined(BFIN_ASM)
+#error I suppose you can have a [ARM4/ARM5E/Blackfin] that has float instructions?
+#endif
+#ifdef FIXED_POINT_DEBUG
+#error "Don't you think enabling fixed-point is a good thing to do if you want to debug that?"
+#endif
+
+
+#endif
+
+#ifndef OUTSIDE_SPEEX
+#include "speex/speexdsp_types.h"
+#endif
+
+#define ABS(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute integer value. */
+#define ABS16(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute 16-bit value. */
+#define MIN16(a,b) ((a) < (b) ? (a) : (b)) /**< Maximum 16-bit value. */
+#define MAX16(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 16-bit value. */
+#define ABS32(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute 32-bit value. */
+#define MIN32(a,b) ((a) < (b) ? (a) : (b)) /**< Maximum 32-bit value. */
+#define MAX32(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 32-bit value. */
+
+#ifdef FIXED_POINT
+
+typedef spx_int16_t spx_word16_t;
+typedef spx_int32_t spx_word32_t;
+typedef spx_word32_t spx_mem_t;
+typedef spx_word16_t spx_coef_t;
+typedef spx_word16_t spx_lsp_t;
+typedef spx_word32_t spx_sig_t;
+
+#define Q15ONE 32767
+
+#define LPC_SCALING 8192
+#define SIG_SCALING 16384
+#define LSP_SCALING 8192.
+#define GAMMA_SCALING 32768.
+#define GAIN_SCALING 64
+#define GAIN_SCALING_1 0.015625
+
+#define LPC_SHIFT 13
+#define LSP_SHIFT 13
+#define SIG_SHIFT 14
+#define GAIN_SHIFT 6
+
+#define WORD2INT(x) ((x) < -32767 ? -32768 : ((x) > 32766 ? 32767 : (x)))
+
+#define VERY_SMALL 0
+#define VERY_LARGE32 ((spx_word32_t)2147483647)
+#define VERY_LARGE16 ((spx_word16_t)32767)
+#define Q15_ONE ((spx_word16_t)32767)
+
+
+#ifdef FIXED_DEBUG
+#include "fixed_debug.h"
+#else
+
+#include "fixed_generic.h"
+
+#ifdef ARM5E_ASM
+#include "fixed_arm5e.h"
+#elif defined (ARM4_ASM)
+#include "fixed_arm4.h"
+#elif defined (BFIN_ASM)
+#include "fixed_bfin.h"
+#endif
+
+#endif
+
+
+#else
+
+typedef float spx_mem_t;
+typedef float spx_coef_t;
+typedef float spx_lsp_t;
+typedef float spx_sig_t;
+typedef float spx_word16_t;
+typedef float spx_word32_t;
+
+#define Q15ONE 1.0f
+#define LPC_SCALING 1.f
+#define SIG_SCALING 1.f
+#define LSP_SCALING 1.f
+#define GAMMA_SCALING 1.f
+#define GAIN_SCALING 1.f
+#define GAIN_SCALING_1 1.f
+
+
+#define VERY_SMALL 1e-15f
+#define VERY_LARGE32 1e15f
+#define VERY_LARGE16 1e15f
+#define Q15_ONE ((spx_word16_t)1.f)
+
+#define QCONST16(x,bits) (x)
+#define QCONST32(x,bits) (x)
+
+#define NEG16(x) (-(x))
+#define NEG32(x) (-(x))
+#define EXTRACT16(x) (x)
+#define EXTEND32(x) (x)
+#define SHR16(a,shift) (a)
+#define SHL16(a,shift) (a)
+#define SHR32(a,shift) (a)
+#define SHL32(a,shift) (a)
+#define PSHR16(a,shift) (a)
+#define PSHR32(a,shift) (a)
+#define VSHR32(a,shift) (a)
+#define SATURATE16(x,a) (x)
+#define SATURATE32(x,a) (x)
+#define SATURATE32PSHR(x,shift,a) (x)
+
+#define PSHR(a,shift) (a)
+#define SHR(a,shift) (a)
+#define SHL(a,shift) (a)
+#define SATURATE(x,a) (x)
+
+#define ADD16(a,b) ((a)+(b))
+#define SUB16(a,b) ((a)-(b))
+#define ADD32(a,b) ((a)+(b))
+#define SUB32(a,b) ((a)-(b))
+#define MULT16_16_16(a,b) ((a)*(b))
+#define MULT16_16(a,b) ((spx_word32_t)(a)*(spx_word32_t)(b))
+#define MAC16_16(c,a,b) ((c)+(spx_word32_t)(a)*(spx_word32_t)(b))
+
+#define MULT16_32_Q11(a,b) ((a)*(b))
+#define MULT16_32_Q13(a,b) ((a)*(b))
+#define MULT16_32_Q14(a,b) ((a)*(b))
+#define MULT16_32_Q15(a,b) ((a)*(b))
+#define MULT16_32_P15(a,b) ((a)*(b))
+
+#define MAC16_32_Q11(c,a,b) ((c)+(a)*(b))
+#define MAC16_32_Q15(c,a,b) ((c)+(a)*(b))
+
+#define MAC16_16_Q11(c,a,b) ((c)+(a)*(b))
+#define MAC16_16_Q13(c,a,b) ((c)+(a)*(b))
+#define MAC16_16_P13(c,a,b) ((c)+(a)*(b))
+#define MULT16_16_Q11_32(a,b) ((a)*(b))
+#define MULT16_16_Q13(a,b) ((a)*(b))
+#define MULT16_16_Q14(a,b) ((a)*(b))
+#define MULT16_16_Q15(a,b) ((a)*(b))
+#define MULT16_16_P15(a,b) ((a)*(b))
+#define MULT16_16_P13(a,b) ((a)*(b))
+#define MULT16_16_P14(a,b) ((a)*(b))
+
+#define DIV32_16(a,b) (((spx_word32_t)(a))/(spx_word16_t)(b))
+#define PDIV32_16(a,b) (((spx_word32_t)(a))/(spx_word16_t)(b))
+#define DIV32(a,b) (((spx_word32_t)(a))/(spx_word32_t)(b))
+#define PDIV32(a,b) (((spx_word32_t)(a))/(spx_word32_t)(b))
+
+#define WORD2INT(x) ((x) < -32767.5f ? -32768 : \
+ ((x) > 32766.5f ? 32767 : (spx_int16_t)floor(.5 + (x))))
+#endif
+
+
+#if defined (CONFIG_TI_C54X) || defined (CONFIG_TI_C55X)
+
+/* 2 on TI C5x DSP */
+#define BYTES_PER_CHAR 2
+#define BITS_PER_CHAR 16
+#define LOG2_BITS_PER_CHAR 4
+
+#else
+
+#define BYTES_PER_CHAR 1
+#define BITS_PER_CHAR 8
+#define LOG2_BITS_PER_CHAR 3
+
+#endif
+
+
+
+#ifdef FIXED_DEBUG
+extern long long spx_mips;
+#endif
+
+
+#endif
diff --git a/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/fixed_generic.h b/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/fixed_generic.h
new file mode 100644
index 0000000000..12d27aac55
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/fixed_generic.h
@@ -0,0 +1,110 @@
+/* Copyright (C) 2003 Jean-Marc Valin */
+/**
+ @file fixed_generic.h
+ @brief Generic fixed-point operations
+*/
+/*
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ - Neither the name of the Xiph.org Foundation nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef FIXED_GENERIC_H
+#define FIXED_GENERIC_H
+
+#define QCONST16(x,bits) ((spx_word16_t)(.5+(x)*(((spx_word32_t)1)<<(bits))))
+#define QCONST32(x,bits) ((spx_word32_t)(.5+(x)*(((spx_word32_t)1)<<(bits))))
+
+#define NEG16(x) (-(x))
+#define NEG32(x) (-(x))
+#define EXTRACT16(x) ((spx_word16_t)(x))
+#define EXTEND32(x) ((spx_word32_t)(x))
+#define SHR16(a,shift) ((a) >> (shift))
+#define SHL16(a,shift) ((a) << (shift))
+#define SHR32(a,shift) ((a) >> (shift))
+#define SHL32(a,shift) ((a) << (shift))
+#define PSHR16(a,shift) (SHR16((a)+((1<<((shift))>>1)),shift))
+#define PSHR32(a,shift) (SHR32((a)+((EXTEND32(1)<<((shift))>>1)),shift))
+#define VSHR32(a, shift) (((shift)>0) ? SHR32(a, shift) : SHL32(a, -(shift)))
+#define SATURATE16(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x)))
+#define SATURATE32(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x)))
+
+#define SATURATE32PSHR(x,shift,a) (((x)>=(SHL32(a,shift))) ? (a) : \
+ (x)<=-(SHL32(a,shift)) ? -(a) : \
+ (PSHR32(x, shift)))
+
+#define SHR(a,shift) ((a) >> (shift))
+#define SHL(a,shift) ((spx_word32_t)(a) << (shift))
+#define PSHR(a,shift) (SHR((a)+((EXTEND32(1)<<((shift))>>1)),shift))
+#define SATURATE(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x)))
+
+
+#define ADD16(a,b) ((spx_word16_t)((spx_word16_t)(a)+(spx_word16_t)(b)))
+#define SUB16(a,b) ((spx_word16_t)(a)-(spx_word16_t)(b))
+#define ADD32(a,b) ((spx_word32_t)(a)+(spx_word32_t)(b))
+#define SUB32(a,b) ((spx_word32_t)(a)-(spx_word32_t)(b))
+
+
+/* result fits in 16 bits */
+#define MULT16_16_16(a,b) ((((spx_word16_t)(a))*((spx_word16_t)(b))))
+
+/* (spx_word32_t)(spx_word16_t) gives TI compiler a hint that it's 16x16->32 multiply */
+#define MULT16_16(a,b) (((spx_word32_t)(spx_word16_t)(a))*((spx_word32_t)(spx_word16_t)(b)))
+
+#define MAC16_16(c,a,b) (ADD32((c),MULT16_16((a),(b))))
+#define MULT16_32_Q12(a,b) ADD32(MULT16_16((a),SHR((b),12)), SHR(MULT16_16((a),((b)&0x00000fff)),12))
+#define MULT16_32_Q13(a,b) ADD32(MULT16_16((a),SHR((b),13)), SHR(MULT16_16((a),((b)&0x00001fff)),13))
+#define MULT16_32_Q14(a,b) ADD32(MULT16_16((a),SHR((b),14)), SHR(MULT16_16((a),((b)&0x00003fff)),14))
+
+#define MULT16_32_Q11(a,b) ADD32(MULT16_16((a),SHR((b),11)), SHR(MULT16_16((a),((b)&0x000007ff)),11))
+#define MAC16_32_Q11(c,a,b) ADD32(c,ADD32(MULT16_16((a),SHR((b),11)), SHR(MULT16_16((a),((b)&0x000007ff)),11)))
+
+#define MULT16_32_P15(a,b) ADD32(MULT16_16((a),SHR((b),15)), PSHR(MULT16_16((a),((b)&0x00007fff)),15))
+#define MULT16_32_Q15(a,b) ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15))
+#define MAC16_32_Q15(c,a,b) ADD32(c,ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15)))
+
+
+#define MAC16_16_Q11(c,a,b) (ADD32((c),SHR(MULT16_16((a),(b)),11)))
+#define MAC16_16_Q13(c,a,b) (ADD32((c),SHR(MULT16_16((a),(b)),13)))
+#define MAC16_16_P13(c,a,b) (ADD32((c),SHR(ADD32(4096,MULT16_16((a),(b))),13)))
+
+#define MULT16_16_Q11_32(a,b) (SHR(MULT16_16((a),(b)),11))
+#define MULT16_16_Q13(a,b) (SHR(MULT16_16((a),(b)),13))
+#define MULT16_16_Q14(a,b) (SHR(MULT16_16((a),(b)),14))
+#define MULT16_16_Q15(a,b) (SHR(MULT16_16((a),(b)),15))
+
+#define MULT16_16_P13(a,b) (SHR(ADD32(4096,MULT16_16((a),(b))),13))
+#define MULT16_16_P14(a,b) (SHR(ADD32(8192,MULT16_16((a),(b))),14))
+#define MULT16_16_P15(a,b) (SHR(ADD32(16384,MULT16_16((a),(b))),15))
+
+#define MUL_16_32_R15(a,bh,bl) ADD32(MULT16_16((a),(bh)), SHR(MULT16_16((a),(bl)),15))
+
+#define DIV32_16(a,b) ((spx_word16_t)(((spx_word32_t)(a))/((spx_word16_t)(b))))
+#define PDIV32_16(a,b) ((spx_word16_t)(((spx_word32_t)(a)+((spx_word16_t)(b)>>1))/((spx_word16_t)(b))))
+#define DIV32(a,b) (((spx_word32_t)(a))/((spx_word32_t)(b)))
+#define PDIV32(a,b) (((spx_word32_t)(a)+((spx_word16_t)(b)>>1))/((spx_word32_t)(b)))
+
+#endif
diff --git a/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/resample.c b/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/resample.c
new file mode 100644
index 0000000000..10cb06593d
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/resample.c
@@ -0,0 +1,1240 @@
+/* Copyright (C) 2007-2008 Jean-Marc Valin
+ Copyright (C) 2008 Thorvald Natvig
+
+ File: resample.c
+ Arbitrary resampling code
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ 3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/*
+ The design goals of this code are:
+ - Very fast algorithm
+ - SIMD-friendly algorithm
+ - Low memory requirement
+ - Good *perceptual* quality (and not best SNR)
+
+ Warning: This resampler is relatively new. Although I think I got rid of
+ all the major bugs and I don't expect the API to change anymore, there
+ may be something I've missed. So use with caution.
+
+ This algorithm is based on this original resampling algorithm:
+ Smith, Julius O. Digital Audio Resampling Home Page
+ Center for Computer Research in Music and Acoustics (CCRMA),
+ Stanford University, 2007.
+ Web published at http://ccrma.stanford.edu/~jos/resample/.
+
+ There is one main difference, though. This resampler uses cubic
+ interpolation instead of linear interpolation in the above paper. This
+ makes the table much smaller and makes it possible to compute that table
+ on a per-stream basis. In turn, being able to tweak the table for each
+ stream makes it possible to both reduce complexity on simple ratios
+ (e.g. 2/3), and get rid of the rounding operations in the inner loop.
+ The latter both reduces CPU time and makes the algorithm more SIMD-friendly.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef OUTSIDE_SPEEX
+#include <stdlib.h>
+static void *speex_alloc (int size) {return calloc(size,1);}
+static void *speex_realloc (void *ptr, int size) {return realloc(ptr, size);}
+static void speex_free (void *ptr) {free(ptr);}
+#include "speex_resampler.h"
+#include "arch.h"
+#else /* OUTSIDE_SPEEX */
+
+#include "speex/speex_resampler.h"
+#include "arch.h"
+#include "os_support.h"
+#endif /* OUTSIDE_SPEEX */
+
+#include "stack_alloc.h"
+#include <math.h>
+#include <limits.h>
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+#define IMAX(a,b) ((a) > (b) ? (a) : (b))
+#define IMIN(a,b) ((a) < (b) ? (a) : (b))
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+#ifndef UINT32_MAX
+#define UINT32_MAX 4294967296U
+#endif
+
+#ifdef _USE_SSE
+#include "resample_sse.h"
+#endif
+
+#ifdef _USE_NEON
+#include "resample_neon.h"
+#endif
+
+/* Numer of elements to allocate on the stack */
+#ifdef VAR_ARRAYS
+#define FIXED_STACK_ALLOC 8192
+#else
+#define FIXED_STACK_ALLOC 1024
+#endif
+
+typedef int (*resampler_basic_func)(SpeexResamplerState *, spx_uint32_t , const spx_word16_t *, spx_uint32_t *, spx_word16_t *, spx_uint32_t *);
+
+struct SpeexResamplerState_ {
+ spx_uint32_t in_rate;
+ spx_uint32_t out_rate;
+ spx_uint32_t num_rate;
+ spx_uint32_t den_rate;
+
+ int quality;
+ spx_uint32_t nb_channels;
+ spx_uint32_t filt_len;
+ spx_uint32_t mem_alloc_size;
+ spx_uint32_t buffer_size;
+ int int_advance;
+ int frac_advance;
+ float cutoff;
+ spx_uint32_t oversample;
+ int initialised;
+ int started;
+
+ /* These are per-channel */
+ spx_int32_t *last_sample;
+ spx_uint32_t *samp_frac_num;
+ spx_uint32_t *magic_samples;
+
+ spx_word16_t *mem;
+ spx_word16_t *sinc_table;
+ spx_uint32_t sinc_table_length;
+ resampler_basic_func resampler_ptr;
+
+ int in_stride;
+ int out_stride;
+} ;
+
+static const double kaiser12_table[68] = {
+ 0.99859849, 1.00000000, 0.99859849, 0.99440475, 0.98745105, 0.97779076,
+ 0.96549770, 0.95066529, 0.93340547, 0.91384741, 0.89213598, 0.86843014,
+ 0.84290116, 0.81573067, 0.78710866, 0.75723148, 0.72629970, 0.69451601,
+ 0.66208321, 0.62920216, 0.59606986, 0.56287762, 0.52980938, 0.49704014,
+ 0.46473455, 0.43304576, 0.40211431, 0.37206735, 0.34301800, 0.31506490,
+ 0.28829195, 0.26276832, 0.23854851, 0.21567274, 0.19416736, 0.17404546,
+ 0.15530766, 0.13794294, 0.12192957, 0.10723616, 0.09382272, 0.08164178,
+ 0.07063950, 0.06075685, 0.05193064, 0.04409466, 0.03718069, 0.03111947,
+ 0.02584161, 0.02127838, 0.01736250, 0.01402878, 0.01121463, 0.00886058,
+ 0.00691064, 0.00531256, 0.00401805, 0.00298291, 0.00216702, 0.00153438,
+ 0.00105297, 0.00069463, 0.00043489, 0.00025272, 0.00013031, 0.0000527734,
+ 0.00001000, 0.00000000};
+/*
+static const double kaiser12_table[36] = {
+ 0.99440475, 1.00000000, 0.99440475, 0.97779076, 0.95066529, 0.91384741,
+ 0.86843014, 0.81573067, 0.75723148, 0.69451601, 0.62920216, 0.56287762,
+ 0.49704014, 0.43304576, 0.37206735, 0.31506490, 0.26276832, 0.21567274,
+ 0.17404546, 0.13794294, 0.10723616, 0.08164178, 0.06075685, 0.04409466,
+ 0.03111947, 0.02127838, 0.01402878, 0.00886058, 0.00531256, 0.00298291,
+ 0.00153438, 0.00069463, 0.00025272, 0.0000527734, 0.00000500, 0.00000000};
+*/
+static const double kaiser10_table[36] = {
+ 0.99537781, 1.00000000, 0.99537781, 0.98162644, 0.95908712, 0.92831446,
+ 0.89005583, 0.84522401, 0.79486424, 0.74011713, 0.68217934, 0.62226347,
+ 0.56155915, 0.50119680, 0.44221549, 0.38553619, 0.33194107, 0.28205962,
+ 0.23636152, 0.19515633, 0.15859932, 0.12670280, 0.09935205, 0.07632451,
+ 0.05731132, 0.04193980, 0.02979584, 0.02044510, 0.01345224, 0.00839739,
+ 0.00488951, 0.00257636, 0.00115101, 0.00035515, 0.00000000, 0.00000000};
+
+static const double kaiser8_table[36] = {
+ 0.99635258, 1.00000000, 0.99635258, 0.98548012, 0.96759014, 0.94302200,
+ 0.91223751, 0.87580811, 0.83439927, 0.78875245, 0.73966538, 0.68797126,
+ 0.63451750, 0.58014482, 0.52566725, 0.47185369, 0.41941150, 0.36897272,
+ 0.32108304, 0.27619388, 0.23465776, 0.19672670, 0.16255380, 0.13219758,
+ 0.10562887, 0.08273982, 0.06335451, 0.04724088, 0.03412321, 0.02369490,
+ 0.01563093, 0.00959968, 0.00527363, 0.00233883, 0.00050000, 0.00000000};
+
+static const double kaiser6_table[36] = {
+ 0.99733006, 1.00000000, 0.99733006, 0.98935595, 0.97618418, 0.95799003,
+ 0.93501423, 0.90755855, 0.87598009, 0.84068475, 0.80211977, 0.76076565,
+ 0.71712752, 0.67172623, 0.62508937, 0.57774224, 0.53019925, 0.48295561,
+ 0.43647969, 0.39120616, 0.34752997, 0.30580127, 0.26632152, 0.22934058,
+ 0.19505503, 0.16360756, 0.13508755, 0.10953262, 0.08693120, 0.06722600,
+ 0.05031820, 0.03607231, 0.02432151, 0.01487334, 0.00752000, 0.00000000};
+
+struct FuncDef {
+ const double *table;
+ int oversample;
+};
+
+static const struct FuncDef _KAISER12 = {kaiser12_table, 64};
+#define KAISER12 (&_KAISER12)
+/*static struct FuncDef _KAISER12 = {kaiser12_table, 32};
+#define KAISER12 (&_KAISER12)*/
+static const struct FuncDef _KAISER10 = {kaiser10_table, 32};
+#define KAISER10 (&_KAISER10)
+static const struct FuncDef _KAISER8 = {kaiser8_table, 32};
+#define KAISER8 (&_KAISER8)
+static const struct FuncDef _KAISER6 = {kaiser6_table, 32};
+#define KAISER6 (&_KAISER6)
+
+struct QualityMapping {
+ int base_length;
+ int oversample;
+ float downsample_bandwidth;
+ float upsample_bandwidth;
+ const struct FuncDef *window_func;
+};
+
+
+/* This table maps conversion quality to internal parameters. There are two
+ reasons that explain why the up-sampling bandwidth is larger than the
+ down-sampling bandwidth:
+ 1) When up-sampling, we can assume that the spectrum is already attenuated
+ close to the Nyquist rate (from an A/D or a previous resampling filter)
+ 2) Any aliasing that occurs very close to the Nyquist rate will be masked
+ by the sinusoids/noise just below the Nyquist rate (guaranteed only for
+ up-sampling).
+*/
+static const struct QualityMapping quality_map[11] = {
+ { 8, 4, 0.830f, 0.860f, KAISER6 }, /* Q0 */
+ { 16, 4, 0.850f, 0.880f, KAISER6 }, /* Q1 */
+ { 32, 4, 0.882f, 0.910f, KAISER6 }, /* Q2 */ /* 82.3% cutoff ( ~60 dB stop) 6 */
+ { 48, 8, 0.895f, 0.917f, KAISER8 }, /* Q3 */ /* 84.9% cutoff ( ~80 dB stop) 8 */
+ { 64, 8, 0.921f, 0.940f, KAISER8 }, /* Q4 */ /* 88.7% cutoff ( ~80 dB stop) 8 */
+ { 80, 16, 0.922f, 0.940f, KAISER10}, /* Q5 */ /* 89.1% cutoff (~100 dB stop) 10 */
+ { 96, 16, 0.940f, 0.945f, KAISER10}, /* Q6 */ /* 91.5% cutoff (~100 dB stop) 10 */
+ {128, 16, 0.950f, 0.950f, KAISER10}, /* Q7 */ /* 93.1% cutoff (~100 dB stop) 10 */
+ {160, 16, 0.960f, 0.960f, KAISER10}, /* Q8 */ /* 94.5% cutoff (~100 dB stop) 10 */
+ {192, 32, 0.968f, 0.968f, KAISER12}, /* Q9 */ /* 95.5% cutoff (~100 dB stop) 10 */
+ {256, 32, 0.975f, 0.975f, KAISER12}, /* Q10 */ /* 96.6% cutoff (~100 dB stop) 10 */
+};
+/*8,24,40,56,80,104,128,160,200,256,320*/
+static double compute_func(float x, const struct FuncDef *func)
+{
+ float y, frac;
+ double interp[4];
+ int ind;
+ y = x*func->oversample;
+ ind = (int)floor(y);
+ frac = (y-ind);
+ /* CSE with handle the repeated powers */
+ interp[3] = -0.1666666667*frac + 0.1666666667*(frac*frac*frac);
+ interp[2] = frac + 0.5*(frac*frac) - 0.5*(frac*frac*frac);
+ /*interp[2] = 1.f - 0.5f*frac - frac*frac + 0.5f*frac*frac*frac;*/
+ interp[0] = -0.3333333333*frac + 0.5*(frac*frac) - 0.1666666667*(frac*frac*frac);
+ /* Just to make sure we don't have rounding problems */
+ interp[1] = 1.f-interp[3]-interp[2]-interp[0];
+
+ /*sum = frac*accum[1] + (1-frac)*accum[2];*/
+ return interp[0]*func->table[ind] + interp[1]*func->table[ind+1] + interp[2]*func->table[ind+2] + interp[3]*func->table[ind+3];
+}
+
+#if 0
+#include <stdio.h>
+int main(int argc, char **argv)
+{
+ int i;
+ for (i=0;i<256;i++)
+ {
+ printf ("%f\n", compute_func(i/256., KAISER12));
+ }
+ return 0;
+}
+#endif
+
+#ifdef FIXED_POINT
+/* The slow way of computing a sinc for the table. Should improve that some day */
+static spx_word16_t sinc(float cutoff, float x, int N, const struct FuncDef *window_func)
+{
+ /*fprintf (stderr, "%f ", x);*/
+ float xx = x * cutoff;
+ if (fabs(x)<1e-6f)
+ return WORD2INT(32768.*cutoff);
+ else if (fabs(x) > .5f*N)
+ return 0;
+ /*FIXME: Can it really be any slower than this? */
+ return WORD2INT(32768.*cutoff*sin(M_PI*xx)/(M_PI*xx) * compute_func(fabs(2.*x/N), window_func));
+}
+#else
+/* The slow way of computing a sinc for the table. Should improve that some day */
+static spx_word16_t sinc(float cutoff, float x, int N, const struct FuncDef *window_func)
+{
+ /*fprintf (stderr, "%f ", x);*/
+ float xx = x * cutoff;
+ if (fabs(x)<1e-6)
+ return cutoff;
+ else if (fabs(x) > .5*N)
+ return 0;
+ /*FIXME: Can it really be any slower than this? */
+ return cutoff*sin(M_PI*xx)/(M_PI*xx) * compute_func(fabs(2.*x/N), window_func);
+}
+#endif
+
+#ifdef FIXED_POINT
+static void cubic_coef(spx_word16_t x, spx_word16_t interp[4])
+{
+ /* Compute interpolation coefficients. I'm not sure whether this corresponds to cubic interpolation
+ but I know it's MMSE-optimal on a sinc */
+ spx_word16_t x2, x3;
+ x2 = MULT16_16_P15(x, x);
+ x3 = MULT16_16_P15(x, x2);
+ interp[0] = PSHR32(MULT16_16(QCONST16(-0.16667f, 15),x) + MULT16_16(QCONST16(0.16667f, 15),x3),15);
+ interp[1] = EXTRACT16(EXTEND32(x) + SHR32(SUB32(EXTEND32(x2),EXTEND32(x3)),1));
+ interp[3] = PSHR32(MULT16_16(QCONST16(-0.33333f, 15),x) + MULT16_16(QCONST16(.5f,15),x2) - MULT16_16(QCONST16(0.16667f, 15),x3),15);
+ /* Just to make sure we don't have rounding problems */
+ interp[2] = Q15_ONE-interp[0]-interp[1]-interp[3];
+ if (interp[2]<32767)
+ interp[2]+=1;
+}
+#else
+static void cubic_coef(spx_word16_t frac, spx_word16_t interp[4])
+{
+ /* Compute interpolation coefficients. I'm not sure whether this corresponds to cubic interpolation
+ but I know it's MMSE-optimal on a sinc */
+ interp[0] = -0.16667f*frac + 0.16667f*frac*frac*frac;
+ interp[1] = frac + 0.5f*frac*frac - 0.5f*frac*frac*frac;
+ /*interp[2] = 1.f - 0.5f*frac - frac*frac + 0.5f*frac*frac*frac;*/
+ interp[3] = -0.33333f*frac + 0.5f*frac*frac - 0.16667f*frac*frac*frac;
+ /* Just to make sure we don't have rounding problems */
+ interp[2] = 1.-interp[0]-interp[1]-interp[3];
+}
+#endif
+
+static int resampler_basic_direct_single(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len)
+{
+ const int N = st->filt_len;
+ int out_sample = 0;
+ int last_sample = st->last_sample[channel_index];
+ spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index];
+ const spx_word16_t *sinc_table = st->sinc_table;
+ const int out_stride = st->out_stride;
+ const int int_advance = st->int_advance;
+ const int frac_advance = st->frac_advance;
+ const spx_uint32_t den_rate = st->den_rate;
+ spx_word32_t sum;
+
+ while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len))
+ {
+ const spx_word16_t *sinct = & sinc_table[samp_frac_num*N];
+ const spx_word16_t *iptr = & in[last_sample];
+
+#ifndef OVERRIDE_INNER_PRODUCT_SINGLE
+ int j;
+ sum = 0;
+ for(j=0;j<N;j++) sum += MULT16_16(sinct[j], iptr[j]);
+
+/* This code is slower on most DSPs which have only 2 accumulators.
+ Plus this this forces truncation to 32 bits and you lose the HW guard bits.
+ I think we can trust the compiler and let it vectorize and/or unroll itself.
+ spx_word32_t accum[4] = {0,0,0,0};
+ for(j=0;j<N;j+=4) {
+ accum[0] += MULT16_16(sinct[j], iptr[j]);
+ accum[1] += MULT16_16(sinct[j+1], iptr[j+1]);
+ accum[2] += MULT16_16(sinct[j+2], iptr[j+2]);
+ accum[3] += MULT16_16(sinct[j+3], iptr[j+3]);
+ }
+ sum = accum[0] + accum[1] + accum[2] + accum[3];
+*/
+ sum = SATURATE32PSHR(sum, 15, 32767);
+#else
+ sum = inner_product_single(sinct, iptr, N);
+#endif
+
+ out[out_stride * out_sample++] = sum;
+ last_sample += int_advance;
+ samp_frac_num += frac_advance;
+ if (samp_frac_num >= den_rate)
+ {
+ samp_frac_num -= den_rate;
+ last_sample++;
+ }
+ }
+
+ st->last_sample[channel_index] = last_sample;
+ st->samp_frac_num[channel_index] = samp_frac_num;
+ return out_sample;
+}
+
+#ifdef FIXED_POINT
+#else
+/* This is the same as the previous function, except with a double-precision accumulator */
+static int resampler_basic_direct_double(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len)
+{
+ const int N = st->filt_len;
+ int out_sample = 0;
+ int last_sample = st->last_sample[channel_index];
+ spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index];
+ const spx_word16_t *sinc_table = st->sinc_table;
+ const int out_stride = st->out_stride;
+ const int int_advance = st->int_advance;
+ const int frac_advance = st->frac_advance;
+ const spx_uint32_t den_rate = st->den_rate;
+ double sum;
+
+ while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len))
+ {
+ const spx_word16_t *sinct = & sinc_table[samp_frac_num*N];
+ const spx_word16_t *iptr = & in[last_sample];
+
+#ifndef OVERRIDE_INNER_PRODUCT_DOUBLE
+ int j;
+ double accum[4] = {0,0,0,0};
+
+ for(j=0;j<N;j+=4) {
+ accum[0] += sinct[j]*iptr[j];
+ accum[1] += sinct[j+1]*iptr[j+1];
+ accum[2] += sinct[j+2]*iptr[j+2];
+ accum[3] += sinct[j+3]*iptr[j+3];
+ }
+ sum = accum[0] + accum[1] + accum[2] + accum[3];
+#else
+ sum = inner_product_double(sinct, iptr, N);
+#endif
+
+ out[out_stride * out_sample++] = PSHR32(sum, 15);
+ last_sample += int_advance;
+ samp_frac_num += frac_advance;
+ if (samp_frac_num >= den_rate)
+ {
+ samp_frac_num -= den_rate;
+ last_sample++;
+ }
+ }
+
+ st->last_sample[channel_index] = last_sample;
+ st->samp_frac_num[channel_index] = samp_frac_num;
+ return out_sample;
+}
+#endif
+
+static int resampler_basic_interpolate_single(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len)
+{
+ const int N = st->filt_len;
+ int out_sample = 0;
+ int last_sample = st->last_sample[channel_index];
+ spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index];
+ const int out_stride = st->out_stride;
+ const int int_advance = st->int_advance;
+ const int frac_advance = st->frac_advance;
+ const spx_uint32_t den_rate = st->den_rate;
+ spx_word32_t sum;
+
+ while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len))
+ {
+ const spx_word16_t *iptr = & in[last_sample];
+
+ const int offset = samp_frac_num*st->oversample/st->den_rate;
+#ifdef FIXED_POINT
+ const spx_word16_t frac = PDIV32(SHL32((samp_frac_num*st->oversample) % st->den_rate,15),st->den_rate);
+#else
+ const spx_word16_t frac = ((float)((samp_frac_num*st->oversample) % st->den_rate))/st->den_rate;
+#endif
+ spx_word16_t interp[4];
+
+
+#ifndef OVERRIDE_INTERPOLATE_PRODUCT_SINGLE
+ int j;
+ spx_word32_t accum[4] = {0,0,0,0};
+
+ for(j=0;j<N;j++) {
+ const spx_word16_t curr_in=iptr[j];
+ accum[0] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset-2]);
+ accum[1] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset-1]);
+ accum[2] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset]);
+ accum[3] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset+1]);
+ }
+
+ cubic_coef(frac, interp);
+ sum = MULT16_32_Q15(interp[0],SHR32(accum[0], 1)) + MULT16_32_Q15(interp[1],SHR32(accum[1], 1)) + MULT16_32_Q15(interp[2],SHR32(accum[2], 1)) + MULT16_32_Q15(interp[3],SHR32(accum[3], 1));
+ sum = SATURATE32PSHR(sum, 15, 32767);
+#else
+ cubic_coef(frac, interp);
+ sum = interpolate_product_single(iptr, st->sinc_table + st->oversample + 4 - offset - 2, N, st->oversample, interp);
+#endif
+
+ out[out_stride * out_sample++] = sum;
+ last_sample += int_advance;
+ samp_frac_num += frac_advance;
+ if (samp_frac_num >= den_rate)
+ {
+ samp_frac_num -= den_rate;
+ last_sample++;
+ }
+ }
+
+ st->last_sample[channel_index] = last_sample;
+ st->samp_frac_num[channel_index] = samp_frac_num;
+ return out_sample;
+}
+
+#ifdef FIXED_POINT
+#else
+/* This is the same as the previous function, except with a double-precision accumulator */
+static int resampler_basic_interpolate_double(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len)
+{
+ const int N = st->filt_len;
+ int out_sample = 0;
+ int last_sample = st->last_sample[channel_index];
+ spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index];
+ const int out_stride = st->out_stride;
+ const int int_advance = st->int_advance;
+ const int frac_advance = st->frac_advance;
+ const spx_uint32_t den_rate = st->den_rate;
+ spx_word32_t sum;
+
+ while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len))
+ {
+ const spx_word16_t *iptr = & in[last_sample];
+
+ const int offset = samp_frac_num*st->oversample/st->den_rate;
+#ifdef FIXED_POINT
+ const spx_word16_t frac = PDIV32(SHL32((samp_frac_num*st->oversample) % st->den_rate,15),st->den_rate);
+#else
+ const spx_word16_t frac = ((float)((samp_frac_num*st->oversample) % st->den_rate))/st->den_rate;
+#endif
+ spx_word16_t interp[4];
+
+
+#ifndef OVERRIDE_INTERPOLATE_PRODUCT_DOUBLE
+ int j;
+ double accum[4] = {0,0,0,0};
+
+ for(j=0;j<N;j++) {
+ const double curr_in=iptr[j];
+ accum[0] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset-2]);
+ accum[1] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset-1]);
+ accum[2] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset]);
+ accum[3] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset+1]);
+ }
+
+ cubic_coef(frac, interp);
+ sum = MULT16_32_Q15(interp[0],accum[0]) + MULT16_32_Q15(interp[1],accum[1]) + MULT16_32_Q15(interp[2],accum[2]) + MULT16_32_Q15(interp[3],accum[3]);
+#else
+ cubic_coef(frac, interp);
+ sum = interpolate_product_double(iptr, st->sinc_table + st->oversample + 4 - offset - 2, N, st->oversample, interp);
+#endif
+
+ out[out_stride * out_sample++] = PSHR32(sum,15);
+ last_sample += int_advance;
+ samp_frac_num += frac_advance;
+ if (samp_frac_num >= den_rate)
+ {
+ samp_frac_num -= den_rate;
+ last_sample++;
+ }
+ }
+
+ st->last_sample[channel_index] = last_sample;
+ st->samp_frac_num[channel_index] = samp_frac_num;
+ return out_sample;
+}
+#endif
+
+/* This resampler is used to produce zero output in situations where memory
+ for the filter could not be allocated. The expected numbers of input and
+ output samples are still processed so that callers failing to check error
+ codes are not surprised, possibly getting into infinite loops. */
+static int resampler_basic_zero(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len)
+{
+ int out_sample = 0;
+ int last_sample = st->last_sample[channel_index];
+ spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index];
+ const int out_stride = st->out_stride;
+ const int int_advance = st->int_advance;
+ const int frac_advance = st->frac_advance;
+ const spx_uint32_t den_rate = st->den_rate;
+
+ while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len))
+ {
+ out[out_stride * out_sample++] = 0;
+ last_sample += int_advance;
+ samp_frac_num += frac_advance;
+ if (samp_frac_num >= den_rate)
+ {
+ samp_frac_num -= den_rate;
+ last_sample++;
+ }
+ }
+
+ st->last_sample[channel_index] = last_sample;
+ st->samp_frac_num[channel_index] = samp_frac_num;
+ return out_sample;
+}
+
+static int _muldiv(spx_uint32_t *result, spx_uint32_t value, spx_uint32_t mul, spx_uint32_t div)
+{
+ speex_assert(result);
+ spx_uint32_t major = value / div;
+ spx_uint32_t remainder = value % div;
+ /* TODO: Could use 64 bits operation to check for overflow. But only guaranteed in C99+ */
+ if (remainder > UINT32_MAX / mul || major > UINT32_MAX / mul
+ || major * mul > UINT32_MAX - remainder * mul / div)
+ return RESAMPLER_ERR_OVERFLOW;
+ *result = remainder * mul / div + major * mul;
+ return RESAMPLER_ERR_SUCCESS;
+}
+
+static int update_filter(SpeexResamplerState *st)
+{
+ spx_uint32_t old_length = st->filt_len;
+ spx_uint32_t old_alloc_size = st->mem_alloc_size;
+ int use_direct;
+ spx_uint32_t min_sinc_table_length;
+ spx_uint32_t min_alloc_size;
+
+ st->int_advance = st->num_rate/st->den_rate;
+ st->frac_advance = st->num_rate%st->den_rate;
+ st->oversample = quality_map[st->quality].oversample;
+ st->filt_len = quality_map[st->quality].base_length;
+
+ if (st->num_rate > st->den_rate)
+ {
+ /* down-sampling */
+ st->cutoff = quality_map[st->quality].downsample_bandwidth * st->den_rate / st->num_rate;
+ if (_muldiv(&st->filt_len,st->filt_len,st->num_rate,st->den_rate) != RESAMPLER_ERR_SUCCESS)
+ goto fail;
+ /* Round up to make sure we have a multiple of 8 for SSE */
+ st->filt_len = ((st->filt_len-1)&(~0x7))+8;
+ if (2*st->den_rate < st->num_rate)
+ st->oversample >>= 1;
+ if (4*st->den_rate < st->num_rate)
+ st->oversample >>= 1;
+ if (8*st->den_rate < st->num_rate)
+ st->oversample >>= 1;
+ if (16*st->den_rate < st->num_rate)
+ st->oversample >>= 1;
+ if (st->oversample < 1)
+ st->oversample = 1;
+ } else {
+ /* up-sampling */
+ st->cutoff = quality_map[st->quality].upsample_bandwidth;
+ }
+
+ /* Choose the resampling type that requires the least amount of memory */
+#ifdef RESAMPLE_FULL_SINC_TABLE
+ use_direct = 1;
+ if (INT_MAX/sizeof(spx_word16_t)/st->den_rate < st->filt_len)
+ goto fail;
+#else
+ use_direct = st->filt_len*st->den_rate <= st->filt_len*st->oversample+8
+ && INT_MAX/sizeof(spx_word16_t)/st->den_rate >= st->filt_len;
+#endif
+ if (use_direct)
+ {
+ min_sinc_table_length = st->filt_len*st->den_rate;
+ } else {
+ if ((INT_MAX/sizeof(spx_word16_t)-8)/st->oversample < st->filt_len)
+ goto fail;
+
+ min_sinc_table_length = st->filt_len*st->oversample+8;
+ }
+ if (st->sinc_table_length < min_sinc_table_length)
+ {
+ spx_word16_t *sinc_table = (spx_word16_t *)speex_realloc(st->sinc_table,min_sinc_table_length*sizeof(spx_word16_t));
+ if (!sinc_table)
+ goto fail;
+
+ st->sinc_table = sinc_table;
+ st->sinc_table_length = min_sinc_table_length;
+ }
+ if (use_direct)
+ {
+ spx_uint32_t i;
+ for (i=0;i<st->den_rate;i++)
+ {
+ spx_int32_t j;
+ for (j=0;j<st->filt_len;j++)
+ {
+ st->sinc_table[i*st->filt_len+j] = sinc(st->cutoff,((j-(spx_int32_t)st->filt_len/2+1)-((float)i)/st->den_rate), st->filt_len, quality_map[st->quality].window_func);
+ }
+ }
+#ifdef FIXED_POINT
+ st->resampler_ptr = resampler_basic_direct_single;
+#else
+ if (st->quality>8)
+ st->resampler_ptr = resampler_basic_direct_double;
+ else
+ st->resampler_ptr = resampler_basic_direct_single;
+#endif
+ /*fprintf (stderr, "resampler uses direct sinc table and normalised cutoff %f\n", cutoff);*/
+ } else {
+ spx_int32_t i;
+ for (i=-4;i<(spx_int32_t)(st->oversample*st->filt_len+4);i++)
+ st->sinc_table[i+4] = sinc(st->cutoff,(i/(float)st->oversample - st->filt_len/2), st->filt_len, quality_map[st->quality].window_func);
+#ifdef FIXED_POINT
+ st->resampler_ptr = resampler_basic_interpolate_single;
+#else
+ if (st->quality>8)
+ st->resampler_ptr = resampler_basic_interpolate_double;
+ else
+ st->resampler_ptr = resampler_basic_interpolate_single;
+#endif
+ /*fprintf (stderr, "resampler uses interpolated sinc table and normalised cutoff %f\n", cutoff);*/
+ }
+
+ /* Here's the place where we update the filter memory to take into account
+ the change in filter length. It's probably the messiest part of the code
+ due to handling of lots of corner cases. */
+
+ /* Adding buffer_size to filt_len won't overflow here because filt_len
+ could be multiplied by sizeof(spx_word16_t) above. */
+ min_alloc_size = st->filt_len-1 + st->buffer_size;
+ if (min_alloc_size > st->mem_alloc_size)
+ {
+ spx_word16_t *mem;
+ if (INT_MAX/sizeof(spx_word16_t)/st->nb_channels < min_alloc_size)
+ goto fail;
+ else if (!(mem = (spx_word16_t*)speex_realloc(st->mem, st->nb_channels*min_alloc_size * sizeof(*mem))))
+ goto fail;
+
+ st->mem = mem;
+ st->mem_alloc_size = min_alloc_size;
+ }
+ if (!st->started)
+ {
+ spx_uint32_t i;
+ for (i=0;i<st->nb_channels*st->mem_alloc_size;i++)
+ st->mem[i] = 0;
+ /*speex_warning("reinit filter");*/
+ } else if (st->filt_len > old_length)
+ {
+ spx_uint32_t i;
+ /* Increase the filter length */
+ /*speex_warning("increase filter size");*/
+ for (i=st->nb_channels;i--;)
+ {
+ spx_uint32_t j;
+ spx_uint32_t olen = old_length;
+ /*if (st->magic_samples[i])*/
+ {
+ /* Try and remove the magic samples as if nothing had happened */
+
+ /* FIXME: This is wrong but for now we need it to avoid going over the array bounds */
+ olen = old_length + 2*st->magic_samples[i];
+ for (j=old_length-1+st->magic_samples[i];j--;)
+ st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]] = st->mem[i*old_alloc_size+j];
+ for (j=0;j<st->magic_samples[i];j++)
+ st->mem[i*st->mem_alloc_size+j] = 0;
+ st->magic_samples[i] = 0;
+ }
+ if (st->filt_len > olen)
+ {
+ /* If the new filter length is still bigger than the "augmented" length */
+ /* Copy data going backward */
+ for (j=0;j<olen-1;j++)
+ st->mem[i*st->mem_alloc_size+(st->filt_len-2-j)] = st->mem[i*st->mem_alloc_size+(olen-2-j)];
+ /* Then put zeros for lack of anything better */
+ for (;j<st->filt_len-1;j++)
+ st->mem[i*st->mem_alloc_size+(st->filt_len-2-j)] = 0;
+ /* Adjust last_sample */
+ st->last_sample[i] += (st->filt_len - olen)/2;
+ } else {
+ /* Put back some of the magic! */
+ st->magic_samples[i] = (olen - st->filt_len)/2;
+ for (j=0;j<st->filt_len-1+st->magic_samples[i];j++)
+ st->mem[i*st->mem_alloc_size+j] = st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]];
+ }
+ }
+ } else if (st->filt_len < old_length)
+ {
+ spx_uint32_t i;
+ /* Reduce filter length, this a bit tricky. We need to store some of the memory as "magic"
+ samples so they can be used directly as input the next time(s) */
+ for (i=0;i<st->nb_channels;i++)
+ {
+ spx_uint32_t j;
+ spx_uint32_t old_magic = st->magic_samples[i];
+ st->magic_samples[i] = (old_length - st->filt_len)/2;
+ /* We must copy some of the memory that's no longer used */
+ /* Copy data going backward */
+ for (j=0;j<st->filt_len-1+st->magic_samples[i]+old_magic;j++)
+ st->mem[i*st->mem_alloc_size+j] = st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]];
+ st->magic_samples[i] += old_magic;
+ }
+ }
+ return RESAMPLER_ERR_SUCCESS;
+
+fail:
+ st->resampler_ptr = resampler_basic_zero;
+ /* st->mem may still contain consumed input samples for the filter.
+ Restore filt_len so that filt_len - 1 still points to the position after
+ the last of these samples. */
+ st->filt_len = old_length;
+ return RESAMPLER_ERR_ALLOC_FAILED;
+}
+
+EXPORT SpeexResamplerState *speex_resampler_init(spx_uint32_t nb_channels, spx_uint32_t in_rate, spx_uint32_t out_rate, int quality, int *err)
+{
+ return speex_resampler_init_frac(nb_channels, in_rate, out_rate, in_rate, out_rate, quality, err);
+}
+
+EXPORT SpeexResamplerState *speex_resampler_init_frac(spx_uint32_t nb_channels, spx_uint32_t ratio_num, spx_uint32_t ratio_den, spx_uint32_t in_rate, spx_uint32_t out_rate, int quality, int *err)
+{
+ SpeexResamplerState *st;
+ int filter_err;
+
+ if (nb_channels == 0 || ratio_num == 0 || ratio_den == 0 || quality > 10 || quality < 0)
+ {
+ if (err)
+ *err = RESAMPLER_ERR_INVALID_ARG;
+ return NULL;
+ }
+ st = (SpeexResamplerState *)speex_alloc(sizeof(SpeexResamplerState));
+ if (!st)
+ {
+ if (err)
+ *err = RESAMPLER_ERR_ALLOC_FAILED;
+ return NULL;
+ }
+ st->initialised = 0;
+ st->started = 0;
+ st->in_rate = 0;
+ st->out_rate = 0;
+ st->num_rate = 0;
+ st->den_rate = 0;
+ st->quality = -1;
+ st->sinc_table_length = 0;
+ st->mem_alloc_size = 0;
+ st->filt_len = 0;
+ st->mem = 0;
+ st->resampler_ptr = 0;
+
+ st->cutoff = 1.f;
+ st->nb_channels = nb_channels;
+ st->in_stride = 1;
+ st->out_stride = 1;
+
+ st->buffer_size = 160;
+
+ /* Per channel data */
+ if (!(st->last_sample = (spx_int32_t*)speex_alloc(nb_channels*sizeof(spx_int32_t))))
+ goto fail;
+ if (!(st->magic_samples = (spx_uint32_t*)speex_alloc(nb_channels*sizeof(spx_uint32_t))))
+ goto fail;
+ if (!(st->samp_frac_num = (spx_uint32_t*)speex_alloc(nb_channels*sizeof(spx_uint32_t))))
+ goto fail;
+
+ speex_resampler_set_quality(st, quality);
+ speex_resampler_set_rate_frac(st, ratio_num, ratio_den, in_rate, out_rate);
+
+ filter_err = update_filter(st);
+ if (filter_err == RESAMPLER_ERR_SUCCESS)
+ {
+ st->initialised = 1;
+ } else {
+ speex_resampler_destroy(st);
+ st = NULL;
+ }
+ if (err)
+ *err = filter_err;
+
+ return st;
+
+fail:
+ if (err)
+ *err = RESAMPLER_ERR_ALLOC_FAILED;
+ speex_resampler_destroy(st);
+ return NULL;
+}
+
+EXPORT void speex_resampler_destroy(SpeexResamplerState *st)
+{
+ speex_free(st->mem);
+ speex_free(st->sinc_table);
+ speex_free(st->last_sample);
+ speex_free(st->magic_samples);
+ speex_free(st->samp_frac_num);
+ speex_free(st);
+}
+
+static int speex_resampler_process_native(SpeexResamplerState *st, spx_uint32_t channel_index, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len)
+{
+ int j=0;
+ const int N = st->filt_len;
+ int out_sample = 0;
+ spx_word16_t *mem = st->mem + channel_index * st->mem_alloc_size;
+ spx_uint32_t ilen;
+
+ st->started = 1;
+
+ /* Call the right resampler through the function ptr */
+ out_sample = st->resampler_ptr(st, channel_index, mem, in_len, out, out_len);
+
+ if (st->last_sample[channel_index] < (spx_int32_t)*in_len)
+ *in_len = st->last_sample[channel_index];
+ *out_len = out_sample;
+ st->last_sample[channel_index] -= *in_len;
+
+ ilen = *in_len;
+
+ for(j=0;j<N-1;++j)
+ mem[j] = mem[j+ilen];
+
+ return RESAMPLER_ERR_SUCCESS;
+}
+
+static int speex_resampler_magic(SpeexResamplerState *st, spx_uint32_t channel_index, spx_word16_t **out, spx_uint32_t out_len) {
+ spx_uint32_t tmp_in_len = st->magic_samples[channel_index];
+ spx_word16_t *mem = st->mem + channel_index * st->mem_alloc_size;
+ const int N = st->filt_len;
+
+ speex_resampler_process_native(st, channel_index, &tmp_in_len, *out, &out_len);
+
+ st->magic_samples[channel_index] -= tmp_in_len;
+
+ /* If we couldn't process all "magic" input samples, save the rest for next time */
+ if (st->magic_samples[channel_index])
+ {
+ spx_uint32_t i;
+ for (i=0;i<st->magic_samples[channel_index];i++)
+ mem[N-1+i]=mem[N-1+i+tmp_in_len];
+ }
+ *out += out_len*st->out_stride;
+ return out_len;
+}
+
+#ifdef FIXED_POINT
+EXPORT int speex_resampler_process_int(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_int16_t *in, spx_uint32_t *in_len, spx_int16_t *out, spx_uint32_t *out_len)
+#else
+EXPORT int speex_resampler_process_float(SpeexResamplerState *st, spx_uint32_t channel_index, const float *in, spx_uint32_t *in_len, float *out, spx_uint32_t *out_len)
+#endif
+{
+ int j;
+ spx_uint32_t ilen = *in_len;
+ spx_uint32_t olen = *out_len;
+ spx_word16_t *x = st->mem + channel_index * st->mem_alloc_size;
+ const int filt_offs = st->filt_len - 1;
+ const spx_uint32_t xlen = st->mem_alloc_size - filt_offs;
+ const int istride = st->in_stride;
+
+ if (st->magic_samples[channel_index])
+ olen -= speex_resampler_magic(st, channel_index, &out, olen);
+ if (! st->magic_samples[channel_index]) {
+ while (ilen && olen) {
+ spx_uint32_t ichunk = (ilen > xlen) ? xlen : ilen;
+ spx_uint32_t ochunk = olen;
+
+ if (in) {
+ for(j=0;j<ichunk;++j)
+ x[j+filt_offs]=in[j*istride];
+ } else {
+ for(j=0;j<ichunk;++j)
+ x[j+filt_offs]=0;
+ }
+ speex_resampler_process_native(st, channel_index, &ichunk, out, &ochunk);
+ ilen -= ichunk;
+ olen -= ochunk;
+ out += ochunk * st->out_stride;
+ if (in)
+ in += ichunk * istride;
+ }
+ }
+ *in_len -= ilen;
+ *out_len -= olen;
+ return st->resampler_ptr == resampler_basic_zero ? RESAMPLER_ERR_ALLOC_FAILED : RESAMPLER_ERR_SUCCESS;
+}
+
+#ifdef FIXED_POINT
+EXPORT int speex_resampler_process_float(SpeexResamplerState *st, spx_uint32_t channel_index, const float *in, spx_uint32_t *in_len, float *out, spx_uint32_t *out_len)
+#else
+EXPORT int speex_resampler_process_int(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_int16_t *in, spx_uint32_t *in_len, spx_int16_t *out, spx_uint32_t *out_len)
+#endif
+{
+ int j;
+ const int istride_save = st->in_stride;
+ const int ostride_save = st->out_stride;
+ spx_uint32_t ilen = *in_len;
+ spx_uint32_t olen = *out_len;
+ spx_word16_t *x = st->mem + channel_index * st->mem_alloc_size;
+ const spx_uint32_t xlen = st->mem_alloc_size - (st->filt_len - 1);
+#ifdef VAR_ARRAYS
+ const unsigned int ylen = (olen < FIXED_STACK_ALLOC) ? olen : FIXED_STACK_ALLOC;
+ VARDECL(spx_word16_t *ystack);
+ ALLOC(ystack, ylen, spx_word16_t);
+#else
+ const unsigned int ylen = FIXED_STACK_ALLOC;
+ spx_word16_t ystack[FIXED_STACK_ALLOC];
+#endif
+
+ st->out_stride = 1;
+
+ while (ilen && olen) {
+ spx_word16_t *y = ystack;
+ spx_uint32_t ichunk = (ilen > xlen) ? xlen : ilen;
+ spx_uint32_t ochunk = (olen > ylen) ? ylen : olen;
+ spx_uint32_t omagic = 0;
+
+ if (st->magic_samples[channel_index]) {
+ omagic = speex_resampler_magic(st, channel_index, &y, ochunk);
+ ochunk -= omagic;
+ olen -= omagic;
+ }
+ if (! st->magic_samples[channel_index]) {
+ if (in) {
+ for(j=0;j<ichunk;++j)
+#ifdef FIXED_POINT
+ x[j+st->filt_len-1]=WORD2INT(in[j*istride_save]);
+#else
+ x[j+st->filt_len-1]=in[j*istride_save];
+#endif
+ } else {
+ for(j=0;j<ichunk;++j)
+ x[j+st->filt_len-1]=0;
+ }
+
+ speex_resampler_process_native(st, channel_index, &ichunk, y, &ochunk);
+ } else {
+ ichunk = 0;
+ ochunk = 0;
+ }
+
+ for (j=0;j<ochunk+omagic;++j)
+#ifdef FIXED_POINT
+ out[j*ostride_save] = ystack[j];
+#else
+ out[j*ostride_save] = WORD2INT(ystack[j]);
+#endif
+
+ ilen -= ichunk;
+ olen -= ochunk;
+ out += (ochunk+omagic) * ostride_save;
+ if (in)
+ in += ichunk * istride_save;
+ }
+ st->out_stride = ostride_save;
+ *in_len -= ilen;
+ *out_len -= olen;
+
+ return st->resampler_ptr == resampler_basic_zero ? RESAMPLER_ERR_ALLOC_FAILED : RESAMPLER_ERR_SUCCESS;
+}
+
+EXPORT int speex_resampler_process_interleaved_float(SpeexResamplerState *st, const float *in, spx_uint32_t *in_len, float *out, spx_uint32_t *out_len)
+{
+ spx_uint32_t i;
+ int istride_save, ostride_save;
+ spx_uint32_t bak_out_len = *out_len;
+ spx_uint32_t bak_in_len = *in_len;
+ istride_save = st->in_stride;
+ ostride_save = st->out_stride;
+ st->in_stride = st->out_stride = st->nb_channels;
+ for (i=0;i<st->nb_channels;i++)
+ {
+ *out_len = bak_out_len;
+ *in_len = bak_in_len;
+ if (in != NULL)
+ speex_resampler_process_float(st, i, in+i, in_len, out+i, out_len);
+ else
+ speex_resampler_process_float(st, i, NULL, in_len, out+i, out_len);
+ }
+ st->in_stride = istride_save;
+ st->out_stride = ostride_save;
+ return st->resampler_ptr == resampler_basic_zero ? RESAMPLER_ERR_ALLOC_FAILED : RESAMPLER_ERR_SUCCESS;
+}
+
+EXPORT int speex_resampler_process_interleaved_int(SpeexResamplerState *st, const spx_int16_t *in, spx_uint32_t *in_len, spx_int16_t *out, spx_uint32_t *out_len)
+{
+ spx_uint32_t i;
+ int istride_save, ostride_save;
+ spx_uint32_t bak_out_len = *out_len;
+ spx_uint32_t bak_in_len = *in_len;
+ istride_save = st->in_stride;
+ ostride_save = st->out_stride;
+ st->in_stride = st->out_stride = st->nb_channels;
+ for (i=0;i<st->nb_channels;i++)
+ {
+ *out_len = bak_out_len;
+ *in_len = bak_in_len;
+ if (in != NULL)
+ speex_resampler_process_int(st, i, in+i, in_len, out+i, out_len);
+ else
+ speex_resampler_process_int(st, i, NULL, in_len, out+i, out_len);
+ }
+ st->in_stride = istride_save;
+ st->out_stride = ostride_save;
+ return st->resampler_ptr == resampler_basic_zero ? RESAMPLER_ERR_ALLOC_FAILED : RESAMPLER_ERR_SUCCESS;
+}
+
+EXPORT int speex_resampler_set_rate(SpeexResamplerState *st, spx_uint32_t in_rate, spx_uint32_t out_rate)
+{
+ return speex_resampler_set_rate_frac(st, in_rate, out_rate, in_rate, out_rate);
+}
+
+EXPORT void speex_resampler_get_rate(SpeexResamplerState *st, spx_uint32_t *in_rate, spx_uint32_t *out_rate)
+{
+ *in_rate = st->in_rate;
+ *out_rate = st->out_rate;
+}
+
+static inline spx_uint32_t _gcd(spx_uint32_t a, spx_uint32_t b)
+{
+ while (b != 0)
+ {
+ spx_uint32_t temp = a;
+
+ a = b;
+ b = temp % b;
+ }
+ return a;
+}
+
+EXPORT int speex_resampler_set_rate_frac(SpeexResamplerState *st, spx_uint32_t ratio_num, spx_uint32_t ratio_den, spx_uint32_t in_rate, spx_uint32_t out_rate)
+{
+ spx_uint32_t fact;
+ spx_uint32_t old_den;
+ spx_uint32_t i;
+
+ if (ratio_num == 0 || ratio_den == 0)
+ return RESAMPLER_ERR_INVALID_ARG;
+
+ if (st->in_rate == in_rate && st->out_rate == out_rate && st->num_rate == ratio_num && st->den_rate == ratio_den)
+ return RESAMPLER_ERR_SUCCESS;
+
+ old_den = st->den_rate;
+ st->in_rate = in_rate;
+ st->out_rate = out_rate;
+ st->num_rate = ratio_num;
+ st->den_rate = ratio_den;
+
+ fact = _gcd (st->num_rate, st->den_rate);
+
+ st->num_rate /= fact;
+ st->den_rate /= fact;
+
+ if (old_den > 0)
+ {
+ for (i=0;i<st->nb_channels;i++)
+ {
+ if (_muldiv(&st->samp_frac_num[i],st->samp_frac_num[i],st->den_rate,old_den) != RESAMPLER_ERR_SUCCESS)
+ return RESAMPLER_ERR_OVERFLOW;
+ /* Safety net */
+ if (st->samp_frac_num[i] >= st->den_rate)
+ st->samp_frac_num[i] = st->den_rate-1;
+ }
+ }
+
+ if (st->initialised)
+ return update_filter(st);
+ return RESAMPLER_ERR_SUCCESS;
+}
+
+EXPORT void speex_resampler_get_ratio(SpeexResamplerState *st, spx_uint32_t *ratio_num, spx_uint32_t *ratio_den)
+{
+ *ratio_num = st->num_rate;
+ *ratio_den = st->den_rate;
+}
+
+EXPORT int speex_resampler_set_quality(SpeexResamplerState *st, int quality)
+{
+ if (quality > 10 || quality < 0)
+ return RESAMPLER_ERR_INVALID_ARG;
+ if (st->quality == quality)
+ return RESAMPLER_ERR_SUCCESS;
+ st->quality = quality;
+ if (st->initialised)
+ return update_filter(st);
+ return RESAMPLER_ERR_SUCCESS;
+}
+
+EXPORT void speex_resampler_get_quality(SpeexResamplerState *st, int *quality)
+{
+ *quality = st->quality;
+}
+
+EXPORT void speex_resampler_set_input_stride(SpeexResamplerState *st, spx_uint32_t stride)
+{
+ st->in_stride = stride;
+}
+
+EXPORT void speex_resampler_get_input_stride(SpeexResamplerState *st, spx_uint32_t *stride)
+{
+ *stride = st->in_stride;
+}
+
+EXPORT void speex_resampler_set_output_stride(SpeexResamplerState *st, spx_uint32_t stride)
+{
+ st->out_stride = stride;
+}
+
+EXPORT void speex_resampler_get_output_stride(SpeexResamplerState *st, spx_uint32_t *stride)
+{
+ *stride = st->out_stride;
+}
+
+EXPORT int speex_resampler_get_input_latency(SpeexResamplerState *st)
+{
+ return st->filt_len / 2;
+}
+
+EXPORT int speex_resampler_get_output_latency(SpeexResamplerState *st)
+{
+ return ((st->filt_len / 2) * st->den_rate + (st->num_rate >> 1)) / st->num_rate;
+}
+
+EXPORT int speex_resampler_skip_zeros(SpeexResamplerState *st)
+{
+ spx_uint32_t i;
+ for (i=0;i<st->nb_channels;i++)
+ st->last_sample[i] = st->filt_len/2;
+ return RESAMPLER_ERR_SUCCESS;
+}
+
+EXPORT int speex_resampler_reset_mem(SpeexResamplerState *st)
+{
+ spx_uint32_t i;
+ for (i=0;i<st->nb_channels;i++)
+ {
+ st->last_sample[i] = 0;
+ st->magic_samples[i] = 0;
+ st->samp_frac_num[i] = 0;
+ }
+ for (i=0;i<st->nb_channels*(st->filt_len-1);i++)
+ st->mem[i] = 0;
+ return RESAMPLER_ERR_SUCCESS;
+}
+
+EXPORT const char *speex_resampler_strerror(int err)
+{
+ switch (err)
+ {
+ case RESAMPLER_ERR_SUCCESS:
+ return "Success.";
+ case RESAMPLER_ERR_ALLOC_FAILED:
+ return "Memory allocation failed.";
+ case RESAMPLER_ERR_BAD_STATE:
+ return "Bad resampler state.";
+ case RESAMPLER_ERR_INVALID_ARG:
+ return "Invalid argument.";
+ case RESAMPLER_ERR_PTR_OVERLAP:
+ return "Input and output buffers overlap.";
+ default:
+ return "Unknown error. Bad error code or strange version mismatch.";
+ }
+}
diff --git a/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/resample_neon.h b/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/resample_neon.h
new file mode 100644
index 0000000000..0acbd27b9a
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/resample_neon.h
@@ -0,0 +1,201 @@
+/* Copyright (C) 2007-2008 Jean-Marc Valin
+ * Copyright (C) 2008 Thorvald Natvig
+ * Copyright (C) 2011 Texas Instruments
+ * author Jyri Sarha
+ */
+/**
+ @file resample_neon.h
+ @brief Resampler functions (NEON version)
+*/
+/*
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ - Neither the name of the Xiph.org Foundation nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <arm_neon.h>
+
+#ifdef FIXED_POINT
+#ifdef __thumb2__
+static inline int32_t saturate_32bit_to_16bit(int32_t a) {
+ int32_t ret;
+ asm ("ssat %[ret], #16, %[a]"
+ : [ret] "=&r" (ret)
+ : [a] "r" (a)
+ : );
+ return ret;
+}
+#else
+static inline int32_t saturate_32bit_to_16bit(int32_t a) {
+ int32_t ret;
+ asm ("vmov.s32 d0[0], %[a]\n"
+ "vqmovn.s32 d0, q0\n"
+ "vmov.s16 %[ret], d0[0]\n"
+ : [ret] "=&r" (ret)
+ : [a] "r" (a)
+ : "q0");
+ return ret;
+}
+#endif
+#undef WORD2INT
+#define WORD2INT(x) (saturate_32bit_to_16bit(x))
+
+#define OVERRIDE_INNER_PRODUCT_SINGLE
+/* Only works when len % 4 == 0 */
+static inline int32_t inner_product_single(const int16_t *a, const int16_t *b, unsigned int len)
+{
+ int32_t ret;
+ uint32_t remainder = len % 16;
+ len = len - remainder;
+
+ asm volatile (" cmp %[len], #0\n"
+ " bne 1f\n"
+ " vld1.16 {d16}, [%[b]]!\n"
+ " vld1.16 {d20}, [%[a]]!\n"
+ " subs %[remainder], %[remainder], #4\n"
+ " vmull.s16 q0, d16, d20\n"
+ " beq 5f\n"
+ " b 4f\n"
+ "1:"
+ " vld1.16 {d16, d17, d18, d19}, [%[b]]!\n"
+ " vld1.16 {d20, d21, d22, d23}, [%[a]]!\n"
+ " subs %[len], %[len], #16\n"
+ " vmull.s16 q0, d16, d20\n"
+ " vmlal.s16 q0, d17, d21\n"
+ " vmlal.s16 q0, d18, d22\n"
+ " vmlal.s16 q0, d19, d23\n"
+ " beq 3f\n"
+ "2:"
+ " vld1.16 {d16, d17, d18, d19}, [%[b]]!\n"
+ " vld1.16 {d20, d21, d22, d23}, [%[a]]!\n"
+ " subs %[len], %[len], #16\n"
+ " vmlal.s16 q0, d16, d20\n"
+ " vmlal.s16 q0, d17, d21\n"
+ " vmlal.s16 q0, d18, d22\n"
+ " vmlal.s16 q0, d19, d23\n"
+ " bne 2b\n"
+ "3:"
+ " cmp %[remainder], #0\n"
+ " beq 5f\n"
+ "4:"
+ " vld1.16 {d16}, [%[b]]!\n"
+ " vld1.16 {d20}, [%[a]]!\n"
+ " subs %[remainder], %[remainder], #4\n"
+ " vmlal.s16 q0, d16, d20\n"
+ " bne 4b\n"
+ "5:"
+ " vaddl.s32 q0, d0, d1\n"
+ " vadd.s64 d0, d0, d1\n"
+ " vqmovn.s64 d0, q0\n"
+ " vqrshrn.s32 d0, q0, #15\n"
+ " vmov.s16 %[ret], d0[0]\n"
+ : [ret] "=&r" (ret), [a] "+r" (a), [b] "+r" (b),
+ [len] "+r" (len), [remainder] "+r" (remainder)
+ :
+ : "cc", "q0",
+ "d16", "d17", "d18", "d19",
+ "d20", "d21", "d22", "d23");
+
+ return ret;
+}
+#elif defined(FLOATING_POINT)
+
+static inline int32_t saturate_float_to_16bit(float a) {
+ int32_t ret;
+ asm ("vmov.f32 d0[0], %[a]\n"
+ "vcvt.s32.f32 d0, d0, #15\n"
+ "vqrshrn.s32 d0, q0, #15\n"
+ "vmov.s16 %[ret], d0[0]\n"
+ : [ret] "=&r" (ret)
+ : [a] "r" (a)
+ : "q0");
+ return ret;
+}
+#undef WORD2INT
+#define WORD2INT(x) (saturate_float_to_16bit(x))
+
+#define OVERRIDE_INNER_PRODUCT_SINGLE
+/* Only works when len % 4 == 0 */
+static inline float inner_product_single(const float *a, const float *b, unsigned int len)
+{
+ float ret;
+ uint32_t remainder = len % 16;
+ len = len - remainder;
+
+ asm volatile (" cmp %[len], #0\n"
+ " bne 1f\n"
+ " vld1.32 {q4}, [%[b]]!\n"
+ " vld1.32 {q8}, [%[a]]!\n"
+ " subs %[remainder], %[remainder], #4\n"
+ " vmul.f32 q0, q4, q8\n"
+ " bne 4f\n"
+ " b 5f\n"
+ "1:"
+ " vld1.32 {q4, q5}, [%[b]]!\n"
+ " vld1.32 {q8, q9}, [%[a]]!\n"
+ " vld1.32 {q6, q7}, [%[b]]!\n"
+ " vld1.32 {q10, q11}, [%[a]]!\n"
+ " subs %[len], %[len], #16\n"
+ " vmul.f32 q0, q4, q8\n"
+ " vmul.f32 q1, q5, q9\n"
+ " vmul.f32 q2, q6, q10\n"
+ " vmul.f32 q3, q7, q11\n"
+ " beq 3f\n"
+ "2:"
+ " vld1.32 {q4, q5}, [%[b]]!\n"
+ " vld1.32 {q8, q9}, [%[a]]!\n"
+ " vld1.32 {q6, q7}, [%[b]]!\n"
+ " vld1.32 {q10, q11}, [%[a]]!\n"
+ " subs %[len], %[len], #16\n"
+ " vmla.f32 q0, q4, q8\n"
+ " vmla.f32 q1, q5, q9\n"
+ " vmla.f32 q2, q6, q10\n"
+ " vmla.f32 q3, q7, q11\n"
+ " bne 2b\n"
+ "3:"
+ " vadd.f32 q4, q0, q1\n"
+ " vadd.f32 q5, q2, q3\n"
+ " cmp %[remainder], #0\n"
+ " vadd.f32 q0, q4, q5\n"
+ " beq 5f\n"
+ "4:"
+ " vld1.32 {q6}, [%[b]]!\n"
+ " vld1.32 {q10}, [%[a]]!\n"
+ " subs %[remainder], %[remainder], #4\n"
+ " vmla.f32 q0, q6, q10\n"
+ " bne 4b\n"
+ "5:"
+ " vadd.f32 d0, d0, d1\n"
+ " vpadd.f32 d0, d0, d0\n"
+ " vmov.f32 %[ret], d0[0]\n"
+ : [ret] "=&r" (ret), [a] "+r" (a), [b] "+r" (b),
+ [len] "+l" (len), [remainder] "+l" (remainder)
+ :
+ : "cc", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8",
+ "q9", "q10", "q11");
+ return ret;
+}
+#endif
diff --git a/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/resample_sse.h b/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/resample_sse.h
new file mode 100644
index 0000000000..fed5b8276f
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/resample_sse.h
@@ -0,0 +1,128 @@
+/* Copyright (C) 2007-2008 Jean-Marc Valin
+ * Copyright (C) 2008 Thorvald Natvig
+ */
+/**
+ @file resample_sse.h
+ @brief Resampler functions (SSE version)
+*/
+/*
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ - Neither the name of the Xiph.org Foundation nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <xmmintrin.h>
+
+#define OVERRIDE_INNER_PRODUCT_SINGLE
+static inline float inner_product_single(const float *a, const float *b, unsigned int len)
+{
+ int i;
+ float ret;
+ __m128 sum = _mm_setzero_ps();
+ for (i=0;i<len;i+=8)
+ {
+ sum = _mm_add_ps(sum, _mm_mul_ps(_mm_loadu_ps(a+i), _mm_loadu_ps(b+i)));
+ sum = _mm_add_ps(sum, _mm_mul_ps(_mm_loadu_ps(a+i+4), _mm_loadu_ps(b+i+4)));
+ }
+ sum = _mm_add_ps(sum, _mm_movehl_ps(sum, sum));
+ sum = _mm_add_ss(sum, _mm_shuffle_ps(sum, sum, 0x55));
+ _mm_store_ss(&ret, sum);
+ return ret;
+}
+
+#define OVERRIDE_INTERPOLATE_PRODUCT_SINGLE
+static inline float interpolate_product_single(const float *a, const float *b, unsigned int len, const spx_uint32_t oversample, float *frac) {
+ int i;
+ float ret;
+ __m128 sum = _mm_setzero_ps();
+ __m128 f = _mm_loadu_ps(frac);
+ for(i=0;i<len;i+=2)
+ {
+ sum = _mm_add_ps(sum, _mm_mul_ps(_mm_load1_ps(a+i), _mm_loadu_ps(b+i*oversample)));
+ sum = _mm_add_ps(sum, _mm_mul_ps(_mm_load1_ps(a+i+1), _mm_loadu_ps(b+(i+1)*oversample)));
+ }
+ sum = _mm_mul_ps(f, sum);
+ sum = _mm_add_ps(sum, _mm_movehl_ps(sum, sum));
+ sum = _mm_add_ss(sum, _mm_shuffle_ps(sum, sum, 0x55));
+ _mm_store_ss(&ret, sum);
+ return ret;
+}
+
+#ifdef _USE_SSE2
+#include <emmintrin.h>
+#define OVERRIDE_INNER_PRODUCT_DOUBLE
+
+static inline double inner_product_double(const float *a, const float *b, unsigned int len)
+{
+ int i;
+ double ret;
+ __m128d sum = _mm_setzero_pd();
+ __m128 t;
+ for (i=0;i<len;i+=8)
+ {
+ t = _mm_mul_ps(_mm_loadu_ps(a+i), _mm_loadu_ps(b+i));
+ sum = _mm_add_pd(sum, _mm_cvtps_pd(t));
+ sum = _mm_add_pd(sum, _mm_cvtps_pd(_mm_movehl_ps(t, t)));
+
+ t = _mm_mul_ps(_mm_loadu_ps(a+i+4), _mm_loadu_ps(b+i+4));
+ sum = _mm_add_pd(sum, _mm_cvtps_pd(t));
+ sum = _mm_add_pd(sum, _mm_cvtps_pd(_mm_movehl_ps(t, t)));
+ }
+ sum = _mm_add_sd(sum, _mm_unpackhi_pd(sum, sum));
+ _mm_store_sd(&ret, sum);
+ return ret;
+}
+
+#define OVERRIDE_INTERPOLATE_PRODUCT_DOUBLE
+static inline double interpolate_product_double(const float *a, const float *b, unsigned int len, const spx_uint32_t oversample, float *frac) {
+ int i;
+ double ret;
+ __m128d sum;
+ __m128d sum1 = _mm_setzero_pd();
+ __m128d sum2 = _mm_setzero_pd();
+ __m128 f = _mm_loadu_ps(frac);
+ __m128d f1 = _mm_cvtps_pd(f);
+ __m128d f2 = _mm_cvtps_pd(_mm_movehl_ps(f,f));
+ __m128 t;
+ for(i=0;i<len;i+=2)
+ {
+ t = _mm_mul_ps(_mm_load1_ps(a+i), _mm_loadu_ps(b+i*oversample));
+ sum1 = _mm_add_pd(sum1, _mm_cvtps_pd(t));
+ sum2 = _mm_add_pd(sum2, _mm_cvtps_pd(_mm_movehl_ps(t, t)));
+
+ t = _mm_mul_ps(_mm_load1_ps(a+i+1), _mm_loadu_ps(b+(i+1)*oversample));
+ sum1 = _mm_add_pd(sum1, _mm_cvtps_pd(t));
+ sum2 = _mm_add_pd(sum2, _mm_cvtps_pd(_mm_movehl_ps(t, t)));
+ }
+ sum1 = _mm_mul_pd(f1, sum1);
+ sum2 = _mm_mul_pd(f2, sum2);
+ sum = _mm_add_pd(sum1, sum2);
+ sum = _mm_add_sd(sum, _mm_unpackhi_pd(sum, sum));
+ _mm_store_sd(&ret, sum);
+ return ret;
+}
+
+#endif
diff --git a/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/speex_config_types.h b/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/speex_config_types.h
new file mode 100644
index 0000000000..cea362890b
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/speex_config_types.h
@@ -0,0 +1,10 @@
+#ifndef __SPEEX_TYPES_H__
+#define __SPEEX_TYPES_H__
+
+/* these are filled in by configure */
+typedef int16_t spx_int16_t;
+typedef uint16_t spx_uint16_t;
+typedef int32_t spx_int32_t;
+typedef uint32_t spx_uint32_t;
+
+ #endif
diff --git a/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/speex_resampler.h b/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/speex_resampler.h
new file mode 100644
index 0000000000..901de37b3d
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/speex_resampler.h
@@ -0,0 +1,343 @@
+/* Copyright (C) 2007 Jean-Marc Valin
+
+ File: speex_resampler.h
+ Resampling code
+
+ The design goals of this code are:
+ - Very fast algorithm
+ - Low memory requirement
+ - Good *perceptual* quality (and not best SNR)
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ 3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+#ifndef SPEEX_RESAMPLER_H
+#define SPEEX_RESAMPLER_H
+
+#ifdef OUTSIDE_SPEEX
+
+/********* WARNING: MENTAL SANITY ENDS HERE *************/
+
+/* If the resampler is defined outside of Speex, we change the symbol names so that
+ there won't be any clash if linking with Speex later on. */
+
+/* #define RANDOM_PREFIX your software name here */
+#ifndef RANDOM_PREFIX
+#error "Please define RANDOM_PREFIX (above) to something specific to your project to prevent symbol name clashes"
+#endif
+
+#define CAT_PREFIX2(a,b) a ## b
+#define CAT_PREFIX(a,b) CAT_PREFIX2(a, b)
+
+#define speex_resampler_init CAT_PREFIX(RANDOM_PREFIX,_resampler_init)
+#define speex_resampler_init_frac CAT_PREFIX(RANDOM_PREFIX,_resampler_init_frac)
+#define speex_resampler_destroy CAT_PREFIX(RANDOM_PREFIX,_resampler_destroy)
+#define speex_resampler_process_float CAT_PREFIX(RANDOM_PREFIX,_resampler_process_float)
+#define speex_resampler_process_int CAT_PREFIX(RANDOM_PREFIX,_resampler_process_int)
+#define speex_resampler_process_interleaved_float CAT_PREFIX(RANDOM_PREFIX,_resampler_process_interleaved_float)
+#define speex_resampler_process_interleaved_int CAT_PREFIX(RANDOM_PREFIX,_resampler_process_interleaved_int)
+#define speex_resampler_set_rate CAT_PREFIX(RANDOM_PREFIX,_resampler_set_rate)
+#define speex_resampler_get_rate CAT_PREFIX(RANDOM_PREFIX,_resampler_get_rate)
+#define speex_resampler_set_rate_frac CAT_PREFIX(RANDOM_PREFIX,_resampler_set_rate_frac)
+#define speex_resampler_get_ratio CAT_PREFIX(RANDOM_PREFIX,_resampler_get_ratio)
+#define speex_resampler_set_quality CAT_PREFIX(RANDOM_PREFIX,_resampler_set_quality)
+#define speex_resampler_get_quality CAT_PREFIX(RANDOM_PREFIX,_resampler_get_quality)
+#define speex_resampler_set_input_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_set_input_stride)
+#define speex_resampler_get_input_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_get_input_stride)
+#define speex_resampler_set_output_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_set_output_stride)
+#define speex_resampler_get_output_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_get_output_stride)
+#define speex_resampler_get_input_latency CAT_PREFIX(RANDOM_PREFIX,_resampler_get_input_latency)
+#define speex_resampler_get_output_latency CAT_PREFIX(RANDOM_PREFIX,_resampler_get_output_latency)
+#define speex_resampler_skip_zeros CAT_PREFIX(RANDOM_PREFIX,_resampler_skip_zeros)
+#define speex_resampler_reset_mem CAT_PREFIX(RANDOM_PREFIX,_resampler_reset_mem)
+#define speex_resampler_strerror CAT_PREFIX(RANDOM_PREFIX,_resampler_strerror)
+
+#define spx_int16_t short
+#define spx_int32_t int
+#define spx_uint16_t unsigned short
+#define spx_uint32_t unsigned int
+
+#define speex_assert(cond)
+
+#else /* OUTSIDE_SPEEX */
+
+#include "speexdsp_types.h"
+
+#endif /* OUTSIDE_SPEEX */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SPEEX_RESAMPLER_QUALITY_MAX 10
+#define SPEEX_RESAMPLER_QUALITY_MIN 0
+#define SPEEX_RESAMPLER_QUALITY_DEFAULT 4
+#define SPEEX_RESAMPLER_QUALITY_VOIP 3
+#define SPEEX_RESAMPLER_QUALITY_DESKTOP 5
+
+enum {
+ RESAMPLER_ERR_SUCCESS = 0,
+ RESAMPLER_ERR_ALLOC_FAILED = 1,
+ RESAMPLER_ERR_BAD_STATE = 2,
+ RESAMPLER_ERR_INVALID_ARG = 3,
+ RESAMPLER_ERR_PTR_OVERLAP = 4,
+ RESAMPLER_ERR_OVERFLOW = 5,
+
+ RESAMPLER_ERR_MAX_ERROR
+};
+
+struct SpeexResamplerState_;
+typedef struct SpeexResamplerState_ SpeexResamplerState;
+
+/** Create a new resampler with integer input and output rates.
+ * @param nb_channels Number of channels to be processed
+ * @param in_rate Input sampling rate (integer number of Hz).
+ * @param out_rate Output sampling rate (integer number of Hz).
+ * @param quality Resampling quality between 0 and 10, where 0 has poor quality
+ * and 10 has very high quality.
+ * @return Newly created resampler state
+ * @retval NULL Error: not enough memory
+ */
+SpeexResamplerState *speex_resampler_init(spx_uint32_t nb_channels,
+ spx_uint32_t in_rate,
+ spx_uint32_t out_rate,
+ int quality,
+ int *err);
+
+/** Create a new resampler with fractional input/output rates. The sampling
+ * rate ratio is an arbitrary rational number with both the numerator and
+ * denominator being 32-bit integers.
+ * @param nb_channels Number of channels to be processed
+ * @param ratio_num Numerator of the sampling rate ratio
+ * @param ratio_den Denominator of the sampling rate ratio
+ * @param in_rate Input sampling rate rounded to the nearest integer (in Hz).
+ * @param out_rate Output sampling rate rounded to the nearest integer (in Hz).
+ * @param quality Resampling quality between 0 and 10, where 0 has poor quality
+ * and 10 has very high quality.
+ * @return Newly created resampler state
+ * @retval NULL Error: not enough memory
+ */
+SpeexResamplerState *speex_resampler_init_frac(spx_uint32_t nb_channels,
+ spx_uint32_t ratio_num,
+ spx_uint32_t ratio_den,
+ spx_uint32_t in_rate,
+ spx_uint32_t out_rate,
+ int quality,
+ int *err);
+
+/** Destroy a resampler state.
+ * @param st Resampler state
+ */
+void speex_resampler_destroy(SpeexResamplerState *st);
+
+/** Resample a float array. The input and output buffers must *not* overlap.
+ * @param st Resampler state
+ * @param channel_index Index of the channel to process for the multi-channel
+ * base (0 otherwise)
+ * @param in Input buffer
+ * @param in_len Number of input samples in the input buffer. Returns the
+ * number of samples processed
+ * @param out Output buffer
+ * @param out_len Size of the output buffer. Returns the number of samples written
+ */
+int speex_resampler_process_float(SpeexResamplerState *st,
+ spx_uint32_t channel_index,
+ const float *in,
+ spx_uint32_t *in_len,
+ float *out,
+ spx_uint32_t *out_len);
+
+/** Resample an int array. The input and output buffers must *not* overlap.
+ * @param st Resampler state
+ * @param channel_index Index of the channel to process for the multi-channel
+ * base (0 otherwise)
+ * @param in Input buffer
+ * @param in_len Number of input samples in the input buffer. Returns the number
+ * of samples processed
+ * @param out Output buffer
+ * @param out_len Size of the output buffer. Returns the number of samples written
+ */
+int speex_resampler_process_int(SpeexResamplerState *st,
+ spx_uint32_t channel_index,
+ const spx_int16_t *in,
+ spx_uint32_t *in_len,
+ spx_int16_t *out,
+ spx_uint32_t *out_len);
+
+/** Resample an interleaved float array. The input and output buffers must *not* overlap.
+ * @param st Resampler state
+ * @param in Input buffer
+ * @param in_len Number of input samples in the input buffer. Returns the number
+ * of samples processed. This is all per-channel.
+ * @param out Output buffer
+ * @param out_len Size of the output buffer. Returns the number of samples written.
+ * This is all per-channel.
+ */
+int speex_resampler_process_interleaved_float(SpeexResamplerState *st,
+ const float *in,
+ spx_uint32_t *in_len,
+ float *out,
+ spx_uint32_t *out_len);
+
+/** Resample an interleaved int array. The input and output buffers must *not* overlap.
+ * @param st Resampler state
+ * @param in Input buffer
+ * @param in_len Number of input samples in the input buffer. Returns the number
+ * of samples processed. This is all per-channel.
+ * @param out Output buffer
+ * @param out_len Size of the output buffer. Returns the number of samples written.
+ * This is all per-channel.
+ */
+int speex_resampler_process_interleaved_int(SpeexResamplerState *st,
+ const spx_int16_t *in,
+ spx_uint32_t *in_len,
+ spx_int16_t *out,
+ spx_uint32_t *out_len);
+
+/** Set (change) the input/output sampling rates (integer value).
+ * @param st Resampler state
+ * @param in_rate Input sampling rate (integer number of Hz).
+ * @param out_rate Output sampling rate (integer number of Hz).
+ */
+int speex_resampler_set_rate(SpeexResamplerState *st,
+ spx_uint32_t in_rate,
+ spx_uint32_t out_rate);
+
+/** Get the current input/output sampling rates (integer value).
+ * @param st Resampler state
+ * @param in_rate Input sampling rate (integer number of Hz) copied.
+ * @param out_rate Output sampling rate (integer number of Hz) copied.
+ */
+void speex_resampler_get_rate(SpeexResamplerState *st,
+ spx_uint32_t *in_rate,
+ spx_uint32_t *out_rate);
+
+/** Set (change) the input/output sampling rates and resampling ratio
+ * (fractional values in Hz supported).
+ * @param st Resampler state
+ * @param ratio_num Numerator of the sampling rate ratio
+ * @param ratio_den Denominator of the sampling rate ratio
+ * @param in_rate Input sampling rate rounded to the nearest integer (in Hz).
+ * @param out_rate Output sampling rate rounded to the nearest integer (in Hz).
+ */
+int speex_resampler_set_rate_frac(SpeexResamplerState *st,
+ spx_uint32_t ratio_num,
+ spx_uint32_t ratio_den,
+ spx_uint32_t in_rate,
+ spx_uint32_t out_rate);
+
+/** Get the current resampling ratio. This will be reduced to the least
+ * common denominator.
+ * @param st Resampler state
+ * @param ratio_num Numerator of the sampling rate ratio copied
+ * @param ratio_den Denominator of the sampling rate ratio copied
+ */
+void speex_resampler_get_ratio(SpeexResamplerState *st,
+ spx_uint32_t *ratio_num,
+ spx_uint32_t *ratio_den);
+
+/** Set (change) the conversion quality.
+ * @param st Resampler state
+ * @param quality Resampling quality between 0 and 10, where 0 has poor
+ * quality and 10 has very high quality.
+ */
+int speex_resampler_set_quality(SpeexResamplerState *st,
+ int quality);
+
+/** Get the conversion quality.
+ * @param st Resampler state
+ * @param quality Resampling quality between 0 and 10, where 0 has poor
+ * quality and 10 has very high quality.
+ */
+void speex_resampler_get_quality(SpeexResamplerState *st,
+ int *quality);
+
+/** Set (change) the input stride.
+ * @param st Resampler state
+ * @param stride Input stride
+ */
+void speex_resampler_set_input_stride(SpeexResamplerState *st,
+ spx_uint32_t stride);
+
+/** Get the input stride.
+ * @param st Resampler state
+ * @param stride Input stride copied
+ */
+void speex_resampler_get_input_stride(SpeexResamplerState *st,
+ spx_uint32_t *stride);
+
+/** Set (change) the output stride.
+ * @param st Resampler state
+ * @param stride Output stride
+ */
+void speex_resampler_set_output_stride(SpeexResamplerState *st,
+ spx_uint32_t stride);
+
+/** Get the output stride.
+ * @param st Resampler state copied
+ * @param stride Output stride
+ */
+void speex_resampler_get_output_stride(SpeexResamplerState *st,
+ spx_uint32_t *stride);
+
+/** Get the latency introduced by the resampler measured in input samples.
+ * @param st Resampler state
+ */
+int speex_resampler_get_input_latency(SpeexResamplerState *st);
+
+/** Get the latency introduced by the resampler measured in output samples.
+ * @param st Resampler state
+ */
+int speex_resampler_get_output_latency(SpeexResamplerState *st);
+
+/** Make sure that the first samples to go out of the resamplers don't have
+ * leading zeros. This is only useful before starting to use a newly created
+ * resampler. It is recommended to use that when resampling an audio file, as
+ * it will generate a file with the same length. For real-time processing,
+ * it is probably easier not to use this call (so that the output duration
+ * is the same for the first frame).
+ * @param st Resampler state
+ */
+int speex_resampler_skip_zeros(SpeexResamplerState *st);
+
+/** Reset a resampler so a new (unrelated) stream can be processed.
+ * @param st Resampler state
+ */
+int speex_resampler_reset_mem(SpeexResamplerState *st);
+
+/** Returns the English meaning for an error code
+ * @param err Error code
+ * @return English string
+ */
+const char *speex_resampler_strerror(int err);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/stack_alloc.h b/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/stack_alloc.h
new file mode 100644
index 0000000000..6c56334f86
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/subprojects/speex/stack_alloc.h
@@ -0,0 +1,115 @@
+/* Copyright (C) 2002 Jean-Marc Valin */
+/**
+ @file stack_alloc.h
+ @brief Temporary memory allocation on stack
+*/
+/*
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ - Neither the name of the Xiph.org Foundation nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef STACK_ALLOC_H
+#define STACK_ALLOC_H
+
+#ifdef USE_ALLOCA
+# ifdef WIN32
+# include <malloc.h>
+# else
+# ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+# else
+# include <stdlib.h>
+# endif
+# endif
+#endif
+
+/**
+ * @def ALIGN(stack, size)
+ *
+ * Aligns the stack to a 'size' boundary
+ *
+ * @param stack Stack
+ * @param size New size boundary
+ */
+
+/**
+ * @def PUSH(stack, size, type)
+ *
+ * Allocates 'size' elements of type 'type' on the stack
+ *
+ * @param stack Stack
+ * @param size Number of elements
+ * @param type Type of element
+ */
+
+/**
+ * @def VARDECL(var)
+ *
+ * Declare variable on stack
+ *
+ * @param var Variable to declare
+ */
+
+/**
+ * @def ALLOC(var, size, type)
+ *
+ * Allocate 'size' elements of 'type' on stack
+ *
+ * @param var Name of variable to allocate
+ * @param size Number of elements
+ * @param type Type of element
+ */
+
+#ifdef ENABLE_VALGRIND
+
+#include <valgrind/memcheck.h>
+
+#define ALIGN(stack, size) ((stack) += ((size) - (long)(stack)) & ((size) - 1))
+
+#define PUSH(stack, size, type) (VALGRIND_MAKE_NOACCESS(stack, 1000),ALIGN((stack),sizeof(type)),VALGRIND_MAKE_WRITABLE(stack, ((size)*sizeof(type))),(stack)+=((size)*sizeof(type)),(type*)((stack)-((size)*sizeof(type))))
+
+#else
+
+#define ALIGN(stack, size) ((stack) += ((size) - (long)(stack)) & ((size) - 1))
+
+#define PUSH(stack, size, type) (ALIGN((stack),sizeof(type)),(stack)+=((size)*sizeof(type)),(type*)((stack)-((size)*sizeof(type))))
+
+#endif
+
+#if defined(VAR_ARRAYS)
+#define VARDECL(var)
+#define ALLOC(var, size, type) type var[size]
+#elif defined(USE_ALLOCA)
+#define VARDECL(var) var
+#define ALLOC(var, size, type) var = alloca(sizeof(type)*(size))
+#else
+#define VARDECL(var) var
+#define ALLOC(var, size, type) var = PUSH(stack, size, type)
+#endif
+
+
+#endif
diff --git a/third_party/rust/cubeb-sys/libcubeb/test/README.md b/third_party/rust/cubeb-sys/libcubeb/test/README.md
new file mode 100644
index 0000000000..7318263989
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/test/README.md
@@ -0,0 +1,13 @@
+Notes on writing tests.
+
+The googletest submodule is currently at 1.6 rather than the latest, and should
+only be updated to track the version used in Gecko to make test compatibility
+easier.
+
+Always #include "gtest/gtest.h" before *anything* else.
+
+All tests should be part of the "cubeb" test case, e.g. TEST(cubeb, my_test).
+
+Tests are built stand-alone in cubeb, but built as a single unit in Gecko, so
+you must use unique names for globally visible items in each test, e.g. rather
+than state_cb use state_cb_my_test.
diff --git a/third_party/rust/cubeb-sys/libcubeb/test/common.h b/third_party/rust/cubeb-sys/libcubeb/test/common.h
new file mode 100644
index 0000000000..6b06f9fcf0
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/test/common.h
@@ -0,0 +1,180 @@
+/*
+ * Copyright © 2013 Sebastien Alaiwan
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#if !defined(TEST_COMMON)
+#define TEST_COMMON
+
+#if defined(_WIN32)
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <objbase.h>
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+
+#include "cubeb/cubeb.h"
+#include "cubeb_mixer.h"
+#include "gtest/gtest.h"
+#include <cstdarg>
+#include <cstdio>
+#include <cstring>
+
+template <typename T, size_t N>
+constexpr size_t
+ARRAY_LENGTH(T (&)[N])
+{
+ return N;
+}
+
+inline void
+delay(unsigned int ms)
+{
+#if defined(_WIN32)
+ Sleep(ms);
+#else
+ sleep(ms / 1000);
+ usleep(ms % 1000 * 1000);
+#endif
+}
+
+#if !defined(M_PI)
+#define M_PI 3.14159265358979323846
+#endif
+
+typedef struct {
+ char const * name;
+ unsigned int const channels;
+ uint32_t const layout;
+} layout_info;
+
+struct backend_caps {
+ const char * id;
+ const int input_capabilities;
+};
+
+// This static table allows knowing if a backend has audio input capabilities.
+// We don't rely on opening a stream and checking if it works, because this
+// would make the test skip the tests that make use of audio input, if a
+// particular backend has a bug that causes a failure during audio input stream
+// creation
+static backend_caps backend_capabilities[] = {
+ {"sun", 1}, {"wasapi", 1}, {"kai", 1}, {"audiounit", 1},
+ {"audiotrack", 0}, {"opensl", 1}, {"aaudio", 1}, {"jack", 1},
+ {"pulse", 1}, {"sndio", 1}, {"oss", 1}, {"winmm", 0},
+ {"alsa", 1},
+};
+
+inline int
+can_run_audio_input_test(cubeb * ctx)
+{
+ cubeb_device_collection devices;
+ int input_device_available = 0;
+ int r;
+ /* Bail out early if the host does not have input devices. */
+ r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &devices);
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "error enumerating devices.");
+ return 0;
+ }
+
+ if (devices.count == 0) {
+ fprintf(stderr, "no input device available, skipping test.\n");
+ cubeb_device_collection_destroy(ctx, &devices);
+ return 0;
+ }
+
+ for (uint32_t i = 0; i < devices.count; i++) {
+ input_device_available |=
+ (devices.device[i].state == CUBEB_DEVICE_STATE_ENABLED);
+ }
+
+ if (!input_device_available) {
+ fprintf(stderr, "there are input devices, but they are not "
+ "available, skipping\n");
+ }
+
+ cubeb_device_collection_destroy(ctx, &devices);
+
+ int backend_has_input_capabilities;
+ const char * backend_id = cubeb_get_backend_id(ctx);
+ for (uint32_t i = 0; i < sizeof(backend_capabilities) / sizeof(backend_caps);
+ i++) {
+ if (strcmp(backend_capabilities[i].id, backend_id) == 0) {
+ backend_has_input_capabilities =
+ backend_capabilities[i].input_capabilities;
+ }
+ }
+
+ return !!input_device_available && !!backend_has_input_capabilities;
+}
+
+inline void
+print_log(const char * msg, ...)
+{
+ va_list args;
+ va_start(args, msg);
+ vprintf(msg, args);
+ va_end(args);
+}
+
+/** Initialize cubeb with backend override.
+ * Create call cubeb_init passing value for CUBEB_BACKEND env var as
+ * override. */
+inline int
+common_init(cubeb ** ctx, char const * ctx_name)
+{
+#ifdef ENABLE_NORMAL_LOG
+ if (cubeb_set_log_callback(CUBEB_LOG_NORMAL, print_log) != CUBEB_OK) {
+ fprintf(stderr, "Set normal log callback failed\n");
+ }
+#endif
+
+#ifdef ENABLE_VERBOSE_LOG
+ if (cubeb_set_log_callback(CUBEB_LOG_VERBOSE, print_log) != CUBEB_OK) {
+ fprintf(stderr, "Set verbose log callback failed\n");
+ }
+#endif
+
+ int r;
+ char const * backend;
+ char const * ctx_backend;
+
+ backend = getenv("CUBEB_BACKEND");
+ r = cubeb_init(ctx, ctx_name, backend);
+ if (r == CUBEB_OK && backend) {
+ ctx_backend = cubeb_get_backend_id(*ctx);
+ if (strcmp(backend, ctx_backend) != 0) {
+ fprintf(stderr, "Requested backend `%s', got `%s'\n", backend,
+ ctx_backend);
+ }
+ }
+
+ return r;
+}
+
+#if defined(_WIN32)
+class TestEnvironment : public ::testing::Environment {
+public:
+ void SetUp() override { hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); }
+
+ void TearDown() override
+ {
+ if (SUCCEEDED(hr)) {
+ CoUninitialize();
+ }
+ }
+
+private:
+ HRESULT hr;
+};
+
+::testing::Environment * const foo_env =
+ ::testing::AddGlobalTestEnvironment(new TestEnvironment);
+#endif
+
+#endif /* TEST_COMMON */
diff --git a/third_party/rust/cubeb-sys/libcubeb/test/test_audio.cpp b/third_party/rust/cubeb-sys/libcubeb/test/test_audio.cpp
new file mode 100644
index 0000000000..7c99a1814a
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/test/test_audio.cpp
@@ -0,0 +1,253 @@
+/*
+ * Copyright © 2013 Sebastien Alaiwan <sebastien.alaiwan@gmail.com>
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* libcubeb api/function exhaustive test. Plays a series of tones in different
+ * conditions. */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include "cubeb/cubeb.h"
+#include <math.h>
+#include <memory>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+
+// #define ENABLE_NORMAL_LOG
+// #define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+using namespace std;
+
+#define MAX_NUM_CHANNELS 32
+#define VOLUME 0.2
+
+float
+get_frequency(int channel_index)
+{
+ return 220.0f * (channel_index + 1);
+}
+
+template <typename T>
+T
+ConvertSample(double input);
+template <>
+float
+ConvertSample(double input)
+{
+ return input;
+}
+template <>
+short
+ConvertSample(double input)
+{
+ return short(input * 32767.0f);
+}
+
+/* store the phase of the generated waveform */
+struct synth_state {
+ synth_state(int num_channels_, float sample_rate_)
+ : num_channels(num_channels_), sample_rate(sample_rate_)
+ {
+ for (int i = 0; i < MAX_NUM_CHANNELS; ++i)
+ phase[i] = 0.0f;
+ }
+
+ template <typename T> void run(T * audiobuffer, long nframes)
+ {
+ for (int c = 0; c < num_channels; ++c) {
+ float freq = get_frequency(c);
+ float phase_inc = 2.0 * M_PI * freq / sample_rate;
+ for (long n = 0; n < nframes; ++n) {
+ audiobuffer[n * num_channels + c] =
+ ConvertSample<T>(sin(phase[c]) * VOLUME);
+ phase[c] += phase_inc;
+ }
+ }
+ }
+
+private:
+ int num_channels;
+ float phase[MAX_NUM_CHANNELS];
+ float sample_rate;
+};
+
+template <typename T>
+long
+data_cb(cubeb_stream * /*stream*/, void * user, const void * /*inputbuffer*/,
+ void * outputbuffer, long nframes)
+{
+ synth_state * synth = (synth_state *)user;
+ synth->run((T *)outputbuffer, nframes);
+ return nframes;
+}
+
+void
+state_cb_audio(cubeb_stream * /*stream*/, void * /*user*/,
+ cubeb_state /*state*/)
+{
+}
+
+/* Our android backends don't support float, only int16. */
+int
+supports_float32(string backend_id)
+{
+ return backend_id != "opensl" && backend_id != "audiotrack";
+}
+
+/* Some backends don't have code to deal with more than mono or stereo. */
+int
+supports_channel_count(string backend_id, int nchannels)
+{
+ return nchannels <= 2 ||
+ (backend_id != "opensl" && backend_id != "audiotrack");
+}
+
+int
+run_test(int num_channels, int sampling_rate, int is_float)
+{
+ int r = CUBEB_OK;
+
+ cubeb * ctx = NULL;
+
+ r = common_init(&ctx, "Cubeb audio test: channels");
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "Error initializing cubeb library\n");
+ return r;
+ }
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, cubeb_destroy);
+
+ const char * backend_id = cubeb_get_backend_id(ctx);
+
+ if ((is_float && !supports_float32(backend_id)) ||
+ !supports_channel_count(backend_id, num_channels)) {
+ /* don't treat this as a test failure. */
+ return CUBEB_OK;
+ }
+
+ fprintf(stderr, "Testing %d channel(s), %d Hz, %s (%s)\n", num_channels,
+ sampling_rate, is_float ? "float" : "short",
+ cubeb_get_backend_id(ctx));
+
+ cubeb_stream_params params;
+ params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
+ params.rate = sampling_rate;
+ params.channels = num_channels;
+ params.layout = CUBEB_LAYOUT_UNDEFINED;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ synth_state synth(params.channels, params.rate);
+
+ cubeb_stream * stream = NULL;
+ r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, &params,
+ 4096, is_float ? &data_cb<float> : &data_cb<short>,
+ state_cb_audio, &synth);
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
+ return r;
+ }
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(200);
+ cubeb_stream_stop(stream);
+
+ return r;
+}
+
+int
+run_volume_test(int is_float)
+{
+ int r = CUBEB_OK;
+
+ cubeb * ctx = NULL;
+
+ r = common_init(&ctx, "Cubeb audio test");
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "Error initializing cubeb library\n");
+ return r;
+ }
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, cubeb_destroy);
+
+ const char * backend_id = cubeb_get_backend_id(ctx);
+
+ if ((is_float && !supports_float32(backend_id))) {
+ /* don't treat this as a test failure. */
+ return CUBEB_OK;
+ }
+
+ cubeb_stream_params params;
+ params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
+ params.rate = 44100;
+ params.channels = 2;
+ params.layout = CUBEB_LAYOUT_STEREO;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ synth_state synth(params.channels, params.rate);
+
+ cubeb_stream * stream = NULL;
+ r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, &params,
+ 4096, is_float ? &data_cb<float> : &data_cb<short>,
+ state_cb_audio, &synth);
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
+ return r;
+ }
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ fprintf(stderr, "Testing: volume\n");
+ for (int i = 0; i <= 4; ++i) {
+ fprintf(stderr, "Volume: %d%%\n", i * 25);
+
+ cubeb_stream_set_volume(stream, i / 4.0f);
+ cubeb_stream_start(stream);
+ delay(400);
+ cubeb_stream_stop(stream);
+ delay(100);
+ }
+
+ return r;
+}
+
+TEST(cubeb, run_volume_test_short) { ASSERT_EQ(run_volume_test(0), CUBEB_OK); }
+
+TEST(cubeb, run_volume_test_float) { ASSERT_EQ(run_volume_test(1), CUBEB_OK); }
+
+TEST(cubeb, run_channel_rate_test)
+{
+ unsigned int channel_values[] = {
+ 1, 2, 3, 4, 6,
+ };
+
+ int freq_values[] = {
+ 16000,
+ 24000,
+ 44100,
+ 48000,
+ };
+
+ for (auto channels : channel_values) {
+ for (auto freq : freq_values) {
+ ASSERT_TRUE(channels < MAX_NUM_CHANNELS);
+ fprintf(stderr, "--------------------------\n");
+ ASSERT_EQ(run_test(channels, freq, 0), CUBEB_OK);
+ ASSERT_EQ(run_test(channels, freq, 1), CUBEB_OK);
+ }
+ }
+}
+
+#undef MAX_NUM_CHANNELS
+#undef VOLUME
diff --git a/third_party/rust/cubeb-sys/libcubeb/test/test_callback_ret.cpp b/third_party/rust/cubeb-sys/libcubeb/test/test_callback_ret.cpp
new file mode 100644
index 0000000000..0737b60ae8
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/test/test_callback_ret.cpp
@@ -0,0 +1,254 @@
+/*
+ * Copyright � 2017 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* libcubeb api/function test. Test that different return values from user
+ specified callbacks are handled correctly. */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include "cubeb/cubeb.h"
+#include <atomic>
+#include <memory>
+#include <string>
+
+// #define ENABLE_NORMAL_LOG
+// #define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+const uint32_t SAMPLE_FREQUENCY = 48000;
+const cubeb_sample_format SAMPLE_FORMAT = CUBEB_SAMPLE_S16NE;
+
+enum test_direction { INPUT_ONLY, OUTPUT_ONLY, DUPLEX };
+
+// Structure which is used by data callbacks to track the total callbacks
+// executed vs the number of callbacks expected.
+struct user_state_callback_ret {
+ std::atomic<int> cb_count{0};
+ std::atomic<int> expected_cb_count{0};
+ std::atomic<int> error_state{0};
+};
+
+// Data callback that always returns 0
+long
+data_cb_ret_zero(cubeb_stream * stream, void * user, const void * inputbuffer,
+ void * outputbuffer, long nframes)
+{
+ user_state_callback_ret * u = (user_state_callback_ret *)user;
+ // If this is the first time the callback has been called set our expected
+ // callback count
+ if (u->cb_count == 0) {
+ u->expected_cb_count = 1;
+ }
+ u->cb_count++;
+ if (nframes < 1) {
+ // This shouldn't happen
+ EXPECT_TRUE(false) << "nframes should not be 0 in data callback!";
+ }
+ return 0;
+}
+
+// Data callback that always returns nframes - 1
+long
+data_cb_ret_nframes_minus_one(cubeb_stream * stream, void * user,
+ const void * inputbuffer, void * outputbuffer,
+ long nframes)
+{
+ user_state_callback_ret * u = (user_state_callback_ret *)user;
+ // If this is the first time the callback has been called set our expected
+ // callback count
+ if (u->cb_count == 0) {
+ u->expected_cb_count = 1;
+ }
+ u->cb_count++;
+ if (nframes < 1) {
+ // This shouldn't happen
+ EXPECT_TRUE(false) << "nframes should not be 0 in data callback!";
+ }
+ if (outputbuffer != NULL) {
+ // If we have an output buffer insert silence
+ short * ob = (short *)outputbuffer;
+ for (long i = 0; i < nframes - 1; i++) {
+ ob[i] = 0;
+ }
+ }
+ return nframes - 1;
+}
+
+// Data callback that always returns nframes
+long
+data_cb_ret_nframes(cubeb_stream * stream, void * user,
+ const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ user_state_callback_ret * u = (user_state_callback_ret *)user;
+ u->cb_count++;
+ // Every callback returns nframes, so every callback is expected
+ u->expected_cb_count++;
+ if (nframes < 1) {
+ // This shouldn't happen
+ EXPECT_TRUE(false) << "nframes should not be 0 in data callback!";
+ }
+ if (outputbuffer != NULL) {
+ // If we have an output buffer insert silence
+ short * ob = (short *)outputbuffer;
+ for (long i = 0; i < nframes; i++) {
+ ob[i] = 0;
+ }
+ }
+ return nframes;
+}
+
+// Data callback that always returns CUBEB_ERROR
+long
+data_cb_ret_error(cubeb_stream * stream, void * user, const void * inputbuffer,
+ void * outputbuffer, long nframes)
+{
+ user_state_callback_ret * u = (user_state_callback_ret *)user;
+ // If this is the first time the callback has been called set our expected
+ // callback count
+ if (u->cb_count == 0) {
+ u->expected_cb_count = 1;
+ }
+ u->cb_count++;
+ if (nframes < 1) {
+ // This shouldn't happen
+ EXPECT_TRUE(false) << "nframes should not be 0 in data callback!";
+ }
+ return CUBEB_ERROR;
+}
+
+void
+state_cb_ret(cubeb_stream * stream, void * user, cubeb_state state)
+{
+ if (stream == NULL)
+ return;
+ user_state_callback_ret * u = (user_state_callback_ret *)user;
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n");
+ break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n");
+ break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n");
+ break;
+ case CUBEB_STATE_ERROR:
+ fprintf(stderr, "stream error\n");
+ u->error_state.fetch_add(1);
+ break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+}
+
+void
+run_test_callback(test_direction direction, cubeb_data_callback data_cb,
+ const std::string & test_desc)
+{
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ user_state_callback_ret user_state;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb callback return value example");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, cubeb_destroy);
+
+ if ((direction == INPUT_ONLY || direction == DUPLEX) &&
+ !can_run_audio_input_test(ctx)) {
+ /* This test needs an available input device, skip it if this host does not
+ * have one or if the backend doesn't implement input. */
+ return;
+ }
+
+ // Setup all params, but only pass them later as required by direction
+ input_params.format = SAMPLE_FORMAT;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_NONE;
+ output_params = input_params;
+
+ r = cubeb_get_min_latency(ctx, &input_params, &latency_frames);
+ if (r != CUBEB_OK) {
+ // not fatal
+ latency_frames = 1024;
+ }
+
+ switch (direction) {
+ case INPUT_ONLY:
+ r = cubeb_stream_init(ctx, &stream, "Cubeb callback ret input", NULL,
+ &input_params, NULL, NULL, latency_frames, data_cb,
+ state_cb_ret, &user_state);
+ break;
+ case OUTPUT_ONLY:
+ r = cubeb_stream_init(ctx, &stream, "Cubeb callback ret output", NULL, NULL,
+ NULL, &output_params, latency_frames, data_cb,
+ state_cb_ret, &user_state);
+ break;
+ case DUPLEX:
+ r = cubeb_stream_init(ctx, &stream, "Cubeb callback ret duplex", NULL,
+ &input_params, NULL, &output_params, latency_frames,
+ data_cb, state_cb_ret, &user_state);
+ break;
+ default:
+ ASSERT_TRUE(false) << "Unrecognized test direction!";
+ }
+ EXPECT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(100);
+ cubeb_stream_stop(stream);
+
+ ASSERT_EQ(user_state.expected_cb_count, user_state.cb_count)
+ << "Callback called unexpected number of times for " << test_desc << "!";
+ // TODO: On some test configurations, the data_callback is never called.
+ if (data_cb == data_cb_ret_error && user_state.cb_count != 0) {
+ ASSERT_EQ(user_state.error_state, 1) << "Callback expected error state";
+ }
+}
+
+TEST(cubeb, test_input_callback)
+{
+ run_test_callback(INPUT_ONLY, data_cb_ret_zero, "input only, return 0");
+ run_test_callback(INPUT_ONLY, data_cb_ret_nframes_minus_one,
+ "input only, return nframes - 1");
+ run_test_callback(INPUT_ONLY, data_cb_ret_nframes,
+ "input only, return nframes");
+ run_test_callback(INPUT_ONLY, data_cb_ret_error,
+ "input only, return CUBEB_ERROR");
+}
+
+TEST(cubeb, test_output_callback)
+{
+ run_test_callback(OUTPUT_ONLY, data_cb_ret_zero, "output only, return 0");
+ run_test_callback(OUTPUT_ONLY, data_cb_ret_nframes_minus_one,
+ "output only, return nframes - 1");
+ run_test_callback(OUTPUT_ONLY, data_cb_ret_nframes,
+ "output only, return nframes");
+ run_test_callback(OUTPUT_ONLY, data_cb_ret_error,
+ "output only, return CUBEB_ERROR");
+}
+
+TEST(cubeb, test_duplex_callback)
+{
+ run_test_callback(DUPLEX, data_cb_ret_zero, "duplex, return 0");
+ run_test_callback(DUPLEX, data_cb_ret_nframes_minus_one,
+ "duplex, return nframes - 1");
+ run_test_callback(DUPLEX, data_cb_ret_nframes, "duplex, return nframes");
+ run_test_callback(DUPLEX, data_cb_ret_error, "duplex, return CUBEB_ERROR");
+}
diff --git a/third_party/rust/cubeb-sys/libcubeb/test/test_device_changed_callback.cpp b/third_party/rust/cubeb-sys/libcubeb/test/test_device_changed_callback.cpp
new file mode 100644
index 0000000000..ddf810da65
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/test/test_device_changed_callback.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright © 2018 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* libcubeb api/function test. Check behaviors of registering device changed
+ * callbacks for the streams. */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include "cubeb/cubeb.h"
+#include <memory>
+#include <stdio.h>
+
+// #define ENABLE_NORMAL_LOG
+// #define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+#define SAMPLE_FREQUENCY 48000
+#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
+#define INPUT_CHANNELS 1
+#define INPUT_LAYOUT CUBEB_LAYOUT_MONO
+#define OUTPUT_CHANNELS 2
+#define OUTPUT_LAYOUT CUBEB_LAYOUT_STEREO
+
+long
+data_callback(cubeb_stream * stream, void * user, const void * inputbuffer,
+ void * outputbuffer, long nframes)
+{
+ return 0;
+}
+
+void
+state_callback(cubeb_stream * stream, void * user, cubeb_state state)
+{
+}
+
+void
+device_changed_callback(void * user)
+{
+ fprintf(stderr, "device changed callback\n");
+ ASSERT_TRUE(false) << "Error: device changed callback"
+ " called without changing devices";
+}
+
+void
+test_registering_null_callback_twice(cubeb_stream * stream)
+{
+ int r = cubeb_stream_register_device_changed_callback(stream, nullptr);
+ if (r == CUBEB_ERROR_NOT_SUPPORTED) {
+ return;
+ }
+ ASSERT_EQ(r, CUBEB_OK) << "Error registering null device changed callback";
+
+ r = cubeb_stream_register_device_changed_callback(stream, nullptr);
+ ASSERT_EQ(r, CUBEB_OK)
+ << "Error registering null device changed callback again";
+}
+
+void
+test_registering_and_unregistering_callback(cubeb_stream * stream)
+{
+ int r = cubeb_stream_register_device_changed_callback(
+ stream, device_changed_callback);
+ if (r == CUBEB_ERROR_NOT_SUPPORTED) {
+ return;
+ }
+ ASSERT_EQ(r, CUBEB_OK) << "Error registering device changed callback";
+
+ r = cubeb_stream_register_device_changed_callback(stream, nullptr);
+ ASSERT_EQ(r, CUBEB_OK) << "Error unregistering device changed callback";
+}
+
+TEST(cubeb, device_changed_callbacks)
+{
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r = CUBEB_OK;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb duplex example with device change");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ if (!can_run_audio_input_test(ctx)) {
+ return;
+ }
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, cubeb_destroy);
+
+ /* typical user-case: mono input, stereo output, low latency. */
+ input_params.format = STREAM_FORMAT;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = INPUT_CHANNELS;
+ input_params.layout = INPUT_LAYOUT;
+ input_params.prefs = CUBEB_STREAM_PREF_NONE;
+ output_params.format = STREAM_FORMAT;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = OUTPUT_CHANNELS;
+ output_params.layout = OUTPUT_LAYOUT;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb duplex", NULL, &input_params, NULL,
+ &output_params, latency_frames, data_callback,
+ state_callback, nullptr);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ test_registering_null_callback_twice(stream);
+
+ test_registering_and_unregistering_callback(stream);
+
+ cubeb_stream_destroy(stream);
+}
+
+#undef SAMPLE_FREQUENCY
+#undef STREAM_FORMAT
+#undef INPUT_CHANNELS
+#undef INPUT_LAYOUT
+#undef OUTPUT_CHANNELS
+#undef OUTPUT_LAYOUT
diff --git a/third_party/rust/cubeb-sys/libcubeb/test/test_devices.cpp b/third_party/rust/cubeb-sys/libcubeb/test/test_devices.cpp
new file mode 100644
index 0000000000..d0eab2da67
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/test/test_devices.cpp
@@ -0,0 +1,264 @@
+/*
+ * Copyright © 2015 Haakon Sporsheim <haakon.sporsheim@telenordigital.com>
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* libcubeb enumerate device test/example.
+ * Prints out a list of devices enumerated. */
+#include "cubeb/cubeb.h"
+#include "gtest/gtest.h"
+#include <memory>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// #define ENABLE_NORMAL_LOG
+// #define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+static long
+data_cb_duplex(cubeb_stream * stream, void * user, const void * inputbuffer,
+ void * outputbuffer, long nframes)
+{
+ // noop, unused
+ return 0;
+}
+
+static void
+state_cb_duplex(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ // noop, unused
+}
+
+static void
+print_device_info(cubeb_device_info * info, FILE * f)
+{
+ char devfmts[64] = "";
+ const char *devtype, *devstate, *devdeffmt;
+
+ switch (info->type) {
+ case CUBEB_DEVICE_TYPE_INPUT:
+ devtype = "input";
+ break;
+ case CUBEB_DEVICE_TYPE_OUTPUT:
+ devtype = "output";
+ break;
+ case CUBEB_DEVICE_TYPE_UNKNOWN:
+ default:
+ devtype = "unknown?";
+ break;
+ };
+
+ switch (info->state) {
+ case CUBEB_DEVICE_STATE_DISABLED:
+ devstate = "disabled";
+ break;
+ case CUBEB_DEVICE_STATE_UNPLUGGED:
+ devstate = "unplugged";
+ break;
+ case CUBEB_DEVICE_STATE_ENABLED:
+ devstate = "enabled";
+ break;
+ default:
+ devstate = "unknown?";
+ break;
+ };
+
+ switch (info->default_format) {
+ case CUBEB_DEVICE_FMT_S16LE:
+ devdeffmt = "S16LE";
+ break;
+ case CUBEB_DEVICE_FMT_S16BE:
+ devdeffmt = "S16BE";
+ break;
+ case CUBEB_DEVICE_FMT_F32LE:
+ devdeffmt = "F32LE";
+ break;
+ case CUBEB_DEVICE_FMT_F32BE:
+ devdeffmt = "F32BE";
+ break;
+ default:
+ devdeffmt = "unknown?";
+ break;
+ };
+
+ if (info->format & CUBEB_DEVICE_FMT_S16LE)
+ strcat(devfmts, " S16LE");
+ if (info->format & CUBEB_DEVICE_FMT_S16BE)
+ strcat(devfmts, " S16BE");
+ if (info->format & CUBEB_DEVICE_FMT_F32LE)
+ strcat(devfmts, " F32LE");
+ if (info->format & CUBEB_DEVICE_FMT_F32BE)
+ strcat(devfmts, " F32BE");
+
+ fprintf(f,
+ "dev: \"%s\"%s\n"
+ "\tName: \"%s\"\n"
+ "\tGroup: \"%s\"\n"
+ "\tVendor: \"%s\"\n"
+ "\tType: %s\n"
+ "\tState: %s\n"
+ "\tCh: %u\n"
+ "\tFormat: %s (0x%x) (default: %s)\n"
+ "\tRate: %u - %u (default: %u)\n"
+ "\tLatency: lo %u frames, hi %u frames\n",
+ info->device_id, info->preferred ? " (PREFERRED)" : "",
+ info->friendly_name, info->group_id, info->vendor_name, devtype,
+ devstate, info->max_channels,
+ (devfmts[0] == '\0') ? devfmts : devfmts + 1,
+ (unsigned int)info->format, devdeffmt, info->min_rate, info->max_rate,
+ info->default_rate, info->latency_lo, info->latency_hi);
+}
+
+static void
+print_device_collection(cubeb_device_collection * collection, FILE * f)
+{
+ uint32_t i;
+
+ for (i = 0; i < collection->count; i++)
+ print_device_info(&collection->device[i], f);
+}
+
+TEST(cubeb, destroy_default_collection)
+{
+ int r;
+ cubeb * ctx = NULL;
+ cubeb_device_collection collection{nullptr, 0};
+
+ r = common_init(&ctx, "Cubeb audio test");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, cubeb_destroy);
+
+ ASSERT_EQ(collection.device, nullptr);
+ ASSERT_EQ(collection.count, (size_t)0);
+
+ r = cubeb_device_collection_destroy(ctx, &collection);
+ if (r != CUBEB_ERROR_NOT_SUPPORTED) {
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_EQ(collection.device, nullptr);
+ ASSERT_EQ(collection.count, (size_t)0);
+ }
+}
+
+TEST(cubeb, enumerate_devices)
+{
+ int r;
+ cubeb * ctx = NULL;
+ cubeb_device_collection collection;
+
+ r = common_init(&ctx, "Cubeb audio test");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, cubeb_destroy);
+
+ fprintf(stdout, "Enumerating input devices for backend %s\n",
+ cubeb_get_backend_id(ctx));
+
+ r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &collection);
+ if (r == CUBEB_ERROR_NOT_SUPPORTED) {
+ fprintf(stderr, "Device enumeration not supported"
+ " for this backend, skipping this test.\n");
+ return;
+ }
+ ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
+
+ fprintf(stdout, "Found %zu input devices\n", collection.count);
+ print_device_collection(&collection, stdout);
+ cubeb_device_collection_destroy(ctx, &collection);
+
+ fprintf(stdout, "Enumerating output devices for backend %s\n",
+ cubeb_get_backend_id(ctx));
+
+ r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
+ ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
+
+ fprintf(stdout, "Found %zu output devices\n", collection.count);
+ print_device_collection(&collection, stdout);
+ cubeb_device_collection_destroy(ctx, &collection);
+
+ uint32_t count_before_creating_duplex_stream;
+ r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
+ ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
+ count_before_creating_duplex_stream = collection.count;
+ cubeb_device_collection_destroy(ctx, &collection);
+
+ if (!can_run_audio_input_test(ctx)) {
+ return;
+ }
+ cubeb_stream * stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+
+ input_params.format = output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ input_params.rate = output_params.rate = 48000;
+ input_params.channels = output_params.channels = 1;
+ input_params.layout = output_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb duplex", NULL, &input_params, NULL,
+ &output_params, 1024, data_cb_duplex, state_cb_duplex,
+ nullptr);
+
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
+ ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
+ ASSERT_EQ(count_before_creating_duplex_stream, collection.count);
+ cubeb_device_collection_destroy(ctx, &collection);
+
+ cubeb_stream_destroy(stream);
+}
+
+TEST(cubeb, stream_get_current_device)
+{
+ cubeb * ctx = NULL;
+ int r = common_init(&ctx, "Cubeb audio test");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, cubeb_destroy);
+
+ fprintf(stdout, "Getting current devices for backend %s\n",
+ cubeb_get_backend_id(ctx));
+
+ if (!can_run_audio_input_test(ctx)) {
+ return;
+ }
+
+ cubeb_stream * stream = NULL;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+
+ input_params.format = output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ input_params.rate = output_params.rate = 48000;
+ input_params.channels = output_params.channels = 1;
+ input_params.layout = output_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb duplex", NULL, &input_params, NULL,
+ &output_params, 1024, data_cb_duplex, state_cb_duplex,
+ nullptr);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_device * device;
+ r = cubeb_stream_get_current_device(stream, &device);
+ if (r == CUBEB_ERROR_NOT_SUPPORTED) {
+ fprintf(stderr, "Getting current device is not supported"
+ " for this backend, skipping this test.\n");
+ return;
+ }
+ ASSERT_EQ(r, CUBEB_OK) << "Error getting current devices";
+
+ fprintf(stdout, "Current output device: %s\n", device->output_name);
+ fprintf(stdout, "Current input device: %s\n", device->input_name);
+
+ r = cubeb_stream_device_destroy(stream, device);
+ ASSERT_EQ(r, CUBEB_OK) << "Error destroying current devices";
+}
diff --git a/third_party/rust/cubeb-sys/libcubeb/test/test_duplex.cpp b/third_party/rust/cubeb-sys/libcubeb/test/test_duplex.cpp
new file mode 100644
index 0000000000..518f44f509
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/test/test_duplex.cpp
@@ -0,0 +1,376 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* libcubeb api/function test. Loops input back to output and check audio
+ * is flowing. */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include "cubeb/cubeb.h"
+#include <atomic>
+#include <math.h>
+#include <memory>
+#include <stdio.h>
+#include <stdlib.h>
+
+// #define ENABLE_NORMAL_LOG
+// #define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+#define SAMPLE_FREQUENCY 48000
+#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
+#define INPUT_CHANNELS 1
+#define INPUT_LAYOUT CUBEB_LAYOUT_MONO
+#define OUTPUT_CHANNELS 2
+#define OUTPUT_LAYOUT CUBEB_LAYOUT_STEREO
+
+struct user_state_duplex {
+ std::atomic<int> invalid_audio_value{0};
+};
+
+long
+data_cb_duplex(cubeb_stream * stream, void * user, const void * inputbuffer,
+ void * outputbuffer, long nframes)
+{
+ user_state_duplex * u = reinterpret_cast<user_state_duplex *>(user);
+ float * ib = (float *)inputbuffer;
+ float * ob = (float *)outputbuffer;
+
+ if (stream == NULL || inputbuffer == NULL || outputbuffer == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ // Loop back: upmix the single input channel to the two output channels,
+ // checking if there is noise in the process.
+ long output_index = 0;
+ for (long i = 0; i < nframes; i++) {
+ if (ib[i] <= -1.0 || ib[i] >= 1.0) {
+ u->invalid_audio_value = 1;
+ }
+ ob[output_index] = ob[output_index + 1] = ib[i];
+ output_index += 2;
+ }
+
+ return nframes;
+}
+
+void
+state_cb_duplex(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ if (stream == NULL)
+ return;
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n");
+ break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n");
+ break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n");
+ break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+
+ return;
+}
+
+TEST(cubeb, duplex)
+{
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ user_state_duplex stream_state;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb duplex example");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, cubeb_destroy);
+
+ /* This test needs an available input device, skip it if this host does not
+ * have one. */
+ if (!can_run_audio_input_test(ctx)) {
+ return;
+ }
+
+ /* typical user-case: mono input, stereo output, low latency. */
+ input_params.format = STREAM_FORMAT;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = INPUT_CHANNELS;
+ input_params.layout = INPUT_LAYOUT;
+ input_params.prefs = CUBEB_STREAM_PREF_NONE;
+ output_params.format = STREAM_FORMAT;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = OUTPUT_CHANNELS;
+ output_params.layout = OUTPUT_LAYOUT;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb duplex", NULL, &input_params, NULL,
+ &output_params, latency_frames, data_cb_duplex,
+ state_cb_duplex, &stream_state);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(500);
+ cubeb_stream_stop(stream);
+
+ ASSERT_FALSE(stream_state.invalid_audio_value.load());
+}
+
+void
+device_collection_changed_callback(cubeb * context, void * user)
+{
+ fprintf(stderr, "collection changed callback\n");
+ ASSERT_TRUE(false) << "Error: device collection changed callback"
+ " called when opening a stream";
+}
+
+void
+duplex_collection_change_impl(cubeb * ctx)
+{
+ cubeb_stream * stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = cubeb_register_device_collection_changed(
+ ctx, static_cast<cubeb_device_type>(CUBEB_DEVICE_TYPE_INPUT),
+ device_collection_changed_callback, nullptr);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ /* typical user-case: mono input, stereo output, low latency. */
+ input_params.format = STREAM_FORMAT;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = INPUT_CHANNELS;
+ input_params.layout = INPUT_LAYOUT;
+ input_params.prefs = CUBEB_STREAM_PREF_NONE;
+ output_params.format = STREAM_FORMAT;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = OUTPUT_CHANNELS;
+ output_params.layout = OUTPUT_LAYOUT;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb duplex", NULL, &input_params, NULL,
+ &output_params, latency_frames, data_cb_duplex,
+ state_cb_duplex, nullptr);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+ cubeb_stream_destroy(stream);
+}
+
+TEST(cubeb, duplex_collection_change)
+{
+ cubeb * ctx;
+ int r;
+
+ r = common_init(&ctx, "Cubeb duplex example with collection change");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, cubeb_destroy);
+
+ /* This test needs an available input device, skip it if this host does not
+ * have one. */
+ if (!can_run_audio_input_test(ctx)) {
+ return;
+ }
+
+ duplex_collection_change_impl(ctx);
+ r = cubeb_register_device_collection_changed(
+ ctx, static_cast<cubeb_device_type>(CUBEB_DEVICE_TYPE_INPUT), nullptr,
+ nullptr);
+ ASSERT_EQ(r, CUBEB_OK);
+}
+
+TEST(cubeb, duplex_collection_change_no_unregister)
+{
+ cubeb * ctx;
+ int r;
+
+ r = common_init(&ctx, "Cubeb duplex example with collection change");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ /* This test needs an available input device, skip it if this host does not
+ * have one. */
+ if (!can_run_audio_input_test(ctx)) {
+ cubeb_destroy(ctx);
+ return;
+ }
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, [](cubeb * p) noexcept { EXPECT_DEATH(cubeb_destroy(p), ""); });
+
+ duplex_collection_change_impl(ctx);
+}
+
+long
+data_cb_input(cubeb_stream * stream, void * user, const void * inputbuffer,
+ void * outputbuffer, long nframes)
+{
+ if (stream == NULL || inputbuffer == NULL || outputbuffer != NULL) {
+ return CUBEB_ERROR;
+ }
+
+ return nframes;
+}
+
+void
+state_cb_input(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ if (stream == NULL)
+ return;
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n");
+ break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n");
+ break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n");
+ break;
+ case CUBEB_STATE_ERROR:
+ fprintf(stderr, "stream runs into error state\n");
+ break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+
+ return;
+}
+
+std::vector<cubeb_devid>
+get_devices(cubeb * ctx, cubeb_device_type type)
+{
+ std::vector<cubeb_devid> devices;
+
+ cubeb_device_collection collection;
+ int r = cubeb_enumerate_devices(ctx, type, &collection);
+
+ if (r != CUBEB_OK) {
+ fprintf(stderr, "Failed to enumerate devices\n");
+ return devices;
+ }
+
+ for (uint32_t i = 0; i < collection.count; i++) {
+ if (collection.device[i].state == CUBEB_DEVICE_STATE_ENABLED) {
+ devices.emplace_back(collection.device[i].devid);
+ }
+ }
+
+ cubeb_device_collection_destroy(ctx, &collection);
+
+ return devices;
+}
+
+TEST(cubeb, one_duplex_one_input)
+{
+ cubeb * ctx;
+ cubeb_stream * duplex_stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ user_state_duplex duplex_stream_state;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb duplex example");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, cubeb_destroy);
+
+ /* This test needs at least two available input devices. */
+ std::vector<cubeb_devid> input_devices =
+ get_devices(ctx, CUBEB_DEVICE_TYPE_INPUT);
+ if (input_devices.size() < 2) {
+ return;
+ }
+
+ /* This test needs at least one available output device. */
+ std::vector<cubeb_devid> output_devices =
+ get_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT);
+ if (output_devices.size() < 1) {
+ return;
+ }
+
+ cubeb_devid duplex_input = input_devices.front();
+ cubeb_devid duplex_output = nullptr; // default device
+ cubeb_devid input_only = input_devices.back();
+
+ /* typical use-case: mono voice input, stereo output, low latency. */
+ input_params.format = STREAM_FORMAT;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = INPUT_CHANNELS;
+ input_params.layout = CUBEB_LAYOUT_UNDEFINED;
+ input_params.prefs = CUBEB_STREAM_PREF_VOICE;
+
+ output_params.format = STREAM_FORMAT;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = OUTPUT_CHANNELS;
+ output_params.layout = OUTPUT_LAYOUT;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ r = cubeb_stream_init(ctx, &duplex_stream, "Cubeb duplex", duplex_input,
+ &input_params, duplex_output, &output_params,
+ latency_frames, data_cb_duplex, state_cb_duplex,
+ &duplex_stream_state);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing duplex cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(duplex_stream, cubeb_stream_destroy);
+
+ r = cubeb_stream_start(duplex_stream);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not start duplex stream";
+ delay(500);
+
+ cubeb_stream * input_stream;
+ r = cubeb_stream_init(ctx, &input_stream, "Cubeb input", input_only,
+ &input_params, NULL, NULL, latency_frames,
+ data_cb_input, state_cb_input, nullptr);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing input-only cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
+
+ r = cubeb_stream_start(input_stream);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not start input stream";
+ delay(500);
+
+ r = cubeb_stream_stop(duplex_stream);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not stop duplex stream";
+
+ r = cubeb_stream_stop(input_stream);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not stop input stream";
+
+ ASSERT_FALSE(duplex_stream_state.invalid_audio_value.load());
+}
+
+#undef SAMPLE_FREQUENCY
+#undef STREAM_FORMAT
+#undef INPUT_CHANNELS
+#undef INPUT_LAYOUT
+#undef OUTPUT_CHANNELS
+#undef OUTPUT_LAYOUT
diff --git a/third_party/rust/cubeb-sys/libcubeb/test/test_latency.cpp b/third_party/rust/cubeb-sys/libcubeb/test/test_latency.cpp
new file mode 100644
index 0000000000..97f24ea498
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/test/test_latency.cpp
@@ -0,0 +1,43 @@
+#include "cubeb/cubeb.h"
+#include "gtest/gtest.h"
+#include <memory>
+#include <stdlib.h>
+// #define ENABLE_NORMAL_LOG
+// #define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+TEST(cubeb, latency)
+{
+ cubeb * ctx = NULL;
+ int r;
+ uint32_t max_channels;
+ uint32_t preferred_rate;
+ uint32_t latency_frames;
+
+ r = common_init(&ctx, "Cubeb audio test");
+ ASSERT_EQ(r, CUBEB_OK);
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, cubeb_destroy);
+
+ r = cubeb_get_max_channel_count(ctx, &max_channels);
+ ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
+ if (r == CUBEB_OK) {
+ ASSERT_GT(max_channels, 0u);
+ }
+
+ r = cubeb_get_preferred_sample_rate(ctx, &preferred_rate);
+ ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
+ if (r == CUBEB_OK) {
+ ASSERT_GT(preferred_rate, 0u);
+ }
+
+ cubeb_stream_params params = {CUBEB_SAMPLE_FLOAT32NE, preferred_rate,
+ max_channels, CUBEB_LAYOUT_UNDEFINED,
+ CUBEB_STREAM_PREF_NONE};
+ r = cubeb_get_min_latency(ctx, &params, &latency_frames);
+ ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
+ if (r == CUBEB_OK) {
+ ASSERT_GT(latency_frames, 0u);
+ }
+}
diff --git a/third_party/rust/cubeb-sys/libcubeb/test/test_logging.cpp b/third_party/rust/cubeb-sys/libcubeb/test/test_logging.cpp
new file mode 100644
index 0000000000..54ff718152
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/test/test_logging.cpp
@@ -0,0 +1,196 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* cubeb_logging test */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include "cubeb/cubeb.h"
+#include "cubeb_log.h"
+#include <atomic>
+#include <math.h>
+#include <memory>
+#include <stdio.h>
+#include <stdlib.h>
+#include <thread>
+
+#include "common.h"
+
+#define PRINT_LOGS_TO_STDERR 0
+
+std::atomic<uint32_t> log_statements_received = {0};
+std::atomic<uint32_t> data_callback_call_count = {0};
+
+static void
+test_logging_callback(char const * fmt, ...)
+{
+ log_statements_received++;
+#if PRINT_LOGS_TO_STDERR == 1
+ char buf[1024];
+ va_list argslist;
+ va_start(argslist, fmt);
+ vsnprintf(buf, 1024, fmt, argslist);
+ fprintf(stderr, "%s\n", buf);
+ va_end(argslist);
+#endif // PRINT_LOGS_TO_STDERR
+}
+
+static long
+data_cb_load(cubeb_stream * stream, void * user, const void * inputbuffer,
+ void * outputbuffer, long nframes)
+{
+ data_callback_call_count++;
+ return nframes;
+}
+
+static void
+state_cb(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ if (stream == NULL)
+ return;
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n");
+ break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n");
+ break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n");
+ break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+
+ return;
+}
+
+// Waits for at least one audio callback to have occured.
+void
+wait_for_audio_callback()
+{
+ uint32_t audio_callback_index =
+ data_callback_call_count.load(std::memory_order_acquire);
+ while (audio_callback_index ==
+ data_callback_call_count.load(std::memory_order_acquire)) {
+ delay(100);
+ }
+}
+
+TEST(cubeb, logging)
+{
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ cubeb_set_log_callback(CUBEB_LOG_NORMAL, test_logging_callback);
+
+ r = common_init(&ctx, "Cubeb logging test");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, cubeb_destroy);
+
+ output_params.format = CUBEB_SAMPLE_FLOAT32LE;
+ output_params.rate = 48000;
+ output_params.channels = 2;
+ output_params.layout = CUBEB_LAYOUT_STEREO;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ if (r != CUBEB_OK) {
+ // not fatal
+ latency_frames = 1024;
+ }
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb logging", NULL, NULL, NULL,
+ &output_params, latency_frames, data_cb_load, state_cb,
+ NULL);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ ASSERT_NE(log_statements_received.load(std::memory_order_acquire), 0u);
+
+ cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr);
+ log_statements_received.store(0, std::memory_order_release);
+
+ // This is synchronous and we'll receive log messages on all backends that we
+ // test
+ cubeb_stream_start(stream);
+
+ ASSERT_EQ(log_statements_received.load(std::memory_order_acquire), 0u);
+
+ cubeb_set_log_callback(CUBEB_LOG_VERBOSE, test_logging_callback);
+
+ wait_for_audio_callback();
+
+ ASSERT_NE(log_statements_received.load(std::memory_order_acquire), 0u);
+
+ bool log_callback_set = true;
+ uint32_t iterations = 100;
+ while (iterations--) {
+ wait_for_audio_callback();
+
+ if (!log_callback_set) {
+ ASSERT_EQ(log_statements_received.load(std::memory_order_acquire), 0u);
+ // Set a logging callback, start logging
+ cubeb_set_log_callback(CUBEB_LOG_VERBOSE, test_logging_callback);
+ log_callback_set = true;
+ } else {
+ // Disable the logging callback, stop logging.
+ ASSERT_NE(log_statements_received.load(std::memory_order_acquire), 0u);
+ cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr);
+ log_statements_received.store(0, std::memory_order_release);
+ // Disabling logging should flush any log message -- wait a bit and check
+ // that this is true.
+ ASSERT_EQ(log_statements_received.load(std::memory_order_acquire), 0u);
+ log_callback_set = false;
+ }
+ }
+
+ cubeb_stream_stop(stream);
+}
+
+TEST(cubeb, logging_stress)
+{
+ cubeb_set_log_callback(CUBEB_LOG_NORMAL, test_logging_callback);
+
+ std::atomic<bool> thread_done = {false};
+
+ auto t = std::thread([&thread_done]() {
+ uint32_t count = 0;
+ do {
+ while (rand() % 10) {
+ ALOG("Log message #%d!", count++);
+ }
+ } while (count < 1e4);
+ thread_done.store(true);
+ });
+
+ bool enabled = true;
+ while (!thread_done.load()) {
+ if (enabled) {
+ cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr);
+ enabled = false;
+ } else {
+ cubeb_set_log_callback(CUBEB_LOG_NORMAL, test_logging_callback);
+ enabled = true;
+ }
+ }
+
+ cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr);
+
+ t.join();
+
+ ASSERT_TRUE(true);
+}
diff --git a/third_party/rust/cubeb-sys/libcubeb/test/test_loopback.cpp b/third_party/rust/cubeb-sys/libcubeb/test/test_loopback.cpp
new file mode 100644
index 0000000000..7410bedb8b
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/test/test_loopback.cpp
@@ -0,0 +1,679 @@
+/*
+ * Copyright © 2017 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* libcubeb api/function test. Requests a loopback device and checks that
+ output is being looped back to input. NOTE: Usage of output devices while
+ performing this test will cause flakey results! */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include "cubeb/cubeb.h"
+#include <algorithm>
+#include <math.h>
+#include <memory>
+#include <mutex>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
+// #define ENABLE_NORMAL_LOG
+// #define ENABLE_VERBOSE_LOG
+#include "common.h"
+const uint32_t SAMPLE_FREQUENCY = 48000;
+const uint32_t TONE_FREQUENCY = 440;
+const double OUTPUT_AMPLITUDE = 0.25;
+const int32_t NUM_FRAMES_TO_OUTPUT =
+ SAMPLE_FREQUENCY / 20; /* play ~50ms of samples */
+
+template <typename T>
+T
+ConvertSampleToOutput(double input);
+template <>
+float
+ConvertSampleToOutput(double input)
+{
+ return float(input);
+}
+template <>
+short
+ConvertSampleToOutput(double input)
+{
+ return short(input * 32767.0f);
+}
+
+template <typename T>
+double
+ConvertSampleFromOutput(T sample);
+template <>
+double
+ConvertSampleFromOutput(float sample)
+{
+ return double(sample);
+}
+template <>
+double
+ConvertSampleFromOutput(short sample)
+{
+ return double(sample / 32767.0);
+}
+
+/* Simple cross correlation to help find phase shift. Not a performant impl */
+std::vector<double>
+cross_correlate(std::vector<double> & f, std::vector<double> & g,
+ size_t signal_length)
+{
+ /* the length we sweep our window through to find the cross correlation */
+ size_t sweep_length = f.size() - signal_length + 1;
+ std::vector<double> correlation;
+ correlation.reserve(sweep_length);
+ for (size_t i = 0; i < sweep_length; i++) {
+ double accumulator = 0.0;
+ for (size_t j = 0; j < signal_length; j++) {
+ accumulator += f.at(j) * g.at(i + j);
+ }
+ correlation.push_back(accumulator);
+ }
+ return correlation;
+}
+
+/* best effort discovery of phase shift between output and (looped) input*/
+size_t
+find_phase(std::vector<double> & output_frames,
+ std::vector<double> & input_frames, size_t signal_length)
+{
+ std::vector<double> correlation =
+ cross_correlate(output_frames, input_frames, signal_length);
+ size_t phase = 0;
+ double max_correlation = correlation.at(0);
+ for (size_t i = 1; i < correlation.size(); i++) {
+ if (correlation.at(i) > max_correlation) {
+ max_correlation = correlation.at(i);
+ phase = i;
+ }
+ }
+ return phase;
+}
+
+std::vector<double>
+normalize_frames(std::vector<double> & frames)
+{
+ double max = abs(
+ *std::max_element(frames.begin(), frames.end(),
+ [](double a, double b) { return abs(a) < abs(b); }));
+ std::vector<double> normalized_frames;
+ normalized_frames.reserve(frames.size());
+ for (const double frame : frames) {
+ normalized_frames.push_back(frame / max);
+ }
+ return normalized_frames;
+}
+
+/* heuristic comparison of aligned output and input signals, gets flaky if
+ * TONE_FREQUENCY is too high */
+void
+compare_signals(std::vector<double> & output_frames,
+ std::vector<double> & input_frames)
+{
+ ASSERT_EQ(output_frames.size(), input_frames.size())
+ << "#Output frames != #input frames";
+ size_t num_frames = output_frames.size();
+ std::vector<double> normalized_output_frames =
+ normalize_frames(output_frames);
+ std::vector<double> normalized_input_frames = normalize_frames(input_frames);
+
+ /* calculate mean absolute errors */
+ /* mean absolute errors between output and input */
+ double io_mas = 0.0;
+ /* mean absolute errors between output and silence */
+ double output_silence_mas = 0.0;
+ /* mean absolute errors between input and silence */
+ double input_silence_mas = 0.0;
+ for (size_t i = 0; i < num_frames; i++) {
+ io_mas +=
+ abs(normalized_output_frames.at(i) - normalized_input_frames.at(i));
+ output_silence_mas += abs(normalized_output_frames.at(i));
+ input_silence_mas += abs(normalized_input_frames.at(i));
+ }
+ io_mas /= num_frames;
+ output_silence_mas /= num_frames;
+ input_silence_mas /= num_frames;
+
+ ASSERT_LT(io_mas, output_silence_mas)
+ << "Error between output and input should be less than output and "
+ "silence!";
+ ASSERT_LT(io_mas, input_silence_mas)
+ << "Error between output and input should be less than output and "
+ "silence!";
+
+ /* make sure extrema are in (roughly) correct location */
+ /* number of maxima + minama expected in the frames*/
+ const long NUM_EXTREMA =
+ 2 * TONE_FREQUENCY * NUM_FRAMES_TO_OUTPUT / SAMPLE_FREQUENCY;
+ /* expected index of first maxima */
+ const long FIRST_MAXIMUM_INDEX = SAMPLE_FREQUENCY / TONE_FREQUENCY / 4;
+ /* Threshold we expect all maxima and minima to be above or below. Ideally
+ the extrema would be 1 or -1, but particularly at the start of loopback
+ the values seen can be significantly lower. */
+ const double THRESHOLD = 0.5;
+
+ for (size_t i = 0; i < NUM_EXTREMA; i++) {
+ bool is_maximum = i % 2 == 0;
+ /* expected offset to current extreme: i * stide between extrema */
+ size_t offset = i * SAMPLE_FREQUENCY / TONE_FREQUENCY / 2;
+ if (is_maximum) {
+ ASSERT_GT(normalized_output_frames.at(FIRST_MAXIMUM_INDEX + offset),
+ THRESHOLD)
+ << "Output frames have unexpected missing maximum!";
+ ASSERT_GT(normalized_input_frames.at(FIRST_MAXIMUM_INDEX + offset),
+ THRESHOLD)
+ << "Input frames have unexpected missing maximum!";
+ } else {
+ ASSERT_LT(normalized_output_frames.at(FIRST_MAXIMUM_INDEX + offset),
+ -THRESHOLD)
+ << "Output frames have unexpected missing minimum!";
+ ASSERT_LT(normalized_input_frames.at(FIRST_MAXIMUM_INDEX + offset),
+ -THRESHOLD)
+ << "Input frames have unexpected missing minimum!";
+ }
+ }
+}
+
+struct user_state_loopback {
+ std::mutex user_state_mutex;
+ long position = 0;
+ /* track output */
+ std::vector<double> output_frames;
+ /* track input */
+ std::vector<double> input_frames;
+};
+
+template <typename T>
+long
+data_cb_loop_duplex(cubeb_stream * stream, void * user,
+ const void * inputbuffer, void * outputbuffer, long nframes)
+{
+ struct user_state_loopback * u = (struct user_state_loopback *)user;
+ T * ib = (T *)inputbuffer;
+ T * ob = (T *)outputbuffer;
+
+ if (stream == NULL || inputbuffer == NULL || outputbuffer == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ std::lock_guard<std::mutex> lock(u->user_state_mutex);
+ /* generate our test tone on the fly */
+ for (int i = 0; i < nframes; i++) {
+ double tone = 0.0;
+ if (u->position + i < NUM_FRAMES_TO_OUTPUT) {
+ /* generate sine wave */
+ tone =
+ sin(2 * M_PI * (i + u->position) * TONE_FREQUENCY / SAMPLE_FREQUENCY);
+ tone *= OUTPUT_AMPLITUDE;
+ }
+ ob[i] = ConvertSampleToOutput<T>(tone);
+ u->output_frames.push_back(tone);
+ /* store any looped back output, may be silence */
+ u->input_frames.push_back(ConvertSampleFromOutput(ib[i]));
+ }
+
+ u->position += nframes;
+
+ return nframes;
+}
+
+template <typename T>
+long
+data_cb_loop_input_only(cubeb_stream * stream, void * user,
+ const void * inputbuffer, void * outputbuffer,
+ long nframes)
+{
+ struct user_state_loopback * u = (struct user_state_loopback *)user;
+ T * ib = (T *)inputbuffer;
+
+ if (outputbuffer != NULL) {
+ // Can't assert as it needs to return, so expect to fail instead
+ EXPECT_EQ(outputbuffer, (void *)NULL)
+ << "outputbuffer should be null in input only callback";
+ return CUBEB_ERROR;
+ }
+
+ if (stream == NULL || inputbuffer == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ std::lock_guard<std::mutex> lock(u->user_state_mutex);
+ for (int i = 0; i < nframes; i++) {
+ u->input_frames.push_back(ConvertSampleFromOutput(ib[i]));
+ }
+
+ return nframes;
+}
+
+template <typename T>
+long
+data_cb_playback(cubeb_stream * stream, void * user, const void * inputbuffer,
+ void * outputbuffer, long nframes)
+{
+ struct user_state_loopback * u = (struct user_state_loopback *)user;
+ T * ob = (T *)outputbuffer;
+
+ if (stream == NULL || outputbuffer == NULL) {
+ return CUBEB_ERROR;
+ }
+
+ std::lock_guard<std::mutex> lock(u->user_state_mutex);
+ /* generate our test tone on the fly */
+ for (int i = 0; i < nframes; i++) {
+ double tone = 0.0;
+ if (u->position + i < NUM_FRAMES_TO_OUTPUT) {
+ /* generate sine wave */
+ tone =
+ sin(2 * M_PI * (i + u->position) * TONE_FREQUENCY / SAMPLE_FREQUENCY);
+ tone *= OUTPUT_AMPLITUDE;
+ }
+ ob[i] = ConvertSampleToOutput<T>(tone);
+ u->output_frames.push_back(tone);
+ }
+
+ u->position += nframes;
+
+ return nframes;
+}
+
+void
+state_cb_loop(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ if (stream == NULL)
+ return;
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n");
+ break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n");
+ break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n");
+ break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+
+ return;
+}
+
+void
+run_loopback_duplex_test(bool is_float)
+{
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb loopback example: duplex stream");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, cubeb_destroy);
+
+ /* This test needs an available input device, skip it if this host does not
+ * have one. */
+ if (!can_run_audio_input_test(ctx)) {
+ return;
+ }
+
+ input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+ output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = 1;
+ output_params.layout = CUBEB_LAYOUT_MONO;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ /* setup a duplex stream with loopback */
+ r = cubeb_stream_init(ctx, &stream, "Cubeb loopback", NULL, &input_params,
+ NULL, &output_params, latency_frames,
+ is_float ? data_cb_loop_duplex<float>
+ : data_cb_loop_duplex<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(300);
+ cubeb_stream_stop(stream);
+
+ /* access after stop should not happen, but lock just in case and to appease
+ * sanitization tools */
+ std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+ std::vector<double> & output_frames = user_data->output_frames;
+ std::vector<double> & input_frames = user_data->input_frames;
+ ASSERT_EQ(output_frames.size(), input_frames.size())
+ << "#Output frames != #input frames";
+
+ size_t phase = find_phase(user_data->output_frames, user_data->input_frames,
+ NUM_FRAMES_TO_OUTPUT);
+
+ /* extract vectors of just the relevant signal from output and input */
+ auto output_frames_signal_start = output_frames.begin();
+ auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_output_frames(output_frames_signal_start,
+ output_frames_signal_end);
+ auto input_frames_signal_start = input_frames.begin() + phase;
+ auto input_frames_signal_end =
+ input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_input_frames(input_frames_signal_start,
+ input_frames_signal_end);
+
+ compare_signals(trimmed_output_frames, trimmed_input_frames);
+}
+
+TEST(cubeb, loopback_duplex)
+{
+ run_loopback_duplex_test(true);
+ run_loopback_duplex_test(false);
+}
+
+void
+run_loopback_separate_streams_test(bool is_float)
+{
+ cubeb * ctx;
+ cubeb_stream * input_stream;
+ cubeb_stream * output_stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb loopback example: separate streams");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, cubeb_destroy);
+
+ if (!can_run_audio_input_test(ctx)) {
+ return;
+ }
+
+ input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+ output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = 1;
+ output_params.layout = CUBEB_LAYOUT_MONO;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ /* setup an input stream with loopback */
+ r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only", NULL,
+ &input_params, NULL, NULL, latency_frames,
+ is_float ? data_cb_loop_input_only<float>
+ : data_cb_loop_input_only<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
+
+ /* setup an output stream */
+ r = cubeb_stream_init(ctx, &output_stream, "Cubeb loopback output only", NULL,
+ NULL, NULL, &output_params, latency_frames,
+ is_float ? data_cb_playback<float>
+ : data_cb_playback<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_output_stream_at_exit(output_stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(input_stream);
+ cubeb_stream_start(output_stream);
+ delay(300);
+ cubeb_stream_stop(output_stream);
+ cubeb_stream_stop(input_stream);
+
+ /* access after stop should not happen, but lock just in case and to appease
+ * sanitization tools */
+ std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+ std::vector<double> & output_frames = user_data->output_frames;
+ std::vector<double> & input_frames = user_data->input_frames;
+ ASSERT_LE(output_frames.size(), input_frames.size())
+ << "#Output frames should be less or equal to #input frames";
+
+ size_t phase = find_phase(user_data->output_frames, user_data->input_frames,
+ NUM_FRAMES_TO_OUTPUT);
+
+ /* extract vectors of just the relevant signal from output and input */
+ auto output_frames_signal_start = output_frames.begin();
+ auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_output_frames(output_frames_signal_start,
+ output_frames_signal_end);
+ auto input_frames_signal_start = input_frames.begin() + phase;
+ auto input_frames_signal_end =
+ input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_input_frames(input_frames_signal_start,
+ input_frames_signal_end);
+
+ compare_signals(trimmed_output_frames, trimmed_input_frames);
+}
+
+TEST(cubeb, loopback_separate_streams)
+{
+ run_loopback_separate_streams_test(true);
+ run_loopback_separate_streams_test(false);
+}
+
+void
+run_loopback_silence_test(bool is_float)
+{
+ cubeb * ctx;
+ cubeb_stream * input_stream;
+ cubeb_stream_params input_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb loopback example: record silence");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, cubeb_destroy);
+
+ if (!can_run_audio_input_test(ctx)) {
+ return;
+ }
+
+ input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+
+ std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+ r = cubeb_get_min_latency(ctx, &input_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ /* setup an input stream with loopback */
+ r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only", NULL,
+ &input_params, NULL, NULL, latency_frames,
+ is_float ? data_cb_loop_input_only<float>
+ : data_cb_loop_input_only<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(input_stream);
+ delay(300);
+ cubeb_stream_stop(input_stream);
+
+ /* access after stop should not happen, but lock just in case and to appease
+ * sanitization tools */
+ std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+ std::vector<double> & input_frames = user_data->input_frames;
+
+ /* expect to have at least ~50ms of frames */
+ ASSERT_GE(input_frames.size(), SAMPLE_FREQUENCY / 20);
+ double EPISILON = 0.0001;
+ /* frames should be 0.0, but use epsilon to avoid possible issues with impls
+ that may use ~0.0 silence values. */
+ for (double frame : input_frames) {
+ ASSERT_LT(abs(frame), EPISILON);
+ }
+}
+
+TEST(cubeb, loopback_silence)
+{
+ run_loopback_silence_test(true);
+ run_loopback_silence_test(false);
+}
+
+void
+run_loopback_device_selection_test(bool is_float)
+{
+ cubeb * ctx;
+ cubeb_device_collection collection;
+ cubeb_stream * input_stream;
+ cubeb_stream * output_stream;
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx,
+ "Cubeb loopback example: device selection, separate streams");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, cubeb_destroy);
+
+ if (!can_run_audio_input_test(ctx)) {
+ return;
+ }
+
+ r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection);
+ if (r == CUBEB_ERROR_NOT_SUPPORTED) {
+ fprintf(stderr, "Device enumeration not supported"
+ " for this backend, skipping this test.\n");
+ return;
+ }
+
+ ASSERT_EQ(r, CUBEB_OK) << "Error enumerating devices " << r;
+ /* get first preferred output device id */
+ std::string device_id;
+ for (size_t i = 0; i < collection.count; i++) {
+ if (collection.device[i].preferred) {
+ device_id = collection.device[i].device_id;
+ break;
+ }
+ }
+ cubeb_device_collection_destroy(ctx, &collection);
+ if (device_id.empty()) {
+ fprintf(stderr, "Could not find preferred device, aborting test.\n");
+ return;
+ }
+
+ input_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ input_params.rate = SAMPLE_FREQUENCY;
+ input_params.channels = 1;
+ input_params.layout = CUBEB_LAYOUT_MONO;
+ input_params.prefs = CUBEB_STREAM_PREF_LOOPBACK;
+ output_params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16LE;
+ output_params.rate = SAMPLE_FREQUENCY;
+ output_params.channels = 1;
+ output_params.layout = CUBEB_LAYOUT_MONO;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ std::unique_ptr<user_state_loopback> user_data(new user_state_loopback());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
+
+ /* setup an input stream with loopback */
+ r = cubeb_stream_init(ctx, &input_stream, "Cubeb loopback input only",
+ device_id.c_str(), &input_params, NULL, NULL,
+ latency_frames,
+ is_float ? data_cb_loop_input_only<float>
+ : data_cb_loop_input_only<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
+
+ /* setup an output stream */
+ r = cubeb_stream_init(ctx, &output_stream, "Cubeb loopback output only", NULL,
+ NULL, device_id.c_str(), &output_params, latency_frames,
+ is_float ? data_cb_playback<float>
+ : data_cb_playback<short>,
+ state_cb_loop, user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_output_stream_at_exit(output_stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(input_stream);
+ cubeb_stream_start(output_stream);
+ delay(300);
+ cubeb_stream_stop(output_stream);
+ cubeb_stream_stop(input_stream);
+
+ /* access after stop should not happen, but lock just in case and to appease
+ * sanitization tools */
+ std::lock_guard<std::mutex> lock(user_data->user_state_mutex);
+ std::vector<double> & output_frames = user_data->output_frames;
+ std::vector<double> & input_frames = user_data->input_frames;
+ ASSERT_LE(output_frames.size(), input_frames.size())
+ << "#Output frames should be less or equal to #input frames";
+
+ size_t phase = find_phase(user_data->output_frames, user_data->input_frames,
+ NUM_FRAMES_TO_OUTPUT);
+
+ /* extract vectors of just the relevant signal from output and input */
+ auto output_frames_signal_start = output_frames.begin();
+ auto output_frames_signal_end = output_frames.begin() + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_output_frames(output_frames_signal_start,
+ output_frames_signal_end);
+ auto input_frames_signal_start = input_frames.begin() + phase;
+ auto input_frames_signal_end =
+ input_frames.begin() + phase + NUM_FRAMES_TO_OUTPUT;
+ std::vector<double> trimmed_input_frames(input_frames_signal_start,
+ input_frames_signal_end);
+
+ compare_signals(trimmed_output_frames, trimmed_input_frames);
+}
+
+TEST(cubeb, loopback_device_selection)
+{
+ run_loopback_device_selection_test(true);
+ run_loopback_device_selection_test(false);
+}
diff --git a/third_party/rust/cubeb-sys/libcubeb/test/test_overload_callback.cpp b/third_party/rust/cubeb-sys/libcubeb/test/test_overload_callback.cpp
new file mode 100644
index 0000000000..14c1ec3e65
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/test/test_overload_callback.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright © 2017 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include "cubeb/cubeb.h"
+#include <atomic>
+#include <math.h>
+#include <memory>
+#include <stdio.h>
+#include <stdlib.h>
+// #define ENABLE_NORMAL_LOG
+// #define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+#define SAMPLE_FREQUENCY 48000
+#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
+
+std::atomic<bool> load_callback{false};
+
+static long
+data_cb(cubeb_stream * stream, void * user, const void * inputbuffer,
+ void * outputbuffer, long nframes)
+{
+ if (load_callback) {
+ fprintf(stderr, "Sleeping...\n");
+ delay(100000);
+ fprintf(stderr, "Sleeping done\n");
+ }
+ return nframes;
+}
+
+static void
+state_cb(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ ASSERT_TRUE(!!stream);
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n");
+ break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n");
+ break;
+ case CUBEB_STATE_DRAINED:
+ FAIL() << "this test is not supposed to drain";
+ break;
+ case CUBEB_STATE_ERROR:
+ fprintf(stderr, "stream error\n");
+ break;
+ default:
+ FAIL() << "this test is not supposed to have a weird state";
+ break;
+ }
+}
+
+TEST(cubeb, overload_callback)
+{
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params output_params;
+ int r;
+ uint32_t latency_frames = 0;
+
+ r = common_init(&ctx, "Cubeb callback overload");
+ ASSERT_EQ(r, CUBEB_OK);
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, cubeb_destroy);
+
+ // This test is specifically designed to test a behaviour of the WASAPI
+ // backend in a specific scenario.
+ if (strcmp(cubeb_get_backend_id(ctx), "wasapi") != 0) {
+ return;
+ }
+
+ output_params.format = STREAM_FORMAT;
+ output_params.rate = 48000;
+ output_params.channels = 2;
+ output_params.layout = CUBEB_LAYOUT_STEREO;
+ output_params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb", NULL, NULL, NULL, &output_params,
+ latency_frames, data_cb, state_cb, NULL);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(500);
+ // This causes the callback to sleep for a large number of seconds.
+ load_callback = true;
+ delay(500);
+ cubeb_stream_stop(stream);
+}
+
+#undef SAMPLE_FREQUENCY
+#undef STREAM_FORMAT
diff --git a/third_party/rust/cubeb-sys/libcubeb/test/test_record.cpp b/third_party/rust/cubeb-sys/libcubeb/test/test_record.cpp
new file mode 100644
index 0000000000..5b46b21020
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/test/test_record.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* libcubeb api/function test. Record the mic and check there is sound. */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include "cubeb/cubeb.h"
+#include <atomic>
+#include <math.h>
+#include <memory>
+#include <stdio.h>
+#include <stdlib.h>
+
+// #define ENABLE_NORMAL_LOG
+// #define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+#define SAMPLE_FREQUENCY 48000
+#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
+
+struct user_state_record {
+ std::atomic<int> invalid_audio_value{0};
+};
+
+long
+data_cb_record(cubeb_stream * stream, void * user, const void * inputbuffer,
+ void * outputbuffer, long nframes)
+{
+ user_state_record * u = reinterpret_cast<user_state_record *>(user);
+ float * b = (float *)inputbuffer;
+
+ if (stream == NULL || inputbuffer == NULL || outputbuffer != NULL) {
+ return CUBEB_ERROR;
+ }
+
+ for (long i = 0; i < nframes; i++) {
+ if (b[i] <= -1.0 || b[i] >= 1.0) {
+ u->invalid_audio_value = 1;
+ break;
+ }
+ }
+
+ return nframes;
+}
+
+void
+state_cb_record(cubeb_stream * stream, void * /*user*/, cubeb_state state)
+{
+ if (stream == NULL)
+ return;
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n");
+ break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n");
+ break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n");
+ break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+
+ return;
+}
+
+TEST(cubeb, record)
+{
+ if (cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr /*print_log*/) !=
+ CUBEB_OK) {
+ fprintf(stderr, "Set log callback failed\n");
+ }
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params params;
+ int r;
+ user_state_record stream_state;
+
+ r = common_init(&ctx, "Cubeb record example");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, cubeb_destroy);
+
+ /* This test needs an available input device, skip it if this host does not
+ * have one. */
+ if (!can_run_audio_input_test(ctx)) {
+ return;
+ }
+
+ params.format = STREAM_FORMAT;
+ params.rate = SAMPLE_FREQUENCY;
+ params.channels = 1;
+ params.layout = CUBEB_LAYOUT_UNDEFINED;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb record (mono)", NULL, &params,
+ NULL, nullptr, 4096, data_cb_record, state_cb_record,
+ &stream_state);
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(500);
+ cubeb_stream_stop(stream);
+
+#ifdef __linux__
+ // user callback does not arrive in Linux, silence the error
+ fprintf(stderr, "Check is disabled in Linux\n");
+#else
+ ASSERT_FALSE(stream_state.invalid_audio_value.load());
+#endif
+}
+
+#undef SAMPLE_FREQUENCY
+#undef STREAM_FORMAT
diff --git a/third_party/rust/cubeb-sys/libcubeb/test/test_resampler.cpp b/third_party/rust/cubeb-sys/libcubeb/test/test_resampler.cpp
new file mode 100644
index 0000000000..adbcfd8c5e
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/test/test_resampler.cpp
@@ -0,0 +1,1164 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif // NOMINMAX
+#include "common.h"
+#include "cubeb_resampler_internal.h"
+#include "gtest/gtest.h"
+#include <algorithm>
+#include <iostream>
+#include <stdio.h>
+
+/* Windows cmath USE_MATH_DEFINE thing... */
+const float PI = 3.14159265359f;
+
+/* Testing all sample rates is very long, so if THOROUGH_TESTING is not defined,
+ * only part of the test suite is ran. */
+#ifdef THOROUGH_TESTING
+/* Some standard sample rates we're testing with. */
+const uint32_t sample_rates[] = {8000, 16000, 32000, 44100,
+ 48000, 88200, 96000, 192000};
+/* The maximum number of channels we're resampling. */
+const uint32_t max_channels = 2;
+/* The minimum an maximum number of milliseconds we're resampling for. This is
+ * used to simulate the fact that the audio stream is resampled in chunks,
+ * because audio is delivered using callbacks. */
+const uint32_t min_chunks = 10; /* ms */
+const uint32_t max_chunks = 30; /* ms */
+const uint32_t chunk_increment = 1;
+
+#else
+
+const uint32_t sample_rates[] = {
+ 8000,
+ 44100,
+ 48000,
+};
+const uint32_t max_channels = 2;
+const uint32_t min_chunks = 10; /* ms */
+const uint32_t max_chunks = 30; /* ms */
+const uint32_t chunk_increment = 10;
+#endif
+
+// #define DUMP_ARRAYS
+#ifdef DUMP_ARRAYS
+/**
+ * Files produced by dump(...) can be converted to .wave files using:
+ *
+ * sox -c <channel_count> -r <rate> -e float -b 32 file.raw file.wav
+ *
+ * for floating-point audio, or:
+ *
+ * sox -c <channel_count> -r <rate> -e unsigned -b 16 file.raw file.wav
+ *
+ * for 16bit integer audio.
+ */
+
+/* Use the correct implementation of fopen, depending on the platform. */
+void
+fopen_portable(FILE ** f, const char * name, const char * mode)
+{
+#ifdef WIN32
+ fopen_s(f, name, mode);
+#else
+ *f = fopen(name, mode);
+#endif
+}
+
+template <typename T>
+void
+dump(const char * name, T * frames, size_t count)
+{
+ FILE * file;
+ fopen_portable(&file, name, "wb");
+
+ if (!file) {
+ fprintf(stderr, "error opening %s\n", name);
+ return;
+ }
+
+ if (count != fwrite(frames, sizeof(T), count, file)) {
+ fprintf(stderr, "error writing to %s\n", name);
+ }
+ fclose(file);
+}
+#else
+template <typename T>
+void
+dump(const char * name, T * frames, size_t count)
+{
+}
+#endif
+
+// The more the ratio is far from 1, the more we accept a big error.
+float
+epsilon_tweak_ratio(float ratio)
+{
+ return ratio >= 1 ? ratio : 1 / ratio;
+}
+
+// Epsilon values for comparing resampled data to expected data.
+// The bigger the resampling ratio is, the more lax we are about errors.
+template <typename T>
+T
+epsilon(float ratio);
+
+template <>
+float
+epsilon(float ratio)
+{
+ return 0.08f * epsilon_tweak_ratio(ratio);
+}
+
+template <>
+int16_t
+epsilon(float ratio)
+{
+ return static_cast<int16_t>(10 * epsilon_tweak_ratio(ratio));
+}
+
+void
+test_delay_lines(uint32_t delay_frames, uint32_t channels, uint32_t chunk_ms)
+{
+ const size_t length_s = 2;
+ const size_t rate = 44100;
+ const size_t length_frames = rate * length_s;
+ delay_line<float> delay(delay_frames, channels, rate);
+ auto_array<float> input;
+ auto_array<float> output;
+ uint32_t chunk_length = channels * chunk_ms * rate / 1000;
+ uint32_t output_offset = 0;
+ uint32_t channel = 0;
+
+ /** Generate diracs every 100 frames, and check they are delayed. */
+ input.push_silence(length_frames * channels);
+ for (uint32_t i = 0; i < input.length() - 1; i += 100) {
+ input.data()[i + channel] = 0.5;
+ channel = (channel + 1) % channels;
+ }
+ dump("input.raw", input.data(), input.length());
+ while (input.length()) {
+ uint32_t to_pop =
+ std::min<uint32_t>(input.length(), chunk_length * channels);
+ float * in = delay.input_buffer(to_pop / channels);
+ input.pop(in, to_pop);
+ delay.written(to_pop / channels);
+ output.push_silence(to_pop);
+ delay.output(output.data() + output_offset, to_pop / channels);
+ output_offset += to_pop;
+ }
+
+ // Check the diracs have been shifted by `delay_frames` frames.
+ for (uint32_t i = 0; i < output.length() - delay_frames * channels + 1;
+ i += 100) {
+ ASSERT_EQ(output.data()[i + channel + delay_frames * channels], 0.5);
+ channel = (channel + 1) % channels;
+ }
+
+ dump("output.raw", output.data(), output.length());
+}
+/**
+ * This takes sine waves with a certain `channels` count, `source_rate`, and
+ * resample them, by chunk of `chunk_duration` milliseconds, to `target_rate`.
+ * Then a sample-wise comparison is performed against a sine wave generated at
+ * the correct rate.
+ */
+template <typename T>
+void
+test_resampler_one_way(uint32_t channels, uint32_t source_rate,
+ uint32_t target_rate, float chunk_duration)
+{
+ size_t chunk_duration_in_source_frames =
+ static_cast<uint32_t>(ceil(chunk_duration * source_rate / 1000.));
+ float resampling_ratio = static_cast<float>(source_rate) / target_rate;
+ cubeb_resampler_speex_one_way<T> resampler(channels, source_rate, target_rate,
+ 3);
+ auto_array<T> source(channels * source_rate * 10);
+ auto_array<T> destination(channels * target_rate * 10);
+ auto_array<T> expected(channels * target_rate * 10);
+ uint32_t phase_index = 0;
+ uint32_t offset = 0;
+ const uint32_t buf_len = 2; /* seconds */
+
+ // generate a sine wave in each channel, at the source sample rate
+ source.push_silence(channels * source_rate * buf_len);
+ while (offset != source.length()) {
+ float p = phase_index++ / static_cast<float>(source_rate);
+ for (uint32_t j = 0; j < channels; j++) {
+ source.data()[offset++] = 0.5 * sin(440. * 2 * PI * p);
+ }
+ }
+
+ dump("input.raw", source.data(), source.length());
+
+ expected.push_silence(channels * target_rate * buf_len);
+ // generate a sine wave in each channel, at the target sample rate.
+ // Insert silent samples at the beginning to account for the resampler
+ // latency.
+ offset = resampler.latency() * channels;
+ for (uint32_t i = 0; i < offset; i++) {
+ expected.data()[i] = 0.0f;
+ }
+ phase_index = 0;
+ while (offset != expected.length()) {
+ float p = phase_index++ / static_cast<float>(target_rate);
+ for (uint32_t j = 0; j < channels; j++) {
+ expected.data()[offset++] = 0.5 * sin(440. * 2 * PI * p);
+ }
+ }
+
+ dump("expected.raw", expected.data(), expected.length());
+
+ // resample by chunk
+ uint32_t write_offset = 0;
+ destination.push_silence(channels * target_rate * buf_len);
+ while (write_offset < destination.length()) {
+ size_t output_frames = static_cast<uint32_t>(
+ floor(chunk_duration_in_source_frames / resampling_ratio));
+ uint32_t input_frames = resampler.input_needed_for_output(output_frames);
+ resampler.input(source.data(), input_frames);
+ source.pop(nullptr, input_frames * channels);
+ resampler.output(
+ destination.data() + write_offset,
+ std::min(output_frames,
+ (destination.length() - write_offset) / channels));
+ write_offset += output_frames * channels;
+ }
+
+ dump("output.raw", destination.data(), expected.length());
+
+ // compare, taking the latency into account
+ bool fuzzy_equal = true;
+ for (uint32_t i = resampler.latency() + 1; i < expected.length(); i++) {
+ float diff = fabs(expected.data()[i] - destination.data()[i]);
+ if (diff > epsilon<T>(resampling_ratio)) {
+ fprintf(stderr, "divergence at %d: %f %f (delta %f)\n", i,
+ expected.data()[i], destination.data()[i], diff);
+ fuzzy_equal = false;
+ }
+ }
+ ASSERT_TRUE(fuzzy_equal);
+}
+
+template <typename T>
+cubeb_sample_format
+cubeb_format();
+
+template <>
+cubeb_sample_format
+cubeb_format<float>()
+{
+ return CUBEB_SAMPLE_FLOAT32NE;
+}
+
+template <>
+cubeb_sample_format
+cubeb_format<short>()
+{
+ return CUBEB_SAMPLE_S16NE;
+}
+
+struct osc_state {
+ osc_state()
+ : input_phase_index(0), output_phase_index(0), output_offset(0),
+ input_channels(0), output_channels(0)
+ {
+ }
+ uint32_t input_phase_index;
+ uint32_t max_output_phase_index;
+ uint32_t output_phase_index;
+ uint32_t output_offset;
+ uint32_t input_channels;
+ uint32_t output_channels;
+ uint32_t output_rate;
+ uint32_t target_rate;
+ auto_array<float> input;
+ auto_array<float> output;
+};
+
+uint32_t
+fill_with_sine(float * buf, uint32_t rate, uint32_t channels, uint32_t frames,
+ uint32_t initial_phase)
+{
+ uint32_t offset = 0;
+ for (uint32_t i = 0; i < frames; i++) {
+ float p = initial_phase++ / static_cast<float>(rate);
+ for (uint32_t j = 0; j < channels; j++) {
+ buf[offset++] = 0.5 * sin(440. * 2 * PI * p);
+ }
+ }
+ return initial_phase;
+}
+
+long
+data_cb_resampler(cubeb_stream * /*stm*/, void * user_ptr,
+ const void * input_buffer, void * output_buffer,
+ long frame_count)
+{
+ osc_state * state = reinterpret_cast<osc_state *>(user_ptr);
+ const float * in = reinterpret_cast<const float *>(input_buffer);
+ float * out = reinterpret_cast<float *>(output_buffer);
+
+ state->input.push(in, frame_count * state->input_channels);
+
+ /* Check how much output frames we need to write */
+ uint32_t remaining =
+ state->max_output_phase_index - state->output_phase_index;
+ uint32_t to_write = std::min<uint32_t>(remaining, frame_count);
+ state->output_phase_index =
+ fill_with_sine(out, state->target_rate, state->output_channels, to_write,
+ state->output_phase_index);
+
+ return to_write;
+}
+
+template <typename T>
+bool
+array_fuzzy_equal(const auto_array<T> & lhs, const auto_array<T> & rhs, T epsi)
+{
+ uint32_t len = std::min(lhs.length(), rhs.length());
+
+ for (uint32_t i = 0; i < len; i++) {
+ if (fabs(lhs.at(i) - rhs.at(i)) > epsi) {
+ std::cout << "not fuzzy equal at index: " << i << " lhs: " << lhs.at(i)
+ << " rhs: " << rhs.at(i)
+ << " delta: " << fabs(lhs.at(i) - rhs.at(i))
+ << " epsilon: " << epsi << std::endl;
+ return false;
+ }
+ }
+ return true;
+}
+
+template <typename T>
+void
+test_resampler_duplex(uint32_t input_channels, uint32_t output_channels,
+ uint32_t input_rate, uint32_t output_rate,
+ uint32_t target_rate, float chunk_duration)
+{
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+ osc_state state;
+
+ input_params.format = output_params.format = cubeb_format<T>();
+ state.input_channels = input_params.channels = input_channels;
+ state.output_channels = output_params.channels = output_channels;
+ input_params.rate = input_rate;
+ state.output_rate = output_params.rate = output_rate;
+ state.target_rate = target_rate;
+ input_params.prefs = output_params.prefs = CUBEB_STREAM_PREF_NONE;
+ long got;
+
+ cubeb_resampler * resampler = cubeb_resampler_create(
+ (cubeb_stream *)nullptr, &input_params, &output_params, target_rate,
+ data_cb_resampler, (void *)&state, CUBEB_RESAMPLER_QUALITY_VOIP,
+ CUBEB_RESAMPLER_RECLOCK_NONE);
+
+ long latency = cubeb_resampler_latency(resampler);
+
+ const uint32_t duration_s = 2;
+ int32_t duration_frames = duration_s * target_rate;
+ uint32_t input_array_frame_count =
+ ceil(chunk_duration * input_rate / 1000) +
+ ceilf(static_cast<float>(input_rate) / target_rate) * 2;
+ uint32_t output_array_frame_count = chunk_duration * output_rate / 1000;
+ auto_array<float> input_buffer(input_channels * input_array_frame_count);
+ auto_array<float> output_buffer(output_channels * output_array_frame_count);
+ auto_array<float> expected_resampled_input(input_channels * duration_frames);
+ auto_array<float> expected_resampled_output(output_channels * output_rate *
+ duration_s);
+
+ state.max_output_phase_index = duration_s * target_rate;
+
+ expected_resampled_input.push_silence(input_channels * duration_frames);
+ expected_resampled_output.push_silence(output_channels * output_rate *
+ duration_s);
+
+ /* expected output is a 440Hz sine wave at 16kHz */
+ fill_with_sine(expected_resampled_input.data() + latency, target_rate,
+ input_channels, duration_frames - latency, 0);
+ /* expected output is a 440Hz sine wave at 32kHz */
+ fill_with_sine(expected_resampled_output.data() + latency, output_rate,
+ output_channels, output_rate * duration_s - latency, 0);
+
+ while (state.output_phase_index != state.max_output_phase_index) {
+ uint32_t leftover_samples = input_buffer.length() * input_channels;
+ input_buffer.reserve(input_array_frame_count);
+ state.input_phase_index = fill_with_sine(
+ input_buffer.data() + leftover_samples, input_rate, input_channels,
+ input_array_frame_count - leftover_samples, state.input_phase_index);
+ long input_consumed = input_array_frame_count;
+ input_buffer.set_length(input_array_frame_count);
+
+ got = cubeb_resampler_fill(resampler, input_buffer.data(), &input_consumed,
+ output_buffer.data(), output_array_frame_count);
+
+ /* handle leftover input */
+ if (input_array_frame_count != static_cast<uint32_t>(input_consumed)) {
+ input_buffer.pop(nullptr, input_consumed * input_channels);
+ } else {
+ input_buffer.clear();
+ }
+
+ state.output.push(output_buffer.data(), got * state.output_channels);
+ }
+
+ dump("input_expected.raw", expected_resampled_input.data(),
+ expected_resampled_input.length());
+ dump("output_expected.raw", expected_resampled_output.data(),
+ expected_resampled_output.length());
+ dump("input.raw", state.input.data(), state.input.length());
+ dump("output.raw", state.output.data(), state.output.length());
+
+ // This is disabled because the latency estimation in the resampler code is
+ // slightly off so we can generate expected vectors.
+ // See https://github.com/kinetiknz/cubeb/issues/93
+ // ASSERT_TRUE(array_fuzzy_equal(state.input, expected_resampled_input,
+ // epsilon<T>(input_rate/target_rate)));
+ // ASSERT_TRUE(array_fuzzy_equal(state.output, expected_resampled_output,
+ // epsilon<T>(output_rate/target_rate)));
+
+ cubeb_resampler_destroy(resampler);
+}
+
+#define array_size(x) (sizeof(x) / sizeof(x[0]))
+
+TEST(cubeb, resampler_one_way)
+{
+ /* Test one way resamplers */
+ for (uint32_t channels = 1; channels <= max_channels; channels++) {
+ for (uint32_t source_rate = 0; source_rate < array_size(sample_rates);
+ source_rate++) {
+ for (uint32_t dest_rate = 0; dest_rate < array_size(sample_rates);
+ dest_rate++) {
+ for (uint32_t chunk_duration = min_chunks; chunk_duration < max_chunks;
+ chunk_duration += chunk_increment) {
+ fprintf(stderr,
+ "one_way: channels: %d, source_rate: %d, dest_rate: %d, "
+ "chunk_duration: %d\n",
+ channels, sample_rates[source_rate], sample_rates[dest_rate],
+ chunk_duration);
+ test_resampler_one_way<float>(channels, sample_rates[source_rate],
+ sample_rates[dest_rate],
+ chunk_duration);
+ }
+ }
+ }
+ }
+}
+
+TEST(cubeb, DISABLED_resampler_duplex)
+{
+ for (uint32_t input_channels = 1; input_channels <= max_channels;
+ input_channels++) {
+ for (uint32_t output_channels = 1; output_channels <= max_channels;
+ output_channels++) {
+ for (uint32_t source_rate_input = 0;
+ source_rate_input < array_size(sample_rates); source_rate_input++) {
+ for (uint32_t source_rate_output = 0;
+ source_rate_output < array_size(sample_rates);
+ source_rate_output++) {
+ for (uint32_t dest_rate = 0; dest_rate < array_size(sample_rates);
+ dest_rate++) {
+ for (uint32_t chunk_duration = min_chunks;
+ chunk_duration < max_chunks;
+ chunk_duration += chunk_increment) {
+ fprintf(stderr,
+ "input channels:%d output_channels:%d input_rate:%d "
+ "output_rate:%d target_rate:%d chunk_ms:%d\n",
+ input_channels, output_channels,
+ sample_rates[source_rate_input],
+ sample_rates[source_rate_output], sample_rates[dest_rate],
+ chunk_duration);
+ test_resampler_duplex<float>(input_channels, output_channels,
+ sample_rates[source_rate_input],
+ sample_rates[source_rate_output],
+ sample_rates[dest_rate],
+ chunk_duration);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+TEST(cubeb, resampler_delay_line)
+{
+ for (uint32_t channel = 1; channel <= 2; channel++) {
+ for (uint32_t delay_frames = 4; delay_frames <= 40;
+ delay_frames += chunk_increment) {
+ for (uint32_t chunk_size = 10; chunk_size <= 30; chunk_size++) {
+ fprintf(stderr, "channel: %d, delay_frames: %d, chunk_size: %d\n",
+ channel, delay_frames, chunk_size);
+ test_delay_lines(delay_frames, channel, chunk_size);
+ }
+ }
+ }
+}
+
+long
+test_output_only_noop_data_cb(cubeb_stream * /*stm*/, void * /*user_ptr*/,
+ const void * input_buffer, void * output_buffer,
+ long frame_count)
+{
+ EXPECT_TRUE(output_buffer);
+ EXPECT_TRUE(!input_buffer);
+ return frame_count;
+}
+
+TEST(cubeb, resampler_output_only_noop)
+{
+ cubeb_stream_params output_params;
+ int target_rate;
+
+ output_params.rate = 44100;
+ output_params.channels = 1;
+ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ target_rate = output_params.rate;
+
+ cubeb_resampler * resampler = cubeb_resampler_create(
+ (cubeb_stream *)nullptr, nullptr, &output_params, target_rate,
+ test_output_only_noop_data_cb, nullptr, CUBEB_RESAMPLER_QUALITY_VOIP,
+ CUBEB_RESAMPLER_RECLOCK_NONE);
+ const long out_frames = 128;
+ float out_buffer[out_frames];
+ long got;
+
+ got =
+ cubeb_resampler_fill(resampler, nullptr, nullptr, out_buffer, out_frames);
+
+ ASSERT_EQ(got, out_frames);
+
+ cubeb_resampler_destroy(resampler);
+}
+
+long
+test_drain_data_cb(cubeb_stream * /*stm*/, void * user_ptr,
+ const void * input_buffer, void * output_buffer,
+ long frame_count)
+{
+ EXPECT_TRUE(output_buffer);
+ EXPECT_TRUE(!input_buffer);
+ auto cb_count = static_cast<int *>(user_ptr);
+ (*cb_count)++;
+ return frame_count - 1;
+}
+
+TEST(cubeb, resampler_drain)
+{
+ cubeb_stream_params output_params;
+ int target_rate;
+
+ output_params.rate = 44100;
+ output_params.channels = 1;
+ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ target_rate = 48000;
+ int cb_count = 0;
+
+ cubeb_resampler * resampler = cubeb_resampler_create(
+ (cubeb_stream *)nullptr, nullptr, &output_params, target_rate,
+ test_drain_data_cb, &cb_count, CUBEB_RESAMPLER_QUALITY_VOIP,
+ CUBEB_RESAMPLER_RECLOCK_NONE);
+
+ const long out_frames = 128;
+ float out_buffer[out_frames];
+ long got;
+
+ do {
+ got = cubeb_resampler_fill(resampler, nullptr, nullptr, out_buffer,
+ out_frames);
+ } while (got == out_frames);
+
+ /* The callback should be called once but not again after returning <
+ * frame_count. */
+ ASSERT_EQ(cb_count, 1);
+
+ cubeb_resampler_destroy(resampler);
+}
+
+// gtest does not support using ASSERT_EQ and friend in a function that returns
+// a value.
+void
+check_output(const void * input_buffer, void * output_buffer, long frame_count)
+{
+ ASSERT_EQ(input_buffer, nullptr);
+ ASSERT_EQ(frame_count, 256);
+ ASSERT_TRUE(!!output_buffer);
+}
+
+long
+cb_passthrough_resampler_output(cubeb_stream * /*stm*/, void * /*user_ptr*/,
+ const void * input_buffer, void * output_buffer,
+ long frame_count)
+{
+ check_output(input_buffer, output_buffer, frame_count);
+ return frame_count;
+}
+
+TEST(cubeb, resampler_passthrough_output_only)
+{
+ // Test that the passthrough resampler works when there is only an output
+ // stream.
+ cubeb_stream_params output_params;
+
+ const size_t output_channels = 2;
+ output_params.channels = output_channels;
+ output_params.rate = 44100;
+ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ int target_rate = output_params.rate;
+
+ cubeb_resampler * resampler = cubeb_resampler_create(
+ (cubeb_stream *)nullptr, nullptr, &output_params, target_rate,
+ cb_passthrough_resampler_output, nullptr, CUBEB_RESAMPLER_QUALITY_VOIP,
+ CUBEB_RESAMPLER_RECLOCK_NONE);
+
+ float output_buffer[output_channels * 256];
+
+ long got;
+ for (uint32_t i = 0; i < 30; i++) {
+ got = cubeb_resampler_fill(resampler, nullptr, nullptr, output_buffer, 256);
+ ASSERT_EQ(got, 256);
+ }
+
+ cubeb_resampler_destroy(resampler);
+}
+
+// gtest does not support using ASSERT_EQ and friend in a function that returns
+// a value.
+void
+check_input(const void * input_buffer, void * output_buffer, long frame_count)
+{
+ ASSERT_EQ(output_buffer, nullptr);
+ ASSERT_EQ(frame_count, 256);
+ ASSERT_TRUE(!!input_buffer);
+}
+
+long
+cb_passthrough_resampler_input(cubeb_stream * /*stm*/, void * /*user_ptr*/,
+ const void * input_buffer, void * output_buffer,
+ long frame_count)
+{
+ check_input(input_buffer, output_buffer, frame_count);
+ return frame_count;
+}
+
+TEST(cubeb, resampler_passthrough_input_only)
+{
+ // Test that the passthrough resampler works when there is only an output
+ // stream.
+ cubeb_stream_params input_params;
+
+ const size_t input_channels = 2;
+ input_params.channels = input_channels;
+ input_params.rate = 44100;
+ input_params.format = CUBEB_SAMPLE_FLOAT32NE;
+ int target_rate = input_params.rate;
+
+ cubeb_resampler * resampler = cubeb_resampler_create(
+ (cubeb_stream *)nullptr, &input_params, nullptr, target_rate,
+ cb_passthrough_resampler_input, nullptr, CUBEB_RESAMPLER_QUALITY_VOIP,
+ CUBEB_RESAMPLER_RECLOCK_NONE);
+
+ float input_buffer[input_channels * 256];
+
+ long got;
+ for (uint32_t i = 0; i < 30; i++) {
+ long int frames = 256;
+ got = cubeb_resampler_fill(resampler, input_buffer, &frames, nullptr, 0);
+ ASSERT_EQ(got, 256);
+ }
+
+ cubeb_resampler_destroy(resampler);
+}
+
+template <typename T>
+long
+seq(T * array, int stride, long start, long count)
+{
+ uint32_t output_idx = 0;
+ for (int i = 0; i < count; i++) {
+ for (int j = 0; j < stride; j++) {
+ array[output_idx + j] = static_cast<T>(start + i);
+ }
+ output_idx += stride;
+ }
+ return start + count;
+}
+
+template <typename T>
+void
+is_seq(T * array, int stride, long count, long expected_start)
+{
+ uint32_t output_index = 0;
+ for (long i = 0; i < count; i++) {
+ for (int j = 0; j < stride; j++) {
+ ASSERT_EQ(array[output_index + j], expected_start + i);
+ }
+ output_index += stride;
+ }
+}
+
+template <typename T>
+void
+is_not_seq(T * array, int stride, long count, long expected_start)
+{
+ uint32_t output_index = 0;
+ for (long i = 0; i < count; i++) {
+ for (int j = 0; j < stride; j++) {
+ ASSERT_NE(array[output_index + j], expected_start + i);
+ }
+ output_index += stride;
+ }
+}
+
+struct closure {
+ int input_channel_count;
+};
+
+// gtest does not support using ASSERT_EQ and friend in a function that returns
+// a value.
+template <typename T>
+void
+check_duplex(const T * input_buffer, T * output_buffer, long frame_count,
+ int input_channel_count)
+{
+ ASSERT_EQ(frame_count, 256);
+ // Silence scan-build warning.
+ ASSERT_TRUE(!!output_buffer);
+ assert(output_buffer);
+ ASSERT_TRUE(!!input_buffer);
+ assert(input_buffer);
+
+ int output_index = 0;
+ int input_index = 0;
+ for (int i = 0; i < frame_count; i++) {
+ // output is two channels, input one or two channels.
+ if (input_channel_count == 1) {
+ output_buffer[output_index] = output_buffer[output_index + 1] =
+ input_buffer[i];
+ } else if (input_channel_count == 2) {
+ output_buffer[output_index] = input_buffer[input_index];
+ output_buffer[output_index + 1] = input_buffer[input_index + 1];
+ }
+ output_index += 2;
+ input_index += input_channel_count;
+ }
+}
+
+long
+cb_passthrough_resampler_duplex(cubeb_stream * /*stm*/, void * user_ptr,
+ const void * input_buffer, void * output_buffer,
+ long frame_count)
+{
+ closure * c = reinterpret_cast<closure *>(user_ptr);
+ check_duplex<float>(static_cast<const float *>(input_buffer),
+ static_cast<float *>(output_buffer), frame_count,
+ c->input_channel_count);
+ return frame_count;
+}
+
+TEST(cubeb, resampler_passthrough_duplex_callback_reordering)
+{
+ // Test that when pre-buffering on resampler creation, we can survive an input
+ // callback being delayed.
+
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+
+ const int input_channels = 1;
+ const int output_channels = 2;
+
+ input_params.channels = input_channels;
+ input_params.rate = 44100;
+ input_params.format = CUBEB_SAMPLE_FLOAT32NE;
+
+ output_params.channels = output_channels;
+ output_params.rate = input_params.rate;
+ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+
+ int target_rate = input_params.rate;
+
+ closure c;
+ c.input_channel_count = input_channels;
+
+ cubeb_resampler * resampler = cubeb_resampler_create(
+ (cubeb_stream *)nullptr, &input_params, &output_params, target_rate,
+ cb_passthrough_resampler_duplex, &c, CUBEB_RESAMPLER_QUALITY_VOIP,
+ CUBEB_RESAMPLER_RECLOCK_NONE);
+
+ const long BUF_BASE_SIZE = 256;
+ float input_buffer_prebuffer[input_channels * BUF_BASE_SIZE * 2];
+ float input_buffer_glitch[input_channels * BUF_BASE_SIZE * 2];
+ float input_buffer_normal[input_channels * BUF_BASE_SIZE];
+ float output_buffer[output_channels * BUF_BASE_SIZE];
+
+ long seq_idx = 0;
+ long output_seq_idx = 0;
+
+ long prebuffer_frames =
+ ARRAY_LENGTH(input_buffer_prebuffer) / input_params.channels;
+ seq_idx =
+ seq(input_buffer_prebuffer, input_channels, seq_idx, prebuffer_frames);
+
+ long got =
+ cubeb_resampler_fill(resampler, input_buffer_prebuffer, &prebuffer_frames,
+ output_buffer, BUF_BASE_SIZE);
+
+ output_seq_idx += BUF_BASE_SIZE;
+
+ // prebuffer_frames will hold the frames used by the resampler.
+ ASSERT_EQ(prebuffer_frames, BUF_BASE_SIZE);
+ ASSERT_EQ(got, BUF_BASE_SIZE);
+
+ for (uint32_t i = 0; i < 300; i++) {
+ long int frames = BUF_BASE_SIZE;
+ // Simulate that sometimes, we don't have the input callback on time
+ if (i != 0 && (i % 100) == 0) {
+ long zero = 0;
+ got =
+ cubeb_resampler_fill(resampler, input_buffer_normal /* unused here */,
+ &zero, output_buffer, BUF_BASE_SIZE);
+ is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx);
+ output_seq_idx += BUF_BASE_SIZE;
+ } else if (i != 0 && (i % 100) == 1) {
+ // if this is the case, the on the next iteration, we'll have twice the
+ // amount of input frames
+ seq_idx =
+ seq(input_buffer_glitch, input_channels, seq_idx, BUF_BASE_SIZE * 2);
+ frames = 2 * BUF_BASE_SIZE;
+ got = cubeb_resampler_fill(resampler, input_buffer_glitch, &frames,
+ output_buffer, BUF_BASE_SIZE);
+ is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx);
+ output_seq_idx += BUF_BASE_SIZE;
+ } else {
+ // normal case
+ seq_idx =
+ seq(input_buffer_normal, input_channels, seq_idx, BUF_BASE_SIZE);
+ long normal_input_frame_count = 256;
+ got = cubeb_resampler_fill(resampler, input_buffer_normal,
+ &normal_input_frame_count, output_buffer,
+ BUF_BASE_SIZE);
+ is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx);
+ output_seq_idx += BUF_BASE_SIZE;
+ }
+ ASSERT_EQ(got, BUF_BASE_SIZE);
+ }
+
+ cubeb_resampler_destroy(resampler);
+}
+
+// Artificially simulate output thread underruns,
+// by building up artificial delay in the input.
+// Check that the frame drop logic kicks in.
+TEST(cubeb, resampler_drift_drop_data)
+{
+ for (uint32_t input_channels = 1; input_channels < 3; input_channels++) {
+ cubeb_stream_params input_params;
+ cubeb_stream_params output_params;
+
+ const int output_channels = 2;
+ const int sample_rate = 44100;
+
+ input_params.channels = input_channels;
+ input_params.rate = sample_rate;
+ input_params.format = CUBEB_SAMPLE_FLOAT32NE;
+
+ output_params.channels = output_channels;
+ output_params.rate = sample_rate;
+ output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+
+ int target_rate = input_params.rate;
+
+ closure c;
+ c.input_channel_count = input_channels;
+
+ cubeb_resampler * resampler = cubeb_resampler_create(
+ (cubeb_stream *)nullptr, &input_params, &output_params, target_rate,
+ cb_passthrough_resampler_duplex, &c, CUBEB_RESAMPLER_QUALITY_VOIP,
+ CUBEB_RESAMPLER_RECLOCK_NONE);
+
+ const long BUF_BASE_SIZE = 256;
+
+ // The factor by which the deadline is missed. This is intentionally
+ // kind of large to trigger the frame drop quickly. In real life, multiple
+ // smaller under-runs would accumulate.
+ const long UNDERRUN_FACTOR = 10;
+ // Number buffer used for pre-buffering, that some backends do.
+ const long PREBUFFER_FACTOR = 2;
+
+ std::vector<float> input_buffer_prebuffer(input_channels * BUF_BASE_SIZE *
+ PREBUFFER_FACTOR);
+ std::vector<float> input_buffer_glitch(input_channels * BUF_BASE_SIZE *
+ UNDERRUN_FACTOR);
+ std::vector<float> input_buffer_normal(input_channels * BUF_BASE_SIZE);
+ std::vector<float> output_buffer(output_channels * BUF_BASE_SIZE);
+
+ long seq_idx = 0;
+ long output_seq_idx = 0;
+
+ long prebuffer_frames =
+ input_buffer_prebuffer.size() / input_params.channels;
+ seq_idx = seq(input_buffer_prebuffer.data(), input_channels, seq_idx,
+ prebuffer_frames);
+
+ long got = cubeb_resampler_fill(resampler, input_buffer_prebuffer.data(),
+ &prebuffer_frames, output_buffer.data(),
+ BUF_BASE_SIZE);
+
+ output_seq_idx += BUF_BASE_SIZE;
+
+ // prebuffer_frames will hold the frames used by the resampler.
+ ASSERT_EQ(prebuffer_frames, BUF_BASE_SIZE);
+ ASSERT_EQ(got, BUF_BASE_SIZE);
+
+ for (uint32_t i = 0; i < 300; i++) {
+ long int frames = BUF_BASE_SIZE;
+ if (i != 0 && (i % 100) == 1) {
+ // Once in a while, the output thread misses its deadline.
+ // The input thread still produces data, so it ends up accumulating.
+ // Simulate this by providing a much bigger input buffer. Check that the
+ // sequence is now unaligned, meaning we've dropped data to keep
+ // everything in sync.
+ seq_idx = seq(input_buffer_glitch.data(), input_channels, seq_idx,
+ BUF_BASE_SIZE * UNDERRUN_FACTOR);
+ frames = BUF_BASE_SIZE * UNDERRUN_FACTOR;
+ got =
+ cubeb_resampler_fill(resampler, input_buffer_glitch.data(), &frames,
+ output_buffer.data(), BUF_BASE_SIZE);
+ is_seq(output_buffer.data(), 2, BUF_BASE_SIZE, output_seq_idx);
+ output_seq_idx += BUF_BASE_SIZE;
+ } else if (i != 0 && (i % 100) == 2) {
+ // On the next iteration, the sequence should be broken
+ seq_idx = seq(input_buffer_normal.data(), input_channels, seq_idx,
+ BUF_BASE_SIZE);
+ long normal_input_frame_count = 256;
+ got = cubeb_resampler_fill(resampler, input_buffer_normal.data(),
+ &normal_input_frame_count,
+ output_buffer.data(), BUF_BASE_SIZE);
+ is_not_seq(output_buffer.data(), output_channels, BUF_BASE_SIZE,
+ output_seq_idx);
+ // Reclock so that we can use is_seq again.
+ output_seq_idx = output_buffer[BUF_BASE_SIZE * output_channels - 1] + 1;
+ } else {
+ // normal case
+ seq_idx = seq(input_buffer_normal.data(), input_channels, seq_idx,
+ BUF_BASE_SIZE);
+ long normal_input_frame_count = 256;
+ got = cubeb_resampler_fill(resampler, input_buffer_normal.data(),
+ &normal_input_frame_count,
+ output_buffer.data(), BUF_BASE_SIZE);
+ is_seq(output_buffer.data(), output_channels, BUF_BASE_SIZE,
+ output_seq_idx);
+ output_seq_idx += BUF_BASE_SIZE;
+ }
+ ASSERT_EQ(got, BUF_BASE_SIZE);
+ }
+
+ cubeb_resampler_destroy(resampler);
+ }
+}
+
+static long
+passthrough_resampler_fill_eq_input(cubeb_stream * stream, void * user_ptr,
+ void const * input_buffer,
+ void * output_buffer, long nframes)
+{
+ // gtest does not support using ASSERT_EQ and friends in a
+ // function that returns a value.
+ [nframes, input_buffer]() {
+ ASSERT_EQ(nframes, 32);
+ const float * input = static_cast<const float *>(input_buffer);
+ for (int i = 0; i < 64; ++i) {
+ ASSERT_FLOAT_EQ(input[i], 0.01 * i);
+ }
+ }();
+ return nframes;
+}
+
+TEST(cubeb, passthrough_resampler_fill_eq_input)
+{
+ uint32_t channels = 2;
+ uint32_t sample_rate = 44100;
+ passthrough_resampler<float> resampler =
+ passthrough_resampler<float>(nullptr, passthrough_resampler_fill_eq_input,
+ nullptr, channels, sample_rate);
+
+ long input_frame_count = 32;
+ long output_frame_count = 32;
+ float input[64] = {};
+ float output[64] = {};
+ for (uint32_t i = 0; i < input_frame_count * channels; ++i) {
+ input[i] = 0.01 * i;
+ }
+ long got =
+ resampler.fill(input, &input_frame_count, output, output_frame_count);
+ ASSERT_EQ(got, output_frame_count);
+ // Input frames used must be equal to output frames.
+ ASSERT_EQ(input_frame_count, output_frame_count);
+}
+
+static long
+passthrough_resampler_fill_short_input(cubeb_stream * stream, void * user_ptr,
+ void const * input_buffer,
+ void * output_buffer, long nframes)
+{
+ // gtest does not support using ASSERT_EQ and friends in a
+ // function that returns a value.
+ [nframes, input_buffer]() {
+ ASSERT_EQ(nframes, 32);
+ const float * input = static_cast<const float *>(input_buffer);
+ // First part contains the input
+ for (int i = 0; i < 32; ++i) {
+ ASSERT_FLOAT_EQ(input[i], 0.01 * i);
+ }
+ // missing part contains silence
+ for (int i = 32; i < 64; ++i) {
+ ASSERT_FLOAT_EQ(input[i], 0.0);
+ }
+ }();
+ return nframes;
+}
+
+TEST(cubeb, passthrough_resampler_fill_short_input)
+{
+ uint32_t channels = 2;
+ uint32_t sample_rate = 44100;
+ passthrough_resampler<float> resampler = passthrough_resampler<float>(
+ nullptr, passthrough_resampler_fill_short_input, nullptr, channels,
+ sample_rate);
+
+ long input_frame_count = 16;
+ long output_frame_count = 32;
+ float input[64] = {};
+ float output[64] = {};
+ for (uint32_t i = 0; i < input_frame_count * channels; ++i) {
+ input[i] = 0.01 * i;
+ }
+ long got =
+ resampler.fill(input, &input_frame_count, output, output_frame_count);
+ ASSERT_EQ(got, output_frame_count);
+ // Input frames used are less than the output frames due to glitch.
+ ASSERT_EQ(input_frame_count, output_frame_count - 16);
+}
+
+static long
+passthrough_resampler_fill_input_left(cubeb_stream * stream, void * user_ptr,
+ void const * input_buffer,
+ void * output_buffer, long nframes)
+{
+ // gtest does not support using ASSERT_EQ and friends in a
+ // function that returns a value.
+ int iteration = *static_cast<int *>(user_ptr);
+ if (iteration == 1) {
+ [nframes, input_buffer]() {
+ ASSERT_EQ(nframes, 32);
+ const float * input = static_cast<const float *>(input_buffer);
+ for (int i = 0; i < 64; ++i) {
+ ASSERT_FLOAT_EQ(input[i], 0.01 * i);
+ }
+ }();
+ } else if (iteration == 2) {
+ [nframes, input_buffer]() {
+ ASSERT_EQ(nframes, 32);
+ const float * input = static_cast<const float *>(input_buffer);
+ for (int i = 0; i < 32; ++i) {
+ // First part contains the reamaining input samples from previous
+ // iteration (since they were more).
+ ASSERT_FLOAT_EQ(input[i], 0.01 * (i + 64));
+ // next part contains the new buffer
+ ASSERT_FLOAT_EQ(input[i + 32], 0.01 * i);
+ }
+ }();
+ } else if (iteration == 3) {
+ [nframes, input_buffer]() {
+ ASSERT_EQ(nframes, 32);
+ const float * input = static_cast<const float *>(input_buffer);
+ for (int i = 0; i < 32; ++i) {
+ // First part (16 frames) contains the reamaining input samples
+ // from previous iteration (since they were more).
+ ASSERT_FLOAT_EQ(input[i], 0.01 * (i + 32));
+ }
+ for (int i = 0; i < 16; ++i) {
+ // next part (8 frames) contains the new input buffer.
+ ASSERT_FLOAT_EQ(input[i + 32], 0.01 * i);
+ // last part (8 frames) contains silence.
+ ASSERT_FLOAT_EQ(input[i + 32 + 16], 0.0);
+ }
+ }();
+ }
+ return nframes;
+}
+
+TEST(cubeb, passthrough_resampler_fill_input_left)
+{
+ const uint32_t channels = 2;
+ const uint32_t sample_rate = 44100;
+ int iteration = 0;
+ passthrough_resampler<float> resampler = passthrough_resampler<float>(
+ nullptr, passthrough_resampler_fill_input_left, &iteration, channels,
+ sample_rate);
+
+ long input_frame_count = 48; // 32 + 16
+ const long output_frame_count = 32;
+ float input[96] = {};
+ float output[64] = {};
+ for (uint32_t i = 0; i < input_frame_count * channels; ++i) {
+ input[i] = 0.01 * i;
+ }
+
+ // 1st iteration, add the extra input.
+ iteration = 1;
+ long got =
+ resampler.fill(input, &input_frame_count, output, output_frame_count);
+ ASSERT_EQ(got, output_frame_count);
+ // Input frames used must be equal to output frames.
+ ASSERT_EQ(input_frame_count, output_frame_count);
+
+ // 2st iteration, use the extra input from previous iteration,
+ // 16 frames are remaining in the input buffer.
+ input_frame_count = 32; // we need 16 input frames but we get more;
+ iteration = 2;
+ got = resampler.fill(input, &input_frame_count, output, output_frame_count);
+ ASSERT_EQ(got, output_frame_count);
+ // Input frames used must be equal to output frames.
+ ASSERT_EQ(input_frame_count, output_frame_count);
+
+ // 3rd iteration, use the extra input from previous iteration.
+ // 16 frames are remaining in the input buffer.
+ input_frame_count = 16 - 8; // We need 16 more input frames but we only get 8.
+ iteration = 3;
+ got = resampler.fill(input, &input_frame_count, output, output_frame_count);
+ ASSERT_EQ(got, output_frame_count);
+ // Input frames used are less than the output frames due to glitch.
+ ASSERT_EQ(input_frame_count, output_frame_count - 8);
+}
+
+TEST(cubeb, individual_methods)
+{
+ const uint32_t channels = 2;
+ const uint32_t sample_rate = 44100;
+ const uint32_t frames = 256;
+
+ delay_line<float> dl(10, channels, sample_rate);
+ uint32_t frames_needed1 = dl.input_needed_for_output(0);
+ ASSERT_EQ(frames_needed1, 0u);
+
+ cubeb_resampler_speex_one_way<float> one_way(
+ channels, sample_rate, sample_rate, CUBEB_RESAMPLER_QUALITY_DEFAULT);
+ float buffer[channels * frames] = {0.0};
+ // Add all frames in the resampler's internal buffer.
+ one_way.input(buffer, frames);
+ // Ask for less than the existing frames, this would create a uint overlflow
+ // without the fix.
+ uint32_t frames_needed2 = one_way.input_needed_for_output(0);
+ ASSERT_EQ(frames_needed2, 0u);
+}
+
+#undef NOMINMAX
+#undef DUMP_ARRAYS
diff --git a/third_party/rust/cubeb-sys/libcubeb/test/test_ring_array.cpp b/third_party/rust/cubeb-sys/libcubeb/test/test_ring_array.cpp
new file mode 100644
index 0000000000..a364684c77
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/test/test_ring_array.cpp
@@ -0,0 +1,72 @@
+#include "gtest/gtest.h"
+#ifdef __APPLE__
+#include "cubeb/cubeb.h"
+#include "cubeb_ring_array.h"
+#include <CoreAudio/CoreAudioTypes.h>
+#include <iostream>
+#include <string.h>
+
+TEST(cubeb, ring_array)
+{
+ ring_array ra;
+
+ ASSERT_EQ(ring_array_init(&ra, 0, 0, 1, 1), CUBEB_ERROR_INVALID_PARAMETER);
+ ASSERT_EQ(ring_array_init(&ra, 1, 0, 0, 1), CUBEB_ERROR_INVALID_PARAMETER);
+
+ unsigned int capacity = 8;
+ ring_array_init(&ra, capacity, sizeof(int), 1, 1);
+ int verify_data[capacity]; // {1,2,3,4,5,6,7,8};
+ AudioBuffer * p_data = NULL;
+
+ for (unsigned int i = 0; i < capacity; ++i) {
+ verify_data[i] = i; // in case capacity change value
+ *(int *)ra.buffer_array[i].mData = i;
+ ASSERT_EQ(ra.buffer_array[i].mDataByteSize, sizeof(int));
+ ASSERT_EQ(ra.buffer_array[i].mNumberChannels, 1u);
+ }
+
+ /* Get store buffers*/
+ for (unsigned int i = 0; i < capacity; ++i) {
+ p_data = ring_array_get_free_buffer(&ra);
+ ASSERT_NE(p_data, nullptr);
+ ASSERT_EQ(*(int *)p_data->mData, verify_data[i]);
+ }
+ /*Now array is full extra store should give NULL*/
+ ASSERT_EQ(ring_array_get_free_buffer(&ra), nullptr);
+ /* Get fetch buffers*/
+ for (unsigned int i = 0; i < capacity; ++i) {
+ p_data = ring_array_get_data_buffer(&ra);
+ ASSERT_NE(p_data, nullptr);
+ ASSERT_EQ(*(int *)p_data->mData, verify_data[i]);
+ }
+ /*Now array is empty extra fetch should give NULL*/
+ ASSERT_EQ(ring_array_get_data_buffer(&ra), nullptr);
+
+ p_data = NULL;
+ /* Repeated store fetch should can go for ever*/
+ for (unsigned int i = 0; i < 2 * capacity; ++i) {
+ p_data = ring_array_get_free_buffer(&ra);
+ ASSERT_NE(p_data, nullptr);
+ ASSERT_EQ(ring_array_get_data_buffer(&ra), p_data);
+ }
+
+ p_data = NULL;
+ /* Verify/modify buffer data*/
+ for (unsigned int i = 0; i < capacity; ++i) {
+ p_data = ring_array_get_free_buffer(&ra);
+ ASSERT_NE(p_data, nullptr);
+ ASSERT_EQ(*((int *)p_data->mData), verify_data[i]);
+ (*((int *)p_data->mData))++; // Modify data
+ }
+ for (unsigned int i = 0; i < capacity; ++i) {
+ p_data = ring_array_get_data_buffer(&ra);
+ ASSERT_NE(p_data, nullptr);
+ ASSERT_EQ(*((int *)p_data->mData),
+ verify_data[i] + 1); // Verify modified data
+ }
+
+ ring_array_destroy(&ra);
+}
+#else
+TEST(cubeb, DISABLED_ring_array) {}
+#endif
diff --git a/third_party/rust/cubeb-sys/libcubeb/test/test_ring_buffer.cpp b/third_party/rust/cubeb-sys/libcubeb/test/test_ring_buffer.cpp
new file mode 100644
index 0000000000..fffe1087e4
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/test/test_ring_buffer.cpp
@@ -0,0 +1,229 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+
+#include "cubeb_ringbuffer.h"
+#include "gtest/gtest.h"
+#include <chrono>
+#include <iostream>
+#include <thread>
+
+/* Generate a monotonically increasing sequence of numbers. */
+template <typename T> class sequence_generator {
+public:
+ sequence_generator(size_t channels) : channels(channels) {}
+ void get(T * elements, size_t frames)
+ {
+ for (size_t i = 0; i < frames; i++) {
+ for (size_t c = 0; c < channels; c++) {
+ elements[i * channels + c] = static_cast<T>(index_);
+ }
+ index_++;
+ }
+ }
+ void rewind(size_t frames) { index_ -= frames; }
+
+private:
+ size_t index_ = 0;
+ size_t channels = 0;
+};
+
+/* Checks that a sequence is monotonically increasing. */
+template <typename T> class sequence_verifier {
+public:
+ sequence_verifier(size_t channels) : channels(channels) {}
+ void check(T * elements, size_t frames)
+ {
+ for (size_t i = 0; i < frames; i++) {
+ for (size_t c = 0; c < channels; c++) {
+ if (elements[i * channels + c] != static_cast<T>(index_)) {
+ std::cerr << "Element " << i << " is different. Expected "
+ << static_cast<T>(index_) << ", got " << elements[i]
+ << ". (channel count: " << channels << ")." << std::endl;
+ ASSERT_TRUE(false);
+ }
+ }
+ index_++;
+ }
+ }
+
+private:
+ size_t index_ = 0;
+ size_t channels = 0;
+};
+
+template <typename T>
+void
+test_ring(lock_free_audio_ring_buffer<T> & buf, int channels,
+ int capacity_frames)
+{
+ std::unique_ptr<T[]> seq(new T[capacity_frames * channels]);
+ sequence_generator<T> gen(channels);
+ sequence_verifier<T> checker(channels);
+
+ int iterations = 1002;
+
+ const int block_size = 128;
+
+ while (iterations--) {
+ gen.get(seq.get(), block_size);
+ int rv = buf.enqueue(seq.get(), block_size);
+ ASSERT_EQ(rv, block_size);
+ PodZero(seq.get(), block_size);
+ rv = buf.dequeue(seq.get(), block_size);
+ ASSERT_EQ(rv, block_size);
+ checker.check(seq.get(), block_size);
+ }
+}
+
+template <typename T>
+void
+test_ring_multi(lock_free_audio_ring_buffer<T> & buf, int channels,
+ int capacity_frames)
+{
+ sequence_verifier<T> checker(channels);
+ std::unique_ptr<T[]> out_buffer(new T[capacity_frames * channels]);
+
+ const int block_size = 128;
+
+ std::thread t([=, &buf] {
+ int iterations = 1002;
+ std::unique_ptr<T[]> in_buffer(new T[capacity_frames * channels]);
+ sequence_generator<T> gen(channels);
+
+ while (iterations--) {
+ std::this_thread::yield();
+ gen.get(in_buffer.get(), block_size);
+ int rv = buf.enqueue(in_buffer.get(), block_size);
+ ASSERT_TRUE(rv <= block_size);
+ if (rv != block_size) {
+ gen.rewind(block_size - rv);
+ }
+ }
+ });
+
+ int remaining = 1002;
+
+ while (remaining--) {
+ std::this_thread::yield();
+ int rv = buf.dequeue(out_buffer.get(), block_size);
+ ASSERT_TRUE(rv <= block_size);
+ checker.check(out_buffer.get(), rv);
+ }
+
+ t.join();
+}
+
+template <typename T>
+void
+basic_api_test(T & ring)
+{
+ ASSERT_EQ(ring.capacity(), 128);
+
+ ASSERT_EQ(ring.available_read(), 0);
+ ASSERT_EQ(ring.available_write(), 128);
+
+ int rv = ring.enqueue_default(63);
+
+ ASSERT_TRUE(rv == 63);
+ ASSERT_EQ(ring.available_read(), 63);
+ ASSERT_EQ(ring.available_write(), 65);
+
+ rv = ring.enqueue_default(65);
+
+ ASSERT_EQ(rv, 65);
+ ASSERT_EQ(ring.available_read(), 128);
+ ASSERT_EQ(ring.available_write(), 0);
+
+ rv = ring.dequeue(nullptr, 63);
+
+ ASSERT_EQ(ring.available_read(), 65);
+ ASSERT_EQ(ring.available_write(), 63);
+
+ rv = ring.dequeue(nullptr, 65);
+
+ ASSERT_EQ(ring.available_read(), 0);
+ ASSERT_EQ(ring.available_write(), 128);
+}
+
+void
+test_reset_api()
+{
+ const size_t ring_buffer_size = 128;
+ const size_t enqueue_size = ring_buffer_size / 2;
+
+ lock_free_queue<float> ring(ring_buffer_size);
+ std::thread t([=, &ring] {
+ std::unique_ptr<float[]> in_buffer(new float[enqueue_size]);
+ ring.enqueue(in_buffer.get(), enqueue_size);
+ });
+
+ t.join();
+
+ ring.reset_thread_ids();
+
+ // Enqueue with a different thread. We have reset the thread ID
+ // in the ring buffer, this should work.
+ std::thread t2([=, &ring] {
+ std::unique_ptr<float[]> in_buffer(new float[enqueue_size]);
+ ring.enqueue(in_buffer.get(), enqueue_size);
+ });
+
+ t2.join();
+
+ ASSERT_TRUE(true);
+}
+
+TEST(cubeb, ring_buffer)
+{
+ /* Basic API test. */
+ const int min_channels = 1;
+ const int max_channels = 10;
+ const int min_capacity = 199;
+ const int max_capacity = 1277;
+ const int capacity_increment = 27;
+
+ lock_free_queue<float> q1(128);
+ basic_api_test(q1);
+ lock_free_queue<short> q2(128);
+ basic_api_test(q2);
+
+ for (size_t channels = min_channels; channels < max_channels; channels++) {
+ lock_free_audio_ring_buffer<float> q3(channels, 128);
+ basic_api_test(q3);
+ lock_free_audio_ring_buffer<short> q4(channels, 128);
+ basic_api_test(q4);
+ }
+
+ /* Single thread testing. */
+ /* Test mono to 9.1 */
+ for (size_t channels = min_channels; channels < max_channels; channels++) {
+ /* Use non power-of-two numbers to catch edge-cases. */
+ for (size_t capacity_frames = min_capacity; capacity_frames < max_capacity;
+ capacity_frames += capacity_increment) {
+ lock_free_audio_ring_buffer<float> ring(channels, capacity_frames);
+ test_ring(ring, channels, capacity_frames);
+ }
+ }
+
+ /* Multi thread testing */
+ for (size_t channels = min_channels; channels < max_channels; channels++) {
+ /* Use non power-of-two numbers to catch edge-cases. */
+ for (size_t capacity_frames = min_capacity; capacity_frames < max_capacity;
+ capacity_frames += capacity_increment) {
+ lock_free_audio_ring_buffer<short> ring(channels, capacity_frames);
+ test_ring_multi(ring, channels, capacity_frames);
+ }
+ }
+
+ test_reset_api();
+}
+
+#undef NOMINMAX
diff --git a/third_party/rust/cubeb-sys/libcubeb/test/test_sanity.cpp b/third_party/rust/cubeb-sys/libcubeb/test/test_sanity.cpp
new file mode 100644
index 0000000000..17d3c542b4
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/test/test_sanity.cpp
@@ -0,0 +1,721 @@
+/*
+ * Copyright © 2011 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include "cubeb/cubeb.h"
+#include <atomic>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+
+// #define ENABLE_NORMAL_LOG
+// #define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+#define STREAM_RATE 44100
+#define STREAM_LATENCY 100 * STREAM_RATE / 1000
+#define STREAM_CHANNELS 1
+#define STREAM_LAYOUT CUBEB_LAYOUT_MONO
+#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
+
+int
+is_windows_7()
+{
+#ifdef __MINGW32__
+ fprintf(stderr,
+ "Warning: this test was built with MinGW.\n"
+ "MinGW does not contain necessary version checking infrastructure. "
+ "Claiming to be Windows 7, even if we're not.\n");
+ return 1;
+#endif
+#if (defined(_WIN32) || defined(__WIN32__)) && (!defined(__MINGW32__))
+ OSVERSIONINFOEX osvi;
+ DWORDLONG condition_mask = 0;
+
+ ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
+ osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+
+ // NT 6.1 is Windows 7
+ osvi.dwMajorVersion = 6;
+ osvi.dwMinorVersion = 1;
+
+ VER_SET_CONDITION(condition_mask, VER_MAJORVERSION, VER_EQUAL);
+ VER_SET_CONDITION(condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL);
+
+ return VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION,
+ condition_mask);
+#else
+ return 0;
+#endif
+}
+
+static int dummy;
+static std::atomic<uint64_t> total_frames_written;
+static int delay_callback;
+
+static long
+test_data_callback(cubeb_stream * stm, void * user_ptr,
+ const void * /*inputbuffer*/, void * outputbuffer,
+ long nframes)
+{
+ EXPECT_TRUE(stm && user_ptr == &dummy && outputbuffer && nframes > 0);
+ assert(outputbuffer);
+ memset(outputbuffer, 0, nframes * sizeof(short));
+
+ total_frames_written += nframes;
+ if (delay_callback) {
+ delay(10);
+ }
+ return nframes;
+}
+
+void
+test_state_callback(cubeb_stream * /*stm*/, void * /*user_ptr*/,
+ cubeb_state /*state*/)
+{
+}
+
+TEST(cubeb, init_destroy_context)
+{
+ int r;
+ cubeb * ctx;
+ char const * backend_id;
+
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ backend_id = cubeb_get_backend_id(ctx);
+ ASSERT_TRUE(backend_id);
+
+ fprintf(stderr, "Backend: %s\n", backend_id);
+
+ cubeb_destroy(ctx);
+}
+
+TEST(cubeb, init_destroy_multiple_contexts)
+{
+ size_t i;
+ int r;
+ cubeb * ctx[4];
+ int order[4] = {2, 0, 3, 1};
+ ASSERT_EQ(ARRAY_LENGTH(ctx), ARRAY_LENGTH(order));
+
+ for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
+ r = common_init(&ctx[i], NULL);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx[i], nullptr);
+ }
+
+ /* destroy in a different order */
+ for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
+ cubeb_destroy(ctx[order[i]]);
+ }
+}
+
+TEST(cubeb, context_variables)
+{
+ int r;
+ cubeb * ctx;
+ uint32_t value;
+ cubeb_stream_params params;
+
+ r = common_init(&ctx, "test_context_variables");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ params.channels = STREAM_CHANNELS;
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_get_min_latency(ctx, &params, &value);
+ ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
+ if (r == CUBEB_OK) {
+ ASSERT_TRUE(value > 0);
+ }
+
+ r = cubeb_get_preferred_sample_rate(ctx, &value);
+ ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
+ if (r == CUBEB_OK) {
+ ASSERT_TRUE(value > 0);
+ }
+
+ cubeb_destroy(ctx);
+}
+
+TEST(cubeb, init_destroy_stream)
+{
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params params;
+
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = STREAM_CHANNELS;
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params,
+ STREAM_LATENCY, test_data_callback, test_state_callback,
+ &dummy);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
+
+ cubeb_stream_destroy(stream);
+ cubeb_destroy(ctx);
+}
+
+TEST(cubeb, init_destroy_multiple_streams)
+{
+ size_t i;
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream[8];
+ cubeb_stream_params params;
+
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = STREAM_CHANNELS;
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
+ r = cubeb_stream_init(ctx, &stream[i], "test", NULL, NULL, NULL, &params,
+ STREAM_LATENCY, test_data_callback,
+ test_state_callback, &dummy);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream[i], nullptr);
+ }
+
+ for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
+ cubeb_stream_destroy(stream[i]);
+ }
+
+ cubeb_destroy(ctx);
+}
+
+TEST(cubeb, configure_stream)
+{
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params params;
+
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = 2;
+ params.layout = CUBEB_LAYOUT_STEREO;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params,
+ STREAM_LATENCY, test_data_callback, test_state_callback,
+ &dummy);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
+
+ r = cubeb_stream_set_volume(stream, 1.0f);
+ ASSERT_TRUE(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED);
+
+ r = cubeb_stream_set_name(stream, "test 2");
+ ASSERT_TRUE(r == 0 || r == CUBEB_ERROR_NOT_SUPPORTED);
+
+ cubeb_stream_destroy(stream);
+ cubeb_destroy(ctx);
+}
+
+TEST(cubeb, configure_stream_undefined_layout)
+{
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params params;
+
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = 2;
+ params.layout = CUBEB_LAYOUT_UNDEFINED;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params,
+ STREAM_LATENCY, test_data_callback, test_state_callback,
+ &dummy);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
+
+ r = cubeb_stream_start(stream);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ delay(100);
+
+ r = cubeb_stream_stop(stream);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ cubeb_stream_destroy(stream);
+ cubeb_destroy(ctx);
+}
+
+static void
+test_init_start_stop_destroy_multiple_streams(int early, int delay_ms)
+{
+ size_t i;
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream[8];
+ cubeb_stream_params params;
+
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = STREAM_CHANNELS;
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
+ r = cubeb_stream_init(ctx, &stream[i], "test", NULL, NULL, NULL, &params,
+ STREAM_LATENCY, test_data_callback,
+ test_state_callback, &dummy);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream[i], nullptr);
+ if (early) {
+ r = cubeb_stream_start(stream[i]);
+ ASSERT_EQ(r, CUBEB_OK);
+ }
+ }
+
+ if (!early) {
+ for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
+ r = cubeb_stream_start(stream[i]);
+ ASSERT_EQ(r, CUBEB_OK);
+ }
+ }
+
+ if (delay_ms) {
+ delay(delay_ms);
+ }
+
+ if (!early) {
+ for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
+ r = cubeb_stream_stop(stream[i]);
+ ASSERT_EQ(r, CUBEB_OK);
+ }
+ }
+
+ for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
+ if (early) {
+ r = cubeb_stream_stop(stream[i]);
+ ASSERT_EQ(r, CUBEB_OK);
+ }
+ cubeb_stream_destroy(stream[i]);
+ }
+
+ cubeb_destroy(ctx);
+}
+
+TEST(cubeb, init_start_stop_destroy_multiple_streams)
+{
+ /* Sometimes, when using WASAPI on windows 7 (vista and 8 are okay), and
+ * calling Activate a lot on an AudioClient, 0x800700b7 is returned. This is
+ * the HRESULT value for "Cannot create a file when that file already exists",
+ * and is not documented as a possible return value for this call. Hence, we
+ * try to limit the number of streams we create in this test. */
+ if (!is_windows_7()) {
+ delay_callback = 0;
+ test_init_start_stop_destroy_multiple_streams(0, 0);
+ test_init_start_stop_destroy_multiple_streams(1, 0);
+ test_init_start_stop_destroy_multiple_streams(0, 150);
+ test_init_start_stop_destroy_multiple_streams(1, 150);
+ delay_callback = 1;
+ test_init_start_stop_destroy_multiple_streams(0, 0);
+ test_init_start_stop_destroy_multiple_streams(1, 0);
+ test_init_start_stop_destroy_multiple_streams(0, 150);
+ test_init_start_stop_destroy_multiple_streams(1, 150);
+ }
+}
+
+TEST(cubeb, init_destroy_multiple_contexts_and_streams)
+{
+ size_t i, j;
+ int r;
+ cubeb * ctx[2];
+ cubeb_stream * stream[8];
+ cubeb_stream_params params;
+ size_t streams_per_ctx = ARRAY_LENGTH(stream) / ARRAY_LENGTH(ctx);
+ ASSERT_EQ(ARRAY_LENGTH(ctx) * streams_per_ctx, ARRAY_LENGTH(stream));
+
+ /* Sometimes, when using WASAPI on windows 7 (vista and 8 are okay), and
+ * calling Activate a lot on an AudioClient, 0x800700b7 is returned. This is
+ * the HRESULT value for "Cannot create a file when that file already exists",
+ * and is not documented as a possible return value for this call. Hence, we
+ * try to limit the number of streams we create in this test. */
+ if (is_windows_7())
+ return;
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = STREAM_CHANNELS;
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
+ r = common_init(&ctx[i], "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx[i], nullptr);
+
+ for (j = 0; j < streams_per_ctx; ++j) {
+ r = cubeb_stream_init(ctx[i], &stream[i * streams_per_ctx + j], "test",
+ NULL, NULL, NULL, &params, STREAM_LATENCY,
+ test_data_callback, test_state_callback, &dummy);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream[i * streams_per_ctx + j], nullptr);
+ }
+ }
+
+ for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
+ for (j = 0; j < streams_per_ctx; ++j) {
+ cubeb_stream_destroy(stream[i * streams_per_ctx + j]);
+ }
+ cubeb_destroy(ctx[i]);
+ }
+}
+
+TEST(cubeb, basic_stream_operations)
+{
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params params;
+ uint64_t position;
+ uint32_t latency;
+
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = STREAM_CHANNELS;
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params,
+ STREAM_LATENCY, test_data_callback, test_state_callback,
+ &dummy);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
+
+ /* position and latency before stream has started */
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_EQ(position, 0u);
+
+ r = cubeb_stream_get_latency(stream, &latency);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ r = cubeb_stream_start(stream);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ /* position and latency after while stream running */
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ r = cubeb_stream_get_latency(stream, &latency);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ r = cubeb_stream_stop(stream);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ /* position and latency after stream has stopped */
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ r = cubeb_stream_get_latency(stream, &latency);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ cubeb_stream_destroy(stream);
+ cubeb_destroy(ctx);
+}
+
+TEST(cubeb, stream_position)
+{
+ size_t i;
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params params;
+ uint64_t position, last_position;
+
+ total_frames_written = 0;
+
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = STREAM_CHANNELS;
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params,
+ STREAM_LATENCY, test_data_callback, test_state_callback,
+ &dummy);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
+
+ /* stream position should not advance before starting playback */
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_EQ(position, 0u);
+
+ delay(500);
+
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_EQ(position, 0u);
+
+ /* stream position should advance during playback */
+ r = cubeb_stream_start(stream);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ /* XXX let start happen */
+ delay(500);
+
+ /* stream should have prefilled */
+ ASSERT_TRUE(total_frames_written.load() > 0);
+
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+ last_position = position;
+
+ delay(500);
+
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_GE(position, last_position);
+ last_position = position;
+
+ /* stream position should not exceed total frames written */
+ for (i = 0; i < 5; ++i) {
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_GE(position, last_position);
+ ASSERT_LE(position, total_frames_written.load());
+ last_position = position;
+ delay(500);
+ }
+
+ /* test that the position is valid even when starting and
+ * stopping the stream. */
+ for (i = 0; i < 5; ++i) {
+ r = cubeb_stream_stop(stream);
+ ASSERT_EQ(r, CUBEB_OK);
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_TRUE(last_position < position);
+ last_position = position;
+ delay(500);
+ r = cubeb_stream_start(stream);
+ ASSERT_EQ(r, CUBEB_OK);
+ delay(500);
+ }
+
+ ASSERT_NE(last_position, 0u);
+
+ /* stream position should not advance after stopping playback */
+ r = cubeb_stream_stop(stream);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ /* XXX allow stream to settle */
+ delay(500);
+
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+ last_position = position;
+
+ delay(500);
+
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+ // The OpenSL backend performs client-side interpolation for its position and
+ // its drain implementation isn't very accurate.
+ if (strcmp(cubeb_get_backend_id(ctx), "opensl")) {
+ ASSERT_EQ(position, last_position);
+ }
+
+ cubeb_stream_destroy(stream);
+ cubeb_destroy(ctx);
+}
+
+static std::atomic<int> do_drain;
+static std::atomic<int> got_drain;
+
+static long
+test_drain_data_callback(cubeb_stream * stm, void * user_ptr,
+ const void * /*inputbuffer*/, void * outputbuffer,
+ long nframes)
+{
+ EXPECT_TRUE(stm && user_ptr == &dummy && outputbuffer && nframes > 0);
+ assert(outputbuffer);
+ if (do_drain == 1) {
+ do_drain = 2;
+ return 0;
+ }
+ /* once drain has started, callback must never be called again */
+ EXPECT_TRUE(do_drain != 2);
+ memset(outputbuffer, 0, nframes * sizeof(short));
+ total_frames_written += nframes;
+ return nframes;
+}
+
+void
+test_drain_state_callback(cubeb_stream * /*stm*/, void * /*user_ptr*/,
+ cubeb_state state)
+{
+ if (state == CUBEB_STATE_DRAINED) {
+ ASSERT_TRUE(!got_drain);
+ got_drain = 1;
+ }
+}
+
+TEST(cubeb, drain)
+{
+ int r;
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params params;
+ uint64_t position;
+
+ delay_callback = 0;
+ total_frames_written = 0;
+
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ params.format = STREAM_FORMAT;
+ params.rate = STREAM_RATE;
+ params.channels = STREAM_CHANNELS;
+ params.layout = STREAM_LAYOUT;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params,
+ STREAM_LATENCY, test_drain_data_callback,
+ test_drain_state_callback, &dummy);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(stream, nullptr);
+
+ r = cubeb_stream_start(stream);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ delay(5000);
+
+ do_drain = 1;
+
+ for (;;) {
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+ if (got_drain) {
+ break;
+ } else {
+ ASSERT_LE(position, total_frames_written.load());
+ }
+ delay(500);
+ }
+
+ r = cubeb_stream_get_position(stream, &position);
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_TRUE(got_drain);
+
+ // Really, we should be able to rely on position reaching our final written
+ // frame, but for now let's make sure it doesn't continue beyond that point.
+ // ASSERT_LE(position, total_frames_written.load());
+
+ cubeb_stream_destroy(stream);
+ cubeb_destroy(ctx);
+
+ got_drain = 0;
+ do_drain = 0;
+}
+
+TEST(cubeb, DISABLED_eos_during_prefill)
+{
+ // This test needs to be implemented.
+}
+
+TEST(cubeb, DISABLED_stream_destroy_pending_drain)
+{
+ // This test needs to be implemented.
+}
+
+TEST(cubeb, stable_devid)
+{
+ /* Test that the devid field of cubeb_device_info is stable
+ * (ie. compares equal) over two invocations of
+ * cubeb_enumerate_devices(). */
+
+ int r;
+ cubeb * ctx;
+ cubeb_device_collection first;
+ cubeb_device_collection second;
+ cubeb_device_type all_devices =
+ (cubeb_device_type)(CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT);
+ size_t n;
+
+ r = common_init(&ctx, "test_sanity");
+ ASSERT_EQ(r, CUBEB_OK);
+ ASSERT_NE(ctx, nullptr);
+
+ r = cubeb_enumerate_devices(ctx, all_devices, &first);
+ if (r == CUBEB_ERROR_NOT_SUPPORTED)
+ return;
+
+ ASSERT_EQ(r, CUBEB_OK);
+
+ r = cubeb_enumerate_devices(ctx, all_devices, &second);
+ ASSERT_EQ(r, CUBEB_OK);
+
+ ASSERT_EQ(first.count, second.count);
+ for (n = 0; n < first.count; n++) {
+ ASSERT_EQ(first.device[n].devid, second.device[n].devid);
+ }
+
+ r = cubeb_device_collection_destroy(ctx, &first);
+ ASSERT_EQ(r, CUBEB_OK);
+ r = cubeb_device_collection_destroy(ctx, &second);
+ ASSERT_EQ(r, CUBEB_OK);
+ cubeb_destroy(ctx);
+}
+
+#undef STREAM_RATE
+#undef STREAM_LATENCY
+#undef STREAM_CHANNELS
+#undef STREAM_LAYOUT
+#undef STREAM_FORMAT
diff --git a/third_party/rust/cubeb-sys/libcubeb/test/test_tone.cpp b/third_party/rust/cubeb-sys/libcubeb/test/test_tone.cpp
new file mode 100644
index 0000000000..56ff8767ba
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/test/test_tone.cpp
@@ -0,0 +1,130 @@
+/*
+ * Copyright © 2011 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* libcubeb api/function test. Plays a simple tone. */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include "cubeb/cubeb.h"
+#include <atomic>
+#include <limits.h>
+#include <math.h>
+#include <memory>
+#include <stdio.h>
+#include <stdlib.h>
+
+// #define ENABLE_NORMAL_LOG
+// #define ENABLE_VERBOSE_LOG
+#include "common.h"
+
+#define SAMPLE_FREQUENCY 48000
+#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
+
+/* store the phase of the generated waveform */
+struct cb_user_data {
+ std::atomic<long> position;
+};
+
+long
+data_cb_tone(cubeb_stream * stream, void * user, const void * /*inputbuffer*/,
+ void * outputbuffer, long nframes)
+{
+ struct cb_user_data * u = (struct cb_user_data *)user;
+ short * b = (short *)outputbuffer;
+ float t1, t2;
+ int i;
+
+ if (stream == NULL || u == NULL)
+ return CUBEB_ERROR;
+
+ /* generate our test tone on the fly */
+ for (i = 0; i < nframes; i++) {
+ /* North American dial tone */
+ t1 = sin(2 * M_PI * (i + u->position) * 350 / SAMPLE_FREQUENCY);
+ t2 = sin(2 * M_PI * (i + u->position) * 440 / SAMPLE_FREQUENCY);
+ b[i] = (SHRT_MAX / 2) * t1;
+ b[i] += (SHRT_MAX / 2) * t2;
+ /* European dial tone */
+ /*
+ t1 = sin(2*M_PI*(i + u->position)*425/SAMPLE_FREQUENCY);
+ b[i] = SHRT_MAX * t1;
+ */
+ }
+ /* remember our phase to avoid clicking on buffer transitions */
+ /* we'll still click if position overflows */
+ u->position += nframes;
+
+ return nframes;
+}
+
+void
+state_cb_tone(cubeb_stream * stream, void * user, cubeb_state state)
+{
+ struct cb_user_data * u = (struct cb_user_data *)user;
+
+ if (stream == NULL || u == NULL)
+ return;
+
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ fprintf(stderr, "stream started\n");
+ break;
+ case CUBEB_STATE_STOPPED:
+ fprintf(stderr, "stream stopped\n");
+ break;
+ case CUBEB_STATE_DRAINED:
+ fprintf(stderr, "stream drained\n");
+ break;
+ default:
+ fprintf(stderr, "unknown stream state %d\n", state);
+ }
+
+ return;
+}
+
+TEST(cubeb, tone)
+{
+ cubeb * ctx;
+ cubeb_stream * stream;
+ cubeb_stream_params params;
+ int r;
+
+ r = common_init(&ctx, "Cubeb tone example");
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
+
+ std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
+ ctx, cubeb_destroy);
+
+ params.format = STREAM_FORMAT;
+ params.rate = SAMPLE_FREQUENCY;
+ params.channels = 1;
+ params.layout = CUBEB_LAYOUT_MONO;
+ params.prefs = CUBEB_STREAM_PREF_NONE;
+
+ std::unique_ptr<cb_user_data> user_data(new cb_user_data());
+ ASSERT_TRUE(!!user_data) << "Error allocating user data";
+
+ user_data->position = 0;
+
+ r = cubeb_stream_init(ctx, &stream, "Cubeb tone (mono)", NULL, NULL, NULL,
+ &params, 4096, data_cb_tone, state_cb_tone,
+ user_data.get());
+ ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
+
+ std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
+ cleanup_stream_at_exit(stream, cubeb_stream_destroy);
+
+ cubeb_stream_start(stream);
+ delay(5000);
+ cubeb_stream_stop(stream);
+
+ ASSERT_TRUE(user_data->position.load());
+}
+
+#undef SAMPLE_FREQUENCY
+#undef STREAM_FORMAT
diff --git a/third_party/rust/cubeb-sys/libcubeb/test/test_triple_buffer.cpp b/third_party/rust/cubeb-sys/libcubeb/test/test_triple_buffer.cpp
new file mode 100644
index 0000000000..a6e0049b79
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/test/test_triple_buffer.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright © 2022 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* cubeb_triple_buffer test */
+#include "gtest/gtest.h"
+#if !defined(_XOPEN_SOURCE)
+#define _XOPEN_SOURCE 600
+#endif
+#include "cubeb/cubeb.h"
+#include "cubeb_triple_buffer.h"
+#include <atomic>
+#include <math.h>
+#include <memory>
+#include <stdio.h>
+#include <stdlib.h>
+#include <thread>
+
+#include "common.h"
+
+TEST(cubeb, triple_buffer)
+{
+ struct AB {
+ uint64_t a;
+ uint64_t b;
+ };
+ triple_buffer<AB> buffer;
+
+ std::atomic<bool> finished = {false};
+
+ ASSERT_TRUE(!buffer.updated());
+
+ auto t = std::thread([&finished, &buffer] {
+ AB ab;
+ ab.a = 0;
+ ab.b = UINT64_MAX;
+ uint64_t counter = 0;
+ do {
+ buffer.write(ab);
+ ab.a++;
+ ab.b--;
+ } while (counter++ < 1e6 && ab.a <= UINT64_MAX && ab.b != 0);
+ finished.store(true);
+ });
+
+ AB ab;
+ AB old_ab;
+ old_ab.a = 0;
+ old_ab.b = UINT64_MAX;
+
+ // Wait to have at least one value produced.
+ while (!buffer.updated()) {
+ }
+
+ // Check that the values are increasing (resp. descreasing) monotonically.
+ while (!finished) {
+ ab = buffer.read();
+ ASSERT_GE(ab.a, old_ab.a);
+ ASSERT_LE(ab.b, old_ab.b);
+ old_ab = ab;
+ }
+
+ t.join();
+}
diff --git a/third_party/rust/cubeb-sys/libcubeb/test/test_utils.cpp b/third_party/rust/cubeb-sys/libcubeb/test/test_utils.cpp
new file mode 100644
index 0000000000..a6227d4d7a
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/test/test_utils.cpp
@@ -0,0 +1,70 @@
+#include "cubeb_utils.h"
+#include "gtest/gtest.h"
+
+TEST(cubeb, auto_array)
+{
+ auto_array<uint32_t> array;
+ auto_array<uint32_t> array2(10);
+ uint32_t a[10];
+
+ ASSERT_EQ(array2.length(), 0u);
+ ASSERT_EQ(array2.capacity(), 10u);
+
+ for (uint32_t i = 0; i < 10; i++) {
+ a[i] = i;
+ }
+
+ ASSERT_EQ(array.capacity(), 0u);
+ ASSERT_EQ(array.length(), 0u);
+
+ array.push(a, 10);
+
+ ASSERT_TRUE(!array.reserve(9));
+
+ for (uint32_t i = 0; i < 10; i++) {
+ ASSERT_EQ(array.data()[i], i);
+ }
+
+ ASSERT_EQ(array.capacity(), 10u);
+ ASSERT_EQ(array.length(), 10u);
+
+ uint32_t b[10];
+
+ array.pop(b, 5);
+
+ ASSERT_EQ(array.capacity(), 10u);
+ ASSERT_EQ(array.length(), 5u);
+ for (uint32_t i = 0; i < 5; i++) {
+ ASSERT_EQ(b[i], i);
+ ASSERT_EQ(array.data()[i], 5 + i);
+ }
+ uint32_t * bb = b + 5;
+ array.pop(bb, 5);
+
+ ASSERT_EQ(array.capacity(), 10u);
+ ASSERT_EQ(array.length(), 0u);
+ for (uint32_t i = 0; i < 5; i++) {
+ ASSERT_EQ(bb[i], 5 + i);
+ }
+
+ ASSERT_TRUE(!array.pop(nullptr, 1));
+
+ array.push(a, 10);
+ array.push(a, 10);
+
+ for (uint32_t j = 0; j < 2; j++) {
+ for (uint32_t i = 0; i < 10; i++) {
+ ASSERT_EQ(array.data()[10 * j + i], i);
+ }
+ }
+ ASSERT_EQ(array.length(), 20u);
+ ASSERT_EQ(array.capacity(), 20u);
+ array.pop(nullptr, 5);
+
+ for (uint32_t i = 0; i < 5; i++) {
+ ASSERT_EQ(array.data()[i], 5 + i);
+ }
+
+ ASSERT_EQ(array.length(), 15u);
+ ASSERT_EQ(array.capacity(), 20u);
+}
diff --git a/third_party/rust/cubeb-sys/libcubeb/tools/cubeb-test.cpp b/third_party/rust/cubeb-sys/libcubeb/tools/cubeb-test.cpp
new file mode 100644
index 0000000000..a18faed8ff
--- /dev/null
+++ b/third_party/rust/cubeb-sys/libcubeb/tools/cubeb-test.cpp
@@ -0,0 +1,660 @@
+#define __STDC_FORMAT_MACROS
+#include "cubeb/cubeb.h"
+#include <atomic>
+#include <cassert>
+#include <cmath>
+#include <cstdarg>
+#include <cstring>
+#include <inttypes.h>
+#include <iostream>
+#include <vector>
+#ifdef _WIN32
+#include <objbase.h> // Used by CoInitialize()
+#endif
+
+#ifndef M_PI
+#define M_PI 3.14159263
+#endif
+
+// Default values if none specified
+#define DEFAULT_RATE 44100
+#define DEFAULT_OUTPUT_CHANNELS 2
+#define DEFAULT_INPUT_CHANNELS 1
+
+static const char* state_to_string(cubeb_state state) {
+ switch (state) {
+ case CUBEB_STATE_STARTED:
+ return "CUBEB_STATE_STARTED";
+ case CUBEB_STATE_STOPPED:
+ return "CUBEB_STATE_STOPPED";
+ case CUBEB_STATE_DRAINED:
+ return "CUBEB_STATE_DRAINED";
+ case CUBEB_STATE_ERROR:
+ return "CUBEB_STATE_ERROR";
+ default:
+ return "Undefined state";
+ }
+}
+
+static const char* device_type_to_string(cubeb_device_type type) {
+ switch (type) {
+ case CUBEB_DEVICE_TYPE_INPUT:
+ return "input";
+ case CUBEB_DEVICE_TYPE_OUTPUT:
+ return "output";
+ case CUBEB_DEVICE_TYPE_UNKNOWN:
+ return "unknown";
+ default:
+ assert(false);
+ }
+}
+
+static const char* device_state_to_string(cubeb_device_state state) {
+ switch (state) {
+ case CUBEB_DEVICE_STATE_DISABLED:
+ return "disabled";
+ case CUBEB_DEVICE_STATE_UNPLUGGED:
+ return "unplugged";
+ case CUBEB_DEVICE_STATE_ENABLED:
+ return "enabled";
+ default:
+ assert(false);
+ }
+}
+
+void print_log(const char* msg, ...) {
+ va_list args;
+ va_start(args, msg);
+ vprintf(msg, args);
+ printf("\n");
+ va_end(args);
+}
+
+class cubeb_client final {
+public:
+ cubeb_client() {}
+ ~cubeb_client() {}
+
+ bool init(char const * backend_name = nullptr);
+ cubeb_devid select_device(cubeb_device_type type) const;
+ bool init_stream();
+ bool start_stream();
+ bool stop_stream();
+ bool destroy_stream() const;
+ bool destroy();
+ bool activate_log(cubeb_log_level log_level) const;
+ void set_latency_testing(bool on);
+ void set_latency_frames(uint32_t latency_frames);
+ uint64_t get_stream_position() const;
+ uint32_t get_stream_output_latency() const;
+ uint32_t get_stream_input_latency() const;
+ uint32_t get_max_channel_count() const;
+
+ long user_data_cb(cubeb_stream* stm, void* user, const void* input_buffer,
+ void* output_buffer, long nframes);
+
+ void user_state_cb(cubeb_stream* stm, void* user, cubeb_state state);
+
+ bool register_device_collection_changed(cubeb_device_type devtype) const;
+ bool unregister_device_collection_changed(cubeb_device_type devtype) const;
+
+ cubeb_stream_params output_params = {};
+ cubeb_devid output_device = nullptr;
+
+ cubeb_stream_params input_params = {};
+ cubeb_devid input_device = nullptr;
+
+ void force_drain() { _force_drain = true; }
+
+private:
+ bool has_input() { return input_params.rate != 0; }
+ bool has_output() { return output_params.rate != 0; }
+
+ cubeb* context = nullptr;
+
+ cubeb_stream* stream = nullptr;
+
+ /* Accessed only from client and audio thread. */
+ std::atomic<uint32_t> _rate = {0};
+ std::atomic<uint32_t> _channels = {0};
+ std::atomic<bool> _latency_testing = {false};
+ std::atomic<uint32_t> _latency_frames = {0}; // if !0, override. Else, use min.
+ std::atomic<bool> _force_drain = {false};
+
+
+ /* Accessed only from audio thread. */
+ uint32_t _total_frames = 0;
+};
+
+bool cubeb_client::init(char const * backend_name) {
+ int rv = cubeb_init(&context, "Cubeb Test Application", backend_name);
+ if (rv != CUBEB_OK) {
+ fprintf(stderr, "Could not init cubeb\n");
+ return false;
+ }
+ fprintf(stderr, "Init cubeb backend: %s\n", cubeb_get_backend_id(context));
+ return true;
+}
+
+static long user_data_cb_s(cubeb_stream* stm, void* user,
+ const void* input_buffer, void* output_buffer,
+ long nframes) {
+ assert(user);
+ return static_cast<cubeb_client*>(user)->user_data_cb(stm, user, input_buffer,
+ output_buffer, nframes);
+}
+
+static void user_state_cb_s(cubeb_stream* stm, void* user, cubeb_state state) {
+ assert(user);
+ return static_cast<cubeb_client*>(user)->user_state_cb(stm, user, state);
+}
+
+void input_device_changed_callback_s(cubeb* context, void* user) {
+ fprintf(stderr, "input_device_changed_callback_s\n");
+}
+
+void output_device_changed_callback_s(cubeb* context, void* user) {
+ fprintf(stderr, "output_device_changed_callback_s\n");
+}
+
+void io_device_changed_callback_s(cubeb* context, void* user) {
+ fprintf(stderr, "io_device_changed_callback\n");
+}
+
+bool cubeb_client::init_stream() {
+ assert(has_input() || has_output());
+
+ _rate = has_output() ? output_params.rate : input_params.rate;
+ _channels = has_output() ? output_params.channels : input_params.channels;
+
+ cubeb_stream_params params;
+ params.rate = _rate;
+ params.channels = 2;
+ params.format = CUBEB_SAMPLE_FLOAT32NE;
+
+ uint32_t latency = 0;
+ int rv = cubeb_get_min_latency(context, &params, &latency);
+ if (rv != CUBEB_OK) {
+ fprintf(stderr, "Could not get min latency.");
+ return false;
+ }
+
+ if (_latency_frames) {
+ latency = _latency_frames.load();
+ printf("Opening a stream with a forced latency of %d frames\n", latency);
+ }
+
+ rv =
+ cubeb_stream_init(context, &stream, "Stream", input_device,
+ has_input() ? &input_params : nullptr, output_device,
+ has_output() ? &output_params : nullptr, latency, user_data_cb_s, user_state_cb_s, this);
+ if (rv != CUBEB_OK) {
+ fprintf(stderr, "Could not open the stream\n");
+ return false;
+ }
+ return true;
+}
+
+bool cubeb_client::start_stream() {
+ _force_drain = false;
+ int rv = cubeb_stream_start(stream);
+ if (rv != CUBEB_OK) {
+ fprintf(stderr, "Could not start the stream\n");
+ return false;
+ }
+ return true;
+}
+
+bool cubeb_client::stop_stream() {
+ _force_drain = false;
+ int rv = cubeb_stream_stop(stream);
+ if (rv != CUBEB_OK) {
+ fprintf(stderr, "Could not stop the stream\n");
+ return false;
+ }
+ return true;
+}
+
+uint64_t cubeb_client::get_stream_position() const {
+ uint64_t pos = 0;
+ int rv = cubeb_stream_get_position(stream, &pos);
+ if (rv != CUBEB_OK) {
+ fprintf(stderr, "Could not get the position of the stream\n");
+ return 0;
+ }
+ return pos;
+}
+
+uint32_t cubeb_client::get_stream_output_latency() const {
+ uint32_t latency = 0;
+ int rv = cubeb_stream_get_latency(stream, &latency);
+ if (rv != CUBEB_OK) {
+ fprintf(stderr, "Could not get the latency of the stream\n");
+ return 0;
+ }
+ return latency;
+}
+
+uint32_t cubeb_client::get_stream_input_latency() const {
+ uint32_t latency = 0;
+ int rv = cubeb_stream_get_input_latency(stream, &latency);
+ if (rv != CUBEB_OK) {
+ fprintf(stderr, "Could not get the latency of the input stream\n");
+ return 0;
+ }
+ return latency;
+}
+
+uint32_t cubeb_client::get_max_channel_count() const {
+ uint32_t channels = 0;
+ int rv = cubeb_get_max_channel_count(context, &channels);
+ if (rv != CUBEB_OK) {
+ fprintf(stderr, "Could not get max channel count\n");
+ return 0;
+ }
+ return channels;
+}
+
+bool cubeb_client::destroy_stream() const {
+ cubeb_stream_destroy(stream);
+ return true;
+}
+
+bool cubeb_client::destroy() {
+ cubeb_destroy(context);
+ return true;
+}
+
+bool cubeb_client::activate_log(cubeb_log_level log_level) const {
+ cubeb_log_callback log_callback = nullptr;
+ if (log_level != CUBEB_LOG_DISABLED) {
+ log_callback = print_log;
+ }
+
+ if (cubeb_set_log_callback(log_level, log_callback) != CUBEB_OK) {
+ fprintf(stderr, "Set log callback failed\n");
+ return false;
+ }
+ return true;
+}
+
+void cubeb_client::set_latency_testing(bool on) {
+ _latency_testing = on;
+}
+
+void cubeb_client::set_latency_frames(uint32_t latency_frames) {
+ _latency_frames = latency_frames;
+}
+
+static void fill_with_sine_tone(float* buf, uint32_t num_of_frames,
+ uint32_t num_of_channels, uint32_t frame_rate,
+ uint32_t position) {
+ for (uint32_t i = 0; i < num_of_frames; ++i) {
+ for (uint32_t c = 0; c < num_of_channels; ++c) {
+ buf[i * num_of_channels + c] =
+ 0.2 * sin(2 * M_PI * (i + position) * 350 / frame_rate);
+ buf[i * num_of_channels + c] +=
+ 0.2 * sin(2 * M_PI * (i + position) * 440 / frame_rate);
+ }
+ }
+}
+
+long cubeb_client::user_data_cb(cubeb_stream* stm, void* user,
+ const void* input_buffer, void* output_buffer,
+ long nframes) {
+ if (input_buffer && output_buffer) {
+ const float* in = static_cast<const float*>(input_buffer);
+ float* out = static_cast<float*>(output_buffer);
+ if (_latency_testing) {
+ for (int32_t i = 0; i < nframes; i++) {
+ // Impulses every second, mixed with the input signal fed back at half
+ // gain, to measure the input-to-output latency via feedback.
+ uint32_t clock = ((_total_frames + i) % _rate);
+ if (!clock) {
+ for (uint32_t j = 0; j < _channels; j++) {
+ out[i * _channels + j] = 1.0 + in[i] * 0.5;
+ }
+ } else {
+ for (uint32_t j = 0; j < _channels; j++) {
+ out[i * _channels + j] = 0.0 + in[i] * 0.5;
+ }
+ }
+ }
+ } else {
+ for (int32_t i = 0; i < nframes; i++) {
+ for (uint32_t j = 0; j < _channels; j++) {
+ out[i * _channels + j] = in[i];
+ }
+ }
+ }
+ } else if (output_buffer && !input_buffer) {
+ fill_with_sine_tone(static_cast<float*>(output_buffer), nframes, _channels,
+ _rate, _total_frames);
+ }
+
+ _total_frames += nframes;
+
+ if (_force_drain) {
+ return nframes - 1;
+ }
+
+ return nframes;
+}
+
+void cubeb_client::user_state_cb(cubeb_stream* stm, void* user,
+ cubeb_state state) {
+ fprintf(stderr, "state is %s\n", state_to_string(state));
+}
+
+bool cubeb_client::register_device_collection_changed(
+ cubeb_device_type devtype) const {
+ cubeb_device_collection_changed_callback callback = nullptr;
+ if (devtype == static_cast<cubeb_device_type>(CUBEB_DEVICE_TYPE_INPUT |
+ CUBEB_DEVICE_TYPE_OUTPUT)) {
+ callback = io_device_changed_callback_s;
+ } else if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
+ callback = output_device_changed_callback_s;
+ } else if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
+ callback = input_device_changed_callback_s;
+ }
+ int r = cubeb_register_device_collection_changed(
+ context, devtype, callback, nullptr);
+ if (r != CUBEB_OK) {
+ return false;
+ }
+ return true;
+}
+
+bool cubeb_client::unregister_device_collection_changed(
+ cubeb_device_type devtype) const {
+ int r = cubeb_register_device_collection_changed(
+ context, devtype, nullptr, nullptr);
+ if (r != CUBEB_OK) {
+ return false;
+ }
+ return true;
+}
+
+enum play_mode {
+ RECORD,
+ PLAYBACK,
+ DUPLEX,
+ LATENCY_TESTING,
+ COLLECTION_CHANGE,
+};
+
+struct operation_data {
+ play_mode pm;
+ uint32_t rate;
+ cubeb_device_type collection_device_type;
+};
+
+void print_help() {
+ const char * msg =
+ "0: change log level to disabled\n"
+ "1: change log level to normal\n"
+ "2: change log level to verbose\n"
+ "c: get max number of channels\n"
+ "p: start a initialized stream\n"
+ "s: stop a started stream\n"
+ "d: destroy stream\n"
+ "e: force stream to drain\n"
+ "f: get stream position (client thread)\n"
+ "i: change device type to input\n"
+ "o: change device type to output\n"
+ "a: change device type to input and output\n"
+ "k: change device type to unknown\n"
+ "r: register device collection changed callback for the current device type\n"
+ "u: unregister device collection changed callback for the current device type\n"
+ "q: quit\n"
+ "h: print this message\n";
+ fprintf(stderr, "%s\n", msg);
+}
+
+bool choose_action(cubeb_client& cl, operation_data * op, int c) {
+ // Consume "enter" and "space"
+ while (c == 10 || c == 32) {
+ c = getchar();
+ }
+ if (c == EOF) {
+ c = 'q';
+ }
+
+ if (c == 'q') {
+ if (op->pm == PLAYBACK || op->pm == RECORD || op->pm == DUPLEX || op->pm == LATENCY_TESTING) {
+ bool res = cl.stop_stream();
+ if (!res) {
+ fprintf(stderr, "stop_stream failed\n");
+ }
+ res = cl.destroy_stream();
+ if (!res) {
+ fprintf(stderr, "destroy_stream failed\n");
+ }
+ } else if (op->pm == COLLECTION_CHANGE) {
+ bool res = cl.unregister_device_collection_changed(op->collection_device_type);
+ if (!res) {
+ fprintf(stderr, "unregister_device_collection_changed failed\n");
+ }
+ }
+ return false; // exit the loop
+ } else if (c == 'h') {
+ print_help();
+ } else if (c == '0') {
+ cl.activate_log(CUBEB_LOG_DISABLED);
+ fprintf(stderr, "Log level changed to DISABLED\n");
+ } else if (c == '1') {
+ cl.activate_log(CUBEB_LOG_DISABLED);
+ cl.activate_log(CUBEB_LOG_NORMAL);
+ fprintf(stderr, "Log level changed to NORMAL\n");
+ } else if (c == '2') {
+ cl.activate_log(CUBEB_LOG_DISABLED);
+ cl.activate_log(CUBEB_LOG_VERBOSE);
+ fprintf(stderr, "Log level changed to VERBOSE\n");
+ } else if (c == 'p') {
+ bool res = cl.start_stream();
+ if (res) {
+ fprintf(stderr, "start_stream succeed\n");
+ } else {
+ fprintf(stderr, "start_stream failed\n");
+ }
+ } else if (c == 's') {
+ bool res = cl.stop_stream();
+ if (res) {
+ fprintf(stderr, "stop_stream succeed\n");
+ } else {
+ fprintf(stderr, "stop_stream failed\n");
+ }
+ } else if (c == 'd') {
+ bool res = cl.destroy_stream();
+ if (res) {
+ fprintf(stderr, "destroy_stream succeed\n");
+ } else {
+ fprintf(stderr, "destroy_stream failed\n");
+ }
+ } else if (c == 'e') {
+ cl.force_drain();
+ } else if (c == 'c') {
+ uint32_t channel_count = cl.get_max_channel_count();
+ fprintf(stderr, "max channel count (default output device): %u\n", channel_count);
+ } else if (c == 'f') {
+ uint64_t pos = cl.get_stream_position();
+ uint32_t latency;
+ fprintf(stderr, "stream position %" PRIu64 ".", pos);
+ if(op->pm == PLAYBACK || op->pm == DUPLEX) {
+ latency = cl.get_stream_output_latency();
+ fprintf(stderr, " (output latency %" PRIu32 ")", latency);
+ }
+ if(op->pm == RECORD || op->pm == DUPLEX) {
+ latency = cl.get_stream_input_latency();
+ fprintf(stderr, " (input latency %" PRIu32 ")", latency);
+ }
+ fprintf(stderr, "\n");
+ } else if (c == 'i') {
+ op->collection_device_type = CUBEB_DEVICE_TYPE_INPUT;
+ fprintf(stderr, "collection device type changed to INPUT\n");
+ } else if (c == 'o') {
+ op->collection_device_type = CUBEB_DEVICE_TYPE_OUTPUT;
+ fprintf(stderr, "collection device type changed to OUTPUT\n");
+ } else if (c == 'a') {
+ op->collection_device_type = static_cast<cubeb_device_type>(CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT);
+ fprintf(stderr, "collection device type changed to INPUT | OUTPUT\n");
+ } else if (c == 'k') {
+ op->collection_device_type = CUBEB_DEVICE_TYPE_UNKNOWN;
+ fprintf(stderr, "collection device type changed to UNKNOWN\n");
+ } else if (c == 'r') {
+ bool res = cl.register_device_collection_changed(op->collection_device_type);
+ if (res) {
+ fprintf(stderr, "register_device_collection_changed succeed\n");
+ } else {
+ fprintf(stderr, "register_device_collection_changed failed\n");
+ }
+ } else if (c == 'u') {
+ bool res = cl.unregister_device_collection_changed(op->collection_device_type);
+ if (res) {
+ fprintf(stderr, "unregister_device_collection_changed succeed\n");
+ } else {
+ fprintf(stderr, "unregister_device_collection_changed failed\n");
+ }
+ } else {
+ fprintf(stderr, "Error: '%c' is not a valid entry\n", c);
+ }
+
+ return true; // Loop up
+}
+
+cubeb_devid cubeb_client::select_device(cubeb_device_type type) const
+{
+ assert(type == CUBEB_DEVICE_TYPE_INPUT || type == CUBEB_DEVICE_TYPE_OUTPUT);
+
+ cubeb_device_collection collection;
+ if (cubeb_enumerate_devices(context, type, &collection) ==
+ CUBEB_ERROR_NOT_SUPPORTED) {
+ fprintf(stderr,
+ "Not support %s device selection. Force to use default device\n",
+ device_type_to_string(type));
+ return nullptr;
+ }
+
+ assert(collection.count);
+ fprintf(stderr, "Found %zu %s devices. Choose one:\n", collection.count,
+ device_type_to_string(type));
+
+ std::vector<cubeb_devid> devices;
+ devices.emplace_back(nullptr);
+ fprintf(stderr, "# 0\n\tname: system default device\n");
+ for (size_t i = 0; i < collection.count; i++) {
+ assert(collection.device[i].type == type);
+ fprintf(stderr,
+ "# %zu %s\n"
+ "\tname: %s\n"
+ "\tdevice id: %s\n"
+ "\tmax channels: %u\n"
+ "\tstate: %s\n",
+ devices.size(),
+ collection.device[i].preferred ? " (PREFERRED)" : "",
+ collection.device[i].friendly_name, collection.device[i].device_id,
+ collection.device[i].max_channels,
+ device_state_to_string(collection.device[i].state));
+ devices.emplace_back(collection.device[i].devid);
+ }
+
+ cubeb_device_collection_destroy(context, &collection);
+
+ size_t number;
+ std::cout << "Enter device number: ";
+ std::cin >> number;
+ while (!std::cin || number >= devices.size()) {
+ std::cin.clear();
+ std::cin.ignore(100, '\n');
+ std::cout << "Error: Please enter a valid numeric input. Enter again: ";
+ std::cin >> number;
+ }
+ return devices[number];
+}
+
+int main(int argc, char* argv[]) {
+#ifdef _WIN32
+ CoInitialize(nullptr);
+#endif
+
+ operation_data op;
+ op.pm = PLAYBACK;
+ if (argc > 1) {
+ if ('r' == argv[1][0]) {
+ op.pm = RECORD;
+ } else if ('p' == argv[1][0]) {
+ op.pm = PLAYBACK;
+ } else if ('d' == argv[1][0]) {
+ op.pm = DUPLEX;
+ } else if ('l' == argv[1][0]) {
+ op.pm = LATENCY_TESTING;
+ } else if ('c' == argv[1][0]) {
+ op.pm = COLLECTION_CHANGE;
+ }
+ }
+ op.rate = DEFAULT_RATE;
+ uint32_t latency_override = 0;
+ if (op.pm == LATENCY_TESTING && argc > 2) {
+ latency_override = strtoul(argv[2], NULL, 10);
+ printf("LATENCY_TESTING %d\n", latency_override);
+ } else if (argc > 2) {
+ op.rate = strtoul(argv[2], NULL, 0);
+ }
+
+ bool res = false;
+ cubeb_client cl;
+ cl.activate_log(CUBEB_LOG_NORMAL);
+ fprintf(stderr, "Log level is DISABLED\n");
+ cl.init(/* default backend */);
+
+ op.collection_device_type = CUBEB_DEVICE_TYPE_UNKNOWN;
+ fprintf(stderr, "collection device type is UNKNOWN\n");
+ if (op.pm == COLLECTION_CHANGE) {
+ op.collection_device_type = CUBEB_DEVICE_TYPE_OUTPUT;
+ fprintf(stderr, "collection device type changed to OUTPUT\n");
+ res = cl.register_device_collection_changed(op.collection_device_type);
+ if (res) {
+ fprintf(stderr, "register_device_collection_changed succeed\n");
+ } else {
+ fprintf(stderr, "register_device_collection_changed failed\n");
+ }
+ } else {
+ if (op.pm == PLAYBACK || op.pm == DUPLEX || op.pm == LATENCY_TESTING) {
+ cl.output_device = cl.select_device(CUBEB_DEVICE_TYPE_OUTPUT);
+ cl.output_params = {CUBEB_SAMPLE_FLOAT32NE, op.rate, DEFAULT_OUTPUT_CHANNELS,
+ CUBEB_LAYOUT_STEREO, CUBEB_STREAM_PREF_NONE};
+ }
+ if (op.pm == RECORD || op.pm == DUPLEX || op.pm == LATENCY_TESTING) {
+ cl.input_device = cl.select_device(CUBEB_DEVICE_TYPE_INPUT);
+ cl.input_params = {CUBEB_SAMPLE_FLOAT32NE, op.rate, DEFAULT_INPUT_CHANNELS, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE};
+ }
+ if (op.pm == LATENCY_TESTING) {
+ cl.set_latency_testing(true);
+ if (latency_override) {
+ cl.set_latency_frames(latency_override);
+ }
+ }
+ res = cl.init_stream();
+ if (!res) {
+ fprintf(stderr, "stream_init failed\n");
+ return -1;
+ }
+ fprintf(stderr, "stream_init succeed\n");
+
+ res = cl.start_stream();
+ if (res) {
+ fprintf(stderr, "stream_start succeed\n");
+ } else {
+ fprintf(stderr, "stream_init failed\n");
+ }
+ }
+
+ // User input
+ do {
+ fprintf(stderr, "press `q` to abort or `h` for help\n");
+ } while (choose_action(cl, &op, getchar()));
+
+ cl.destroy();
+
+ return 0;
+}
diff --git a/third_party/rust/cubeb-sys/src/callbacks.rs b/third_party/rust/cubeb-sys/src/callbacks.rs
new file mode 100644
index 0000000000..72e4e84554
--- /dev/null
+++ b/third_party/rust/cubeb-sys/src/callbacks.rs
@@ -0,0 +1,23 @@
+// Copyright © 2017-2018 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+use context::cubeb;
+use std::os::raw::{c_long, c_void};
+use stream::{cubeb_state, cubeb_stream};
+
+pub type cubeb_data_callback = Option<
+ unsafe extern "C" fn(
+ *mut cubeb_stream,
+ *mut c_void,
+ *const c_void,
+ *mut c_void,
+ c_long,
+ ) -> c_long,
+>;
+pub type cubeb_state_callback =
+ Option<unsafe extern "C" fn(*mut cubeb_stream, *mut c_void, cubeb_state)>;
+pub type cubeb_device_changed_callback = Option<unsafe extern "C" fn(*mut c_void)>;
+pub type cubeb_device_collection_changed_callback =
+ Option<unsafe extern "C" fn(*mut cubeb, *mut c_void)>;
diff --git a/third_party/rust/cubeb-sys/src/channel.rs b/third_party/rust/cubeb-sys/src/channel.rs
new file mode 100644
index 0000000000..231d719cdd
--- /dev/null
+++ b/third_party/rust/cubeb-sys/src/channel.rs
@@ -0,0 +1,72 @@
+// Copyright © 2017-2018 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+#[cfg(all(windows, not(target_env = "gnu")))]
+use std::os::raw::c_int as c_enum;
+#[cfg(any(not(windows), all(windows, target_env = "gnu")))]
+use std::os::raw::c_uint as c_enum;
+
+cubeb_enum! {
+ pub enum cubeb_channel : c_enum {
+ CHANNEL_UNKNOWN = 0,
+ CHANNEL_FRONT_LEFT = 1 << 0,
+ CHANNEL_FRONT_RIGHT = 1 << 1,
+ CHANNEL_FRONT_CENTER = 1 << 2,
+ CHANNEL_LOW_FREQUENCY = 1 << 3,
+ CHANNEL_BACK_LEFT = 1 << 4,
+ CHANNEL_BACK_RIGHT = 1 << 5,
+ CHANNEL_FRONT_LEFT_OF_CENTER = 1 << 6,
+ CHANNEL_FRONT_RIGHT_OF_CENTER = 1 << 7,
+ CHANNEL_BACK_CENTER = 1 << 8,
+ CHANNEL_SIDE_LEFT = 1 << 9,
+ CHANNEL_SIDE_RIGHT = 1 << 10,
+ CHANNEL_TOP_CENTER = 1 << 11,
+ CHANNEL_TOP_FRONT_LEFT = 1 << 12,
+ CHANNEL_TOP_FRONT_CENTER = 1 << 13,
+ CHANNEL_TOP_FRONT_RIGHT = 1 << 14,
+ CHANNEL_TOP_BACK_LEFT = 1 << 15,
+ CHANNEL_TOP_BACK_CENTER = 1 << 16,
+ CHANNEL_TOP_BACK_RIGHT = 1 << 17,
+ }
+}
+
+cubeb_enum! {
+ pub enum cubeb_channel_layout : c_enum {
+ CUBEB_LAYOUT_UNDEFINED = 0,
+ CUBEB_LAYOUT_MONO = CHANNEL_FRONT_CENTER,
+ CUBEB_LAYOUT_MONO_LFE = CUBEB_LAYOUT_MONO | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_STEREO = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT,
+ CUBEB_LAYOUT_STEREO_LFE = CUBEB_LAYOUT_STEREO | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER,
+ CUBEB_LAYOUT_3F_LFE = CUBEB_LAYOUT_3F | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_2F1 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_BACK_CENTER,
+ CUBEB_LAYOUT_2F1_LFE = CUBEB_LAYOUT_2F1 | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F1 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_BACK_CENTER,
+ CUBEB_LAYOUT_3F1_LFE = CUBEB_LAYOUT_3F1 | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_2F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT,
+ CUBEB_LAYOUT_2F2_LFE = CUBEB_LAYOUT_2F2 | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_QUAD = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT,
+ CUBEB_LAYOUT_QUAD_LFE = CUBEB_LAYOUT_QUAD | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_SIDE_LEFT |
+ CHANNEL_SIDE_RIGHT,
+ CUBEB_LAYOUT_3F2_LFE = CUBEB_LAYOUT_3F2 | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F2_BACK = CUBEB_LAYOUT_QUAD | CHANNEL_FRONT_CENTER,
+ CUBEB_LAYOUT_3F2_LFE_BACK = CUBEB_LAYOUT_3F2_BACK | CHANNEL_LOW_FREQUENCY,
+ CUBEB_LAYOUT_3F3R_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY |
+ CHANNEL_BACK_CENTER | CHANNEL_SIDE_LEFT |
+ CHANNEL_SIDE_RIGHT,
+ CUBEB_LAYOUT_3F4_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY |
+ CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT |
+ CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT,
+ }
+}
diff --git a/third_party/rust/cubeb-sys/src/context.rs b/third_party/rust/cubeb-sys/src/context.rs
new file mode 100644
index 0000000000..d1932bb390
--- /dev/null
+++ b/third_party/rust/cubeb-sys/src/context.rs
@@ -0,0 +1,45 @@
+// Copyright © 2017-2018 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+use callbacks::{cubeb_data_callback, cubeb_state_callback};
+use device::cubeb_devid;
+use std::os::raw::{c_char, c_int, c_uint, c_void};
+use stream::{cubeb_input_processing_params, cubeb_stream, cubeb_stream_params};
+
+pub enum cubeb {}
+
+extern "C" {
+ pub fn cubeb_init(
+ context: *mut *mut cubeb,
+ context_name: *const c_char,
+ backend_name: *const c_char,
+ ) -> c_int;
+ pub fn cubeb_get_backend_id(context: *mut cubeb) -> *const c_char;
+ pub fn cubeb_get_max_channel_count(context: *mut cubeb, max_channels: *mut c_uint) -> c_int;
+ pub fn cubeb_get_min_latency(
+ context: *mut cubeb,
+ params: *mut cubeb_stream_params,
+ latency_frames: *mut c_uint,
+ ) -> c_int;
+ pub fn cubeb_get_preferred_sample_rate(context: *mut cubeb, rate: *mut c_uint) -> c_int;
+ pub fn cubeb_get_supported_input_processing_params(
+ context: *mut cubeb,
+ params: *mut cubeb_input_processing_params,
+ ) -> c_int;
+ pub fn cubeb_destroy(context: *mut cubeb);
+ pub fn cubeb_stream_init(
+ context: *mut cubeb,
+ stream: *mut *mut cubeb_stream,
+ stream_name: *const c_char,
+ input_device: cubeb_devid,
+ input_stream_params: *mut cubeb_stream_params,
+ output_device: cubeb_devid,
+ output_stream_params: *mut cubeb_stream_params,
+ latency_frames: c_uint,
+ data_callback: cubeb_data_callback,
+ state_callback: cubeb_state_callback,
+ user_ptr: *mut c_void,
+ ) -> c_int;
+}
diff --git a/third_party/rust/cubeb-sys/src/device.rs b/third_party/rust/cubeb-sys/src/device.rs
new file mode 100644
index 0000000000..4672a94ecb
--- /dev/null
+++ b/third_party/rust/cubeb-sys/src/device.rs
@@ -0,0 +1,174 @@
+// Copyright © 2017-2018 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+use callbacks::cubeb_device_collection_changed_callback;
+use context::cubeb;
+use std::os::raw::{c_char, c_int, c_uint, c_void};
+use std::{fmt, mem};
+
+cubeb_enum! {
+ pub enum cubeb_device_fmt {
+ CUBEB_DEVICE_FMT_S16LE = 0x0010,
+ CUBEB_DEVICE_FMT_S16BE = 0x0020,
+ CUBEB_DEVICE_FMT_F32LE = 0x1000,
+ CUBEB_DEVICE_FMT_F32BE = 0x2000,
+ }
+}
+
+#[cfg(target_endian = "big")]
+pub const CUBEB_DEVICE_FMT_S16NE: cubeb_device_fmt = CUBEB_DEVICE_FMT_S16BE;
+#[cfg(target_endian = "big")]
+pub const CUBEB_DEVICE_FMT_F32NE: cubeb_device_fmt = CUBEB_DEVICE_FMT_F32BE;
+#[cfg(target_endian = "little")]
+pub const CUBEB_DEVICE_FMT_S16NE: cubeb_device_fmt = CUBEB_DEVICE_FMT_S16LE;
+#[cfg(target_endian = "little")]
+pub const CUBEB_DEVICE_FMT_F32NE: cubeb_device_fmt = CUBEB_DEVICE_FMT_F32LE;
+
+pub const CUBEB_DEVICE_FMT_S16_MASK: cubeb_device_fmt =
+ CUBEB_DEVICE_FMT_S16LE | CUBEB_DEVICE_FMT_S16BE;
+pub const CUBEB_DEVICE_FMT_F32_MASK: cubeb_device_fmt =
+ CUBEB_DEVICE_FMT_F32LE | CUBEB_DEVICE_FMT_F32BE;
+pub const CUBEB_DEVICE_FMT_ALL: cubeb_device_fmt =
+ CUBEB_DEVICE_FMT_S16_MASK | CUBEB_DEVICE_FMT_F32_MASK;
+
+cubeb_enum! {
+ pub enum cubeb_device_pref {
+ CUBEB_DEVICE_PREF_NONE = 0x00,
+ CUBEB_DEVICE_PREF_MULTIMEDIA = 0x01,
+ CUBEB_DEVICE_PREF_VOICE = 0x02,
+ CUBEB_DEVICE_PREF_NOTIFICATION = 0x04,
+ CUBEB_DEVICE_PREF_ALL = 0x0F,
+ }
+}
+
+cubeb_enum! {
+ pub enum cubeb_device_state {
+ CUBEB_DEVICE_STATE_DISABLED,
+ CUBEB_DEVICE_STATE_UNPLUGGED,
+ CUBEB_DEVICE_STATE_ENABLED,
+ }
+}
+cubeb_enum! {
+ pub enum cubeb_device_type {
+ CUBEB_DEVICE_TYPE_UNKNOWN,
+ CUBEB_DEVICE_TYPE_INPUT,
+ CUBEB_DEVICE_TYPE_OUTPUT,
+ }
+}
+
+pub type cubeb_devid = *const c_void;
+
+#[repr(C)]
+pub struct cubeb_device {
+ pub output_name: *mut c_char,
+ pub input_name: *mut c_char,
+}
+
+// Explicit Debug impl to work around bug in ctest
+impl Default for cubeb_device {
+ fn default() -> Self {
+ unsafe { mem::zeroed() }
+ }
+}
+
+impl fmt::Debug for cubeb_device {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("cubeb_device")
+ .field("output_name", &self.output_name)
+ .field("input_name", &self.input_name)
+ .finish()
+ }
+}
+
+#[repr(C)]
+pub struct cubeb_device_collection {
+ pub device: *mut cubeb_device_info,
+ pub count: usize,
+}
+
+impl Default for cubeb_device_collection {
+ fn default() -> Self {
+ unsafe { mem::zeroed() }
+ }
+}
+
+impl fmt::Debug for cubeb_device_collection {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("cubeb_device_collection")
+ .field("device", &self.device)
+ .field("count", &self.count)
+ .finish()
+ }
+}
+
+#[repr(C)]
+pub struct cubeb_device_info {
+ pub devid: cubeb_devid,
+ pub device_id: *const c_char,
+ pub friendly_name: *const c_char,
+ pub group_id: *const c_char,
+ pub vendor_name: *const c_char,
+
+ pub device_type: cubeb_device_type,
+ pub state: cubeb_device_state,
+ pub preferred: cubeb_device_pref,
+
+ pub format: cubeb_device_fmt,
+ pub default_format: cubeb_device_fmt,
+ pub max_channels: c_uint,
+ pub default_rate: c_uint,
+ pub max_rate: c_uint,
+ pub min_rate: c_uint,
+
+ pub latency_lo: c_uint,
+ pub latency_hi: c_uint,
+}
+
+impl Default for cubeb_device_info {
+ fn default() -> Self {
+ unsafe { mem::zeroed() }
+ }
+}
+
+impl fmt::Debug for cubeb_device_info {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("cubeb_device_info")
+ .field("devid", &self.devid)
+ .field("device_id", &self.device_id)
+ .field("friendly_name", &self.friendly_name)
+ .field("group_id", &self.group_id)
+ .field("vendor_name", &self.vendor_name)
+ .field("device_type", &self.device_type)
+ .field("state", &self.state)
+ .field("preferred", &self.preferred)
+ .field("format", &self.format)
+ .field("default_format", &self.default_format)
+ .field("max_channels", &self.max_channels)
+ .field("default_rate", &self.default_rate)
+ .field("max_rate", &self.max_rate)
+ .field("min_rate", &self.min_rate)
+ .field("latency_lo", &self.latency_lo)
+ .field("latency_hi", &self.latency_hi)
+ .finish()
+ }
+}
+
+extern "C" {
+ pub fn cubeb_enumerate_devices(
+ context: *mut cubeb,
+ devtype: cubeb_device_type,
+ collection: *mut cubeb_device_collection,
+ ) -> c_int;
+ pub fn cubeb_device_collection_destroy(
+ context: *mut cubeb,
+ collection: *mut cubeb_device_collection,
+ ) -> c_int;
+ pub fn cubeb_register_device_collection_changed(
+ context: *mut cubeb,
+ devtype: cubeb_device_type,
+ callback: cubeb_device_collection_changed_callback,
+ user_ptr: *mut c_void,
+ ) -> c_int;
+}
diff --git a/third_party/rust/cubeb-sys/src/error.rs b/third_party/rust/cubeb-sys/src/error.rs
new file mode 100644
index 0000000000..5b93604d20
--- /dev/null
+++ b/third_party/rust/cubeb-sys/src/error.rs
@@ -0,0 +1,13 @@
+// Copyright © 2017-2018 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+use std::os::raw::c_int;
+
+pub const CUBEB_OK: c_int = 0;
+pub const CUBEB_ERROR: c_int = -1;
+pub const CUBEB_ERROR_INVALID_FORMAT: c_int = -2;
+pub const CUBEB_ERROR_INVALID_PARAMETER: c_int = -3;
+pub const CUBEB_ERROR_NOT_SUPPORTED: c_int = -4;
+pub const CUBEB_ERROR_DEVICE_UNAVAILABLE: c_int = -5;
diff --git a/third_party/rust/cubeb-sys/src/format.rs b/third_party/rust/cubeb-sys/src/format.rs
new file mode 100644
index 0000000000..f9ae7d520b
--- /dev/null
+++ b/third_party/rust/cubeb-sys/src/format.rs
@@ -0,0 +1,22 @@
+// Copyright © 2017-2018 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+cubeb_enum! {
+ pub enum cubeb_sample_format {
+ CUBEB_SAMPLE_S16LE,
+ CUBEB_SAMPLE_S16BE,
+ CUBEB_SAMPLE_FLOAT32LE,
+ CUBEB_SAMPLE_FLOAT32BE,
+ }
+}
+
+#[cfg(target_endian = "big")]
+pub const CUBEB_SAMPLE_S16NE: cubeb_sample_format = CUBEB_SAMPLE_S16BE;
+#[cfg(target_endian = "big")]
+pub const CUBEB_SAMPLE_FLOAT32NE: cubeb_sample_format = CUBEB_SAMPLE_FLOAT32BE;
+#[cfg(target_endian = "little")]
+pub const CUBEB_SAMPLE_S16NE: cubeb_sample_format = CUBEB_SAMPLE_S16LE;
+#[cfg(target_endian = "little")]
+pub const CUBEB_SAMPLE_FLOAT32NE: cubeb_sample_format = CUBEB_SAMPLE_FLOAT32LE;
diff --git a/third_party/rust/cubeb-sys/src/internal.rs b/third_party/rust/cubeb-sys/src/internal.rs
new file mode 100644
index 0000000000..92c90a24d0
--- /dev/null
+++ b/third_party/rust/cubeb-sys/src/internal.rs
@@ -0,0 +1,29 @@
+// Copyright © 2017-2018 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+use std::{fmt, mem};
+use std::os::raw::{c_char, c_uint};
+
+#[repr(C)]
+pub struct cubeb_layout_map {
+ name: *const c_char,
+ channels: c_uint,
+ layout: cubeb_channel_layout,
+}
+
+impl Default for cubeb_layout_map {
+ fn default() -> Self { unsafe { mem::zeroed() } }
+}
+
+// Explicit Debug impl to work around bug in ctest
+impl fmt::Debug for cubeb_layout_map {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("cubeb_layout_map")
+ .field("name", &self.name)
+ .field("channels", &self.channels)
+ .field("layout", &self.layout)
+ .finish()
+ }
+}
diff --git a/third_party/rust/cubeb-sys/src/lib.rs b/third_party/rust/cubeb-sys/src/lib.rs
new file mode 100644
index 0000000000..7767910ff6
--- /dev/null
+++ b/third_party/rust/cubeb-sys/src/lib.rs
@@ -0,0 +1,31 @@
+// Copyright © 2017-2018 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+#![allow(non_camel_case_types)]
+
+#[macro_use]
+mod macros;
+
+mod callbacks;
+mod channel;
+mod context;
+mod device;
+mod error;
+mod format;
+mod log;
+mod mixer;
+mod resampler;
+mod stream;
+
+pub use callbacks::*;
+pub use channel::*;
+pub use context::*;
+pub use device::*;
+pub use error::*;
+pub use format::*;
+pub use log::*;
+pub use mixer::*;
+pub use resampler::*;
+pub use stream::*;
diff --git a/third_party/rust/cubeb-sys/src/log.rs b/third_party/rust/cubeb-sys/src/log.rs
new file mode 100644
index 0000000000..d1a1c4e448
--- /dev/null
+++ b/third_party/rust/cubeb-sys/src/log.rs
@@ -0,0 +1,29 @@
+// Copyright © 2017-2018 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+use std::os::raw::{c_char, c_int, c_void};
+
+cubeb_enum! {
+ pub enum cubeb_log_level {
+ CUBEB_LOG_DISABLED = 0,
+ CUBEB_LOG_NORMAL = 1,
+ CUBEB_LOG_VERBOSE = 2,
+ }
+}
+
+pub type cubeb_log_callback = Option<unsafe extern "C" fn(*const c_char, ...)>;
+
+extern "C" {
+ pub fn cubeb_set_log_callback(
+ log_level: cubeb_log_level,
+ log_callback: cubeb_log_callback,
+ ) -> c_int;
+
+ pub fn cubeb_log_get_callback() -> cubeb_log_callback;
+ pub fn cubeb_log_get_level() -> cubeb_log_level;
+
+ pub fn cubeb_async_log_reset_threads(_: c_void);
+ pub fn cubeb_async_log(msg: *const c_char, ...);
+}
diff --git a/third_party/rust/cubeb-sys/src/macros.rs b/third_party/rust/cubeb-sys/src/macros.rs
new file mode 100644
index 0000000000..02f1b7a8af
--- /dev/null
+++ b/third_party/rust/cubeb-sys/src/macros.rs
@@ -0,0 +1,27 @@
+// Copyright © 2017-2018 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+macro_rules! cubeb_enum {
+ (pub enum $name:ident { $($variants:tt)* }) => {
+ #[cfg(target_env = "msvc")]
+ pub type $name = i32;
+ #[cfg(not(target_env = "msvc"))]
+ pub type $name = u32;
+ cubeb_enum!(gen, $name, 0, $($variants)*);
+ };
+ (pub enum $name:ident: $t:ty { $($variants:tt)* }) => {
+ pub type $name = $t;
+ cubeb_enum!(gen, $name, 0, $($variants)*);
+ };
+ (gen, $name:ident, $val:expr, $variant:ident, $($rest:tt)*) => {
+ pub const $variant: $name = $val;
+ cubeb_enum!(gen, $name, $val+1, $($rest)*);
+ };
+ (gen, $name:ident, $val:expr, $variant:ident = $e:expr, $($rest:tt)*) => {
+ pub const $variant: $name = $e;
+ cubeb_enum!(gen, $name, $e+1, $($rest)*);
+ };
+ (gen, $name:ident, $val:expr, ) => {}
+}
diff --git a/third_party/rust/cubeb-sys/src/mixer.rs b/third_party/rust/cubeb-sys/src/mixer.rs
new file mode 100644
index 0000000000..d0452036bf
--- /dev/null
+++ b/third_party/rust/cubeb-sys/src/mixer.rs
@@ -0,0 +1,31 @@
+// Copyright © 2017-2018 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+use channel::cubeb_channel_layout;
+use format::cubeb_sample_format;
+use std::os::raw::{c_int, c_uint, c_void};
+
+pub enum cubeb_mixer {}
+
+extern "C" {
+ pub fn cubeb_mixer_create(
+ format: cubeb_sample_format,
+ in_channels: u32,
+ in_layout: cubeb_channel_layout,
+ out_channels: u32,
+ out_layout: cubeb_channel_layout,
+ ) -> *mut cubeb_mixer;
+ pub fn cubeb_mixer_destroy(mixer: *mut cubeb_mixer);
+ pub fn cubeb_mixer_mix(
+ mixer: *mut cubeb_mixer,
+ frames: usize,
+ input_buffer: *const c_void,
+ input_buffer_length: usize,
+ output_buffer: *mut c_void,
+ output_buffer_length: usize,
+ ) -> c_int;
+
+ pub fn cubeb_channel_layout_nb_channels(channel_layout: cubeb_channel_layout) -> c_uint;
+}
diff --git a/third_party/rust/cubeb-sys/src/resampler.rs b/third_party/rust/cubeb-sys/src/resampler.rs
new file mode 100644
index 0000000000..58cf811ce3
--- /dev/null
+++ b/third_party/rust/cubeb-sys/src/resampler.rs
@@ -0,0 +1,49 @@
+// Copyright © 2017-2018 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+use callbacks::cubeb_data_callback;
+use std::os::raw::{c_long, c_uint, c_void};
+use stream::{cubeb_stream, cubeb_stream_params};
+
+pub enum cubeb_resampler {}
+
+cubeb_enum! {
+ pub enum cubeb_resampler_quality {
+ CUBEB_RESAMPLER_QUALITY_VOIP,
+ CUBEB_RESAMPLER_QUALITY_DEFAULT,
+ CUBEB_RESAMPLER_QUALITY_DESKTOP,
+ }
+}
+
+cubeb_enum! {
+ pub enum cubeb_resampler_reclock {
+ CUBEB_RESAMPLER_RECLOCK_NONE,
+ CUBEB_RESAMPLER_RECLOCK_INPUT,
+ }
+}
+
+extern "C" {
+ pub fn cubeb_resampler_create(
+ stream: *mut cubeb_stream,
+ input_params: *mut cubeb_stream_params,
+ output_params: *mut cubeb_stream_params,
+ target_rate: c_uint,
+ callback: cubeb_data_callback,
+ user_ptr: *mut c_void,
+ quality: cubeb_resampler_quality,
+ reclock: cubeb_resampler_reclock,
+ ) -> *mut cubeb_resampler;
+
+ pub fn cubeb_resampler_fill(
+ resampler: *mut cubeb_resampler,
+ input_buffer: *mut c_void,
+ input_frame_count: *mut c_long,
+ output_buffer: *mut c_void,
+ output_frames_needed: c_long,
+ ) -> c_long;
+
+ pub fn cubeb_resampler_destroy(resampler: *mut cubeb_resampler);
+ pub fn cubeb_resampler_latency(resampler: *mut cubeb_resampler) -> c_long;
+}
diff --git a/third_party/rust/cubeb-sys/src/stream.rs b/third_party/rust/cubeb-sys/src/stream.rs
new file mode 100644
index 0000000000..1ca2ca8e5a
--- /dev/null
+++ b/third_party/rust/cubeb-sys/src/stream.rs
@@ -0,0 +1,100 @@
+// Copyright © 2017-2018 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+use callbacks::cubeb_device_changed_callback;
+use channel::cubeb_channel_layout;
+use device::cubeb_device;
+use format::cubeb_sample_format;
+use std::os::raw::{c_char, c_float, c_int, c_uint, c_void};
+use std::{fmt, mem};
+
+cubeb_enum! {
+ pub enum cubeb_input_processing_params {
+ CUBEB_INPUT_PROCESSING_PARAM_NONE = 0x00,
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION = 0x01,
+ CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION = 0x02,
+ CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL = 0x04,
+ CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION = 0x08,
+ }
+}
+
+cubeb_enum! {
+ pub enum cubeb_stream_prefs {
+ CUBEB_STREAM_PREF_NONE = 0x00,
+ CUBEB_STREAM_PREF_LOOPBACK = 0x01,
+ CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING = 0x02,
+ CUBEB_STREAM_PREF_VOICE = 0x04,
+ }
+}
+
+cubeb_enum! {
+ pub enum cubeb_state {
+ CUBEB_STATE_STARTED,
+ CUBEB_STATE_STOPPED,
+ CUBEB_STATE_DRAINED,
+ CUBEB_STATE_ERROR,
+ }
+}
+
+pub enum cubeb_stream {}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct cubeb_stream_params {
+ pub format: cubeb_sample_format,
+ pub rate: c_uint,
+ pub channels: c_uint,
+ pub layout: cubeb_channel_layout,
+ pub prefs: cubeb_stream_prefs,
+}
+
+impl Default for cubeb_stream_params {
+ fn default() -> Self {
+ unsafe { mem::zeroed() }
+ }
+}
+
+// Explicit Debug impl to work around bug in ctest
+impl fmt::Debug for cubeb_stream_params {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("cubeb_stream_params")
+ .field("format", &self.format)
+ .field("rate", &self.rate)
+ .field("channels", &self.channels)
+ .field("layout", &self.layout)
+ .field("prefs", &self.prefs)
+ .finish()
+ }
+}
+
+extern "C" {
+ pub fn cubeb_stream_destroy(stream: *mut cubeb_stream);
+ pub fn cubeb_stream_start(stream: *mut cubeb_stream) -> c_int;
+ pub fn cubeb_stream_stop(stream: *mut cubeb_stream) -> c_int;
+ pub fn cubeb_stream_get_position(stream: *mut cubeb_stream, position: *mut u64) -> c_int;
+ pub fn cubeb_stream_get_latency(stream: *mut cubeb_stream, latency: *mut c_uint) -> c_int;
+ pub fn cubeb_stream_get_input_latency(stream: *mut cubeb_stream, latency: *mut c_uint)
+ -> c_int;
+ pub fn cubeb_stream_set_volume(stream: *mut cubeb_stream, volume: c_float) -> c_int;
+ pub fn cubeb_stream_set_name(stream: *mut cubeb_stream, name: *const c_char) -> c_int;
+ pub fn cubeb_stream_get_current_device(
+ stream: *mut cubeb_stream,
+ device: *mut *mut cubeb_device,
+ ) -> c_int;
+ pub fn cubeb_stream_set_input_mute(stream: *mut cubeb_stream, mute: c_int) -> c_int;
+ pub fn cubeb_stream_set_input_processing_params(
+ stream: *mut cubeb_stream,
+ params: cubeb_input_processing_params,
+ ) -> c_int;
+ pub fn cubeb_stream_device_destroy(
+ stream: *mut cubeb_stream,
+ devices: *mut cubeb_device,
+ ) -> c_int;
+ pub fn cubeb_stream_register_device_changed_callback(
+ stream: *mut cubeb_stream,
+ device_changed_callback: cubeb_device_changed_callback,
+ ) -> c_int;
+ pub fn cubeb_stream_user_ptr(stream: *mut cubeb_stream) -> *mut c_void;
+}