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