video-toolbox 0.2.1

Safe bindings to VideoToolbox framework
Documentation
use std::ptr::{null, null_mut};

use block::{Block, ConcreteBlock};
use core_foundation::{
    base::{kCFAllocatorDefault, Boolean, CFAllocatorRef, CFType, CFTypeID, OSStatus, TCFType},
    declare_TCFType,
    dictionary::{CFDictionary, CFDictionaryRef},
    impl_CFTypeDescription, impl_TCFType,
    string::CFString,
};
use core_media::{
    format_description::{CMFormatDescription, CMFormatDescriptionRef, CMVideoCodecType, CMVideoFormatDescription, CMVideoFormatDescriptionRef},
    sample_buffer::{CMSampleBuffer, CMSampleBufferRef},
    time::CMTime,
};
use core_video::{
    image_buffer::{CVImageBuffer, CVImageBufferRef},
    pixel_buffer::{CVPixelBuffer, CVPixelBufferRef},
};
use libc::c_void;

use crate::{
    errors::{VTDecodeFrameFlags, VTDecodeInfoFlags},
    session::{TVTSession, VTSessionRef},
};

pub type VTDecompressionSessionRef = VTSessionRef;

#[repr(C, packed(4))]
#[derive(Debug, Copy, Clone)]
pub struct VTDecompressionOutputCallbackRecord {
    pub decompressionOutputCallback: Option<VTDecompressionOutputCallback>,
    pub decompressionOutputRefCon: *mut c_void,
}

pub type VTDecompressionOutputHandler = *const Block<(OSStatus, VTDecodeInfoFlags, CVImageBufferRef, CMTime, CMTime), ()>;

pub type VTDecompressionOutputCallback = extern "C" fn(
    decompressionOutputRefCon: *mut c_void,
    sourceFrameRefCon: *mut c_void,
    status: OSStatus,
    infoFlags: VTDecodeInfoFlags,
    imageBuffer: CVImageBufferRef,
    presentationTimeStamp: CMTime,
    presentationDuration: CMTime,
);

extern "C" {
    pub fn VTDecompressionSessionCreate(
        allocator: CFAllocatorRef,
        videoFormatDescription: CMVideoFormatDescriptionRef,
        videoDecoderSpecification: CFDictionaryRef,
        destinationImageBufferAttributes: CFDictionaryRef,
        outputCallback: *const VTDecompressionOutputCallbackRecord,
        decompressionSessionOut: *mut VTDecompressionSessionRef,
    ) -> OSStatus;
    pub fn VTDecompressionSessionGetTypeID() -> CFTypeID;
    pub fn VTDecompressionSessionDecodeFrame(
        session: VTDecompressionSessionRef,
        sampleBuffer: CMSampleBufferRef,
        decodeFlags: VTDecodeFrameFlags,
        sourceFrameRefCon: *mut c_void,
        infoFlagsOut: *mut VTDecodeInfoFlags,
    ) -> OSStatus;
    pub fn VTDecompressionSessionDecodeFrameWithOutputHandler(
        session: VTDecompressionSessionRef,
        sampleBuffer: CMSampleBufferRef,
        decodeFlags: VTDecodeFrameFlags,
        infoFlagsOut: *mut VTDecodeInfoFlags,
        outputHandler: VTDecompressionOutputHandler,
    ) -> OSStatus;
    pub fn VTDecompressionSessionFinishDelayedFrames(session: VTDecompressionSessionRef) -> OSStatus;
    pub fn VTDecompressionSessionCanAcceptFormatDescription(session: VTDecompressionSessionRef, newFormatDesc: CMFormatDescriptionRef) -> Boolean;
    pub fn VTDecompressionSessionWaitForAsynchronousFrames(session: VTDecompressionSessionRef) -> OSStatus;
    pub fn VTDecompressionSessionCopyBlackPixelBuffer(session: VTDecompressionSessionRef, pixelBufferOut: *mut CVPixelBufferRef) -> OSStatus;
    pub fn VTIsHardwareDecodeSupported(codecType: CMVideoCodecType) -> Boolean;
}

declare_TCFType!(VTDecompressionSession, VTDecompressionSessionRef);
impl_TCFType!(VTDecompressionSession, VTDecompressionSessionRef, VTDecompressionSessionGetTypeID);
impl_CFTypeDescription!(VTDecompressionSession);

impl TVTSession for VTDecompressionSession {}

impl VTDecompressionSession {
    pub fn new(
        video_format_description: CMVideoFormatDescription,
        video_decoder_specification: Option<CFDictionary<CFString, CFType>>,
        destination_image_buffer_attributes: Option<CFDictionary<CFString, CFType>>,
    ) -> Result<Self, OSStatus> {
        unsafe { Self::new_with_callback(video_format_description, video_decoder_specification, destination_image_buffer_attributes, None) }
    }

