1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
use super::{
AVBufferRef, AVCodecContext, AVHWDeviceType, AVPixelFormat, DecodeError, HardwareAccel,
VideoDecoderInner, ptr,
};
impl VideoDecoderInner {
/// Maps our `HardwareAccel` enum to the corresponding FFmpeg `AVHWDeviceType`.
///
/// Returns `None` for `Auto` and `None` variants as they require special handling.
pub(super) fn hw_accel_to_device_type(accel: HardwareAccel) -> Option<AVHWDeviceType> {
match accel {
HardwareAccel::Auto => None,
HardwareAccel::None => None,
HardwareAccel::Nvdec => Some(ff_sys::AVHWDeviceType_AV_HWDEVICE_TYPE_CUDA),
HardwareAccel::Qsv => Some(ff_sys::AVHWDeviceType_AV_HWDEVICE_TYPE_QSV),
HardwareAccel::Amf => Some(ff_sys::AVHWDeviceType_AV_HWDEVICE_TYPE_D3D11VA), // AMF uses D3D11
HardwareAccel::VideoToolbox => {
Some(ff_sys::AVHWDeviceType_AV_HWDEVICE_TYPE_VIDEOTOOLBOX)
}
HardwareAccel::Vaapi => Some(ff_sys::AVHWDeviceType_AV_HWDEVICE_TYPE_VAAPI),
}
}
/// Returns the hardware decoders to try in priority order for Auto mode.
const fn hw_accel_auto_priority() -> &'static [HardwareAccel] {
// Priority order: NVDEC, QSV, VideoToolbox, VA-API, AMF
&[
HardwareAccel::Nvdec,
HardwareAccel::Qsv,
HardwareAccel::VideoToolbox,
HardwareAccel::Vaapi,
HardwareAccel::Amf,
]
}
/// Attempts to initialize hardware acceleration.
///
/// # Arguments
///
/// * `codec_ctx` - The codec context to configure
/// * `accel` - Requested hardware acceleration mode
///
/// # Returns
///
/// Returns `Ok((hw_device_ctx, active_accel))` if hardware acceleration was initialized,
/// or `Ok((None, HardwareAccel::None))` if software decoding should be used.
///
/// # Errors
///
/// Returns an error only if a specific hardware accelerator was requested but failed to initialize.
pub(super) unsafe fn init_hardware_accel(
codec_ctx: *mut AVCodecContext,
accel: HardwareAccel,
) -> Result<(Option<*mut AVBufferRef>, HardwareAccel), DecodeError> {
match accel {
HardwareAccel::Auto => {
// Try hardware accelerators in priority order
for &hw_type in Self::hw_accel_auto_priority() {
// SAFETY: Caller ensures codec_ctx is valid and not yet configured with hardware
match unsafe { Self::try_init_hw_device(codec_ctx, hw_type) } {
Ok((Some(ctx), active)) => {
log::info!("hwaccel selected backend={}", active.name());
return Ok((Some(ctx), active));
}
_ => {
log::debug!(
"hwaccel probe failed backend={} trying next",
hw_type.name()
);
}
}
}
// All hardware accelerators failed, fall back to software
Ok((None, HardwareAccel::None))
}
HardwareAccel::None => {
// Software decoding explicitly requested
Ok((None, HardwareAccel::None))
}
_ => {
// Specific hardware accelerator requested
// SAFETY: Caller ensures codec_ctx is valid and not yet configured with hardware
unsafe { Self::try_init_hw_device(codec_ctx, accel) }
}
}
}
/// Tries to initialize a specific hardware device.
///
/// # Safety
///
/// Caller must ensure `codec_ctx` is valid and not yet configured with a hardware device.
unsafe fn try_init_hw_device(
codec_ctx: *mut AVCodecContext,
accel: HardwareAccel,
) -> Result<(Option<*mut AVBufferRef>, HardwareAccel), DecodeError> {
// Get the FFmpeg device type
let Some(device_type) = Self::hw_accel_to_device_type(accel) else {
return Ok((None, HardwareAccel::None));
};
// Create hardware device context
// SAFETY: FFmpeg is initialized, device_type is valid
let mut hw_device_ctx: *mut AVBufferRef = ptr::null_mut();
let ret = unsafe {
ff_sys::av_hwdevice_ctx_create(
ptr::addr_of_mut!(hw_device_ctx),
device_type,
ptr::null(), // device: null for default device
ptr::null_mut(), // opts: null for default options
0, // flags: currently unused by FFmpeg
)
};
if ret < 0 {
// Hardware device creation failed
return Err(DecodeError::HwAccelUnavailable { accel });
}
// Assign hardware device context to codec context
// We transfer ownership of the reference to codec_ctx
// SAFETY: codec_ctx and hw_device_ctx are valid
unsafe {
(*codec_ctx).hw_device_ctx = hw_device_ctx;
}
// We keep our own reference for cleanup in Drop
// SAFETY: hw_device_ctx is valid
let our_ref = unsafe { ff_sys::av_buffer_ref(hw_device_ctx) };
if our_ref.is_null() {
// Failed to create our reference
// codec_ctx still owns the original, so we don't need to clean it up here
return Err(DecodeError::HwAccelUnavailable { accel });
}
Ok((Some(our_ref), accel))
}
/// Returns the currently active hardware acceleration mode.
pub(crate) fn hardware_accel(&self) -> HardwareAccel {
self.active_hw_accel
}
/// Checks if a pixel format is a hardware format.
///
/// Hardware formats include: D3D11, CUDA, VAAPI, VideoToolbox, QSV, etc.
const fn is_hardware_format(format: AVPixelFormat) -> bool {
matches!(
format,
ff_sys::AVPixelFormat_AV_PIX_FMT_D3D11
| ff_sys::AVPixelFormat_AV_PIX_FMT_CUDA
| ff_sys::AVPixelFormat_AV_PIX_FMT_VAAPI
| ff_sys::AVPixelFormat_AV_PIX_FMT_VIDEOTOOLBOX
| ff_sys::AVPixelFormat_AV_PIX_FMT_QSV
| ff_sys::AVPixelFormat_AV_PIX_FMT_VDPAU
| ff_sys::AVPixelFormat_AV_PIX_FMT_DXVA2_VLD
| ff_sys::AVPixelFormat_AV_PIX_FMT_OPENCL
| ff_sys::AVPixelFormat_AV_PIX_FMT_MEDIACODEC
| ff_sys::AVPixelFormat_AV_PIX_FMT_VULKAN
)
}
/// Transfers a hardware frame to CPU memory if needed.
///
/// If `self.frame` is a hardware frame, creates a new software frame
/// and transfers the data from GPU to CPU memory.
///
/// # Safety
///
/// Caller must ensure `self.frame` contains a valid decoded frame.
pub(super) unsafe fn transfer_hardware_frame_if_needed(&mut self) -> Result<(), DecodeError> {
// SAFETY: self.frame is valid and owned by this instance
let frame_format = unsafe { (*self.frame).format };
if !Self::is_hardware_format(frame_format) {
// Not a hardware frame, no transfer needed
return Ok(());
}
// Create a temporary software frame for transfer
// SAFETY: FFmpeg is initialized
let sw_frame = unsafe { ff_sys::av_frame_alloc() };
if sw_frame.is_null() {
return Err(DecodeError::Ffmpeg {
code: 0,
message: "Failed to allocate software frame for hardware transfer".to_string(),
});
}
// Transfer data from hardware frame to software frame
// SAFETY: self.frame and sw_frame are valid
let ret = unsafe {
ff_sys::av_hwframe_transfer_data(
sw_frame, self.frame, 0, // flags: currently unused
)
};
if ret < 0 {
// Transfer failed, clean up
unsafe {
ff_sys::av_frame_free(&mut (sw_frame as *mut _));
}
return Err(DecodeError::Ffmpeg {
code: ret,
message: format!(
"Failed to transfer hardware frame to CPU memory: {}",
ff_sys::av_error_string(ret)
),
});
}
// Copy metadata (pts, duration, etc.) from hardware frame to software frame
// SAFETY: Both frames are valid
unsafe {
(*sw_frame).pts = (*self.frame).pts;
(*sw_frame).pkt_dts = (*self.frame).pkt_dts;
(*sw_frame).duration = (*self.frame).duration;
(*sw_frame).time_base = (*self.frame).time_base;
}
// Replace self.frame with the software frame
// SAFETY: self.frame is valid and owned by this instance
unsafe {
ff_sys::av_frame_unref(self.frame);
ff_sys::av_frame_move_ref(self.frame, sw_frame);
ff_sys::av_frame_free(&mut (sw_frame as *mut _));
}
Ok(())
}
}