use ffmpeg_next::{
codec::context::Context as CodecContext, decoder::Video as VideoDecoder,
frame::Video as VideoFrame,
};
use ffmpeg_sys_next::{
AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX, AVBufferRef, AVCodecContext, AVCodecHWConfig,
AVHWDeviceType,
};
use crate::error::UnbundleError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum HardwareAccelerationMode {
#[default]
Auto,
Software,
Specific(HardwareDeviceType),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HardwareDeviceType {
Cuda,
Vaapi,
Dxva2,
D3d11va,
VideoToolbox,
Qsv,
}
impl HardwareDeviceType {
fn to_av_hw_device_type(self) -> AVHWDeviceType {
match self {
HardwareDeviceType::Cuda => AVHWDeviceType::AV_HWDEVICE_TYPE_CUDA,
HardwareDeviceType::Vaapi => AVHWDeviceType::AV_HWDEVICE_TYPE_VAAPI,
HardwareDeviceType::Dxva2 => AVHWDeviceType::AV_HWDEVICE_TYPE_DXVA2,
HardwareDeviceType::D3d11va => AVHWDeviceType::AV_HWDEVICE_TYPE_D3D11VA,
HardwareDeviceType::VideoToolbox => AVHWDeviceType::AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
HardwareDeviceType::Qsv => AVHWDeviceType::AV_HWDEVICE_TYPE_QSV,
}
}
}
pub fn available_hardware_devices() -> Vec<HardwareDeviceType> {
let mut devices = Vec::new();
let mut device_type = AVHWDeviceType::AV_HWDEVICE_TYPE_NONE;
loop {
device_type = unsafe { ffmpeg_sys_next::av_hwdevice_iterate_types(device_type) };
if device_type == AVHWDeviceType::AV_HWDEVICE_TYPE_NONE {
break;
}
let mapped = match device_type {
AVHWDeviceType::AV_HWDEVICE_TYPE_CUDA => Some(HardwareDeviceType::Cuda),
AVHWDeviceType::AV_HWDEVICE_TYPE_VAAPI => Some(HardwareDeviceType::Vaapi),
AVHWDeviceType::AV_HWDEVICE_TYPE_DXVA2 => Some(HardwareDeviceType::Dxva2),
AVHWDeviceType::AV_HWDEVICE_TYPE_D3D11VA => Some(HardwareDeviceType::D3d11va),
AVHWDeviceType::AV_HWDEVICE_TYPE_VIDEOTOOLBOX => Some(HardwareDeviceType::VideoToolbox),
AVHWDeviceType::AV_HWDEVICE_TYPE_QSV => Some(HardwareDeviceType::Qsv),
_ => None,
};
if let Some(dev) = mapped {
devices.push(dev);
}
}
devices
}
pub(crate) struct HardwareDecoderSetup {
pub decoder: VideoDecoder,
pub hardware_active: bool,
}
pub(crate) fn try_create_hardware_decoder(
codec_context: CodecContext,
mode: HardwareAccelerationMode,
) -> Result<HardwareDecoderSetup, UnbundleError> {
if mode == HardwareAccelerationMode::Software {
let decoder = codec_context.decoder().video()?;
return Ok(HardwareDecoderSetup {
decoder,
hardware_active: false,
});
}
let device_type = match mode {
HardwareAccelerationMode::Auto => find_best_hardware_device_for_codec(&codec_context),
HardwareAccelerationMode::Specific(device) => {
let av_type = device.to_av_hw_device_type();
if codec_supports_hardware_type(&codec_context, av_type) {
Some(av_type)
} else {
None
}
}
HardwareAccelerationMode::Software => unreachable!(),
};
let Some(av_device_type) = device_type else {
let decoder = codec_context.decoder().video()?;
return Ok(HardwareDecoderSetup {
decoder,
hardware_active: false,
});
};
match create_hardware_device_context(av_device_type) {
Ok(hardware_device_context) => {
unsafe {
let context_pointer = codec_context.as_ptr() as *mut AVCodecContext;
(*context_pointer).hw_device_ctx =
ffmpeg_sys_next::av_buffer_ref(hardware_device_context);
}
let decoder = codec_context.decoder().video()?;
unsafe {
let mut hardware_reference = hardware_device_context;
ffmpeg_sys_next::av_buffer_unref(&mut hardware_reference);
}
Ok(HardwareDecoderSetup {
decoder,
hardware_active: true,
})
}
Err(_) => {
let decoder = codec_context.decoder().video()?;
Ok(HardwareDecoderSetup {
decoder,
hardware_active: false,
})
}
}
}
pub(crate) fn transfer_hardware_frame(
hardware_frame: &VideoFrame,
) -> Result<VideoFrame, UnbundleError> {
let format = unsafe { (*hardware_frame.as_ptr()).format };
let mut software_frame = VideoFrame::empty();
let result = unsafe {
ffmpeg_sys_next::av_hwframe_transfer_data(
software_frame.as_mut_ptr(),
hardware_frame.as_ptr(),
0,
)
};
if result < 0 {
Err(UnbundleError::VideoDecodeError(format!(
"Hardware frame transfer failed (format={format}, result={result})"
)))
} else {
unsafe {
(*software_frame.as_mut_ptr()).pts = (*hardware_frame.as_ptr()).pts;
(*software_frame.as_mut_ptr()).pkt_dts = (*hardware_frame.as_ptr()).pkt_dts;
}
Ok(software_frame)
}
}
fn find_best_hardware_device_for_codec(codec_context: &CodecContext) -> Option<AVHWDeviceType> {
let codec_ptr = unsafe { (*codec_context.as_ptr()).codec };
if codec_ptr.is_null() {
return None;
}
let mut index: i32 = 0;
let mut best: Option<AVHWDeviceType> = None;
loop {
let config: *const AVCodecHWConfig =
unsafe { ffmpeg_sys_next::avcodec_get_hw_config(codec_ptr, index) };
if config.is_null() {
break;
}
let methods = unsafe { (*config).methods };
if methods & (AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX as i32) != 0 {
let device_type = unsafe { (*config).device_type };
if device_type != AVHWDeviceType::AV_HWDEVICE_TYPE_NONE {
if best.is_none() {
best = Some(device_type);
}
}
}
index += 1;
}
best
}
fn codec_supports_hardware_type(codec_context: &CodecContext, device_type: AVHWDeviceType) -> bool {
let codec_ptr = unsafe { (*codec_context.as_ptr()).codec };
if codec_ptr.is_null() {
return false;
}
let mut index: i32 = 0;
loop {
let config: *const AVCodecHWConfig =
unsafe { ffmpeg_sys_next::avcodec_get_hw_config(codec_ptr, index) };
if config.is_null() {
break;
}
let methods = unsafe { (*config).methods };
let queried_device_type = unsafe { (*config).device_type };
if methods & (AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX as i32) != 0
&& queried_device_type == device_type
{
return true;
}
index += 1;
}
false
}
fn create_hardware_device_context(
device_type: AVHWDeviceType,
) -> Result<*mut AVBufferRef, UnbundleError> {
let mut hardware_device_context: *mut AVBufferRef = std::ptr::null_mut();
let result = unsafe {
ffmpeg_sys_next::av_hwdevice_ctx_create(
&mut hardware_device_context,
device_type,
std::ptr::null(),
std::ptr::null_mut(),
0,
)
};
if result < 0 {
Err(UnbundleError::VideoDecodeError(format!(
"Failed to create hardware device context (result={result})"
)))
} else {
Ok(hardware_device_context)
}
}