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}