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}