Skip to main content

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/// Bit flags marking which fields the batched [`CMSampleBuffer::frame_info`]
12/// fetch managed to populate. Mirrors `FrameInfoFieldBits` in the Swift
13/// bridge — keep them in sync.
14struct FrameInfoFields;
15
16impl FrameInfoFields {
17    const STATUS: u32 = 1 << 0;
18    const DISPLAY_TIME: u32 = 1 << 1;
19    const SCALE_FACTOR: u32 = 1 << 2;
20    const CONTENT_SCALE: u32 = 1 << 3;
21    const CONTENT_RECT: u32 = 1 << 4;
22    const BOUNDING_RECT: u32 = 1 << 5;
23    const SCREEN_RECT: u32 = 1 << 6;
24    const PRESENTER_OVERLAY_RECT: u32 = 1 << 7;
25}
26
27/// Snapshot of every `SCStreamFrameInfo` attachment on a sample buffer.
28///
29/// Returned by [`CMSampleBuffer::frame_info`]. Each field is `Some` when the
30/// underlying attachment was present (depends on macOS version, output type,
31/// and stream configuration); `None` indicates the attachment was missing.
32//
33// Eq cannot be derived because of the f64-bearing fields (CGRect, f64). The
34// allow keeps clippy happy under -D warnings without forcing us to ship a
35// hand-rolled Eq that would lie about NaN handling.
36#[allow(clippy::derive_partial_eq_without_eq)]
37#[derive(Debug, Default, Clone, PartialEq)]
38pub struct FrameInfo {
39    /// `SCStreamFrameInfo.status` — frame completeness / idle state.
40    pub frame_status: Option<SCFrameStatus>,
41    /// `SCStreamFrameInfo.displayTime` — mach absolute time the frame was
42    /// composited.
43    pub display_time: Option<u64>,
44    /// `SCStreamFrameInfo.scaleFactor` — display scale (e.g. 2.0 for Retina).
45    pub scale_factor: Option<f64>,
46    /// `SCStreamFrameInfo.contentScale` — capture scale relative to the
47    /// source content.
48    pub content_scale: Option<f64>,
49    /// `SCStreamFrameInfo.contentRect` — captured content within the frame.
50    pub content_rect: Option<crate::cg::CGRect>,
51    /// `SCStreamFrameInfo.boundingRect` — bounding rect of all captured
52    /// windows (macOS 14.0+).
53    pub bounding_rect: Option<crate::cg::CGRect>,
54    /// `SCStreamFrameInfo.screenRect` — full screen rect (macOS 13.1+).
55    pub screen_rect: Option<crate::cg::CGRect>,
56    /// `SCStreamFrameInfo.presenterOverlayContentRect` — Presenter Overlay
57    /// bounding rect (macOS 14.2+).
58    pub presenter_overlay_content_rect: Option<crate::cg::CGRect>,
59}
60
61/// Opaque handle to `CMSampleBuffer`
62#[repr(transparent)]
63#[derive(Debug)]
64pub struct CMSampleBuffer(*mut std::ffi::c_void);
65
66impl PartialEq for CMSampleBuffer {
67    fn eq(&self, other: &Self) -> bool {
68        self.0 == other.0
69    }
70}
71
72impl Eq for CMSampleBuffer {}
73
74impl std::hash::Hash for CMSampleBuffer {
75    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
76        unsafe {
77            let hash_value = ffi::cm_sample_buffer_hash(self.0);
78            hash_value.hash(state);
79        }
80    }
81}
82
83impl CMSampleBuffer {
84    pub fn from_raw(ptr: *mut std::ffi::c_void) -> Option<Self> {
85        if ptr.is_null() {
86            None
87        } else {
88            Some(Self(ptr))
89        }
90    }
91
92    /// # Safety
93    /// The caller must ensure the pointer is a valid `CMSampleBuffer` pointer.
94    pub unsafe fn from_ptr(ptr: *mut std::ffi::c_void) -> Self {
95        Self(ptr)
96    }
97
98    pub fn as_ptr(&self) -> *mut std::ffi::c_void {
99        self.0
100    }
101
102    /// Create a sample buffer for an image buffer (video frame)
103    ///
104    /// # Arguments
105    ///
106    /// * `image_buffer` - The pixel buffer containing the video frame
107    /// * `presentation_time` - When the frame should be presented
108    /// * `duration` - How long the frame should be displayed
109    ///
110    /// # Errors
111    ///
112    /// Returns a Core Media error code if the sample buffer creation fails.
113    ///
114    /// # Examples
115    ///
116    /// ```
117    /// use screencapturekit::cm::{CMSampleBuffer, CMTime};
118    /// use screencapturekit::cv::CVPixelBuffer;
119    ///
120    /// // Create a pixel buffer
121    /// let pixel_buffer = CVPixelBuffer::create(1920, 1080, 0x42475241)
122    ///     .expect("Failed to create pixel buffer");
123    ///
124    /// // Create timing information (30fps video)
125    /// let presentation_time = CMTime::new(0, 30); // Frame 0 at 30 fps
126    /// let duration = CMTime::new(1, 30);          // 1/30th of a second
127    ///
128    /// // Create sample buffer
129    /// let sample = CMSampleBuffer::create_for_image_buffer(
130    ///     &pixel_buffer,
131    ///     presentation_time,
132    ///     duration,
133    /// ).expect("Failed to create sample buffer");
134    ///
135    /// assert!(sample.is_valid());
136    /// assert_eq!(sample.presentation_timestamp().value, 0);
137    /// assert_eq!(sample.presentation_timestamp().timescale, 30);
138    /// ```
139    pub fn create_for_image_buffer(
140        image_buffer: &CVPixelBuffer,
141        presentation_time: CMTime,
142        duration: CMTime,
143    ) -> Result<Self, i32> {
144        unsafe {
145            let mut sample_buffer_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
146            let status = ffi::cm_sample_buffer_create_for_image_buffer(
147                image_buffer.as_ptr(),
148                presentation_time.value,
149                presentation_time.timescale,
150                duration.value,
151                duration.timescale,
152                &mut sample_buffer_ptr,
153            );
154
155            if status == 0 && !sample_buffer_ptr.is_null() {
156                Ok(Self(sample_buffer_ptr))
157            } else {
158                Err(status)
159            }
160        }
161    }
162
163    /// Get the image buffer (pixel buffer) from this sample
164    pub fn image_buffer(&self) -> Option<CVPixelBuffer> {
165        unsafe {
166            let ptr = ffi::cm_sample_buffer_get_image_buffer(self.0);
167            CVPixelBuffer::from_raw(ptr)
168        }
169    }
170
171    /// Get the frame status from a sample buffer
172    ///
173    /// Returns the `SCFrameStatus` attachment from the sample buffer,
174    /// indicating whether the frame is complete, idle, blank, etc.
175    ///
176    /// # Examples
177    ///
178    /// ```no_run
179    /// use screencapturekit::cm::{CMSampleBuffer, SCFrameStatus};
180    ///
181    /// fn handle_frame(sample: CMSampleBuffer) {
182    ///     if let Some(status) = sample.frame_status() {
183    ///         match status {
184    ///             SCFrameStatus::Complete => {
185    ///                 println!("Frame is complete, process it");
186    ///             }
187    ///             SCFrameStatus::Idle => {
188    ///                 println!("Frame is idle, no changes");
189    ///             }
190    ///             _ => {
191    ///                 println!("Frame status: {}", status);
192    ///             }
193    ///         }
194    ///     }
195    /// }
196    /// ```
197    pub fn frame_status(&self) -> Option<SCFrameStatus> {
198        unsafe {
199            let status = ffi::cm_sample_buffer_get_frame_status(self.0);
200            if status >= 0 {
201                SCFrameStatus::from_raw(status)
202            } else {
203                None
204            }
205        }
206    }
207
208    /// Get the display time (mach absolute time) from frame info
209    ///
210    /// This is the time when the frame was displayed on screen.
211    pub fn display_time(&self) -> Option<u64> {
212        unsafe {
213            let mut value: u64 = 0;
214            if ffi::cm_sample_buffer_get_display_time(self.0, &mut value) {
215                Some(value)
216            } else {
217                None
218            }
219        }
220    }
221
222    /// Get the scale factor (point-to-pixel ratio) from frame info
223    ///
224    /// This indicates the display's scale factor (e.g., 2.0 for Retina displays).
225    pub fn scale_factor(&self) -> Option<f64> {
226        unsafe {
227            let mut value: f64 = 0.0;
228            if ffi::cm_sample_buffer_get_scale_factor(self.0, &mut value) {
229                Some(value)
230            } else {
231                None
232            }
233        }
234    }
235
236    /// Get the content scale from frame info
237    pub fn content_scale(&self) -> Option<f64> {
238        unsafe {
239            let mut value: f64 = 0.0;
240            if ffi::cm_sample_buffer_get_content_scale(self.0, &mut value) {
241                Some(value)
242            } else {
243                None
244            }
245        }
246    }
247
248    /// Get the content rectangle from frame info
249    ///
250    /// This is the rectangle of the captured content within the frame.
251    pub fn content_rect(&self) -> Option<crate::cg::CGRect> {
252        unsafe {
253            let mut x: f64 = 0.0;
254            let mut y: f64 = 0.0;
255            let mut width: f64 = 0.0;
256            let mut height: f64 = 0.0;
257            if ffi::cm_sample_buffer_get_content_rect(
258                self.0,
259                &mut x,
260                &mut y,
261                &mut width,
262                &mut height,
263            ) {
264                Some(crate::cg::CGRect::new(x, y, width, height))
265            } else {
266                None
267            }
268        }
269    }
270
271    /// Get the bounding rectangle from frame info
272    ///
273    /// This is the bounding rectangle of all captured windows.
274    pub fn bounding_rect(&self) -> Option<crate::cg::CGRect> {
275        unsafe {
276            let mut x: f64 = 0.0;
277            let mut y: f64 = 0.0;
278            let mut width: f64 = 0.0;
279            let mut height: f64 = 0.0;
280            if ffi::cm_sample_buffer_get_bounding_rect(
281                self.0,
282                &mut x,
283                &mut y,
284                &mut width,
285                &mut height,
286            ) {
287                Some(crate::cg::CGRect::new(x, y, width, height))
288            } else {
289                None
290            }
291        }
292    }
293
294    /// Get the screen rectangle from frame info
295    ///
296    /// This is the rectangle of the screen being captured.
297    pub fn screen_rect(&self) -> Option<crate::cg::CGRect> {
298        unsafe {
299            let mut x: f64 = 0.0;
300            let mut y: f64 = 0.0;
301            let mut width: f64 = 0.0;
302            let mut height: f64 = 0.0;
303            if ffi::cm_sample_buffer_get_screen_rect(
304                self.0,
305                &mut x,
306                &mut y,
307                &mut width,
308                &mut height,
309            ) {
310                Some(crate::cg::CGRect::new(x, y, width, height))
311            } else {
312                None
313            }
314        }
315    }
316
317    /// Get the Presenter Overlay content rectangle from frame info (macOS 14.2+).
318    ///
319    /// When a stream is configured with Presenter Overlay (see
320    /// [`SCStreamConfiguration::set_presenter_overlay_privacy_alert_setting`](
321    /// crate::stream::configuration::SCStreamConfiguration::set_presenter_overlay_privacy_alert_setting)),
322    /// `ScreenCaptureKit` attaches the overlay's bounding rectangle (within the
323    /// captured frame) to each delivered sample. Returns `None` when the
324    /// attachment is missing — typically because the stream isn't using
325    /// Presenter Overlay or no overlay is currently visible.
326    ///
327    /// This complements the existing [`content_rect`](Self::content_rect),
328    /// [`bounding_rect`](Self::bounding_rect), [`screen_rect`](Self::screen_rect),
329    /// and [`dirty_rects`](Self::dirty_rects) accessors and rounds out the
330    /// `SCStreamFrameInfo` attachment surface (9 of 9 keys exposed).
331    #[cfg(feature = "macos_14_2")]
332    pub fn presenter_overlay_content_rect(&self) -> Option<crate::cg::CGRect> {
333        unsafe {
334            let mut x: f64 = 0.0;
335            let mut y: f64 = 0.0;
336            let mut width: f64 = 0.0;
337            let mut height: f64 = 0.0;
338            if ffi::cm_sample_buffer_get_presenter_overlay_content_rect(
339                self.0,
340                &mut x,
341                &mut y,
342                &mut width,
343                &mut height,
344            ) {
345                Some(crate::cg::CGRect::new(x, y, width, height))
346            } else {
347                None
348            }
349        }
350    }
351
352    /// Get the dirty rectangles from frame info
353    ///
354    /// Dirty rectangles indicate areas of the screen that have changed since the last frame.
355    /// This can be used for efficient partial screen updates.
356    pub fn dirty_rects(&self) -> Option<Vec<crate::cg::CGRect>> {
357        unsafe {
358            let mut rects_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
359            let mut count: usize = 0;
360            if ffi::cm_sample_buffer_get_dirty_rects(self.0, &mut rects_ptr, &mut count) {
361                if rects_ptr.is_null() || count == 0 {
362                    return None;
363                }
364                let data = rects_ptr as *const f64;
365                let mut rects = Vec::with_capacity(count);
366                for i in 0..count {
367                    let x = *data.add(i * 4);
368                    let y = *data.add(i * 4 + 1);
369                    let width = *data.add(i * 4 + 2);
370                    let height = *data.add(i * 4 + 3);
371                    rects.push(crate::cg::CGRect::new(x, y, width, height));
372                }
373                ffi::cm_sample_buffer_free_dirty_rects(rects_ptr);
374                Some(rects)
375            } else {
376                None
377            }
378        }
379    }
380
381    /// Read every `SCStreamFrameInfo` attachment in a single FFI round-trip.
382    ///
383    /// Each per-attribute accessor (`frame_status`, `display_time`,
384    /// `scale_factor`, `content_scale`, `content_rect`, `bounding_rect`,
385    /// `screen_rect`, `presenter_overlay_content_rect`) re-fetches the
386    /// `CMSampleBuffer` attachment array and re-bridges the dictionary from
387    /// `CoreFoundation` to Swift. Calling five of them per frame measured at
388    /// ~11 µs on a captured 1080p frame; this batched method does it once for
389    /// ~3 µs (~4× faster). Use this in any per-frame consumer that reads more
390    /// than one attribute.
391    ///
392    /// Returns `None` when the sample buffer carries no attachments at all.
393    /// Otherwise returns a [`FrameInfo`] whose individual fields may still be
394    /// `None` if a particular attachment was missing (e.g. older macOS
395    /// versions or non-screen samples).
396    pub fn frame_info(&self) -> Option<FrameInfo> {
397        let mut fields: u32 = 0;
398        let mut status: i32 = 0;
399        let mut display_time: u64 = 0;
400        let mut scale_factor: f64 = 0.0;
401        let mut content_scale: f64 = 0.0;
402        let mut content_rect: [f64; 4] = [0.0; 4];
403        let mut bounding_rect: [f64; 4] = [0.0; 4];
404        let mut screen_rect: [f64; 4] = [0.0; 4];
405        let mut presenter_overlay_rect: [f64; 4] = [0.0; 4];
406
407        let any = unsafe {
408            ffi::cm_sample_buffer_get_frame_info(
409                self.0,
410                &mut fields,
411                &mut status,
412                &mut display_time,
413                &mut scale_factor,
414                &mut content_scale,
415                content_rect.as_mut_ptr(),
416                bounding_rect.as_mut_ptr(),
417                screen_rect.as_mut_ptr(),
418                presenter_overlay_rect.as_mut_ptr(),
419            )
420        };
421
422        if !any {
423            return None;
424        }
425
426        let to_rect = |arr: [f64; 4]| crate::cg::CGRect::new(arr[0], arr[1], arr[2], arr[3]);
427
428        Some(FrameInfo {
429            frame_status: ((fields & FrameInfoFields::STATUS) != 0)
430                .then(|| SCFrameStatus::from_raw(status))
431                .flatten(),
432            display_time: ((fields & FrameInfoFields::DISPLAY_TIME) != 0).then_some(display_time),
433            scale_factor: ((fields & FrameInfoFields::SCALE_FACTOR) != 0).then_some(scale_factor),
434            content_scale: ((fields & FrameInfoFields::CONTENT_SCALE) != 0)
435                .then_some(content_scale),
436            content_rect: ((fields & FrameInfoFields::CONTENT_RECT) != 0)
437                .then(|| to_rect(content_rect)),
438            bounding_rect: ((fields & FrameInfoFields::BOUNDING_RECT) != 0)
439                .then(|| to_rect(bounding_rect)),
440            screen_rect: ((fields & FrameInfoFields::SCREEN_RECT) != 0)
441                .then(|| to_rect(screen_rect)),
442            presenter_overlay_content_rect: ((fields & FrameInfoFields::PRESENTER_OVERLAY_RECT)
443                != 0)
444                .then(|| to_rect(presenter_overlay_rect)),
445        })
446    }
447
448    /// Get the presentation timestamp
449    pub fn presentation_timestamp(&self) -> CMTime {
450        unsafe {
451            let mut value: i64 = 0;
452            let mut timescale: i32 = 0;
453            let mut flags: u32 = 0;
454            let mut epoch: i64 = 0;
455            ffi::cm_sample_buffer_get_presentation_timestamp(
456                self.0,
457                &mut value,
458                &mut timescale,
459                &mut flags,
460                &mut epoch,
461            );
462            CMTime {
463                value,
464                timescale,
465                flags,
466                epoch,
467            }
468        }
469    }
470
471    /// Get the duration of the sample
472    pub fn duration(&self) -> CMTime {
473        unsafe {
474            let mut value: i64 = 0;
475            let mut timescale: i32 = 0;
476            let mut flags: u32 = 0;
477            let mut epoch: i64 = 0;
478            ffi::cm_sample_buffer_get_duration(
479                self.0,
480                &mut value,
481                &mut timescale,
482                &mut flags,
483                &mut epoch,
484            );
485            CMTime {
486                value,
487                timescale,
488                flags,
489                epoch,
490            }
491        }
492    }
493
494    pub fn is_valid(&self) -> bool {
495        unsafe { ffi::cm_sample_buffer_is_valid(self.0) }
496    }
497
498    /// Get the number of samples in this buffer
499    pub fn num_samples(&self) -> usize {
500        unsafe { ffi::cm_sample_buffer_get_num_samples(self.0) }
501    }
502
503    /// Get the audio buffer list from this sample
504    pub fn audio_buffer_list(&self) -> Option<AudioBufferList> {
505        unsafe {
506            let mut num_buffers: u32 = 0;
507            let mut buffers_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
508            let mut buffers_len: usize = 0;
509            let mut block_buffer_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
510
511            ffi::cm_sample_buffer_get_audio_buffer_list(
512                self.0,
513                &mut num_buffers,
514                &mut buffers_ptr,
515                &mut buffers_len,
516                &mut block_buffer_ptr,
517            );
518
519            if num_buffers == 0 {
520                None
521            } else {
522                Some(AudioBufferList {
523                    inner: AudioBufferListRaw {
524                        num_buffers,
525                        buffers_ptr: buffers_ptr.cast::<AudioBuffer>(),
526                        buffers_len,
527                    },
528                    block_buffer_ptr,
529                })
530            }
531        }
532    }
533
534    /// Get the data buffer (for compressed data)
535    pub fn data_buffer(&self) -> Option<CMBlockBuffer> {
536        unsafe {
537            let ptr = ffi::cm_sample_buffer_get_data_buffer(self.0);
538            CMBlockBuffer::from_raw(ptr)
539        }
540    }
541
542    /// Get the decode timestamp of the sample buffer
543    pub fn decode_timestamp(&self) -> CMTime {
544        unsafe {
545            let mut value: i64 = 0;
546            let mut timescale: i32 = 0;
547            let mut flags: u32 = 0;
548            let mut epoch: i64 = 0;
549            ffi::cm_sample_buffer_get_decode_timestamp(
550                self.0,
551                &mut value,
552                &mut timescale,
553                &mut flags,
554                &mut epoch,
555            );
556            CMTime {
557                value,
558                timescale,
559                flags,
560                epoch,
561            }
562        }
563    }
564
565    /// Get the output presentation timestamp
566    pub fn output_presentation_timestamp(&self) -> CMTime {
567        unsafe {
568            let mut value: i64 = 0;
569            let mut timescale: i32 = 0;
570            let mut flags: u32 = 0;
571            let mut epoch: i64 = 0;
572            ffi::cm_sample_buffer_get_output_presentation_timestamp(
573                self.0,
574                &mut value,
575                &mut timescale,
576                &mut flags,
577                &mut epoch,
578            );
579            CMTime {
580                value,
581                timescale,
582                flags,
583                epoch,
584            }
585        }
586    }
587
588    /// Set the output presentation timestamp
589    ///
590    /// # Errors
591    ///
592    /// Returns a Core Media error code if the operation fails.
593    pub fn set_output_presentation_timestamp(&self, time: CMTime) -> Result<(), i32> {
594        unsafe {
595            let status = ffi::cm_sample_buffer_set_output_presentation_timestamp(
596                self.0,
597                time.value,
598                time.timescale,
599                time.flags,
600                time.epoch,
601            );
602            if status == 0 {
603                Ok(())
604            } else {
605                Err(status)
606            }
607        }
608    }
609
610    /// Get the size of a specific sample
611    pub fn sample_size(&self, index: usize) -> usize {
612        unsafe { ffi::cm_sample_buffer_get_sample_size(self.0, index) }
613    }
614
615    /// Get the total size of all samples
616    pub fn total_sample_size(&self) -> usize {
617        unsafe { ffi::cm_sample_buffer_get_total_sample_size(self.0) }
618    }
619
620    /// Check if the sample buffer data is ready for access
621    pub fn is_data_ready(&self) -> bool {
622        unsafe { ffi::cm_sample_buffer_is_ready_for_data_access(self.0) }
623    }
624
625    /// Make the sample buffer data ready for access
626    ///
627    /// # Errors
628    ///
629    /// Returns a Core Media error code if the operation fails.
630    pub fn make_data_ready(&self) -> Result<(), i32> {
631        unsafe {
632            let status = ffi::cm_sample_buffer_make_data_ready(self.0);
633            if status == 0 {
634                Ok(())
635            } else {
636                Err(status)
637            }
638        }
639    }
640
641    /// Get the format description
642    pub fn format_description(&self) -> Option<CMFormatDescription> {
643        unsafe {
644            let ptr = ffi::cm_sample_buffer_get_format_description(self.0);
645            CMFormatDescription::from_raw(ptr)
646        }
647    }
648
649    /// Get sample timing info for a specific sample
650    ///
651    /// # Errors
652    ///
653    /// Returns a Core Media error code if the timing info cannot be retrieved.
654    pub fn sample_timing_info(&self, index: usize) -> Result<CMSampleTimingInfo, i32> {
655        unsafe {
656            let mut timing_info = CMSampleTimingInfo {
657                duration: CMTime::INVALID,
658                presentation_time_stamp: CMTime::INVALID,
659                decode_time_stamp: CMTime::INVALID,
660            };
661            let status = ffi::cm_sample_buffer_get_sample_timing_info(
662                self.0,
663                index,
664                &mut timing_info.duration.value,
665                &mut timing_info.duration.timescale,
666                &mut timing_info.duration.flags,
667                &mut timing_info.duration.epoch,
668                &mut timing_info.presentation_time_stamp.value,
669                &mut timing_info.presentation_time_stamp.timescale,
670                &mut timing_info.presentation_time_stamp.flags,
671                &mut timing_info.presentation_time_stamp.epoch,
672                &mut timing_info.decode_time_stamp.value,
673                &mut timing_info.decode_time_stamp.timescale,
674                &mut timing_info.decode_time_stamp.flags,
675                &mut timing_info.decode_time_stamp.epoch,
676            );
677            if status == 0 {
678                Ok(timing_info)
679            } else {
680                Err(status)
681            }
682        }
683    }
684
685    /// Get all sample timing info as a vector
686    ///
687    /// # Errors
688    ///
689    /// Returns a Core Media error code if any timing info cannot be retrieved.
690    pub fn sample_timing_info_array(&self) -> Result<Vec<CMSampleTimingInfo>, i32> {
691        let num_samples = self.num_samples();
692        let mut result = Vec::with_capacity(num_samples);
693        for i in 0..num_samples {
694            result.push(self.sample_timing_info(i)?);
695        }
696        Ok(result)
697    }
698
699    /// Invalidate the sample buffer
700    ///
701    /// # Errors
702    ///
703    /// Returns a Core Media error code if the invalidation fails.
704    pub fn invalidate(&self) -> Result<(), i32> {
705        unsafe {
706            let status = ffi::cm_sample_buffer_invalidate(self.0);
707            if status == 0 {
708                Ok(())
709            } else {
710                Err(status)
711            }
712        }
713    }
714
715    /// Create a copy with new timing information
716    ///
717    /// # Errors
718    ///
719    /// Returns a Core Media error code if the copy cannot be created.
720    pub fn create_copy_with_new_timing(
721        &self,
722        timing_info: &[CMSampleTimingInfo],
723    ) -> Result<Self, i32> {
724        unsafe {
725            let mut new_buffer_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
726            let status = ffi::cm_sample_buffer_create_copy_with_new_timing(
727                self.0,
728                timing_info.len(),
729                timing_info.as_ptr().cast::<std::ffi::c_void>(),
730                &mut new_buffer_ptr,
731            );
732            if status == 0 && !new_buffer_ptr.is_null() {
733                Ok(Self(new_buffer_ptr))
734            } else {
735                Err(status)
736            }
737        }
738    }
739
740    /// Copy PCM audio data into an audio buffer list
741    ///
742    /// # Errors
743    ///
744    /// Returns a Core Media error code if the copy operation fails.
745    pub fn copy_pcm_data_into_audio_buffer_list(
746        &self,
747        frame_offset: i32,
748        num_frames: i32,
749        buffer_list: &mut AudioBufferList,
750    ) -> Result<(), i32> {
751        unsafe {
752            let status = ffi::cm_sample_buffer_copy_pcm_data_into_audio_buffer_list(
753                self.0,
754                frame_offset,
755                num_frames,
756                (buffer_list as *mut AudioBufferList).cast::<std::ffi::c_void>(),
757            );
758            if status == 0 {
759                Ok(())
760            } else {
761                Err(status)
762            }
763        }
764    }
765}
766
767impl Drop for CMSampleBuffer {
768    fn drop(&mut self) {
769        unsafe {
770            ffi::cm_sample_buffer_release(self.0);
771        }
772    }
773}
774
775unsafe impl Send for CMSampleBuffer {}
776unsafe impl Sync for CMSampleBuffer {}
777
778impl fmt::Display for CMSampleBuffer {
779    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
780        write!(
781            f,
782            "CMSampleBuffer(pts: {}, duration: {}, samples: {})",
783            self.presentation_timestamp(),
784            self.duration(),
785            self.num_samples()
786        )
787    }
788}