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}