Files
lhns--steam-frame-nix/modules/jellyfin/check.nix
T
Pierre Kisters fd97347e18 Jellyfin: hardware video decoding (jellyfin.hardwareDecoding)
Jellyfin Desktop hard-sets mpv's hwdec=auto-copy, whose probe list leaves
out V4L2 M2M, and the Flatpak can't see the Frame's V4L2 decoder. An
LD_PRELOAD shim (preloaded straight from the store, only that path exposed
read-only) rewrites hwdec to v4l2m2m-copy,auto-copy; devices=all makes the
decoder visible. Overrides via nix-flatpak, or a home-manager-owned override
file without it.
2026-09-28 05:50:47 +02:00

71 lines
3.2 KiB
Nix

# Checks of the Jellyfin hardware decoding pieces: the mpv hwdec shim's ELF
# (libc only, no rpath, GLIBC <= 2.34) and rewriting (against a fake libmpv
# that prints what it receives).
{ pkgs }:
let
shim = pkgs.callPackage ./shim.nix { };
in
pkgs.runCommandCC "jellyfin-check" { nativeBuildInputs = [ pkgs.binutils ]; } ''
so=${shim}/lib/mpv-hwdec-shim.so
fail() { echo "FAIL: $*" >&2; exit 1; }
needed=$(readelf -dW $so | sed -n 's/.*(NEEDED).*\[\(.*\)\]/\1/p')
for lib in $needed; do
case $lib in libc.so.6|ld-linux*) ;; *) fail "needs $lib" ;; esac
done
! readelf -dW $so | grep -Eq '\((RPATH|RUNPATH)\)' || fail "has an rpath"
glibc=$(readelf -VW $so | grep -o 'GLIBC_[0-9.]*' | sort -uV | tail -1)
[ "$(printf '%s\n' "$glibc" GLIBC_2.34 | sort -V | tail -1)" = GLIBC_2.34 ] ||
fail "needs $glibc"
echo "ELF ok: NEEDED" $needed", newest $glibc"
cat > mpv.c <<'C'
#include <stdio.h>
#include <stdint.h>
typedef struct { union { char *string; } u; int format; } mpv_node;
static void show(const char *n, int f, void *d) {
printf("%s=%s\n", n, f == 1 ? *(char **)d : ((mpv_node *)d)->u.string);
}
int mpv_set_property(void *c, const char *n, int f, void *d) { show(n, f, d); return 0; }
int mpv_set_option(void *c, const char *n, int f, void *d) { show(n, f, d); return 0; }
int mpv_set_property_async(void *c, uint64_t u, const char *n, int f, void *d) { show(n, f, d); return 0; }
int mpv_set_property_string(void *c, const char *n, const char *d) { printf("%s=%s\n", n, d); return 0; }
int mpv_set_option_string(void *c, const char *n, const char *d) { printf("%s=%s\n", n, d); return 0; }
C
cat > app.c <<'C'
#include <stdint.h>
typedef struct { union { char *string; } u; int format; } mpv_node;
int mpv_set_property(void *, const char *, int, void *);
int mpv_set_option(void *, const char *, int, void *);
int mpv_set_property_async(void *, uint64_t, const char *, int, void *);
int mpv_set_property_string(void *, const char *, const char *);
int mpv_set_option_string(void *, const char *, const char *);
int main(void) {
char *s = "auto-copy", *no = "no";
mpv_node node = { { "auto" }, 1 };
mpv_set_property_string(0, "hwdec", "auto-copy");
mpv_set_option_string(0, "hwdec", "auto");
mpv_set_property(0, "hwdec", 1, &s);
mpv_set_option(0, "hwdec", 6, &node);
mpv_set_property_async(0, 0, "hwdec", 1, &s);
mpv_set_property(0, "hwdec", 1, &no);
mpv_set_property_string(0, "vo", "auto");
return 0;
}
C
$CC -shared -fPIC -o libmpv.so mpv.c
$CC -o app app.c -L. -lmpv -Wl,-rpath,$PWD
h=v4l2m2m-copy,auto-copy
expect="hwdec=$h hwdec=$h hwdec=$h hwdec=$h hwdec=$h hwdec=no vo=auto"
got=$(LD_PRELOAD=$so ./app 2> err)
[ "$(echo $got)" = "$expect" ] || fail "got: $(echo $got)"
[ "$(grep -c mpv-hwdec-shim: err)" = 1 ] || fail "logged: $(cat err)"
got=$(SFN_MPV_HWDEC=vaapi LD_PRELOAD=$so ./app 2> /dev/null)
[ "$(echo $got)" = "$(echo "$expect" | sed "s/$h/vaapi/g")" ] || fail "SFN_MPV_HWDEC: $(echo $got)"
# Processes without libmpv (preloaded all over the sandbox) are unaffected.
[ "$(LD_PRELOAD=$so ${pkgs.coreutils}/bin/echo ok)" = ok ] || fail "without libmpv"
echo "rewrite ok"
touch $out
''