apple-cf 0.10.0

Safe Rust bindings for Apple's shared Core* frameworks (CoreFoundation, CoreMedia, CoreVideo, CoreGraphics, IOSurface, Dispatch).
Documentation
//! [`CMSampleBuffer`] — framework-agnostic safe wrapper around a `CoreMedia`
//! `CMSampleBufferRef`.
//!
//! This wrapper exposes the *generic* `CMSampleBuffer` surface that every
//! consumer needs: presentation timestamp, format description, attached
//! data buffer (`CMBlockBuffer`), sample count, validity. Framework-specific
//! attachment readers (e.g. `SCStreamFrameInfo`'s frame status, content
//! rect, dirty rects) live in the consuming crates so that, for example,
//! `screencapturekit-rs`'s SC-attachment readers don't get pulled into
//! `videotoolbox-rs`.

use super::{CMBlockBuffer, CMFormatDescription, CMTime};
use crate::ffi;
use std::fmt;

/// Owned reference to a `CoreMedia` `CMSampleBufferRef`.
///
/// Cloning increments the underlying refcount via `CFRetain`; dropping
/// releases via `CFRelease`. The pointer is opaque to safe Rust — accessor
/// methods on this type are the only sanctioned way to inspect it.
pub struct CMSampleBuffer(*mut std::ffi::c_void);

// SAFETY: CMSampleBufferRef is documented as thread-safe for read access;
// we only share the opaque pointer between threads and never dereference
// it from Rust.
unsafe impl Send for CMSampleBuffer {}
unsafe impl Sync for CMSampleBuffer {}

impl CMSampleBuffer {
    /// Wrap a raw `CMSampleBufferRef` without bumping its refcount or
    /// checking for NULL.
    ///
    /// # Safety
    ///
    /// `ptr` must be a non-null, live `CMSampleBufferRef` of the exact type
    /// carrying one retain transferred to this wrapper. The caller must not
    /// release or separately adopt that retain. For NULL-tolerant construction
    /// prefer [`Self::from_raw`].
    #[must_use]
    pub const unsafe fn from_ptr(ptr: *mut std::ffi::c_void) -> Self {
        Self(ptr)
    }

    /// Adopt a raw `CMSampleBufferRef` without bumping its refcount.
    ///
    /// Use this when the caller has just received a `+1` retained pointer
    /// (e.g. a Swift `Unmanaged.passRetained(...).toOpaque()`). The
    /// returned `CMSampleBuffer` will release the pointer when dropped.
    ///
    /// Returns `None` for a NULL pointer.
    ///
    /// # Safety
    ///
    /// A non-null `ptr` must be a live `CMSampleBufferRef` of the exact type
    /// carrying one retain transferred to this wrapper. The caller must not
    /// release or separately adopt that transferred retain.
    #[must_use]
    pub unsafe fn from_raw(ptr: *mut std::ffi::c_void) -> Option<Self> {
        if ptr.is_null() {
            None
        } else {
            Some(Self(ptr))
        }
    }

    /// Wrap a raw `CMSampleBufferRef`, calling `CFRetain` to bump its
    /// refcount before taking ownership.
    ///
    /// Use this when the caller holds a borrowed (non-owning) reference
    /// and wants to take ownership without affecting the source.
    ///
    /// # Safety
    ///
    /// A non-null `ptr` must be a live `CMSampleBufferRef` of the exact type for
    /// the duration of the retain call.
    #[must_use]
    pub unsafe fn from_raw_borrowed(ptr: *mut std::ffi::c_void) -> Option<Self> {
        if ptr.is_null() {
            None
        } else {
            let retained = unsafe { ffi::cm_sample_buffer_retain(ptr) };
            unsafe { Self::from_raw(retained) }
        }
    }

    /// Borrow the underlying +0 `CMSampleBufferRef` without changing its
    /// refcount. The pointer remains valid for the lifetime of `self`.
    #[must_use]
    pub const fn as_ptr(&self) -> *mut std::ffi::c_void {
        self.0
    }

    /// Whether the sample buffer is in a valid state.
    #[must_use]
    pub fn is_valid(&self) -> bool {
        unsafe { ffi::cm_sample_buffer_is_valid(self.0) }
    }

    /// Whether the sample buffer's data is ready for consumption.
    #[must_use]
    pub fn data_is_ready(&self) -> bool {
        unsafe { ffi::cm_sample_buffer_data_is_ready(self.0) }
    }

    /// Number of samples carried by this buffer (1 for video, N for audio).
    #[must_use]
    pub fn num_samples(&self) -> i64 {
        unsafe { ffi::cm_sample_buffer_get_num_samples(self.0) }
    }

