use core::ffi::c_void;
use core::ptr;
use apple_cf::cf::{CFDictionary, CFType};
use crate::error::VTError;
use crate::ffi;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Codec {
H264,
HEVC,
ProRes422,
ProRes422HQ,
ProRes422LT,
ProRes422Proxy,
ProRes4444,
}
impl Codec {
#[must_use]
pub const fn as_cm_codec_type(self) -> ffi::CMVideoCodecType {
match self {
Self::H264 => ffi::kCMVideoCodecType_H264,
Self::HEVC => ffi::kCMVideoCodecType_HEVC,
Self::ProRes422 => ffi::kCMVideoCodecType_AppleProRes422,
Self::ProRes422HQ => ffi::kCMVideoCodecType_AppleProRes422HQ,
Self::ProRes422LT => ffi::kCMVideoCodecType_AppleProRes422LT,
Self::ProRes422Proxy => ffi::kCMVideoCodecType_AppleProRes422Proxy,
Self::ProRes4444 => ffi::kCMVideoCodecType_AppleProRes4444,
}
}
}
pub(crate) unsafe fn copy_property(
session: *mut c_void,
key: ffi::CFStringRef,
) -> Result<Option<CFType>, VTError> {
let mut value: *mut c_void = ptr::null_mut();
let status = unsafe {
ffi::VTSessionCopyProperty(
session,
key,
ffi::kCFAllocatorDefault,
(&raw mut value).cast(),
)
};
if status != 0 {
return Err(VTError::ApiFailed {
api: "VTSessionCopyProperty",
status,
});
}
Ok(unsafe { CFType::from_raw(value) })
}
pub(crate) unsafe fn copy_supported_property_dictionary(
session: *mut c_void,
) -> Result<CFDictionary, VTError> {
let mut out: ffi::CFDictionaryRef = ptr::null();
let status = unsafe { ffi::VTSessionCopySupportedPropertyDictionary(session, &raw mut out) };
if status != 0 || out.is_null() {
return Err(VTError::ApiFailed {
api: "VTSessionCopySupportedPropertyDictionary",
status,
});
}
unsafe { CFDictionary::from_raw(out.cast_mut().cast()) }.ok_or(VTError::ApiFailed {
api: "VTSessionCopySupportedPropertyDictionary",
status,
})
}
pub(crate) unsafe fn copy_serializable_properties(
session: *mut c_void,
) -> Result<CFDictionary, VTError> {
let mut out: ffi::CFDictionaryRef = ptr::null();
let status = unsafe {
ffi::VTSessionCopySerializableProperties(session, ffi::kCFAllocatorDefault, &raw mut out)
};
if status != 0 || out.is_null() {
return Err(VTError::ApiFailed {
api: "VTSessionCopySerializableProperties",
status,
});
}
unsafe { CFDictionary::from_raw(out.cast_mut().cast()) }.ok_or(VTError::ApiFailed {
api: "VTSessionCopySerializableProperties",
status,
})
}
pub(crate) unsafe fn set_properties(
session: *mut c_void,
properties: &CFDictionary,
) -> Result<(), VTError> {
let status = unsafe { ffi::VTSessionSetProperties(session, properties.as_ptr().cast()) };
if status == 0 {
Ok(())
} else {
Err(VTError::ApiFailed {
api: "VTSessionSetProperties",
status,
})
}
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use super::Codec;
use crate::ffi;
#[test]
fn h264_and_hevc_codecs_match_expected_fourcc_values() {
assert_eq!(Codec::H264.as_cm_codec_type(), u32::from_be_bytes(*b"avc1"));
assert_eq!(Codec::H264.as_cm_codec_type(), ffi::kCMVideoCodecType_H264);
assert_eq!(Codec::HEVC.as_cm_codec_type(), u32::from_be_bytes(*b"hvc1"));
assert_eq!(Codec::HEVC.as_cm_codec_type(), ffi::kCMVideoCodecType_HEVC);
}
#[test]
fn prores_family_codecs_match_expected_fourcc_values() {
assert_eq!(
Codec::ProRes422.as_cm_codec_type(),
u32::from_be_bytes(*b"apcn")
);
assert_eq!(
Codec::ProRes422HQ.as_cm_codec_type(),
u32::from_be_bytes(*b"apch")
);
assert_eq!(
Codec::ProRes422LT.as_cm_codec_type(),
u32::from_be_bytes(*b"apcs")
);
assert_eq!(
Codec::ProRes422Proxy.as_cm_codec_type(),
u32::from_be_bytes(*b"apco")
);
assert_eq!(
Codec::ProRes4444.as_cm_codec_type(),
u32::from_be_bytes(*b"ap4h")
);
}
#[test]
fn codec_mappings_are_unique_across_variants() {
let codecs = [
Codec::H264,
Codec::HEVC,
Codec::ProRes422,
Codec::ProRes422HQ,
Codec::ProRes422LT,
Codec::ProRes422Proxy,
Codec::ProRes4444,
];
let mut seen = HashSet::new();
for codec in codecs {
assert!(seen.insert(codec.as_cm_codec_type()));
}
assert_eq!(seen.len(), 7);
}
}