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}