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: &mut EncodedAccessUnit) -> Result<(), VideoError> {
let source = std::mem::take(&mut frame.data);
let mut in_place = None;
match source.try_into_mut() {
Ok(mut data) => {
if annex_b::to_length_prefixed_in_place(&mut data)? {
in_place = Some(data);
} else {
frame.data = data.freeze();
}
}
Err(data) => frame.data = data,
}
let converted = in_place
.is_none()
.then(|| annex_b::to_length_prefixed(&frame.data))
.transpose()?;
let data =
in_place
.as_deref()
.or(converted.as_deref())
.ok_or(VideoError::InvalidAnnexB(
"access unit contains no NAL 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() };
}
}