// SPDX-License-Identifier: AGPL-3.0-or-later use core::ffi::c_void; use core::ptr::{self, NonNull}; use std::collections::HashMap; use std::collections::VecDeque; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::{Arc, Mutex}; use crate::encoder_handoff::{ EncodedBitstream, EncoderDims, EncoderError, EncoderFrameRate, EncoderSubmission, HandoffSlot, MAX_BITSTREAM_BYTES, PicParams, VideoToolboxHandoff, }; use crate::ring::RingError; type CfDictionaryRef = *const c_void; type CfStringRef = *const c_void; type CfNumberRef = *const c_void; type CfBooleanRef = *const c_void; type CfAllocatorRef = *const c_void; type CmTime = OsCmTime; pub(crate) type CvPixelBufferRef = *mut c_void; type CmSampleBufferRef = *mut c_void; type CmBlockBufferRef = *mut c_void; type VtCompressionSessionRef = *mut c_void; type VtPixelTransferSessionRef = *mut c_void; type OsStatus = i32; pub(crate) type IoSurfaceRef = *mut c_void; const KCM_VIDEO_CODEC_TYPE_H264: u32 = u32::from_be_bytes(*b"avc1"); #[cfg(test)] const KCV_PIXEL_FORMAT_TYPE_420_YPCBCR8_BIPLANAR_VIDEO: u32 = u32::from_be_bytes(*b"420v"); const NOERR: OsStatus = 0; const KCM_TIME_FLAGS_VALID: u32 = 1; const KCF_NUMBER_SINT32_TYPE: i32 = 3; const COMPLETION_RING_CAPACITY: usize = 16; const KVT_INVALID_SESSION_ERR: OsStatus = -12903; const VT_SESSION_REBUILD_MAX: u64 = 3; const VT_ENCODE_ATTEMPTS_PER_FRAME_MAX: usize = 2; #[repr(C)] #[derive(Copy, Clone)] struct OsCmTime { value: i64, timescale: i32, flags: u32, epoch: i64, } #[link(name = "CoreFoundation", kind = "framework")] unsafe extern "C" { static kCFAllocatorDefault: CfAllocatorRef; static kCFBooleanTrue: CfBooleanRef; fn CFNumberCreate( allocator: CfAllocatorRef, the_type: i32, value_ptr: *const c_void, ) -> CfNumberRef; fn CFRelease(cf: *const c_void); fn CFRetain(cf: *const c_void) -> *const c_void; } #[link(name = "CoreVideo", kind = "framework")] unsafe extern "C" { fn CVPixelBufferCreateWithIOSurface( allocator: CfAllocatorRef, surface: IoSurfaceRef, pixel_buffer_attributes: CfDictionaryRef, pixel_buffer_out: *mut CvPixelBufferRef, ) -> i32; fn CVPixelBufferRelease(buffer: CvPixelBufferRef); } #[link(name = "CoreMedia", kind = "framework")] unsafe extern "C" { fn CMSampleBufferGetDataBuffer(sample_buffer: CmSampleBufferRef) -> CmBlockBufferRef; fn CMBlockBufferGetDataLength(block_buffer: CmBlockBufferRef) -> usize; fn CMBlockBufferCopyDataBytes( block_buffer: CmBlockBufferRef, offset_to_data: usize, data_length: usize, destination: *mut c_void, ) -> OsStatus; } #[link(name = "VideoToolbox", kind = "framework")] unsafe extern "C" { static kVTCompressionPropertyKey_RealTime: CfStringRef; static kVTCompressionPropertyKey_ProfileLevel: CfStringRef; static kVTProfileLevel_H264_Baseline_AutoLevel: CfStringRef; static kVTCompressionPropertyKey_AverageBitRate: CfStringRef; static kVTCompressionPropertyKey_ExpectedFrameRate: CfStringRef; static kVTCompressionPropertyKey_AllowFrameReordering: CfStringRef; fn VTCompressionSessionCreate( allocator: CfAllocatorRef, width: i32, height: i32, codec_type: u32, encoder_specification: CfDictionaryRef, source_image_buffer_attributes: CfDictionaryRef, compressed_data_allocator: CfAllocatorRef, output_callback: Option, output_callback_refcon: *mut c_void, session_out: *mut VtCompressionSessionRef, ) -> OsStatus; fn VTCompressionSessionEncodeFrame( session: VtCompressionSessionRef, image_buffer: CvPixelBufferRef, presentation_time_stamp: CmTime, duration: CmTime, frame_properties: CfDictionaryRef, source_frame_refcon: *mut c_void, info_flags_out: *mut u32, ) -> OsStatus; fn VTCompressionSessionCompleteFrames( session: VtCompressionSessionRef, complete_until: CmTime, ) -> OsStatus; fn VTCompressionSessionInvalidate(session: VtCompressionSessionRef); fn VTSessionSetProperty( session: *mut c_void, property_key: CfStringRef, property_value: *const c_void, ) -> OsStatus; fn VTPixelTransferSessionCreate( allocator: CfAllocatorRef, session_out: *mut VtPixelTransferSessionRef, ) -> OsStatus; fn VTPixelTransferSessionTransferImage( session: VtPixelTransferSessionRef, source: CvPixelBufferRef, destination: CvPixelBufferRef, ) -> OsStatus; fn VTPixelTransferSessionInvalidate(session: VtPixelTransferSessionRef); } type VtCompressionOutputCallback = unsafe extern "C" fn( output_callback_refcon: *mut c_void, source_frame_refcon: *mut c_void, status: OsStatus, info_flags: u32, sample_buffer: CmSampleBufferRef, ); fn is_invalid_session_error(err: &EncoderError) -> bool { match err { EncoderError::EncodeFailed { vendor, status } => { assert!(!vendor.is_empty(), "encode error vendor non-empty"); *vendor == "vt-compression-encode" && *status == KVT_INVALID_SESSION_ERR as i64 } _ => false, } } fn cf_num_i32(v: i32) -> CfNumberRef { let ptr: *const i32 = &v; unsafe { CFNumberCreate( kCFAllocatorDefault, KCF_NUMBER_SINT32_TYPE, ptr as *const c_void, ) } } pub struct VtPixelTransfer { session: NonNull, } unsafe impl Send for VtPixelTransfer {} impl VtPixelTransfer { pub fn new() -> Result { let mut raw: VtPixelTransferSessionRef = ptr::null_mut(); let status = unsafe { VTPixelTransferSessionCreate(kCFAllocatorDefault, &mut raw) }; if status != NOERR || raw.is_null() { return Err(EncoderError::SessionInitFailed { vendor: "vt-pixel-transfer", status: status as i64, }); } let session = NonNull::new(raw).ok_or(EncoderError::SessionInitFailed { vendor: "vt-pixel-transfer", status: -1, })?; assert!( session.as_ptr() as usize != 0, "transfer session non-null after create" ); Ok(Self { session }) } #[allow(clippy::missing_safety_doc)] pub unsafe fn transfer( &self, source: CvPixelBufferRef, destination: CvPixelBufferRef, ) -> Result<(), EncoderError> { assert!(!source.is_null(), "source pixel buffer non-null"); assert!(!destination.is_null(), "destination pixel buffer non-null"); let status = unsafe { VTPixelTransferSessionTransferImage(self.session.as_ptr(), source, destination) }; if status != NOERR { return Err(EncoderError::EncodeFailed { vendor: "vt-pixel-transfer", status: status as i64, }); } Ok(()) } #[allow(clippy::missing_safety_doc)] pub unsafe fn wrap_iosurface( surface: IoSurfaceRef, width: u32, height: u32, ) -> Result { assert!(!surface.is_null(), "wrap source IOSurface non-null"); assert!(width > 0 && height > 0, "wrap dims positive"); let mut pb: CvPixelBufferRef = ptr::null_mut(); let status = unsafe { CVPixelBufferCreateWithIOSurface(kCFAllocatorDefault, surface, ptr::null(), &mut pb) }; if status != 0 || pb.is_null() { return Err(EncoderError::EncodeFailed { vendor: "cv-create-iosurface", status: status as i64, }); } assert!(!pb.is_null(), "wrap pb non-null after create"); Ok(pb) } } impl Drop for VtPixelTransfer { fn drop(&mut self) { let p = self.session.as_ptr(); if !p.is_null() { unsafe { VTPixelTransferSessionInvalidate(p) }; unsafe { CFRelease(p) }; } } } struct CompletionRing { buffer: VecDeque, } impl CompletionRing { fn new() -> Self { Self { buffer: VecDeque::with_capacity(COMPLETION_RING_CAPACITY), } } fn push(&mut self, item: EncodedBitstream) { if self.buffer.len() >= COMPLETION_RING_CAPACITY { self.buffer.pop_front(); } self.buffer.push_back(item); assert!( self.buffer.len() <= COMPLETION_RING_CAPACITY, "completion ring bounded" ); } fn pop(&mut self) -> Option { let item = self.buffer.pop_front(); assert!( self.buffer.len() <= COMPLETION_RING_CAPACITY, "completion ring bounded after pop" ); item } fn len(&self) -> usize { let n = self.buffer.len(); assert!(n <= COMPLETION_RING_CAPACITY, "completion ring len bounded"); n } } struct SharedState { completed: CompletionRing, accepted: u64, last_pts_us: u64, } struct VtCallbackContext { state: Arc>, failed_completions: AtomicU64, } unsafe extern "C" fn vt_compression_output( output_callback_refcon: *mut c_void, source_frame_refcon: *mut c_void, status: OsStatus, _info_flags: u32, sample_buffer: CmSampleBufferRef, ) { if output_callback_refcon.is_null() { return; } let ctx = unsafe { &*(output_callback_refcon as *const VtCallbackContext) }; if status != NOERR { ctx.failed_completions.fetch_add(1, Ordering::Relaxed); return; } if sample_buffer.is_null() { return; } let block = unsafe { CMSampleBufferGetDataBuffer(sample_buffer) }; if block.is_null() { return; } let len = unsafe { CMBlockBufferGetDataLength(block) }; if len == 0 || len > MAX_BITSTREAM_BYTES { return; } let mut bytes: Vec = vec![0u8; len]; let copy_status = unsafe { CMBlockBufferCopyDataBytes(block, 0, len, bytes.as_mut_ptr() as *mut c_void) }; if copy_status != NOERR { return; } let pts_us = source_frame_refcon as usize as u64; let payload = match std::panic::catch_unwind(|| EncodedBitstream::new(bytes, pts_us, pts_us, true)) { Ok(p) => p, Err(_) => return, }; if let Ok(mut guard) = ctx.state.lock() { guard.completed.push(payload); } } struct SlotSurface { surface: IoSurfaceRef, pixel_buffer: CvPixelBufferRef, } pub struct VtCompressionHandoff { session: NonNull, callback_context: Box, shared: Arc>, surfaces: HashMap, dims: EncoderDims, next_slot: u32, frame_interval_us: u64, frame_rate: EncoderFrameRate, session_rebuilds: u64, } unsafe impl Send for VtCompressionHandoff {} impl VtCompressionHandoff { pub fn try_new(dims: EncoderDims) -> Result { Self::try_new_with_frame_rate(dims, EncoderFrameRate::from_fps(30)) } pub fn try_new_with_frame_rate( dims: EncoderDims, frame_rate: EncoderFrameRate, ) -> Result { assert!( dims.width > 0 && dims.height > 0, "compression dims positive" ); assert!(frame_rate.numerator > 0, "frame rate numerator positive"); assert!( frame_rate.denominator > 0, "frame rate denominator positive" ); if dims.width > 7680 || dims.height > 4320 { return Err(EncoderError::DimensionsOutOfRange { width: dims.width, height: dims.height, }); } let shared = Arc::new(Mutex::new(SharedState { completed: CompletionRing::new(), accepted: 0, last_pts_us: 0, })); let mut callback_context = Box::new(VtCallbackContext { state: shared.clone(), failed_completions: AtomicU64::new(0), }); let cb_ctx_ptr = callback_context.as_mut() as *mut VtCallbackContext as *mut c_void; let session = Self::create_session(dims, cb_ctx_ptr, frame_rate)?; let handoff = Self { session, callback_context, shared, surfaces: HashMap::new(), dims, next_slot: 0, frame_interval_us: frame_rate.frame_interval_us(), frame_rate, session_rebuilds: 0, }; assert!( handoff.session.as_ptr() as usize != 0, "session non-null after init" ); assert_eq!(handoff.next_slot, 0, "next slot fresh"); Ok(handoff) } fn create_session( dims: EncoderDims, cb_ctx_ptr: *mut c_void, frame_rate: EncoderFrameRate, ) -> Result, EncoderError> { assert!(dims.width > 0, "session dims width positive"); assert!(!cb_ctx_ptr.is_null(), "session callback context non-null"); assert!(frame_rate.numerator > 0, "frame rate numerator positive"); let mut raw: VtCompressionSessionRef = ptr::null_mut(); let status = unsafe { VTCompressionSessionCreate( kCFAllocatorDefault, dims.width as i32, dims.height as i32, KCM_VIDEO_CODEC_TYPE_H264, ptr::null(), ptr::null(), kCFAllocatorDefault, Some(vt_compression_output), cb_ctx_ptr, &mut raw, ) }; if status != NOERR || raw.is_null() { return Err(EncoderError::SessionInitFailed { vendor: "vt-compression", status: status as i64, }); } let session = NonNull::new(raw).ok_or(EncoderError::SessionInitFailed { vendor: "vt-compression", status: -1, })?; Self::configure(session.as_ptr(), frame_rate)?; Ok(session) } fn try_rebuild_session(&mut self) -> bool { assert!( self.session_rebuilds <= VT_SESSION_REBUILD_MAX, "rebuild count within cap" ); if self.session_rebuilds >= VT_SESSION_REBUILD_MAX { return false; } let cb_ctx_ptr = self.callback_context.as_mut() as *mut VtCallbackContext as *mut c_void; let Ok(new_session) = Self::create_session(self.dims, cb_ctx_ptr, self.frame_rate) else { return false; }; let old = self.session.as_ptr(); assert!(!old.is_null(), "old session non-null before replace"); unsafe { VTCompressionSessionInvalidate(old); CFRelease(old); } self.session = new_session; self.session_rebuilds = self.session_rebuilds.saturating_add(1); assert!( self.session_rebuilds <= VT_SESSION_REBUILD_MAX, "rebuild count stays within cap" ); true } fn configure(session: *mut c_void, frame_rate: EncoderFrameRate) -> Result<(), EncoderError> { assert!(!session.is_null(), "configure session non-null"); assert!(frame_rate.numerator > 0, "frame rate numerator positive"); let real_time_true = unsafe { kCFBooleanTrue }; let st1 = unsafe { VTSessionSetProperty(session, kVTCompressionPropertyKey_RealTime, real_time_true) }; if st1 != NOERR { return Err(EncoderError::SessionInitFailed { vendor: "vt-compression-realtime", status: st1 as i64, }); } let st2 = unsafe { VTSessionSetProperty( session, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Baseline_AutoLevel, ) }; if st2 != NOERR { return Err(EncoderError::SessionInitFailed { vendor: "vt-compression-profile", status: st2 as i64, }); } let bitrate = cf_num_i32(2_000_000); let st3 = unsafe { VTSessionSetProperty(session, kVTCompressionPropertyKey_AverageBitRate, bitrate) }; unsafe { CFRelease(bitrate) }; if st3 != NOERR { return Err(EncoderError::SessionInitFailed { vendor: "vt-compression-bitrate", status: st3 as i64, }); } let fps_value = frame_rate.gop_pic_size() as i32; let fps = cf_num_i32(fps_value); let st4 = unsafe { VTSessionSetProperty(session, kVTCompressionPropertyKey_ExpectedFrameRate, fps) }; unsafe { CFRelease(fps) }; if st4 != NOERR { return Err(EncoderError::SessionInitFailed { vendor: "vt-compression-fps", status: st4 as i64, }); } let no_reorder = unsafe { CFRetain(kCFBooleanTrue) }; let st5 = unsafe { VTSessionSetProperty( session, kVTCompressionPropertyKey_AllowFrameReordering, no_reorder, ) }; unsafe { CFRelease(no_reorder) }; if st5 != NOERR { return Err(EncoderError::SessionInitFailed { vendor: "vt-compression-reorder", status: st5 as i64, }); } Ok(()) } pub fn pending_completion(&self) -> usize { let guard = match self.shared.lock() { Ok(g) => g, Err(_) => return 0, }; let n = guard.completed.len(); assert!(n <= COMPLETION_RING_CAPACITY, "pending count bounded"); n } pub fn accepted_count(&self) -> u64 { let guard = match self.shared.lock() { Ok(g) => g, Err(_) => return 0, }; let n = guard.accepted; assert!( self.surfaces.len() as u64 <= u32::MAX as u64, "surfaces table bounded" ); n } pub fn failed_completion_count(&self) -> u64 { let n = self .callback_context .failed_completions .load(Ordering::Relaxed); assert!( self.session_rebuilds <= VT_SESSION_REBUILD_MAX, "rebuild count within cap" ); n } pub fn session_rebuild_count(&self) -> u64 { let n = self.session_rebuilds; assert!(n <= VT_SESSION_REBUILD_MAX, "rebuild count within cap"); n } pub fn wait_for_completion(&self) -> Result<(), EncoderError> { let until = CmTime { value: 0, timescale: 0, flags: 0, epoch: 0, }; let status = unsafe { VTCompressionSessionCompleteFrames(self.session.as_ptr(), until) }; if status != NOERR { return Err(EncoderError::EncodeFailed { vendor: "vt-compression-complete", status: status as i64, }); } Ok(()) } fn encode_frame_once( &self, pixel_buffer: CvPixelBufferRef, pic_params: PicParams, ) -> Result<(), EncoderError> { assert!(!pixel_buffer.is_null(), "encode pixel buffer non-null"); assert!(self.frame_interval_us > 0, "frame interval positive"); let pts = CmTime { value: pic_params.pts_us as i64, timescale: 1_000_000, flags: KCM_TIME_FLAGS_VALID, epoch: 0, }; let dur = CmTime { value: self.frame_interval_us as i64, timescale: 1_000_000, flags: KCM_TIME_FLAGS_VALID, epoch: 0, }; let mut flags_out: u32 = 0; let refcon = pic_params.pts_us as usize as *mut c_void; let enc_status = unsafe { VTCompressionSessionEncodeFrame( self.session.as_ptr(), pixel_buffer, pts, dur, ptr::null(), refcon, &mut flags_out, ) }; if enc_status != NOERR { return Err(EncoderError::EncodeFailed { vendor: "vt-compression-encode", status: enc_status as i64, }); } Ok(()) } fn encode_one(&mut self, slot: HandoffSlot, pic_params: PicParams) -> Result<(), EncoderError> { assert!(slot.shared_handle != 0, "encode slot handle non-zero"); let pixel_buffer = match self.surfaces.get(&slot.slot_index) { Some(entry) => { assert!(!entry.surface.is_null(), "cached slot surface non-null"); assert!( !entry.pixel_buffer.is_null(), "cached pixel buffer non-null" ); entry.pixel_buffer } None => { return Err(EncoderError::SlotUnknown { slot_index: slot.slot_index, }); } }; let mut attempts: usize = 0; while attempts < VT_ENCODE_ATTEMPTS_PER_FRAME_MAX { attempts += 1; assert!( attempts <= VT_ENCODE_ATTEMPTS_PER_FRAME_MAX, "encode attempts bounded" ); match self.encode_frame_once(pixel_buffer, pic_params) { Ok(()) => { if let Ok(mut guard) = self.shared.lock() { guard.accepted = guard.accepted.saturating_add(1); guard.last_pts_us = pic_params.pts_us; } return Ok(()); } Err(err) => { if !is_invalid_session_error(&err) { return Err(err); } if !self.try_rebuild_session() { break; } } } } Err(EncoderError::EncodeFailed { vendor: "vt-compression-invalid-session", status: KVT_INVALID_SESSION_ERR as i64, }) } } impl Drop for VtCompressionHandoff { fn drop(&mut self) { for (_, entry) in self.surfaces.drain() { if !entry.pixel_buffer.is_null() { unsafe { CVPixelBufferRelease(entry.pixel_buffer) }; } } let p = self.session.as_ptr(); if !p.is_null() { unsafe { VTCompressionSessionInvalidate(p) }; unsafe { CFRelease(p) }; } let _ = &self.callback_context; } } impl VideoToolboxHandoff for VtCompressionHandoff { fn register_slot( &mut self, iosurface_handle: u64, _key: u64, dims: EncoderDims, ) -> Result { if iosurface_handle == 0 { return Err(EncoderError::SlotUnknown { slot_index: u32::MAX, }); } if dims.width == 0 || dims.height == 0 { return Err(EncoderError::DimensionsOutOfRange { width: dims.width, height: dims.height, }); } let idx = self.next_slot; let surface = iosurface_handle as IoSurfaceRef; if surface.is_null() { return Err(EncoderError::SlotUnknown { slot_index: idx }); } let mut pixel_buffer: CvPixelBufferRef = ptr::null_mut(); let status = unsafe { CVPixelBufferCreateWithIOSurface( kCFAllocatorDefault, surface, ptr::null(), &mut pixel_buffer, ) }; if status != 0 || pixel_buffer.is_null() { return Err(EncoderError::RegisterFailed { vendor: "vt-cv-create", status: status as i64, }); } self.surfaces.insert( idx, SlotSurface { surface, pixel_buffer, }, ); self.next_slot = self.next_slot.saturating_add(1); let slot = HandoffSlot::new(idx, iosurface_handle); assert_eq!( slot.shared_handle, iosurface_handle, "slot handle preserved" ); assert!(self.next_slot > idx, "slot counter advanced"); Ok(slot) } fn encode_shared_async( &mut self, slot: HandoffSlot, _key: u64, dims: EncoderDims, pic_params: PicParams, ) -> Result<(), EncoderError> { if dims.width == 0 || dims.height == 0 { return Err(EncoderError::DimensionsOutOfRange { width: dims.width, height: dims.height, }); } self.encode_one(slot, pic_params) } fn poll_completed(&mut self, _slot: HandoffSlot) -> Option { let mut guard = self.shared.lock().ok()?; let item = guard.completed.pop(); if let Some(ref payload) = item { assert!(!payload.data.is_empty(), "polled payload non-empty"); assert!( payload.data.len() <= MAX_BITSTREAM_BYTES, "polled payload bounded" ); } item } fn unregister_slot(&mut self, slot: HandoffSlot) { assert!(slot.shared_handle != 0, "unregister handle non-zero"); if let Some(entry) = self.surfaces.remove(&slot.slot_index) { assert!( !entry.pixel_buffer.is_null(), "cached pixel buffer non-null on unregister" ); unsafe { CVPixelBufferRelease(entry.pixel_buffer) }; } } fn encode_shared( &mut self, submission: EncoderSubmission, callback: &mut dyn crate::encoder_handoff::EncoderCompletionCallback, ) -> Result<(), RingError> { if submission.shared_handle == 0 { return Err(RingError::UnknownSlot); } if submission.dims.width == 0 || submission.dims.height == 0 { return Err(RingError::BackendFailed { source: crate::backend::BackendError::DimensionsOutOfRange { width: submission.dims.width, height: submission.dims.height, }, }); } let slot = HandoffSlot::new( (submission.sequence & 0x3f) as u32, submission.shared_handle, ); let pts_us = match submission.capture_pts_us { Some(capture_pts_us) => capture_pts_us, None => submission.sequence.saturating_mul(self.frame_interval_us), }; let params = PicParams::new(pts_us, false); let encoded_bytes_estimate = self.pending_completion() as u32; if let Err(e) = self.encode_one(slot, params) { return Err(RingError::BackendFailed { source: crate::backend::BackendError::PlatformUnsupported { reason: match e { EncoderError::SlotUnknown { .. } => "vt slot unknown", EncoderError::EncodeFailed { .. } => "vt encode failed", _ => "vt session error", }, }, }); } callback.on_complete(submission.sequence, encoded_bytes_estimate); Ok(()) } } #[cfg(test)] pub(crate) fn make_cv_nv12_pixel_buffer( width: u32, height: u32, ) -> objc2_core_foundation::CFRetained { use core::ptr::NonNull; use objc2_core_foundation::{CFDictionary, CFRetained}; use objc2_core_video::CVPixelBuffer; let mut empty_keys: [*const c_void; 0] = []; let mut empty_vals: [*const c_void; 0] = []; let iosurf_dict: CFRetained = unsafe { CFDictionary::new( None, empty_keys.as_mut_ptr(), empty_vals.as_mut_ptr(), 0, &objc2_core_foundation::kCFTypeDictionaryKeyCallBacks, &objc2_core_foundation::kCFTypeDictionaryValueCallBacks, ) .expect("iosurf empty dict") }; let key_ref: &objc2_core_foundation::CFString = unsafe { objc2_core_video::kCVPixelBufferIOSurfacePropertiesKey }; let key_ptr: *const c_void = key_ref as *const _ as *const c_void; let val_ptr: *const c_void = &*iosurf_dict as *const _ as *const c_void; let mut keys = [key_ptr]; let mut vals = [val_ptr]; let attrs: CFRetained = unsafe { CFDictionary::new( None, keys.as_mut_ptr(), vals.as_mut_ptr(), 1, &objc2_core_foundation::kCFTypeDictionaryKeyCallBacks, &objc2_core_foundation::kCFTypeDictionaryValueCallBacks, ) .expect("attrs dict") }; let mut out: *mut CVPixelBuffer = std::ptr::null_mut(); let status = unsafe { objc2_core_video::CVPixelBufferCreate( None, width as usize, height as usize, KCV_PIXEL_FORMAT_TYPE_420_YPCBCR8_BIPLANAR_VIDEO, Some(&attrs), NonNull::new(&mut out).expect("out pointer non-null"), ) }; assert_eq!(status, 0, "CVPixelBufferCreate succeeded"); assert!(!out.is_null(), "CVPixelBufferCreate produced buffer"); unsafe { CFRetained::from_raw(NonNull::new_unchecked(out)) } } #[cfg(test)] mod tests { use super::*; fn make_handoff() -> VtCompressionHandoff { VtCompressionHandoff::try_new(EncoderDims::new(1280, 720)).expect("create handoff") } pub(super) fn iosurface_ptr_from_pb(pb: &objc2_core_video::CVPixelBuffer) -> *mut c_void { let s = objc2_core_video::CVPixelBufferGetIOSurface(Some(pb)); let surface = s.expect("pixel buffer must be IOSurface-backed"); let raw: *const objc2_io_surface::IOSurfaceRef = &*surface; raw as *mut c_void } #[test] fn vt_compression_handoff_encodes_one_frame() { let mut handoff = make_handoff(); let pb = make_cv_nv12_pixel_buffer(1280, 720); let surface = iosurface_ptr_from_pb(&pb); let slot = handoff .register_slot(surface as u64, 0, EncoderDims::new(1280, 720)) .expect("register slot"); let params = PicParams::new(0, false); handoff .encode_shared_async(slot, 0, EncoderDims::new(1280, 720), params) .expect("encode async"); handoff.wait_for_completion().expect("complete frames"); let payload = handoff.poll_completed(slot).expect("polled bitstream"); assert!(!payload.data.is_empty(), "encoded payload non-empty"); assert!( payload.data.len() <= MAX_BITSTREAM_BYTES, "encoded payload bounded" ); } struct RecordingCallback { seen: Vec<(u64, u32)>, } impl crate::encoder_handoff::EncoderCompletionCallback for RecordingCallback { fn on_complete(&mut self, sequence: u64, encoded_bytes: u32) { self.seen.push((sequence, encoded_bytes)); } } #[test] fn output_callback_counts_failed_completions() { let ctx = VtCallbackContext { state: Arc::new(Mutex::new(SharedState { completed: CompletionRing::new(), accepted: 0, last_pts_us: 0, })), failed_completions: AtomicU64::new(0), }; let ctx_ptr = &ctx as *const VtCallbackContext as *mut c_void; unsafe { vt_compression_output( ctx_ptr, ptr::null_mut(), KVT_INVALID_SESSION_ERR, 0, ptr::null_mut(), ); vt_compression_output(ctx_ptr, ptr::null_mut(), -1, 0, ptr::null_mut()); } assert_eq!(ctx.failed_completions.load(Ordering::Relaxed), 2); let guard = ctx.state.lock().expect("state lock"); assert_eq!( guard.completed.len(), 0, "failed completions enqueue nothing" ); } #[test] fn failed_completion_count_starts_at_zero_and_survives_encode() { let mut handoff = make_handoff(); assert_eq!(handoff.failed_completion_count(), 0); let pb = make_cv_nv12_pixel_buffer(1280, 720); let surface = iosurface_ptr_from_pb(&pb); let slot = handoff .register_slot(surface as u64, 0, EncoderDims::new(1280, 720)) .expect("register slot"); handoff .encode_shared_async( slot, 0, EncoderDims::new(1280, 720), PicParams::new(0, false), ) .expect("encode async"); handoff.wait_for_completion().expect("complete frames"); assert_eq!(handoff.failed_completion_count(), 0); } #[test] fn session_rebuild_bounded_by_named_cap() { let mut handoff = make_handoff(); assert_eq!(handoff.session_rebuild_count(), 0); for expected in 1..=VT_SESSION_REBUILD_MAX { assert!(handoff.try_rebuild_session(), "rebuild within cap succeeds"); assert_eq!(handoff.session_rebuild_count(), expected); } assert!( !handoff.try_rebuild_session(), "rebuild beyond cap rejected" ); assert_eq!(handoff.session_rebuild_count(), VT_SESSION_REBUILD_MAX); } #[test] fn session_still_encodes_after_rebuild() { let mut handoff = make_handoff(); assert!(handoff.try_rebuild_session(), "first rebuild succeeds"); let pb = make_cv_nv12_pixel_buffer(1280, 720); let surface = iosurface_ptr_from_pb(&pb); let slot = handoff .register_slot(surface as u64, 0, EncoderDims::new(1280, 720)) .expect("register slot"); handoff .encode_shared_async( slot, 0, EncoderDims::new(1280, 720), PicParams::new(0, false), ) .expect("encode async after rebuild"); handoff.wait_for_completion().expect("complete frames"); let payload = handoff.poll_completed(slot).expect("polled bitstream"); assert!(!payload.data.is_empty(), "encoded payload non-empty"); assert_eq!(handoff.session_rebuild_count(), 1); } #[test] fn encode_shared_uses_capture_pts_when_present() { let mut handoff = make_handoff(); let pb = make_cv_nv12_pixel_buffer(1280, 720); let surface = iosurface_ptr_from_pb(&pb); let slot = handoff .register_slot(surface as u64, 0, EncoderDims::new(1280, 720)) .expect("register slot"); assert_eq!(slot.slot_index, 0); let dims = EncoderDims::new(1280, 720); let submission = EncoderSubmission::new(surface as u64, 0, dims, 0).with_capture_pts_us(987_654); let mut cb = RecordingCallback { seen: Vec::new() }; VideoToolboxHandoff::encode_shared(&mut handoff, submission, &mut cb) .expect("encode shared with capture pts"); handoff.wait_for_completion().expect("complete frames"); let payload = handoff.poll_completed(slot).expect("polled bitstream"); assert_eq!(payload.pts_us, 987_654, "real capture pts threaded through"); assert_eq!(cb.seen.len(), 1); } #[test] fn encode_shared_synthesizes_pts_when_capture_pts_absent() { let mut handoff = make_handoff(); let pb = make_cv_nv12_pixel_buffer(1280, 720); let surface = iosurface_ptr_from_pb(&pb); let slot = handoff .register_slot(surface as u64, 0, EncoderDims::new(1280, 720)) .expect("register slot"); assert_eq!(slot.slot_index, 0); let dims = EncoderDims::new(1280, 720); let submission = EncoderSubmission::new(surface as u64, 0, dims, 0); assert_eq!(submission.capture_pts_us, None); let mut cb = RecordingCallback { seen: Vec::new() }; VideoToolboxHandoff::encode_shared(&mut handoff, submission, &mut cb) .expect("encode shared without capture pts"); handoff.wait_for_completion().expect("complete frames"); let payload = handoff.poll_completed(slot).expect("polled bitstream"); assert_eq!(payload.pts_us, 0, "sequence 0 synthesizes pts 0"); } #[test] fn invalid_session_error_detection_is_exact() { let invalid = EncoderError::EncodeFailed { vendor: "vt-compression-encode", status: KVT_INVALID_SESSION_ERR as i64, }; assert!(is_invalid_session_error(&invalid)); let other_status = EncoderError::EncodeFailed { vendor: "vt-compression-encode", status: -1, }; assert!(!is_invalid_session_error(&other_status)); let other_vendor = EncoderError::EncodeFailed { vendor: "vt-cv-create", status: KVT_INVALID_SESSION_ERR as i64, }; assert!(!is_invalid_session_error(&other_vendor)); } #[test] fn vt_compression_handoff_handles_back_pressure() { let mut handoff = make_handoff(); let pb = make_cv_nv12_pixel_buffer(1280, 720); let surface = iosurface_ptr_from_pb(&pb); let slot = handoff .register_slot(surface as u64, 0, EncoderDims::new(1280, 720)) .expect("register slot"); let total: u64 = (COMPLETION_RING_CAPACITY as u64) + 8; for i in 0..total { let params = PicParams::new(i * 33_333, false); handoff .encode_shared_async(slot, 0, EncoderDims::new(1280, 720), params) .expect("encode async"); } handoff.wait_for_completion().expect("complete frames"); let pending = handoff.pending_completion(); assert!( pending <= COMPLETION_RING_CAPACITY, "completion ring stays bounded" ); let mut drained = 0usize; while handoff.poll_completed(slot).is_some() { drained += 1; if drained > COMPLETION_RING_CAPACITY * 2 { break; } } assert!( drained <= COMPLETION_RING_CAPACITY, "drained respects completion bound" ); } }