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