    pub unsafe fn new_with_callback(
        video_format_description: CMVideoFormatDescription,
        video_decoder_specification: Option<CFDictionary<CFString, CFType>>,
        destination_image_buffer_attributes: Option<CFDictionary<CFString, CFType>>,
        output_callback: Option<*const VTDecompressionOutputCallbackRecord>,
    ) -> Result<Self, OSStatus> {
        let mut session: VTDecompressionSessionRef = null_mut();
        let status = VTDecompressionSessionCreate(
            kCFAllocatorDefault,
            video_format_description.as_concrete_TypeRef(),
            video_decoder_specification.as_ref().map_or(null(), |s| s.as_concrete_TypeRef()),
            destination_image_buffer_attributes.as_ref().map_or(null(), |a| a.as_concrete_TypeRef()),
            output_callback.unwrap_or(null()),
            &mut session,
        );
        if status == 0 {
            Ok(TCFType::wrap_under_create_rule(session))
        } else {
            Err(status)
        }
    }

    pub unsafe fn decode_frame(
        &self,
        sample_buffer: CMSampleBuffer,
        decode_flags: VTDecodeFrameFlags,
        source_frame_ref_con: *mut c_void,
    ) -> Result<VTDecodeInfoFlags, OSStatus> {
        let mut info_flags_out: VTDecodeInfoFlags = VTDecodeInfoFlags::empty();

        let status = VTDecompressionSessionDecodeFrame(
            self.as_concrete_TypeRef(),
            sample_buffer.as_concrete_TypeRef(),
            decode_flags,
            source_frame_ref_con,
            &mut info_flags_out,
        );

        if status == 0 {
            Ok(info_flags_out)
        } else {
            Err(status)
        }
    }

    pub fn decode_frame_with_closure<F>(
        &self,
        sample_buffer: CMSampleBuffer,
        decode_flags: VTDecodeFrameFlags,
        closure: F,
    ) -> Result<VTDecodeInfoFlags, OSStatus>
    where
        F: Fn(OSStatus, VTDecodeInfoFlags, CVImageBuffer, CMTime, CMTime) + 'static,
    {
        let handler = ConcreteBlock::new(
            move |status: OSStatus, info_flags: VTDecodeInfoFlags, image_buffer: CVImageBufferRef, pts: CMTime, duration: CMTime| {
                let image_buffer = unsafe { CVImageBuffer::wrap_under_get_rule(image_buffer) };
                closure(status, info_flags, image_buffer, pts, duration)
            },
        )
        .copy();

        let mut info_flags_out: VTDecodeInfoFlags = VTDecodeInfoFlags::empty();

        let status = unsafe {
            VTDecompressionSessionDecodeFrameWithOutputHandler(
                self.as_concrete_TypeRef(),
                sample_buffer.as_concrete_TypeRef(),
                decode_flags,
                &mut info_flags_out,
                &*handler,
            )
        };

        if status == 0 {
            Ok(info_flags_out)
        } else {
            Err(status)
        }
    }

    pub fn finish_delayed_frames(&self) -> Result<(), OSStatus> {
        let status = unsafe { VTDecompressionSessionFinishDelayedFrames(self.as_concrete_TypeRef()) };
        if status == 0 {
            Ok(())
        } else {
            Err(status)
        }
    }

    pub fn can_accept_format_description(&self, new_format_desc: CMFormatDescription) -> bool {
        unsafe { VTDecompressionSessionCanAcceptFormatDescription(self.as_concrete_TypeRef(), new_format_desc.as_concrete_TypeRef()) != 0 }
    }

    pub fn wait_for_asynchronous_frames(&self) -> Result<(), OSStatus> {
        let status = unsafe { VTDecompressionSessionWaitForAsynchronousFrames(self.as_concrete_TypeRef()) };
        if status == 0 {
            Ok(())
        } else {
            Err(status)
        }
    }

    pub fn copy_black_pixel_buffer(&self) -> Result<CVPixelBuffer, OSStatus> {
        let mut pixel_buffer_out: CVPixelBufferRef = null_mut();
        let status = unsafe { VTDecompressionSessionCopyBlackPixelBuffer(self.as_concrete_TypeRef(), &mut pixel_buffer_out) };
        if status == 0 {
            Ok(unsafe { CVPixelBuffer::wrap_under_create_rule(pixel_buffer_out) })
        } else {
            Err(status)
        }
    }

    pub fn is_hardware_decode_supported(codec_type: CMVideoCodecType) -> bool {
        unsafe { VTIsHardwareDecodeSupported(codec_type) != 0 }
    }
}