#![allow(clippy::missing_transmute_annotations)]
use core_foundation::base::TCFType;
use core_foundation::boolean::CFBoolean;
use core_foundation::dictionary::CFDictionary;
use core_foundation::number::CFNumber;
use core_foundation::string::CFString;
use core_foundation_sys::base::{kCFAllocatorDefault, CFTypeRef, OSStatus};
use core_foundation_sys::dictionary::CFDictionaryRef;
use core_foundation_sys::string::CFStringRef;
use super::cv_ffi::kCVPixelBufferPixelFormatTypeKey;
use libc::c_void;
use std::ptr;
use crate::codecs;
use crate::compression::{
kVTCompressionPropertyKey_AverageBitRate, kVTCompressionPropertyKey_ExpectedFrameRate,
kVTCompressionPropertyKey_MaxKeyFrameInterval, kVTCompressionPropertyKey_ProfileLevel,
kVTCompressionPropertyKey_RealTime,
kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder,
kVTVideoEncoderSpecification_EnableLowLatencyRateControl,
VTCompressionSessionCreate, VTCompressionSessionInvalidate,
VTCompressionSessionPrepareToEncodeFrames, VTCompressionSessionRef,
};
use crate::session::VTSessionSetProperty;
#[derive(Clone)]
pub struct CompressionSessionConfig {
pub width: i32,
pub height: i32,
pub codec: u32,
pub pixel_format: u32,
pub hardware_accelerated: bool,
pub low_latency: bool,
pub real_time: bool,
pub bitrate: Option<i64>,
pub frame_rate: Option<f64>,
pub keyframe_interval: Option<i32>,
pub profile_level: Option<CFStringRef>,
}
impl CompressionSessionConfig {
pub fn new(width: i32, height: i32, codec: u32) -> Self {
Self {
width,
height,
codec,
pixel_format: codecs::pixel::BGRA32,
hardware_accelerated: true,
low_latency: false,
real_time: true,
bitrate: None,
frame_rate: None,
keyframe_interval: None,
profile_level: None,
}
}
}
pub struct CompressionSessionBuilder {
config: CompressionSessionConfig,
}
impl CompressionSessionBuilder {
pub fn new(width: i32, height: i32, codec: u32) -> Self {
Self {
config: CompressionSessionConfig::new(width, height, codec),
}
}
pub fn from_config(config: CompressionSessionConfig) -> Self {
Self { config }
}
pub fn pixel_format(mut self, format: u32) -> Self {
self.config.pixel_format = format;
self
}
pub fn hardware_accelerated(mut self, enabled: bool) -> Self {
self.config.hardware_accelerated = enabled;
self
}
pub fn low_latency(mut self, enabled: bool) -> Self {
self.config.low_latency = enabled;
self
}
pub fn real_time(mut self, enabled: bool) -> Self {
self.config.real_time = enabled;
self
}
pub fn bitrate(mut self, bps: i64) -> Self {
self.config.bitrate = Some(bps);
self
}
pub fn frame_rate(mut self, fps: f64) -> Self {
self.config.frame_rate = Some(fps);
self
}
pub fn keyframe_interval(mut self, frames: i32) -> Self {
self.config.keyframe_interval = Some(frames);
self
}
pub fn profile_level(mut self, level: CFStringRef) -> Self {
self.config.profile_level = Some(level);
self
}
pub fn build<F>(self, callback: F) -> Result<VTCompressionSessionRef, OSStatus>
where
F: Fn(*mut c_void, *mut c_void, OSStatus, u32, *mut c_void) + 'static,
{
let callback_box = Box::new(callback);
let callback_ptr = Box::into_raw(callback_box);
unsafe {
self.build_with_context(
Some(trampoline::<F>),
callback_ptr as *mut c_void,
)
}
}
pub unsafe fn build_with_context(
self,
callback: Option<
extern "C" fn(*mut c_void, *mut c_void, OSStatus, u32, *mut c_void),
>,
context: *mut c_void,
) -> Result<VTCompressionSessionRef, OSStatus> {
self.create_session(callback, context)
}
unsafe fn create_session(
self,
callback: Option<
extern "C" fn(*mut c_void, *mut c_void, OSStatus, u32, *mut c_void),
>,
context: *mut c_void,
) -> Result<VTCompressionSessionRef, OSStatus> {
let config = &self.config;
let mut encoder_spec_pairs = Vec::new();
let hw_key = CFString::wrap_under_get_rule(
kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder as CFStringRef,
);
let hw_value = if config.hardware_accelerated {
CFBoolean::true_value()
} else {
CFBoolean::false_value()
};
encoder_spec_pairs.push((hw_key.as_CFType(), hw_value.as_CFType()));
if config.low_latency {
let ll_key = CFString::wrap_under_get_rule(
kVTVideoEncoderSpecification_EnableLowLatencyRateControl as CFStringRef,
);
encoder_spec_pairs.push((ll_key.as_CFType(), CFBoolean::true_value().as_CFType()));
}
let encoder_spec = CFDictionary::from_CFType_pairs(&encoder_spec_pairs);
let format_key = CFString::wrap_under_get_rule(kCVPixelBufferPixelFormatTypeKey);
let width_key = CFString::from_static_string("Width");
let height_key = CFString::from_static_string("Height");
let source_attrs = CFDictionary::from_CFType_pairs(&[
(
format_key.as_CFType(),
CFNumber::from(config.pixel_format as i32).as_CFType(),
),
(
width_key.as_CFType(),
CFNumber::from(config.width).as_CFType(),
),
(
height_key.as_CFType(),
CFNumber::from(config.height).as_CFType(),
),
]);
let mut session: VTCompressionSessionRef = ptr::null_mut();
let status = VTCompressionSessionCreate(
kCFAllocatorDefault,
config.width,
config.height,
config.codec,
encoder_spec.as_concrete_TypeRef() as CFDictionaryRef,
source_attrs.as_concrete_TypeRef() as CFDictionaryRef,
kCFAllocatorDefault,
std::mem::transmute(callback),
context,
&mut session,
);
if status != 0 {
return Err(status);
}
if let Some(profile) = config.profile_level {
let key = CFString::wrap_under_get_rule(
kVTCompressionPropertyKey_ProfileLevel as CFStringRef,
);
let value = CFString::wrap_under_get_rule(profile);
VTSessionSetProperty(
session,
key.as_concrete_TypeRef(),
value.as_concrete_TypeRef() as CFTypeRef,
);
}
if let Some(bitrate) = config.bitrate {
let key = CFString::wrap_under_get_rule(
kVTCompressionPropertyKey_AverageBitRate as CFStringRef,
);
let value = CFNumber::from(bitrate);
VTSessionSetProperty(
session,
key.as_concrete_TypeRef(),
value.as_concrete_TypeRef() as CFTypeRef,
);
}
if let Some(fps) = config.frame_rate {
let key = CFString::wrap_under_get_rule(
kVTCompressionPropertyKey_ExpectedFrameRate as CFStringRef,
);
let value = CFNumber::from(fps);
VTSessionSetProperty(
session,
key.as_concrete_TypeRef(),
value.as_concrete_TypeRef() as CFTypeRef,
);
}
if let Some(interval) = config.keyframe_interval {
let key = CFString::wrap_under_get_rule(
kVTCompressionPropertyKey_MaxKeyFrameInterval as CFStringRef,
);
let value = CFNumber::from(interval);
VTSessionSetProperty(
session,
key.as_concrete_TypeRef(),
value.as_concrete_TypeRef() as CFTypeRef,
);
}
if config.real_time {
let key =
CFString::wrap_under_get_rule(kVTCompressionPropertyKey_RealTime as CFStringRef);
VTSessionSetProperty(
session,
key.as_concrete_TypeRef(),
CFBoolean::true_value().as_concrete_TypeRef() as CFTypeRef,
);
}
let prep_status = VTCompressionSessionPrepareToEncodeFrames(session);
if prep_status != 0 {
VTCompressionSessionInvalidate(session);
return Err(prep_status);
}
Ok(session)
}
}
extern "C" fn trampoline<F>(
output_ref: *mut c_void,
source_ref: *mut c_void,
status: OSStatus,
info_flags: u32,
sample_buffer: *mut c_void,
) where
F: Fn(*mut c_void, *mut c_void, OSStatus, u32, *mut c_void),
{
unsafe {
let callback = &*(output_ref as *const F);
callback(output_ref, source_ref, status, info_flags, sample_buffer);
}
}