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        let mut data = vec![0u8; length];
298        unsafe {
299            let status = ffi::cm_block_buffer_copy_data_bytes(
300                self.0,
301                offset,
302                length,
303                data.as_mut_ptr().cast::<std::ffi::c_void>(),
304            );
305
306            if status == 0 {
307                Some(data)
308            } else {
309                None
310            }
311        }
312    }
313
314    /// Copy data bytes from the buffer into an existing slice
315    ///
316    /// # Arguments
317    ///
318    /// * `offset` - Starting offset in the buffer
319    /// * `destination` - Mutable slice to copy data into
320    ///
321    /// # Errors
322    ///
323    /// Returns a Core Media error code if the copy fails.
324    ///
325    /// # Examples
326    ///
327    /// ```no_run
328    /// use screencapturekit::cm::CMBlockBuffer;
329    ///
330    /// fn read_header(buffer: &CMBlockBuffer) -> Result<[u8; 4], i32> {
331    ///     let mut header = [0u8; 4];
332    ///     buffer.copy_data_bytes_into(0, &mut header)?;
333    ///     Ok(header)
334    /// }
335    /// ```
336    pub fn copy_data_bytes_into(&self, offset: usize, destination: &mut [u8]) -> Result<(), i32> {
337        if destination.is_empty() {
338            return Ok(());
339        }
340
341        unsafe {
342            let status = ffi::cm_block_buffer_copy_data_bytes(
343                self.0,
344                offset,
345                destination.len(),
346                destination.as_mut_ptr().cast::<std::ffi::c_void>(),
347            );
348
349            if status == 0 {
350                Ok(())
351            } else {
352                Err(status)
353            }
354        }
355    }
356
357    /// Get a slice view of the data if the entire buffer is contiguous
358    ///
359    /// This is a zero-copy way to access the data, but only works if the
360    /// buffer's data is stored contiguously in memory.
361    ///
362    /// # Returns
363    ///
364    /// `Some(&[u8])` if the buffer is contiguous, `None` otherwise.
365    ///
366    /// # Examples
367    ///
368    /// ```no_run
369    /// use screencapturekit::cm::CMBlockBuffer;
370    ///
371    /// fn process_contiguous(buffer: &CMBlockBuffer) {
372    ///     if let Some(data) = buffer.as_slice() {
373    ///         println!("Processing {} contiguous bytes", data.len());
374    ///     } else {
375    ///         // Fall back to copying
376    ///         if let Some(data) = buffer.copy_data_bytes(0, buffer.data_length()) {
377    ///             println!("Processing {} copied bytes", data.len());
378    ///         }
379    ///     }
380    /// }
381    /// ```
382    pub fn as_slice(&self) -> Option<&[u8]> {
383        let len = self.data_length();
384        if len == 0 {
385            return Some(&[]);
386        }
387
388        // Check if the entire buffer is contiguous
389        if !self.is_range_contiguous(0, len) {
390            return None;
391        }
392
393        self.data_pointer(0).map(|(ptr, length)| {
394            // Use the minimum of reported length and data_length for safety
395            let safe_len = length.min(len);
396            unsafe { std::slice::from_raw_parts(ptr, safe_len) }
397        })
398    }
399
400    /// Access buffer with a standard `std::io::Cursor`
401    ///
402    /// Returns a cursor over a copy of the buffer data. The cursor implements
403    /// `Read` and `Seek` traits for convenient sequential data access.
404    ///
405    /// Note: This copies the data because `CMBlockBuffer` may not be contiguous.
406    /// For zero-copy access to contiguous buffers, use [`as_slice()`](Self::as_slice).
407    ///
408    /// # Returns
409    ///
410    /// `Some(Cursor)` if data could be copied, `None` if the copy failed.
411    ///
412    /// # Examples
413    ///
414    /// ```no_run
415    /// use std::io::{Read, Seek, SeekFrom};
416    /// use screencapturekit::cm::CMBlockBuffer;
417    ///
418    /// fn read_data(buffer: &CMBlockBuffer) {
419    ///     if let Some(mut cursor) = buffer.cursor() {
420    ///         // Read first 4 bytes
421    ///         let mut header = [0u8; 4];
422    ///         cursor.read_exact(&mut header).unwrap();
423    ///
424    ///         // Seek to a position
425    ///         cursor.seek(SeekFrom::Start(100)).unwrap();
426    ///
427    ///         // Read more data
428    ///         let mut buf = [0u8; 16];
429    ///         cursor.read_exact(&mut buf).unwrap();
430    ///     }
431    /// }
432    /// ```
433    pub fn cursor(&self) -> Option<io::Cursor<Vec<u8>>> {
434        self.copy_data_bytes(0, self.data_length())
435            .map(io::Cursor::new)
436    }
437
438    /// Access contiguous buffer with a zero-copy `std::io::Cursor`
439    ///
440    /// Returns a cursor over the buffer data without copying, but only works
441    /// if the buffer is contiguous in memory.
442    ///
443    /// # Returns
444    ///
445    /// `Some(Cursor)` if the buffer is contiguous, `None` otherwise.
446    ///
447    /// # Examples
448    ///
449    /// ```no_run
450    /// use std::io::{Read, Seek, SeekFrom};
451    /// use screencapturekit::cm::CMBlockBuffer;
452    ///
453    /// fn read_contiguous(buffer: &CMBlockBuffer) {
454    ///     // Try zero-copy first
455    ///     if let Some(mut cursor) = buffer.cursor_ref() {
456    ///         let mut header = [0u8; 4];
457    ///         cursor.read_exact(&mut header).unwrap();
458    ///     } else {
459    ///         // Fall back to copying cursor
460    ///         if let Some(mut cursor) = buffer.cursor() {
461    ///             let mut header = [0u8; 4];
462    ///             cursor.read_exact(&mut header).unwrap();
463    ///         }
464    ///     }
465    /// }
466    /// ```
467    pub fn cursor_ref(&self) -> Option<io::Cursor<&[u8]>> {
468        self.as_slice().map(io::Cursor::new)
469    }
470}
471
472impl Clone for CMBlockBuffer {
473    fn clone(&self) -> Self {
474        unsafe {
475            let ptr = ffi::cm_block_buffer_retain(self.0);
476            Self(ptr)
477        }
478    }
479}
480
481impl Drop for CMBlockBuffer {
482    fn drop(&mut self) {
483        if !self.0.is_null() {
484            unsafe {
485                ffi::cm_block_buffer_release(self.0);
486            }
487        }
488    }
489}
490
491unsafe impl Send for CMBlockBuffer {}
492unsafe impl Sync for CMBlockBuffer {}
493
494impl std::fmt::Debug for CMBlockBuffer {
495    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
496        f.debug_struct("CMBlockBuffer")
497            .field("ptr", &self.0)
498            .field("data_length", &self.data_length())
499            .field("is_empty", &self.is_empty())
500            .finish()
501    }
502}
503
504impl std::fmt::Display for CMBlockBuffer {
505    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
506        write!(f, "CMBlockBuffer({} bytes)", self.data_length())
507    }
508}