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, SCFrameStatus,
7};
8use crate::cv::CVPixelBuffer;
9use std::fmt;
10
11/// Opaque handle to `CMSampleBuffer`
12#[repr(transparent)]
13#[derive(Debug)]
14pub struct CMSampleBuffer(*mut std::ffi::c_void);
15
16impl PartialEq for CMSampleBuffer {
17    fn eq(&self, other: &Self) -> bool {
18        self.0 == other.0
19    }
20}
21
22impl Eq for CMSampleBuffer {}
23
24impl std::hash::Hash for CMSampleBuffer {
25    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
26        unsafe {
27            let hash_value = ffi::cm_sample_buffer_hash(self.0);
28            hash_value.hash(state);
29        }
30    }
31}
32
33impl CMSampleBuffer {
34    pub fn from_raw(ptr: *mut std::ffi::c_void) -> Option<Self> {
35        if ptr.is_null() {
36            None
37        } else {
38            Some(Self(ptr))
39        }
40    }
41
42    /// # Safety
43    /// The caller must ensure the pointer is a valid `CMSampleBuffer` pointer.
44    pub unsafe fn from_ptr(ptr: *mut std::ffi::c_void) -> Self {
45        Self(ptr)
46    }
47
48    pub fn as_ptr(&self) -> *mut std::ffi::c_void {
49        self.0
50    }
51
52    /// Create a sample buffer for an image buffer (video frame)
53    ///
54    /// # Arguments
55    ///
56    /// * `image_buffer` - The pixel buffer containing the video frame
57    /// * `presentation_time` - When the frame should be presented
58    /// * `duration` - How long the frame should be displayed
59    ///
60    /// # Errors
61    ///
62    /// Returns a Core Media error code if the sample buffer creation fails.
63    ///
64    /// # Examples
65    ///
66    /// ```
67    /// use screencapturekit::cm::{CMSampleBuffer, CMTime};
68    /// use screencapturekit::cv::CVPixelBuffer;
69    ///
70    /// // Create a pixel buffer
71    /// let pixel_buffer = CVPixelBuffer::create(1920, 1080, 0x42475241)
72    ///     .expect("Failed to create pixel buffer");
73    ///
74    /// // Create timing information (30fps video)
75    /// let presentation_time = CMTime::new(0, 30); // Frame 0 at 30 fps
76    /// let duration = CMTime::new(1, 30);          // 1/30th of a second
77    ///
78    /// // Create sample buffer
79    /// let sample = CMSampleBuffer::create_for_image_buffer(
80    ///     &pixel_buffer,
81    ///     presentation_time,
82    ///     duration,
83    /// ).expect("Failed to create sample buffer");
84    ///
85    /// assert!(sample.is_valid());
86    /// assert_eq!(sample.presentation_timestamp().value, 0);
87    /// assert_eq!(sample.presentation_timestamp().timescale, 30);
88    /// ```
89    pub fn create_for_image_buffer(
90        image_buffer: &CVPixelBuffer,
91        presentation_time: CMTime,
92        duration: CMTime,
93    ) -> Result<Self, i32> {
94        unsafe {
95            let mut sample_buffer_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
96            let status = ffi::cm_sample_buffer_create_for_image_buffer(
97                image_buffer.as_ptr(),
98                presentation_time.value,
99                presentation_time.timescale,
100                duration.value,
101                duration.timescale,
102                &mut sample_buffer_ptr,
103            );
104
105            if status == 0 && !sample_buffer_ptr.is_null() {
106                Ok(Self(sample_buffer_ptr))
107            } else {
108                Err(status)
109            }
110        }
111    }
112
113    /// Get the image buffer (pixel buffer) from this sample
114    pub fn image_buffer(&self) -> Option<CVPixelBuffer> {
115        unsafe {
116            let ptr = ffi::cm_sample_buffer_get_image_buffer(self.0);
117            CVPixelBuffer::from_raw(ptr)
118        }
119    }
120
121    /// Get the frame status from a sample buffer
122    ///
123    /// Returns the `SCFrameStatus` attachment from the sample buffer,
124    /// indicating whether the frame is complete, idle, blank, etc.
125    ///
126    /// # Examples
127    ///
128    /// ```no_run
129    /// use screencapturekit::cm::{CMSampleBuffer, SCFrameStatus};
130    ///
131    /// fn handle_frame(sample: CMSampleBuffer) {
132    ///     if let Some(status) = sample.frame_status() {
133    ///         match status {
134    ///             SCFrameStatus::Complete => {
135    ///                 println!("Frame is complete, process it");
136    ///             }
137    ///             SCFrameStatus::Idle => {
138    ///                 println!("Frame is idle, no changes");
139    ///             }
140    ///             _ => {
141    ///                 println!("Frame status: {}", status);
142    ///             }
143    ///         }
144    ///     }
145    /// }
146    /// ```
147    pub fn frame_status(&self) -> Option<SCFrameStatus> {
148        unsafe {
149            let status = ffi::cm_sample_buffer_get_frame_status(self.0);
150            if status >= 0 {
151                SCFrameStatus::from_raw(status)
152            } else {
153                None
154            }
155        }
156    }
157
158    /// Get the display time (mach absolute time) from frame info
159    ///
160    /// This is the time when the frame was displayed on screen.
161    pub fn display_time(&self) -> Option<u64> {
162        unsafe {
163            let mut value: u64 = 0;
164            if ffi::cm_sample_buffer_get_display_time(self.0, &mut value) {
165                Some(value)
166            } else {
167                None
168            }
169        }
170    }
171
172    /// Get the scale factor (point-to-pixel ratio) from frame info
173    ///
174    /// This indicates the display's scale factor (e.g., 2.0 for Retina displays).
175    pub fn scale_factor(&self) -> Option<f64> {
176        unsafe {
177            let mut value: f64 = 0.0;
178            if ffi::cm_sample_buffer_get_scale_factor(self.0, &mut value) {
179                Some(value)
180            } else {
181                None
182            }
183        }
184    }
185
186    /// Get the content scale from frame info
187    pub fn content_scale(&self) -> Option<f64> {
188        unsafe {
189            let mut value: f64 = 0.0;
190            if ffi::cm_sample_buffer_get_content_scale(self.0, &mut value) {
191                Some(value)
192            } else {
193                None
194            }
195        }
196    }
197
198    /// Get the content rectangle from frame info
199    ///
200    /// This is the rectangle of the captured content within the frame.
201    pub fn content_rect(&self) -> Option<crate::cg::CGRect> {
202        unsafe {
203            let mut x: f64 = 0.0;
204            let mut y: f64 = 0.0;
205            let mut width: f64 = 0.0;
206            let mut height: f64 = 0.0;
207            if ffi::cm_sample_buffer_get_content_rect(
208                self.0,
209                &mut x,
210                &mut y,
211                &mut width,
212                &mut height,
213            ) {
214                Some(crate::cg::CGRect::new(x, y, width, height))
215            } else {
216                None
217            }
218        }
219    }
220
221    /// Get the bounding rectangle from frame info
222    ///
223    /// This is the bounding rectangle of all captured windows.
224    pub fn bounding_rect(&self) -> Option<crate::cg::CGRect> {
225        unsafe {
226            let mut x: f64 = 0.0;
227            let mut y: f64 = 0.0;
228            let mut width: f64 = 0.0;
229            let mut height: f64 = 0.0;
230            if ffi::cm_sample_buffer_get_bounding_rect(
231                self.0,
232                &mut x,
233                &mut y,
234                &mut width,
235                &mut height,
236            ) {
237                Some(crate::cg::CGRect::new(x, y, width, height))
238            } else {
239                None
240            }
241        }
242    }
243
244    /// Get the screen rectangle from frame info
245    ///
246    /// This is the rectangle of the screen being captured.
247    pub fn screen_rect(&self) -> Option<crate::cg::CGRect> {
248        unsafe {
249            let mut x: f64 = 0.0;
250            let mut y: f64 = 0.0;
251            let mut width: f64 = 0.0;
252            let mut height: f64 = 0.0;
253            if ffi::cm_sample_buffer_get_screen_rect(
254                self.0,
255                &mut x,
256                &mut y,
257                &mut width,
258                &mut height,
259            ) {
260                Some(crate::cg::CGRect::new(x, y, width, height))
261            } else {
262                None
263            }
264        }
265    }
266
267    /// Get the dirty rectangles from frame info
268    ///
269    /// Dirty rectangles indicate areas of the screen that have changed since the last frame.
270    /// This can be used for efficient partial screen updates.
271    pub fn dirty_rects(&self) -> Option<Vec<crate::cg::CGRect>> {
272        unsafe {
273            let mut rects_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
274            let mut count: usize = 0;
275            if ffi::cm_sample_buffer_get_dirty_rects(self.0, &mut rects_ptr, &mut count) {
276                if rects_ptr.is_null() || count == 0 {
277                    return None;
278                }
279                let data = rects_ptr as *const f64;
280                let mut rects = Vec::with_capacity(count);
281                for i in 0..count {
282                    let x = *data.add(i * 4);
283                    let y = *data.add(i * 4 + 1);
284                    let width = *data.add(i * 4 + 2);
285                    let height = *data.add(i * 4 + 3);
286                    rects.push(crate::cg::CGRect::new(x, y, width, height));
287                }
288                ffi::cm_sample_buffer_free_dirty_rects(rects_ptr);
289                Some(rects)
290            } else {
291                None
292            }
293        }
294    }
295
296    /// Get the presentation timestamp
297    pub fn presentation_timestamp(&self) -> CMTime {
298        unsafe {
299            let mut value: i64 = 0;
300            let mut timescale: i32 = 0;
301            let mut flags: u32 = 0;
302            let mut epoch: i64 = 0;
303            ffi::cm_sample_buffer_get_presentation_timestamp(
304                self.0,
305                &mut value,
306                &mut timescale,
307                &mut flags,
308                &mut epoch,
309            );
310            CMTime {
311                value,
312                timescale,
313                flags,
314                epoch,
315            }
316        }
317    }
318
319    /// Get the duration of the sample
320    pub fn duration(&self) -> CMTime {
321        unsafe {
322            let mut value: i64 = 0;
323            let mut timescale: i32 = 0;
324            let mut flags: u32 = 0;
325            let mut epoch: i64 = 0;
326            ffi::cm_sample_buffer_get_duration(
327                self.0,
328                &mut value,
329                &mut timescale,
330                &mut flags,
331                &mut epoch,
332            );
333            CMTime {
334                value,
335                timescale,
336                flags,
337                epoch,
338            }
339        }
340    }
341
342    pub fn is_valid(&self) -> bool {
343        unsafe { ffi::cm_sample_buffer_is_valid(self.0) }
344    }
345
346    /// Get the number of samples in this buffer
347    pub fn num_samples(&self) -> usize {
348        unsafe { ffi::cm_sample_buffer_get_num_samples(self.0) }
349    }
350
351    /// Get the audio buffer list from this sample
352    pub fn audio_buffer_list(&self) -> Option<AudioBufferList> {
353        unsafe {
354            let mut num_buffers: u32 = 0;
355            let mut buffers_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
356            let mut buffers_len: usize = 0;
357            let mut block_buffer_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
358
359            ffi::cm_sample_buffer_get_audio_buffer_list(
360                self.0,
361                &mut num_buffers,
362                &mut buffers_ptr,
363                &mut buffers_len,
364                &mut block_buffer_ptr,
365            );
366
367            if num_buffers == 0 {
368                None
369            } else {
370                Some(AudioBufferList {
371                    inner: AudioBufferListRaw {
372                        num_buffers,
373                        buffers_ptr: buffers_ptr.cast::<AudioBuffer>(),
374                        buffers_len,
375                    },
376                    block_buffer_ptr,
377                })
378            }
379        }
380    }
381
382    /// Get the data buffer (for compressed data)
383    pub fn data_buffer(&self) -> Option<CMBlockBuffer> {
384        unsafe {
385            let ptr = ffi::cm_sample_buffer_get_data_buffer(self.0);
386            CMBlockBuffer::from_raw(ptr)
387        }
388    }
389
390    /// Get the decode timestamp of the sample buffer
391    pub fn decode_timestamp(&self) -> CMTime {
392        unsafe {
393            let mut value: i64 = 0;
394            let mut timescale: i32 = 0;
395            let mut flags: u32 = 0;
396            let mut epoch: i64 = 0;
397            ffi::cm_sample_buffer_get_decode_timestamp(
398                self.0,
399                &mut value,
400                &mut timescale,
401                &mut flags,
402                &mut epoch,
403            );
404            CMTime {
405                value,
406                timescale,
407                flags,
408                epoch,
409            }
410        }
411    }
412
413    /// Get the output presentation timestamp
414    pub fn output_presentation_timestamp(&self) -> CMTime {
415        unsafe {
416            let mut value: i64 = 0;
417            let mut timescale: i32 = 0;
418            let mut flags: u32 = 0;
419            let mut epoch: i64 = 0;
420            ffi::cm_sample_buffer_get_output_presentation_timestamp(
421                self.0,
422                &mut value,
423                &mut timescale,
424                &mut flags,
425                &mut epoch,
426            );
427            CMTime {
428                value,
429                timescale,
430                flags,
431                epoch,
432            }
433        }
434    }
435
436    /// Set the output presentation timestamp
437    ///
438    /// # Errors
439    ///
440    /// Returns a Core Media error code if the operation fails.
441    pub fn set_output_presentation_timestamp(&self, time: CMTime) -> Result<(), i32> {
442        unsafe {
443            let status = ffi::cm_sample_buffer_set_output_presentation_timestamp(
444                self.0,
445                time.value,
446                time.timescale,
447                time.flags,
448                time.epoch,
449            );
450            if status == 0 {
451                Ok(())
452            } else {
453                Err(status)
454            }
455        }
456    }
457
458    /// Get the size of a specific sample
459    pub fn sample_size(&self, index: usize) -> usize {
460        unsafe { ffi::cm_sample_buffer_get_sample_size(self.0, index) }
461    }
462
463    /// Get the total size of all samples
464    pub fn total_sample_size(&self) -> usize {
465        unsafe { ffi::cm_sample_buffer_get_total_sample_size(self.0) }
466    }
467
468    /// Check if the sample buffer data is ready for access
469    pub fn is_data_ready(&self) -> bool {
470        unsafe { ffi::cm_sample_buffer_is_ready_for_data_access(self.0) }
471    }
472
473    /// Make the sample buffer data ready for access
474    ///
475    /// # Errors
476    ///
477    /// Returns a Core Media error code if the operation fails.
478    pub fn make_data_ready(&self) -> Result<(), i32> {
479        unsafe {
480            let status = ffi::cm_sample_buffer_make_data_ready(self.0);
481            if status == 0 {
482                Ok(())
483            } else {
484                Err(status)
485            }
486        }
487    }
488
489    /// Get the format description
490    pub fn format_description(&self) -> Option<CMFormatDescription> {
491        unsafe {
492            let ptr = ffi::cm_sample_buffer_get_format_description(self.0);
493            CMFormatDescription::from_raw(ptr)
494        }
495    }
496
497    /// Get sample timing info for a specific sample
498    ///
499    /// # Errors
500    ///
501    /// Returns a Core Media error code if the timing info cannot be retrieved.
502    pub fn sample_timing_info(&self, index: usize) -> Result<CMSampleTimingInfo, i32> {
503        unsafe {
504            let mut timing_info = CMSampleTimingInfo {
505                duration: CMTime::INVALID,
506                presentation_time_stamp: CMTime::INVALID,
507                decode_time_stamp: CMTime::INVALID,
508            };
509            let status = ffi::cm_sample_buffer_get_sample_timing_info(
510                self.0,
511                index,
512                &mut timing_info.duration.value,
513                &mut timing_info.duration.timescale,
514                &mut timing_info.duration.flags,
515                &mut timing_info.duration.epoch,
516                &mut timing_info.presentation_time_stamp.value,
517                &mut timing_info.presentation_time_stamp.timescale,
518                &mut timing_info.presentation_time_stamp.flags,
519                &mut timing_info.presentation_time_stamp.epoch,
520                &mut timing_info.decode_time_stamp.value,
521                &mut timing_info.decode_time_stamp.timescale,
522                &mut timing_info.decode_time_stamp.flags,
523                &mut timing_info.decode_time_stamp.epoch,
524            );
525            if status == 0 {
526                Ok(timing_info)
527            } else {
528                Err(status)
529            }
530        }
531    }
532
533    /// Get all sample timing info as a vector
534    ///
535    /// # Errors
536    ///
537    /// Returns a Core Media error code if any timing info cannot be retrieved.
538    pub fn sample_timing_info_array(&self) -> Result<Vec<CMSampleTimingInfo>, i32> {
539        let num_samples = self.num_samples();
540        let mut result = Vec::with_capacity(num_samples);
541        for i in 0..num_samples {
542            result.push(self.sample_timing_info(i)?);
543        }
544        Ok(result)
545    }
546
547    /// Invalidate the sample buffer
548    ///
549    /// # Errors
550    ///
551    /// Returns a Core Media error code if the invalidation fails.
552    pub fn invalidate(&self) -> Result<(), i32> {
553        unsafe {
554            let status = ffi::cm_sample_buffer_invalidate(self.0);
555            if status == 0 {
556                Ok(())
557            } else {
558                Err(status)
559            }
560        }
561    }
562
563    /// Create a copy with new timing information
564    ///
565    /// # Errors
566    ///
567    /// Returns a Core Media error code if the copy cannot be created.
568    pub fn create_copy_with_new_timing(
569        &self,
570        timing_info: &[CMSampleTimingInfo],
571    ) -> Result<Self, i32> {
572        unsafe {
573            let mut new_buffer_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
574            let status = ffi::cm_sample_buffer_create_copy_with_new_timing(
575                self.0,
576                timing_info.len(),
577                timing_info.as_ptr().cast::<std::ffi::c_void>(),
578                &mut new_buffer_ptr,
579            );
580            if status == 0 && !new_buffer_ptr.is_null() {
581                Ok(Self(new_buffer_ptr))
582            } else {
583                Err(status)
584            }
585        }
586    }
587
588    /// Copy PCM audio data into an audio buffer list
589    ///
590    /// # Errors
591    ///
592    /// Returns a Core Media error code if the copy operation fails.
593    pub fn copy_pcm_data_into_audio_buffer_list(
594        &self,
595        frame_offset: i32,
596        num_frames: i32,
597        buffer_list: &mut AudioBufferList,
598    ) -> Result<(), i32> {
599        unsafe {
600            let status = ffi::cm_sample_buffer_copy_pcm_data_into_audio_buffer_list(
601                self.0,
602                frame_offset,
603                num_frames,
604                (buffer_list as *mut AudioBufferList).cast::<std::ffi::c_void>(),
605            );
606            if status == 0 {
607                Ok(())
608            } else {
609                Err(status)
610            }
611        }
612    }
613}
614
615impl Drop for CMSampleBuffer {
616    fn drop(&mut self) {
617        unsafe {
618            ffi::cm_sample_buffer_release(self.0);
619        }
620    }
621}
622
623unsafe impl Send for CMSampleBuffer {}
624unsafe impl Sync for CMSampleBuffer {}
625
626impl fmt::Display for CMSampleBuffer {
627    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
628        write!(
629            f,
630            "CMSampleBuffer(pts: {}, duration: {}, samples: {})",
631            self.presentation_timestamp(),
632            self.duration(),
633            self.num_samples()
634        )
635    }
636}