screencapturekit/cm/
sample_buffer.rs

1//! `CMSampleBuffer` - Container for media samples
2
3use super::ffi;
4use super::{
5    AudioBuffer, AudioBufferList, AudioBufferListRaw, CMBlockBuffer, CMFormatDescription,
6    CMSampleTimingInfo, CMTime, CVPixelBuffer, SCFrameStatus,
7};
8use std::fmt;
9
10/// Opaque handle to `CMSampleBuffer`
11#[repr(transparent)]
12#[derive(Debug)]
13pub struct CMSampleBuffer(*mut std::ffi::c_void);
14
15impl PartialEq for CMSampleBuffer {
16    fn eq(&self, other: &Self) -> bool {
17        self.0 == other.0
18    }
19}
20
21impl Eq for CMSampleBuffer {}
22
23impl std::hash::Hash for CMSampleBuffer {
24    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
25        unsafe {
26            let hash_value = ffi::cm_sample_buffer_hash(self.0);
27            hash_value.hash(state);
28        }
29    }
30}
31
32impl CMSampleBuffer {
33    pub fn from_raw(ptr: *mut std::ffi::c_void) -> Option<Self> {
34        if ptr.is_null() {
35            None
36        } else {
37            Some(Self(ptr))
38        }
39    }
40
41    /// # Safety
42    /// The caller must ensure the pointer is a valid `CMSampleBuffer` pointer.
43    pub unsafe fn from_ptr(ptr: *mut std::ffi::c_void) -> Self {
44        Self(ptr)
45    }
46
47    pub fn as_ptr(&self) -> *mut std::ffi::c_void {
48        self.0
49    }
50
51    /// Create a sample buffer for an image buffer (video frame)
52    ///
53    /// # Arguments
54    ///
55    /// * `image_buffer` - The pixel buffer containing the video frame
56    /// * `presentation_time` - When the frame should be presented
57    /// * `duration` - How long the frame should be displayed
58    ///
59    /// # Errors
60    ///
61    /// Returns a Core Media error code if the sample buffer creation fails.
62    ///
63    /// # Examples
64    ///
65    /// ```
66    /// use screencapturekit::cm::{CMSampleBuffer, CVPixelBuffer, CMTime};
67    ///
68    /// // Create a pixel buffer
69    /// let pixel_buffer = CVPixelBuffer::create(1920, 1080, 0x42475241)
70    ///     .expect("Failed to create pixel buffer");
71    ///
72    /// // Create timing information (30fps video)
73    /// let presentation_time = CMTime::new(0, 30); // Frame 0 at 30 fps
74    /// let duration = CMTime::new(1, 30);          // 1/30th of a second
75    ///
76    /// // Create sample buffer
77    /// let sample = CMSampleBuffer::create_for_image_buffer(
78    ///     &pixel_buffer,
79    ///     presentation_time,
80    ///     duration,
81    /// ).expect("Failed to create sample buffer");
82    ///
83    /// assert!(sample.is_valid());
84    /// assert_eq!(sample.get_presentation_timestamp().value, 0);
85    /// assert_eq!(sample.get_presentation_timestamp().timescale, 30);
86    /// ```
87    pub fn create_for_image_buffer(
88        image_buffer: &CVPixelBuffer,
89        presentation_time: CMTime,
90        duration: CMTime,
91    ) -> Result<Self, i32> {
92        unsafe {
93            let mut sample_buffer_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
94            let status = ffi::cm_sample_buffer_create_for_image_buffer(
95                image_buffer.as_ptr(),
96                presentation_time.value,
97                presentation_time.timescale,
98                duration.value,
99                duration.timescale,
100                &mut sample_buffer_ptr,
101            );
102
103            if status == 0 && !sample_buffer_ptr.is_null() {
104                Ok(Self(sample_buffer_ptr))
105            } else {
106                Err(status)
107            }
108        }
109    }
110
111    pub fn get_image_buffer(&self) -> Option<CVPixelBuffer> {
112        unsafe {
113            let ptr = ffi::cm_sample_buffer_get_image_buffer(self.0);
114            CVPixelBuffer::from_raw(ptr)
115        }
116    }
117
118    /// Get the frame status from a sample buffer
119    ///
120    /// Returns the `SCFrameStatus` attachment from the sample buffer,
121    /// indicating whether the frame is complete, idle, blank, etc.
122    ///
123    /// # Examples
124    ///
125    /// ```no_run
126    /// use screencapturekit::cm::{CMSampleBuffer, SCFrameStatus};
127    ///
128    /// fn handle_frame(sample: CMSampleBuffer) {
129    ///     if let Some(status) = sample.get_frame_status() {
130    ///         match status {
131    ///             SCFrameStatus::Complete => {
132    ///                 println!("Frame is complete, process it");
133    ///             }
134    ///             SCFrameStatus::Idle => {
135    ///                 println!("Frame is idle, no changes");
136    ///             }
137    ///             _ => {
138    ///                 println!("Frame status: {}", status);
139    ///             }
140    ///         }
141    ///     }
142    /// }
143    /// ```
144    pub fn get_frame_status(&self) -> Option<SCFrameStatus> {
145        unsafe {
146            let status = ffi::cm_sample_buffer_get_frame_status(self.0);
147            if status >= 0 {
148                SCFrameStatus::from_raw(status)
149            } else {
150                None
151            }
152        }
153    }
154
155    pub fn get_presentation_timestamp(&self) -> CMTime {
156        unsafe {
157            let mut value: i64 = 0;
158            let mut timescale: i32 = 0;
159            let mut flags: u32 = 0;
160            let mut epoch: i64 = 0;
161            ffi::cm_sample_buffer_get_presentation_timestamp(
162                self.0,
163                &mut value,
164                &mut timescale,
165                &mut flags,
166                &mut epoch,
167            );
168            CMTime {
169                value,
170                timescale,
171                flags,
172                epoch,
173            }
174        }
175    }
176
177    pub fn get_duration(&self) -> CMTime {
178        unsafe {
179            let mut value: i64 = 0;
180            let mut timescale: i32 = 0;
181            let mut flags: u32 = 0;
182            let mut epoch: i64 = 0;
183            ffi::cm_sample_buffer_get_duration(
184                self.0,
185                &mut value,
186                &mut timescale,
187                &mut flags,
188                &mut epoch,
189            );
190            CMTime {
191                value,
192                timescale,
193                flags,
194                epoch,
195            }
196        }
197    }
198
199    pub fn is_valid(&self) -> bool {
200        unsafe { ffi::cm_sample_buffer_is_valid(self.0) }
201    }
202
203    pub fn get_num_samples(&self) -> usize {
204        unsafe { ffi::cm_sample_buffer_get_num_samples(self.0) }
205    }
206
207    pub fn get_audio_buffer_list(&self) -> Option<AudioBufferList> {
208        unsafe {
209            let mut num_buffers: u32 = 0;
210            let mut buffers_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
211            let mut buffers_len: usize = 0;
212
213            ffi::cm_sample_buffer_get_audio_buffer_list(
214                self.0,
215                &mut num_buffers,
216                &mut buffers_ptr,
217                &mut buffers_len,
218            );
219
220            if num_buffers == 0 {
221                None
222            } else {
223                Some(AudioBufferList {
224                    inner: AudioBufferListRaw {
225                        num_buffers,
226                        buffers_ptr: buffers_ptr.cast::<AudioBuffer>(),
227                        buffers_len,
228                    },
229                })
230            }
231        }
232    }
233
234    pub fn get_data_buffer(&self) -> Option<CMBlockBuffer> {
235        unsafe {
236            let ptr = ffi::cm_sample_buffer_get_data_buffer(self.0);
237            CMBlockBuffer::from_raw(ptr)
238        }
239    }
240
241    /// Get the decode timestamp of the sample buffer
242    pub fn get_decode_timestamp(&self) -> CMTime {
243        unsafe {
244            let mut value: i64 = 0;
245            let mut timescale: i32 = 0;
246            let mut flags: u32 = 0;
247            let mut epoch: i64 = 0;
248            ffi::cm_sample_buffer_get_decode_timestamp(
249                self.0,
250                &mut value,
251                &mut timescale,
252                &mut flags,
253                &mut epoch,
254            );
255            CMTime {
256                value,
257                timescale,
258                flags,
259                epoch,
260            }
261        }
262    }
263
264    /// Get the output presentation timestamp
265    pub fn get_output_presentation_timestamp(&self) -> CMTime {
266        unsafe {
267            let mut value: i64 = 0;
268            let mut timescale: i32 = 0;
269            let mut flags: u32 = 0;
270            let mut epoch: i64 = 0;
271            ffi::cm_sample_buffer_get_output_presentation_timestamp(
272                self.0,
273                &mut value,
274                &mut timescale,
275                &mut flags,
276                &mut epoch,
277            );
278            CMTime {
279                value,
280                timescale,
281                flags,
282                epoch,
283            }
284        }
285    }
286
287    /// Set the output presentation timestamp
288    ///
289    /// # Errors
290    ///
291    /// Returns a Core Media error code if the operation fails.
292    pub fn set_output_presentation_timestamp(&self, time: CMTime) -> Result<(), i32> {
293        unsafe {
294            let status = ffi::cm_sample_buffer_set_output_presentation_timestamp(
295                self.0,
296                time.value,
297                time.timescale,
298                time.flags,
299                time.epoch,
300            );
301            if status == 0 {
302                Ok(())
303            } else {
304                Err(status)
305            }
306        }
307    }
308
309    /// Get the size of a specific sample
310    pub fn get_sample_size(&self, index: usize) -> usize {
311        unsafe { ffi::cm_sample_buffer_get_sample_size(self.0, index) }
312    }
313
314    /// Get the total size of all samples
315    pub fn get_total_sample_size(&self) -> usize {
316        unsafe { ffi::cm_sample_buffer_get_total_sample_size(self.0) }
317    }
318
319    /// Check if the sample buffer data is ready for access
320    pub fn is_data_ready(&self) -> bool {
321        unsafe { ffi::cm_sample_buffer_is_ready_for_data_access(self.0) }
322    }
323
324    /// Make the sample buffer data ready for access
325    ///
326    /// # Errors
327    ///
328    /// Returns a Core Media error code if the operation fails.
329    pub fn make_data_ready(&self) -> Result<(), i32> {
330        unsafe {
331            let status = ffi::cm_sample_buffer_make_data_ready(self.0);
332            if status == 0 {
333                Ok(())
334            } else {
335                Err(status)
336            }
337        }
338    }
339
340    /// Get the format description
341    pub fn get_format_description(&self) -> Option<CMFormatDescription> {
342        unsafe {
343            let ptr = ffi::cm_sample_buffer_get_format_description(self.0);
344            CMFormatDescription::from_raw(ptr)
345        }
346    }
347
348    /// Get sample timing info for a specific sample
349    ///
350    /// # Errors
351    ///
352    /// Returns a Core Media error code if the timing info cannot be retrieved.
353    pub fn get_sample_timing_info(&self, index: usize) -> Result<CMSampleTimingInfo, i32> {
354        unsafe {
355            let mut timing_info = CMSampleTimingInfo {
356                duration: CMTime::INVALID,
357                presentation_time_stamp: CMTime::INVALID,
358                decode_time_stamp: CMTime::INVALID,
359            };
360            let status = ffi::cm_sample_buffer_get_sample_timing_info(
361                self.0,
362                index,
363                &mut timing_info.duration.value,
364                &mut timing_info.duration.timescale,
365                &mut timing_info.duration.flags,
366                &mut timing_info.duration.epoch,
367                &mut timing_info.presentation_time_stamp.value,
368                &mut timing_info.presentation_time_stamp.timescale,
369                &mut timing_info.presentation_time_stamp.flags,
370                &mut timing_info.presentation_time_stamp.epoch,
371                &mut timing_info.decode_time_stamp.value,
372                &mut timing_info.decode_time_stamp.timescale,
373                &mut timing_info.decode_time_stamp.flags,
374                &mut timing_info.decode_time_stamp.epoch,
375            );
376            if status == 0 {
377                Ok(timing_info)
378            } else {
379                Err(status)
380            }
381        }
382    }
383
384    /// Get all sample timing info as a vector
385    ///
386    /// # Errors
387    ///
388    /// Returns a Core Media error code if any timing info cannot be retrieved.
389    pub fn get_sample_timing_info_array(&self) -> Result<Vec<CMSampleTimingInfo>, i32> {
390        let num_samples = self.get_num_samples();
391        let mut result = Vec::with_capacity(num_samples);
392        for i in 0..num_samples {
393            result.push(self.get_sample_timing_info(i)?);
394        }
395        Ok(result)
396    }
397
398    /// Invalidate the sample buffer
399    ///
400    /// # Errors
401    ///
402    /// Returns a Core Media error code if the invalidation fails.
403    pub fn invalidate(&self) -> Result<(), i32> {
404        unsafe {
405            let status = ffi::cm_sample_buffer_invalidate(self.0);
406            if status == 0 {
407                Ok(())
408            } else {
409                Err(status)
410            }
411        }
412    }
413
414    /// Create a copy with new timing information
415    ///
416    /// # Errors
417    ///
418    /// Returns a Core Media error code if the copy cannot be created.
419    pub fn create_copy_with_new_timing(
420        &self,
421        timing_info: &[CMSampleTimingInfo],
422    ) -> Result<Self, i32> {
423        unsafe {
424            let mut new_buffer_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
425            let status = ffi::cm_sample_buffer_create_copy_with_new_timing(
426                self.0,
427                timing_info.len(),
428                timing_info.as_ptr().cast::<std::ffi::c_void>(),
429                &mut new_buffer_ptr,
430            );
431            if status == 0 && !new_buffer_ptr.is_null() {
432                Ok(Self(new_buffer_ptr))
433            } else {
434                Err(status)
435            }
436        }
437    }
438
439    /// Copy PCM audio data into an audio buffer list
440    ///
441    /// # Errors
442    ///
443    /// Returns a Core Media error code if the copy operation fails.
444    pub fn copy_pcm_data_into_audio_buffer_list(
445        &self,
446        frame_offset: i32,
447        num_frames: i32,
448        buffer_list: &mut AudioBufferList,
449    ) -> Result<(), i32> {
450        unsafe {
451            let status = ffi::cm_sample_buffer_copy_pcm_data_into_audio_buffer_list(
452                self.0,
453                frame_offset,
454                num_frames,
455                (buffer_list as *mut AudioBufferList).cast::<std::ffi::c_void>(),
456            );
457            if status == 0 {
458                Ok(())
459            } else {
460                Err(status)
461            }
462        }
463    }
464}
465
466impl Drop for CMSampleBuffer {
467    fn drop(&mut self) {
468        unsafe {
469            ffi::cm_sample_buffer_release(self.0);
470        }
471    }
472}
473
474unsafe impl Send for CMSampleBuffer {}
475unsafe impl Sync for CMSampleBuffer {}
476
477impl fmt::Display for CMSampleBuffer {
478    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
479        write!(
480            f,
481            "CMSampleBuffer(pts: {}, duration: {}, samples: {})",
482            self.get_presentation_timestamp(),
483            self.get_duration(),
484            self.get_num_samples()
485        )
486    }
487}