From 1707d641ab17cb48581bfde221aca3953020e9b9 Mon Sep 17 00:00:00 2001 From: "Abdulmhsen B. A. A." Date: Tue, 13 Aug 2024 13:49:39 +0300 Subject: [PATCH 1/4] Added basic hwaccel support for player. --- frontend/pages/play/[id].vue | 90 ++++++++++++++++++++++++++++++++++-- src/API/History/Index.php | 39 ++++++++++++++++ src/API/Player/Segments.php | 63 ++++++++++++++++++++----- 3 files changed, 176 insertions(+), 16 deletions(-) diff --git a/frontend/pages/play/[id].vue b/frontend/pages/play/[id].vue index 2f6c2b64..30da1fe9 100644 --- a/frontend/pages/play/[id].vue +++ b/frontend/pages/play/[id].vue @@ -139,14 +139,70 @@

We recommend using the burn subtitle function only when you are using a picture based subtitles, - Text based subtitles are able to be selected and converted on the fly using the player. + Text based subtitles are able to be selected and converted on the fly using the player. We plan to + support direct play of compatible streams in the future.

+ +
- @@ -205,12 +261,20 @@ const isLoading = ref(false) const isPlaying = ref(false) const isGenerating = ref(false) const playUrl = ref('') -const showAdvanced = ref(false) +const showAdvanced = useStorage('play_showAdvanced', false) const show_page_tips = useStorage('show_page_tips', true) +const video_codec = useStorage('play_vcodec', 'libx264') +const vaapi_device = useStorage('play_vaapi_device', '') +const session_debug = useStorage('play_debug', false) + const config = ref({ path: '', audio: '', subtitle: '', + video_codec: video_codec, + vaapi_device: vaapi_device, + hwaccel: false, + debug: session_debug, }) const selectedItem = ref({}) @@ -242,9 +306,16 @@ const generateToken = async () => { path: config.value.path, config: { audio: config.value.audio, + video_codec: config.value.video_codec, + hwaccel: config.value.hwaccel, + debug: Boolean(config.value.debug), } }; + if (config.value.vaapi_device && 'h264_vaapi' === config.value.video_codec) { + userConfig.config.vaapi_device = config.value.vaapi_device + } + if (config.value.subtitle) { // -- check if the value is number it's internal subtitle if (String(config.value.subtitle).match(/^\d+$/)) { @@ -373,7 +444,18 @@ onMounted(async () => { playUrl.value = `${useStorage('api_url', '').value}${useStorage('api_path', '/v1/api').value}/player/playlist/${route.query.token}/master.m3u8` isPlaying.value = true } + updateHwAccel(video_codec.value) }) +const updateHwAccel = codec => { + const codecInfo = item.value.hardware.codecs.filter(c => c.codec === codec); + console.log(codecInfo) + if (codecInfo.length < 1) { + config.value.hwaccel = false + return; + } + config.value.hwaccel = Boolean(codecInfo[0].hwaccel) +} + onUnmounted(() => window.removeEventListener('popstate', onPopState)) diff --git a/src/API/History/Index.php b/src/API/History/Index.php index f42f34cc..d25b3453 100644 --- a/src/API/History/Index.php +++ b/src/API/History/Index.php @@ -23,6 +23,7 @@ use Psr\Http\Message\ServerRequestInterface as iRequest; use Psr\SimpleCache\CacheInterface as iCache; use RuntimeException; use SplFileInfo; +use Throwable; final class Index { @@ -506,6 +507,44 @@ final class Index } $entity['files'] = $ffprobe; + + $entity['hardware'] = [ + 'codecs' => [ + [ + 'codec' => 'libx264', + 'name' => 'H.264 (CPU) (All)', + 'hwaccel' => false, + ], + [ + 'codec' => 'h264_vaapi', + 'name' => 'H.264 (VA-API) (VAAPI)', + 'hwaccel' => true, + ], + [ + 'codec' => 'h264_nvenc', + 'name' => 'H.264 (NVENC) (Nvidia)', + 'hwaccel' => true, + ], + [ + 'codec' => 'h264_qsv', + 'name' => 'H.264 (QSV) (Intel)', + 'hwaccel' => true, + ], + ], + 'devices' => [], + ]; + + if (is_dir('/dev/dri/') && is_readable('/dev/dri/')) { + try { + foreach (scandir('/dev/dri') as $dev) { + if (false === str_starts_with($dev, 'render')) { + continue; + } + $entity['hardware']['devices'][] = '/dev/dri/' . $dev; + } + } catch (Throwable) { + } + } } return api_response(Status::OK, $entity); diff --git a/src/API/Player/Segments.php b/src/API/Player/Segments.php index 3a82a1e3..0e1f8452 100644 --- a/src/API/Player/Segments.php +++ b/src/API/Player/Segments.php @@ -91,10 +91,11 @@ readonly class Segments $hwaccel = (bool)$params->get('hwaccel', false); $vaapi_device = $params->get('vaapi_device', '/dev/dri/renderD128'); $vCodec = $params->get('video_codec', $hwaccel ? 'h264_vaapi' : 'libx264'); - $isIntel = $hwaccel && 'h264_vaapi' === $vCodec; + $isVAAPI = $hwaccel && 'h264_vaapi' === $vCodec; + $isQSV = $hwaccel && 'h264_qsv' === $vCodec; $segmentSize = number_format((int)$params->get('segment_size', Playlist::SEGMENT_DUR), 6); - if ($hwaccel && false === file_exists($vaapi_device)) { + if ($isVAAPI && false === file_exists($vaapi_device)) { return api_error(r("VAAPI device '{device}' not found.", ['device' => $vaapi_device]), Status::BAD_REQUEST); } @@ -146,7 +147,17 @@ readonly class Segments } $cmd[] = '-copyts'; - if ($isIntel) { + + if ($isQSV) { + $cmd[] = '-hwaccel'; + $cmd[] = 'qsv'; + if ($overlay) { + $cmd[] = '-hwaccel_output_format'; + $cmd[] = 'qsv'; + } + } + + if ($isVAAPI) { $cmd[] = '-hwaccel'; $cmd[] = 'vaapi'; $cmd[] = '-vaapi_device'; @@ -167,14 +178,14 @@ readonly class Segments $cmd[] = '-1'; $cmd[] = '-pix_fmt'; - $cmd[] = $isIntel ? 'vaapi_vld' : 'yuv420p'; + $cmd[] = $isVAAPI ? 'vaapi_vld' : 'yuv420p'; $cmd[] = '-g'; $cmd[] = '52'; if ($overlay && empty($external) && null !== $subtitle) { $cmd[] = '-filter_complex'; - if ($isIntel) { + if ($isVAAPI) { $cmd[] = "[0:0]hwdownload,format=nv12[base];[base][0:" . $subtitle . "]overlay[v];[v]hwupload[k]"; $cmd[] = '-map'; $cmd[] = '[k]'; @@ -190,7 +201,7 @@ readonly class Segments $cmd[] = '-strict'; $cmd[] = '-2'; - if (empty($external) && $isIntel) { + if (empty($external) && $isVAAPI) { $cmd[] = '-vf'; $cmd[] = 'format=nv12,hwupload'; } @@ -237,7 +248,7 @@ readonly class Segments symlink($external, $tmpSubFile); } $cmd[] = '-vf'; - $cmd[] = "subtitles={$tmpSubFile}" . ($isIntel ? ',format=nv12,hwupload' : ''); + $cmd[] = "subtitles={$tmpSubFile}" . ($isVAAPI ? ',format=nv12,hwupload' : ''); } elseif (null !== $subtitle && !$overlay) { $subStreamIndex = (int)$subIndex[$subtitle]; $tmpSubFile = r("{path}/t-{name}-internal-sub-{index}.{type}", [ @@ -254,7 +265,7 @@ readonly class Segments ); $cmd[] = '-vf'; - $cmd[] = "subtitles={$streamLink}" . ($isIntel ? ',format=nv12,hwupload' : ''); + $cmd[] = "subtitles={$streamLink}" . ($isVAAPI ? ',format=nv12,hwupload' : ''); } else { $cmd[] = '-sn'; } @@ -291,9 +302,26 @@ readonly class Segments ] ); - return api_error('Failed to generate segment. check logs.', Status::INTERNAL_SERVER_ERROR, headers: [ - 'X-Transcode-Time' => round($end - $start, 6), - ]); + $response = api_error( + r("Failed to generate segment. '{error}'", [ + 'error' => $debug ? $process->getErrorOutput() : 'check logs.', + ]), + Status::INTERNAL_SERVER_ERROR, + headers: [ + 'X-Transcode-Time' => round($end - $start, 6), + ] + ); + + if (true === $debug) { + $response = $response + ->withHeader('X-Ffmpeg', $process->getCommandLine()) + ->withHeader( + 'X-Transcode-Config', + json_encode($sConfig, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) + ); + } + + return $response; } $response = api_response(Status::OK, body: Stream::create($process->getOutput()), headers: [ @@ -330,7 +358,18 @@ readonly class Segments 'trace' => $e->getTrace(), ]); - return api_error('Failed to generate segment. check logs.', Status::INTERNAL_SERVER_ERROR); + $response = api_error('Failed to generate segment. check logs.', Status::INTERNAL_SERVER_ERROR); + if (true === $debug) { + if (isset($process)) { + $response = $response->withHeader('X-Ffmpeg', $process->getCommandLine()); + } + + $response = $response->withHeader( + 'X-Transcode-Config', + json_encode($sConfig, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) + ); + } + return $response; } finally { if (file_exists($tmpVidLock)) { unlink($tmpVidLock); From 01dec200e288b45675123c6d6e0e0b588ebb29f5 Mon Sep 17 00:00:00 2001 From: "Abdulmhsen B. A. A." Date: Tue, 13 Aug 2024 14:24:12 +0300 Subject: [PATCH 2/4] Added intel drivers to the container. --- Dockerfile | 4 ++-- src/API/Player/Segments.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 49897003..b5abafbc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,8 +31,8 @@ ENV FPM_PORT="${PHP_FPM_PORT}" RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone && \ for ext in ${PHP_PACKAGES}; do PACKAGES="${PACKAGES} ${PHP_V}-${ext}"; done && \ apk add --no-cache bash caddy icu-data-full nano curl procps net-tools iproute2 ffmpeg \ - shadow sqlite redis tzdata gettext fcgi ca-certificates nss mailcap libcap fontconfig \ - ttf-freefont font-noto terminus-font font-dejavu ${PHP_V} ${PACKAGES} && \ + shadow sqlite redis tzdata gettext fcgi ca-certificates nss mailcap libcap ${PHP_V} ${PACKAGES} \ + fontconfig ttf-freefont font-noto terminus-font font-dejavu libva-utils intel-media-driver && \ # Delete unused users change users group gid to allow unRaid users to use gid 100 deluser redis && deluser caddy && groupmod -g 1588787 users && \ # Create our own user. diff --git a/src/API/Player/Segments.php b/src/API/Player/Segments.php index 0e1f8452..9aec55a7 100644 --- a/src/API/Player/Segments.php +++ b/src/API/Player/Segments.php @@ -178,7 +178,7 @@ readonly class Segments $cmd[] = '-1'; $cmd[] = '-pix_fmt'; - $cmd[] = $isVAAPI ? 'vaapi_vld' : 'yuv420p'; + $cmd[] = $params->get('pix_fmt', 'yuv420p'); $cmd[] = '-g'; $cmd[] = '52'; From 06d965064f72611cdefbdea3f00e5eaf6c3825fd Mon Sep 17 00:00:00 2001 From: "Abdulmhsen B. A. A." Date: Tue, 13 Aug 2024 14:27:52 +0300 Subject: [PATCH 3/4] build fix. --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index b5abafbc..673be326 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,8 +31,8 @@ ENV FPM_PORT="${PHP_FPM_PORT}" RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone && \ for ext in ${PHP_PACKAGES}; do PACKAGES="${PACKAGES} ${PHP_V}-${ext}"; done && \ apk add --no-cache bash caddy icu-data-full nano curl procps net-tools iproute2 ffmpeg \ - shadow sqlite redis tzdata gettext fcgi ca-certificates nss mailcap libcap ${PHP_V} ${PACKAGES} \ - fontconfig ttf-freefont font-noto terminus-font font-dejavu libva-utils intel-media-driver && \ + shadow sqlite redis tzdata gettext fcgi ca-certificates nss mailcap libcap fontconfig ttf-freefont font-noto \ + terminus-font font-dejavu libva-utils intel-media-driver ${PHP_V} ${PACKAGES} && \ # Delete unused users change users group gid to allow unRaid users to use gid 100 deluser redis && deluser caddy && groupmod -g 1588787 users && \ # Create our own user. From 84508fc9c598a980fa7b4fc19dad72c49c19b165 Mon Sep 17 00:00:00 2001 From: "Abdulmhsen B. A. A." Date: Tue, 13 Aug 2024 14:46:39 +0300 Subject: [PATCH 4/4] Only install intel driver on x86_64 builds. --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 673be326..b6d66ec6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,9 +30,10 @@ ENV FPM_PORT="${PHP_FPM_PORT}" # RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone && \ for ext in ${PHP_PACKAGES}; do PACKAGES="${PACKAGES} ${PHP_V}-${ext}"; done && \ + ARCH=`uname -m` && if [ "${ARCH}" == "x86_64" ]; then PACKAGES="${PACKAGES} intel-media-driver"; fi && \ apk add --no-cache bash caddy icu-data-full nano curl procps net-tools iproute2 ffmpeg \ shadow sqlite redis tzdata gettext fcgi ca-certificates nss mailcap libcap fontconfig ttf-freefont font-noto \ - terminus-font font-dejavu libva-utils intel-media-driver ${PHP_V} ${PACKAGES} && \ + terminus-font font-dejavu libva-utils ${PHP_V} ${PACKAGES} && \ # Delete unused users change users group gid to allow unRaid users to use gid 100 deluser redis && deluser caddy && groupmod -g 1588787 users && \ # Create our own user.