Skip to main content

apple_cf/cm/
sample_buffer.rs

1//! [`CMSampleBuffer`] — framework-agnostic safe wrapper around a `CoreMedia`
2//! `CMSampleBufferRef`.
3//!
4//! This wrapper exposes the *generic* `CMSampleBuffer` surface that every
5//! consumer needs: presentation timestamp, format description, attached
6//! data buffer (`CMBlockBuffer`), sample count, validity. Framework-specific
7//! attachment readers (e.g. `SCStreamFrameInfo`'s frame status, content
8//! rect, dirty rects) live in the consuming crates so that, for example,
9//! `screencapturekit-rs`'s SC-attachment readers don't get pulled into
10//! `videotoolbox-rs`.
11
12use super::{CMBlockBuffer, CMFormatDescription, CMTime};
13use crate::ffi;
14use std::fmt;
15
16/// Owned reference to a `CoreMedia` `CMSampleBufferRef`.
17///
18/// Cloning increments the underlying refcount via `CFRetain`; dropping
19/// releases via `CFRelease`. The pointer is opaque to safe Rust — accessor
20/// methods on this type are the only sanctioned way to inspect it.
21pub struct CMSampleBuffer(*mut std::ffi::c_void);
22
23// SAFETY: CMSampleBufferRef is documented as thread-safe for read access;
24// we only share the opaque pointer between threads and never dereference
25// it from Rust.
26unsafe impl Send for CMSampleBuffer {}
27unsafe impl Sync for CMSampleBuffer {}
28
29impl CMSampleBuffer {
30    /// Wrap a raw `CMSampleBufferRef` without bumping its refcount or
31    /// checking for NULL.
32    ///
33    /// # Safety
34    ///
35    /// `ptr` must be a non-null, live `CMSampleBufferRef` of the exact type
36    /// carrying one retain transferred to this wrapper. The caller must not
37    /// release or separately adopt that retain. For NULL-tolerant construction
38    /// prefer [`Self::from_raw`].
39    #[must_use]
40    pub const unsafe fn from_ptr(ptr: *mut std::ffi::c_void) -> Self {
41        Self(ptr)
42    }
43
44    /// Adopt a raw `CMSampleBufferRef` without bumping its refcount.
45    ///
46    /// Use this when the caller has just received a `+1` retained pointer
47    /// (e.g. a Swift `Unmanaged.passRetained(...).toOpaque()`). The
48    /// returned `CMSampleBuffer` will release the pointer when dropped.
49    ///
50    /// Returns `None` for a NULL pointer.
51    ///
52    /// # Safety
53    ///
54    /// A non-null `ptr` must be a live `CMSampleBufferRef` of the exact type
55    /// carrying one retain transferred to this wrapper. The caller must not
56    /// release or separately adopt that transferred retain.
57    #[must_use]
58    pub unsafe fn from_raw(ptr: *mut std::ffi::c_void) -> Option<Self> {
59        if ptr.is_null() {
60            None
61        } else {
62            Some(Self(ptr))
63        }
64    }
65
66    /// Wrap a raw `CMSampleBufferRef`, calling `CFRetain` to bump its
67    /// refcount before taking ownership.
68    ///
69    /// Use this when the caller holds a borrowed (non-owning) reference
70    /// and wants to take ownership without affecting the source.
71    ///
72    /// # Safety
73    ///
74    /// A non-null `ptr` must be a live `CMSampleBufferRef` of the exact type for
75    /// the duration of the retain call.
76    #[must_use]
77    pub unsafe fn from_raw_borrowed(ptr: *mut std::ffi::c_void) -> Option<Self> {
78        if ptr.is_null() {
79            None
80        } else {
81            let retained = unsafe { ffi::cm_sample_buffer_retain(ptr) };
82            unsafe { Self::from_raw(retained) }
83        }
84    }
85
86    /// Borrow the underlying +0 `CMSampleBufferRef` without changing its
87    /// refcount. The pointer remains valid for the lifetime of `self`.
88    #[must_use]
89    pub const fn as_ptr(&self) -> *mut std::ffi::c_void {
90        self.0
91    }
92
93    /// Whether the sample buffer is in a valid state.
94    #[must_use]
95    pub fn is_valid(&self) -> bool {
96        unsafe { ffi::cm_sample_buffer_is_valid(self.0) }
97    }
98
99    /// Whether the sample buffer's data is ready for consumption.
100    #[must_use]
101    pub fn data_is_ready(&self) -> bool {
102        unsafe { ffi::cm_sample_buffer_data_is_ready(self.0) }
103    }
104
105    /// Number of samples carried by this buffer (1 for video, N for audio).
106    #[must_use]
107    pub fn num_samples(&self) -> i64 {
108        unsafe { ffi::cm_sample_buffer_get_num_samples(self.0) }
109    }
110
111    /// Presentation timestamp of the first sample.
112    ///
113    /// Returns [`CMTime::INVALID`] if the buffer has no PTS.
114    #[must_use]
115    pub fn presentation_timestamp(&self) -> CMTime {
116        let mut t = CMTime::INVALID;
117        unsafe {
118            ffi::cm_sample_buffer_get_presentation_timestamp(
119                self.0,
120                &mut t.value,
121                &mut t.timescale,
122                &mut t.flags,
123                &mut t.epoch,
124            );
125        }
126        t
127    }
128
129    /// Decode timestamp of the first sample (matters when there's B-frame
130    /// reordering between PTS and DTS).
131    #[must_use]
132    pub fn decode_timestamp(&self) -> CMTime {
133        let mut t = CMTime::INVALID;
134        unsafe {
135            ffi::cm_sample_buffer_get_decode_timestamp(
136                self.0,
137                &mut t.value,
138                &mut t.timescale,
139                &mut t.flags,
140                &mut t.epoch,
141            );
142        }
143        t
144    }
145
146    /// Total duration of all samples in this buffer.
147    #[must_use]
148    pub fn duration(&self) -> CMTime {
149        let mut t = CMTime::INVALID;
150        unsafe {
151            ffi::cm_sample_buffer_get_duration(
152                self.0,
153                &mut t.value,
154                &mut t.timescale,
155                &mut t.flags,
156                &mut t.epoch,
157            );
158        }
159        t
160    }
161
162    /// The attached [`CMBlockBuffer`] holding the encoded sample data, if
163    /// the sample buffer is data-bearing (as opposed to image-bearing).
164    ///
165    /// Video frames from `VTCompressionSession` always have a data buffer
166    /// (the encoded NAL units / `ProRes` frame data). Decoded video frames
167    /// from a capture pipeline typically use an image buffer instead — see
168    /// [`Self::image_buffer_ptr_borrowed`].
169    #[must_use]
170    pub fn data_buffer(&self) -> Option<CMBlockBuffer> {
171        let ptr = unsafe { ffi::cm_sample_buffer_get_data_buffer(self.0) };
172        if ptr.is_null() {
173            None
174        } else {
175            // CMSampleBufferGetDataBuffer returns an unretained reference;
176            // bump the refcount so our wrapper can release on drop.
177            let retained = unsafe { ffi::cm_block_buffer_retain(ptr) };
178            unsafe { CMBlockBuffer::from_raw(retained) }
179        }
180    }
181
182    /// Format description (codec, dimensions, audio params, ...) attached
183    /// to this sample buffer.
184    #[must_use]
185    pub fn format_description(&self) -> Option<CMFormatDescription> {
186        let ptr = unsafe { ffi::cm_sample_buffer_get_format_description(self.0) };
187        if ptr.is_null() {
188            None
189        } else {
190            let retained = unsafe { ffi::cm_format_description_retain(ptr) };
191            unsafe { CMFormatDescription::from_raw(retained) }
192        }
193    }
194
195    /// Borrowed +0 `CVImageBufferRef` if the sample is image-bearing.
196    ///
197    /// Returns NULL for sample buffers that don't carry an image buffer
198    /// (for example, compressed video or audio samples). The pointer remains
199    /// valid only while `self` and the sample buffer's image association remain
200    /// alive. Retain it before storing or adopting it.
201    #[must_use]
202    pub fn image_buffer_ptr_borrowed(&self) -> *mut std::ffi::c_void {
203        extern "C" {
204            fn CMSampleBufferGetImageBuffer(
205                sample_buffer: *mut std::ffi::c_void,
206            ) -> *mut std::ffi::c_void;
207        }
208        unsafe { CMSampleBufferGetImageBuffer(self.0) }
209    }
210
211    /// Copy the sample buffer's image buffer into an independently owned wrapper.
212    #[cfg(feature = "cv")]
213    #[must_use]
214    pub fn image_buffer(&self) -> Option<crate::cv::CVImageBuffer> {
215        let ptr = unsafe { ffi::cm_sample_buffer_copy_image_buffer(self.0) };
216        unsafe { crate::cv::CVImageBuffer::from_raw(ptr) }
217    }
218}
219
220crate::utils::retained::cf_retained!(
221    CMSampleBuffer,
222    retain = ffi::cm_sample_buffer_retain,
223    release = ffi::cm_sample_buffer_release,
224);
225
226impl PartialEq for CMSampleBuffer {
227    fn eq(&self, other: &Self) -> bool {
228        self.0 == other.0
229    }
230}
231
232impl Eq for CMSampleBuffer {}
233
234impl std::hash::Hash for CMSampleBuffer {
235    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
236        unsafe {
237            let h = ffi::cm_sample_buffer_hash(self.0);
238            h.hash(state);
239        }
240    }
241}
242
243impl fmt::Debug for CMSampleBuffer {
244    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245        f.debug_struct("CMSampleBuffer")
246            .field("ptr", &self.0)
247            .field("num_samples", &self.num_samples())
248            .field("pts", &self.presentation_timestamp())
249            .finish()
250    }
251}