use bitflags::bitflags;
use std::ffi::c_void;
use std::fmt::Debug;
use std::mem::MaybeUninit;
use std::num::NonZero;
use std::time::Duration;
use vpx_sys::vpx_codec_enc_cfg;
use crate::common::StreamInfo;
use crate::image::{YUVImageData, YUVPixelType};
use crate::NoDebugWrapper;
use crate::{call_vpx, call_vpx_ptr};
use crate::{Error, Result};
use ctrl::EncoderControlSet;
pub mod ctrl;
#[allow(missing_docs)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum CodecId {
VP8,
VP9,
}
impl CodecId {
pub(crate) fn iface(&self) -> Result<*const vpx_sys::vpx_codec_iface_t> {
match self {
Self::VP8 => call_vpx_ptr!(
vpx_sys::vpx_codec_vp8_cx(),
Error::CodecInterfaceNotAvailable,
),
Self::VP9 => call_vpx_ptr!(
vpx_sys::vpx_codec_vp9_cx(),
Error::CodecInterfaceNotAvailable,
),
}
}
}
#[derive(Debug)]
pub struct Encoder<T: YUVPixelType> {
ctx: NoDebugWrapper<vpx_sys::vpx_codec_ctx_t>,
input_pixel_type: std::marker::PhantomData<T>,
}
unsafe impl<T: YUVPixelType> Send for Encoder<T> {}
unsafe impl<T: YUVPixelType> Sync for Encoder<T> {}
impl<T: YUVPixelType> Encoder<T> {
pub fn new(config: EncoderConfig<T>) -> Result<Self> {
let (iface, cfg) = unsafe { config.cfg()? };
let mut ctx = unsafe {
MaybeUninit::<vpx_sys::vpx_codec_ctx_t>::zeroed().assume_init()
};
call_vpx!(
vpx_sys::vpx_codec_enc_init_ver(
&mut ctx,
iface,
&cfg,
config.flags.bits() as _,
vpx_sys::VPX_ENCODER_ABI_VERSION as _,
),
Error::VpxCodecInitFailed,
)?;
let mut encoder = Self {
ctx: NoDebugWrapper(ctx),
input_pixel_type: std::marker::PhantomData,
};
match config.rate_control {
RateControl::ConstrainedQuality {
max_quality: qual, ..
}
| RateControl::ConstantQuality(qual) => {
encoder.codec_control_set(EncoderControlSet::CQLevel(qual))?;
}
RateControl::Lossless => match config.codec {
CodecId::VP8 => return Err(Error::UnsupportedRateControlMode),
CodecId::VP9 => {
encoder.codec_control_set(
EncoderControlSet::Vp9CodingMode(
ctrl::Vp9CodingMode::Lossless,
),
)?;
}
},
_ => (),
}
Ok(encoder)
}
pub fn encode(
&mut self,
pts: i64,
duration: u64,
image: YUVImageData<T>,
deadline: EncodingDeadline,
flags: EncoderFrameFlags,
) -> Result<PacketIterator> {
let wrapped = unsafe { image.vpx_img_wrap()? };
call_vpx!(
vpx_sys::vpx_codec_encode(
&mut self.ctx.0,
&wrapped,
pts as _,
duration as _,
flags.bits() as _,
deadline.into(),
),
Error::ErrorEncodingImage,
)?;
Ok(PacketIterator {
ctx: &mut self.ctx.0,
iter: std::ptr::null(),
})
}
pub fn stream_info(&mut self) -> Option<StreamInfo> {
crate::common::get_stream_info(&mut self.ctx.0)
}
pub fn codec_control_set(
&mut self,
control: ctrl::EncoderControlSet,
) -> Result<()> {
control.set(&mut self.ctx.0)
}
}
impl<T: YUVPixelType> Drop for Encoder<T> {
fn drop(&mut self) {
unsafe {
let error = vpx_sys::vpx_codec_destroy(&mut self.ctx.0);
assert_eq!(error, vpx_sys::VPX_CODEC_OK);
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct EncoderConfig<T: YUVPixelType> {
pub codec: CodecId,
pub profile: EncoderProfile,
pub flags: EncoderFlags,
pub width: u32,
pub height: u32,
pub timebase: Timebase,
pub rate_control: RateControl,
pub threads: u32,
pub output_bit_depth: vpx_sys::vpx_bit_depth,
pub error_resilient: ErrorResilient,
pub pass: vpx_sys::vpx_enc_pass,
pub lag_in_frames: u32,
pub rc_dropframe_thresh: u32,
pub rc_resize_allowed: Option<SpatialResizeParams>,
pub rc_twopass_stats_in: Option<FixedBuffer>,
pub rc_firstpass_mb_stats_in: Option<FixedBuffer>,
pub rc_undershoot_pct: u32,
pub rc_overshoot_pct: u32,
pub rc_buf_sz: Duration,
pub rc_buf_initial_sz: Duration,
pub rc_buf_optimal_sz: Duration,
pub rc_2pass_vbr_bias_pct: u32,
pub rc_2pass_vbr_minsection_pct: u32,
pub rc_2pass_vbr_maxsection_pct: u32,
pub rc_2pass_vbr_corpus_complexity: u32,
pub kf_mode: KeyFrameMode,
pub spatial_layers: Vec<SpatialLayer>,
pub temporal_layers: Vec<TemporalLayer>,
pub temporal_periodicity: Vec<u32>,
pub layer_target_bitrate: [u32; vpx_sys::VPX_MAX_LAYERS as usize],
pub temporal_layering_mode: Option<vpx_sys::vp9e_temporal_layering_mode>,
input_pixel_type: std::marker::PhantomData<T>,
}
impl<T: YUVPixelType> EncoderConfig<T> {
pub fn new(
codec: CodecId,
width: u32,
height: u32,
timebase: Timebase,
rate_control: RateControl,
) -> Result<Self> {
let mut cfg = unsafe { MaybeUninit::zeroed().assume_init() };
call_vpx!(
vpx_sys::vpx_codec_enc_config_default(codec.iface()?, &mut cfg, 0),
Error::EncoderConfigInitFailed,
)?;
let temporal_layering_mode = if codec == CodecId::VP9 {
Self::try_from_temporal_layering_mode(cfg.temporal_layering_mode)
} else {
None
};
Ok(Self {
codec,
profile: EncoderProfile::default(),
flags: EncoderFlags::empty(),
width,
height,
timebase,
rate_control,
threads: cfg.g_threads,
output_bit_depth: cfg.g_bit_depth,
error_resilient: ErrorResilient::empty(),
pass: cfg.g_pass,
lag_in_frames: cfg.g_lag_in_frames,
rc_dropframe_thresh: cfg.rc_dropframe_thresh,
rc_resize_allowed: SpatialResizeParams::from_cfg(&cfg),
rc_twopass_stats_in: None,
rc_firstpass_mb_stats_in: None,
rc_undershoot_pct: cfg.rc_undershoot_pct,
rc_overshoot_pct: cfg.rc_overshoot_pct,
rc_buf_sz: Duration::from_millis(cfg.rc_buf_sz as u64),
rc_buf_initial_sz: Duration::from_millis(
cfg.rc_buf_initial_sz as u64,
),
rc_buf_optimal_sz: Duration::from_millis(
cfg.rc_buf_optimal_sz as u64,
),
rc_2pass_vbr_bias_pct: cfg.rc_2pass_vbr_bias_pct,
rc_2pass_vbr_minsection_pct: cfg.rc_2pass_vbr_minsection_pct,
rc_2pass_vbr_maxsection_pct: cfg.rc_2pass_vbr_maxsection_pct,
rc_2pass_vbr_corpus_complexity: cfg.rc_2pass_vbr_corpus_complexity,
kf_mode: KeyFrameMode::from_cfg(&cfg),
spatial_layers: SpatialLayer::from_cfg(&cfg),
temporal_layers: TemporalLayer::from_cfg(&cfg),
temporal_periodicity: Self::ts_periodicity_from_cfg(&cfg),
layer_target_bitrate: cfg.layer_target_bitrate,
temporal_layering_mode,
input_pixel_type: std::marker::PhantomData,
})
}
unsafe fn cfg(
&self,
) -> Result<(
*const vpx_sys::vpx_codec_iface_t,
vpx_sys::vpx_codec_enc_cfg,
)> {
let iface = self.codec.iface()?;
let mut cfg = unsafe { MaybeUninit::zeroed().assume_init() };
call_vpx!(
vpx_sys::vpx_codec_enc_config_default(iface, &mut cfg, 0),
Error::EncoderConfigInitFailed,
)?;
cfg.g_w = self.width;
cfg.g_h = self.height;
cfg.g_timebase.num = self.timebase.num.get() as _;
cfg.g_timebase.den = self.timebase.den.get() as _;
cfg.g_threads = self.threads;
cfg.g_error_resilient = self.error_resilient.bits();
cfg.g_pass = self.pass;
cfg.g_bit_depth = self.output_bit_depth;
cfg.g_input_bit_depth = (size_of::<T>() * 8) as u32;
cfg.g_lag_in_frames = self.lag_in_frames;
if self.profile != EncoderProfile::Default && self.codec == CodecId::VP8
{
return Err(Error::InvalidProfileSelected);
}
cfg.g_profile = self.profile as _;
match self.rate_control {
RateControl::VariableBitRate(bitrate) => {
cfg.rc_end_usage = vpx_sys::vpx_rc_mode::VPX_VBR;
cfg.rc_target_bitrate = bitrate;
}
RateControl::ConstantBitRate(bitrate) => {
cfg.rc_end_usage = vpx_sys::vpx_rc_mode::VPX_CBR;
cfg.rc_target_bitrate = bitrate;
}
RateControl::ConstrainedQuality { bitrate, .. } => {
cfg.rc_end_usage = vpx_sys::vpx_rc_mode::VPX_CQ;
cfg.rc_target_bitrate = bitrate;
}
RateControl::ConstantQuality(_) => {
cfg.rc_end_usage = vpx_sys::vpx_rc_mode::VPX_Q;
}
_ => (),
}
cfg.rc_dropframe_thresh = self.rc_dropframe_thresh;
SpatialResizeParams::to_cfg(&self.rc_resize_allowed, &mut cfg);
if let Some(stats) = &self.rc_twopass_stats_in {
cfg.rc_twopass_stats_in = vpx_sys::vpx_fixed_buf {
buf: stats.0.as_ptr() as *mut c_void,
sz: stats.0.len(),
};
}
if let Some(stats) = &self.rc_firstpass_mb_stats_in {
cfg.rc_firstpass_mb_stats_in = vpx_sys::vpx_fixed_buf {
buf: stats.0.as_ptr() as *mut c_void,
sz: stats.0.len(),
};
}
cfg.rc_undershoot_pct = self.rc_undershoot_pct;
cfg.rc_overshoot_pct = self.rc_overshoot_pct;
cfg.rc_buf_sz = self.rc_buf_sz.as_millis() as u32;
cfg.rc_buf_initial_sz = self.rc_buf_initial_sz.as_millis() as u32;
cfg.rc_buf_optimal_sz = self.rc_buf_optimal_sz.as_millis() as u32;
cfg.rc_2pass_vbr_bias_pct = self.rc_2pass_vbr_bias_pct;
cfg.rc_2pass_vbr_minsection_pct = self.rc_2pass_vbr_minsection_pct;
cfg.rc_2pass_vbr_maxsection_pct = self.rc_2pass_vbr_maxsection_pct;
cfg.rc_2pass_vbr_corpus_complexity =
self.rc_2pass_vbr_corpus_complexity;
self.kf_mode.to_cfg(&mut cfg);
SpatialLayer::to_cfg(&self.spatial_layers, &mut cfg);
TemporalLayer::to_cfg(&self.temporal_layers, &mut cfg);
assert!(
self.temporal_periodicity.len()
< vpx_sys::VPX_TS_MAX_PERIODICITY as usize
);
let n_ts_periodicity = self.temporal_periodicity.len();
cfg.ts_periodicity = n_ts_periodicity as u32;
cfg.ts_layer_id[..n_ts_periodicity]
.copy_from_slice(&self.temporal_periodicity[..n_ts_periodicity]);
cfg.layer_target_bitrate = self.layer_target_bitrate;
if let Some(x) = self.temporal_layering_mode {
cfg.temporal_layering_mode = x as i32;
}
Ok((iface, cfg))
}
fn ts_periodicity_from_cfg(cfg: &vpx_sys::vpx_codec_enc_cfg) -> Vec<u32> {
let mut periodicity = vec![];
for i in 0..cfg.ts_periodicity as usize {
periodicity.push(cfg.ts_layer_id[i]);
}
periodicity
}
fn try_from_temporal_layering_mode(
value: i32,
) -> Option<vpx_sys::vp9e_temporal_layering_mode> {
use vpx_sys::vp9e_temporal_layering_mode::*;
match value {
x if x == VP9E_TEMPORAL_LAYERING_MODE_NOLAYERING as i32 => {
Some(VP9E_TEMPORAL_LAYERING_MODE_NOLAYERING)
}
x if x == VP9E_TEMPORAL_LAYERING_MODE_BYPASS as i32 => {
Some(VP9E_TEMPORAL_LAYERING_MODE_BYPASS)
}
x if x == VP9E_TEMPORAL_LAYERING_MODE_0101 as i32 => {
Some(VP9E_TEMPORAL_LAYERING_MODE_0101)
}
x if x == VP9E_TEMPORAL_LAYERING_MODE_0212 as i32 => {
Some(VP9E_TEMPORAL_LAYERING_MODE_0212)
}
_ => None,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SpatialResizeParams {
pub width: u32,
pub height: u32,
pub resize_up_thresh: u32,
pub resize_down_thresh: u32,
}
impl SpatialResizeParams {
pub fn default_for_codec(codec: CodecId) -> Result<Self> {
let iface = codec.iface()?;
let mut cfg = unsafe { MaybeUninit::zeroed().assume_init() };
call_vpx!(
vpx_sys::vpx_codec_enc_config_default(iface, &mut cfg, 0),
Error::EncoderConfigInitFailed,
)?;
Ok(Self {
width: cfg.rc_scaled_width,
height: cfg.rc_scaled_height,
resize_up_thresh: cfg.rc_resize_up_thresh,
resize_down_thresh: cfg.rc_resize_down_thresh,
})
}
fn from_cfg(cfg: &vpx_codec_enc_cfg) -> Option<Self> {
(cfg.rc_resize_allowed != 0).then_some(Self {
width: cfg.rc_scaled_width,
height: cfg.rc_scaled_height,
resize_up_thresh: cfg.rc_resize_up_thresh,
resize_down_thresh: cfg.rc_resize_down_thresh,
})
}
fn to_cfg(
params: &Option<SpatialResizeParams>,
cfg: &mut vpx_codec_enc_cfg,
) {
if let Some(params) = params {
cfg.rc_resize_allowed = 1;
cfg.rc_scaled_width = params.width;
cfg.rc_scaled_height = params.height;
cfg.rc_resize_up_thresh = params.resize_up_thresh;
cfg.rc_resize_down_thresh = params.resize_down_thresh;
} else {
cfg.rc_resize_allowed = 0;
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum RateControl {
ConstantBitRate(u32),
VariableBitRate(u32),
ConstrainedQuality {
bitrate: u32,
max_quality: u32,
},
ConstantQuality(u32),
Lossless,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Timebase {
pub num: NonZero<u32>,
pub den: NonZero<u32>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum EncodingDeadline {
Realtime,
#[default]
GoodQuality,
BestQuality,
Custom(Duration),
}
impl From<EncodingDeadline> for std::ffi::c_ulong {
fn from(value: EncodingDeadline) -> Self {
match value {
EncodingDeadline::Realtime => vpx_sys::VPX_DL_REALTIME as _,
EncodingDeadline::GoodQuality => vpx_sys::VPX_DL_GOOD_QUALITY as _,
EncodingDeadline::BestQuality => vpx_sys::VPX_DL_BEST_QUALITY as _,
EncodingDeadline::Custom(dl) => dl.as_micros() as _,
}
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u32)]
pub enum EncoderProfile {
#[default]
Default = 0,
Vp9Profile1 = 1,
Vp9Profile2 = 2,
Vp9Profile3 = 3,
}
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ErrorResilient: u32 {
const DEFAULT = vpx_sys::VPX_ERROR_RESILIENT_DEFAULT;
const RESILIENT_PARTITIONS = vpx_sys::VPX_ERROR_RESILIENT_PARTITIONS;
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct EncoderFlags: u32 {
const PSNR = vpx_sys::VPX_CODEC_USE_PSNR;
const OUTPUT_PARTITION = vpx_sys::VPX_CODEC_USE_OUTPUT_PARTITION;
const HIGH_BIT_DEPTH = vpx_sys::VPX_CODEC_USE_HIGHBITDEPTH;
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct EncoderFrameFlags: u32 {
const FORCE_KF = vpx_sys::VPX_EFLAG_FORCE_KF;
const CALCULATE_PSNR = 1 << 1;
const NO_REF_LAST = vpx_sys::VP8_EFLAG_NO_REF_LAST;
const NO_REF_GF = vpx_sys::VP8_EFLAG_NO_REF_GF;
const NO_REF_ARF = vpx_sys::VP8_EFLAG_NO_REF_ARF;
const NO_UDP_LAST = vpx_sys::VP8_EFLAG_NO_UPD_LAST;
const NO_UDP_GF = vpx_sys::VP8_EFLAG_NO_UPD_GF;
const NO_UDP_ARF = vpx_sys::VP8_EFLAG_NO_UPD_ARF;
const FORCE_GF = vpx_sys::VP8_EFLAG_FORCE_GF;
const FORCE_ARF = vpx_sys::VP8_EFLAG_FORCE_ARF;
const NO_UDP_ENTROPY = vpx_sys::VP8_EFLAG_NO_UPD_ENTROPY;
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum KeyFrameMode {
Auto {
min_dist: u32,
max_dist: u32,
},
Disabled,
}
impl KeyFrameMode {
fn from_cfg(cfg: &vpx_sys::vpx_codec_enc_cfg) -> Self {
match cfg.kf_mode {
vpx_sys::vpx_kf_mode::VPX_KF_AUTO => Self::Auto {
min_dist: cfg.kf_min_dist,
max_dist: cfg.kf_max_dist,
},
vpx_sys::vpx_kf_mode::VPX_KF_DISABLED => Self::Disabled,
}
}
fn to_cfg(self, cfg: &mut vpx_sys::vpx_codec_enc_cfg) {
match self {
KeyFrameMode::Auto { min_dist, max_dist } => {
cfg.kf_mode = vpx_sys::vpx_kf_mode::VPX_KF_AUTO;
cfg.kf_min_dist = min_dist;
cfg.kf_max_dist = max_dist;
}
KeyFrameMode::Disabled => {
cfg.kf_mode = vpx_sys::vpx_kf_mode::VPX_KF_DISABLED;
}
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SpatialLayer {
pub enable_auto_alt_ref: bool,
pub target_bitrate: u32,
}
impl SpatialLayer {
fn from_cfg(cfg: &vpx_sys::vpx_codec_enc_cfg) -> Vec<Self> {
let mut layers = vec![];
for i in 0..cfg.ss_number_layers as usize {
layers.push(SpatialLayer {
enable_auto_alt_ref: cfg.ss_enable_auto_alt_ref[i] != 0,
target_bitrate: cfg.ss_target_bitrate[i],
});
}
layers
}
fn to_cfg(layers: &[Self], cfg: &mut vpx_sys::vpx_codec_enc_cfg) {
assert!(layers.len() < vpx_sys::VPX_SS_MAX_LAYERS as usize);
cfg.ss_number_layers = layers.len() as u32;
layers.iter().enumerate().for_each(|(i, layer)| {
cfg.ss_enable_auto_alt_ref[i] = layer.enable_auto_alt_ref as i32;
cfg.ss_target_bitrate[i] = layer.target_bitrate;
});
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TemporalLayer {
target_bitrate: u32,
rate_decimator: u32,
}
impl TemporalLayer {
fn from_cfg(cfg: &vpx_sys::vpx_codec_enc_cfg) -> Vec<Self> {
let mut layers = vec![];
for i in 0..cfg.ts_number_layers as usize {
layers.push(Self {
target_bitrate: cfg.ts_target_bitrate[i],
rate_decimator: cfg.ts_rate_decimator[i],
})
}
layers
}
fn to_cfg(layers: &[Self], cfg: &mut vpx_sys::vpx_codec_enc_cfg) {
assert!(layers.len() < vpx_sys::VPX_TS_MAX_LAYERS as usize);
cfg.ts_number_layers = layers.len() as u32;
layers.iter().enumerate().for_each(|(i, layer)| {
cfg.ts_target_bitrate[i] = layer.target_bitrate;
cfg.ts_rate_decimator[i] = layer.rate_decimator;
});
}
}
pub struct PacketIterator<'a> {
ctx: &'a mut vpx_sys::vpx_codec_ctx_t,
iter: vpx_sys::vpx_codec_iter_t,
}
unsafe impl Send for PacketIterator<'_> {}
impl Iterator for PacketIterator<'_> {
type Item = Packet;
fn next(&mut self) -> Option<Self::Item> {
loop {
let pkt = unsafe {
vpx_sys::vpx_codec_get_cx_data(self.ctx, &mut self.iter)
.as_ref()?
};
if let Some(pkt) = Packet::from_vpx_packet(pkt) {
return Some(pkt);
}
}
}
}
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub enum Packet {
CompressedFrame(CompressedFrame),
TwoPassStats(FixedBuffer),
FirstPassMbStats(FixedBuffer),
PSNRPacket(PSNRPacket),
Custom(FixedBuffer),
}
impl Packet {
fn from_vpx_packet(pkt: &vpx_sys::vpx_codec_cx_pkt) -> Option<Self> {
match pkt.kind {
vpx_sys::vpx_codec_cx_pkt_kind::VPX_CODEC_CX_FRAME_PKT => {
let frame = unsafe { &pkt.data.frame };
if frame.buf.is_null() {
return None;
}
Some(Self::CompressedFrame(CompressedFrame {
data: unsafe {
std::slice::from_raw_parts(frame.buf as _, frame.sz)
}
.to_vec(),
pts: frame.pts,
duration: frame.duration as _,
flags: CompressedFrameFlags::from(frame.flags),
partition_id: frame.partition_id,
width: frame.width,
height: frame.height,
spatial_layer_encoded: frame.spatial_layer_encoded,
}))
}
vpx_sys::vpx_codec_cx_pkt_kind::VPX_CODEC_STATS_PKT => {
Some(Self::TwoPassStats(FixedBuffer::from_vpx(unsafe {
&pkt.data.twopass_stats
})?))
}
vpx_sys::vpx_codec_cx_pkt_kind::VPX_CODEC_FPMB_STATS_PKT => {
Some(Self::FirstPassMbStats(FixedBuffer::from_vpx(unsafe {
&pkt.data.firstpass_mb_stats
})?))
}
vpx_sys::vpx_codec_cx_pkt_kind::VPX_CODEC_PSNR_PKT => {
let psnr = unsafe { &pkt.data.psnr };
Some(Self::PSNRPacket(PSNRPacket {
samples: PSNRSamples {
total: psnr.samples[0],
y: psnr.samples[1],
u: psnr.samples[2],
v: psnr.samples[3],
},
sse: PSNRSumSquaredError {
total: psnr.sse[0],
y: psnr.sse[1],
u: psnr.sse[2],
v: psnr.sse[3],
},
psnr: PSNR {
total: psnr.psnr[0],
y: psnr.psnr[1],
u: psnr.psnr[2],
v: psnr.psnr[3],
},
}))
}
_ => Some(Self::Custom(FixedBuffer::from_vpx(unsafe {
&pkt.data.raw
})?)),
}
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct CompressedFrame {
pub data: Vec<u8>,
pub pts: i64,
pub duration: u64,
pub flags: CompressedFrameFlags,
pub partition_id: i32,
pub width: [u32; vpx_sys::VPX_SS_MAX_LAYERS as usize],
pub height: [u32; vpx_sys::VPX_SS_MAX_LAYERS as usize],
pub spatial_layer_encoded: [u8; vpx_sys::VPX_SS_MAX_LAYERS as usize],
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct CompressedFrameFlags {
pub is_key: bool,
pub is_droppable: bool,
pub is_invisible: bool,
pub is_fragment: bool,
}
impl From<vpx_sys::vpx_codec_frame_flags_t> for CompressedFrameFlags {
fn from(value: vpx_sys::vpx_codec_frame_flags_t) -> Self {
Self {
is_key: (value & vpx_sys::VPX_FRAME_IS_KEY) != 0,
is_droppable: (value & vpx_sys::VPX_FRAME_IS_DROPPABLE) != 0,
is_invisible: (value & vpx_sys::VPX_FRAME_IS_INVISIBLE) != 0,
is_fragment: (value & vpx_sys::VPX_FRAME_IS_FRAGMENT) != 0,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct FixedBuffer(Vec<u8>);
impl FixedBuffer {
pub unsafe fn inner<T: Sized>(&self) -> Option<&T> {
if self.0.len() != size_of::<T>() {
return None;
}
unsafe { (self.0.as_ptr() as *const T).as_ref() }
}
fn from_vpx(value: &vpx_sys::vpx_fixed_buf_t) -> Option<Self> {
let buf = unsafe {
std::slice::from_raw_parts(
std::ptr::NonNull::new(value.buf)?.as_ptr() as *const u8,
value.sz,
)
.to_vec()
};
Some(Self(buf))
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct PSNRPacket {
pub samples: PSNRSamples,
pub sse: PSNRSumSquaredError,
pub psnr: PSNR,
}
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct PSNRSamples {
pub total: u32,
pub y: u32,
pub u: u32,
pub v: u32,
}
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct PSNRSumSquaredError {
pub total: u64,
pub y: u64,
pub u: u64,
pub v: u64,
}
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct PSNR {
pub total: f64,
pub y: f64,
pub u: f64,
pub v: f64,
}
#[cfg(test)]
mod test {
use super::{
CodecId, CompressedFrame, CompressedFrameFlags, Encoder, EncoderConfig,
EncoderFlags, EncoderFrameFlags, ErrorResilient, FixedBuffer,
KeyFrameMode, PSNRPacket, PSNRSamples, PSNRSumSquaredError,
PacketIterator, RateControl, SpatialLayer, SpatialResizeParams,
TemporalLayer, Timebase, PSNR,
};
#[test]
fn test_send() {
fn assert_send<T: Send>() {}
assert_send::<CodecId>();
assert_send::<CompressedFrame>();
assert_send::<CompressedFrameFlags>();
assert_send::<Encoder<u8>>();
assert_send::<EncoderConfig<u8>>();
assert_send::<EncoderFlags>();
assert_send::<EncoderFrameFlags>();
assert_send::<ErrorResilient>();
assert_send::<FixedBuffer>();
assert_send::<KeyFrameMode>();
assert_send::<PacketIterator>();
assert_send::<PSNR>();
assert_send::<PSNRPacket>();
assert_send::<PSNRSamples>();
assert_send::<PSNRSumSquaredError>();
assert_send::<RateControl>();
assert_send::<SpatialLayer>();
assert_send::<SpatialResizeParams>();
assert_send::<TemporalLayer>();
assert_send::<Timebase>();
}
#[test]
fn test_sync() {
fn assert_sync<T: Sync>() {}
assert_sync::<CodecId>();
assert_sync::<CompressedFrame>();
assert_sync::<CompressedFrameFlags>();
assert_sync::<Encoder<u8>>();
assert_sync::<EncoderConfig<u8>>();
assert_sync::<EncoderFlags>();
assert_sync::<EncoderFrameFlags>();
assert_sync::<ErrorResilient>();
assert_sync::<FixedBuffer>();
assert_sync::<KeyFrameMode>();
assert_sync::<PSNR>();
assert_sync::<PSNRPacket>();
assert_sync::<PSNRSamples>();
assert_sync::<PSNRSumSquaredError>();
assert_sync::<RateControl>();
assert_sync::<SpatialLayer>();
assert_sync::<SpatialResizeParams>();
assert_sync::<TemporalLayer>();
assert_sync::<Timebase>();
}
}