use std::ptr;
use ffmpeg_next::ffi::{
AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX, AVCodec, AVCodecContext, AVHWDeviceType, AVPixelFormat,
avcodec_get_hw_config,
};
#[repr(C)]
pub(crate) struct CallbackState {
pub(crate) wanted: AVPixelFormat,
pub(crate) wanted_int: i32,
}
pub(crate) unsafe extern "C" fn get_hw_format(
ctx: *mut AVCodecContext,
pix_fmts: *const AVPixelFormat,
) -> AVPixelFormat {
debug_assert!(!ctx.is_null());
debug_assert!(!pix_fmts.is_null());
let state = unsafe { (*ctx).opaque as *const CallbackState };
let (wanted, wanted_int) = if state.is_null() {
(
AVPixelFormat::AV_PIX_FMT_NONE,
AVPixelFormat::AV_PIX_FMT_NONE as i32,
)
} else {
unsafe { ((*state).wanted, (*state).wanted_int) }
};
let mut p = pix_fmts as *const i32;
let none_int = AVPixelFormat::AV_PIX_FMT_NONE as i32;
loop {
let v = unsafe { ptr::read(p) };
if v == none_int {
return AVPixelFormat::AV_PIX_FMT_NONE;
}
if v == wanted_int {
return wanted;
}
p = unsafe { p.add(1) };
}
}
pub(crate) fn codec_supports_hwaccel(
codec: *const AVCodec,
device_type: AVHWDeviceType,
wanted_pix_fmt: i32,
) -> bool {
debug_assert!(!codec.is_null());
let device_type_int = device_type as i32;
let mut i = 0;
loop {
let cfg = unsafe { avcodec_get_hw_config(codec, i) };
if cfg.is_null() {
return false;
}
let methods: i32 = unsafe { ptr::read(ptr::addr_of!((*cfg).methods)) };
let cfg_device_type_int: i32 =
unsafe { ptr::read(ptr::addr_of!((*cfg).device_type) as *const i32) };
let cfg_pix_fmt_int: i32 = unsafe { ptr::read(ptr::addr_of!((*cfg).pix_fmt) as *const i32) };
if methods & (AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX as i32) != 0
&& cfg_device_type_int == device_type_int
&& cfg_pix_fmt_int == wanted_pix_fmt
{
return true;
}
i += 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
struct FakeCtx(*mut AVCodecContext);
impl FakeCtx {
fn new(state: *mut CallbackState) -> Self {
let boxed: Box<AVCodecContext> = unsafe { Box::new(std::mem::zeroed()) };
let raw = Box::into_raw(boxed);
unsafe { (*raw).opaque = state.cast() };
Self(raw)
}
}
impl Drop for FakeCtx {
fn drop(&mut self) {
unsafe { drop(Box::from_raw(self.0)) };
}
}
fn make_state(wanted: AVPixelFormat) -> CallbackState {
CallbackState {
wanted,
wanted_int: wanted as i32,
}
}
fn run(state: &CallbackState, mut offered: Vec<i32>) -> AVPixelFormat {
offered.push(AVPixelFormat::AV_PIX_FMT_NONE as i32);
let ctx = FakeCtx::new(state as *const _ as *mut _);
unsafe { get_hw_format(ctx.0, offered.as_ptr() as *const AVPixelFormat) }
}
#[test]
fn returns_wanted_hw_format_when_offered() {
let state = make_state(AVPixelFormat::AV_PIX_FMT_VIDEOTOOLBOX);
let got = run(
&state,
vec![
AVPixelFormat::AV_PIX_FMT_VIDEOTOOLBOX as i32,
AVPixelFormat::AV_PIX_FMT_NV12 as i32,
],
);
assert_eq!(got, AVPixelFormat::AV_PIX_FMT_VIDEOTOOLBOX);
}
#[test]
fn returns_none_when_wanted_absent() {
let state = make_state(AVPixelFormat::AV_PIX_FMT_VIDEOTOOLBOX);
let got = run(
&state,
vec![
AVPixelFormat::AV_PIX_FMT_NV12 as i32,
AVPixelFormat::AV_PIX_FMT_YUV420P as i32,
],
);
assert_eq!(got, AVPixelFormat::AV_PIX_FMT_NONE);
}
#[test]
fn null_opaque_is_treated_as_strict() {
let boxed: Box<AVCodecContext> = unsafe { Box::new(std::mem::zeroed()) };
let ctx_raw = Box::into_raw(boxed);
unsafe { (*ctx_raw).opaque = ptr::null_mut() };
let offered = [
AVPixelFormat::AV_PIX_FMT_NV12 as i32,
AVPixelFormat::AV_PIX_FMT_NONE as i32,
];
let got = unsafe { get_hw_format(ctx_raw, offered.as_ptr() as *const AVPixelFormat) };
assert_eq!(got, AVPixelFormat::AV_PIX_FMT_NONE);
unsafe { drop(Box::from_raw(ctx_raw)) };
}
#[test]
fn unknown_offered_value_is_skipped_without_ub() {
let state = make_state(AVPixelFormat::AV_PIX_FMT_VIDEOTOOLBOX);
let got = run(
&state,
vec![
99_999_i32, AVPixelFormat::AV_PIX_FMT_NV12 as i32,
],
);
assert_eq!(got, AVPixelFormat::AV_PIX_FMT_NONE);
}
#[cfg(target_os = "macos")]
#[test]
fn codec_supports_hwaccel_requires_matching_pix_fmt() {
use ffmpeg_next::ffi::{AVCodecID, AVHWDeviceType, AVPixelFormat, avcodec_find_decoder};
let codec_ptr = unsafe { avcodec_find_decoder(AVCodecID::AV_CODEC_ID_H264) };
assert!(!codec_ptr.is_null(), "H.264 decoder must be present");
let device = AVHWDeviceType::AV_HWDEVICE_TYPE_VIDEOTOOLBOX;
let videotoolbox = AVPixelFormat::AV_PIX_FMT_VIDEOTOOLBOX as i32;
let nv12 = AVPixelFormat::AV_PIX_FMT_NV12 as i32;
assert!(
codec_supports_hwaccel(codec_ptr, device, videotoolbox),
"VideoToolbox + AV_PIX_FMT_VIDEOTOOLBOX must be advertised by FFmpeg's H.264 decoder"
);
assert!(
!codec_supports_hwaccel(codec_ptr, device, nv12),
"VideoToolbox + AV_PIX_FMT_NV12 must NOT match the codec's HW config — \
the strict get_format would have no offered HW format to return"
);
}
}