Skip to main content

screencapturekit/cm/
audio.rs

1//! Audio buffer types for captured audio samples
2//!
3//! This module provides types for accessing audio data from captured samples.
4//!
5//! ## Main Types
6//!
7//! - [`AudioBuffer`] - Single audio buffer containing sample data
8//! - [`AudioBufferList`] - Collection of audio buffers (typically one per channel)
9//! - [`AudioBufferRef`] - Reference to an audio buffer with convenience methods
10
11use super::ffi;
12use std::fmt;
13
14/// Raw audio buffer containing sample data
15///
16/// An `AudioBuffer` represents a single channel or interleaved audio data.
17/// Access the raw bytes via [`data()`](Self::data) or [`data_mut()`](Self::data_mut).
18#[repr(C)]
19pub struct AudioBuffer {
20    /// Number of audio channels in this buffer
21    pub number_channels: u32,
22    /// Size of the audio data in bytes
23    pub data_bytes_size: u32,
24    data_ptr: *mut std::ffi::c_void,
25}
26
27impl PartialEq for AudioBuffer {
28    fn eq(&self, other: &Self) -> bool {
29        self.number_channels == other.number_channels
30            && self.data_bytes_size == other.data_bytes_size
31            && self.data_ptr == other.data_ptr
32    }
33}
34
35impl Eq for AudioBuffer {}
36
37impl std::hash::Hash for AudioBuffer {
38    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
39        self.number_channels.hash(state);
40        self.data_bytes_size.hash(state);
41        self.data_ptr.hash(state);
42    }
43}
44
45impl fmt::Display for AudioBuffer {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        write!(
48            f,
49            "AudioBuffer({} channels, {} bytes)",
50            self.number_channels, self.data_bytes_size
51        )
52    }
53}
54
55impl AudioBuffer {
56    /// Get the raw audio data as a byte slice
57    pub fn data(&self) -> &[u8] {
58        if self.data_ptr.is_null() || self.data_bytes_size == 0 {
59            &[]
60        } else {
61            unsafe {
62                std::slice::from_raw_parts(
63                    self.data_ptr as *const u8,
64                    self.data_bytes_size as usize,
65                )
66            }
67        }
68    }
69
70    /// Get the raw audio data as a mutable byte slice
71    ///
72    /// # Warning
73    ///
74    /// This returns a `&mut [u8]` that points directly into the `CoreMedia` block
75    /// buffer backing the captured sample. That memory may be **aliased** with the
76    /// still-live source `CMSampleBuffer` (and any other copies of the underlying
77    /// `CMBlockBuffer`), so mutating it can race with or corrupt data observed
78    /// elsewhere. Only mutate this slice if you are certain no other reader holds a
79    /// view into the same block buffer, and never mutate it concurrently. Prefer
80    /// copying the bytes out via [`data()`](Self::data) when in doubt.
81    pub fn data_mut(&mut self) -> &mut [u8] {
82        if self.data_ptr.is_null() || self.data_bytes_size == 0 {
83            &mut []
84        } else {
85            unsafe {
86                std::slice::from_raw_parts_mut(
87                    self.data_ptr.cast::<u8>(),
88                    self.data_bytes_size as usize,
89                )
90            }
91        }
92    }
93
94    /// Get the size of the data in bytes
95    pub fn data_byte_size(&self) -> usize {
96        self.data_bytes_size as usize
97    }
98}
99
100/// Reference to an audio buffer with convenience methods
101pub struct AudioBufferRef<'a> {
102    buffer: &'a AudioBuffer,
103}
104
105impl<'a> AudioBufferRef<'a> {
106    /// Get the size of the data in bytes
107    pub fn data_byte_size(&self) -> usize {
108        self.buffer.data_byte_size()
109    }
110
111    /// Get the raw audio data as a byte slice
112    ///
113    /// The returned slice is tied to the lifetime `'a` of the wrapped buffer
114    /// reference rather than to this `AudioBufferRef`, because the underlying
115    /// block-buffer memory lives at least as long as the borrowed
116    /// [`AudioBuffer`] (and the [`AudioBufferList`] that owns it).
117    pub fn data(&self) -> &'a [u8] {
118        self.buffer.data()
119    }
120}
121
122impl std::fmt::Debug for AudioBufferRef<'_> {
123    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124        f.debug_struct("AudioBufferRef")
125            .field("channels", &self.buffer.number_channels)
126            .field("data_bytes", &self.buffer.data_bytes_size)
127            .finish()
128    }
129}
130
131impl std::fmt::Debug for AudioBuffer {
132    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133        f.debug_struct("AudioBuffer")
134            .field("number_channels", &self.number_channels)
135            .field("data_bytes_size", &self.data_bytes_size)
136            .finish_non_exhaustive()
137    }
138}
139
140/// List of audio buffers from an audio sample
141#[repr(C)]
142#[derive(Debug)]
143pub struct AudioBufferListRaw {
144    pub(crate) num_buffers: u32,
145    pub(crate) buffers_ptr: *mut AudioBuffer,
146    pub(crate) buffers_len: usize,
147}
148
149/// List of audio buffers from an audio sample
150///
151/// Contains one or more [`AudioBuffer`]s, typically one per audio channel.
152/// Use [`iter()`](Self::iter) to iterate over the buffers.
153pub struct AudioBufferList {
154    pub(crate) inner: AudioBufferListRaw,
155    /// Block buffer that owns the audio data - must be kept alive
156    pub(crate) block_buffer_ptr: *mut std::ffi::c_void,
157}
158
159impl AudioBufferList {
160    /// Get the number of buffers in the list
161    pub fn num_buffers(&self) -> usize {
162        self.inner.num_buffers as usize
163    }
164
165    /// Get a buffer by index
166    pub fn get(&self, index: usize) -> Option<&AudioBuffer> {
167        if index >= self.num_buffers() {
168            None
169        } else {
170            unsafe { Some(&*self.inner.buffers_ptr.add(index)) }
171        }
172    }
173
174    /// Get a buffer reference by index
175    pub fn buffer(&self, index: usize) -> Option<AudioBufferRef<'_>> {
176        self.get(index).map(|buffer| AudioBufferRef { buffer })
177    }
178
179    /// Get a mutable buffer by index
180    pub fn get_mut(&mut self, index: usize) -> Option<&mut AudioBuffer> {
181        if index >= self.num_buffers() {
182            None
183        } else {
184            unsafe { Some(&mut *self.inner.buffers_ptr.add(index)) }
185        }
186    }
187
188    /// Iterate over the audio buffers
189    pub fn iter(&self) -> AudioBufferListIter<'_> {
190        AudioBufferListIter {
191            list: self,
192            index: 0,
193        }
194    }
195}
196
197impl Drop for AudioBufferList {
198    fn drop(&mut self) {
199        // Free the buffers array allocated in Swift via UnsafeMutablePointer.allocate().
200        // Must use the system allocator (not Rust's global allocator) because Swift
201        // allocates with the system malloc. Using Vec::from_raw_parts here would route
202        // through the global allocator, which crashes when a custom allocator like
203        // mimalloc is active.
204        if !self.inner.buffers_ptr.is_null() {
205            unsafe {
206                use std::alloc::{GlobalAlloc, Layout, System};
207                let layout = Layout::array::<AudioBuffer>(self.inner.buffers_len)
208                    .expect("AudioBufferList layout overflow");
209                System.dealloc(self.inner.buffers_ptr.cast::<u8>(), layout);
210            }
211        }
212        // Release the block buffer that owns the audio data
213        if !self.block_buffer_ptr.is_null() {
214            unsafe {
215                ffi::cm_block_buffer_release(self.block_buffer_ptr);
216            }
217        }
218    }
219}
220
221impl<'a> IntoIterator for &'a AudioBufferList {
222    type Item = &'a AudioBuffer;
223    type IntoIter = AudioBufferListIter<'a>;
224
225    fn into_iter(self) -> Self::IntoIter {
226        self.iter()
227    }
228}
229
230impl fmt::Display for AudioBufferList {
231    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232        write!(f, "AudioBufferList({} buffers)", self.num_buffers())
233    }
234}
235
236impl fmt::Debug for AudioBufferList {
237    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238        f.debug_struct("AudioBufferList")
239            .field("num_buffers", &self.num_buffers())
240            .finish()
241    }
242}
243
244/// Iterator over audio buffers in an [`AudioBufferList`]
245pub struct AudioBufferListIter<'a> {
246    list: &'a AudioBufferList,
247    index: usize,
248}
249
250impl std::fmt::Debug for AudioBufferListIter<'_> {
251    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252        f.debug_struct("AudioBufferListIter")
253            .field("total", &self.list.num_buffers())
254            .field(
255                "remaining",
256                &(self.list.num_buffers().saturating_sub(self.index)),
257            )
258            .finish()
259    }
260}
261
262impl<'a> Iterator for AudioBufferListIter<'a> {
263    type Item = &'a AudioBuffer;
264
265    fn next(&mut self) -> Option<Self::Item> {
266        if self.index < self.list.num_buffers() {
267            let buffer = self.list.get(self.index);
268            self.index += 1;
269            buffer
270        } else {
271            None
272        }
273    }
274}