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 let mut data = vec![0u8; length];
298 unsafe {
299 let status = ffi::cm_block_buffer_copy_data_bytes(
300 self.0,
301 offset,
302 length,
303 data.as_mut_ptr().cast::<std::ffi::c_void>(),
304 );
305
306 if status == 0 {
307 Some(data)
308 } else {
309 None
310 }
311 }
312 }
313
314 /// Copy data bytes from the buffer into an existing slice
315 ///
316 /// # Arguments
317 ///
318 /// * `offset` - Starting offset in the buffer
319 /// * `destination` - Mutable slice to copy data into
320 ///
321 /// # Errors
322 ///
323 /// Returns a Core Media error code if the copy fails.
324 ///
325 /// # Examples
326 ///
327 /// ```no_run
328 /// use screencapturekit::cm::CMBlockBuffer;
329 ///
330 /// fn read_header(buffer: &CMBlockBuffer) -> Result<[u8; 4], i32> {
331 /// let mut header = [0u8; 4];
332 /// buffer.copy_data_bytes_into(0, &mut header)?;
333 /// Ok(header)
334 /// }
335 /// ```
336 pub fn copy_data_bytes_into(&self, offset: usize, destination: &mut [u8]) -> Result<(), i32> {
337 if destination.is_empty() {
338 return Ok(());
339 }
340
341 unsafe {
342 let status = ffi::cm_block_buffer_copy_data_bytes(
343 self.0,
344 offset,
345 destination.len(),
346 destination.as_mut_ptr().cast::<std::ffi::c_void>(),
347 );
348
349 if status == 0 {
350 Ok(())
351 } else {
352 Err(status)
353 }
354 }
355 }
356
357 /// Get a slice view of the data if the entire buffer is contiguous
358 ///
359 /// This is a zero-copy way to access the data, but only works if the
360 /// buffer's data is stored contiguously in memory.
361 ///
362 /// # Returns
363 ///
364 /// `Some(&[u8])` if the buffer is contiguous, `None` otherwise.
365 ///
366 /// # Examples
367 ///
368 /// ```no_run
369 /// use screencapturekit::cm::CMBlockBuffer;
370 ///
371 /// fn process_contiguous(buffer: &CMBlockBuffer) {
372 /// if let Some(data) = buffer.as_slice() {
373 /// println!("Processing {} contiguous bytes", data.len());
374 /// } else {
375 /// // Fall back to copying
376 /// if let Some(data) = buffer.copy_data_bytes(0, buffer.data_length()) {
377 /// println!("Processing {} copied bytes", data.len());
378 /// }
379 /// }
380 /// }
381 /// ```
382 pub fn as_slice(&self) -> Option<&[u8]> {
383 let len = self.data_length();
384 if len == 0 {
385 return Some(&[]);
386 }
387
388 // Check if the entire buffer is contiguous
389 if !self.is_range_contiguous(0, len) {
390 return None;
391 }
392
393 self.data_pointer(0).map(|(ptr, length)| {
394 // Use the minimum of reported length and data_length for safety
395 let safe_len = length.min(len);
396 unsafe { std::slice::from_raw_parts(ptr, safe_len) }
397 })
398 }
399
400 /// Access buffer with a standard `std::io::Cursor`
401 ///
402 /// Returns a cursor over a copy of the buffer data. The cursor implements
403 /// `Read` and `Seek` traits for convenient sequential data access.
404 ///
405 /// Note: This copies the data because `CMBlockBuffer` may not be contiguous.
406 /// For zero-copy access to contiguous buffers, use [`as_slice()`](Self::as_slice).
407 ///
408 /// # Returns
409 ///
410 /// `Some(Cursor)` if data could be copied, `None` if the copy failed.
411 ///
412 /// # Examples
413 ///
414 /// ```no_run
415 /// use std::io::{Read, Seek, SeekFrom};
416 /// use screencapturekit::cm::CMBlockBuffer;
417 ///
418 /// fn read_data(buffer: &CMBlockBuffer) {
419 /// if let Some(mut cursor) = buffer.cursor() {
420 /// // Read first 4 bytes
421 /// let mut header = [0u8; 4];
422 /// cursor.read_exact(&mut header).unwrap();
423 ///
424 /// // Seek to a position
425 /// cursor.seek(SeekFrom::Start(100)).unwrap();
426 ///
427 /// // Read more data
428 /// let mut buf = [0u8; 16];
429 /// cursor.read_exact(&mut buf).unwrap();
430 /// }
431 /// }
432 /// ```
433 pub fn cursor(&self) -> Option<io::Cursor<Vec<u8>>> {
434 self.copy_data_bytes(0, self.data_length())
435 .map(io::Cursor::new)
436 }
437
438 /// Access contiguous buffer with a zero-copy `std::io::Cursor`
439 ///
440 /// Returns a cursor over the buffer data without copying, but only works
441 /// if the buffer is contiguous in memory.
442 ///
443 /// # Returns
444 ///
445 /// `Some(Cursor)` if the buffer is contiguous, `None` otherwise.
446 ///
447 /// # Examples
448 ///
449 /// ```no_run
450 /// use std::io::{Read, Seek, SeekFrom};
451 /// use screencapturekit::cm::CMBlockBuffer;
452 ///
453 /// fn read_contiguous(buffer: &CMBlockBuffer) {
454 /// // Try zero-copy first
455 /// if let Some(mut cursor) = buffer.cursor_ref() {
456 /// let mut header = [0u8; 4];
457 /// cursor.read_exact(&mut header).unwrap();
458 /// } else {
459 /// // Fall back to copying cursor
460 /// if let Some(mut cursor) = buffer.cursor() {
461 /// let mut header = [0u8; 4];
462 /// cursor.read_exact(&mut header).unwrap();
463 /// }
464 /// }
465 /// }
466 /// ```
467 pub fn cursor_ref(&self) -> Option<io::Cursor<&[u8]>> {
468 self.as_slice().map(io::Cursor::new)
469 }
470}
471
472impl Clone for CMBlockBuffer {
473 fn clone(&self) -> Self {
474 unsafe {
475 let ptr = ffi::cm_block_buffer_retain(self.0);
476 Self(ptr)
477 }
478 }
479}
480
481impl Drop for CMBlockBuffer {
482 fn drop(&mut self) {
483 if !self.0.is_null() {
484 unsafe {
485 ffi::cm_block_buffer_release(self.0);
486 }
487 }
488 }
489}
490
491unsafe impl Send for CMBlockBuffer {}
492unsafe impl Sync for CMBlockBuffer {}
493
494impl std::fmt::Debug for CMBlockBuffer {
495 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
496 f.debug_struct("CMBlockBuffer")
497 .field("ptr", &self.0)
498 .field("data_length", &self.data_length())
499 .field("is_empty", &self.is_empty())
500 .finish()
501 }
502}
503
504impl std::fmt::Display for CMBlockBuffer {
505 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
506 write!(f, "CMBlockBuffer({} bytes)", self.data_length())
507 }
508}