Skip to main content

screencapturekit/cm/
block_buffer.rs

1//! `CMBlockBuffer` - Block of contiguous data
2//!
3//! A `CMBlockBuffer` represents a contiguous range of data, typically used
4//! for audio samples or compressed video data. It manages memory ownership
5//! and provides access to the underlying data bytes.
6
7use super::ffi;
8use std::io;
9
10/// Block buffer containing contiguous media data
11///
12/// `CMBlockBuffer` is a Core Media type that represents a block of data,
13/// commonly used for audio samples or compressed video data. The data is
14/// managed by Core Media and released when the buffer is dropped.
15///
16/// Unlike `CVPixelBuffer` or `IOSurface`, `CMBlockBuffer` does not require
17/// locking for data access - the data pointer is valid as long as the buffer
18/// is retained.
19///
20/// # Examples
21///
22/// ```no_run
23/// use screencapturekit::cm::CMBlockBuffer;
24///
25/// fn process_block_buffer(buffer: &CMBlockBuffer) {
26///     // Check if there's any data
27///     if buffer.is_empty() {
28///         return;
29///     }
30///
31///     println!("Buffer has {} bytes", buffer.data_length());
32///
33///     // Get a pointer to the data
34///     if let Some((ptr, length)) = buffer.data_pointer(0) {
35///         println!("Got {} bytes at offset 0", length);
36///     }
37///
38///     // Or copy data to a Vec
39///     if let Some(data) = buffer.copy_data_bytes(0, buffer.data_length()) {
40///         println!("Copied {} bytes", data.len());
41///     }
42/// }
43/// ```
44pub struct CMBlockBuffer(*mut std::ffi::c_void);
45
46impl PartialEq for CMBlockBuffer {
47    fn eq(&self, other: &Self) -> bool {
48        self.0 == other.0
49    }
50}
51
52impl Eq for CMBlockBuffer {}
53
54impl std::hash::Hash for CMBlockBuffer {
55    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
56        unsafe {
57            let hash_value = ffi::cm_block_buffer_hash(self.0);
58            hash_value.hash(state);
59        }
60    }
61}
62
63impl CMBlockBuffer {
64    /// Create a new `CMBlockBuffer` with the given data
65    ///
66    /// # Arguments
67    ///
68    /// * `data` - The data to copy into the block buffer
69    ///
70    /// # Returns
71    ///
72    /// `Some(CMBlockBuffer)` if successful, `None` if creation failed.
73    ///
74    /// # Examples
75    ///
76    /// ```
77    /// use screencapturekit::cm::CMBlockBuffer;
78    ///
79    /// let data = vec![1u8, 2, 3, 4, 5];
80    /// let buffer = CMBlockBuffer::create(&data).expect("Failed to create buffer");
81    /// assert_eq!(buffer.data_length(), 5);
82    /// ```
83    #[must_use]
84    pub fn create(data: &[u8]) -> Option<Self> {
85        if data.is_empty() {
86            return Self::create_empty();
87        }
88        let mut ptr: *mut std::ffi::c_void = std::ptr::null_mut();
89        let status = unsafe {
90            ffi::cm_block_buffer_create_with_data(data.as_ptr().cast(), data.len(), &mut ptr)
91        };
92        if status == 0 && !ptr.is_null() {
93            Some(Self(ptr))
94        } else {
95            None
96        }
97    }
98
99    /// Create an empty `CMBlockBuffer`
100    ///
101    /// # Returns
102    ///
103    /// `Some(CMBlockBuffer)` if successful, `None` if creation failed.
104    ///
105    /// # Examples
106    ///
107    /// ```
108    /// use screencapturekit::cm::CMBlockBuffer;
109    ///
110    /// let buffer = CMBlockBuffer::create_empty().expect("Failed to create empty buffer");
111    /// assert!(buffer.is_empty());
112    /// ```
113    #[must_use]
114    pub fn create_empty() -> Option<Self> {
115        let mut ptr: *mut std::ffi::c_void = std::ptr::null_mut();
116        let status = unsafe { ffi::cm_block_buffer_create_empty(&mut ptr) };
117        if status == 0 && !ptr.is_null() {
118            Some(Self(ptr))
119        } else {
120            None
121        }
122    }
123
124    /// Create from a raw pointer, returning `None` if null
125    pub fn from_raw(ptr: *mut std::ffi::c_void) -> Option<Self> {
126        if ptr.is_null() {
127            None
128        } else {
129            Some(Self(ptr))
130        }
131    }
132
133    /// # Safety
134    /// The caller must ensure the pointer is a valid `CMBlockBuffer` pointer.
135    pub unsafe fn from_ptr(ptr: *mut std::ffi::c_void) -> Self {
136        Self(ptr)
137    }
138
139    /// Get the raw pointer to the block buffer
140    pub fn as_ptr(&self) -> *mut std::ffi::c_void {
141        self.0
142    }
143
144    /// Get the total data length of the buffer in bytes
145    ///
146    /// # Examples
147    ///
148    /// ```no_run
149    /// use screencapturekit::cm::CMBlockBuffer;
150    ///
151    /// fn check_size(buffer: &CMBlockBuffer) {
152    ///     let size = buffer.data_length();
153    ///     println!("Buffer contains {} bytes", size);
154    /// }
155    /// ```
156    pub fn data_length(&self) -> usize {
157        unsafe { ffi::cm_block_buffer_get_data_length(self.0) }
158    }
159
160    /// Check if the buffer is empty (contains no data)
161    ///
162    /// # Examples
163    ///
164    /// ```no_run
165    /// use screencapturekit::cm::CMBlockBuffer;
166    ///
167    /// fn process(buffer: &CMBlockBuffer) {
168    ///     if buffer.is_empty() {
169    ///         println!("No data to process");
170    ///         return;
171    ///     }
172    ///     // Process data...
173    /// }
174    /// ```
175    pub fn is_empty(&self) -> bool {
176        unsafe { ffi::cm_block_buffer_is_empty(self.0) }
177    }
178
179    /// Check if a range of bytes is stored contiguously in memory
180    ///
181    /// # Arguments
182    ///
183    /// * `offset` - Starting offset in the buffer
184    /// * `length` - Length of the range to check
185    ///
186    /// # Returns
187    ///
188    /// `true` if the specified range is contiguous in memory
189    pub fn is_range_contiguous(&self, offset: usize, length: usize) -> bool {
190        unsafe { ffi::cm_block_buffer_is_range_contiguous(self.0, offset, length) }
191    }
192
193    /// Get a pointer to the data at the specified offset
194    ///
195    /// Returns a tuple of (data pointer, length available at that offset) if successful.
196    /// The pointer is valid as long as this `CMBlockBuffer` is retained.
197    ///
198    /// # Arguments
199    ///
200    /// * `offset` - Byte offset into the buffer
201    ///
202    /// # Returns
203    ///
204    /// `Some((pointer, length_at_offset))` if the data pointer was obtained successfully,
205    /// `None` if the operation failed.
206    ///
207    /// # Examples
208    ///
209    /// ```no_run
210    /// use screencapturekit::cm::CMBlockBuffer;
211    ///
212    /// fn read_data(buffer: &CMBlockBuffer) {
213    ///     if let Some((ptr, length)) = buffer.data_pointer(0) {
214    ///         // SAFETY: ptr is valid for `length` bytes while buffer is alive
215    ///         let slice = unsafe { std::slice::from_raw_parts(ptr, length) };
216    ///         println!("First byte: {:02x}", slice[0]);
217    ///     }
218    /// }
219    /// ```
220    pub fn data_pointer(&self, offset: usize) -> Option<(*const u8, usize)> {
221        unsafe {
222            let mut length_at_offset: usize = 0;
223            let mut total_length: usize = 0;
224            let mut data_pointer: *mut std::ffi::c_void = std::ptr::null_mut();
225
226            let status = ffi::cm_block_buffer_get_data_pointer(
227                self.0,
228                offset,
229                &mut length_at_offset,
230                &mut total_length,
231                &mut data_pointer,
232            );
233
234            if status == 0 && !data_pointer.is_null() {
235                Some((data_pointer.cast::<u8>().cast_const(), length_at_offset))
236            } else {
237                None
238            }
239        }
240    }
241
242    /// Get a mutable pointer to the data at the specified offset
243    ///
244    /// # Safety
245    ///
246    /// The caller must ensure that modifying the data is safe and that no other
247    /// references to this data exist.
248    pub unsafe fn data_pointer_mut(&self, offset: usize) -> Option<(*mut u8, usize)> {
249        let mut length_at_offset: usize = 0;
250        let mut total_length: usize = 0;
251        let mut data_pointer: *mut std::ffi::c_void = std::ptr::null_mut();
252
253        let status = ffi::cm_block_buffer_get_data_pointer(
254            self.0,
255            offset,
256            &mut length_at_offset,
257            &mut total_length,
258            &mut data_pointer,
259        );
260
261        if status == 0 && !data_pointer.is_null() {
262            Some((data_pointer.cast::<u8>(), length_at_offset))
263        } else {
264            None
265        }
266    }
267
268    /// Copy data bytes from the buffer into a new `Vec<u8>`
269    ///
270    /// This is the safest way to access buffer data as it copies the bytes
271    /// into owned memory.
272    ///
273    /// # Arguments
274    ///
275    /// * `offset` - Starting offset in the buffer
276    /// * `length` - Number of bytes to copy
277    ///
278    /// # Returns
279    ///
280    /// `Some(Vec<u8>)` containing the copied data, or `None` if the copy failed.
281    ///
282    /// # Examples
283    ///
284    /// ```no_run
285    /// use screencapturekit::cm::CMBlockBuffer;
286    ///
287    /// fn extract_data(buffer: &CMBlockBuffer) -> Option<Vec<u8>> {
288    ///     // Copy all data from the buffer
289    ///     buffer.copy_data_bytes(0, buffer.data_length())
290    /// }
291    /// ```
292    pub fn copy_data_bytes(&self, offset: usize, length: usize) -> Option<Vec<u8>> {
293        if length == 0 {
294            return Some(Vec::new());
295        }
296
297        // Allocate uninitialised — `cm_block_buffer_copy_data_bytes` writes the full
298        // `length` bytes on success, so the `vec![0u8; length]` zero-init is wasted
299        // work (measured ~25% overhead on multi-MB buffers). On failure we drop the
300        // Vec without ever calling `set_len`, so no uninitialised bytes are exposed.
301        let mut data: Vec<u8> = Vec::with_capacity(length);
302        unsafe {
303            let status = ffi::cm_block_buffer_copy_data_bytes(
304                self.0,
305                offset,
306                length,
307                data.as_mut_ptr().cast::<std::ffi::c_void>(),
308            );
309
310            if status == 0 {
311                data.set_len(length);
312                Some(data)
313            } else {
314                None
315            }
316        }
317    }
318
319    /// Copy data bytes from the buffer into an existing slice
320    ///
321    /// # Arguments
322    ///
323    /// * `offset` - Starting offset in the buffer
324    /// * `destination` - Mutable slice to copy data into
325    ///
326    /// # Errors
327    ///
328    /// Returns a Core Media error code if the copy fails.
329    ///
330    /// # Examples
331    ///
332    /// ```no_run
333    /// use screencapturekit::cm::CMBlockBuffer;
334    ///
335    /// fn read_header(buffer: &CMBlockBuffer) -> Result<[u8; 4], i32> {
336    ///     let mut header = [0u8; 4];
337    ///     buffer.copy_data_bytes_into(0, &mut header)?;
338    ///     Ok(header)
339    /// }
340    /// ```
341    pub fn copy_data_bytes_into(&self, offset: usize, destination: &mut [u8]) -> Result<(), i32> {
342        if destination.is_empty() {
343            return Ok(());
344        }
345
346        unsafe {
347            let status = ffi::cm_block_buffer_copy_data_bytes(
348                self.0,
349                offset,
350                destination.len(),
351                destination.as_mut_ptr().cast::<std::ffi::c_void>(),
352            );
353
354            if status == 0 {
355                Ok(())
356            } else {
357                Err(status)
358            }
359        }
360    }
361
362    /// Get a slice view of the data if the entire buffer is contiguous
363    ///
364    /// This is a zero-copy way to access the data, but only works if the
365    /// buffer's data is stored contiguously in memory.
366    ///
367    /// # Returns
368    ///
369    /// `Some(&[u8])` if the buffer is contiguous, `None` otherwise.
370    ///
371    /// # Examples
372    ///
373    /// ```no_run
374    /// use screencapturekit::cm::CMBlockBuffer;
375    ///
376    /// fn process_contiguous(buffer: &CMBlockBuffer) {
377    ///     if let Some(data) = buffer.as_slice() {
378    ///         println!("Processing {} contiguous bytes", data.len());
379    ///     } else {
380    ///         // Fall back to copying
381    ///         if let Some(data) = buffer.copy_data_bytes(0, buffer.data_length()) {
382    ///             println!("Processing {} copied bytes", data.len());
383    ///         }
384    ///     }
385    /// }
386    /// ```
387    pub fn as_slice(&self) -> Option<&[u8]> {
388        let len = self.data_length();
389        if len == 0 {
390            return Some(&[]);
391        }
392
393        // Check if the entire buffer is contiguous
394        if !self.is_range_contiguous(0, len) {
395            return None;
396        }
397
398        self.data_pointer(0).map(|(ptr, length)| {
399            // Use the minimum of reported length and data_length for safety
400            let safe_len = length.min(len);
401            unsafe { std::slice::from_raw_parts(ptr, safe_len) }
402        })
403    }
404
405    /// Access buffer with a standard `std::io::Cursor`
406    ///
407    /// Returns a cursor over a copy of the buffer data. The cursor implements
408    /// `Read` and `Seek` traits for convenient sequential data access.
409    ///
410    /// Note: This copies the data because `CMBlockBuffer` may not be contiguous.
411    /// For zero-copy access to contiguous buffers, use [`as_slice()`](Self::as_slice).
412    ///
413    /// # Returns
414    ///
415    /// `Some(Cursor)` if data could be copied, `None` if the copy failed.
416    ///
417    /// # Examples
418    ///
419    /// ```no_run
420    /// use std::io::{Read, Seek, SeekFrom};
421    /// use screencapturekit::cm::CMBlockBuffer;
422    ///
423    /// fn read_data(buffer: &CMBlockBuffer) {
424    ///     if let Some(mut cursor) = buffer.cursor() {
425    ///         // Read first 4 bytes
426    ///         let mut header = [0u8; 4];
427    ///         cursor.read_exact(&mut header).unwrap();
428    ///
429    ///         // Seek to a position
430    ///         cursor.seek(SeekFrom::Start(100)).unwrap();
431    ///
432    ///         // Read more data
433    ///         let mut buf = [0u8; 16];
434    ///         cursor.read_exact(&mut buf).unwrap();
435    ///     }
436    /// }
437    /// ```
438    pub fn cursor(&self) -> Option<io::Cursor<Vec<u8>>> {
439        // Try the zero-copy path first: if the buffer is contiguous we can hand
440        // out a `Vec` cloned from a borrowed slice (single allocation, no FFI
441        // round-trip), instead of going through `copy_data_bytes` which would
442        // call `CMBlockBufferCopyDataBytes` even though every byte is already
443        // reachable in process. For discontiguous buffers we fall back to the
444        // FFI copy path.
445        if let Some(slice) = self.as_slice() {
446            return Some(io::Cursor::new(slice.to_vec()));
447        }
448        self.copy_data_bytes(0, self.data_length())
449            .map(io::Cursor::new)
450    }
451
452    /// Access contiguous buffer with a zero-copy `std::io::Cursor`
453    ///
454    /// Returns a cursor over the buffer data without copying, but only works
455    /// if the buffer is contiguous in memory.
456    ///
457    /// # Returns
458    ///
459    /// `Some(Cursor)` if the buffer is contiguous, `None` otherwise.
460    ///
461    /// # Examples
462    ///
463    /// ```no_run
464    /// use std::io::{Read, Seek, SeekFrom};
465    /// use screencapturekit::cm::CMBlockBuffer;
466    ///
467    /// fn read_contiguous(buffer: &CMBlockBuffer) {
468    ///     // Try zero-copy first
469    ///     if let Some(mut cursor) = buffer.cursor_ref() {
470    ///         let mut header = [0u8; 4];
471    ///         cursor.read_exact(&mut header).unwrap();
472    ///     } else {
473    ///         // Fall back to copying cursor
474    ///         if let Some(mut cursor) = buffer.cursor() {
475    ///             let mut header = [0u8; 4];
476    ///             cursor.read_exact(&mut header).unwrap();
477    ///         }
478    ///     }
479    /// }
480    /// ```
481    pub fn cursor_ref(&self) -> Option<io::Cursor<&[u8]>> {
482        self.as_slice().map(io::Cursor::new)
483    }
484}
485
486impl Clone for CMBlockBuffer {
487    fn clone(&self) -> Self {
488        unsafe {
489            let ptr = ffi::cm_block_buffer_retain(self.0);
490            Self(ptr)
491        }
492    }
493}
494
495impl Drop for CMBlockBuffer {
496    fn drop(&mut self) {
497        if !self.0.is_null() {
498            unsafe {
499                ffi::cm_block_buffer_release(self.0);
500            }
501        }
502    }
503}
504
505unsafe impl Send for CMBlockBuffer {}
506unsafe impl Sync for CMBlockBuffer {}
507
508impl std::fmt::Debug for CMBlockBuffer {
509    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
510        f.debug_struct("CMBlockBuffer")
511            .field("ptr", &self.0)
512            .field("data_length", &self.data_length())
513            .field("is_empty", &self.is_empty())
514            .finish()
515    }
516}
517
518impl std::fmt::Display for CMBlockBuffer {
519    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
520        write!(f, "CMBlockBuffer({} bytes)", self.data_length())
521    }
522}