    /// Presentation timestamp of the first sample.
    ///
    /// Returns [`CMTime::INVALID`] if the buffer has no PTS.
    #[must_use]
    pub fn presentation_timestamp(&self) -> CMTime {
        let mut t = CMTime::INVALID;
        unsafe {
            ffi::cm_sample_buffer_get_presentation_timestamp(
                self.0,
                &mut t.value,
                &mut t.timescale,
                &mut t.flags,
                &mut t.epoch,
            );
        }
        t
    }

    /// Decode timestamp of the first sample (matters when there's B-frame
    /// reordering between PTS and DTS).
    #[must_use]
    pub fn decode_timestamp(&self) -> CMTime {
        let mut t = CMTime::INVALID;
        unsafe {
            ffi::cm_sample_buffer_get_decode_timestamp(
                self.0,
                &mut t.value,
                &mut t.timescale,
                &mut t.flags,
                &mut t.epoch,
            );
        }
        t
    }

    /// Total duration of all samples in this buffer.
    #[must_use]
    pub fn duration(&self) -> CMTime {
        let mut t = CMTime::INVALID;
        unsafe {
            ffi::cm_sample_buffer_get_duration(
                self.0,
                &mut t.value,
                &mut t.timescale,
                &mut t.flags,
                &mut t.epoch,
            );
        }
        t
    }

    /// The attached [`CMBlockBuffer`] holding the encoded sample data, if
    /// the sample buffer is data-bearing (as opposed to image-bearing).
    ///
    /// Video frames from `VTCompressionSession` always have a data buffer
    /// (the encoded NAL units / `ProRes` frame data). Decoded video frames
    /// from a capture pipeline typically use an image buffer instead — see
    /// [`Self::image_buffer_ptr_borrowed`].
    #[must_use]
    pub fn data_buffer(&self) -> Option<CMBlockBuffer> {
        let ptr = unsafe { ffi::cm_sample_buffer_get_data_buffer(self.0) };
        if ptr.is_null() {
            None
        } else {
            // CMSampleBufferGetDataBuffer returns an unretained reference;
            // bump the refcount so our wrapper can release on drop.
            let retained = unsafe { ffi::cm_block_buffer_retain(ptr) };
            unsafe { CMBlockBuffer::from_raw(retained) }
        }
    }

    /// Format description (codec, dimensions, audio params, ...) attached
    /// to this sample buffer.
    #[must_use]
    pub fn format_description(&self) -> Option<CMFormatDescription> {
        let ptr = unsafe { ffi::cm_sample_buffer_get_format_description(self.0) };
        if ptr.is_null() {
            None
        } else {
            let retained = unsafe { ffi::cm_format_description_retain(ptr) };
            unsafe { CMFormatDescription::from_raw(retained) }
        }
    }

    /// Borrowed +0 `CVImageBufferRef` if the sample is image-bearing.
    ///
    /// Returns NULL for sample buffers that don't carry an image buffer
    /// (for example, compressed video or audio samples). The pointer remains
    /// valid only while `self` and the sample buffer's image association remain
    /// alive. Retain it before storing or adopting it.
    #[must_use]
    pub fn image_buffer_ptr_borrowed(&self) -> *mut std::ffi::c_void {
        extern "C" {
            fn CMSampleBufferGetImageBuffer(
                sample_buffer: *mut std::ffi::c_void,
            ) -> *mut std::ffi::c_void;
        }
        unsafe { CMSampleBufferGetImageBuffer(self.0) }
    }

    /// Copy the sample buffer's image buffer into an independently owned wrapper.
    #[cfg(feature = "cv")]
    #[must_use]
    pub fn image_buffer(&self) -> Option<crate::cv::CVImageBuffer> {
        let ptr = unsafe { ffi::cm_sample_buffer_copy_image_buffer(self.0) };
        unsafe { crate::cv::CVImageBuffer::from_raw(ptr) }
    }
}

crate::utils::retained::cf_retained!(
    CMSampleBuffer,
    retain = ffi::cm_sample_buffer_retain,
    release = ffi::cm_sample_buffer_release,
);

impl PartialEq for CMSampleBuffer {
    fn eq(&self, other: &Self) -> bool {
        self.0 == other.0
    }
}

impl Eq for CMSampleBuffer {}

impl std::hash::Hash for CMSampleBuffer {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        unsafe {
            let h = ffi::cm_sample_buffer_hash(self.0);
            h.hash(state);
        }
    }
}

impl fmt::Debug for CMSampleBuffer {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("CMSampleBuffer")
            .field("ptr", &self.0)
            .field("num_samples", &self.num_samples())
            .field("pts", &self.presentation_timestamp())
            .finish()
    }
}