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