#![allow(unsafe_code)]
use std::ptr::NonNull;
use bytes::Bytes;
use crate::DecodeError;
use mediaway_common::CodecKind;
use objc2_core_foundation::{CFData, CFDictionary, CFRetained, CFString, CFType};
use objc2_core_media::{
CMFormatDescription, CMVideoCodecType, CMVideoFormatDescription,
CMVideoFormatDescriptionCreate, CMVideoFormatDescriptionCreateFromH264ParameterSets,
CMVideoFormatDescriptionCreateFromHEVCParameterSets,
kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms, kCMVideoCodecType_AV1,
kCMVideoCodecType_AppleProRes422, kCMVideoCodecType_AppleProRes422HQ,
kCMVideoCodecType_AppleProRes422LT, kCMVideoCodecType_AppleProRes422Proxy,
kCMVideoCodecType_AppleProRes4444, kCMVideoCodecType_AppleProRes4444XQ, kCMVideoCodecType_VP9,
};
const NO_ERROR: i32 = 0;
#[must_use]
pub(super) const fn raw_codec_type(codec: CodecKind) -> Option<CMVideoCodecType> {
match codec {
CodecKind::Vp9 => Some(kCMVideoCodecType_VP9),
CodecKind::Av1 => Some(kCMVideoCodecType_AV1),
CodecKind::ProRes422Proxy => Some(kCMVideoCodecType_AppleProRes422Proxy),
CodecKind::ProRes422Lt => Some(kCMVideoCodecType_AppleProRes422LT),
CodecKind::ProRes422 => Some(kCMVideoCodecType_AppleProRes422),
CodecKind::ProRes422Hq => Some(kCMVideoCodecType_AppleProRes422HQ),
CodecKind::ProRes4444 => Some(kCMVideoCodecType_AppleProRes4444),
CodecKind::ProRes4444Xq => Some(kCMVideoCodecType_AppleProRes4444XQ),
_ => None,
}
}
fn retained_from_create(
raw: *const CMFormatDescription,
) -> Result<CFRetained<CMFormatDescription>, DecodeError> {
let ptr = NonNull::new(raw.cast_mut()).ok_or(DecodeError::Backend)?;
Ok(unsafe { CFRetained::from_raw(ptr) })
}
pub(super) fn create_h264(
sps: &Bytes,
pps: &Bytes,
) -> Result<CFRetained<CMVideoFormatDescription>, DecodeError> {
if sps.is_empty() || pps.is_empty() {
return Err(DecodeError::InvalidInput);
}
let Some(sps_ptr) = NonNull::new(sps.as_ptr().cast_mut()) else {
return Err(DecodeError::InvalidInput);
};
let Some(pps_ptr) = NonNull::new(pps.as_ptr().cast_mut()) else {
return Err(DecodeError::InvalidInput);
};
let mut pointers = [sps_ptr, pps_ptr];
let mut sizes = [sps.len(), pps.len()];
let Some(pointers_ptr) = NonNull::new(pointers.as_mut_ptr()) else {
return Err(DecodeError::Backend);
};
let Some(sizes_ptr) = NonNull::new(sizes.as_mut_ptr()) else {
return Err(DecodeError::Backend);
};
let mut format_desc_out: *const CMFormatDescription = std::ptr::null();
let status = unsafe {
CMVideoFormatDescriptionCreateFromH264ParameterSets(
None,
2,
pointers_ptr,
sizes_ptr,
4,
NonNull::from(&mut format_desc_out),
)
};
if status != NO_ERROR {
return Err(DecodeError::Backend);
}
retained_from_create(format_desc_out)
}
pub(super) fn create_hevc(
vps: &Bytes,
sps: &Bytes,
pps: &Bytes,
) -> Result<CFRetained<CMVideoFormatDescription>, DecodeError> {
if vps.is_empty() || sps.is_empty() || pps.is_empty() {
return Err(DecodeError::InvalidInput);
}
let Some(vps_ptr) = NonNull::new(vps.as_ptr().cast_mut()) else {
return Err(DecodeError::InvalidInput);
};
let Some(sps_ptr) = NonNull::new(sps.as_ptr().cast_mut()) else {
return Err(DecodeError::InvalidInput);
};
let Some(pps_ptr) = NonNull::new(pps.as_ptr().cast_mut()) else {
return Err(DecodeError::InvalidInput);
};
let mut pointers = [vps_ptr, sps_ptr, pps_ptr];
let mut sizes = [vps.len(), sps.len(), pps.len()];
let Some(pointers_ptr) = NonNull::new(pointers.as_mut_ptr()) else {
return Err(DecodeError::Backend);
};
let Some(sizes_ptr) = NonNull::new(sizes.as_mut_ptr()) else {
return Err(DecodeError::Backend);
};
let mut format_desc_out: *const CMFormatDescription = std::ptr::null();
let status = unsafe {
CMVideoFormatDescriptionCreateFromHEVCParameterSets(
None,
3,
pointers_ptr,
sizes_ptr,
4,
None,
NonNull::from(&mut format_desc_out),
)
};
if status != NO_ERROR {
return Err(DecodeError::Backend);
}
retained_from_create(format_desc_out)
}
pub(super) fn create_raw(
codec_type: CMVideoCodecType,
width: i32,
height: i32,
atom_key: &'static str,
atom_payload: &[u8],
) -> Result<CFRetained<CMVideoFormatDescription>, DecodeError> {
if atom_payload.is_empty() {
return Err(DecodeError::InvalidInput);
}
let atom_data = CFData::from_bytes(atom_payload);
let atom_data_ct: &CFType = &atom_data;
let atom_key_cf = CFString::from_static_str(atom_key);
let atom_key_ref: &CFString = &atom_key_cf;
let inner = CFDictionary::<CFString, CFType>::from_slices(&[atom_key_ref], &[atom_data_ct]);
let inner_ct: &CFType = &inner;
let outer_key = unsafe { kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms };
let outer = CFDictionary::<CFString, CFType>::from_slices(&[outer_key], &[inner_ct]);
let outer: CFRetained<CFDictionary> = unsafe { CFRetained::cast_unchecked(outer) };
let mut format_desc_out: *const CMVideoFormatDescription = std::ptr::null();
let status = unsafe {
CMVideoFormatDescriptionCreate(
None,
codec_type,
width,
height,
Some(&outer),
NonNull::from(&mut format_desc_out),
)
};
if status != NO_ERROR {
return Err(DecodeError::Backend);
}
retained_from_create(format_desc_out)
}
pub(super) fn create_raw_no_extension(
codec_type: CMVideoCodecType,
width: i32,
height: i32,
) -> Result<CFRetained<CMVideoFormatDescription>, DecodeError> {
let mut format_desc_out: *const CMVideoFormatDescription = std::ptr::null();
let status = unsafe {
CMVideoFormatDescriptionCreate(
None,
codec_type,
width,
height,
None,
NonNull::from(&mut format_desc_out),
)
};
if status != NO_ERROR {
return Err(DecodeError::Backend);
}
retained_from_create(format_desc_out)
}