use anyhow::{Context, Result, bail};
use bytes::Bytes;
use std::collections::VecDeque;
use std::ffi::c_void;
use std::os::raw::c_char;
use std::ptr;
use super::tuning::{self, QsvRateControl};
use super::{AUTO_FROM_TARGET, EncodedPacket, Encoder, EncoderConfig};
#[cfg(test)]
use crate::frame::ColorMetadata;
use crate::frame::{PixelFormat, TransferFn, VideoFrame};
use crate::qsv_ffi::{
MfxBitstream, MfxExtBuffer, MfxFrameData, MfxFrameInfo, MfxFrameSurface1, MfxInfoMfx,
MfxVideoParam,
};
type MfxStatus = i32;
const MFX_ERR_NONE: MfxStatus = 0;
const MFX_ERR_MORE_DATA: MfxStatus = -10;
#[allow(dead_code)]
const MFX_ERR_MORE_SURFACE: MfxStatus = -11;
const MFX_WRN_IN_EXECUTION: MfxStatus = 1;
const MFX_WRN_INCOMPATIBLE_VIDEO_PARAM: MfxStatus = 5;
const MFX_WRN_VIDEO_PARAM_CHANGED: MfxStatus = 3;
const MFX_WRN_PARTIAL_ACCELERATION: MfxStatus = 4;
const MFX_CODEC_AV1: u32 = 0x20315641; const MFX_FOURCC_NV12: u32 = 0x3231564e; const MFX_FOURCC_P010: u32 = 0x30313050;
const MFX_RATECONTROL_CQP: u16 = 3;
const MFX_RATECONTROL_ICQ: u16 = 9;
const MFX_PROFILE_AV1_MAIN: u16 = 1;
const MFX_CHROMAFORMAT_YUV420: u16 = 1;
const MFX_IOPATTERN_IN_SYSTEM_MEMORY: u16 = 0x02;
const MFX_PICSTRUCT_PROGRESSIVE: u16 = 1;
const MFX_FRAMETYPE_I: u16 = 0x0001;
const MFX_FRAMETYPE_IDR: u16 = 0x8000;
const MFX_EXTBUFF_AV1_TILE_PARAM: u32 = 0x4c543141; #[allow(dead_code)]
const MFX_EXTBUFF_AV1_BITSTREAM_PARAM: u32 = 0x42315641; const MFX_EXTBUFF_CODING_OPTION3: u32 = 0x334f4443; const MFX_EXTBUFF_VIDEO_SIGNAL_INFO: u32 = 0x4e495356;
const MFX_TARGET_CHROMAFORMAT_YUV420_PLUS1: u16 = 2;
const RING_SIZE: usize = 4;
#[repr(C)]
struct MfxExtAv1TileParam {
header: MfxExtBuffer,
num_tile_rows: u16,
num_tile_columns: u16,
num_tile_groups: u16,
reserved: [u16; 5],
}
#[repr(C)]
struct MfxExtCodingOption3 {
header: MfxExtBuffer, _pad_to_158: [u8; 150], target_chroma_format_plus1: u16, target_bit_depth_luma: u16, target_bit_depth_chroma: u16, _tail: [u8; 348], }
#[repr(C)]
struct MfxExtVideoSignalInfo {
header: MfxExtBuffer,
video_format: u16,
video_full_range: u16,
colour_description_present: u16,
colour_primaries: u16,
transfer_characteristics: u16,
matrix_coefficients: u16,
}
#[repr(C)]
#[allow(dead_code)]
struct MfxEncodeCtrl {
header: MfxExtBuffer,
reserved: [u32; 4],
mfx_pic_struct: u16,
mfx_skip_frame: u16,
qp: u16,
frame_type: u16,
num_ext_param: u16,
_pad: u16,
num_payload: u16,
_pad2: u16,
ext_param: *mut *mut MfxExtBuffer,
payload: *mut c_void,
}
type MfxSession = *mut c_void;
type MfxSyncPoint = *mut c_void;
type FnMfxClose = unsafe extern "C" fn(MfxSession) -> MfxStatus;
type MfxLoader = *mut c_void;
type MfxConfig = *mut c_void;
type FnMfxLoad = unsafe extern "C" fn() -> MfxLoader;
type FnMfxUnload = unsafe extern "C" fn(MfxLoader);
type FnMfxCreateConfig = unsafe extern "C" fn(MfxLoader) -> MfxConfig;
type FnMfxSetConfigFilterProperty =
unsafe extern "C" fn(MfxConfig, *const u8, MfxVariant) -> MfxStatus;
type FnMfxCreateSession = unsafe extern "C" fn(MfxLoader, u32, *mut MfxSession) -> MfxStatus;
#[repr(C)]
#[derive(Clone, Copy)]
struct MfxVariant {
version: u16,
_pad: u16,
ty: u32,
data: u64, }
const _: () = assert!(std::mem::size_of::<MfxVariant>() == 16);
const MFX_VARIANT_TYPE_U32: u32 = 5;
const MFX_IMPL_TYPE_HARDWARE: u32 = 2;
type FnEncodeQuery =
unsafe extern "C" fn(MfxSession, *mut MfxVideoParam, *mut MfxVideoParam) -> MfxStatus;
type FnEncodeInit = unsafe extern "C" fn(MfxSession, *mut MfxVideoParam) -> MfxStatus;
type FnEncodeClose = unsafe extern "C" fn(MfxSession) -> MfxStatus;
type FnEncodeFrameAsync = unsafe extern "C" fn(
MfxSession,
*mut c_void,
*mut MfxFrameSurface1,
*mut MfxBitstream,
*mut MfxSyncPoint,
) -> MfxStatus;
type FnSyncOperation = unsafe extern "C" fn(MfxSession, MfxSyncPoint, u32) -> MfxStatus;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct RateSlots {
slot0_qpi_or_delay: u16,
slot1_qpp_or_kbps_or_icq: u16,
slot2_qpb_or_maxkbps: u16,
}
fn rate_slots_for_rc(mode: QsvRateControl, qp_i: u16, qp_p: u16, icq_quality: u16) -> RateSlots {
match mode {
QsvRateControl::Cqp => RateSlots {
slot0_qpi_or_delay: qp_i,
slot1_qpp_or_kbps_or_icq: qp_p,
slot2_qpb_or_maxkbps: qp_p, },
QsvRateControl::Icq => RateSlots {
slot0_qpi_or_delay: 0,
slot1_qpp_or_kbps_or_icq: icq_quality,
slot2_qpb_or_maxkbps: 0,
},
}
}
fn qsv_fourcc_for(fmt: PixelFormat) -> Result<u32> {
match fmt {
PixelFormat::Yuv420p => Ok(MFX_FOURCC_NV12),
PixelFormat::Yuv420p10le => Ok(MFX_FOURCC_P010),
other => bail!(
"QSV AV1 expects Yuv420p or Yuv420p10le, got {other:?}"
),
}
}
const fn qsv_bit_depth_triple(fmt: PixelFormat) -> (u16, u16, u16) {
match fmt {
PixelFormat::Yuv420p10le => (10, 10, 1),
_ => (8, 8, 0),
}
}
fn transfer_to_h273(tf: TransferFn) -> u16 {
match tf {
TransferFn::Bt709 => 1,
TransferFn::Bt470Bg => 4,
TransferFn::Linear => 8,
TransferFn::St2084 => 16,
TransferFn::AribStdB67 => 18,
TransferFn::Unspecified => 1,
}
}
fn clamp_target_usage(tp_target_usage: u16) -> u16 {
tp_target_usage.clamp(1, 7)
}
struct SurfaceSlot {
surface: MfxFrameSurface1,
_backing: Box<[u8]>,
sync: MfxSyncPoint,
}
unsafe impl Send for SurfaceSlot {}
struct QsvSession {
session: MfxSession,
width: u32,
height: u32,
pts_timescale: u64,
input_pixel_format: PixelFormat,
fn_mfx_close: FnMfxClose,
fn_encode_close: FnEncodeClose,
fn_encode_frame_async: FnEncodeFrameAsync,
fn_sync_operation: FnSyncOperation,
loader: MfxLoader,
fn_unload: FnMfxUnload,
#[allow(dead_code)]
tile_ext: Box<MfxExtAv1TileParam>,
#[allow(dead_code)]
coding_option3_ext: Option<Box<MfxExtCodingOption3>>,
#[allow(dead_code)]
signal_info_ext: Box<MfxExtVideoSignalInfo>,
#[allow(dead_code)]
ext_param_array: Vec<*mut MfxExtBuffer>,
surfaces: [SurfaceSlot; RING_SIZE],
ring_idx: usize,
inflight: VecDeque<usize>,
input_pitch: u32,
height_aligned: u32,
bitstream: MfxBitstream,
_bitstream_buf: Box<[u8]>,
}
unsafe impl Send for QsvSession {}
impl Drop for QsvSession {
fn drop(&mut self) {
unsafe {
if !self.session.is_null() {
let _ = (self.fn_encode_close)(self.session);
let _ = (self.fn_mfx_close)(self.session);
}
if !self.loader.is_null() {
(self.fn_unload)(self.loader);
}
}
}
}
pub struct QsvEncoder {
#[allow(dead_code)]
config: EncoderConfig,
session: Option<QsvSession>,
encoded_packets: Vec<EncodedPacket>,
packet_cursor: usize,
flushed: bool,
frame_counter: u32,
_runtime_lib: libloading::Library,
}
impl QsvEncoder {
pub fn new(config: EncoderConfig, gpu_index: u32) -> Result<Self> {
let runtime_lib = unsafe { libloading::Library::new("libvpl.so.2") }
.or_else(|_| unsafe { libloading::Library::new("libvpl.so") })
.or_else(|_| unsafe { libloading::Library::new("libvpl.dll") })
.or_else(|_| unsafe { libloading::Library::new("libmfx.so.1") })
.or_else(|_| unsafe { libloading::Library::new("libmfxhw64.dll") })
.context("loading oneVPL runtime library (Intel GPU driver not present?)")?;
unsafe {
let fn_load: libloading::Symbol<FnMfxLoad> =
runtime_lib.get(b"MFXLoad").context("MFXLoad symbol")?;
let fn_create_config: libloading::Symbol<FnMfxCreateConfig> = runtime_lib
.get(b"MFXCreateConfig")
.context("MFXCreateConfig symbol")?;
let fn_set_filter: libloading::Symbol<FnMfxSetConfigFilterProperty> = runtime_lib
.get(b"MFXSetConfigFilterProperty")
.context("MFXSetConfigFilterProperty symbol")?;
let fn_create_session: libloading::Symbol<FnMfxCreateSession> = runtime_lib
.get(b"MFXCreateSession")
.context("MFXCreateSession symbol")?;
let fn_unload: libloading::Symbol<FnMfxUnload> =
runtime_lib.get(b"MFXUnload").context("MFXUnload symbol")?;
let mfx_close: libloading::Symbol<FnMfxClose> =
runtime_lib.get(b"MFXClose").context("MFXClose symbol")?;
let fn_encode_query: libloading::Symbol<FnEncodeQuery> = runtime_lib
.get(b"MFXVideoENCODE_Query")
.context("MFXVideoENCODE_Query")?;
let fn_encode_init: libloading::Symbol<FnEncodeInit> = runtime_lib
.get(b"MFXVideoENCODE_Init")
.context("MFXVideoENCODE_Init")?;
let fn_encode_close: libloading::Symbol<FnEncodeClose> = runtime_lib
.get(b"MFXVideoENCODE_Close")
.context("MFXVideoENCODE_Close")?;
let fn_encode_frame_async: libloading::Symbol<FnEncodeFrameAsync> = runtime_lib
.get(b"MFXVideoENCODE_EncodeFrameAsync")
.context("MFXVideoENCODE_EncodeFrameAsync")?;
let fn_sync_operation: libloading::Symbol<FnSyncOperation> = runtime_lib
.get(b"MFXVideoCORE_SyncOperation")
.context("MFXVideoCORE_SyncOperation")?;
if gpu_index != 0 {
tracing::warn!(
gpu_index,
"QSV dispatcher picks the first HW implementation; \
iGPU+dGPU hosts need ONEVPL_PRIORITY_PATH"
);
}
let loader = fn_load();
if loader.is_null() {
bail!("MFXLoad returned a null loader (oneVPL dispatcher unavailable)");
}
let cfg = fn_create_config(loader);
if cfg.is_null() {
fn_unload(loader);
bail!("MFXCreateConfig returned null");
}
let impl_var = MfxVariant {
version: 0,
_pad: 0,
ty: MFX_VARIANT_TYPE_U32,
data: MFX_IMPL_TYPE_HARDWARE as u64,
};
let rc = fn_set_filter(cfg, b"mfxImplDescription.Impl\0".as_ptr(), impl_var);
if rc < 0 {
fn_unload(loader);
bail!("MFXSetConfigFilterProperty(Impl=HARDWARE) failed: {rc}");
}
let mut session: MfxSession = ptr::null_mut();
let rc = fn_create_session(loader, 0, &mut session);
if rc < 0 || session.is_null() {
fn_unload(loader);
bail!("MFXCreateSession failed: {rc} (no AV1-capable Intel HW implementation?)");
}
let tp =
tuning::qsv_av1_params(config.target, config.tier, config.width, config.height);
let input_fourcc = qsv_fourcc_for(config.pixel_format)?;
let (bit_depth_luma, bit_depth_chroma, shift) =
qsv_bit_depth_triple(config.pixel_format);
let mut tile_ext = Box::new(MfxExtAv1TileParam {
header: MfxExtBuffer {
buffer_id: MFX_EXTBUFF_AV1_TILE_PARAM,
buffer_sz: std::mem::size_of::<MfxExtAv1TileParam>() as u32,
},
num_tile_rows: tp.num_tile_rows as u16,
num_tile_columns: tp.num_tile_columns as u16,
num_tile_groups: 1,
reserved: [0u16; 5],
});
let mut coding_option3_ext: Option<Box<MfxExtCodingOption3>> =
if config.pixel_format == PixelFormat::Yuv420p10le {
Some(Box::new(MfxExtCodingOption3 {
header: MfxExtBuffer {
buffer_id: MFX_EXTBUFF_CODING_OPTION3,
buffer_sz: std::mem::size_of::<MfxExtCodingOption3>() as u32,
},
_pad_to_158: [0; 150],
target_chroma_format_plus1: MFX_TARGET_CHROMAFORMAT_YUV420_PLUS1,
target_bit_depth_luma: 10,
target_bit_depth_chroma: 10,
_tail: [0; 348],
}))
} else {
None
};
let cm = &config.color_metadata;
let mut signal_info_ext = Box::new(MfxExtVideoSignalInfo {
header: MfxExtBuffer {
buffer_id: MFX_EXTBUFF_VIDEO_SIGNAL_INFO,
buffer_sz: std::mem::size_of::<MfxExtVideoSignalInfo>() as u32,
},
video_format: 5, video_full_range: if cm.full_range { 1 } else { 0 },
colour_description_present: 1,
colour_primaries: cm.colour_primaries as u16,
transfer_characteristics: transfer_to_h273(cm.transfer),
matrix_coefficients: cm.matrix_coefficients as u16,
});
let mut ext_param_array: Vec<*mut MfxExtBuffer> = Vec::with_capacity(3);
ext_param_array.push(
(&mut *tile_ext as *mut MfxExtAv1TileParam) as *mut MfxExtBuffer,
);
ext_param_array.push(
(&mut *signal_info_ext as *mut MfxExtVideoSignalInfo) as *mut MfxExtBuffer,
);
if let Some(ref mut co3) = coding_option3_ext {
ext_param_array.push(
(&mut **co3 as *mut MfxExtCodingOption3) as *mut MfxExtBuffer,
);
}
let num_ext_param = ext_param_array.len() as u16;
let force_cqp = config.constant_qp || tp.rc_mode == QsvRateControl::Cqp;
let (rc_mode_u16, qp_i_effective, qp_p_effective, icq_effective) = if force_cqp {
let qp_i = if config.quality == AUTO_FROM_TARGET {
tp.qp_i
} else {
(config.quality as u16 * 4).min(255)
};
(MFX_RATECONTROL_CQP, qp_i, tp.qp_p, 0u16)
} else {
(MFX_RATECONTROL_ICQ, 0u16, 0u16, tp.icq_quality)
};
let slots = rate_slots_for_rc(
tp.rc_mode,
qp_i_effective,
qp_p_effective,
icq_effective,
);
let frame_info = MfxFrameInfo {
reserved: [0; 4],
channel_id: 0,
bit_depth_luma,
bit_depth_chroma,
shift,
frame_id: [0; 4],
fourcc: input_fourcc,
width: align_up(config.width as u16, 16),
height: align_up(config.height as u16, 16),
crop_x: 0,
crop_y: 0,
crop_w: config.width as u16,
crop_h: config.height as u16,
frame_rate_ext_n: (config.frame_rate * 1000.0).round() as u32,
frame_rate_ext_d: 1000,
reserved3: 0,
aspect_ratio_w: 1,
aspect_ratio_h: 1,
pic_struct: MFX_PICSTRUCT_PROGRESSIVE,
chroma_format: MFX_CHROMAFORMAT_YUV420,
reserved2: 0,
};
let mfx = MfxInfoMfx {
reserved: [0; 7],
low_power: tp.low_power,
brc_param_multiplier: 0,
frame_info,
codec_id: MFX_CODEC_AV1,
codec_profile: MFX_PROFILE_AV1_MAIN,
codec_level: 0, num_thread: 0,
target_usage: clamp_target_usage(tp.target_usage),
gop_pic_size: config.keyframe_interval as u16,
gop_ref_dist: 1, gop_opt_flag: 0,
idr_interval: 0,
rate_control_method: rc_mode_u16,
qpi_or_delay: slots.slot0_qpi_or_delay,
buffer_size_kb: 0,
qpp_or_kbps_or_icq: slots.slot1_qpp_or_kbps_or_icq,
qpb_or_maxkbps: slots.slot2_qpb_or_maxkbps,
num_slice: 0,
num_ref_frame: 1,
encoded_order: 0,
};
let mut par = MfxVideoParam {
alloc_id: 0,
reserved: [0; 2],
reserved3: 0,
async_depth: RING_SIZE as u16,
mfx,
_mfx_union_pad: [0; 32],
protected: 0,
io_pattern: MFX_IOPATTERN_IN_SYSTEM_MEMORY,
ext_param: ext_param_array.as_ptr() as *mut *mut MfxExtBuffer,
num_ext_param,
reserved2: 0,
};
let mut out = zeroed_video_param();
let rc = (*fn_encode_query)(session, &mut par, &mut out);
let rewrote = match rc {
MFX_ERR_NONE => false,
MFX_WRN_INCOMPATIBLE_VIDEO_PARAM | MFX_WRN_VIDEO_PARAM_CHANGED => {
tracing::warn!(
status = rc,
req_rc_method = par.mfx.rate_control_method,
got_rc_method = out.mfx.rate_control_method,
req_target_usage = par.mfx.target_usage,
got_target_usage = out.mfx.target_usage,
req_qpi_or_delay = par.mfx.qpi_or_delay,
got_qpi_or_delay = out.mfx.qpi_or_delay,
req_qpp_or_kbps_or_icq = par.mfx.qpp_or_kbps_or_icq,
got_qpp_or_kbps_or_icq = out.mfx.qpp_or_kbps_or_icq,
req_profile = par.mfx.codec_profile,
got_profile = out.mfx.codec_profile,
req_width = par.mfx.frame_info.width,
got_width = out.mfx.frame_info.width,
req_height = par.mfx.frame_info.height,
got_height = out.mfx.frame_info.height,
"QSV Query rewrote encoder parameters"
);
true
}
MFX_WRN_PARTIAL_ACCELERATION => {
tracing::warn!(
"QSV runtime reports partial acceleration — \
some encoder stages may fall back to CPU"
);
false
}
err => {
tracing::warn!(
status = err,
codec = par.mfx.codec_id,
rate_control = par.mfx.rate_control_method,
low_power = par.mfx.low_power,
"MFXVideoENCODE_Query returned an error; proceeding to Init \
(Query is advisory on this runtime)"
);
false
}
};
if rewrote {
if out.mfx.frame_info.width != 0 {
par.mfx.frame_info.width = out.mfx.frame_info.width;
}
if out.mfx.frame_info.height != 0 {
par.mfx.frame_info.height = out.mfx.frame_info.height;
}
if out.mfx.frame_info.fourcc != 0 {
par.mfx.frame_info.fourcc = out.mfx.frame_info.fourcc;
}
if out.mfx.frame_info.chroma_format != 0 {
par.mfx.frame_info.chroma_format = out.mfx.frame_info.chroma_format;
}
if out.mfx.rate_control_method != 0 {
par.mfx.rate_control_method = out.mfx.rate_control_method;
}
if out.mfx.target_usage != 0 {
par.mfx.target_usage = out.mfx.target_usage;
}
if out.mfx.codec_profile != 0 {
par.mfx.codec_profile = out.mfx.codec_profile;
}
if out.mfx.codec_level != 0 {
par.mfx.codec_level = out.mfx.codec_level;
}
}
par.ext_param = ext_param_array.as_ptr() as *mut *mut MfxExtBuffer;
par.num_ext_param = num_ext_param;
let rc = (*fn_encode_init)(session, &mut par);
if rc < 0 {
let _ = mfx_close(session);
bail!(
"MFXVideoENCODE_Init failed: {rc} (likely the AV1 encode component \
is not available — Arc / Meteor Lake + required)"
);
} else if rc > 0 {
tracing::warn!(
status = rc,
"MFXVideoENCODE_Init returned a warning; encoder will run with \
adjusted parameters"
);
}
tracing::info!(
width = config.width,
height = config.height,
target = ?config.target,
tier = ?config.tier,
rc_mode = ?tp.rc_mode,
icq_quality = tp.icq_quality,
qp_i = tp.qp_i,
target_usage = tp.target_usage,
tile_cols = tp.num_tile_columns,
tile_rows = tp.num_tile_rows,
"QSV AV1 tuning applied"
);
let bytes_per_sample: u32 = if shift == 1 { 2 } else { 1 };
let pitch = align_up(config.width * bytes_per_sample, 64u32); let h_aligned = align_up(config.height, 16u32);
let surface_bytes = (pitch as usize * h_aligned as usize * 3) / 2;
let mut surfaces_vec: Vec<SurfaceSlot> = Vec::with_capacity(RING_SIZE);
let y_plane_bytes = pitch as usize * h_aligned as usize;
for _ in 0..RING_SIZE {
let (y_fill, c_fill): (u8, u8) = (16, 128);
let mut backing: Box<[u8]> = if config.pixel_format == PixelFormat::Yuv420p10le {
let mut v = vec![0u8; surface_bytes].into_boxed_slice();
let (yb, cb) = ((16u16 << 6).to_le_bytes(), (128u16 << 6).to_le_bytes());
for i in (0..y_plane_bytes).step_by(2) {
v[i] = yb[0];
v[i + 1] = yb[1];
}
for i in (y_plane_bytes..surface_bytes).step_by(2) {
v[i] = cb[0];
v[i + 1] = cb[1];
}
v
} else {
let mut v = vec![c_fill; surface_bytes].into_boxed_slice();
v[..y_plane_bytes].fill(y_fill);
v
};
let y_ptr = backing.as_mut_ptr();
let uv_ptr = y_ptr.add(pitch as usize * h_aligned as usize);
let surface = MfxFrameSurface1 {
reserved: [0; 4],
info: frame_info,
data: MfxFrameData {
ext_param_or_reserved2: 0,
num_ext_param: 0,
reserved: [0; 9],
mem_type: 0,
pitch_high: (pitch >> 16) as u16,
time_stamp: 0,
frame_order: 0,
locked: 0,
pitch: (pitch & 0xFFFF) as u16,
y: y_ptr,
u: uv_ptr,
v: uv_ptr.add(1),
a: ptr::null_mut(),
mem_id: ptr::null_mut(),
corrupted: 0,
data_flag: 0,
},
};
surfaces_vec.push(SurfaceSlot {
surface,
_backing: backing,
sync: ptr::null_mut(),
});
}
let surfaces: [SurfaceSlot; RING_SIZE] = surfaces_vec
.try_into()
.map_err(|_| anyhow::anyhow!("RING_SIZE mismatch during surface allocation"))?;
let bitstream_capacity = surface_bytes.max(2 * 1024 * 1024);
let mut bitstream_buf: Box<[u8]> = vec![0u8; bitstream_capacity].into_boxed_slice();
let bitstream = MfxBitstream {
reserved: [0; 6],
decode_time_stamp: 0,
time_stamp: 0,
data: bitstream_buf.as_mut_ptr(),
data_offset: 0,
data_length: 0,
max_length: bitstream_buf.len() as u32,
pic_struct: MFX_PICSTRUCT_PROGRESSIVE,
frame_type: 0,
data_flag: 0,
reserved2: 0,
};
let sess = QsvSession {
session,
width: config.width,
height: config.height,
pts_timescale: (10_000_000.0f64 / config.frame_rate).round() as u64,
input_pixel_format: config.pixel_format,
fn_mfx_close: *mfx_close,
fn_encode_close: *fn_encode_close,
fn_encode_frame_async: *fn_encode_frame_async,
fn_sync_operation: *fn_sync_operation,
loader,
fn_unload: *fn_unload,
tile_ext,
coding_option3_ext,
signal_info_ext,
ext_param_array,
surfaces,
ring_idx: 0,
inflight: VecDeque::with_capacity(RING_SIZE),
input_pitch: pitch,
height_aligned: h_aligned,
bitstream,
_bitstream_buf: bitstream_buf,
};
tracing::info!(
width = config.width,
height = config.height,
gpu = gpu_index,
ring_size = RING_SIZE,
"QSV AV1 encoder ready"
);
let _ = (MFX_EXTBUFF_AV1_BITSTREAM_PARAM, 0 as c_char);
Ok(Self {
config,
session: Some(sess),
encoded_packets: Vec::new(),
packet_cursor: 0,
flushed: false,
frame_counter: 0,
_runtime_lib: runtime_lib,
})
}
}
fn encode_one(&mut self, frame: &VideoFrame) -> Result<()> {
let session = self
.session
.as_mut()
.ok_or_else(|| anyhow::anyhow!("encode_one called after session drop"))?;
if frame.format != session.input_pixel_format {
bail!(
"QSV session was initialized with {:?} but frame is {:?} \
— pipeline must reinit the encoder if pixel format changes",
session.input_pixel_format,
frame.format
);
}
let w = session.width as usize;
let h = session.height as usize;
let cw = w.div_ceil(2);
let ch = h.div_ceil(2);
let bytes_per_sample: usize = if session.input_pixel_format
== PixelFormat::Yuv420p10le
{
2
} else {
1
};
let y_size_bytes = w * h * bytes_per_sample;
let uv_size_bytes = cw * ch * bytes_per_sample;
if frame.data.len() < y_size_bytes + 2 * uv_size_bytes {
bail!(
"frame data too small for {}x{} {:?}: need {} bytes, got {}",
w,
h,
session.input_pixel_format,
y_size_bytes + 2 * uv_size_bytes,
frame.data.len()
);
}
let pitch = session.input_pitch as usize;
let h_aligned = session.height_aligned as usize;
let slot_idx = session.ring_idx;
if !session.surfaces[slot_idx].sync.is_null() {
let oldest = session
.inflight
.pop_front()
.ok_or_else(|| anyhow::anyhow!("ring full but inflight queue empty"))?;
let sync = session.surfaces[oldest].sync;
session.surfaces[oldest].sync = ptr::null_mut();
unsafe {
sync_and_drain(session, sync, &mut self.encoded_packets)?;
}
}
let slot = &mut session.surfaces[slot_idx];
unsafe {
let y_dst = slot.surface.data.y;
let uv_dst = y_dst.add(pitch * h_aligned);
if session.input_pixel_format == PixelFormat::Yuv420p10le {
let src_ptr = frame.data.as_ptr();
for row in 0..h {
let src_row = src_ptr.add(row * w * 2) as *const u16;
let dst_row = y_dst.add(row * pitch) as *mut u16;
for col in 0..w {
let sample = (*src_row.add(col)) & 0x03FF;
*dst_row.add(col) = sample << 6;
}
}
let u_src_base = src_ptr.add(y_size_bytes);
let v_src_base = u_src_base.add(uv_size_bytes);
for row in 0..ch {
let u_src = u_src_base.add(row * cw * 2) as *const u16;
let v_src = v_src_base.add(row * cw * 2) as *const u16;
let dst_row = uv_dst.add(row * pitch) as *mut u16;
for col in 0..cw {
let u = (*u_src.add(col)) & 0x03FF;
let v = (*v_src.add(col)) & 0x03FF;
*dst_row.add(col * 2) = u << 6;
*dst_row.add(col * 2 + 1) = v << 6;
}
}
} else {
for row in 0..h {
let src = frame.data.as_ptr().add(row * w);
let dst = y_dst.add(row * pitch);
ptr::copy_nonoverlapping(src, dst, w);
}
let u_src_base = frame.data.as_ptr().add(y_size_bytes);
let v_src_base = u_src_base.add(uv_size_bytes);
for row in 0..ch {
let u_src = u_src_base.add(row * cw);
let v_src = v_src_base.add(row * cw);
let dst_row = uv_dst.add(row * pitch);
for col in 0..cw {
*dst_row.add(col * 2) = *u_src.add(col);
*dst_row.add(col * 2 + 1) = *v_src.add(col);
}
}
}
}
slot.surface.data.time_stamp = frame.pts * session.pts_timescale;
slot.surface.data.frame_order = self.frame_counter;
let packets = &mut self.encoded_packets;
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
let mut sync: MfxSyncPoint = ptr::null_mut();
let rc = (session.fn_encode_frame_async)(
session.session,
ptr::null_mut(),
&mut session.surfaces[slot_idx].surface as *mut MfxFrameSurface1,
&mut session.bitstream as *mut MfxBitstream,
&mut sync,
);
match rc {
MFX_ERR_NONE => {
session.surfaces[slot_idx].sync = sync;
session.inflight.push_back(slot_idx);
}
MFX_ERR_MORE_DATA => {
}
MFX_WRN_IN_EXECUTION => {
std::thread::yield_now();
if !sync.is_null() {
sync_and_drain(session, sync, packets)?;
}
}
err => {
tracing::error!(
status = err,
w,
h,
pitch,
h_aligned,
"MFXVideoENCODE_EncodeFrameAsync failed"
);
bail!("MFXVideoENCODE_EncodeFrameAsync failed: {err}");
}
}
Ok::<(), anyhow::Error>(())
}));
session.ring_idx = (session.ring_idx + 1) % RING_SIZE;
self.frame_counter += 1;
match result {
Ok(inner) => inner,
Err(_) => bail!("panic in QSV encode path — aborting rather than unwinding across FFI"),
}
}
fn flush_drain(&mut self) -> Result<()> {
if self.session.is_none() {
return Ok(());
}
let packets_ref = &mut self.encoded_packets;
let session_ref = self.session.as_mut().expect("checked Some above");
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
while let Some(slot_idx) = session_ref.inflight.pop_front() {
let sync = session_ref.surfaces[slot_idx].sync;
session_ref.surfaces[slot_idx].sync = ptr::null_mut();
if !sync.is_null() {
sync_and_drain(session_ref, sync, packets_ref)?;
}
}
loop {
let mut sync: MfxSyncPoint = ptr::null_mut();
let rc = (session_ref.fn_encode_frame_async)(
session_ref.session,
ptr::null_mut(),
ptr::null_mut(),
&mut session_ref.bitstream as *mut MfxBitstream,
&mut sync,
);
match rc {
MFX_ERR_NONE => {
if !sync.is_null() {
sync_and_drain(session_ref, sync, packets_ref)?;
}
}
MFX_ERR_MORE_DATA => return Ok::<(), anyhow::Error>(()),
err if err > 0 => {
if !sync.is_null() {
sync_and_drain(session_ref, sync, packets_ref)?;
}
}
err => bail!("MFXVideoENCODE_EncodeFrameAsync(flush) failed: {err}"),
}
}
}));
match result {
Ok(inner) => inner,
Err(_panic) => bail!(
"panic in QSV flush path — aborting rather than unwinding across FFI"
),
}
}
}
unsafe fn sync_and_drain(
session: &mut QsvSession,
sync: MfxSyncPoint,
packets: &mut Vec<EncodedPacket>,
) -> Result<()> {
unsafe {
let rc = (session.fn_sync_operation)(session.session, sync, 60_000);
if rc != MFX_ERR_NONE {
bail!("MFXVideoCORE_SyncOperation failed: {rc}");
}
let len = session.bitstream.data_length as usize;
if len == 0 {
return Ok(());
}
let offset = session.bitstream.data_offset as usize;
let slice = std::slice::from_raw_parts(
session.bitstream.data.add(offset),
len,
);
let data_bytes = Bytes::copy_from_slice(slice);
let is_keyframe =
(session.bitstream.frame_type & (MFX_FRAMETYPE_I | MFX_FRAMETYPE_IDR)) != 0;
let pts = session.bitstream.time_stamp;
packets.push(EncodedPacket {
data: data_bytes,
pts,
is_keyframe,
});
session.bitstream.data_length = 0;
session.bitstream.data_offset = 0;
Ok(())
}
}
fn zeroed_video_param() -> MfxVideoParam {
MfxVideoParam {
alloc_id: 0,
reserved: [0; 2],
reserved3: 0,
async_depth: 0,
mfx: MfxInfoMfx {
reserved: [0; 7],
low_power: 0,
brc_param_multiplier: 0,
frame_info: MfxFrameInfo {
reserved: [0; 4],
channel_id: 0,
bit_depth_luma: 0,
bit_depth_chroma: 0,
shift: 0,
frame_id: [0; 4],
fourcc: 0,
width: 0,
height: 0,
crop_x: 0,
crop_y: 0,
crop_w: 0,
crop_h: 0,
frame_rate_ext_n: 0,
frame_rate_ext_d: 0,
reserved3: 0,
aspect_ratio_w: 0,
aspect_ratio_h: 0,
pic_struct: 0,
chroma_format: 0,
reserved2: 0,
},
codec_id: 0,
codec_profile: 0,
codec_level: 0,
num_thread: 0,
target_usage: 0,
gop_pic_size: 0,
gop_ref_dist: 0,
gop_opt_flag: 0,
idr_interval: 0,
rate_control_method: 0,
qpi_or_delay: 0,
buffer_size_kb: 0,
qpp_or_kbps_or_icq: 0,
qpb_or_maxkbps: 0,
num_slice: 0,
num_ref_frame: 0,
encoded_order: 0,
},
_mfx_union_pad: [0; 32],
protected: 0,
io_pattern: 0,
ext_param: ptr::null_mut(),
num_ext_param: 0,
reserved2: 0,
}
}
impl Encoder for QsvEncoder {
fn send_frame(&mut self, frame: &VideoFrame) -> Result<()> {
self.encode_one(frame)
}
fn flush(&mut self) -> Result<()> {
if !self.flushed {
self.flush_drain()?;
self.flushed = true;
}
Ok(())
}
fn receive_packet(&mut self) -> Result<Option<EncodedPacket>> {
if self.packet_cursor < self.encoded_packets.len() {
let pkt = self.encoded_packets[self.packet_cursor].clone();
self.packet_cursor += 1;
Ok(Some(pkt))
} else {
Ok(None)
}
}
}
fn align_up<T>(v: T, a: T) -> T
where
T: Copy
+ std::ops::Add<Output = T>
+ std::ops::Sub<Output = T>
+ std::ops::BitAnd<Output = T>
+ std::ops::Not<Output = T>
+ From<u8>,
{
let one = T::from(1u8);
(v + a - one) & !(a - one)
}
const _: () = assert!(std::mem::size_of::<MfxExtAv1TileParam>() == 24);
const _: () = assert!(std::mem::size_of::<MfxEncodeCtrl>() == 56);
const _: () = assert!(std::mem::size_of::<MfxExtCodingOption3>() == 512);
const _: () = assert!(std::mem::size_of::<MfxExtVideoSignalInfo>() >= 20);
const _: () = assert!({
let (l, c, s) = qsv_bit_depth_triple(PixelFormat::Yuv420p10le);
l == 10 && c == 10 && s == 1
});
const _: () = assert!({
let (l, c, s) = qsv_bit_depth_triple(PixelFormat::Yuv420p);
l == 8 && c == 8 && s == 0
});
const _: () = assert!(MFX_FOURCC_P010 == 0x30313050);
const _: () = assert!(MFX_FOURCC_NV12 == 0x3231564e);
const _: () = assert!(MFX_TARGET_CHROMAFORMAT_YUV420_PLUS1 == 2);
const _: () = assert!(MFX_EXTBUFF_CODING_OPTION3 == 0x334f4443);
const _: () = assert!(MFX_EXTBUFF_VIDEO_SIGNAL_INFO == 0x4e495356);
#[cfg(test)]
mod tests {
use super::*;
use crate::encode::tuning::{QualityTarget, SpeedTier};
#[test]
fn test_qsv_icq_quality_lands_on_correct_struct_field() {
let slots = rate_slots_for_rc(QsvRateControl::Icq, 0, 0, 28);
assert_eq!(
slots.slot1_qpp_or_kbps_or_icq, 28,
"ICQ quality must land in slot 1 (qpp_or_kbps_or_icq) per \
vendor/intel/mfxstructs.h:83 — this is the TargetKbps/QPP/ICQQuality \
union arm"
);
assert_eq!(
slots.slot0_qpi_or_delay, 0,
"slot 0 is InitialDelayInKB/QPI/Accuracy per \
vendor/intel/mfxstructs.h:74-78 — no ICQQuality here"
);
assert_eq!(
slots.slot2_qpb_or_maxkbps, 0,
"slot 2 is MaxKbps/QPB/Convergence per \
vendor/intel/mfxstructs.h:85-89 — no ICQQuality here"
);
}
#[test]
fn test_qsv_cqp_slots_mirror_qpi_qpp_qpb() {
let slots = rate_slots_for_rc(QsvRateControl::Cqp, 72, 96, 0);
assert_eq!(slots.slot0_qpi_or_delay, 72);
assert_eq!(slots.slot1_qpp_or_kbps_or_icq, 96);
assert_eq!(slots.slot2_qpb_or_maxkbps, 96);
}
#[test]
fn test_qsv_target_usage_maps_from_speed_tier() {
let (w, h) = (1920, 1080);
let cases = [
(SpeedTier::Archive, 1u16, "1 = BEST_QUALITY per mfxdefs.h:91"),
(SpeedTier::Standard, 4u16, "4 = BALANCED per mfxdefs.h:92"),
(SpeedTier::Draft, 6u16, "6 = one step from BEST_SPEED (7)"),
];
for (tier, expected, reason) in cases {
let tp = tuning::qsv_av1_params(QualityTarget::Standard, tier, w, h);
let got = clamp_target_usage(tp.target_usage);
assert_eq!(got, expected, "{tier:?} → {got} (want {expected}, {reason})");
assert!(
(1..=7).contains(&got),
"TargetUsage must be 1..7 per vendor/intel/mfxdefs.h:91-93"
);
}
}
#[test]
fn test_qsv_target_usage_clamps_out_of_range() {
assert_eq!(clamp_target_usage(0), 1, "0 clamps up to 1");
assert_eq!(clamp_target_usage(8), 7, "8 clamps down to 7");
assert_eq!(clamp_target_usage(255), 7, "255 clamps down to 7");
assert_eq!(clamp_target_usage(4), 4, "4 passes through");
}
#[test]
fn test_qsv_ring_buffer_index_cycles() {
let mut idx = 0usize;
let mut seen = Vec::new();
for _ in 0..(RING_SIZE * 3) {
seen.push(idx);
idx = (idx + 1) % RING_SIZE;
}
assert_eq!(
seen,
vec![0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3],
"ring index must cycle through 0..RING_SIZE"
);
}
#[test]
fn test_qsv_ring_size_is_four() {
assert_eq!(RING_SIZE, 4);
}
#[test]
fn test_qsv_more_data_on_encode_returns_no_packet() {
fn simulate_encode(rc: MfxStatus) -> std::result::Result<Option<()>, String> {
match rc {
MFX_ERR_NONE => Ok(Some(())), MFX_ERR_MORE_DATA => Ok(None), err if err > 0 => Ok(None), err => Err(format!("encode failed: {err}")),
}
}
assert_eq!(simulate_encode(MFX_ERR_MORE_DATA).unwrap(), None);
assert_eq!(simulate_encode(MFX_ERR_NONE).unwrap(), Some(()));
assert!(simulate_encode(-1).is_err(), "unknown negative = hard error");
}
#[test]
fn test_qsv_eof_drain_ends_cleanly() {
fn simulate_flush_tick(rc: MfxStatus) -> std::result::Result<bool, String> {
match rc {
MFX_ERR_NONE => Ok(false),
MFX_ERR_MORE_DATA => Ok(true),
err if err > 0 => Ok(false),
err => Err(format!("flush failed: {err}")),
}
}
assert_eq!(simulate_flush_tick(MFX_ERR_MORE_DATA).unwrap(), true,
"clean EOF: flush terminates on MORE_DATA without error");
assert_eq!(simulate_flush_tick(MFX_ERR_NONE).unwrap(), false,
"NONE: flush has more output to drain");
assert_eq!(simulate_flush_tick(MFX_WRN_VIDEO_PARAM_CHANGED).unwrap(), false,
"warning: flush keeps looping to drain the bitstream");
assert!(simulate_flush_tick(-5).is_err(), "hard negative error bails");
}
#[test]
fn test_qsv_more_data_and_more_surface_are_distinct() {
assert_eq!(MFX_ERR_MORE_DATA, -10);
assert_eq!(MFX_ERR_MORE_SURFACE, -11);
assert_ne!(MFX_ERR_MORE_DATA, MFX_ERR_MORE_SURFACE);
}
#[test]
fn test_qsv_fourcc_literals_match_macro() {
fn make(a: u8, b: u8, c: u8, d: u8) -> u32 {
(a as u32) | ((b as u32) << 8) | ((c as u32) << 16) | ((d as u32) << 24)
}
assert_eq!(MFX_CODEC_AV1, make(b'A', b'V', b'1', b' '));
assert_eq!(MFX_FOURCC_NV12, make(b'N', b'V', b'1', b'2'));
assert_eq!(MFX_EXTBUFF_AV1_TILE_PARAM, make(b'A', b'1', b'T', b'L'));
assert_eq!(MFX_EXTBUFF_AV1_BITSTREAM_PARAM, make(b'A', b'V', b'1', b'B'));
}
#[test]
fn test_qsv_profile_main_equals_one() {
assert_eq!(MFX_PROFILE_AV1_MAIN, 1);
}
#[test]
fn test_qsv_chroma_format_yuv420_equals_one() {
assert_eq!(MFX_CHROMAFORMAT_YUV420, 1);
}
#[test]
fn test_qsv_rc_mode_values_match_spec() {
assert_eq!(MFX_RATECONTROL_CQP, 3);
assert_eq!(MFX_RATECONTROL_ICQ, 9); }
#[test]
fn test_qsv_mfx_info_fields_from_slots() {
let slots = rate_slots_for_rc(QsvRateControl::Icq, 0, 0, 33);
let mfx = MfxInfoMfx {
reserved: [0; 7],
low_power: 0,
brc_param_multiplier: 0,
frame_info: MfxFrameInfo {
reserved: [0; 4],
channel_id: 0,
bit_depth_luma: 8,
bit_depth_chroma: 8,
shift: 0,
frame_id: [0; 4],
fourcc: MFX_FOURCC_NV12,
width: 1920,
height: 1080,
crop_x: 0,
crop_y: 0,
crop_w: 1920,
crop_h: 1080,
frame_rate_ext_n: 30000,
frame_rate_ext_d: 1000,
reserved3: 0,
aspect_ratio_w: 1,
aspect_ratio_h: 1,
pic_struct: MFX_PICSTRUCT_PROGRESSIVE,
chroma_format: MFX_CHROMAFORMAT_YUV420,
reserved2: 0,
},
codec_id: MFX_CODEC_AV1,
codec_profile: MFX_PROFILE_AV1_MAIN,
codec_level: 0,
num_thread: 0,
target_usage: 4,
gop_pic_size: 240,
gop_ref_dist: 1,
gop_opt_flag: 0,
idr_interval: 0,
rate_control_method: MFX_RATECONTROL_ICQ,
qpi_or_delay: slots.slot0_qpi_or_delay,
buffer_size_kb: 0,
qpp_or_kbps_or_icq: slots.slot1_qpp_or_kbps_or_icq,
qpb_or_maxkbps: slots.slot2_qpb_or_maxkbps,
num_slice: 0,
num_ref_frame: 1,
encoded_order: 0,
};
assert_eq!(mfx.qpp_or_kbps_or_icq, 33, "ICQQuality lives at slot 1");
assert_eq!(mfx.qpi_or_delay, 0, "slot 0 must be zero in ICQ mode");
assert_eq!(mfx.qpb_or_maxkbps, 0, "slot 2 must be zero in ICQ mode");
assert_eq!(mfx.rate_control_method, MFX_RATECONTROL_ICQ);
}
#[test]
fn test_qsv_zeroed_video_param_is_all_zero() {
let z = zeroed_video_param();
assert_eq!(z.mfx.codec_id, 0);
assert_eq!(z.mfx.codec_profile, 0);
assert_eq!(z.mfx.rate_control_method, 0);
assert_eq!(z.mfx.qpi_or_delay, 0);
assert_eq!(z.mfx.qpp_or_kbps_or_icq, 0);
assert_eq!(z.mfx.qpb_or_maxkbps, 0);
assert_eq!(z.mfx.frame_info.width, 0);
assert_eq!(z.mfx.frame_info.height, 0);
assert!(z.ext_param.is_null());
}
#[test]
fn test_qsv_align_up_power_of_two() {
assert_eq!(align_up(1u32, 16u32), 16);
assert_eq!(align_up(16u32, 16u32), 16);
assert_eq!(align_up(17u32, 16u32), 32);
assert_eq!(align_up(1920u32, 64u32), 1920);
assert_eq!(align_up(1921u32, 64u32), 1984);
}
#[test]
fn test_qsv_icq_flow_preserves_tuning_adapter_value() {
for (w, h) in [(640, 360), (1920, 1080), (3840, 2160)] {
for target in [
QualityTarget::Low,
QualityTarget::Standard,
QualityTarget::High,
] {
let tp = tuning::qsv_av1_params(target, SpeedTier::Standard, w, h);
assert_eq!(tp.rc_mode, QsvRateControl::Icq);
let slots = rate_slots_for_rc(tp.rc_mode, 0, 0, tp.icq_quality);
assert_eq!(
slots.slot1_qpp_or_kbps_or_icq, tp.icq_quality,
"ICQ quality value must reach slot 1 end-to-end — \
{target:?}/{w}x{h}: adapter={}, slot1={}",
tp.icq_quality, slots.slot1_qpp_or_kbps_or_icq
);
assert_eq!(slots.slot0_qpi_or_delay, 0);
assert_eq!(slots.slot2_qpb_or_maxkbps, 0);
}
}
}
#[test]
fn test_qsv_encode_ctrl_struct_size() {
assert_eq!(std::mem::size_of::<MfxEncodeCtrl>(), 56);
}
#[test]
fn test_qsv_fourcc_dispatch_10bit() {
assert_eq!(qsv_fourcc_for(PixelFormat::Yuv420p).unwrap(), MFX_FOURCC_NV12);
assert_eq!(qsv_fourcc_for(PixelFormat::Yuv420p10le).unwrap(), MFX_FOURCC_P010);
assert_eq!(MFX_FOURCC_P010, 0x30313050, "P010 FOURCC = 'P','0','1','0' LE");
}
#[test]
fn test_qsv_fourcc_dispatch_rejects_4_2_2_and_4_4_4() {
for unsupported in [
PixelFormat::Yuv422p,
PixelFormat::Yuv422p10le,
PixelFormat::Yuv444p,
PixelFormat::Yuv444p10le,
PixelFormat::Yuva444p10le,
PixelFormat::Nv12,
PixelFormat::Rgb24,
] {
assert!(
qsv_fourcc_for(unsupported).is_err(),
"{unsupported:?} must be rejected by QSV dispatch"
);
}
}
#[test]
fn test_qsv_bit_depth_triple_dispatch() {
let (luma8, chroma8, shift8) = qsv_bit_depth_triple(PixelFormat::Yuv420p);
assert_eq!((luma8, chroma8, shift8), (8, 8, 0), "NV12: 8-bit, no shift");
let (luma10, chroma10, shift10) = qsv_bit_depth_triple(PixelFormat::Yuv420p10le);
assert_eq!(
(luma10, chroma10, shift10),
(10, 10, 1),
"P010: 10-bit + Shift=1 (upper-10-bit convention)"
);
}
#[test]
fn test_qsv_transfer_to_h273_codes() {
assert_eq!(transfer_to_h273(TransferFn::Bt709), 1);
assert_eq!(transfer_to_h273(TransferFn::Bt470Bg), 4);
assert_eq!(transfer_to_h273(TransferFn::Linear), 8);
assert_eq!(transfer_to_h273(TransferFn::St2084), 16, "HDR10 PQ");
assert_eq!(transfer_to_h273(TransferFn::AribStdB67), 18, "HLG");
assert_eq!(transfer_to_h273(TransferFn::Unspecified), 1);
}
#[test]
fn test_qsv_frame_info_p010_layout() {
let (bdl, bdc, shift) = qsv_bit_depth_triple(PixelFormat::Yuv420p10le);
let fourcc = qsv_fourcc_for(PixelFormat::Yuv420p10le).unwrap();
let fi = MfxFrameInfo {
reserved: [0; 4],
channel_id: 0,
bit_depth_luma: bdl,
bit_depth_chroma: bdc,
shift,
frame_id: [0; 4],
fourcc,
width: 1920,
height: 1080,
crop_x: 0,
crop_y: 0,
crop_w: 1920,
crop_h: 1080,
frame_rate_ext_n: 30000,
frame_rate_ext_d: 1000,
reserved3: 0,
aspect_ratio_w: 1,
aspect_ratio_h: 1,
pic_struct: MFX_PICSTRUCT_PROGRESSIVE,
chroma_format: MFX_CHROMAFORMAT_YUV420,
reserved2: 0,
};
assert_eq!(fi.bit_depth_luma, 10);
assert_eq!(fi.bit_depth_chroma, 10);
assert_eq!(fi.shift, 1, "P010 must set Shift=1");
assert_eq!(fi.fourcc, MFX_FOURCC_P010);
assert_eq!(fi.chroma_format, MFX_CHROMAFORMAT_YUV420, "still 4:2:0 sub-sampling");
let bytes = unsafe {
std::slice::from_raw_parts(
&fi as *const MfxFrameInfo as *const u8,
std::mem::size_of::<MfxFrameInfo>(),
)
};
let fourcc_offset = std::mem::offset_of!(MfxFrameInfo, fourcc);
assert_eq!(
u32::from_le_bytes(bytes[fourcc_offset..fourcc_offset + 4].try_into().unwrap()),
MFX_FOURCC_P010,
"fourcc reads back as P010 from the expected struct offset"
);
}
#[test]
fn test_qsv_coding_option3_10bit_layout() {
let co3 = MfxExtCodingOption3 {
header: MfxExtBuffer {
buffer_id: MFX_EXTBUFF_CODING_OPTION3,
buffer_sz: std::mem::size_of::<MfxExtCodingOption3>() as u32,
},
_pad_to_158: [0; 150],
target_chroma_format_plus1: MFX_TARGET_CHROMAFORMAT_YUV420_PLUS1,
target_bit_depth_luma: 10,
target_bit_depth_chroma: 10,
_tail: [0; 348],
};
assert_eq!(co3.target_bit_depth_luma, 10, "AV1 BitDepth=10 in seq header");
assert_eq!(co3.target_bit_depth_chroma, 10, "AV1 BitDepth=10 in seq header");
assert_eq!(
co3.target_chroma_format_plus1, 2,
"MFX_CHROMAFORMAT_YUV420 (1) + 1 = 2"
);
assert_eq!(co3.header.buffer_id, MFX_EXTBUFF_CODING_OPTION3);
assert_eq!(memoffset_target_bit_depth_luma(), 160);
assert_eq!(MFX_EXTBUFF_CODING_OPTION3, 0x334f4443);
}
fn memoffset_target_bit_depth_luma() -> usize {
let base = std::mem::MaybeUninit::<MfxExtCodingOption3>::uninit();
let ptr = base.as_ptr();
unsafe {
(std::ptr::addr_of!((*ptr).target_bit_depth_luma) as usize) - (ptr as usize)
}
}
#[test]
fn test_qsv_signal_info_hdr10_layout() {
let cm = ColorMetadata {
transfer: TransferFn::St2084,
matrix_coefficients: 9, colour_primaries: 9, full_range: true,
mastering_display: None,
content_light_level: None,
};
let signal_info = MfxExtVideoSignalInfo {
header: MfxExtBuffer {
buffer_id: MFX_EXTBUFF_VIDEO_SIGNAL_INFO,
buffer_sz: std::mem::size_of::<MfxExtVideoSignalInfo>() as u32,
},
video_format: 5,
video_full_range: if cm.full_range { 1 } else { 0 },
colour_description_present: 1,
colour_primaries: cm.colour_primaries as u16,
transfer_characteristics: transfer_to_h273(cm.transfer),
matrix_coefficients: cm.matrix_coefficients as u16,
};
assert_eq!(signal_info.colour_description_present, 1, "must be set so codes emit");
assert_eq!(signal_info.colour_primaries, 9, "BT.2020");
assert_eq!(signal_info.transfer_characteristics, 16, "ST 2084 / PQ");
assert_eq!(signal_info.matrix_coefficients, 9, "BT.2020 NCL");
assert_eq!(signal_info.video_full_range, 1, "full range");
assert_eq!(signal_info.header.buffer_id, MFX_EXTBUFF_VIDEO_SIGNAL_INFO);
assert_eq!(
MFX_EXTBUFF_VIDEO_SIGNAL_INFO, 0x4e495356,
"ext buffer ID must match upstream MFX_MAKE_FOURCC('V','S','I','N')"
);
}
#[test]
fn test_qsv_8bit_sdr_layout_unchanged() {
let (bdl, bdc, shift) = qsv_bit_depth_triple(PixelFormat::Yuv420p);
assert_eq!((bdl, bdc, shift), (8, 8, 0), "8-bit dispatch unchanged");
let cm = ColorMetadata::default();
let signal_info = MfxExtVideoSignalInfo {
header: MfxExtBuffer {
buffer_id: MFX_EXTBUFF_VIDEO_SIGNAL_INFO,
buffer_sz: std::mem::size_of::<MfxExtVideoSignalInfo>() as u32,
},
video_format: 5,
video_full_range: if cm.full_range { 1 } else { 0 },
colour_description_present: 1,
colour_primaries: cm.colour_primaries as u16,
transfer_characteristics: transfer_to_h273(cm.transfer),
matrix_coefficients: cm.matrix_coefficients as u16,
};
assert_eq!(signal_info.colour_primaries, 1, "BT.709 default");
assert_eq!(signal_info.transfer_characteristics, 1, "BT.709 default");
assert_eq!(signal_info.matrix_coefficients, 1, "BT.709 default");
assert_eq!(signal_info.video_full_range, 0, "studio range default");
}
}