Skip to main content

screencapturekit/stream/configuration/
audio.rs

1//! Audio capture configuration
2//!
3//! Methods for configuring audio capture, sample rate, and channel count.
4//!
5//! ## Supported Values
6//!
7//! `ScreenCaptureKit` supports specific sample rates and channel counts:
8//!
9//! | Sample Rate | Description |
10//! |-------------|-------------|
11//! | 8000 Hz | Low quality, telephony |
12//! | 16000 Hz | Speech quality |
13//! | 24000 Hz | Medium quality |
14//! | 48000 Hz | Professional audio (default) |
15//!
16//! | Channel Count | Description |
17//! |---------------|-------------|
18//! | 1 | Mono |
19//! | 2 | Stereo (default) |
20
21#[cfg(feature = "macos_15_0")]
22use crate::error::{SCError, SCResult};
23#[cfg(feature = "macos_15_0")]
24use crate::utils::ffi_string::{ffi_string_from_buffer, SMALL_BUFFER_SIZE};
25
26use super::internal::SCStreamConfiguration;
27#[cfg(feature = "macos_15_0")]
28use super::InteriorNulError;
29
30/// Audio sample rate for capture
31///
32/// `ScreenCaptureKit` supports a fixed set of sample rates. Using values outside
33/// this set will result in the system defaulting to 48000 Hz.
34///
35/// # Examples
36///
37/// ```
38/// use screencapturekit::stream::configuration::audio::AudioSampleRate;
39///
40/// // Get the Hz value
41/// assert_eq!(AudioSampleRate::Rate48000.as_hz(), 48000);
42///
43/// // Use default (48000 Hz)
44/// let rate = AudioSampleRate::default();
45/// assert_eq!(rate, AudioSampleRate::Rate48000);
46/// ```
47#[repr(i32)]
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
49pub enum AudioSampleRate {
50    /// 8000 Hz - Low quality, suitable for telephony
51    Rate8000 = 8000,
52    /// 16000 Hz - Speech quality
53    Rate16000 = 16000,
54    /// 24000 Hz - Medium quality
55    Rate24000 = 24000,
56    /// 48000 Hz - Professional audio quality (default)
57    #[default]
58    Rate48000 = 48000,
59}
60
61impl AudioSampleRate {
62    /// Get the sample rate in Hz
63    #[must_use]
64    pub const fn as_hz(self) -> i32 {
65        self as i32
66    }
67
68    /// Create from Hz value, returning None if unsupported
69    ///
70    /// # Examples
71    ///
72    /// ```
73    /// use screencapturekit::stream::configuration::audio::AudioSampleRate;
74    ///
75    /// assert_eq!(AudioSampleRate::from_hz(48000), Some(AudioSampleRate::Rate48000));
76    /// assert_eq!(AudioSampleRate::from_hz(44100), None); // Not supported
77    /// ```
78    #[must_use]
79    pub const fn from_hz(hz: i32) -> Option<Self> {
80        match hz {
81            8000 => Some(Self::Rate8000),
82            16000 => Some(Self::Rate16000),
83            24000 => Some(Self::Rate24000),
84            48000 => Some(Self::Rate48000),
85            _ => None,
86        }
87    }
88}
89
90impl std::fmt::Display for AudioSampleRate {
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92        write!(f, "{} Hz", self.as_hz())
93    }
94}
95
96impl From<AudioSampleRate> for i32 {
97    fn from(rate: AudioSampleRate) -> Self {
98        rate.as_hz()
99    }
100}
101
102/// Audio channel configuration for capture
103///
104/// `ScreenCaptureKit` supports mono (1 channel) or stereo (2 channels) audio.
105/// Using other values will result in the system defaulting to stereo.
106///
107/// # Examples
108///
109/// ```
110/// use screencapturekit::stream::configuration::audio::AudioChannelCount;
111///
112/// // Get the channel count
113/// assert_eq!(AudioChannelCount::Stereo.as_count(), 2);
114///
115/// // Use default (stereo)
116/// let channels = AudioChannelCount::default();
117/// assert_eq!(channels, AudioChannelCount::Stereo);
118/// ```
119#[repr(i32)]
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
121pub enum AudioChannelCount {
122    /// Mono - single channel audio
123    Mono = 1,
124    /// Stereo - two channel audio (default)
125    #[default]
126    Stereo = 2,
127}
128
129impl AudioChannelCount {
130    /// Get the channel count as an integer
131    #[must_use]
132    pub const fn as_count(self) -> i32 {
133        self as i32
134    }
135
136    /// Create from channel count, returning None if unsupported
137    ///
138    /// # Examples
139    ///
140    /// ```
141    /// use screencapturekit::stream::configuration::audio::AudioChannelCount;
142    ///
143    /// assert_eq!(AudioChannelCount::from_count(2), Some(AudioChannelCount::Stereo));
144    /// assert_eq!(AudioChannelCount::from_count(6), None); // Not supported
145    /// ```
146    #[must_use]
147    pub const fn from_count(count: i32) -> Option<Self> {
148        match count {
149            1 => Some(Self::Mono),
150            2 => Some(Self::Stereo),
151            _ => None,
152        }
153    }
154}
155
156impl std::fmt::Display for AudioChannelCount {
157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158        match self {
159            Self::Mono => write!(f, "Mono (1 channel)"),
160            Self::Stereo => write!(f, "Stereo (2 channels)"),
161        }
162    }
163}
164
165impl From<AudioChannelCount> for i32 {
166    fn from(count: AudioChannelCount) -> Self {
167        count.as_count()
168    }
169}
170
171impl SCStreamConfiguration {
172    /// Enable or disable audio capture
173    ///
174    /// # Examples
175    ///
176    /// ```
177    /// use screencapturekit::prelude::*;
178    ///
179    /// let mut config = SCStreamConfiguration::default();
180    /// config.set_captures_audio(true);
181    /// assert!(config.captures_audio());
182    /// ```
183    pub fn set_captures_audio(&mut self, captures_audio: bool) -> &mut Self {
184        unsafe {
185            crate::ffi::sc_stream_configuration_set_captures_audio(self.as_ptr(), captures_audio);
186        }
187        self
188    }
189
190    /// Enable or disable audio capture (builder pattern)
191    #[must_use]
192    pub fn with_captures_audio(mut self, captures_audio: bool) -> Self {
193        self.set_captures_audio(captures_audio);
194        self
195    }
196
197    /// Check if audio capture is enabled
198    pub fn captures_audio(&self) -> bool {
199        unsafe { crate::ffi::sc_stream_configuration_get_captures_audio(self.as_ptr()) }
200    }
201
202    /// Set the audio sample rate
203    ///
204    /// Accepts either an [`AudioSampleRate`] enum or a raw `i32` Hz value.
205    ///
206    /// # Supported Values
207    ///
208    /// `ScreenCaptureKit` supports: 8000, 16000, 24000, 48000 Hz.
209    /// Other values will default to 48000 Hz.
210    ///
211    /// # Examples
212    ///
213    /// ```
214    /// use screencapturekit::prelude::*;
215    /// use screencapturekit::stream::configuration::audio::AudioSampleRate;
216    ///
217    /// // Using the enum (recommended)
218    /// let config = SCStreamConfiguration::new()
219    ///     .with_sample_rate(AudioSampleRate::Rate48000);
220    ///
221    /// // Using raw value (still works)
222    /// let config = SCStreamConfiguration::new()
223    ///     .with_sample_rate(48000);
224    /// ```
225    pub fn set_sample_rate(&mut self, sample_rate: impl Into<i32>) -> &mut Self {
226        unsafe {
227            crate::ffi::sc_stream_configuration_set_sample_rate(
228                self.as_ptr(),
229                sample_rate.into() as isize,
230            );
231        }
232        self
233    }
234
235    /// Set the audio sample rate (builder pattern)
236    #[must_use]
237    pub fn with_sample_rate(mut self, sample_rate: impl Into<i32>) -> Self {
238        self.set_sample_rate(sample_rate);
239        self
240    }
241
242    /// Get the configured audio sample rate in Hz
243    pub fn sample_rate(&self) -> i32 {
244        // FFI returns isize but sample rate fits in i32 (typical values: 44100, 48000)
245        #[allow(clippy::cast_possible_truncation)]
246        unsafe {
247            crate::ffi::sc_stream_configuration_get_sample_rate(self.as_ptr()) as i32
248        }
249    }
250
251    /// Get the configured audio sample rate as an enum
252    ///
253    /// Returns `None` if the current sample rate is not a supported value.
254    pub fn audio_sample_rate(&self) -> Option<AudioSampleRate> {
255        AudioSampleRate::from_hz(self.sample_rate())
256    }
257
258    /// Set the number of audio channels
259    ///
260    /// Accepts either an [`AudioChannelCount`] enum or a raw `i32` value.
261    ///
262    /// # Supported Values
263    ///
264    /// `ScreenCaptureKit` supports: 1 (mono), 2 (stereo).
265    /// Other values will default to stereo.
266    ///
267    /// # Examples
268    ///
269    /// ```
270    /// use screencapturekit::prelude::*;
271    /// use screencapturekit::stream::configuration::audio::AudioChannelCount;
272    ///
273    /// // Using the enum (recommended)
274    /// let config = SCStreamConfiguration::new()
275    ///     .with_channel_count(AudioChannelCount::Stereo);
276    ///
277    /// // Using raw value (still works)
278    /// let config = SCStreamConfiguration::new()
279    ///     .with_channel_count(2);
280    /// ```
281    pub fn set_channel_count(&mut self, channel_count: impl Into<i32>) -> &mut Self {
282        unsafe {
283            crate::ffi::sc_stream_configuration_set_channel_count(
284                self.as_ptr(),
285                channel_count.into() as isize,
286            );
287        }
288        self
289    }
290
291    /// Set the number of audio channels (builder pattern)
292    #[must_use]
293    pub fn with_channel_count(mut self, channel_count: impl Into<i32>) -> Self {
294        self.set_channel_count(channel_count);
295        self
296    }
297
298    /// Get the configured channel count
299    pub fn channel_count(&self) -> i32 {
300        // FFI returns isize but channel count fits in i32 (typical values: 1-8)
301        #[allow(clippy::cast_possible_truncation)]
302        unsafe {
303            crate::ffi::sc_stream_configuration_get_channel_count(self.as_ptr()) as i32
304        }
305    }
306
307    /// Get the configured channel count as an enum
308    ///
309    /// Returns `None` if the current channel count is not a supported value.
310    pub fn audio_channel_count(&self) -> Option<AudioChannelCount> {
311        AudioChannelCount::from_count(self.channel_count())
312    }
313
314    /// Enable microphone capture (macOS 15.0+)
315    ///
316    /// When set to `true`, the stream will capture audio from the microphone
317    /// in addition to system/application audio (if `captures_audio` is also enabled).
318    ///
319    /// **Note**: Requires `NSMicrophoneUsageDescription` in your app's Info.plist
320    /// for microphone access permission.
321    ///
322    /// # Availability
323    /// macOS 15.0+. On earlier versions it returns
324    /// [`SCError::FeatureNotAvailable`].
325    ///
326    /// # Example
327    /// ```rust,no_run
328    /// use screencapturekit::prelude::*;
329    ///
330    /// let config = SCStreamConfiguration::new()
331    ///     .with_captures_audio(true)       // System audio
332    ///     .with_captures_microphone(true)  // Microphone audio (macOS 15.0+)
333    ///     .expect("macOS 15.0 or later")
334    ///     .with_sample_rate(48000)
335    ///     .with_channel_count(2);
336    /// ```
337    #[cfg(feature = "macos_15_0")]
338    #[allow(clippy::missing_errors_doc)]
339    pub fn set_captures_microphone(&mut self, captures_microphone: bool) -> SCResult<&mut Self> {
340        let applied = unsafe {
341            crate::ffi::sc_stream_configuration_set_captures_microphone(
342                self.as_ptr(),
343                captures_microphone,
344            )
345        };
346        applied.then_some(self).ok_or_else(|| {
347            SCError::feature_not_available("SCStreamConfiguration.captureMicrophone", "15.0")
348        })
349    }
350
351    /// Enable microphone capture (builder pattern)
352    #[cfg(feature = "macos_15_0")]
353    #[allow(clippy::missing_errors_doc)]
354    pub fn with_captures_microphone(mut self, captures_microphone: bool) -> SCResult<Self> {
355        self.set_captures_microphone(captures_microphone)?;
356        Ok(self)
357    }
358
359    /// Get whether microphone capture is enabled (macOS 15.0+).
360    #[cfg(feature = "macos_15_0")]
361    pub fn captures_microphone(&self) -> bool {
362        unsafe { crate::ffi::sc_stream_configuration_get_captures_microphone(self.as_ptr()) }
363    }
364
365    /// Exclude current process audio from capture.
366    ///
367    /// When set to `true`, the stream will not capture audio from the current
368    /// process, preventing feedback loops in recording applications.
369    ///
370    /// # Example
371    /// ```rust,no_run
372    /// use screencapturekit::prelude::*;
373    ///
374    /// let config = SCStreamConfiguration::new()
375    ///     .with_captures_audio(true)
376    ///     .with_excludes_current_process_audio(true); // Prevent feedback
377    /// ```
378    pub fn set_excludes_current_process_audio(&mut self, excludes: bool) -> &mut Self {
379        unsafe {
380            crate::ffi::sc_stream_configuration_set_excludes_current_process_audio(
381                self.as_ptr(),
382                excludes,
383            );
384        }
385        self
386    }
387
388    /// Exclude current process audio (builder pattern)
389    #[must_use]
390    pub fn with_excludes_current_process_audio(mut self, excludes: bool) -> Self {
391        self.set_excludes_current_process_audio(excludes);
392        self
393    }
394
395    /// Get whether current process audio is excluded from capture.
396    pub fn excludes_current_process_audio(&self) -> bool {
397        unsafe {
398            crate::ffi::sc_stream_configuration_get_excludes_current_process_audio(self.as_ptr())
399        }
400    }
401
402    /// Set microphone capture device ID (macOS 15.0+).
403    ///
404    /// Specifies which microphone device to capture from.
405    ///
406    /// # Availability
407    /// macOS 15.0+.
408    ///
409    /// # Errors
410    ///
411    /// Returns [`SCError::InvalidConfiguration`] — leaving the configuration
412    /// unchanged — if `device_id` contains an interior NUL byte, and
413    /// [`SCError::FeatureNotAvailable`] before macOS 15.0.
414    ///
415    /// # Example
416    /// ```rust,no_run
417    /// use screencapturekit::prelude::*;
418    ///
419    /// let mut config = SCStreamConfiguration::new()
420    ///     .with_captures_microphone(true)
421    ///     .expect("macOS 15.0 or later");
422    /// config
423    ///     .set_microphone_capture_device_id("AppleHDAEngineInput:1B,0,1,0:1")
424    ///     .expect("device ID has no NUL byte");
425    /// ```
426    #[cfg(feature = "macos_15_0")]
427    pub fn set_microphone_capture_device_id(&mut self, device_id: &str) -> SCResult<&mut Self> {
428        let c_id = std::ffi::CString::new(device_id).map_err(|_| InteriorNulError)?;
429        let applied = unsafe {
430            crate::ffi::sc_stream_configuration_set_microphone_capture_device_id(
431                self.as_ptr(),
432                c_id.as_ptr(),
433            )
434        };
435        applied.then_some(self).ok_or_else(|| {
436            SCError::feature_not_available(
437                "SCStreamConfiguration.microphoneCaptureDeviceID",
438                "15.0",
439            )
440        })
441    }
442
443    /// Set microphone capture device ID (builder pattern)
444    #[cfg(feature = "macos_15_0")]
445    #[allow(clippy::missing_errors_doc)]
446    pub fn with_microphone_capture_device_id(mut self, device_id: &str) -> SCResult<Self> {
447        self.set_microphone_capture_device_id(device_id)?;
448        Ok(self)
449    }
450
451    /// Clear microphone capture device ID, reverting to default system microphone
452    #[cfg(feature = "macos_15_0")]
453    #[allow(clippy::missing_errors_doc)]
454    pub fn clear_microphone_capture_device_id(&mut self) -> SCResult<&mut Self> {
455        let applied = unsafe {
456            crate::ffi::sc_stream_configuration_set_microphone_capture_device_id(
457                self.as_ptr(),
458                std::ptr::null(),
459            )
460        };
461        applied.then_some(self).ok_or_else(|| {
462            SCError::feature_not_available(
463                "SCStreamConfiguration.microphoneCaptureDeviceID",
464                "15.0",
465            )
466        })
467    }
468
469    /// Get microphone capture device ID (macOS 15.0+).
470    #[cfg(feature = "macos_15_0")]
471    pub fn microphone_capture_device_id(&self) -> Option<String> {
472        unsafe {
473            ffi_string_from_buffer(SMALL_BUFFER_SIZE, |buf, len| {
474                crate::ffi::sc_stream_configuration_get_microphone_capture_device_id(
475                    self.as_ptr(),
476                    buf,
477                    len,
478                )
479            })
480        }
481    }
482}