use core::ffi::c_void;
use core::ptr;
use std::sync::{Arc, Mutex};
use crate::error::VTError;
use crate::ffi;
pub struct DecodedFrame {
pub image_buffer: Option<apple_cf::cv::CVPixelBuffer>,
pub presentation_time: (i64, i32),
pub duration: (i64, i32),
pub info_flags: u32,
pub status: i32,
}
type DecodeCallback = Box<dyn FnMut(DecodedFrame) + Send + 'static>;
struct CallbackState {
callback: Mutex<DecodeCallback>,
}
pub struct DecompressionSession {
session: ffi::VTDecompressionSessionRef,
_state: Arc<CallbackState>,
}
unsafe impl Send for DecompressionSession {}
unsafe impl Sync for DecompressionSession {}
impl Drop for DecompressionSession {
fn drop(&mut self) {
if !self.session.is_null() {
unsafe {
ffi::VTDecompressionSessionInvalidate(self.session);
ffi::CFRelease(self.session.cast_const());
}
self.session = ptr::null_mut();
}
}
}
impl DecompressionSession {
pub fn new<F>(
format_description: &apple_cf::cm::CMFormatDescription,
callback: F,
) -> Result<Self, VTError>
where
F: FnMut(DecodedFrame) + Send + 'static,
{
let state = Arc::new(CallbackState {
callback: Mutex::new(Box::new(callback)),
});
let state_for_callback = state.clone();
let ref_con = Arc::into_raw(state_for_callback).cast::<c_void>().cast_mut();
let record = ffi::VTDecompressionOutputCallbackRecord {
decompression_output_callback: decode_trampoline,
decompression_output_ref_con: ref_con,
};
let mut session: ffi::VTDecompressionSessionRef = ptr::null_mut();
let status = unsafe {
ffi::VTDecompressionSessionCreate(
ffi::kCFAllocatorDefault,
format_description.as_ptr().cast(),
ptr::null(),
ptr::null(),
&record,
&mut session,
)
};
if status != 0 {
unsafe { Arc::from_raw(ref_con.cast::<CallbackState>()) };
return Err(VTError::EncoderCallback(status));
}
Ok(Self {
session,
_state: state,
})
}
pub fn decode(
&self,
sample_buffer: &apple_cf::cm::CMSampleBuffer,
) -> Result<(), VTError> {
let mut info_flags: u32 = 0;
let status = unsafe {
ffi::VTDecompressionSessionDecodeFrame(
self.session,
sample_buffer.as_ptr(),
0,
ptr::null_mut(),
&mut info_flags,
)
};
if status == 0 {
Ok(())
} else {
Err(VTError::EncoderCallback(status))
}
}
pub fn wait_for_async_frames(&self) -> Result<(), VTError> {
let status =
unsafe { ffi::VTDecompressionSessionWaitForAsynchronousFrames(self.session) };
if status == 0 {
Ok(())
} else {
Err(VTError::EncoderCallback(status))
}
}
pub unsafe fn set_property(
&self,
key: ffi::CFStringRef,
value: ffi::CFTypeRef,
) -> Result<(), VTError> {
let status = ffi::VTSessionSetProperty(self.session.cast(), key, value);
if status != 0 {
return Err(VTError::SetPropertyFailed {
key: "<custom>".to_string(),
status,
});
}
Ok(())
}
pub fn set_real_time(&self, real_time: bool) -> Result<(), VTError> {
let v = unsafe {
if real_time {
ffi::kCFBooleanTrue
} else {
ffi::kCFBooleanFalse
}
};
unsafe {
self.set_property(ffi::kVTDecompressionPropertyKey_RealTime, v.cast())
}
}
pub fn set_max_output_buffer_depth(&self, depth: i32) -> Result<(), VTError> {
let v = unsafe {
ffi::CFNumberCreate(
ffi::kCFAllocatorDefault,
ffi::kCFNumberSInt32Type,
core::ptr::from_ref(&depth).cast(),
)
};
let r = unsafe {
self.set_property(
ffi::kVTDecompressionPropertyKey_MaximumOutputBufferDepth,
v.cast(),
)
};
unsafe { ffi::CFRelease(v) };
r
}
pub fn finish_delayed_frames(&self) -> Result<(), VTError> {
let status =
unsafe { ffi::VTDecompressionSessionFinishDelayedFrames(self.session) };
if status == 0 {
Ok(())
} else {
Err(VTError::EncoderCallback(status))
}
}
pub unsafe fn can_accept_format(&self, format: ffi::CMFormatDescriptionRef) -> bool {
ffi::VTDecompressionSessionCanAcceptFormatDescription(self.session, format)
}
}
unsafe extern "C" fn decode_trampoline(
output_ref_con: *mut c_void,
_source_frame_ref_con: *mut c_void,
status: ffi::OSStatus,
info_flags: u32,
image_buffer: *mut c_void,
pts: ffi::CMTime,
duration: ffi::CMTime,
) {
if output_ref_con.is_null() {
return;
}
let state = unsafe { Arc::from_raw(output_ref_con.cast::<CallbackState>()) };
let state_clone = state.clone();
core::mem::forget(state);
let image = if image_buffer.is_null() {
None
} else {
unsafe { ffi::CFRetain(image_buffer.cast_const()) };
apple_cf::cv::CVPixelBuffer::from_raw(image_buffer)
};
let frame = DecodedFrame {
image_buffer: image,
presentation_time: (pts.value, pts.timescale),
duration: (duration.value, duration.timescale),
info_flags,
status,
};
let Ok(mut guard) = state_clone.callback.lock() else {
return;
};
guard(frame);
}