use std::{ffi::c_void, ptr, ptr::NonNull};
use objc2_core_foundation::{CFBoolean, CFRetained};
use objc2_core_media::CMFormatDescription;
use objc2_video_toolbox::{
kVTDecompressionPropertyKey_RealTime, VTDecodeFrameFlags, VTDecompressionOutputCallbackRecord,
VTDecompressionSession, VTSessionSetProperty,
};
use crate::{codecs::annex_b, CodecConfig, DecoderOptions, EncodedAccessUnit, VideoError};
use super::{
callback::{decompression_output_callback, CallbackState},
codecs, ffi,
};
pub(crate) struct VideoToolboxSession {
decoder: CFRetained<VTDecompressionSession>,
format: CFRetained<CMFormatDescription>,
callback: Box<CallbackState>,
drained: bool,
}
unsafe impl Send for VideoToolboxSession {}
unsafe impl Sync for VideoToolboxSession {}
impl VideoToolboxSession {
pub(crate) fn new(
config: &CodecConfig,
options: DecoderOptions,
callback: CallbackState,
) -> Result<Self, VideoError> {
let format = codecs::format_description(config)?;
let image_attributes = ffi::decoder_image_attributes();
let decoder_specification = ffi::decoder_specification(options.require_hardware);
let callback = Box::new(callback);
let callback_record = VTDecompressionOutputCallbackRecord {
decompressionOutputCallback: Some(decompression_output_callback),
decompressionOutputRefCon: ptr::from_ref(callback.as_ref()).cast_mut().cast::<c_void>(),
};
let mut decoder_ptr = ptr::null_mut();
let status = unsafe {
VTDecompressionSession::create(
None,
&format,
Some(decoder_specification.as_opaque()),
Some(image_attributes.as_opaque()),
ptr::from_ref(&callback_record),
NonNull::from(&mut decoder_ptr),
)
};
if status != 0 {
return Err(VideoError::Platform {
api: "VTDecompressionSessionCreate",
status,
});
}
let decoder_ptr = NonNull::new(decoder_ptr).ok_or(VideoError::Platform {
api: "VTDecompressionSessionCreate",
status,
})?;
let decoder = unsafe { CFRetained::from_raw(decoder_ptr) };
if options.low_latency {
let status = unsafe {
VTSessionSetProperty(
(*decoder).as_ref(),
kVTDecompressionPropertyKey_RealTime,
Some(CFBoolean::new(true).as_ref()),
)
};
if status != 0 {
unsafe { decoder.invalidate() };
return Err(VideoError::Platform {
api: "VTSessionSetProperty(RealTime)",
status,
});
}
}
Ok(Self {
decoder,
format,
callback,
drained: false,
})
}
pub(crate) fn submit(&self, frame: &EncodedAccessUnit) -> Result<(), VideoError> {
let data = annex_b::to_length_prefixed(&frame.data)?;
let sample = ffi::sample_buffer(&data, &self.format, frame.timestamp)?;
self.callback.submitted(frame.timestamp);
let flags = VTDecodeFrameFlags::Frame_EnableAsynchronousDecompression;
let status = unsafe {
self.decoder
.decode_frame(&sample, flags, ptr::null_mut(), ptr::null_mut())
};
if status != 0 {
self.callback.rejected(frame.timestamp, status);
return Err(VideoError::Platform {
api: "VTDecompressionSessionDecodeFrame",
status,
});
}
Ok(())
}
pub(crate) fn finish(mut self) -> Result<(), VideoError> {
let status = unsafe { self.decoder.wait_for_asynchronous_frames() };
self.drained = status == 0;
if status == 0 {
Ok(())
} else {
Err(VideoError::Platform {
api: "VTDecompressionSessionWaitForAsynchronousFrames",
status,
})
}
}
}
impl Drop for VideoToolboxSession {
fn drop(&mut self) {
if !self.drained {
let _ = unsafe { self.decoder.wait_for_asynchronous_frames() };
}
unsafe { self.decoder.invalidate() };
}
}