Skip to main content

screencapturekit/
audio_devices.rs

1//! Audio input device enumeration using `AVFoundation`.
2//!
3//! This module provides access to available microphone devices on macOS.
4
5use crate::utils::ffi_string::{ffi_string_from_buffer, SMALL_BUFFER_SIZE};
6
7/// Represents an audio input device (microphone).
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct AudioInputDevice {
10    /// The unique device ID used with `SCStreamConfiguration::with_microphone_capture_device_id`
11    pub id: String,
12    /// Human-readable device name
13    pub name: String,
14    /// Whether this is the system default audio input device
15    pub is_default: bool,
16}
17
18impl AudioInputDevice {
19    /// List all available audio input devices.
20    ///
21    /// # Caching
22    ///
23    /// **Not cached.** Each call walks Apple's audio device list via
24    /// `AVAudioSession`/`AudioUnit` and copies the per-device strings
25    /// across the FFI boundary. The cost is small in absolute terms
26    /// (microseconds) but is **non-zero on every call**. Code that
27    /// repeatedly needs the device list (e.g. inside a UI render loop
28    /// or per-frame decision) should cache the result and re-list only
29    /// when the user signals a possible device change (e.g. on a
30    /// settings-pane open or an `AVAudioRouteChangeNotification`).
31    ///
32    /// # Example
33    ///
34    /// ```no_run
35    /// use screencapturekit::audio_devices::AudioInputDevice;
36    ///
37    /// let devices = AudioInputDevice::list();
38    /// for device in &devices {
39    ///     println!("{}: {} {}", device.id, device.name,
40    ///         if device.is_default { "(default)" } else { "" });
41    /// }
42    /// ```
43    #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
44    pub fn list() -> Vec<Self> {
45        let count = unsafe { crate::ffi::sc_audio_get_input_device_count() };
46        let mut devices = Vec::with_capacity(count as usize);
47
48        for i in 0..count {
49            let id = unsafe {
50                ffi_string_from_buffer(SMALL_BUFFER_SIZE, |buf, len| {
51                    crate::ffi::sc_audio_get_input_device_id(i, buf, len)
52                })
53            };
54            let name = unsafe {
55                ffi_string_from_buffer(SMALL_BUFFER_SIZE, |buf, len| {
56                    crate::ffi::sc_audio_get_input_device_name(i, buf, len)
57                })
58            };
59            let is_default = unsafe { crate::ffi::sc_audio_is_default_input_device(i) };
60
61            if let (Some(id), Some(name)) = (id, name) {
62                devices.push(Self {
63                    id,
64                    name,
65                    is_default,
66                });
67            }
68        }
69
70        devices
71    }
72
73    /// Get the default audio input device, if any.
74    ///
75    /// # Example
76    ///
77    /// ```no_run
78    /// use screencapturekit::audio_devices::AudioInputDevice;
79    ///
80    /// if let Some(device) = AudioInputDevice::default_device() {
81    ///     println!("Default microphone: {}", device.name);
82    /// }
83    /// ```
84    pub fn default_device() -> Option<Self> {
85        let id = unsafe {
86            ffi_string_from_buffer(SMALL_BUFFER_SIZE, |buf, len| {
87                crate::ffi::sc_audio_get_default_input_device_id(buf, len)
88            })
89        };
90        let name = unsafe {
91            ffi_string_from_buffer(SMALL_BUFFER_SIZE, |buf, len| {
92                crate::ffi::sc_audio_get_default_input_device_name(buf, len)
93            })
94        };
95
96        match (id, name) {
97            (Some(id), Some(name)) => Some(Self {
98                id,
99                name,
100                is_default: true,
101            }),
102            _ => None,
103        }
104    }
105}