feat(oakcodec): hardware video decoding by default on all platforms

FFmpeg 8 removed the standalone hardware decoders (h264_videotoolbox/
vaapi/nvdec/d3d11va no longer exist in its configure) — hardware decode
now only exists as a hwaccel attached to the software decoder. The new
oakcodec::hwdecode module therefore opens the regular decoder with the
platform's hardware device context attached (VideoToolbox on macOS,
VA-API then NVDEC on Linux, D3D11VA then NVDEC on Windows): FFmpeg
engages the matching hwaccel, decodes into hardware surfaces, and we
transfer them to system memory (NV12/P010) ahead of swscale.

- HardwareDecoding config switch, default ON by mandate; a checkbox in
  Preferences > Rendering (EN/ZH); device creation failure skips to the
  next candidate and finally to software; a decode-time failure on a
  hardware session reopens it as software and retries once.
- hw_decoder_name() observability hook plus a HW_TRANSFERS counter so
  tests can prove the hwaccel really engaged (not silently software).
- Verification: demo.mp4 H.264 decodes through VideoToolbox with a
  transferred hardware surface, and the pixels match the software
  decode within 0.05; switch off forces software.
- build-ffmpeg.sh also enables nvdec when ffnvcodec headers exist.
This commit is contained in:
2026-08-19 14:04:55 +08:00
parent 4dd4ceb08a
commit 35b9ad9541
7 changed files with 458 additions and 15 deletions
+27
View File
@@ -95,6 +95,7 @@ pub struct PreferencesContent {
theme: Entity<ComboBox>,
cache_dir: Entity<PathField>,
use_proxy: Entity<CheckBox>,
hw_decode: Entity<CheckBox>,
proxy_divider: Entity<ComboBox>,
snapshot_interval: Entity<SpinBox>,
transition_length: Entity<SpinBox>,
@@ -251,6 +252,30 @@ impl PreferencesContent {
combo.set_selected(Some(divider_selected), cx)
});
// --- 渲染 Rendering: hardware decoding switch -------------------
// Default ON (user mandate); off forces software decoding.
let hw_decode = cx.new(|cx| {
CheckBox::new(
9,
if config_get_bool("HardwareDecoding", true) {
CheckState::Checked
} else {
CheckState::Unchecked
},
window,
cx,
)
.with_label(i18n::tr("preferences.hwdecode.enable"))
});
cx.subscribe(&hw_decode, |_this, check, event: &CheckBoxEvent, cx| {
if let CheckBoxEvent::Toggled { state, .. } = event {
let enabled = *state == CheckState::Checked;
config_set_bool("HardwareDecoding", enabled);
check.update(cx, |check, cx| check.set_state(*state, cx));
}
})
.detach();
// --- 项目 Project: snapshot interval + default transition ----------
let snapshot_interval = cx.new(|cx| {
let current =
@@ -342,6 +367,7 @@ impl PreferencesContent {
theme,
cache_dir,
use_proxy,
hw_decode,
proxy_divider,
snapshot_interval,
transition_length,
@@ -499,6 +525,7 @@ impl Render for PreferencesContent {
i18n::tr("preferences.backend").into(),
self.backend.clone(),
))
.child(self.hw_decode.clone())
// 缓存 Cache
.child(section_header(&colors, i18n::tr("preferences.section.cache").into()))
.child(form_row(
+2
View File
@@ -434,6 +434,7 @@ const EN: &[(&str, &str)] = &[
("preferences.section.project", "Project"),
("preferences.section.audio", "Audio"),
("preferences.backend", "Renderer backend"),
("preferences.hwdecode.enable", "Hardware-accelerated video decoding (VideoToolbox / VA-API / NVDEC / D3D11VA)"),
("preferences.backend.placeholder", "Select a backend…"),
("preferences.language", "Language"),
("preferences.language.placeholder", "Select a language…"),
@@ -899,6 +900,7 @@ const ZH: &[(&str, &str)] = &[
("preferences.section.project", "项目"),
("preferences.section.audio", "音频"),
("preferences.backend", "渲染后端"),
("preferences.hwdecode.enable", "硬件加速视频解码(VideoToolbox / VA-API / NVDEC / D3D11VA"),
("preferences.backend.placeholder", "选择一个后端…"),
("preferences.language", "语言"),
("preferences.language.placeholder", "选择语言…"),