From 292ea7ad3064de10f00835e4cbdae3d544f09bb0 Mon Sep 17 00:00:00 2001 From: Miriam Zimmerman Date: Mon, 23 Sep 2024 15:38:23 -0400 Subject: [PATCH] Implement ADM using cubeb --- .github/workflows/ringrtc.yml | 14 +- BUILDING.md | 11 + Cargo.lock | 116 ++- acknowledgments/acknowledgments.html | 37 +- acknowledgments/acknowledgments.md | 25 +- acknowledgments/acknowledgments.plist | 29 +- src/rust/Cargo.toml | 8 +- src/rust/src/lib.rs | 2 + src/rust/src/webrtc/audio_device_module.rs | 868 ++++++++++++++++-- .../src/webrtc/audio_device_module_utils.rs | 201 ++++ .../src/webrtc/ffi/audio_device_module.rs | 33 +- 11 files changed, 1226 insertions(+), 118 deletions(-) create mode 100644 src/rust/src/webrtc/audio_device_module_utils.rs diff --git a/.github/workflows/ringrtc.yml b/.github/workflows/ringrtc.yml index ef8a58c..1150798 100644 --- a/.github/workflows/ringrtc.yml +++ b/.github/workflows/ringrtc.yml @@ -26,6 +26,8 @@ jobs: steps: - name: Install protoc run: sudo apt-get update && sudo apt-get install -y protobuf-compiler + - name: Install cmake + run: sudo apt-get update && sudo apt-get install -y cmake - name: Install Python tools run: pip3 install flake8 mypy - uses: actions/checkout@v4 @@ -92,6 +94,8 @@ jobs: steps: - name: Install protoc run: sudo apt-get update && sudo apt-get install -y protobuf-compiler + - name: Install cmake + run: sudo apt-get update && sudo apt-get install -y cmake - uses: actions/checkout@v4 - run: rustup toolchain install $(cat rust-toolchain) --profile minimal - name: Run rust tests @@ -105,12 +109,12 @@ jobs: os: [ubuntu-latest, windows-latest, macos-13] include: - os: ubuntu-latest - install-deps: sudo apt-get update && sudo apt-get install -y protobuf-compiler + install-deps: sudo apt-get update && sudo apt-get install -y protobuf-compiler cmake test-runner: xvfb-run --auto-servernum - os: windows-latest - install-deps: choco install protoc + install-deps: choco install protoc cmake - os: macos-13 - install-deps: brew install protobuf coreutils + install-deps: brew install protobuf coreutils cmake runs-on: ${{ matrix.os }} defaults: run: @@ -140,9 +144,9 @@ jobs: os: [ubuntu-latest, macos-13] include: - os: ubuntu-latest - install-deps: sudo apt-get update && sudo apt-get install -y protobuf-compiler + install-deps: sudo apt-get update && sudo apt-get install -y protobuf-compiler cmake - os: macos-13 - install-deps: brew install protobuf coreutils + install-deps: brew install protobuf coreutils cmake runs-on: ${{ matrix.os }} defaults: run: diff --git a/BUILDING.md b/BUILDING.md index cb95172..4008152 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -26,6 +26,17 @@ Install rustup, the Rust management system: We use a pinned nightly toolchain for official builds, specified by our [rust-toolchain file](https://github.com/signalapp/ringrtc/blob/master/rust-toolchain) ([more information](https://rust-lang.github.io/rustup/overrides.html)). + +### cmake + +For Desktop builds, one of ringrtc's dependencies relies on `cmake` being +available. This can be installed via some package managers, such as: + + brew install cmake # MacOS dev machine + +If it is not available in your system's package manger, see +https://cmake.org/download/. + #### Android Install Rust target support for Android via `rustup`: diff --git a/Cargo.lock b/Cargo.lock index 632db9c..86203c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -352,7 +352,7 @@ version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cexpr", "clang-sys", "itertools", @@ -369,6 +369,12 @@ dependencies = [ "which", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" @@ -749,6 +755,35 @@ dependencies = [ "cipher", ] +[[package]] +name = "cubeb" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46141374032595c0fa92563b6ff21c2fef695c4e932ab1a35d8726d78b35255d" +dependencies = [ + "cubeb-core", +] + +[[package]] +name = "cubeb-core" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9d9d5a70e005de5a7ca350d973822702244af08c0e0720112641a799d8be4c" +dependencies = [ + "bitflags 1.3.2", + "cubeb-sys", +] + +[[package]] +name = "cubeb-sys" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "192cd49cc8485ceb2b9d82ba7ca583af01b9b0a9251f4128550d154b06702c6e" +dependencies = [ + "cmake", + "pkg-config", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -1563,7 +1598,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags", + "bitflags 2.6.0", "libc", ] @@ -2236,6 +2271,8 @@ dependencies = [ "chrono", "clap", "ctr", + "cubeb", + "cubeb-core", "env_logger", "fern", "futures", @@ -2271,6 +2308,7 @@ dependencies = [ "tower", "ureq", "uuid", + "windows 0.58.0", "x25519-dalek", "zkgroup", ] @@ -2302,7 +2340,7 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -2624,7 +2662,7 @@ dependencies = [ "libc", "memchr", "ntapi", - "windows", + "windows 0.57.0", ] [[package]] @@ -3153,6 +3191,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -3168,9 +3216,22 @@ version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" dependencies = [ - "windows-implement", - "windows-interface", - "windows-result", + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings", "windows-targets 0.52.6", ] @@ -3185,6 +3246,17 @@ dependencies = [ "syn 2.0.71", ] +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + [[package]] name = "windows-interface" version = "0.57.0" @@ -3196,6 +3268,17 @@ dependencies = [ "syn 2.0.71", ] +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + [[package]] name = "windows-result" version = "0.1.2" @@ -3205,6 +3288,25 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.45.0" diff --git a/acknowledgments/acknowledgments.html b/acknowledgments/acknowledgments.html index 91c6887..03bb7b7 100644 --- a/acknowledgments/acknowledgments.html +++ b/acknowledgments/acknowledgments.html @@ -45,11 +45,11 @@

Overview of licenses:

@@ -1534,6 +1534,29 @@ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR 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. + + +
  • +

    ISC License

    +

    Used by:

    + +
    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.
     
  • @@ -1709,7 +1732,10 @@ SOFTWARE.

    MIT License

    Used by:

    Copyright (c) 2014 Alex Crichton
     
    @@ -1775,6 +1801,7 @@ DEALINGS IN THE SOFTWARE.
                     

    MIT License

    Used by:

    MIT License
     
    diff --git a/acknowledgments/acknowledgments.md b/acknowledgments/acknowledgments.md
    index 8929437..9be6e12 100644
    --- a/acknowledgments/acknowledgments.md
    +++ b/acknowledgments/acknowledgments.md
    @@ -1453,6 +1453,25 @@ THIS SOFTWARE.
     
     ```
     
    +## cubeb-core 0.14.0, cubeb-sys 0.14.0, cubeb 0.14.0
    +
    +```
    +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.
    +
    +```
    +
     ## windows-sys 0.45.0, windows-sys 0.52.0, windows-targets 0.42.2, windows-targets 0.52.6, windows_aarch64_msvc 0.42.2, windows_aarch64_msvc 0.52.6, windows_x86_64_gnu 0.52.6, windows_x86_64_msvc 0.42.2, windows_x86_64_msvc 0.52.6
     
     ```
    @@ -1601,7 +1620,7 @@ SOFTWARE.
     
     ```
     
    -## cfg-if 1.0.0
    +## cc 1.1.5, cfg-if 1.0.0, cmake 0.1.50, pkg-config 0.3.30
     
     ```
     Copyright (c) 2014 Alex Crichton
    @@ -1663,7 +1682,7 @@ DEALINGS IN THE SOFTWARE.
     
     ```
     
    -## bitflags 2.6.0, log 0.4.22, regex-automata 0.4.7, regex-syntax 0.8.4, regex 1.10.5
    +## bitflags 1.3.2, bitflags 2.6.0, log 0.4.22, regex-automata 0.4.7, regex-syntax 0.8.4, regex 1.10.5
     
     ```
     Copyright (c) 2014 The Rust Project Developers
    @@ -3110,7 +3129,7 @@ SOFTWARE.
     
     ```
     
    -## cesu8 1.1.0, neon 1.0.0, windows-core 0.57.0, windows-implement 0.57.0, windows-interface 0.57.0, windows-result 0.1.2, windows 0.57.0
    +## cesu8 1.1.0, neon 1.0.0, windows-core 0.57.0, windows-core 0.58.0, windows-implement 0.57.0, windows-implement 0.58.0, windows-interface 0.57.0, windows-interface 0.58.0, windows-result 0.1.2, windows-result 0.2.0, windows-strings 0.1.0, windows 0.57.0, windows 0.58.0
     
     ```
     MIT License
    diff --git a/acknowledgments/acknowledgments.plist b/acknowledgments/acknowledgments.plist
    index a7b5b1c..913dd3d 100644
    --- a/acknowledgments/acknowledgments.plist
    +++ b/acknowledgments/acknowledgments.plist
    @@ -1499,6 +1499,29 @@ THIS SOFTWARE.
     			Type
     			PSGroupSpecifier
     		
    +		
    +			FooterText
    +			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.
    +
    +			License
    +			ISC License
    +			Title
    +			cubeb-core 0.14.0, cubeb-sys 0.14.0, cubeb 0.14.0
    +			Type
    +			PSGroupSpecifier
    +		
     		
     			FooterText
     			    MIT License
    @@ -1698,7 +1721,7 @@ DEALINGS IN THE SOFTWARE.
     			License
     			MIT License
     			Title
    -			cfg-if 1.0.0
    +			cc 1.1.5, cfg-if 1.0.0, cmake 0.1.50, pkg-config 0.3.30
     			Type
     			PSGroupSpecifier
     		
    @@ -1768,7 +1791,7 @@ DEALINGS IN THE SOFTWARE.
     			License
     			MIT License
     			Title
    -			bitflags 2.6.0, log 0.4.22, regex-automata 0.4.7, regex-syntax 0.8.4, regex 1.10.5
    +			bitflags 1.3.2, bitflags 2.6.0, log 0.4.22, regex-automata 0.4.7, regex-syntax 0.8.4, regex 1.10.5
     			Type
     			PSGroupSpecifier
     		
    @@ -3391,7 +3414,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRES
     			License
     			MIT License
     			Title
    -			cesu8 1.1.0, neon 1.0.0, windows-core 0.57.0, windows-implement 0.57.0, windows-interface 0.57.0, windows-result 0.1.2, windows 0.57.0
    +			cesu8 1.1.0, neon 1.0.0, windows-core 0.57.0, windows-core 0.58.0, windows-implement 0.57.0, windows-implement 0.58.0, windows-interface 0.57.0, windows-interface 0.58.0, windows-result 0.1.2, windows-result 0.2.0, windows-strings 0.1.0, windows 0.57.0, windows 0.58.0
     			Type
     			PSGroupSpecifier
     		
    diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml
    index 4aa66fa..434dffe 100644
    --- a/src/rust/Cargo.toml
    +++ b/src/rust/Cargo.toml
    @@ -94,6 +94,12 @@ chrono = {version = "0.4.38", optional = true }
     call_protobuf = { path = "../../protobuf", package = "protobuf"}
     mrp = { path = "../../mrp" }
     
    +# Optional, needed by "native" feature
    +cubeb = {  version = "0.14.0", optional = true }
    +cubeb-core = {  version = "0.14.0", optional = true }
    +# Only needed by native feature on windows
    +windows = { version = "0.58.0", optional = true, features = ["Win32_System_Com"] }
    +
     [target.'cfg(not(target_os="android"))'.dependencies]
     # sysinfo depends on getgrgid_r, which was added in Android API level 24
     sysinfo = { version = "0.31.2", default-features = false, features = ["system"] }
    @@ -102,7 +108,7 @@ sysinfo = { version = "0.31.2", default-features = false, features = ["system"]
     default = []
     sim = []
     electron = ["neon", "native"]
    -native = []
    +native = ["cubeb", "cubeb-core", "windows"]
     prebuilt_webrtc = ["native"]
     simnet = ["injectable_network"]
     injectable_network = []
    diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs
    index f407674..09b3a0a 100644
    --- a/src/rust/src/lib.rs
    +++ b/src/rust/src/lib.rs
    @@ -91,6 +91,8 @@ pub mod webrtc {
         pub use arc::Arc;
         #[cfg(all(not(feature = "sim"), feature = "native"))]
         pub mod audio_device_module;
    +    #[cfg(all(not(feature = "sim"), feature = "native"))]
    +    pub mod audio_device_module_utils;
         pub mod field_trial;
         pub mod ice_gatherer;
         #[cfg(feature = "injectable_network")]
    diff --git a/src/rust/src/webrtc/audio_device_module.rs b/src/rust/src/webrtc/audio_device_module.rs
    index 4cde279..fdf5962 100644
    --- a/src/rust/src/webrtc/audio_device_module.rs
    +++ b/src/rust/src/webrtc/audio_device_module.rs
    @@ -4,10 +4,16 @@
     //
     
     use crate::webrtc;
    +use crate::webrtc::audio_device_module_utils::{copy_and_truncate_string, DeviceCollectionWrapper};
     use crate::webrtc::ffi::audio_device_module::RffiAudioTransport;
    -use std::ffi::c_void;
    -use std::os::raw::c_char;
    +use anyhow::anyhow;
    +use cubeb::{Context, DeviceId, DeviceType, MonoFrame, Stream, StreamPrefs};
    +use std::collections::VecDeque;
    +use std::ffi::{c_uchar, c_void, CString};
    +use std::sync::{Arc, Mutex};
     use std::time::Duration;
    +#[cfg(target_os = "windows")]
    +use windows::Win32::System::Com;
     
     // Stays in sync with AudioLayer in webrtc
     #[repr(C)]
    @@ -50,234 +56,905 @@ struct PlayData {
         ntp_time: Option,
     }
     
    +type Frame = MonoFrame;
    +
     pub struct AudioDeviceModule {
    -    audio_transport: webrtc::ptr::Borrowed,
    +    audio_transport: Arc>,
    +    cubeb_ctx: Option,
    +    initialized: bool,
    +    playout_device: Option,
    +    recording_device: Option,
    +    output_stream: Option>,
    +    input_stream: Option>,
    +    playing: bool,
    +    recording: bool,
     }
     
     impl Default for AudioDeviceModule {
         fn default() -> Self {
             Self {
    -            audio_transport: webrtc::ptr::Borrowed::null(),
    +            audio_transport: Arc::new(Mutex::new(RffiAudioTransport {
    +                callback: std::ptr::null(),
    +            })),
    +            cubeb_ctx: None,
    +            initialized: false,
    +            playout_device: None,
    +            recording_device: None,
    +            output_stream: None,
    +            input_stream: None,
    +            playing: false,
    +            recording: false,
    +        }
    +    }
    +}
    +
    +impl Drop for AudioDeviceModule {
    +    // Clean up in case the application exits without properly calling terminate().
    +    fn drop(&mut self) {
    +        if self.initialized {
    +            let out = self.terminate();
    +            if out != 0 {
    +                error!("Failed to terminate: {}", out);
    +            }
    +        }
    +    }
    +}
    +
    +// Maximum lengths (and allocated amount of memory) for device names and GUIDs.
    +const ADM_MAX_DEVICE_NAME_SIZE: usize = 128;
    +const ADM_MAX_GUID_SIZE: usize = 128;
    +
    +/// Arbitrary string to uniquely identify ringrtc for creating the cubeb object.
    +const ADM_CONTEXT: &str = "ringrtc";
    +
    +const SAMPLE_FREQUENCY: u32 = 48_000;
    +
    +// Windows hack: Need sample latency to be at most 22ms
    +#[cfg(target_os = "windows")]
    +const SAMPLE_LATENCY: u32 = SAMPLE_FREQUENCY / 10;
    +#[cfg(not(target_os = "windows"))]
    +const SAMPLE_LATENCY: u32 = SAMPLE_FREQUENCY / 100;
    +
    +// WebRTC always expects to provide 10ms of samples at a time.
    +const WEBRTC_WINDOW: usize = SAMPLE_FREQUENCY as usize / 100;
    +
    +const STREAM_FORMAT: cubeb::SampleFormat = cubeb::SampleFormat::S16NE;
    +const NUM_CHANNELS: u32 = 1;
    +
    +fn write_to_null_or_valid_pointer(ptr: webrtc::ptr::Borrowed, v: T) -> anyhow::Result<()> {
    +    // Safety: As long as the C code passes a valid or null pointer, this is safe.
    +    unsafe {
    +        match ptr.as_mut() {
    +            Some(p) => {
    +                *p = v;
    +                Ok(())
    +            }
    +            None => Err(anyhow!("null pointer")),
             }
         }
     }
     
     impl AudioDeviceModule {
         pub fn new() -> Self {
    -        Self {
    -            audio_transport: webrtc::ptr::Borrowed::null(),
    -        }
    +        Self::default()
         }
     
         pub fn active_audio_layer(&self, _audio_layer: webrtc::ptr::Borrowed) -> i32 {
             -1
         }
     
    -    pub fn register_audio_callback(
    -        &mut self,
    -        audio_transport: webrtc::ptr::Borrowed,
    -    ) -> i32 {
    +    pub fn register_audio_callback(&mut self, audio_transport: *const c_void) -> i32 {
             // It is unsafe to change this callback while playing or recording, as
             // the change might then race with invocations of the callback, which
             // need not be serialized.
             if self.playing() || self.recording() {
                 return -1;
             }
    -        self.audio_transport = audio_transport;
    +        self.audio_transport = std::sync::Arc::new(Mutex::new(RffiAudioTransport {
    +            callback: audio_transport,
    +        }));
             0
         }
     
         // Main initialization and termination
    -    pub fn init(&self) -> i32 {
    -        -1
    +    pub fn init(&mut self) -> i32 {
    +        // Don't bother re-initializing.
    +        if self.initialized {
    +            return 0;
    +        }
    +        #[cfg(target_os = "windows")]
    +        {
    +            // Safety: calling with valid parameters.
    +            let res = unsafe {
    +                Com::CoInitializeEx(
    +                    None,
    +                    Com::COINIT_MULTITHREADED | Com::COINIT_DISABLE_OLE1DDE,
    +                )
    +            };
    +            if res.is_err() {
    +                error!("Failed to initialize COM: {}", res);
    +                return -1;
    +            }
    +        }
    +        let ctx_name = CString::new(ADM_CONTEXT).unwrap();
    +        match Context::init(Some(ctx_name.as_c_str()), None) {
    +            Ok(ctx) => {
    +                self.cubeb_ctx = Some(ctx);
    +                self.initialized = true;
    +                0
    +            }
    +            Err(e) => {
    +                error!("Failed to initialize: {}", e);
    +                -1
    +            }
    +        }
         }
    -    pub fn terminate(&self) -> i32 {
    -        -1
    +
    +    pub fn terminate(&mut self) -> i32 {
    +        if self.recording {
    +            self.stop_recording();
    +        }
    +        if self.playing {
    +            self.stop_playout();
    +        }
    +        // Cause these to Drop
    +        self.input_stream = None;
    +        self.output_stream = None;
    +        self.cubeb_ctx = None;
    +        self.initialized = false;
    +        #[cfg(target_os = "windows")]
    +        {
    +            // Safety: No parameters, was already initialized.
    +            unsafe {
    +                Com::CoUninitialize();
    +            };
    +        }
    +        0
         }
    +
         pub fn initialized(&self) -> bool {
    -        false
    +        self.initialized
    +    }
    +
    +    fn enumerate_devices(
    +        &self,
    +        device_type: DeviceType,
    +    ) -> anyhow::Result {
    +        match &self.cubeb_ctx {
    +            Some(ctx) => Ok(DeviceCollectionWrapper::new(
    +                ctx.enumerate_devices(device_type)?,
    +            )),
    +            None => Err(anyhow!("Cannot enumerate devices without a cubeb ctx"))?,
    +        }
    +    }
    +
    +    fn device_str(device: &cubeb::DeviceInfo) -> String {
    +        // Only print friendly name in debug builds.
    +        #[cfg(debug_assertions)]
    +        let friendly_name = device.friendly_name();
    +        #[cfg(not(debug_assertions))]
    +        let friendly_name: Option<&str> = None;
    +        format!(
    +            concat!("dev id: {:?}, device_id: {:?}, friendly_name: {:?}, group_id: {:?}, ",
    +            "vendor_name: {:?}, device_type: {:?}, state: {:?}, preferred: {:?}, format: {:?}, ",
    +            "default_format: {:?}, max channels: {:?}, default_rate: {:?}, max_rate: {:?}, ",
    +            "min_rate: {:?}, latency_lo: {:?}, latency_hi: {:?})"),
    +            device.devid(),
    +            device.device_id(),
    +            friendly_name,
    +            device.group_id(),
    +            device.vendor_name(),
    +            device.device_type(),
    +            device.state(),
    +            device.preferred(),
    +            device.format(),
    +            device.default_format(),
    +            device.max_channels(),
    +            device.default_rate(),
    +            device.max_rate(),
    +            device.min_rate(),
    +            device.latency_lo(),
    +            device.latency_hi()
    +        )
         }
     
         // Device enumeration
         pub fn playout_devices(&self) -> i16 {
    -        -1
    +        match self.enumerate_devices(DeviceType::OUTPUT) {
    +            Ok(device_collection) => device_collection.count().try_into().unwrap_or(-1),
    +            Err(e) => {
    +                error!("Failed to get playout devices: {}", e);
    +                -1
    +            }
    +        }
         }
    +
         pub fn recording_devices(&self) -> i16 {
    -        -1
    +        match self.enumerate_devices(DeviceType::INPUT) {
    +            Ok(device_collection) => device_collection.count().try_into().unwrap_or(-1),
    +            Err(e) => {
    +                error!("Failed to get recording devices: {}", e);
    +                -1
    +            }
    +        }
         }
    +
    +    fn copy_name_and_id(
    +        index: u16,
    +        devices: DeviceCollectionWrapper,
    +        name_out: webrtc::ptr::Borrowed,
    +        guid_out: webrtc::ptr::Borrowed,
    +    ) -> anyhow::Result<()> {
    +        if let Some(d) = devices.get(index.into()) {
    +            if let Some(name) = d.friendly_name() {
    +                let mut name_copy = name.to_string();
    +                // TODO(mutexlox): Localize these strings.
    +                #[cfg(not(target_os = "windows"))]
    +                if index == 0 {
    +                    name_copy = format!("default ({})", name);
    +                }
    +                #[cfg(target_os = "windows")]
    +                {
    +                    if index == 0 {
    +                        name_copy = format!("Default - {}", name);
    +                    } else if index == 1 {
    +                        name_copy = format!("Communication - {}", name);
    +                    }
    +                }
    +                copy_and_truncate_string(&name_copy, name_out, ADM_MAX_DEVICE_NAME_SIZE)?;
    +            } else {
    +                return Err(anyhow!("Could not get device name"));
    +            }
    +            if let Some(id) = d.device_id() {
    +                copy_and_truncate_string(id, guid_out, ADM_MAX_GUID_SIZE)?;
    +            } else {
    +                return Err(anyhow!("Could not get device ID"));
    +            }
    +            Ok(())
    +        } else {
    +            Err(anyhow!(
    +                "Could not get device at index {} (len {})",
    +                index,
    +                devices.count()
    +            ))
    +        }
    +    }
    +
         pub fn playout_device_name(
             &self,
    -        _index: u16,
    -        _name: webrtc::ptr::Borrowed,
    -        _guid: webrtc::ptr::Borrowed,
    +        index: u16,
    +        name_out: webrtc::ptr::Borrowed,
    +        guid_out: webrtc::ptr::Borrowed,
         ) -> i32 {
    -        -1
    +        match self.enumerate_devices(DeviceType::OUTPUT) {
    +            Ok(devices) => {
    +                match AudioDeviceModule::copy_name_and_id(index, devices, name_out, guid_out) {
    +                    Ok(_) => 0,
    +                    Err(e) => {
    +                        error!("Failed to copy name and ID for playout device: {}", e);
    +                        -1
    +                    }
    +                }
    +            }
    +            Err(e) => {
    +                error!("Failed to enumerate devices for playout device: {}", e);
    +                -1
    +            }
    +        }
         }
    +
         pub fn recording_device_name(
             &self,
    -        _index: u16,
    -        _name: webrtc::ptr::Borrowed,
    -        _guid: webrtc::ptr::Borrowed,
    +        index: u16,
    +        name_out: webrtc::ptr::Borrowed,
    +        guid_out: webrtc::ptr::Borrowed,
         ) -> i32 {
    -        -1
    +        match self.enumerate_devices(DeviceType::INPUT) {
    +            Ok(devices) => {
    +                match AudioDeviceModule::copy_name_and_id(index, devices, name_out, guid_out) {
    +                    Ok(_) => 0,
    +                    Err(e) => {
    +                        error!("Failed to copy name and ID for recording device: {}", e);
    +                        -1
    +                    }
    +                }
    +            }
    +            Err(e) => {
    +                error!("Failed to enumerate devices for recording device: {}", e);
    +                -1
    +            }
    +        }
         }
     
         // Device selection
    -    pub fn set_playout_device(&self, _index: u16) -> i32 {
    -        -1
    -    }
    -    pub fn set_playout_device_win(&self, _device: WindowsDeviceType) -> i32 {
    -        -1
    +    pub fn set_playout_device(&mut self, index: u16) -> i32 {
    +        let device = match self.enumerate_devices(DeviceType::OUTPUT) {
    +            Ok(devices) => {
    +                for device in devices.iter() {
    +                    info!(
    +                        "Playout device: ({})",
    +                        AudioDeviceModule::device_str(device)
    +                    );
    +                }
    +
    +                match devices.get(index as usize) {
    +                    Some(device) => device.devid(),
    +                    None => {
    +                        error!(
    +                            "Invalid device index {} requested (len {})",
    +                            index,
    +                            devices.count()
    +                        );
    +                        return -1;
    +                    }
    +                }
    +            }
    +            Err(e) => {
    +                error!("failed to enumerate devices for playout device: {}", e);
    +                return -1;
    +            }
    +        };
    +        self.playout_device = Some(device);
    +        0
         }
     
    -    pub fn set_recording_device(&self, _index: u16) -> i32 {
    -        -1
    +    pub fn set_playout_device_win(&mut self, device: WindowsDeviceType) -> i32 {
    +        // DefaultDevice is at index 0 and DefaultCommunicationDevice at index 1
    +        self.set_playout_device(if device == WindowsDeviceType::DefaultDevice {
    +            0
    +        } else {
    +            1
    +        })
         }
    -    pub fn set_recording_device_win(&self, _device: WindowsDeviceType) -> i32 {
    -        -1
    +
    +    pub fn set_recording_device(&mut self, index: u16) -> i32 {
    +        let device = match self.enumerate_devices(DeviceType::INPUT) {
    +            Ok(devices) => {
    +                for device in devices.iter() {
    +                    info!(
    +                        "Recording device: ({})",
    +                        AudioDeviceModule::device_str(device)
    +                    );
    +                }
    +                match devices.get(index as usize) {
    +                    Some(device) => device.devid(),
    +                    None => {
    +                        error!(
    +                            "Invalid device index {} requested (len {})",
    +                            index,
    +                            devices.count()
    +                        );
    +                        return -1;
    +                    }
    +                }
    +            }
    +            Err(e) => {
    +                error!("failed to enumerate devices for playout device: {}", e);
    +                return -1;
    +            }
    +        };
    +        self.recording_device = Some(device);
    +        0
    +    }
    +
    +    pub fn set_recording_device_win(&mut self, device: WindowsDeviceType) -> i32 {
    +        // DefaultDevice is at index 0 and DefaultCommunicationDevice at index 1
    +        self.set_recording_device(if device == WindowsDeviceType::DefaultDevice {
    +            0
    +        } else {
    +            1
    +        })
         }
     
         // Audio transport initialization
    -    pub fn playout_is_available(&self, _available: webrtc::ptr::Borrowed) -> i32 {
    -        -1
    +    pub fn playout_is_available(&self, available_out: webrtc::ptr::Borrowed) -> i32 {
    +        let available = self.initialized && self.playout_device.is_some();
    +        match write_to_null_or_valid_pointer(available_out, available) {
    +            Ok(_) => 0,
    +            Err(e) => {
    +                error!("writing playout available state: {:?}", e);
    +                -1
    +            }
    +        }
         }
    -    pub fn init_playout(&self) -> i32 {
    -        -1
    +
    +    pub fn init_playout(&mut self) -> i32 {
    +        if !self.initialized {
    +            error!("Tried to init playout without initializing ADM");
    +            return -1;
    +        }
    +        let out_device = if let Some(device) = self.playout_device {
    +            device
    +        } else {
    +            error!("Tried to init playout without a playout device");
    +            return -1;
    +        };
    +        let ctx = if let Some(c) = &self.cubeb_ctx {
    +            c
    +        } else {
    +            error!("Tried to init playout without a ctx");
    +            return -1;
    +        };
    +        let params = cubeb::StreamParamsBuilder::new()
    +            .format(STREAM_FORMAT)
    +            .rate(SAMPLE_FREQUENCY)
    +            .channels(NUM_CHANNELS)
    +            .layout(cubeb::ChannelLayout::MONO)
    +            .prefs(StreamPrefs::VOICE)
    +            .take();
    +        let mut builder = cubeb::StreamBuilder::::new();
    +        let transport = Arc::clone(&self.audio_transport);
    +        // WebRTC can only report data in WEBRTC_WINDOW-sized chunks.
    +        // This buffer tracks any extra data that would not fit in `output`,
    +        // if `output.len()` is not an exact multiple of WEBRTC_WINDOW.
    +        let mut buffer = VecDeque::::new();
    +        buffer.reserve(WEBRTC_WINDOW);
    +        builder
    +            .name("ringrtc output")
    +            .output(out_device, ¶ms)
    +            // TODO(mutexlox): Clamp this to latency_lo and latency_hi, if those are nonzero
    +            .latency(SAMPLE_LATENCY)
    +            .data_callback(move |_, output| {
    +                if output.is_empty() {
    +                    return 0;
    +                }
    +
    +                // WebRTC cannot give data in anything other than 10ms chunks, so request
    +                // these.
    +                // If the data callback is invoked with an `output` length that is
    +                // not a multiple of WEBRTC_WINDOW, make one "extra" call to webrtc and
    +                // store "extra" data in `buffer`.
    +
    +                // First, copy any leftover data from prior invocations.
    +                let mut written = 0;
    +                while let Some(data) = buffer.pop_front() {
    +                    output[written] = Frame { m: data };
    +                    written += 1;
    +                    if written >= output.len() {
    +                        // Short-circuit; we already have enough data.
    +                        return output.len() as isize;
    +                    }
    +                }
    +
    +                // Then, request more data from WebRTC.
    +                while written < output.len() {
    +                    let play_data = AudioDeviceModule::need_more_play_data(
    +                        Arc::clone(&transport),
    +                        WEBRTC_WINDOW,
    +                        NUM_CHANNELS,
    +                        SAMPLE_FREQUENCY,
    +                    );
    +                    if play_data.success < 0 {
    +                        // C function failed; propagate error and don't continue.
    +                        return play_data.success as isize;
    +                    } else if play_data.data.len() > WEBRTC_WINDOW {
    +                        error!("need_more_play_data returned too much data");
    +                        return -1;
    +                    }
    +                    // Put data into the right format and add it to the output
    +                    // array for cubeb to play.
    +                    // If there's more data than was requested, add it to the
    +                    // buffer for the next invocation of the callback.
    +                    for data in play_data.data.iter() {
    +                        if written < output.len() {
    +                            output[written] = Frame { m: *data };
    +                            written += 1;
    +                        } else {
    +                            buffer.push_back(*data);
    +                        }
    +                    }
    +                }
    +
    +                if written != output.len() {
    +                    error!(
    +                        "Got wrong amount of output data (want {} got {}), may drain.",
    +                        output.len(),
    +                        written
    +                    );
    +                }
    +                written as isize
    +            })
    +            .state_callback(|state| {
    +                warn!("Playout state: {:?}", state);
    +            });
    +        match builder.init(ctx) {
    +            Ok(stream) => {
    +                self.output_stream = Some(stream);
    +                0
    +            }
    +            Err(e) => {
    +                error!("Couldn't initialize output stream: {}", e);
    +                -1
    +            }
    +        }
         }
    +
         pub fn playout_is_initialized(&self) -> bool {
    -        false
    +        self.output_stream.is_some()
         }
    -    pub fn recording_is_available(&self, _available: webrtc::ptr::Borrowed) -> i32 {
    -        -1
    +
    +    pub fn recording_is_available(&self, available_out: webrtc::ptr::Borrowed) -> i32 {
    +        let available = self.initialized && self.recording_device.is_some();
    +        match write_to_null_or_valid_pointer(available_out, available) {
    +            Ok(_) => 0,
    +            Err(e) => {
    +                error!("writing recording available state: {:?}", e);
    +                -1
    +            }
    +        }
         }
    -    pub fn init_recording(&self) -> i32 {
    -        -1
    +
    +    pub fn init_recording(&mut self) -> i32 {
    +        if !self.initialized {
    +            error!("Tried to init recording without initializing ADM");
    +            return -1;
    +        }
    +        let recording_device = if let Some(device) = self.recording_device {
    +            device
    +        } else {
    +            error!("Tried to init recording without a recording device");
    +            return -1;
    +        };
    +        let ctx = if let Some(c) = &self.cubeb_ctx {
    +            c
    +        } else {
    +            error!("Tried to init recording without a ctx");
    +            return -1;
    +        };
    +        let params = cubeb::StreamParamsBuilder::new()
    +            .format(STREAM_FORMAT)
    +            .rate(SAMPLE_FREQUENCY)
    +            .channels(NUM_CHANNELS)
    +            .layout(cubeb::ChannelLayout::MONO)
    +            .prefs(StreamPrefs::VOICE)
    +            .take();
    +        let mut builder = cubeb::StreamBuilder::::new();
    +        let transport = Arc::clone(&self.audio_transport);
    +        // WebRTC can only accept data in WEBRTC_WINDOW-sized chunks.
    +        // This buffer tracks any extra data that would not fit in a call to WebRTC,
    +        // if `input.len()` is not an exact multiple of WEBRTC_WINDOW.
    +        let mut buffer = VecDeque::::new();
    +        buffer.reserve(WEBRTC_WINDOW);
    +        builder
    +            .name("ringrtc input")
    +            .input(recording_device, ¶ms)
    +            // TODO(mutexlox): Clamp this to latency_lo and latency_hi, if those are nonzero
    +            .latency(SAMPLE_LATENCY)
    +            .data_callback(move |input, _| {
    +                // First add data from prior call(s).
    +                let data = buffer
    +                    .drain(0..)
    +                    .chain(input.iter().map(|f| f.m))
    +                    .collect::>();
    +                // WebRTC cannot accept data in anything other than 10ms chunks, so report in these.
    +                // Buffer any excess data beyond a multiple of WEBRTC_WINDOW for a subsequent
    +                // callback invocation.
    +                let input_chunks = data.chunks(WEBRTC_WINDOW);
    +                for chunk in input_chunks {
    +                    if chunk.len() < WEBRTC_WINDOW {
    +                        // Do not try to invoke WebRTC with a too-short chunk.
    +                        buffer.extend(chunk);
    +                        break;
    +                    }
    +                    let (ret, _new_mic_level) = AudioDeviceModule::recorded_data_is_available(
    +                        Arc::clone(&transport),
    +                        chunk.to_vec(),
    +                        NUM_CHANNELS,
    +                        SAMPLE_FREQUENCY,
    +                        // TODO(mutexlox): do we need different values here?
    +                        Duration::new(0, 0),
    +                        0,
    +                        0,
    +                        false,
    +                        None,
    +                    );
    +                    if ret < 0 {
    +                        error!("Failed to report recorded data: {}", ret);
    +                        return ret as isize;
    +                    }
    +                }
    +                input.len() as isize
    +            })
    +            .state_callback(|state| {
    +                warn!("recording state: {:?}", state);
    +            });
    +        match builder.init(ctx) {
    +            Ok(stream) => {
    +                match ctx.supported_input_processing_params() {
    +                    Ok(params) => {
    +                        info!("Available input processing params: {:?}", params);
    +                    }
    +                    Err(e) => warn!("Failed to get supported input processing parameters; proceeding without: {}", e)
    +                }
    +                self.input_stream = Some(stream);
    +                0
    +            }
    +            Err(e) => {
    +                error!("Couldn't initialize input stream: {}", e);
    +                -1
    +            }
    +        }
         }
    +
         pub fn recording_is_initialized(&self) -> bool {
    -        false
    +        self.input_stream.is_some()
         }
     
         // Audio transport control
    -    pub fn start_playout(&self) -> i32 {
    -        -1
    +    pub fn start_playout(&mut self) -> i32 {
    +        if let Some(output_stream) = &self.output_stream {
    +            if let Err(e) = output_stream.start() {
    +                error!("Failed to start playout: {}", e);
    +                return -1;
    +            }
    +            self.playing = true;
    +            0
    +        } else {
    +            error!("Cannot start playout without an output stream -- did you forget init_playout?");
    +            -1
    +        }
         }
    -    pub fn stop_playout(&self) -> i32 {
    -        -1
    +
    +    pub fn stop_playout(&mut self) -> i32 {
    +        if let Some(output_stream) = &self.output_stream {
    +            if let Err(e) = output_stream.stop() {
    +                error!("Failed to stop playout: {}", e);
    +                return -1;
    +            }
    +            // Drop the stream so that it isn't reused on future calls.
    +            self.output_stream = None;
    +            self.playing = false;
    +        }
    +        0
         }
    +
         pub fn playing(&self) -> bool {
    -        false
    +        self.playing
         }
    -    pub fn start_recording(&self) -> i32 {
    -        -1
    +
    +    pub fn start_recording(&mut self) -> i32 {
    +        if let Some(input_stream) = &self.input_stream {
    +            if let Err(e) = input_stream.start() {
    +                error!("Failed to start recording: {}", e);
    +                return -1;
    +            }
    +            self.recording = true;
    +            0
    +        } else {
    +            error!(
    +                "Cannot start recording without an input stream -- did you forget init_recording?"
    +            );
    +            -1
    +        }
         }
    -    pub fn stop_recording(&self) -> i32 {
    -        -1
    +
    +    pub fn stop_recording(&mut self) -> i32 {
    +        if let Some(input_stream) = &self.input_stream {
    +            if let Err(e) = input_stream.stop() {
    +                error!("Failed to stop recording: {}", e);
    +                return -1;
    +            }
    +            // Drop the stream so that it isn't reused on future calls.
    +            self.input_stream = None;
    +            self.recording = false;
    +        }
    +        0
         }
    +
         pub fn recording(&self) -> bool {
    -        false
    +        self.recording
         }
     
         // Audio mixer initialization
         pub fn init_speaker(&self) -> i32 {
    -        -1
    +        if self.initialized {
    +            0
    +        } else {
    +            -1
    +        }
         }
    +
         pub fn speaker_is_initialized(&self) -> bool {
    -        false
    +        self.initialized
         }
    +
         pub fn init_microphone(&self) -> i32 {
    -        -1
    +        if self.initialized {
    +            0
    +        } else {
    +            -1
    +        }
         }
    +
         pub fn microphone_is_initialized(&self) -> bool {
    -        false
    +        self.initialized
         }
     
         // Speaker volume controls
    -    pub fn speaker_volume_is_available(&self, _available: webrtc::ptr::Borrowed) -> i32 {
    -        -1
    +    pub fn speaker_volume_is_available(&self, available: webrtc::ptr::Borrowed) -> i32 {
    +        if !self.initialized {
    +            return -1;
    +        }
    +        match write_to_null_or_valid_pointer(available, false) {
    +            Ok(_) => 0,
    +            Err(e) => {
    +                error!("writing speaker volume status: {:?}", e);
    +                -1
    +            }
    +        }
         }
    +
    +    // This implementation doesn't support overriding speaker volume.
         pub fn set_speaker_volume(&self, _volume: u32) -> i32 {
             -1
         }
    +
         pub fn speaker_volume(&self, _volume: webrtc::ptr::Borrowed) -> i32 {
             -1
         }
    +
         pub fn max_speaker_volume(&self, _max_volume: webrtc::ptr::Borrowed) -> i32 {
             -1
         }
    +
         pub fn min_speaker_volume(&self, _min_volume: webrtc::ptr::Borrowed) -> i32 {
             -1
         }
     
         // Microphone volume controls
    -    pub fn microphone_volume_is_available(&self, _available: webrtc::ptr::Borrowed) -> i32 {
    -        -1
    +    pub fn microphone_volume_is_available(&self, available: webrtc::ptr::Borrowed) -> i32 {
    +        if !self.initialized {
    +            return -1;
    +        }
    +        match write_to_null_or_valid_pointer(available, false) {
    +            Ok(_) => 0,
    +            Err(e) => {
    +                error!("writing microphone volume status: {:?}", e);
    +                -1
    +            }
    +        }
         }
    +
    +    // This implementation doesn't support setting microphone volume.
         pub fn set_microphone_volume(&self, _volume: u32) -> i32 {
             -1
         }
    +
         pub fn microphone_volume(&self, _volume: webrtc::ptr::Borrowed) -> i32 {
             -1
         }
    +
         pub fn max_microphone_volume(&self, _max_volume: webrtc::ptr::Borrowed) -> i32 {
             -1
         }
    +
         pub fn min_microphone_volume(&self, _min_volume: webrtc::ptr::Borrowed) -> i32 {
             -1
         }
     
         // Speaker mute control
    -    pub fn speaker_mute_is_available(&self, _available: webrtc::ptr::Borrowed) -> i32 {
    -        -1
    +    pub fn speaker_mute_is_available(&self, available: webrtc::ptr::Borrowed) -> i32 {
    +        if !self.initialized {
    +            return -1;
    +        }
    +        match write_to_null_or_valid_pointer(available, false) {
    +            Ok(_) => 0,
    +            Err(e) => {
    +                error!("writing speaker mute status: {:?}", e);
    +                -1
    +            }
    +        }
         }
    +
    +    // This implementation doesn't support speaker mute in this way
         pub fn set_speaker_mute(&self, _enable: bool) -> i32 {
             -1
         }
    +
         pub fn speaker_mute(&self, _enabled: webrtc::ptr::Borrowed) -> i32 {
             -1
         }
     
         // Microphone mute control
    -    pub fn microphone_mute_is_available(&self, _available: webrtc::ptr::Borrowed) -> i32 {
    -        -1
    +    pub fn microphone_mute_is_available(&self, available: webrtc::ptr::Borrowed) -> i32 {
    +        if !self.initialized {
    +            return -1;
    +        }
    +        match write_to_null_or_valid_pointer(available, false) {
    +            Ok(_) => 0,
    +            Err(e) => {
    +                error!("writing microphone mute status: {:?}", e);
    +                -1
    +            }
    +        }
         }
    +
         pub fn set_microphone_mute(&self, _enable: bool) -> i32 {
             -1
         }
    +
         pub fn microphone_mute(&self, _enabled: webrtc::ptr::Borrowed) -> i32 {
             -1
         }
     
         // Stereo support
    -    pub fn stereo_playout_is_available(&self, _available: webrtc::ptr::Borrowed) -> i32 {
    -        -1
    +    pub fn stereo_playout_is_available(&self, available: webrtc::ptr::Borrowed) -> i32 {
    +        if !self.initialized {
    +            return -1;
    +        }
    +        match write_to_null_or_valid_pointer(available, false) {
    +            Ok(_) => 0,
    +            Err(e) => {
    +                error!("writing stereo playout status: {:?}", e);
    +                -1
    +            }
    +        }
         }
    +
    +    // This implementation only supports mono playout
         pub fn set_stereo_playout(&self, _enable: bool) -> i32 {
             -1
         }
    +
         pub fn stereo_playout(&self, _enabled: webrtc::ptr::Borrowed) -> i32 {
             -1
         }
    -    pub fn stereo_recording_is_available(&self, _available: webrtc::ptr::Borrowed) -> i32 {
    -        -1
    +
    +    pub fn stereo_recording_is_available(&self, available: webrtc::ptr::Borrowed) -> i32 {
    +        if !self.initialized {
    +            return -1;
    +        }
    +        match write_to_null_or_valid_pointer(available, false) {
    +            Ok(_) => 0,
    +            Err(e) => {
    +                error!("writing stereo recording status: {:?}", e);
    +                -1
    +            }
    +        }
         }
    +
    +    // This implementation only supports mono recording.
         pub fn set_stereo_recording(&self, _enable: bool) -> i32 {
             -1
         }
    +
         pub fn stereo_recording(&self, _enabled: webrtc::ptr::Borrowed) -> i32 {
             -1
         }
     
    -    // Playout delay
    -    pub fn playout_delay(&self, _delay_ms: webrtc::ptr::Borrowed) -> i32 {
    -        -1
    +    pub fn playout_delay(&self, delay_ms: webrtc::ptr::Borrowed) -> i32 {
    +        match &self.output_stream {
    +            Some(output_stream) => {
    +                let latency_samples = output_stream.latency();
    +                match latency_samples {
    +                    Ok(latency_samples) => {
    +                        let latency_ms = latency_samples / (SAMPLE_FREQUENCY / 1000);
    +                        match write_to_null_or_valid_pointer(delay_ms, latency_ms as u16) {
    +                            Ok(_) => 0,
    +                            Err(e) => {
    +                                error!("writing delay: {:?}", e);
    +                                -1
    +                            }
    +                        }
    +                    }
    +                    Err(e) => {
    +                        error!("Failed to get latency: {}", e);
    +                        -1
    +                    }
    +                }
    +            }
    +            None => {
    +                error!("Cannot get playout delay with no stream");
    +                -1
    +            }
    +        }
         }
     
         #[allow(clippy::too_many_arguments)]
    -    #[allow(dead_code)]
         fn recorded_data_is_available(
    -        &self,
    +        rffi_audio_transport: Arc>,
             samples: Vec,
    -        channels: usize,
    +        channels: u32,
             samples_per_sec: u32,
             total_delay: Duration,
             clock_drift: i32,
    @@ -288,6 +965,13 @@ impl AudioDeviceModule {
             let mut new_mic_level = 0u32;
             let estimated_capture_time_ns = estimated_capture_time.map_or(-1, |d| d.as_nanos() as i64);
     
    +        let guard = match rffi_audio_transport.lock() {
    +            Ok(g) => g,
    +            Err(e) => {
    +                error!("Failed to get mutex: {:?}", e);
    +                return (-1, 0);
    +            }
    +        };
             // Safety:
             // * self.audio_transport is within self, and will remain valid while this function is running
             //   because we enforce that the callback cannot change while playing or recording.
    @@ -297,11 +981,11 @@ impl AudioDeviceModule {
             //   remain valid while it runs.
             let ret = unsafe {
                 crate::webrtc::ffi::audio_device_module::Rust_recordedDataIsAvailable(
    -                self.audio_transport,
    +                guard.callback,
                     samples.as_ptr() as *const c_void,
                     samples.len(),
                     std::mem::size_of::(),
    -                channels,
    +                channels.try_into().unwrap(), // constant, so unwrap is safe
                     samples_per_sec,
                     total_delay.as_millis() as u32,
                     clock_drift,
    @@ -314,11 +998,10 @@ impl AudioDeviceModule {
             (ret, new_mic_level)
         }
     
    -    #[allow(dead_code)]
         fn need_more_play_data(
    -        &self,
    +        rffi_audio_transport: Arc>,
             samples: usize,
    -        channels: usize,
    +        channels: u32,
             samples_per_sec: u32,
         ) -> PlayData {
             let mut data = vec![0i16; samples];
    @@ -326,8 +1009,20 @@ impl AudioDeviceModule {
             let mut elapsed_time_ms = 0i64;
             let mut ntp_time_ms = 0i64;
     
    +        let guard = match rffi_audio_transport.lock() {
    +            Ok(g) => g,
    +            Err(e) => {
    +                error!("Failed to get mutex: {:?}", e);
    +                return PlayData {
    +                    success: -1,
    +                    data: Vec::new(),
    +                    elapsed_time: None,
    +                    ntp_time: None,
    +                };
    +            }
    +        };
             // Safety:
    -        // * self.audio_transport is within self, and will remain valid while this function is running
    +        // * rffi_audio_transport will remain valid while this function is running
             //   because we enforce that the callback cannot change while playing or recording.
             // * The vector has sizeof(i16) * samples bytes allocated, and we pass both of these
             //   to the C layer, which should not write beyond that bound.
    @@ -335,10 +1030,10 @@ impl AudioDeviceModule {
             //   remain valid while it runs.
             let ret = unsafe {
                 crate::webrtc::ffi::audio_device_module::Rust_needMorePlayData(
    -                self.audio_transport,
    +                guard.callback,
                     samples,
                     std::mem::size_of::(),
    -                channels,
    +                channels.try_into().unwrap(), // constant, so unwrap is safe
                     samples_per_sec,
                     data.as_mut_ptr() as *mut c_void,
                     &mut samples_out,
    @@ -350,6 +1045,7 @@ impl AudioDeviceModule {
             if ret != 0 {
                 // For safety, prevent reading any potentially invalid data if the call failed
                 // (note the truncate below).
    +            error!("failed to get output data");
                 samples_out = 0;
             }
     
    diff --git a/src/rust/src/webrtc/audio_device_module_utils.rs b/src/rust/src/webrtc/audio_device_module_utils.rs
    new file mode 100644
    index 0000000..1bb755f
    --- /dev/null
    +++ b/src/rust/src/webrtc/audio_device_module_utils.rs
    @@ -0,0 +1,201 @@
    +//
    +// Copyright 2024 Signal Messenger, LLC
    +// SPDX-License-Identifier: AGPL-3.0-only
    +//
    +
    +//! Utility functions for audio_device_module.rs
    +//! Nothing in here should depend on webrtc directly.
    +
    +use crate::webrtc;
    +use anyhow::anyhow;
    +use cubeb::{DeviceCollection, DeviceInfo, DeviceState};
    +use cubeb_core::DevicePref;
    +use std::ffi::{c_uchar, CString};
    +
    +/// Wrapper struct for DeviceCollection that handles default devices.
    +pub struct DeviceCollectionWrapper<'a> {
    +    device_collection: DeviceCollection<'a>,
    +}
    +
    +impl DeviceCollectionWrapper<'_> {
    +    pub fn new(device_collection: DeviceCollection<'_>) -> DeviceCollectionWrapper<'_> {
    +        DeviceCollectionWrapper { device_collection }
    +    }
    +
    +    /// Iterate over all Enabled devices (those that are plugged in and not disabled by the OS)
    +    pub fn iter(
    +        &self,
    +    ) -> std::iter::Filter, fn(&&DeviceInfo) -> bool> {
    +        self.device_collection
    +            .iter()
    +            .filter(|d| d.state() == DeviceState::Enabled)
    +    }
    +
    +    #[cfg(target_os = "windows")]
    +    /// Get a specified device index, accounting for the two default devices.
    +    pub fn get(&self, idx: usize) -> Option<&DeviceInfo> {
    +        // 0 should be "default device" and 1 should be "default communications device".
    +        // Note: On windows, CUBEB_DEVICE_PREF_VOICE will be set for default communications device,
    +        // and CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION for default device.
    +        // https://github.com/mozilla/cubeb/blob/bbbe5bb0b29ed64cc7dd191d7a72fe24bba0d284/src/cubeb_wasapi.cpp#L3322
    +        if self.count() == 0 {
    +            None
    +        } else if idx > 1 {
    +            self.iter().nth(idx - 2)
    +        } else if idx == 1 {
    +            // Find a device that's preferred for VOICE -- device 1 is the "default communications"
    +            self.iter()
    +                .find(|&device| device.preferred().contains(DevicePref::VOICE))
    +        } else {
    +            // Find a device that's preferred for MULTIMEDIA -- device 0 is the "default"
    +            self.iter()
    +                .find(|&device| device.preferred().contains(DevicePref::MULTIMEDIA))
    +        }
    +    }
    +
    +    #[cfg(not(target_os = "windows"))]
    +    /// Get a specified device index, accounting for the default device.
    +    pub fn get(&self, idx: usize) -> Option<&DeviceInfo> {
    +        if self.count() == 0 {
    +            None
    +        } else if idx > 0 {
    +            self.iter().nth(idx - 1)
    +        } else {
    +            // Find a device that's preferred for VOICE -- device 0 is the "default"
    +            self.iter()
    +                .find(|&device| device.preferred().contains(DevicePref::VOICE))
    +        }
    +    }
    +
    +    #[cfg(target_os = "windows")]
    +    /// Returns the number of devices.
    +    /// Note: On Windows, this is 2 smaller than the number of addressable
    +    /// devices, because the default device and default communications device
    +    /// are not counted.
    +    pub fn count(&self) -> usize {
    +        self.iter().count()
    +    }
    +    #[cfg(not(target_os = "windows"))]
    +    /// Returns the number of devices, counting the default device.
    +    pub fn count(&self) -> usize {
    +        let count = self.iter().count();
    +        if count == 0 {
    +            0
    +        } else {
    +            count + 1
    +        }
    +    }
    +}
    +
    +/// Copy from |src| into |dest| at most |dest_size| - 1 bytes and write a nul terminator either after |src| or at the end of |dest_size|
    +pub fn copy_and_truncate_string(
    +    src: &str,
    +    dest: webrtc::ptr::Borrowed,
    +    dest_size: usize,
    +) -> anyhow::Result<()> {
    +    // Leave room for the nul terminator.
    +    let size = std::cmp::min(src.len(), dest_size - 1);
    +    let c_str = CString::new(src.get(0..size).ok_or(anyhow!("couldn't get substring"))?)?;
    +    let c_str_bytes = c_str.as_bytes_with_nul();
    +    // Safety: dest has at least |dest_size| bytes allocated, and we won't
    +    // write any more than that. In addition, we are copying from a slice that
    +    // includes the nul-terminator, and we are not copying beyond the end of that
    +    // slice.
    +    unsafe {
    +        std::ptr::copy(
    +            c_str_bytes.as_ptr(),
    +            std::ptr::from_mut(
    +                dest.as_mut()
    +                    .ok_or(anyhow!("couldn't get mutable pointer"))?,
    +            ),
    +            c_str_bytes.len(),
    +        );
    +    }
    +    Ok(())
    +}
    +
    +#[cfg(test)]
    +mod audio_device_module_tests {
    +    use super::*;
    +    #[test]
    +    // Verify that extremely long strings are properly truncated and
    +    // nul-terminated
    +    fn copy_and_truncate_long_string() {
    +        let data = vec![0xaau8; 10];
    +        let src = String::from_iter(['A'; 20]); // longer than data
    +        let out = webrtc::ptr::Borrowed::from_ptr(data.as_ptr());
    +        copy_and_truncate_string(&src, out, data.len()).unwrap();
    +        let mut expected = vec![0x41u8; 9]; // 'A'
    +        expected.push(0);
    +        assert_eq!(data, expected);
    +    }
    +
    +    #[test]
    +    // Ensure that we do not read past the end of `src`
    +    fn copy_and_truncate_short_string() {
    +        let data = vec![0xaau8; 10];
    +        let src = String::from_iter(['A'; 4]); // shorter than data
    +        let out = webrtc::ptr::Borrowed::from_ptr(data.as_ptr());
    +        copy_and_truncate_string(&src, out, data.len()).unwrap();
    +        let expected = vec![0x41u8, 0x41, 0x41, 0x41, 0x0, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa];
    +        assert_eq!(data, expected);
    +    }
    +
    +    #[test]
    +    // Check for off-by-one errors
    +    fn copy_and_truncate_max_len_string() {
    +        let data = vec![0xaau8; 10];
    +        let src = String::from_iter(['A'; 10]); // equal length to data
    +        let out = webrtc::ptr::Borrowed::from_ptr(data.as_ptr());
    +        copy_and_truncate_string(&src, out, data.len()).unwrap();
    +        let mut expected = vec![0x41u8; 9]; // 'A'
    +        expected.push(0);
    +        assert_eq!(data, expected);
    +    }
    +
    +    #[test]
    +    // Check for off-by-one errors
    +    fn copy_and_truncate_barely_short_string() {
    +        let data = vec![0xaau8; 10];
    +        let src = String::from_iter(['A'; 9]); // one shorter than data
    +        let out = webrtc::ptr::Borrowed::from_ptr(data.as_ptr());
    +        copy_and_truncate_string(&src, out, data.len()).unwrap();
    +        let mut expected = vec![0x41u8; 9]; // 'A'
    +        expected.push(0);
    +        assert_eq!(data, expected);
    +    }
    +
    +    #[test]
    +    // Check for overwrite errors
    +    fn copy_no_overwrite() {
    +        let data = vec![0xaau8; 10];
    +        let src = String::from_iter(['A'; 20]); // longer than data
    +        let out = webrtc::ptr::Borrowed::from_ptr(data.as_ptr());
    +        // State that data has one fewer byte than it actually does to make sure
    +        // the function doesn't write past the end.
    +        copy_and_truncate_string(&src, out, data.len() - 1).unwrap();
    +        let mut expected = vec![0x41u8; 8]; // 'A'
    +        expected.push(0);
    +        expected.push(0xaa);
    +        assert_eq!(data, expected);
    +    }
    +
    +    #[test]
    +    // Verify that a string with internal nul characters is handled gracefully.
    +    fn string_with_nuls() {
    +        let data = vec![0xaau8; 10];
    +        let src = "a\0b";
    +        let out = webrtc::ptr::Borrowed::from_ptr(data.as_ptr());
    +        assert!(copy_and_truncate_string(src, out, data.len() - 1).is_err());
    +        // data should be untouched
    +        assert_eq!(data, vec![0xaau8; 10]);
    +    }
    +
    +    #[test]
    +    // Verify that a null dest pointer is handled gracefully
    +    fn null_ptr() {
    +        let src = "AA";
    +        let out = webrtc::ptr::Borrowed::null();
    +        assert!(copy_and_truncate_string(src, out, 5).is_err());
    +    }
    +}
    diff --git a/src/rust/src/webrtc/ffi/audio_device_module.rs b/src/rust/src/webrtc/ffi/audio_device_module.rs
    index cd2ef15..2cb4d8c 100644
    --- a/src/rust/src/webrtc/ffi/audio_device_module.rs
    +++ b/src/rust/src/webrtc/ffi/audio_device_module.rs
    @@ -8,15 +8,17 @@
     use crate::webrtc;
     use crate::webrtc::audio_device_module::{AudioDeviceModule, AudioLayer, WindowsDeviceType};
     use libc::size_t;
    -use std::ffi::c_void;
    -use std::os::raw::c_char;
    +use std::ffi::{c_uchar, c_void};
     
    -/// Incomplete type for C++ AudioTransport.
    -#[repr(C)]
    +/// Wrapper type for C++ AudioTransport.
    +#[derive(Copy, Clone)]
     pub struct RffiAudioTransport {
    -    _private: [u8; 0],
    +    pub callback: *const c_void,
     }
     
    +// Safety: managed by not allowing changes to the transport while playout or recording are in progress.
    +unsafe impl Send for RffiAudioTransport {}
    +
     /// all_adm_functions is a higher-level macro that enables "tt muncher" macros
     /// The list of functions MUST be kept in sync with AudioDeviceCallbacks in webrtc C++, and
     /// in particular the order must match.
    @@ -25,7 +27,7 @@ macro_rules! all_adm_functions {
             $macro!(
                 active_audio_layer(audio_layer: webrtc::ptr::Borrowed) -> i32;
     
    -            register_audio_callback(audio_callback: webrtc::ptr::Borrowed) -> i32;
    +            register_audio_callback(audio_callback: *const c_void) -> i32;
     
                 // Main initialization and termination
                 init() -> i32;
    @@ -35,8 +37,8 @@ macro_rules! all_adm_functions {
                 // Device enumeration
                 playout_devices() -> i16;
                 recording_devices() -> i16;
    -            playout_device_name(index: u16, name: webrtc::ptr::Borrowed, guid: webrtc::ptr::Borrowed) -> i32;
    -            recording_device_name(index: u16, name: webrtc::ptr::Borrowed, guid: webrtc::ptr::Borrowed) -> i32;
    +            playout_device_name(index: u16, name: webrtc::ptr::Borrowed, guid: webrtc::ptr::Borrowed) -> i32;
    +            recording_device_name(index: u16, name: webrtc::ptr::Borrowed, guid: webrtc::ptr::Borrowed) -> i32;
     
                 // Device selection
                 set_playout_device(index: u16) -> i32;
    @@ -136,7 +138,16 @@ macro_rules! adm_wrapper {
         () => {};
         ($f:ident($($param:ident: $arg_ty:ty),*) -> $ret:ty ; $($t:tt)*) => {
             extern "C" fn $f(ptr: webrtc::ptr::Borrowed, $($param: $arg_ty),*) -> $ret {
    -            debug!("{} wrapper", stringify!($f));
    +            // Safety: Safe as long as this function is only called from a single thread, which will
    +            // be the case.
    +            static mut LOG_COUNT: i32 = 0;
    +            unsafe {
    +                if LOG_COUNT % 100 == 0 {
    +                    info!("{} wrapper", stringify!($f));
    +                    LOG_COUNT = 0;
    +                }
    +                LOG_COUNT += 1;
    +            }
                 if let Some(adm) = unsafe { ptr.as_mut() } {
                     adm.$f($($param),*)
                 } else {
    @@ -208,7 +219,7 @@ pub const AUDIO_DEVICE_CBS_PTR: *const AudioDeviceCallbacks = &AUDIO_DEVICE_CBS;
     
     extern "C" {
         pub fn Rust_recordedDataIsAvailable(
    -        audio_transport: webrtc::ptr::Borrowed,
    +        audio_transport: *const c_void,
             audio_samples: *const c_void,
             n_samples: size_t,
             n_bytes_per_sample: size_t,
    @@ -223,7 +234,7 @@ extern "C" {
         ) -> i32;
     
         pub fn Rust_needMorePlayData(
    -        audio_transport: webrtc::ptr::Borrowed,
    +        audio_transport: *const c_void,
             n_samples: size_t,
             n_bytes_per_sample: size_t,
             n_channels: size_t,