#![cfg(feature = "async")]
use core::future::Future;
use apple_cf::cm::CMSampleBuffer;
use apple_cf::cv::CVImageBuffer;
#[cfg(any(feature = "compression", feature = "frame_processor"))]
use apple_cf::cv::CVPixelBuffer;
#[cfg(feature = "compression")]
use apple_cf::{cf::CFDictionary, cm::CMTime};
#[cfg(feature = "frame_processor")]
use doom_fish_utils::{
panic_safe::catch_user_panic,
stream::{BoundedAsyncStream, NextItem},
};
#[cfg(feature = "compression")]
use crate::compression::CompressionSession;
#[cfg(feature = "frame_processor")]
use crate::raw_processing::{RawProcessingParameter, RawProcessingSession};
use crate::{DecompressionSession, VTError};
#[cfg(feature = "compression")]
#[cfg_attr(docsrs, doc(cfg(all(feature = "async", feature = "compression"))))]
pub struct AsyncCompressionSession<'a> {
session: &'a CompressionSession,
}
#[cfg(feature = "compression")]
impl core::fmt::Debug for AsyncCompressionSession<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("AsyncCompressionSession")
.finish_non_exhaustive()
}
}
#[cfg(feature = "compression")]
impl<'a> AsyncCompressionSession<'a> {
#[must_use]
pub const fn new(session: &'a CompressionSession) -> Self {
Self { session }
}
#[must_use = "futures do nothing unless awaited"]
#[allow(clippy::future_not_send)]
pub fn encode_frame(
&self,
image_buffer: CVPixelBuffer,
presentation_timestamp: CMTime,
duration: CMTime,
frame_properties: Option<CFDictionary>,
) -> impl Future<Output = Result<CMSampleBuffer, VTError>> + '_ {
self.session.encode_frame_async(
image_buffer,
presentation_timestamp,
duration,
frame_properties,
)
}
}
#[cfg(feature = "compression")]
impl<'a> From<&'a CompressionSession> for AsyncCompressionSession<'a> {
fn from(session: &'a CompressionSession) -> Self {
Self::new(session)
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub struct AsyncDecompressionSession<'a> {
session: &'a DecompressionSession,
}
impl core::fmt::Debug for AsyncDecompressionSession<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("AsyncDecompressionSession")
.finish_non_exhaustive()
}
}
impl<'a> AsyncDecompressionSession<'a> {
#[must_use]
pub const fn new(session: &'a DecompressionSession) -> Self {
Self { session }
}
#[must_use = "futures do nothing unless awaited"]
#[allow(clippy::future_not_send)]
pub fn decode_frame(
&self,
sample_buffer: CMSampleBuffer,
frame_flags: crate::ffi::VTDecodeFrameFlags,
) -> impl Future<Output = Result<CVImageBuffer, VTError>> + '_ {
self.session.decode_frame_async(sample_buffer, frame_flags)
}
}
impl<'a> From<&'a DecompressionSession> for AsyncDecompressionSession<'a> {
fn from(session: &'a DecompressionSession) -> Self {
Self::new(session)
}
}
#[cfg(feature = "frame_processor")]
#[cfg_attr(docsrs, doc(cfg(all(feature = "async", feature = "frame_processor"))))]
pub struct AsyncRawProcessingSession<'a> {
session: &'a RawProcessingSession,
}
#[cfg(feature = "frame_processor")]
impl core::fmt::Debug for AsyncRawProcessingSession<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("AsyncRawProcessingSession")
.finish_non_exhaustive()
}
}
#[cfg(feature = "frame_processor")]
impl<'a> AsyncRawProcessingSession<'a> {
#[must_use]
pub const fn new(session: &'a RawProcessingSession) -> Self {
Self { session }
}
#[must_use = "futures do nothing unless awaited"]
#[allow(clippy::future_not_send)]
pub fn process_frame(
&self,
input_pixel_buffer: CVPixelBuffer,
) -> impl Future<Output = Result<CVPixelBuffer, VTError>> + '_ {
self.session.process_frame_async(input_pixel_buffer)
}
pub fn parameter_changes(
&self,
capacity: usize,
) -> Result<RawProcessingParameterChangeStream<'a>, VTError> {
RawProcessingParameterChangeStream::subscribe(self.session, capacity)
}
}
#[cfg(feature = "frame_processor")]
impl<'a> From<&'a RawProcessingSession> for AsyncRawProcessingSession<'a> {
fn from(session: &'a RawProcessingSession) -> Self {
Self::new(session)
}
}
#[cfg(feature = "frame_processor")]
#[cfg_attr(docsrs, doc(cfg(all(feature = "async", feature = "frame_processor"))))]
pub struct RawProcessingParameterChangeStream<'a> {
inner: BoundedAsyncStream<Vec<RawProcessingParameter>>,
session: &'a RawProcessingSession,
}
#[cfg(feature = "frame_processor")]
impl RawProcessingParameterChangeStream<'_> {
fn subscribe(
session: &RawProcessingSession,
capacity: usize,
) -> Result<RawProcessingParameterChangeStream<'_>, VTError> {
let (stream, sender) = BoundedAsyncStream::new(capacity);
session.set_parameter_changed_handler(move |parameters| {
catch_user_panic(
"videotoolbox::async_api::raw_processing_parameter_changes",
|| {
sender.push(parameters);
},
);
})?;
Ok(RawProcessingParameterChangeStream {
inner: stream,
session,
})
}
#[must_use]
pub const fn next(&self) -> NextItem<'_, Vec<RawProcessingParameter>> {
self.inner.next()
}
#[must_use]
pub fn try_next(&self) -> Option<Vec<RawProcessingParameter>> {
self.inner.try_next()
}
#[must_use]
pub fn buffered_count(&self) -> usize {
self.inner.buffered_count()
}
}
#[cfg(feature = "frame_processor")]
impl Drop for RawProcessingParameterChangeStream<'_> {
fn drop(&mut self) {
let _ = self.session.clear_parameter_changed_handler();
}
}
#[cfg(feature = "frame_processor")]
impl core::fmt::Debug for RawProcessingParameterChangeStream<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("RawProcessingParameterChangeStream")
.field("buffered_count", &self.buffered_count())
.finish_non_exhaustive()
}
}
#[cfg(all(test, feature = "frame_processor"))]
mod tests {
use apple_cf::{cm::CMFormatDescription, cv::CVPixelBuffer};
use super::AsyncRawProcessingSession;
use crate::{copy_raw_processor_extension_properties, RawProcessingSession, VTError};
const BGRA: u32 = u32::from_be_bytes(*b"BGRA");
#[test]
fn async_raw_processing_process_frame_matches_existing_async_method_when_supported(
) -> Result<(), VTError> {
pollster::block_on(async {
let input = make_test_pixel_buffer(16, 16);
let Ok(format) = make_video_format_description(&input) else {
return Ok(());
};
if copy_raw_processor_extension_properties(&format).is_err() {
return Ok(());
}
let session = match RawProcessingSession::new(&format) {
Ok(session) => session,
Err(VTError::SessionCreateFailed(_)) => return Ok(()),
Err(error) => return Err(error),
};
let processed = match AsyncRawProcessingSession::new(&session)
.process_frame(input)
.await
{
Ok(processed) => processed,
Err(VTError::EncodeFailed(_)) => return Ok(()),
Err(error) => return Err(error),
};
assert_eq!(processed.width(), 16);
assert_eq!(processed.height(), 16);
Ok(())
})
}
#[test]
fn async_raw_processing_parameter_changes_constructs_stream_or_reports_runtime_gap(
) -> Result<(), VTError> {
let input = make_test_pixel_buffer(16, 16);
let Ok(format) = make_video_format_description(&input) else {
return Ok(());
};
if copy_raw_processor_extension_properties(&format).is_err() {
return Ok(());
}
let session = match RawProcessingSession::new(&format) {
Ok(session) => session,
Err(VTError::SessionCreateFailed(_)) => return Ok(()),
Err(error) => return Err(error),
};
let result = match AsyncRawProcessingSession::new(&session).parameter_changes(4) {
Ok(stream) => {
assert_eq!(stream.buffered_count(), 0);
assert!(stream.try_next().is_none());
Ok(())
}
Err(VTError::ApiFailed { api, status }) => {
assert_eq!(api, "VTRAWProcessingSessionSetParameterChangedHandler");
assert_ne!(status, 0);
Ok(())
}
Err(error) => Err(error),
};
result
}
fn make_test_pixel_buffer(width: usize, height: usize) -> CVPixelBuffer {
CVPixelBuffer::create(width, height, BGRA)
.unwrap_or_else(|status| panic!("CVPixelBuffer::create failed: {status}"))
}
fn make_video_format_description(
pixel_buffer: &CVPixelBuffer,
) -> Result<CMFormatDescription, i32> {
let mut description: apple_cf::raw::CMVideoFormatDescriptionRef = core::ptr::null();
let status = unsafe {
apple_cf::raw::CMVideoFormatDescriptionCreateForImageBuffer(
apple_cf::raw::kCFAllocatorDefault,
pixel_buffer.as_ptr().cast(),
&raw mut description,
)
};
if status == 0 && !description.is_null() {
CMFormatDescription::from_raw(description.cast_mut().cast()).ok_or(status)
} else {
Err(status)
}
}
}