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}