use std::ffi::c_void;
use objc2::{Encode, Encoding, RefEncode};
pub struct SampleBuffer {
inner: CMSampleBufferRef,
}
impl SampleBuffer {
pub fn new(sample_buffer: CMSampleBufferRef) -> Self {
Self { inner: unsafe { CFRetain(sample_buffer.cast()).cast_mut().cast() } }
}
pub fn size_usize(&self) -> (usize, usize) {
let ibuf = unsafe { CMSampleBufferGetImageBuffer(self.inner) };
let width = unsafe { CVPixelBufferGetWidth(ibuf) };
let height = unsafe { CVPixelBufferGetHeight(ibuf) };
(width, height)
}
}
impl Drop for SampleBuffer {
fn drop(&mut self) {
unsafe { CFRelease(self.inner.cast()) };
}
}
impl SampleBuffer {
pub fn pixels(&self) -> Pixels {
Pixels::new(self)
}
}
impl std::fmt::Debug for SampleBuffer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let sbuf = self.inner;
let format = unsafe { CMSampleBufferGetFormatDescription(sbuf) };
let dim = unsafe { CMVideoFormatDescriptionGetDimensions(format) };
let fourcc = unsafe { CMFormatDescriptionGetMediaSubType(format) };
let pixel_format = fourcc_to_string(fourcc);
let width = dim.width;
let height = dim.height;
let name = format!("SampleBuffer {width}x{height} {pixel_format:?}");
f.debug_struct(&name).field("inner", &self.inner).finish()
}
}
#[link(name = "CoreMedia", kind = "framework")]
extern "C" {
pub fn CMSampleBufferGetFormatDescription(sbuf: CMSampleBufferRef) -> CMFormatDescriptionRef;
pub fn CMSampleBufferGetImageBuffer(sbuf: CMSampleBufferRef) -> CVImageBufferRef;
pub fn CMFormatDescriptionGetMediaSubType(desc: CMFormatDescriptionRef) -> u32;
pub fn CMVideoFormatDescriptionGetDimensions(desc: CMFormatDescriptionRef)
-> CMVideoDimensions;
}
#[link(name = "CoreFoundation", kind = "framework")]
extern "C" {
pub fn CFRetain(cf: *const c_void) -> *const c_void;
pub fn CFRelease(cf: *const c_void);
}
#[link(name = "CoreVideo", kind = "framework")]
extern "C" {
pub fn CVPixelBufferLockBaseAddress(buf: CVBufferRef, option: u64) -> i32;
pub fn CVPixelBufferUnlockBaseAddress(buf: CVBufferRef, option: u64) -> i32;
pub fn CVPixelBufferGetBaseAddress(buf: CVBufferRef) -> *const u8;
pub fn CVPixelBufferGetBytesPerRow(buf: CVBufferRef) -> usize;
pub fn CVPixelBufferGetWidth(buf: CVBufferRef) -> usize;
pub fn CVPixelBufferGetHeight(buf: CVBufferRef) -> usize;
pub fn CVPixelBufferIsPlanar(buf: CVBufferRef) -> bool;
pub fn CVPixelBufferGetPlaneCount(buf: CVBufferRef) -> usize;
pub fn CVPixelBufferGetHeightOfPlane(buf: CVBufferRef, index: usize) -> usize;
pub fn CVPixelBufferGetBytesPerRowOfPlane(buf: CVBufferRef, index: usize) -> usize;
pub fn CVPixelBufferGetDataSize(buf: CVBufferRef) -> usize;
pub fn CVPixelBufferGetPixelFormatType(buf: CVBufferRef) -> u32;
pub fn CVPixelBufferGetBaseAddressOfPlane(buf: CVBufferRef, index: usize) -> *const u8;
}
#[repr(C)]
pub struct CVBuffer {
_priv: [u8; 0],
}
pub type CVBufferRef = *const CVBuffer;
pub type CVImageBufferRef = CVBufferRef;
#[repr(C)]
#[derive(Debug)]
pub struct CMVideoDimensions {
pub width: i32,
pub height: i32,
}
#[repr(C)]
pub struct CMSampleBuffer {
_priv: [u8; 0],
}
pub type CMSampleBufferRef = *mut CMSampleBuffer;
unsafe impl Encode for CMSampleBuffer {
const ENCODING: Encoding = Encoding::Struct("opaqueCMSampleBuffer", &[]);
}
unsafe impl RefEncode for CMSampleBuffer {
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
}
#[repr(C)]
pub struct CMFormatDescription {
_priv: [u8; 0],
}
pub type CMFormatDescriptionRef = *mut CMFormatDescription;
pub fn fourcc_to_string(px_format_u32: u32) -> String {
let bytes = px_format_u32.to_ne_bytes();
if bytes[1..4] == [0, 0, 0] {
match bytes[0] {
32 => "ARGB",
24 => "RGB ",
_ => return format!("0x{px_format_u32:08X}"),
}
.into()
} else {
String::from_utf8_lossy(&bytes).to_string()
}
}
pub struct Pixels<'a> {
pub ibuf: CVImageBufferRef,
pub data: &'a [u8],
pub u32: &'a [u32],
pub width: usize,
pub height: usize,
}
impl<'a> Pixels<'a> {
fn new(sample: &'a SampleBuffer) -> Self {
let ibuf = unsafe { CMSampleBufferGetImageBuffer(sample.inner) };
debug_assert!(0 == unsafe { CVPixelBufferLockBaseAddress(ibuf, 1) });
let _address = unsafe { CVPixelBufferGetBaseAddress(ibuf) };
let stride = unsafe { CVPixelBufferGetBytesPerRow(ibuf) };
let width = unsafe { CVPixelBufferGetWidth(ibuf) };
let height = unsafe { CVPixelBufferGetHeight(ibuf) };
let is_planar = unsafe { CVPixelBufferIsPlanar(ibuf) };
let plane_count = unsafe { CVPixelBufferGetPlaneCount(ibuf) };
let _data_size = unsafe { CVPixelBufferGetDataSize(ibuf) };
let _fourcc = unsafe { CVPixelBufferGetPixelFormatType(ibuf) };
let plane_address = unsafe { CVPixelBufferGetBaseAddressOfPlane(ibuf, 0) };
let mut plane_sizes = 0;
if is_planar {
for index in 0..plane_count {
let _plane_address = unsafe { CVPixelBufferGetBaseAddressOfPlane(ibuf, index) };
let plane_stride = unsafe { CVPixelBufferGetBytesPerRowOfPlane(ibuf, index) };
let plane_height = unsafe { CVPixelBufferGetHeightOfPlane(ibuf, index) };
plane_sizes += plane_stride * plane_height;
}
} else {
plane_sizes += stride * height;
}
let data = unsafe { std::slice::from_raw_parts(plane_address, plane_sizes) };
let (a, u32, b) = unsafe { data.align_to() };
debug_assert!(a.is_empty() && b.is_empty());
Self { ibuf, data, u32, width, height }
}
}
impl Drop for Pixels<'_> {
fn drop(&mut self) {
debug_assert!(0 == unsafe { CVPixelBufferUnlockBaseAddress(self.ibuf, 1) });
}
}