fix(linux): restore VAAPI hardware decode and AV1 software fallback in the bundled libmpv

Hardware decoding stopped working for Linux users on 2.13.0 (Fedora 44
report): every source decodes in software, and AV1 plays black video with
audio. Two defects in the pinned libmpv build.

First, mpv's meson 'drm' feature silently disabled itself because the CI
builder lacks libdisplay-info, and every VAAPI path that does not depend
on a display server is derived from it: vaapi-copy's standalone render-node
device (the path 2.12.1 worked on) and the GL dmabuf interop for direct
vaapi. With only the Wayland VA provider compiled in, a machine whose
Wayland VA display fails to initialize has no fallback, and vaapi-copy
has an empty provider list - every source lands on software decoding.
Pin -Ddrm=enabled, -Dvaapi-drm=enabled, -Degl=enabled and
-Dvaapi-wayland=enabled, and add libdisplay-info-dev to the CI package
lists, so a missing piece fails the build instead of shipping silent
software decode.

Second, the bundled static FFmpeg has no AV1 software decoder: its native
av1 codec is hardware-accelerated only, so once hwdec fails there is no AV1
path at all - every packet errors, video hits EOF, the plane goes black
while audio keeps playing. Pin dav1d 1.5.4 (both VideoLAN remotes agree on
the tag object and root commit), build it static before ffmpeg, and pass
--enable-libdav1d.

The build-plan stub test now asserts the hwdec feature flags, the dav1d
static build, and ffmpeg's libdav1d. Verified in an ubuntu:24.04 container
with the production flag sets: meson reports drm, vaapi-drm, vaapi-wayland,
egl and dmabuf-interop-gl enabled, and ffmpeg configures CONFIG_LIBDAV1D=yes
with the AV1 VAAPI hwaccel.

