Skip to main content

apple_cf/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 crate::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 apple_cf::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 apple_cf::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 apple_cf::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 const 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    #[must_use]
141    pub const fn as_ptr(&self) -> *mut std::ffi::c_void {
142        self.0
143    }
144
145    /// Get the total data length of the buffer in bytes
146    ///
147    /// # Examples
148    ///
149    /// ```no_run
150    /// use apple_cf::cm::CMBlockBuffer;
151    ///
152    /// fn check_size(buffer: &CMBlockBuffer) {
153    ///     let size = buffer.data_length();
154    ///     println!("Buffer contains {} bytes", size);
155    /// }
156    /// ```
157    #[must_use]
158    pub fn data_length(&self) -> usize {
159        unsafe { ffi::cm_block_buffer_get_data_length(self.0) }
160    }
161
162    /// Check if the buffer is empty (contains no data)
163    ///
164    /// # Examples
165    ///
166    /// ```no_run
167    /// use apple_cf::cm::CMBlockBuffer;
168    ///
169    /// fn process(buffer: &CMBlockBuffer) {
170    ///     if buffer.is_empty() {
171    ///         println!("No data to process");
172    ///         return;
173    ///     }
174    ///     // Process data...
175    /// }
176    /// ```
177    #[must_use]
178    pub fn is_empty(&self) -> bool {
179        unsafe { ffi::cm_block_buffer_is_empty(self.0) }
180    }
181
182    /// Check if a range of bytes is stored contiguously in memory
183    ///
184    /// # Arguments
185    ///
186    /// * `offset` - Starting offset in the buffer
187    /// * `length` - Length of the range to check
188    ///
189    /// # Returns
190    ///
191    /// `true` if the specified range is contiguous in memory
192    #[must_use]
193    pub fn is_range_contiguous(&self, offset: usize, length: usize) -> bool {
194        unsafe { ffi::cm_block_buffer_is_range_contiguous(self.0, offset, length) }
195    }
196
197    /// Get a pointer to the data at the specified offset
198    ///
199    /// Returns a tuple of (data pointer, length available at that offset) if successful.
200    /// The pointer is valid as long as this `CMBlockBuffer` is retained.
201    ///
202    /// # Arguments
203    ///
204    /// * `offset` - Byte offset into the buffer
205    ///
206    /// # Returns
207    ///
208    /// `Some((pointer, length_at_offset))` if the data pointer was obtained successfully,
209    /// `None` if the operation failed.
210    ///
211    /// # Examples
212    ///
213    /// ```no_run
214    /// use apple_cf::cm::CMBlockBuffer;
215    ///
216    /// fn read_data(buffer: &CMBlockBuffer) {
217    ///     if let Some((ptr, length)) = buffer.data_pointer(0) {
218    ///         // SAFETY: ptr is valid for `length` bytes while buffer is alive
219    ///         let slice = unsafe { std::slice::from_raw_parts(ptr, length) };
220    ///         println!("First byte: {:02x}", slice[0]);
221    ///     }
222    /// }
223    /// ```
224    #[must_use]
225    pub fn data_pointer(&self, offset: usize) -> Option<(*const u8, usize)> {
226        unsafe {
227            let mut length_at_offset: usize = 0;
228            let mut total_length: usize = 0;
229            let mut data_pointer: *mut std::ffi::c_void = std::ptr::null_mut();
230
231            let status = ffi::cm_block_buffer_get_data_pointer(
232                self.0,
233                offset,
234                &mut length_at_offset,
235                &mut total_length,
236                &mut data_pointer,
237            );
238
239            if status == 0 && !data_pointer.is_null() {
240                Some((data_pointer.cast::<u8>().cast_const(), length_at_offset))
241            } else {
242                None
243            }
244        }
245    }
246
247    /// Get a mutable pointer to the data at the specified offset
248    ///
249    /// # Safety
250    ///
251    /// The caller must ensure that modifying the data is safe and that no other
252    /// references to this data exist.
253    #[must_use]
254    pub unsafe fn data_pointer_mut(&self, offset: usize) -> Option<(*mut u8, usize)> {
255        let mut length_at_offset: usize = 0;
256        let mut total_length: usize = 0;
257        let mut data_pointer: *mut std::ffi::c_void = std::ptr::null_mut();
258
259        let status = ffi::cm_block_buffer_get_data_pointer(
260            self.0,
261            offset,
262            &mut length_at_offset,
263            &mut total_length,
264            &mut data_pointer,
265        );
266
267        if status == 0 && !data_pointer.is_null() {
268            Some((data_pointer.cast::<u8>(), length_at_offset))
269        } else {
270            None
271        }
272    }
273
274    /// Copy data bytes from the buffer into a new `Vec<u8>`
275    ///
276    /// This is the safest way to access buffer data as it copies the bytes
277    /// into owned memory.
278    ///
279    /// # Arguments
280    ///
281    /// * `offset` - Starting offset in the buffer
282    /// * `length` - Number of bytes to copy
283    ///
284    /// # Returns
285    ///
286    /// `Some(Vec<u8>)` containing the copied data, or `None` if the copy failed.
287    ///
288    /// # Examples
289    ///
290    /// ```no_run
291    /// use apple_cf::cm::CMBlockBuffer;
292    ///
293    /// fn extract_data(buffer: &CMBlockBuffer) -> Option<Vec<u8>> {
294    ///     // Copy all data from the buffer
295    ///     buffer.copy_data_bytes(0, buffer.data_length())
296    /// }
297    /// ```
298    #[must_use]
299    pub fn copy_data_bytes(&self, offset: usize, length: usize) -> Option<Vec<u8>> {
300        if length == 0 {
301            return Some(Vec::new());
302        }
303
304        // Allocate uninitialised — `cm_block_buffer_copy_data_bytes` writes the full
305        // `length` bytes on success, so the `vec![0u8; length]` zero-init is wasted
306        // work (measured ~25% overhead on multi-MB buffers). On failure we drop the
307        // Vec without ever calling `set_len`, so no uninitialised bytes are exposed.
308        let mut data: Vec<u8> = Vec::with_capacity(length);
309        unsafe {
310            let status = ffi::cm_block_buffer_copy_data_bytes(
311                self.0,
312                offset,
313                length,
314                data.as_mut_ptr().cast::<std::ffi::c_void>(),
315            );
316
317            if status == 0 {
318                data.set_len(length);
319                Some(data)
320            } else {
321                None
322            }
323        }
324    }
325
326    /// Copy data bytes from the buffer into an existing slice
327    ///
328    /// # Arguments
329    ///
330    /// * `offset` - Starting offset in the buffer
331    /// * `destination` - Mutable slice to copy data into
332    ///
333    /// # Errors
334    ///
335    /// Returns a Core Media error code if the copy fails.
336    ///
337    /// # Examples
338    ///
339    /// ```no_run
340    /// use apple_cf::cm::CMBlockBuffer;
341    ///
342    /// fn read_header(buffer: &CMBlockBuffer) -> Result<[u8; 4], i32> {
343    ///     let mut header = [0u8; 4];
344    ///     buffer.copy_data_bytes_into(0, &mut header)?;
345    ///     Ok(header)
346    /// }
347    /// ```
348    pub fn copy_data_bytes_into(&self, offset: usize, destination: &mut [u8]) -> Result<(), i32> {
349        if destination.is_empty() {
350            return Ok(());
351        }
352
353        unsafe {
354            let status = ffi::cm_block_buffer_copy_data_bytes(
355                self.0,
356                offset,
357                destination.len(),
358                destination.as_mut_ptr().cast::<std::ffi::c_void>(),
359            );
360
361            if status == 0 {
362                Ok(())
363            } else {
364                Err(status)
365            }
366        }
367    }
368
369    /// Get a slice view of the data if the entire buffer is contiguous
370    ///
371    /// This is a zero-copy way to access the data, but only works if the
372    /// buffer's data is stored contiguously in memory.
373    ///
374    /// # Returns
375    ///
376    /// `Some(&[u8])` if the buffer is contiguous, `None` otherwise.
377    ///
378    /// # Examples
379    ///
380    /// ```no_run
381    /// use apple_cf::cm::CMBlockBuffer;
382    ///
383    /// fn process_contiguous(buffer: &CMBlockBuffer) {
384    ///     if let Some(data) = buffer.as_slice() {
385    ///         println!("Processing {} contiguous bytes", data.len());
386    ///     } else {
387    ///         // Fall back to copying
388    ///         if let Some(data) = buffer.copy_data_bytes(0, buffer.data_length()) {
389    ///             println!("Processing {} copied bytes", data.len());
390    ///         }
391    ///     }
392    /// }
393    /// ```
394    #[must_use]
395    pub fn as_slice(&self) -> Option<&[u8]> {
396        let len = self.data_length();
397        if len == 0 {
398            return Some(&[]);
399        }
400
401        // Check if the entire buffer is contiguous
402        if !self.is_range_contiguous(0, len) {
403            return None;
404        }
405
406        self.data_pointer(0).map(|(ptr, length)| {
407            // Use the minimum of reported length and data_length for safety
408            let safe_len = length.min(len);
409            unsafe { std::slice::from_raw_parts(ptr, safe_len) }
410        })
411    }
412
413    /// Access buffer with a standard `std::io::Cursor`
414    ///
415    /// Returns a cursor over a copy of the buffer data. The cursor implements
416    /// `Read` and `Seek` traits for convenient sequential data access.
417    ///
418    /// Note: This copies the data because `CMBlockBuffer` may not be contiguous.
419    /// For zero-copy access to contiguous buffers, use [`as_slice()`](Self::as_slice).
420    ///
421    /// # Returns
422    ///
423    /// `Some(Cursor)` if data could be copied, `None` if the copy failed.
424    ///
425    /// # Examples
426    ///
427    /// ```no_run
428    /// use std::io::{Read, Seek, SeekFrom};
429    /// use apple_cf::cm::CMBlockBuffer;
430    ///
431    /// fn read_data(buffer: &CMBlockBuffer) {
432    ///     if let Some(mut cursor) = buffer.cursor() {
433    ///         // Read first 4 bytes
434    ///         let mut header = [0u8; 4];
435    ///         cursor.read_exact(&mut header).unwrap();
436    ///
437    ///         // Seek to a position
438    ///         cursor.seek(SeekFrom::Start(100)).unwrap();
439    ///
440    ///         // Read more data
441    ///         let mut buf = [0u8; 16];
442    ///         cursor.read_exact(&mut buf).unwrap();
443    ///     }
444    /// }
445    /// ```
446    pub fn cursor(&self) -> Option<io::Cursor<Vec<u8>>> {
447        // Try the zero-copy path first: if the buffer is contiguous we can hand
448        // out a `Vec` cloned from a borrowed slice (single allocation, no FFI
449        // round-trip), instead of going through `copy_data_bytes` which would
450        // call `CMBlockBufferCopyDataBytes` even though every byte is already
451        // reachable in process. For discontiguous buffers we fall back to the
452        // FFI copy path.
453        if let Some(slice) = self.as_slice() {
454            return Some(io::Cursor::new(slice.to_vec()));
455        }
456        self.copy_data_bytes(0, self.data_length())
457            .map(io::Cursor::new)
458    }
459
460    /// Access contiguous buffer with a zero-copy `std::io::Cursor`
461    ///
462    /// Returns a cursor over the buffer data without copying, but only works
463    /// if the buffer is contiguous in memory.
464    ///
465    /// # Returns
466    ///
467    /// `Some(Cursor)` if the buffer is contiguous, `None` otherwise.
468    ///
469    /// # Examples
470    ///
471    /// ```no_run
472    /// use std::io::{Read, Seek, SeekFrom};
473    /// use apple_cf::cm::CMBlockBuffer;
474    ///
475    /// fn read_contiguous(buffer: &CMBlockBuffer) {
476    ///     // Try zero-copy first
477    ///     if let Some(mut cursor) = buffer.cursor_ref() {
478    ///         let mut header = [0u8; 4];
479    ///         cursor.read_exact(&mut header).unwrap();
480    ///     } else {
481    ///         // Fall back to copying cursor
482    ///         if let Some(mut cursor) = buffer.cursor() {
483    ///             let mut header = [0u8; 4];
484    ///             cursor.read_exact(&mut header).unwrap();
485    ///         }
486    ///     }
487    /// }
488    /// ```
489    pub fn cursor_ref(&self) -> Option<io::Cursor<&[u8]>> {
490        self.as_slice().map(io::Cursor::new)
491    }
492}
493
494impl Clone for CMBlockBuffer {
495    fn clone(&self) -> Self {
496        unsafe {
497            let ptr = ffi::cm_block_buffer_retain(self.0);
498            Self(ptr)
499        }
500    }
501}
502
503impl Drop for CMBlockBuffer {
504    fn drop(&mut self) {
505        if !self.0.is_null() {
506            unsafe {
507                ffi::cm_block_buffer_release(self.0);
508            }
509        }
510    }
511}
512
513// SAFETY: `CMBlockBufferRef` is a Core Foundation type; Apple documents its
514// retain/release operations as thread-safe.  Our wrapper never mutates the
515// data behind the pointer.
516unsafe impl Send for CMBlockBuffer {}
517unsafe impl Sync for CMBlockBuffer {}
518
519impl std::fmt::Debug for CMBlockBuffer {
520    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
521        f.debug_struct("CMBlockBuffer")
522            .field("ptr", &self.0)
523            .field("data_length", &self.data_length())
524            .field("is_empty", &self.is_empty())
525            .finish()
526    }
527}
528
529impl std::fmt::Display for CMBlockBuffer {
530    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
531        write!(f, "CMBlockBuffer({} bytes)", self.data_length())
532    }
533}