diff --git a/Dockerfile b/Dockerfile
index 49897003..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 ${PHP_V} ${PACKAGES} && \
+ shadow sqlite redis tzdata gettext fcgi ca-certificates nss mailcap libcap fontconfig ttf-freefont font-noto \
+ 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.
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 @@
-
+
Advanced settings
@@ -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..9aec55a7 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[] = $params->get('pix_fmt', '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);