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    /// The caller must ensure `ptr` is a valid `CMSampleBufferRef` retained
36    /// at +1 (the wrapper will release on drop). For NULL-tolerant
37    /// construction prefer [`Self::from_raw`].
38    #[must_use]
39    pub const unsafe fn from_ptr(ptr: *mut std::ffi::c_void) -> Self {
40        Self(ptr)
41    }
42
43    /// Wrap a raw `CMSampleBufferRef` without bumping its refcount.
44    ///
45    /// Use this when the caller has just received a `+1` retained pointer
46    /// (e.g. a Swift `Unmanaged.passRetained(...).toOpaque()`). The
47    /// returned `CMSampleBuffer` will release the pointer when dropped.
48    ///
49    /// Returns `None` for a NULL pointer.
50    #[must_use]
51    pub fn from_raw(ptr: *mut std::ffi::c_void) -> Option<Self> {
52        if ptr.is_null() {
53            None
54        } else {
55            Some(Self(ptr))
56        }
57    }
58
59    /// Wrap a raw `CMSampleBufferRef`, calling `CFRetain` to bump its
60    /// refcount before taking ownership.
61    ///
62    /// Use this when the caller holds a borrowed (non-owning) reference
63    /// and wants to take ownership without affecting the source.
64    ///
65    /// # Safety
66    ///
67    /// `ptr` must be a valid `CMSampleBufferRef` (or NULL). Passing a
68    /// dangling or wrong-type pointer is undefined behaviour.
69    #[must_use]
70    pub unsafe fn from_raw_retained(ptr: *mut std::ffi::c_void) -> Option<Self> {
71        if ptr.is_null() {
72            None
73        } else {
74            let retained = unsafe { ffi::cm_sample_buffer_retain(ptr) };
75            Self::from_raw(retained)
76        }
77    }
78
79    /// Borrow the underlying `CMSampleBufferRef` for hand-off to other
80    /// Apple bindings without changing its refcount. The pointer remains
81    /// valid for the lifetime of `self`.
82    #[must_use]
83    pub const fn as_ptr(&self) -> *mut std::ffi::c_void {
84        self.0
85    }
86
87    /// Whether the sample buffer is in a valid state.
88    #[must_use]
89    pub fn is_valid(&self) -> bool {
90        unsafe { ffi::cm_sample_buffer_is_valid(self.0) }
91    }
92
93    /// Whether the sample buffer's data is ready for consumption.
94    #[must_use]
95    pub fn data_is_ready(&self) -> bool {
96        unsafe { ffi::cm_sample_buffer_data_is_ready(self.0) }
97    }
98
99    /// Number of samples carried by this buffer (1 for video, N for audio).
100    #[must_use]
101    pub fn num_samples(&self) -> i64 {
102        unsafe { ffi::cm_sample_buffer_get_num_samples(self.0) }
103    }
104
105    /// Presentation timestamp of the first sample.
106    ///
107    /// Returns [`CMTime::INVALID`] if the buffer has no PTS.
108    #[must_use]
109    pub fn presentation_timestamp(&self) -> CMTime {
110        let mut t = CMTime::INVALID;
111        unsafe {
112            ffi::cm_sample_buffer_get_presentation_timestamp(
113                self.0,
114                &mut t.value,
115                &mut t.timescale,
116                &mut t.flags,
117                &mut t.epoch,
118            );
119        }
120        t
121    }
122
123    /// Decode timestamp of the first sample (matters when there's B-frame
124    /// reordering between PTS and DTS).
125    #[must_use]
126    pub fn decode_timestamp(&self) -> CMTime {
127        let mut t = CMTime::INVALID;
128        unsafe {
129            ffi::cm_sample_buffer_get_decode_timestamp(
130                self.0,
131                &mut t.value,
132                &mut t.timescale,
133                &mut t.flags,
134                &mut t.epoch,
135            );
136        }
137        t
138    }
139
140    /// Total duration of all samples in this buffer.
141    #[must_use]
142    pub fn duration(&self) -> CMTime {
143        let mut t = CMTime::INVALID;
144        unsafe {
145            ffi::cm_sample_buffer_get_duration(
146                self.0,
147                &mut t.value,
148                &mut t.timescale,
149                &mut t.flags,
150                &mut t.epoch,
151            );
152        }
153        t
154    }
155
156    /// The attached [`CMBlockBuffer`] holding the encoded sample data, if
157    /// the sample buffer is data-bearing (as opposed to image-bearing).
158    ///
159    /// Video frames from `VTCompressionSession` always have a data buffer
160    /// (the encoded NAL units / ProRes frame data). Decoded video frames
161    /// from a capture pipeline typically use an image buffer instead — see
162    /// [`Self::image_buffer_ptr`].
163    #[must_use]
164    pub fn data_buffer(&self) -> Option<CMBlockBuffer> {
165        let ptr = unsafe { ffi::cm_sample_buffer_get_data_buffer(self.0) };
166        if ptr.is_null() {
167            None
168        } else {
169            // CMSampleBufferGetDataBuffer returns an unretained reference;
170            // bump the refcount so our wrapper can release on drop.
171            let retained = unsafe { ffi::cm_block_buffer_retain(ptr) };
172            CMBlockBuffer::from_raw(retained)
173        }
174    }
175
176    /// Format description (codec, dimensions, audio params, ...) attached
177    /// to this sample buffer.
178    #[must_use]
179    pub fn format_description(&self) -> Option<CMFormatDescription> {
180        let ptr = unsafe { ffi::cm_sample_buffer_get_format_description(self.0) };
181        if ptr.is_null() {
182            None
183        } else {
184            let retained = unsafe { ffi::cm_format_description_retain(ptr) };
185            CMFormatDescription::from_raw(retained)
186        }
187    }
188
189    /// Raw `CVImageBufferRef` if the sample is image-bearing (decoded
190    /// video frames from a capture pipeline). Use a CV crate's wrapper to
191    /// turn this into a safe `CVPixelBuffer`.
192    ///
193    /// Returns NULL for sample buffers that don't carry an image buffer
194    /// (e.g. compressed video from VideoToolbox, audio samples).
195    #[must_use]
196    pub fn image_buffer_ptr(&self) -> *mut std::ffi::c_void {
197        unsafe { ffi::cm_sample_buffer_get_image_buffer(self.0) }
198    }
199}
200
201impl Clone for CMSampleBuffer {
202    fn clone(&self) -> Self {
203        let retained = unsafe { ffi::cm_sample_buffer_retain(self.0) };
204        Self(retained)
205    }
206}
207
208impl Drop for CMSampleBuffer {
209    fn drop(&mut self) {
210        if !self.0.is_null() {
211            unsafe { ffi::cm_sample_buffer_release(self.0) };
212        }
213    }
214}
215
216impl PartialEq for CMSampleBuffer {
217    fn eq(&self, other: &Self) -> bool {
218        self.0 == other.0
219    }
220}
221
222impl Eq for CMSampleBuffer {}
223
224impl std::hash::Hash for CMSampleBuffer {
225    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
226        unsafe {
227            let h = ffi::cm_sample_buffer_hash(self.0);
228            h.hash(state);
229        }
230    }
231}
232
233impl fmt::Debug for CMSampleBuffer {
234    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235        f.debug_struct("CMSampleBuffer")
236            .field("ptr", &self.0)
237            .field("num_samples", &self.num_samples())
238            .field("pts", &self.presentation_timestamp())
239            .finish()
240    }
241}