use std::{ffi::c_void, ptr, ptr::NonNull};
use objc2_core_foundation::{CFBoolean, CFDictionary, CFNumber, CFRetained, CFType};
use objc2_core_media::{
kCMBlockBufferAssureMemoryNowFlag, CMBlockBuffer, CMFormatDescription, CMSampleBuffer,
CMSampleTimingInfo, CMTime, CMTimeFlags,
};
use objc2_core_video::{
kCVPixelBufferIOSurfacePropertiesKey, kCVPixelBufferMetalCompatibilityKey,
kCVPixelBufferPixelFormatTypeKey, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
};
use objc2_video_toolbox::{
kVTVideoDecoderSpecification_EnableHardwareAcceleratedVideoDecoder,
kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder,
};
use crate::{VideoError, VideoTimestamp};
pub(crate) fn decoder_image_attributes() -> CFRetained<CFDictionary<CFType, CFType>> {
let empty_surface_properties = CFDictionary::<CFType, CFType>::empty();
let pixel_format = CFNumber::new_i32(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange as i32);
let metal_key = unsafe { kCVPixelBufferMetalCompatibilityKey };
let io_surface_key = unsafe { kCVPixelBufferIOSurfacePropertiesKey };
let pixel_format_key = unsafe { kCVPixelBufferPixelFormatTypeKey };
CFDictionary::from_slices(
&[
metal_key.as_ref(),
io_surface_key.as_ref(),
pixel_format_key.as_ref(),
],
&[
CFBoolean::new(true).as_ref(),
(*empty_surface_properties).as_ref(),
pixel_format.as_ref(),
],
)
}
pub(crate) fn decoder_specification(
require_hardware: bool,
) -> CFRetained<CFDictionary<CFType, CFType>> {
let key = if require_hardware {
unsafe { kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder }
} else {
unsafe { kVTVideoDecoderSpecification_EnableHardwareAcceleratedVideoDecoder }
};
CFDictionary::from_slices(&[key.as_ref()], &[CFBoolean::new(true).as_ref()])
}
pub(crate) fn h264_format_description<const N: usize>(
parameter_sets: [&[u8]; N],
) -> Result<CFRetained<CMFormatDescription>, VideoError> {
let mut pointers = parameter_sets.map(|parameter_set| {
NonNull::new(parameter_set.as_ptr().cast_mut())
.expect("slice pointers are non-null, including empty slices")
});
let sizes = parameter_sets.map(<[u8]>::len);
let mut output: *const CMFormatDescription = ptr::null();
let status = unsafe {
objc2_core_media::CMVideoFormatDescriptionCreateFromH264ParameterSets(
None,
N,
NonNull::new(pointers.as_mut_ptr()).expect("arrays have a non-null base pointer"),
NonNull::new(sizes.as_ptr().cast_mut()).expect("arrays have a non-null base pointer"),
4,
NonNull::from(&mut output),
)
};
format_description_result(
"CMVideoFormatDescriptionCreateFromH264ParameterSets",
status,
output,
)
}
pub(crate) fn h265_format_description<const N: usize>(
parameter_sets: [&[u8]; N],
) -> Result<CFRetained<CMFormatDescription>, VideoError> {
let mut pointers = parameter_sets.map(|parameter_set| {
NonNull::new(parameter_set.as_ptr().cast_mut())
.expect("slice pointers are non-null, including empty slices")
});
let sizes = parameter_sets.map(<[u8]>::len);
let mut output: *const CMFormatDescription = ptr::null();
let status = unsafe {
objc2_core_media::CMVideoFormatDescriptionCreateFromHEVCParameterSets(
None,
N,
NonNull::new(pointers.as_mut_ptr()).expect("arrays have a non-null base pointer"),
NonNull::new(sizes.as_ptr().cast_mut()).expect("arrays have a non-null base pointer"),
4,
None,
NonNull::from(&mut output),
)
};
format_description_result(
"CMVideoFormatDescriptionCreateFromHEVCParameterSets",
status,
output,
)
}
pub(crate) fn sample_buffer(
data: &[u8],
format: &CMFormatDescription,
timestamp: VideoTimestamp,
) -> Result<CFRetained<CMSampleBuffer>, VideoError> {
let mut block_ptr = ptr::null_mut();
let status = unsafe {
CMBlockBuffer::create_with_memory_block(
None,
ptr::null_mut(),
data.len(),
None,
ptr::null(),
0,
data.len(),
kCMBlockBufferAssureMemoryNowFlag,
NonNull::from(&mut block_ptr),
)
};
let block_ptr = platform_pointer("CMBlockBufferCreateWithMemoryBlock", status, block_ptr)?;
let block = unsafe { CFRetained::from_raw(block_ptr) };
let source = NonNull::new(data.as_ptr().cast_mut().cast::<c_void>()).ok_or(
VideoError::InvalidAnnexB("access unit contains no NAL data"),
)?;
let status = unsafe { CMBlockBuffer::replace_data_bytes(source, &block, 0, data.len()) };
platform_status("CMBlockBufferReplaceDataBytes", status)?;
let timing = CMSampleTimingInfo {
duration: invalid_time(),
presentationTimeStamp: valid_time(timestamp),
decodeTimeStamp: invalid_time(),
};
let sample_size = data.len();
let mut output = ptr::null_mut();
let status = unsafe {
CMSampleBuffer::create(
None,
Some(&block),
true,
None,
ptr::null_mut(),
Some(format),
1,
1,
&timing,
1,
&sample_size,
NonNull::from(&mut output),
)
};
let output = platform_pointer("CMSampleBufferCreate", status, output)?;
Ok(unsafe { CFRetained::from_raw(output) })
}
fn format_description_result(
api: &'static str,
status: i32,
output: *const CMFormatDescription,
) -> Result<CFRetained<CMFormatDescription>, VideoError> {
let output = platform_pointer(api, status, output.cast_mut())?;
Ok(unsafe { CFRetained::from_raw(output) })
}
fn platform_status(api: &'static str, status: i32) -> Result<(), VideoError> {
if status == 0 {
Ok(())
} else {
Err(VideoError::Platform { api, status })
}
}
fn platform_pointer<T>(
api: &'static str,
status: i32,
output: *mut T,
) -> Result<NonNull<T>, VideoError> {
platform_status(api, status)?;
NonNull::new(output).ok_or(VideoError::Platform { api, status })
}
fn valid_time(timestamp: VideoTimestamp) -> CMTime {
CMTime {
value: timestamp.value,
timescale: timestamp.timescale,
flags: CMTimeFlags::Valid,
epoch: 0,
}
}
fn invalid_time() -> CMTime {
CMTime {
value: 0,
timescale: 0,
flags: CMTimeFlags::empty(),
epoch: 0,
}
}