From 6f97cfa72da2f9d781e82fc3b8f09c7b3cd41b6c Mon Sep 17 00:00:00 2001 From: Odilon Hugonnot Date: Fri, 29 May 2026 14:59:24 +0000 Subject: [PATCH] fix(transcode): only pick a GPU encoder when its device is actually present MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit detect_hw_encoder() in 'auto' mode picked nvenc as soon as ffmpeg *listed* h264_nvenc — even on a host with no NVIDIA GPU. The encoder is compiled into many ffmpeg builds, so on a GPU-less server transcoding failed at runtime with "[h264_nvenc] Cannot load libcuda.so.1 / Error initializing output stream", and the player showed "invalid video source". Observed in production (no GPU, ffmpeg shipping nvenc). Now nvenc requires a real NVIDIA device (/dev/nvidia0 or /dev/nvidiactl) and v4l2m2m requires /dev/video10 — mirroring the device check VAAPI already does. 'auto' falls back to software (libx264) when no usable device exists. Explicit FFMPEG_HW_ACCEL still forces a backend for non-standard setups. Added HwAccelTest::testGpuBackendChosenOnlyWhenDevicePresent locking the invariant. phpunit 442/887 green, PHPStan clean. Co-Authored-By: Claude Opus 4.8 --- functions.php | 13 +++++++++---- tests/HwAccelTest.php | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/functions.php b/functions.php index 78ce07c..c534278 100644 --- a/functions.php +++ b/functions.php @@ -743,15 +743,20 @@ function detect_hw_encoder(): string { } } - // NVENC (Nvidia GPU) + // NVENC (Nvidia GPU) — h264_nvenc peut être compilé dans ffmpeg SANS qu'aucun + // GPU ne soit présent. Exiger un device NVIDIA réel, sinon on choisirait nvenc + // sur une machine sans carte → échec runtime "Cannot load libcuda.so.1" et + // lecture cassée. (VAAPI fait déjà cette vérif de device plus haut.) $encoders = shell_exec('ffmpeg -encoders 2>/dev/null') ?? ''; - if (str_contains($encoders, 'h264_nvenc')) { + $hasNvidiaDevice = file_exists('/dev/nvidia0') || file_exists('/dev/nvidiactl'); + if ($hasNvidiaDevice && str_contains($encoders, 'h264_nvenc')) { $cached = 'nvenc'; return $cached; } - // V4L2 M2M (Raspberry Pi 4) - if (file_exists('/dev/video10') || str_contains($encoders, 'h264_v4l2m2m')) { + // V4L2 M2M (Raspberry Pi 4) — pareil : exiger le device ET l'encodeur, pas l'un + // ou l'autre (un ffmpeg complet liste h264_v4l2m2m même hors Pi). + if (file_exists('/dev/video10') && str_contains($encoders, 'h264_v4l2m2m')) { $cached = 'v4l2m2m'; return $cached; } diff --git a/tests/HwAccelTest.php b/tests/HwAccelTest.php index 4416445..704a062 100644 --- a/tests/HwAccelTest.php +++ b/tests/HwAccelTest.php @@ -28,6 +28,27 @@ public function testDetectHwEncoderReturnsNoneWhenNoGpu(): void $this->assertNotEmpty($result); } + /** + * En mode auto, un backend GPU ne doit être choisi QUE si son device existe — + * sinon ffmpeg liste l'encodeur (compilé) mais l'encodage plante au runtime + * (ex. nvenc sans GPU : "Cannot load libcuda.so.1"). Régression réelle observée + * en prod sur un serveur sans carte dont le ffmpeg embarquait nvenc. + */ + public function testGpuBackendChosenOnlyWhenDevicePresent(): void + { + $result = detect_hw_encoder(); + if ($result === 'nvenc') { + $this->assertTrue( + file_exists('/dev/nvidia0') || file_exists('/dev/nvidiactl'), + 'nvenc ne doit être choisi que si un device NVIDIA est présent' + ); + } elseif ($result === 'v4l2m2m') { + $this->assertTrue(file_exists('/dev/video10'), 'v4l2m2m exige /dev/video10'); + } else { + $this->assertContains($result, ['none', 'vaapi']); + } + } + // ── buildFfmpegCodecArgs with hardware encoders ────────────────────── public function testBuildFfmpegCodecArgsSoftware(): void