close #1874
This commit is contained in:
edde746
2026-08-14 21:49:41 +02:00
parent 18c9f710bb
commit ae001d9ff3
6 changed files with 108 additions and 10 deletions
+1 -1
View File
@@ -36,7 +36,7 @@ env:
libharfbuzz-dev libepoxy-dev libegl-dev libgl-dev libgnutls28-dev libpipewire-0.3-dev
libva-dev libvdpau-dev libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxi-dev
libxss-dev libxpresent-dev libxkbcommon-dev libpulse-dev libdbus-1-dev libdrm-dev
libgbm-dev libwayland-dev wayland-protocols liblcms2-dev libmujs-dev liblua5.2-dev
libdisplay-info-dev libgbm-dev libwayland-dev wayland-protocols liblcms2-dev libmujs-dev liblua5.2-dev
ruby ruby-dev rubygems build-essential rpm libarchive-tools imagemagick libcurl4-openssl-dev
jobs:
+1 -1
View File
@@ -657,7 +657,7 @@ jobs:
libasound2-dev libass-dev libfreetype-dev libfontconfig-dev libfribidi-dev \
libharfbuzz-dev libegl-dev libgl-dev libgnutls28-dev libpipewire-0.3-dev \
libva-dev libxkbcommon-dev libpulse-dev libdbus-1-dev libdrm-dev \
libgbm-dev libwayland-dev wayland-protocols liblcms2-dev libmujs-dev \
libdisplay-info-dev libgbm-dev libwayland-dev wayland-protocols liblcms2-dev libmujs-dev \
liblua5.2-dev rpm libarchive-tools imagemagick ruby-dev build-essential
sudo gem install fpm --version 1.17.0 --no-document
+52 -4
View File
@@ -38,6 +38,11 @@ PY
FFMPEG_VERSION="$(manifest_value ffmpeg version)"
FFMPEG_URL="$(manifest_value ffmpeg url)"
FFMPEG_SHA256="$(manifest_value ffmpeg sha256)"
DAV1D_VERSION="$(manifest_value dav1d version)"
DAV1D_URL="$(manifest_value dav1d url)"
DAV1D_MIRROR="$(manifest_optional dav1d mirror)"
DAV1D_REF="$(manifest_value dav1d ref)"
DAV1D_COMMIT="$(manifest_value dav1d commit)"
SHADERC_VERSION="$(manifest_value shaderc version)"
SHADERC_URL="$(manifest_value shaderc url)"
SHADERC_REF="$(manifest_value shaderc ref)"
@@ -183,7 +188,37 @@ main() {
echo "==> Install prefix: $prefix"
echo ""
# ─── Step 1: ffmpeg (static libraries) ─────────────────────────────────────
# ─── Step 1: dav1d (static library) ────────────────────────────────────────
# The bundled ffmpeg has no AV1 software decoder: its native av1 decoder is
# hardware-accelerated only, and no libaom/libdav1d is linked in. When hwdec
# is unavailable or cannot serve the source (an AV1 file on a GPU without AV1
# decode), AV1 has no path at all - every packet fails, video hits EOF and
# the plane goes black while audio keeps playing. dav1d is the software
# floor under AV1, exactly as libass is for subtitles. It must come before
# ffmpeg, whose configure resolves --enable-libdav1d against dav1d's
# pkg-config file.
echo "==> Building dav1d $DAV1D_VERSION (static)..."
checkout_verified_ref \
"$DAV1D_URL" "$DAV1D_REF" "$DAV1D_COMMIT" \
"$srcdir/dav1d-v${DAV1D_VERSION}" "$DAV1D_MIRROR"
cd "dav1d-v${DAV1D_VERSION}"
meson setup build \
--prefix="$prefix" \
--default-library=static \
-Denable_tools=false \
-Denable_tests=false \
-Denable_examples=false \
-Denable_docs=false
ninja -C build -j"$jobs"
ninja -C build install
cd "$srcdir"
echo ""
echo "==> dav1d done."
echo ""
# ─── Step 2: ffmpeg (static libraries) ─────────────────────────────────────
echo "==> Building ffmpeg $FFMPEG_VERSION (static, decoder-only)..."
download_verified "$FFMPEG_URL" "$FFMPEG_SHA256" "$srcdir/ffmpeg.tar.xz"
tar -xJf "$srcdir/ffmpeg.tar.xz"
@@ -208,6 +243,7 @@ main() {
--enable-filter=aformat,aresample,format,null,scale \
--enable-gnutls \
--enable-vaapi \
--enable-libdav1d \
--disable-vdpau \
--disable-debug \
--disable-stripping
@@ -219,7 +255,7 @@ main() {
echo "==> ffmpeg done."
echo ""
# ─── Step 2: shaderc (static library) ───────────────────────────────────────
# ─── Step 3: shaderc (static library) ───────────────────────────────────────
echo "==> Building shaderc $SHADERC_VERSION (static)..."
checkout_verified_ref \
"$SHADERC_URL" "$SHADERC_REF" "$SHADERC_COMMIT" \
@@ -243,7 +279,7 @@ main() {
echo "==> shaderc done."
echo ""
# ─── Step 3: libplacebo (static library) ───────────────────────────────────
# ─── Step 4: libplacebo (static library) ───────────────────────────────────
echo "==> Building libplacebo $LIBPLACEBO_VERSION (static)..."
checkout_verified_ref \
"$LIBPLACEBO_URL" "$LIBPLACEBO_REF" "$LIBPLACEBO_COMMIT" \
@@ -266,7 +302,7 @@ main() {
echo "==> libplacebo done."
echo ""
# ─── Step 4: mpv (shared libmpv) ───────────────────────────────────────────
# ─── Step 5: mpv (shared libmpv) ───────────────────────────────────────────
echo "==> Building mpv $MPV_VERSION (shared libmpv only)..."
download_verified "$MPV_URL" "$MPV_SHA256" "$srcdir/mpv.tar.gz"
tar -xzf "$srcdir/mpv.tar.gz"
@@ -276,6 +312,14 @@ main() {
# MPV_RENDER_PARAM_WL_DISPLAY so VAAPI can find the device instead of falling
# back to software decoding. A libmpv built without Wayland cannot use that.
# VDPAU goes with X11 - it has no Wayland backend at all.
#
# drm/vaapi-drm/egl are pinned enabled, not left on auto: mpv's `drm` feature
# silently drops to disabled when libdisplay-info is missing, and every VAAPI
# path that does not depend on a display server - vaapi-copy's standalone
# render-node device and the GL dmabuf interop - is derived from it. Shipping
# that build quietly lands every source on software decoding (the 2.13.0
# Fedora report). Enabled means the configure fails when the pieces are
# absent instead of degrading in silence.
meson setup build \
--prefix="$prefix" \
-Dlibmpv=true \
@@ -289,7 +333,11 @@ main() {
-Dvulkan=disabled \
-Dd3d11=disabled \
-Dgl=enabled \
-Degl=enabled \
-Ddrm=enabled \
-Dvaapi=enabled \
-Dvaapi-drm=enabled \
-Dvaapi-wayland=enabled \
-Dalsa=enabled \
-Dpulse=enabled \
-Dpipewire=enabled \
+37 -1
View File
@@ -100,6 +100,7 @@ mkdir -p "$stub_bin" "$stub_extra" "$records" "$temporary/tmp"
# Deliberately unlike the pinned versions: every path below is derived from the
# manifest, so a stub that matched by accident would prove nothing.
ffmpeg_version="9.9.9"
dav1d_version="2.2.2"
shaderc_version="6.6.6"
libplacebo_version="7.7.7"
mpv_version="8.8.8"
@@ -172,6 +173,14 @@ git -C "$shaderc_repository" commit --quiet -m shaderc
git -C "$shaderc_repository" tag release
shaderc_commit="$(git -C "$shaderc_repository" rev-parse HEAD)"
dav1d_repository="$temporary/dav1d-source"
init_repository "$dav1d_repository"
printf 'stub dav1d\n' >"$dav1d_repository/meson.build"
git -C "$dav1d_repository" add meson.build
git -C "$dav1d_repository" commit --quiet -m dav1d
git -C "$dav1d_repository" tag release
dav1d_commit="$(git -C "$dav1d_repository" rev-parse HEAD)"
libplacebo_repository="$temporary/libplacebo-source"
init_repository "$libplacebo_repository"
printf 'stub libplacebo\n' >"$libplacebo_repository/meson.build"
@@ -189,6 +198,12 @@ cat >"$manifest" <<JSON
"url": "https://stub.invalid/ffmpeg.tar.xz",
"sha256": "$payload_sha256"
},
"dav1d": {
"version": "$dav1d_version",
"url": "file://$dav1d_repository",
"ref": "release",
"commit": "$dav1d_commit"
},
"shaderc": {
"version": "$shaderc_version",
"url": "file://$shaderc_repository",
@@ -255,11 +270,32 @@ assert_argument "$mpv_meson" "-Dwayland=enabled" "mpv's meson setup"
assert_argument "$mpv_meson" "-Dx11=disabled" "mpv's meson setup"
assert_argument "$mpv_meson" "-Dvaapi=enabled" "mpv's meson setup"
assert_argument "$mpv_meson" "-Dgl=enabled" "mpv's meson setup"
assert_argument "$mpv_meson" "-Degl=enabled" "mpv's meson setup"
# vaapi has to reach ffmpeg too, or mpv's hwdec has no decoder behind it.
# The DRM-derived providers are the ones that keep hardware decoding alive when
# the Wayland VA display cannot be had: vaapi-copy opens the render node on its
# own, and the GL dmabuf interop gives direct vaapi a display-independent
# path. mpv auto-disables the whole `drm` feature when libdisplay-info is
# missing, so these are pinned enabled and asserted here.
assert_argument "$mpv_meson" "-Ddrm=enabled" "mpv's meson setup"
assert_argument "$mpv_meson" "-Dvaapi-drm=enabled" "mpv's meson setup"
assert_argument "$mpv_meson" "-Dvaapi-wayland=enabled" "mpv's meson setup"
# vaapi has to reach ffmpeg too, or mpv's hwdec has no decoder behind it. And
# the bundled ffmpeg needs a software AV1 decoder: its native av1 codec is
# hardware-accelerated only, so without dav1d an AV1 source on a machine whose
# GPU cannot decode it fails every packet and plays black video with audio.
ffmpeg_configure="$(recorded_call configure "ffmpeg-$ffmpeg_version")" ||
fail "the build plan never configured ffmpeg"
assert_argument "$ffmpeg_configure" "--enable-vaapi" "ffmpeg's configure"
assert_argument "$ffmpeg_configure" "--enable-libdav1d" "ffmpeg's configure"
# dav1d must be built static so ffmpeg absorbs it into the bundled libmpv:
# a shared dav1d would have to travel in the bundle and be kept version-true
# with the host, which is exactly the coupling the rest of the plan avoids.
dav1d_meson="$(recorded_call meson "dav1d-v$dav1d_version")" ||
fail "the build plan never ran meson in the dav1d source tree"
assert_argument "$dav1d_meson" "--default-library=static" "dav1d's meson setup"
# libplacebo runs meson as well, and its call carries no Wayland flag: finding
# it separately is what proves the directory above really discriminated.
+9
View File
@@ -17,6 +17,15 @@
"sha256": "40973d44970dbc83ef302b0609f2e74982be2d85916dd2ee7472d30678a7abe6",
"provenance": "FFmpeg release archive verified against ffmpeg-7.1.tar.xz.asc with the official ffmpeg-devel.asc full fingerprint FCF986EA15E6E293A5644F10B4322F04D67658D8 before recording this digest."
},
"dav1d": {
"kind": "git",
"version": "1.5.4",
"url": "https://code.videolan.org/videolan/dav1d.git",
"mirror": "https://github.com/videolan/dav1d.git",
"ref": "1.5.4",
"commit": "54706fc6bc0cdecab7e9593974a4039cc038fca7",
"provenance": "Official VideoLAN GitLab tag record 1.5.4 dereferences to this root commit; upstream's own GitHub mirror (videolan/dav1d, not a fork) reports the same annotated tag object 191bdda98ec3c68137754dc97da1db34043d7cd4 dereferencing to that same commit. The tag is annotated by the dav1d maintainers but not GPG-verified by GitHub; the two independent remotes agreeing on the tag object and root commit is the pinning evidence. The mirror is consulted only after the primary fails, and the commit pin is verified identically either way."
},
"shaderc": {
"kind": "git",
"version": "2024.4",
+8 -3
View File
@@ -13,7 +13,7 @@ from typing import Any
HEX_256 = re.compile(r"^[0-9a-f]{64}$")
HEX_COMMIT = re.compile(r"^[0-9a-f]{40}$")
NATIVE_NAMES = {"ffmpeg", "shaderc", "libplacebo", "mpv", "simdutf"}
NATIVE_NAMES = {"dav1d", "ffmpeg", "shaderc", "libplacebo", "mpv", "simdutf"}
BINDING_ARTIFACTS = {
"pigeons/messages.dart",
"android/src/main/kotlin/dev/fluttercommunity/plus/wakelock/WakelockPlusMessages.g.kt",
@@ -103,8 +103,12 @@ def _validate_native(root: Path, errors: list[str]) -> None:
elif kind == "git":
ref = value.get("ref")
commit = value.get("commit")
if not isinstance(ref, str) or ref != f"v{version}":
errors.append(f"{label}.ref: must be v{version}")
# dav1d tags releases as bare versions ("1.5.4"); the other pinned
# Git inputs tag them "v{version}". Either way the ref is verified
# against the recorded commit, which is the actual pin.
expected_ref = version if name == "dav1d" else f"v{version}"
if not isinstance(ref, str) or ref != expected_ref:
errors.append(f"{label}.ref: must be {expected_ref}")
if not isinstance(commit, str) or HEX_COMMIT.fullmatch(commit) is None:
errors.append(f"{label}.commit: must be a lowercase full Git commit")
else:
@@ -139,6 +143,7 @@ def _validate_native(root: Path, errors: list[str]) -> None:
"native-inputs.json",
'download_verified "$FFMPEG_URL" "$FFMPEG_SHA256"',
'download_verified "$MPV_URL" "$MPV_SHA256"',
'"$DAV1D_URL" "$DAV1D_REF" "$DAV1D_COMMIT"',
'"$SHADERC_URL" "$SHADERC_REF" "$SHADERC_COMMIT"',
'"$LIBPLACEBO_URL" "$LIBPLACEBO_REF" "$LIBPLACEBO_COMMIT"',
'git submodule update --init --recursive',