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