From ae001d9ff3ae70e7dc2f0b4aa41bb6bc30dfce44 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Fri, 14 Aug 2026 21:49:32 +0200 Subject: [PATCH] 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 --- .github/workflows/build.yml | 2 +- .github/workflows/ci.yml | 2 +- linux/packaging/build-libmpv.sh | 56 ++++++++++++++++++++++++++-- linux/packaging/build-libmpv_test.sh | 38 ++++++++++++++++++- linux/packaging/native-inputs.json | 9 +++++ scripts/verify_runtime_inputs.py | 11 ++++-- 6 files changed, 108 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a4a8d5336..5ce6cfb63 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 575d9a3dc..a333277f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/linux/packaging/build-libmpv.sh b/linux/packaging/build-libmpv.sh index 50f104de6..b511c5c01 100755 --- a/linux/packaging/build-libmpv.sh +++ b/linux/packaging/build-libmpv.sh @@ -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 \ diff --git a/linux/packaging/build-libmpv_test.sh b/linux/packaging/build-libmpv_test.sh index 23396daab..e67bc3273 100755 --- a/linux/packaging/build-libmpv_test.sh +++ b/linux/packaging/build-libmpv_test.sh @@ -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" < 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',