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            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(&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                &mut length_at_offset,
260                &mut total_length,
261                &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            &mut length_at_offset,
288            &mut total_length,
289            &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) = 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    pub fn as_slice(&self) -> Option<&[u8]> {
421        let len = self.data_length();
422        if len == 0 {
423            return Some(&[]);
424        }
425
426        // Check if the entire buffer is contiguous
427        if !self.is_range_contiguous(0, len) {
428            return None;
429        }
430
431        self.data_pointer(0).map(|(ptr, length)| {
432            // Use the minimum of reported length and data_length for safety
433            let safe_len = length.min(len);
434            unsafe { std::slice::from_raw_parts(ptr, safe_len) }
435        })
436    }
437
438    /// Access buffer with a standard `std::io::Cursor`
439    ///
440    /// Returns a cursor over a copy of the buffer data. The cursor implements
441    /// `Read` and `Seek` traits for convenient sequential data access.
442    ///
443    /// Note: This copies the data because `CMBlockBuffer` may not be contiguous.
444    /// For zero-copy access to contiguous buffers, use [`as_slice()`](Self::as_slice).
445    ///
446    /// # Returns
447    ///
448    /// `Some(Cursor)` if data could be copied, `None` if the copy failed.
449    ///
450    /// # Examples
451    ///
452    /// ```no_run
453    /// use std::io::{Read, Seek, SeekFrom};
454    /// use apple_cf::cm::CMBlockBuffer;
455    ///
456    /// fn read_data(buffer: &CMBlockBuffer) {
457    ///     if let Some(mut cursor) = buffer.cursor() {
458    ///         // Read first 4 bytes
459    ///         let mut header = [0u8; 4];
460    ///         cursor.read_exact(&mut header).unwrap();
461    ///
462    ///         // Seek to a position
463    ///         cursor.seek(SeekFrom::Start(100)).unwrap();
464    ///
465    ///         // Read more data
466    ///         let mut buf = [0u8; 16];
467    ///         cursor.read_exact(&mut buf).unwrap();
468    ///     }
469    /// }
470    /// ```
471    pub fn cursor(&self) -> Option<io::Cursor<Vec<u8>>> {
472        // Try the zero-copy path first: if the buffer is contiguous we can hand
473        // out a `Vec` cloned from a borrowed slice (single allocation, no FFI
474        // round-trip), instead of going through `copy_data_bytes` which would
475        // call `CMBlockBufferCopyDataBytes` even though every byte is already
476        // reachable in process. For discontiguous buffers we fall back to the
477        // FFI copy path.
478        if let Some(slice) = self.as_slice() {
479            return Some(io::Cursor::new(slice.to_vec()));
480        }
481        self.copy_data_bytes(0, self.data_length())
482            .map(io::Cursor::new)
483    }
484
485    /// Access contiguous buffer with a zero-copy `std::io::Cursor`
486    ///
487    /// Returns a cursor over the buffer data without copying, but only works
488    /// if the buffer is contiguous in memory.
489    ///
490    /// # Returns
491    ///
492    /// `Some(Cursor)` if the buffer is contiguous, `None` otherwise.
493    ///
494    /// # Examples
495    ///
496    /// ```no_run
497    /// use std::io::{Read, Seek, SeekFrom};
498    /// use apple_cf::cm::CMBlockBuffer;
499    ///
500    /// fn read_contiguous(buffer: &CMBlockBuffer) {
501    ///     // Try zero-copy first
502    ///     if let Some(mut cursor) = buffer.cursor_ref() {
503    ///         let mut header = [0u8; 4];
504    ///         cursor.read_exact(&mut header).unwrap();
505    ///     } else {
506    ///         // Fall back to copying cursor
507    ///         if let Some(mut cursor) = buffer.cursor() {
508    ///             let mut header = [0u8; 4];
509    ///             cursor.read_exact(&mut header).unwrap();
510    ///         }
511    ///     }
512    /// }
513    /// ```
514    pub fn cursor_ref(&self) -> Option<io::Cursor<&[u8]>> {
515        self.as_slice().map(io::Cursor::new)
516    }
517}
518
519crate::utils::retained::cf_retained!(
520    CMBlockBuffer,
521    retain = ffi::cm_block_buffer_retain,
522    release = ffi::cm_block_buffer_release,
523);
524
525// SAFETY: `CMBlockBufferRef` is a Core Foundation type; Apple documents its
526// retain/release operations as thread-safe.  Our wrapper never mutates the
527// data behind the pointer.
528unsafe impl Send for CMBlockBuffer {}
529unsafe impl Sync for CMBlockBuffer {}
530
531impl std::fmt::Debug for CMBlockBuffer {
532    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
533        f.debug_struct("CMBlockBuffer")
534            .field("ptr", &self.0)
535            .field("data_length", &self.data_length())
536            .field("is_empty", &self.is_empty())
537            .finish()
538    }
539}
540
541impl std::fmt::Display for CMBlockBuffer {
542    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
543        write!(f, "CMBlockBuffer({} bytes)", self.data_length())
544    }
545}