aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2026-08-05 02:06:11 +0200
committerdec05eba <dec05eba@protonmail.com>2026-08-05 02:06:11 +0200
commitb9d252214a46aa64047893cb557f060a8978b006 (patch)
tree63bafc0eb9d2a9b6bc23aa7242e81e0cac86794c
parent1869cf30519fc61c7f9852f8f25f8b1c2b2e8c3b (diff)
Add option to statically compile ffmpeg from source
-rw-r--r--.gitignore4
-rw-r--r--README.md8
-rwxr-xr-xextra/build_ffmpeg.sh233
-rw-r--r--meson.build5
-rw-r--r--meson_options.txt1
-rw-r--r--subprojects/ffmpeg.wrap7
-rw-r--r--subprojects/nv-codec-headers.wrap6
-rw-r--r--subprojects/openssl.wrap6
-rw-r--r--subprojects/opus.wrap6
-rw-r--r--subprojects/packagefiles/ffmpeg-nvenc-runtime-api-version.patch837
-rw-r--r--subprojects/packagefiles/ffmpeg/meson.build35
-rw-r--r--subprojects/packagefiles/nv-codec-headers/meson.build3
-rw-r--r--subprojects/packagefiles/openssl/meson.build3
-rw-r--r--subprojects/packagefiles/opus/meson.build3
-rw-r--r--subprojects/packagefiles/srt/meson.build3
-rw-r--r--subprojects/packagefiles/x264/meson.build3
-rw-r--r--subprojects/srt.wrap6
-rw-r--r--subprojects/x264.wrap6
18 files changed, 1175 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index 89ee27d..510361f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,7 @@ gsr-kms-server
*.jpg
*.jpeg
*.png
+
+subprojects/*
+!subprojects/*.wrap
+!subprojects/packagefiles/
diff --git a/README.md b/README.md
index 26e59e5..9403e23 100644
--- a/README.md
+++ b/README.md
@@ -75,6 +75,14 @@ When building GPU Screen Recorder with portal support (`-Dportal=true` meson opt
* libdbus
* libpipewire (and libspa which is usually part of libpipewire)
+## Building with a statically linked ffmpeg
+The `-Dffmpeg_static=true` meson option (disabled by default) downloads ffmpeg and the libraries that ffmpeg depends on (x264, opus, openssl, srt and nv-codec-headers), builds them from source with lto and only the components that GPU Screen Recorder uses and then links them statically into GPU Screen Recorder.
+This removes the runtime dependency on the system ffmpeg and on all of those libraries. `make`, `nasm`, `cmake` and `perl` are needed to build them and the first `meson setup` takes a few minutes longer because of it.
+
+The only libraries that ffmpeg is still dynamically linked to are libva, libdrm (which GPU Screen Recorder also uses directly) and the c/c++ runtime.
+
+This uses the same ffmpeg version as the GPU Screen Recorder flatpak, including the patch that makes nvenc negotiate the NVENC API version at runtime instead of at build time, which makes nvidia video encoding work on drivers down to 470.57.02 (Kepler) while still supporting AV1 and the RTX 5000 series.
+
## Runtime dependencies
* libglvnd (which provides libgl, libglx and libegl) is needed. Your system needs to support at least OpenGL ES 3.0 (released in 2012)
* vulkan-icd-loader (which provides the runtime vulkan library. This is only needed if vulkan video encoding option is used)
diff --git a/extra/build_ffmpeg.sh b/extra/build_ffmpeg.sh
new file mode 100755
index 0000000..06c3344
--- /dev/null
+++ b/extra/build_ffmpeg.sh
@@ -0,0 +1,233 @@
+#!/bin/sh
+
+# Builds ffmpeg, or one of the libraries that ffmpeg depends on, from source as a
+# static library with lto. Only the components that gpu-screen-recorder uses are enabled.
+# This is run by meson when the "ffmpeg_static" option is enabled.
+
+set -eu
+
+if [ $# -ne 5 ]; then
+ echo "usage: $0 <library> <source-dir> <build-dir> <install-prefix> <c-compiler>" >&2
+ exit 1
+fi
+
+library=$1
+source_dir=$2
+build_dir=$3
+prefix=$4
+compiler=$5
+log_file=$build_dir/build.log
+stamp_file=$prefix/$library.stamp
+jobs=$(nproc 2>/dev/null || echo 4)
+
+export CC=$compiler
+export PKG_CONFIG_PATH=$prefix/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}
+
+require_program() {
+ if ! command -v "$1" >/dev/null 2>&1; then
+ echo "error: $1 is required to build $library from source" >&2
+ exit 1
+ fi
+}
+
+require_program pkg-config
+require_program make
+
+case $library in
+ nv-codec-headers)
+ set -- PREFIX="$prefix"
+ build() {
+ make -C "$source_dir" "$@" install
+ }
+ ;;
+ x264)
+ require_program nasm
+ set -- --prefix="$prefix" \
+ --enable-static \
+ --enable-pic \
+ --enable-lto \
+ --disable-cli \
+ --disable-opencl
+ build() {
+ "$source_dir/configure" "$@"
+ make -j"$jobs"
+ make install
+ }
+ ;;
+ opus)
+ set -- --prefix="$prefix" \
+ --disable-shared \
+ --enable-static \
+ --with-pic \
+ --disable-doc \
+ --disable-extra-programs \
+ CFLAGS="-O3 -flto -fPIC" \
+ LDFLAGS="-flto"
+ build() {
+ "$source_dir/configure" "$@"
+ make -j"$jobs"
+ make install
+ }
+ ;;
+ openssl)
+ require_program perl
+ # The certificate directory of the system openssl is used so that certificate
+ # verification keeps working the same way as it does with the system openssl.
+ openssl_dir=$(openssl version -d 2>/dev/null | sed -n 's/^OPENSSLDIR: "\(.*\)"$/\1/p')
+ if [ -z "$openssl_dir" ]; then
+ openssl_dir=/etc/ssl
+ fi
+ set -- --prefix="$prefix" \
+ --libdir=lib \
+ --openssldir="$openssl_dir" \
+ no-shared \
+ no-apps \
+ no-docs \
+ no-tests \
+ no-legacy \
+ no-engine \
+ no-comp \
+ no-quic \
+ no-ssl3 \
+ no-weak-ssl-ciphers \
+ no-cms \
+ no-ct \
+ no-ts \
+ no-ocsp \
+ no-srp \
+ no-psk \
+ no-dsa \
+ no-ec2m \
+ no-gost \
+ no-idea \
+ no-md2 \
+ no-md4 \
+ no-mdc2 \
+ no-rc2 \
+ no-rc4 \
+ no-rc5 \
+ no-bf \
+ no-cast \
+ no-seed \
+ no-camellia \
+ no-whirlpool \
+ no-rmd160 \
+ no-sm2 \
+ no-sm3 \
+ no-sm4 \
+ no-siphash \
+ -O3 -flto -fPIC
+ build() {
+ "$source_dir/Configure" "$@"
+ make -j"$jobs"
+ make install_sw
+ }
+ ;;
+ srt)
+ require_program cmake
+ set -- -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_INSTALL_PREFIX="$prefix" \
+ -DCMAKE_INSTALL_LIBDIR=lib \
+ -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \
+ -DCMAKE_POSITION_INDEPENDENT_CODE=ON \
+ -DENABLE_SHARED=OFF \
+ -DENABLE_STATIC=ON \
+ -DENABLE_APPS=OFF \
+ -DENABLE_EXAMPLES=OFF \
+ -DENABLE_TESTING=OFF \
+ -DENABLE_UNITTESTS=OFF \
+ -DUSE_ENCLIB=openssl \
+ -DOPENSSL_ROOT_DIR="$prefix" \
+ -DOPENSSL_USE_STATIC_LIBS=ON
+ build() {
+ cmake -S "$source_dir" -B "$build_dir" "$@"
+ cmake --build "$build_dir" -j"$jobs"
+ cmake --install "$build_dir"
+ }
+ ;;
+ ffmpeg)
+ require_program nasm
+
+ encoders=aac,flac,libx264,libopus,h264_nvenc,hevc_nvenc,av1_nvenc,h264_vaapi,hevc_vaapi,vp8_vaapi,vp9_vaapi,h264_vulkan,hevc_vulkan
+ muxers=mp4,mov,matroska,webm,flv,mpegts,hls,whip
+ protocols=file,pipe,tcp,udp,http,https,tls,rtmp,rtmps,libsrt
+ filters=abuffer,abuffersink,amix,aresample,aformat,anull
+
+ if pkg-config --atleast-version=1.16 libva; then
+ encoders=$encoders,av1_vaapi
+ else
+ echo "warning: libva is too old for av1 vaapi encoding" >&2
+ fi
+
+ if pkg-config --atleast-version=1.4.317 vulkan; then
+ encoders=$encoders,av1_vulkan
+ else
+ echo "warning: the vulkan headers are too old for av1 vulkan encoding" >&2
+ fi
+
+ # ffmpeg ignores $CC and the same compiler as gpu-screen-recorder has to be
+ # used because lto objects are not compatible between compilers.
+ set -- --prefix="$prefix" \
+ --cc="$compiler" \
+ --pkg-config-flags=--static \
+ --disable-debug \
+ --disable-everything \
+ --disable-autodetect \
+ --disable-programs \
+ --disable-doc \
+ --disable-avdevice \
+ --disable-swscale \
+ --disable-shared \
+ --enable-static \
+ --enable-pic \
+ --enable-lto \
+ --enable-gpl \
+ --enable-version3 \
+ --enable-network \
+ --enable-encoder="$encoders" \
+ --enable-muxer="$muxers" \
+ --enable-protocol="$protocols" \
+ --enable-filter="$filters" \
+ --enable-bsf=extract_extradata \
+ --enable-libx264 \
+ --enable-libopus \
+ --enable-libsrt \
+ --enable-openssl \
+ --enable-ffnvcodec \
+ --enable-nvenc \
+ --enable-cuda \
+ --enable-vaapi \
+ --enable-vulkan
+ build() {
+ "$source_dir/configure" "$@"
+ make -j"$jobs"
+ make install
+ }
+ ;;
+ *)
+ echo "error: unknown library $library" >&2
+ exit 1
+ ;;
+esac
+
+# Rebuilding takes a long time, only do it when the source or the configuration changed.
+stamp="$source_dir $compiler $*"
+if [ -f "$stamp_file" ] && [ "$(cat "$stamp_file")" = "$stamp" ]; then
+ exit 0
+fi
+
+rm -rf "$build_dir"
+mkdir -p "$build_dir"
+
+echo "Building $library, this takes a while. Output is written to $log_file" >&2
+
+(
+ cd "$build_dir"
+ build "$@"
+) >"$log_file" 2>&1 || {
+ echo "error: failed to build $library, see $log_file for details:" >&2
+ tail -n 30 "$log_file" >&2
+ exit 1
+}
+
+printf '%s' "$stamp" > "$stamp_file"
diff --git a/meson.build b/meson.build
index 8e9d8a4..0f7ded6 100644
--- a/meson.build
+++ b/meson.build
@@ -73,6 +73,11 @@ src += protocol_src
cc = meson.get_compiler('c')
m_dep = cc.find_library('m', required : true)
+# Overrides the libav* dependencies below with a statically linked ffmpeg built from source.
+if get_option('ffmpeg_static') == true
+ subproject('ffmpeg')
+endif
+
dep = [
dependency('threads'),
m_dep,
diff --git a/meson_options.txt b/meson_options.txt
index d50e2db..23751b6 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -4,3 +4,4 @@ option('nvidia_suspend_fix', type : 'boolean', value : true, description : 'Inst
option('portal', type : 'boolean', value : true, description : 'Build with support for xdg desktop portal ScreenCast capture (wayland only) (-w portal option). Requires pipewire')
option('app_audio', type : 'boolean', value : true, description : 'Build with support for recording a single audio source (-a app: option). Requires pipewire')
option('plugin_examples', type : 'boolean', value : false, description : 'Build plugin examples')
+option('ffmpeg_static', type : 'boolean', value : false, description : 'Download and build a minimal ffmpeg and the libraries it depends on from source with lto and link them statically into gpu-screen-recorder instead of using the system ffmpeg. Requires make, nasm, cmake and perl')
diff --git a/subprojects/ffmpeg.wrap b/subprojects/ffmpeg.wrap
new file mode 100644
index 0000000..56e8e65
--- /dev/null
+++ b/subprojects/ffmpeg.wrap
@@ -0,0 +1,7 @@
+[wrap-file]
+directory = ffmpeg-8.1
+source_url = https://ffmpeg.org/releases/ffmpeg-8.1.tar.xz
+source_filename = ffmpeg-8.1.tar.xz
+source_hash = b072aed6871998cce9b36e7774033105ca29e33632be5b6347f3206898e0756a
+patch_directory = ffmpeg
+diff_files = ffmpeg-nvenc-runtime-api-version.patch
diff --git a/subprojects/nv-codec-headers.wrap b/subprojects/nv-codec-headers.wrap
new file mode 100644
index 0000000..e3b36de
--- /dev/null
+++ b/subprojects/nv-codec-headers.wrap
@@ -0,0 +1,6 @@
+[wrap-file]
+directory = nv-codec-headers-n13.0.19.0
+source_url = https://github.com/FFmpeg/nv-codec-headers/archive/refs/tags/n13.0.19.0.tar.gz
+source_filename = nv-codec-headers-13.0.19.0.tar.gz
+source_hash = 86d15d1a7c0ac73a0eafdfc57bebfeba7da8264595bf531cf4d8db1c22940116
+patch_directory = nv-codec-headers
diff --git a/subprojects/openssl.wrap b/subprojects/openssl.wrap
new file mode 100644
index 0000000..fe386af
--- /dev/null
+++ b/subprojects/openssl.wrap
@@ -0,0 +1,6 @@
+[wrap-file]
+directory = openssl-3.6.3
+source_url = https://github.com/openssl/openssl/releases/download/openssl-3.6.3/openssl-3.6.3.tar.gz
+source_filename = openssl-3.6.3.tar.gz
+source_hash = 243a86649cf6f23eeb6a2ff2456e09e5d77dd9018a54d3d96b0c6bdd6ba6c7f1
+patch_directory = openssl
diff --git a/subprojects/opus.wrap b/subprojects/opus.wrap
new file mode 100644
index 0000000..af08d8b
--- /dev/null
+++ b/subprojects/opus.wrap
@@ -0,0 +1,6 @@
+[wrap-file]
+directory = opus-1.6.1
+source_url = https://downloads.xiph.org/releases/opus/opus-1.6.1.tar.gz
+source_filename = opus-1.6.1.tar.gz
+source_hash = 6ffcb593207be92584df15b32466ed64bbec99109f007c82205f0194572411a1
+patch_directory = opus
diff --git a/subprojects/packagefiles/ffmpeg-nvenc-runtime-api-version.patch b/subprojects/packagefiles/ffmpeg-nvenc-runtime-api-version.patch
new file mode 100644
index 0000000..53c8419
--- /dev/null
+++ b/subprojects/packagefiles/ffmpeg-nvenc-runtime-api-version.patch
@@ -0,0 +1,837 @@
+diff --git a/libavcodec/nvenc.c b/libavcodec/nvenc.c
+index 392230526d..ba26580acb 100644
+--- a/libavcodec/nvenc.c
++++ b/libavcodec/nvenc.c
+@@ -263,7 +263,15 @@ static void nvenc_map_preset(NvencContext *ctx)
+
+ static void nvenc_print_driver_requirement(AVCodecContext *avctx, int level)
+ {
+-#if NVENCAPI_CHECK_VERSION(13, 1)
++/* With runtime API version negotiation the build-time header version no
++ * longer dictates the required driver; the runtime floor is API 11.1. */
++#if 1
++# if defined(_WIN32) || defined(__CYGWIN__)
++ const char *minver = "471.41";
++# else
++ const char *minver = "470.57.02";
++# endif
++#elif NVENCAPI_CHECK_VERSION(13, 1)
+ const char *minver = "(unknown)";
+ #elif NVENCAPI_CHECK_VERSION(13, 0)
+ const char *minver = "570.0";
+@@ -347,6 +355,193 @@ static void nvenc_print_driver_requirement(AVCodecContext *avctx, int level)
+ #define to_nv_color_trc(n) (uint32_t)(n)
+ #endif
+
++/*
++ * Runtime NVENC API version negotiation, see nvenc.h.
++ *
++ * Each row holds the exact NV_ENC_*_VER constants of one SDK's nvEncodeAPI.h.
++ * A struct version word is composed as
++ * major | (minor << 24) | (struct_revision << 16) | (0x7 << 28)
++ * optionally OR'd with (1u << 31); the driver validates the whole word, so
++ * presenting a row of these values is indistinguishable from an FFmpeg that
++ * was actually compiled against that SDK's headers.
++ */
++#define NVENC_API_WORD(major, minor) \
++ ((uint32_t)(major) | ((uint32_t)(minor) << 24))
++#define NVENC_STRUCT_VER(major, minor, rev) \
++ (NVENC_API_WORD(major, minor) | ((uint32_t)(rev) << 16) | (0x7u << 28))
++#define NVENC_STRUCT_VER_RES(major, minor, rev) \
++ (NVENC_STRUCT_VER(major, minor, rev) | (1u << 31))
++
++// Newest first; negotiation picks the first row the driver supports.
++static const NvencApiVersion nvenc_api_versions[] = {
++ { // SDK 13.0, driver >= 570.0 (Linux) / 570.0 (Windows)
++ .packed = NVENC_API_PACKED(13, 0),
++ .api = NVENC_API_WORD(13, 0),
++ .function_list = NVENC_STRUCT_VER(13, 0, 2),
++ .open_session_params = NVENC_STRUCT_VER(13, 0, 1),
++ .caps_param = NVENC_STRUCT_VER(13, 0, 1),
++ .initialize_params = NVENC_STRUCT_VER_RES(13, 0, 7),
++ .config = NVENC_STRUCT_VER_RES(13, 0, 9),
++ .preset_config = NVENC_STRUCT_VER_RES(13, 0, 5),
++ .pic_params = NVENC_STRUCT_VER_RES(13, 0, 7),
++ .lock_bitstream = NVENC_STRUCT_VER_RES(13, 0, 2),
++ .lock_input_buffer = NVENC_STRUCT_VER(13, 0, 1),
++ .map_input_resource = NVENC_STRUCT_VER(13, 0, 4),
++ .register_resource = NVENC_STRUCT_VER(13, 0, 5),
++ .create_input_buffer = NVENC_STRUCT_VER(13, 0, 2),
++ .create_bitstream_buffer = NVENC_STRUCT_VER(13, 0, 1),
++ .reconfigure_params = NVENC_STRUCT_VER_RES(13, 0, 2),
++ .sequence_param_payload = NVENC_STRUCT_VER(13, 0, 1),
++ },
++ { // SDK 12.2, driver >= 550.54.14 (Linux) / 551.76 (Windows)
++ .packed = NVENC_API_PACKED(12, 2),
++ .api = NVENC_API_WORD(12, 2),
++ .function_list = NVENC_STRUCT_VER(12, 2, 2),
++ .open_session_params = NVENC_STRUCT_VER(12, 2, 1),
++ .caps_param = NVENC_STRUCT_VER(12, 2, 1),
++ .initialize_params = NVENC_STRUCT_VER_RES(12, 2, 7),
++ .config = NVENC_STRUCT_VER_RES(12, 2, 9),
++ .preset_config = NVENC_STRUCT_VER_RES(12, 2, 5),
++ .pic_params = NVENC_STRUCT_VER_RES(12, 2, 7),
++ .lock_bitstream = NVENC_STRUCT_VER_RES(12, 2, 2),
++ .lock_input_buffer = NVENC_STRUCT_VER(12, 2, 1),
++ .map_input_resource = NVENC_STRUCT_VER(12, 2, 4),
++ .register_resource = NVENC_STRUCT_VER(12, 2, 5),
++ .create_input_buffer = NVENC_STRUCT_VER(12, 2, 2),
++ .create_bitstream_buffer = NVENC_STRUCT_VER(12, 2, 1),
++ .reconfigure_params = NVENC_STRUCT_VER_RES(12, 2, 2),
++ .sequence_param_payload = NVENC_STRUCT_VER(12, 2, 1),
++ },
++ { // SDK 12.1, driver >= 530.41.03 (Linux) / 531.61 (Windows)
++ .packed = NVENC_API_PACKED(12, 1),
++ .api = NVENC_API_WORD(12, 1),
++ .function_list = NVENC_STRUCT_VER(12, 1, 2),
++ .open_session_params = NVENC_STRUCT_VER(12, 1, 1),
++ .caps_param = NVENC_STRUCT_VER(12, 1, 1),
++ .initialize_params = NVENC_STRUCT_VER_RES(12, 1, 6),
++ .config = NVENC_STRUCT_VER_RES(12, 1, 8),
++ .preset_config = NVENC_STRUCT_VER_RES(12, 1, 4),
++ .pic_params = NVENC_STRUCT_VER_RES(12, 1, 6),
++ .lock_bitstream = NVENC_STRUCT_VER_RES(12, 1, 1),
++ .lock_input_buffer = NVENC_STRUCT_VER(12, 1, 1),
++ .map_input_resource = NVENC_STRUCT_VER(12, 1, 4),
++ .register_resource = NVENC_STRUCT_VER(12, 1, 4),
++ .create_input_buffer = NVENC_STRUCT_VER(12, 1, 1),
++ .create_bitstream_buffer = NVENC_STRUCT_VER(12, 1, 1),
++ .reconfigure_params = NVENC_STRUCT_VER_RES(12, 1, 1),
++ .sequence_param_payload = NVENC_STRUCT_VER(12, 1, 1),
++ },
++ { // SDK 12.0, driver >= 520.56.06 (Linux) / 522.25 (Windows)
++ .packed = NVENC_API_PACKED(12, 0),
++ .api = NVENC_API_WORD(12, 0),
++ .function_list = NVENC_STRUCT_VER(12, 0, 2),
++ .open_session_params = NVENC_STRUCT_VER(12, 0, 1),
++ .caps_param = NVENC_STRUCT_VER(12, 0, 1),
++ .initialize_params = NVENC_STRUCT_VER_RES(12, 0, 5),
++ .config = NVENC_STRUCT_VER_RES(12, 0, 8),
++ .preset_config = NVENC_STRUCT_VER_RES(12, 0, 4),
++ .pic_params = NVENC_STRUCT_VER_RES(12, 0, 6),
++ .lock_bitstream = NVENC_STRUCT_VER(12, 0, 2),
++ .lock_input_buffer = NVENC_STRUCT_VER(12, 0, 1),
++ .map_input_resource = NVENC_STRUCT_VER(12, 0, 4),
++ .register_resource = NVENC_STRUCT_VER(12, 0, 4),
++ .create_input_buffer = NVENC_STRUCT_VER(12, 0, 1),
++ .create_bitstream_buffer = NVENC_STRUCT_VER(12, 0, 1),
++ .reconfigure_params = NVENC_STRUCT_VER_RES(12, 0, 1),
++ .sequence_param_payload = NVENC_STRUCT_VER(12, 0, 1),
++ },
++ { // SDK 11.1, driver >= 470.57.02 (Linux) / 471.41 (Windows) - Kepler's last branch
++ .packed = NVENC_API_PACKED(11, 1),
++ .api = NVENC_API_WORD(11, 1),
++ .function_list = NVENC_STRUCT_VER(11, 1, 2),
++ .open_session_params = NVENC_STRUCT_VER(11, 1, 1),
++ .caps_param = NVENC_STRUCT_VER(11, 1, 1),
++ .initialize_params = NVENC_STRUCT_VER_RES(11, 1, 5),
++ .config = NVENC_STRUCT_VER_RES(11, 1, 7),
++ .preset_config = NVENC_STRUCT_VER_RES(11, 1, 4),
++ .pic_params = NVENC_STRUCT_VER_RES(11, 1, 4),
++ .lock_bitstream = NVENC_STRUCT_VER(11, 1, 1),
++ .lock_input_buffer = NVENC_STRUCT_VER(11, 1, 1),
++ .map_input_resource = NVENC_STRUCT_VER(11, 1, 4),
++ .register_resource = NVENC_STRUCT_VER(11, 1, 3),
++ .create_input_buffer = NVENC_STRUCT_VER(11, 1, 1),
++ .create_bitstream_buffer = NVENC_STRUCT_VER(11, 1, 1),
++ .reconfigure_params = NVENC_STRUCT_VER_RES(11, 1, 1),
++ .sequence_param_payload = NVENC_STRUCT_VER(11, 1, 1),
++ },
++};
++
++/* The newest row must be exactly what this build would present anyway; if any
++ * of these fire, the headers changed and the whole table needs re-validation. */
++_Static_assert(NVENC_API_WORD(13, 0) == NVENCAPI_VERSION, "nvenc api version table outdated");
++_Static_assert(NVENC_STRUCT_VER(13, 0, 2) == NV_ENCODE_API_FUNCTION_LIST_VER, "nvenc api version table outdated");
++_Static_assert(NVENC_STRUCT_VER(13, 0, 1) == NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER, "nvenc api version table outdated");
++_Static_assert(NVENC_STRUCT_VER(13, 0, 1) == NV_ENC_CAPS_PARAM_VER, "nvenc api version table outdated");
++_Static_assert(NVENC_STRUCT_VER_RES(13, 0, 7) == NV_ENC_INITIALIZE_PARAMS_VER, "nvenc api version table outdated");
++_Static_assert(NVENC_STRUCT_VER_RES(13, 0, 9) == NV_ENC_CONFIG_VER, "nvenc api version table outdated");
++_Static_assert(NVENC_STRUCT_VER_RES(13, 0, 5) == NV_ENC_PRESET_CONFIG_VER, "nvenc api version table outdated");
++_Static_assert(NVENC_STRUCT_VER_RES(13, 0, 7) == NV_ENC_PIC_PARAMS_VER, "nvenc api version table outdated");
++_Static_assert(NVENC_STRUCT_VER_RES(13, 0, 2) == NV_ENC_LOCK_BITSTREAM_VER, "nvenc api version table outdated");
++_Static_assert(NVENC_STRUCT_VER(13, 0, 1) == NV_ENC_LOCK_INPUT_BUFFER_VER, "nvenc api version table outdated");
++_Static_assert(NVENC_STRUCT_VER(13, 0, 4) == NV_ENC_MAP_INPUT_RESOURCE_VER, "nvenc api version table outdated");
++_Static_assert(NVENC_STRUCT_VER(13, 0, 5) == NV_ENC_REGISTER_RESOURCE_VER, "nvenc api version table outdated");
++_Static_assert(NVENC_STRUCT_VER(13, 0, 2) == NV_ENC_CREATE_INPUT_BUFFER_VER, "nvenc api version table outdated");
++_Static_assert(NVENC_STRUCT_VER(13, 0, 1) == NV_ENC_CREATE_BITSTREAM_BUFFER_VER, "nvenc api version table outdated");
++_Static_assert(NVENC_STRUCT_VER_RES(13, 0, 2) == NV_ENC_RECONFIGURE_PARAMS_VER, "nvenc api version table outdated");
++_Static_assert(NVENC_STRUCT_VER(13, 0, 1) == NV_ENC_SEQUENCE_PARAM_PAYLOAD_VER, "nvenc api version table outdated");
++
++/* Layout guards for the compatibility padding after NV_ENC_INITIALIZE_PARAMS /
++ * NV_ENC_RECONFIGURE_PARAMS / NV_ENC_LOCK_BITSTREAM (their pre-12.2 layouts
++ * are up to 8 bytes larger: 1808, 1824 and 1552 bytes respectively) and for
++ * the legacy bit-depth pokes below. */
++_Static_assert(sizeof(NV_ENC_INITIALIZE_PARAMS) == 1800, "unexpected NV_ENC_INITIALIZE_PARAMS layout");
++_Static_assert(sizeof(NV_ENC_RECONFIGURE_PARAMS) == 1816, "unexpected NV_ENC_RECONFIGURE_PARAMS layout");
++_Static_assert(sizeof(NV_ENC_LOCK_BITSTREAM) == 1544, "unexpected NV_ENC_LOCK_BITSTREAM layout");
++_Static_assert(offsetof(NV_ENC_CONFIG_HEVC, tier) == 4 && offsetof(NV_ENC_CONFIG_HEVC, idrPeriod) == 20,
++ "unexpected NV_ENC_CONFIG_HEVC layout");
++_Static_assert(offsetof(NV_ENC_CONFIG_AV1, tier) == 4 && offsetof(NV_ENC_CONFIG_AV1, idrPeriod) == 20,
++ "unexpected NV_ENC_CONFIG_AV1 layout");
++
++/*
++ * SDK 12.2 removed the NV_ENC_CONFIG_HEVC.pixelBitDepthMinus8 and
++ * NV_ENC_CONFIG_AV1.inputPixelBitDepthMinus8/pixelBitDepthMinus8 bitfields
++ * and replaced them with the inputBitDepth/outputBitDepth enums that live at
++ * different offsets. Drivers negotiated to API < 12.2 still read the old bit
++ * positions (reserved-and-zero in the compiled 12.2+ layout), so 10 bit
++ * encoding on those drivers has to set the old bits by hand.
++ *
++ * Verified against the SDK 11.1/12.0/12.1 headers: both codec configs keep
++ * their flag bitfields in the uint32 word at byte offset 16 (between
++ * maxCUSize/maxPartSize at 12 and idrPeriod at 20, anchored by the asserts
++ * above). Within that word:
++ * NV_ENC_CONFIG_HEVC: pixelBitDepthMinus8 = bits 11-13
++ * NV_ENC_CONFIG_AV1: inputPixelBitDepthMinus8 = bits 12-14,
++ * pixelBitDepthMinus8 = bits 15-17
++ */
++static void nvenc_legacy_bitfield_poke(void *codec_config, size_t word_offset,
++ unsigned bit_offset, unsigned width, uint32_t value)
++{
++ uint32_t word;
++ memcpy(&word, (uint8_t*)codec_config + word_offset, sizeof(word));
++ word &= ~(((1u << width) - 1) << bit_offset);
++ word |= (value & ((1u << width) - 1)) << bit_offset;
++ memcpy((uint8_t*)codec_config + word_offset, &word, sizeof(word));
++}
++
++static void nvenc_set_legacy_hevc_bit_depth(NV_ENC_CONFIG_HEVC *hevc, uint32_t depth_minus8)
++{
++ nvenc_legacy_bitfield_poke(hevc, 16, 11, 3, depth_minus8);
++}
++
++#if CONFIG_AV1_NVENC_ENCODER
++static void nvenc_set_legacy_av1_bit_depth(NV_ENC_CONFIG_AV1 *av1,
++ uint32_t input_depth_minus8, uint32_t output_depth_minus8)
++{
++ nvenc_legacy_bitfield_poke(av1, 16, 12, 3, input_depth_minus8);
++ nvenc_legacy_bitfield_poke(av1, 16, 15, 3, output_depth_minus8);
++}
++#endif
++
+ static av_cold int nvenc_load_libraries(AVCodecContext *avctx)
+ {
+ NvencContext *ctx = avctx->priv_data;
+@@ -371,16 +566,35 @@ static av_cold int nvenc_load_libraries(AVCodecContext *avctx)
+
+ av_log(avctx, AV_LOG_VERBOSE, "Loaded Nvenc version %d.%d\n", nvenc_max_ver >> 4, nvenc_max_ver & 0xf);
+
+- if ((NVENCAPI_MAJOR_VERSION << 4 | NVENCAPI_MINOR_VERSION) > nvenc_max_ver) {
++ /* Negotiate: present the newest API version both the driver and this
++ * build support instead of failing on drivers older than the headers. */
++ ctx->api_vers = NULL;
++ for (int i = 0; i < (int)FF_ARRAY_ELEMS(nvenc_api_versions); i++) {
++ if (nvenc_api_versions[i].packed <= nvenc_max_ver) {
++ ctx->api_vers = &nvenc_api_versions[i];
++ break;
++ }
++ }
++
++ if (!ctx->api_vers) {
+ av_log(avctx, AV_LOG_ERROR, "Driver does not support the required nvenc API version. "
+ "Required: %d.%d Found: %d.%d\n",
+- NVENCAPI_MAJOR_VERSION, NVENCAPI_MINOR_VERSION,
++ 11, 1,
+ nvenc_max_ver >> 4, nvenc_max_ver & 0xf);
+ nvenc_print_driver_requirement(avctx, AV_LOG_ERROR);
+ return AVERROR(ENOSYS);
+ }
+
+- dl_fn->nvenc_funcs.version = NV_ENCODE_API_FUNCTION_LIST_VER;
++ ctx->api_version = ctx->api_vers->packed;
++
++ if (ctx->api_version < NVENC_API_PACKED(NVENCAPI_MAJOR_VERSION, NVENCAPI_MINOR_VERSION))
++ av_log(avctx, AV_LOG_VERBOSE, "Driver supports nvenc API up to %d.%d (compiled for %d.%d), "
++ "using API %d.%d; features requiring a newer driver are disabled\n",
++ nvenc_max_ver >> 4, nvenc_max_ver & 0xf,
++ NVENCAPI_MAJOR_VERSION, NVENCAPI_MINOR_VERSION,
++ ctx->api_version >> 4, ctx->api_version & 0xf);
++
++ dl_fn->nvenc_funcs.version = ctx->api_vers->function_list;
+
+ err = dl_fn->nvenc_dl->NvEncodeAPICreateInstance(&dl_fn->nvenc_funcs);
+ if (err != NV_ENC_SUCCESS)
+@@ -421,8 +635,8 @@ static av_cold int nvenc_open_session(AVCodecContext *avctx)
+ NV_ENCODE_API_FUNCTION_LIST *p_nvenc = &ctx->nvenc_dload_funcs.nvenc_funcs;
+ NVENCSTATUS ret;
+
+- params.version = NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER;
+- params.apiVersion = NVENCAPI_VERSION;
++ params.version = ctx->api_vers->open_session_params;
++ params.apiVersion = ctx->api_vers->api;
+ if (ctx->d3d11_device) {
+ params.device = ctx->d3d11_device;
+ params.deviceType = NV_ENC_DEVICE_TYPE_DIRECTX;
+@@ -483,7 +697,7 @@ static int nvenc_check_cap(AVCodecContext *avctx, NV_ENC_CAPS cap)
+ NV_ENC_CAPS_PARAM params = { 0 };
+ int ret, val = 0;
+
+- params.version = NV_ENC_CAPS_PARAM_VER;
++ params.version = ctx->api_vers->caps_param;
+ params.capsToQuery = cap;
+
+ ret = p_nvenc->nvEncGetEncodeCaps(ctx->nvencoder, ctx->init_encode_params.encodeGUID, &params, &val);
+@@ -498,6 +712,15 @@ static int nvenc_check_capabilities(AVCodecContext *avctx)
+ NvencContext *ctx = avctx->priv_data;
+ int tmp, ret;
+
++#if CONFIG_AV1_NVENC_ENCODER
++ if (avctx->codec->id == AV_CODEC_ID_AV1 && !NVENC_API_AT_LEAST(ctx, 12, 0)) {
++ av_log(avctx, AV_LOG_ERROR, "AV1 encoding needs nvenc API 12.0, but the driver "
++ "only supports %d.%d. Driver 520.56.06 or newer is required for AV1.\n",
++ ctx->api_version >> 4, ctx->api_version & 0xf);
++ return AVERROR(ENOSYS);
++ }
++#endif
++
+ ret = nvenc_check_codec_support(avctx);
+ if (ret < 0) {
+ av_log(avctx, AV_LOG_WARNING, "Codec not supported\n");
+@@ -1219,7 +1442,10 @@ static av_cold int nvenc_setup_rate_control(AVCodecContext *avctx)
+ ctx->encode_config.rcParams.lookaheadDepth, ctx->rc_lookahead);
+
+ #ifdef NVENC_HAVE_LOOKAHEAD_LEVEL
+- if (ctx->lookahead_level >= 0) {
++ if (ctx->lookahead_level >= 0 && !NVENC_API_AT_LEAST(ctx, 12, 2)) {
++ av_log(avctx, AV_LOG_WARNING, "Lookahead level needs nvenc API 12.2 "
++ "(driver 550.54.14+), ignoring.\n");
++ } else if (ctx->lookahead_level >= 0) {
+ switch (ctx->lookahead_level) {
+ case NV_ENC_LOOKAHEAD_LEVEL_0:
+ case NV_ENC_LOOKAHEAD_LEVEL_1:
+@@ -1413,8 +1639,12 @@ static av_cold int nvenc_setup_h264_config(AVCodecContext *avctx)
+ h264->level = ctx->level;
+
+ #ifdef NVENC_HAVE_NEW_BIT_DEPTH_API
+- h264->inputBitDepth = IS_10BIT(ctx->data_pix_fmt) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
+- h264->outputBitDepth = (IS_10BIT(ctx->data_pix_fmt) || ctx->highbitdepth) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
++ /* Pre-12.2 drivers read reserved space at these offsets; H264 on such
++ * drivers is always 8 bit (10 bit was rejected by the caps check). */
++ if (NVENC_API_AT_LEAST(ctx, 12, 2)) {
++ h264->inputBitDepth = IS_10BIT(ctx->data_pix_fmt) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
++ h264->outputBitDepth = (IS_10BIT(ctx->data_pix_fmt) || ctx->highbitdepth) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
++ }
+ #endif
+
+ if (ctx->coder >= 0)
+@@ -1450,6 +1680,13 @@ static av_cold int nvenc_setup_h264_config(AVCodecContext *avctx)
+ #endif
+
+ #ifdef NVENC_HAVE_TIME_CODE
++ /* On by default; quietly drop it on old drivers like a pre-12.2 build
++ * (this also disables the per-frame timeCode fill). */
++ if (ctx->s12m_tc && !NVENC_API_AT_LEAST(ctx, 12, 2)) {
++ av_log(avctx, AV_LOG_VERBOSE, "S12M timecodes need nvenc API 12.2, disabling.\n");
++ ctx->s12m_tc = 0;
++ }
++
+ if (ctx->s12m_tc)
+ h264->enableTimeCode = 1;
+ #endif
+@@ -1501,7 +1738,8 @@ static av_cold int nvenc_setup_hevc_config(AVCodecContext *avctx)
+ hevc->intraRefreshCnt = cc->gopLength - 1;
+ cc->gopLength = NVENC_INFINITE_GOPLENGTH;
+ #ifdef NVENC_HAVE_HEVC_OUTPUT_RECOVERY_POINT_SEI
+- hevc->outputRecoveryPointSEI = 1;
++ if (NVENC_API_AT_LEAST(ctx, 12, 0))
++ hevc->outputRecoveryPointSEI = 1;
+ #endif
+ #ifdef NVENC_HAVE_SINGLE_SLICE_INTRA_REFRESH
+ hevc->singleSliceIntraRefresh = ctx->single_slice_intra_refresh;
+@@ -1509,12 +1747,14 @@ static av_cold int nvenc_setup_hevc_config(AVCodecContext *avctx)
+ }
+
+ #ifdef NVENC_HAVE_HEVC_AND_AV1_MASTERING_METADATA
+- ctx->mdm = hevc->outputMasteringDisplay = !!av_frame_side_data_get(avctx->decoded_side_data,
+- avctx->nb_decoded_side_data,
+- AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
+- ctx->cll = hevc->outputMaxCll = !!av_frame_side_data_get(avctx->decoded_side_data,
+- avctx->nb_decoded_side_data,
+- AV_FRAME_DATA_CONTENT_LIGHT_LEVEL);
++ if (NVENC_API_AT_LEAST(ctx, 13, 0)) {
++ ctx->mdm = hevc->outputMasteringDisplay = !!av_frame_side_data_get(avctx->decoded_side_data,
++ avctx->nb_decoded_side_data,
++ AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
++ ctx->cll = hevc->outputMaxCll = !!av_frame_side_data_get(avctx->decoded_side_data,
++ avctx->nb_decoded_side_data,
++ AV_FRAME_DATA_CONTENT_LIGHT_LEVEL);
++ }
+ #endif
+
+ #ifdef NVENC_HAVE_HEVC_CONSTRAINED_ENCODING
+@@ -1613,8 +1853,18 @@ static av_cold int nvenc_setup_hevc_config(AVCodecContext *avctx)
+ hevc->chromaFormatIDC = IS_YUV444(ctx->data_pix_fmt) ? 3 : IS_YUV422(ctx->data_pix_fmt) ? 2 : 1;
+
+ #ifdef NVENC_HAVE_NEW_BIT_DEPTH_API
+- hevc->inputBitDepth = IS_10BIT(ctx->data_pix_fmt) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
+- hevc->outputBitDepth = (IS_10BIT(ctx->data_pix_fmt) || ctx->highbitdepth) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
++ if (NVENC_API_AT_LEAST(ctx, 12, 2)) {
++ hevc->inputBitDepth = IS_10BIT(ctx->data_pix_fmt) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
++ hevc->outputBitDepth = (IS_10BIT(ctx->data_pix_fmt) || ctx->highbitdepth) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
++ } else {
++ if (ctx->highbitdepth && !IS_10BIT(ctx->data_pix_fmt)) {
++ av_log(avctx, AV_LOG_ERROR, "8 -> 10 bit HEVC encoding needs nvenc API 12.2 "
++ "(driver 550.54.14+).\n");
++ return AVERROR(ENOSYS);
++ }
++ // The old pixelBitDepthMinus8 bitfield read by pre-12.2 drivers
++ nvenc_set_legacy_hevc_bit_depth(hevc, IS_10BIT(ctx->data_pix_fmt) ? 2 : 0);
++ }
+ #else
+ hevc->pixelBitDepthMinus8 = IS_10BIT(ctx->data_pix_fmt) ? 2 : 0;
+ #endif
+@@ -1722,20 +1972,28 @@ static av_cold int nvenc_setup_av1_config(AVCodecContext *avctx)
+ av1->chromaFormatIDC = IS_YUV444(ctx->data_pix_fmt) ? 3 : 1;
+
+ #ifdef NVENC_HAVE_NEW_BIT_DEPTH_API
+- av1->inputBitDepth = IS_10BIT(ctx->data_pix_fmt) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
+- av1->outputBitDepth = (IS_10BIT(ctx->data_pix_fmt) || ctx->highbitdepth) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
++ if (NVENC_API_AT_LEAST(ctx, 12, 2)) {
++ av1->inputBitDepth = IS_10BIT(ctx->data_pix_fmt) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
++ av1->outputBitDepth = (IS_10BIT(ctx->data_pix_fmt) || ctx->highbitdepth) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
++ } else {
++ // The old bitfields read by 12.0/12.1 drivers
++ nvenc_set_legacy_av1_bit_depth(av1, IS_10BIT(ctx->data_pix_fmt) ? 2 : 0,
++ (IS_10BIT(ctx->data_pix_fmt) || ctx->highbitdepth) ? 2 : 0);
++ }
+ #else
+ av1->inputPixelBitDepthMinus8 = IS_10BIT(ctx->data_pix_fmt) ? 2 : 0;
+ av1->pixelBitDepthMinus8 = (IS_10BIT(ctx->data_pix_fmt) || ctx->highbitdepth) ? 2 : 0;
+ #endif
+
+ #ifdef NVENC_HAVE_HEVC_AND_AV1_MASTERING_METADATA
+- ctx->mdm = av1->outputMasteringDisplay = !!av_frame_side_data_get(avctx->decoded_side_data,
+- avctx->nb_decoded_side_data,
+- AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
+- ctx->cll = av1->outputMaxCll = !!av_frame_side_data_get(avctx->decoded_side_data,
+- avctx->nb_decoded_side_data,
+- AV_FRAME_DATA_CONTENT_LIGHT_LEVEL);
++ if (NVENC_API_AT_LEAST(ctx, 13, 0)) {
++ ctx->mdm = av1->outputMasteringDisplay = !!av_frame_side_data_get(avctx->decoded_side_data,
++ avctx->nb_decoded_side_data,
++ AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
++ ctx->cll = av1->outputMaxCll = !!av_frame_side_data_get(avctx->decoded_side_data,
++ avctx->nb_decoded_side_data,
++ AV_FRAME_DATA_CONTENT_LIGHT_LEVEL);
++ }
+ #endif
+
+ if (ctx->b_ref_mode >= 0)
+@@ -1829,16 +2087,16 @@ static av_cold int nvenc_setup_encoder(AVCodecContext *avctx)
+ int res = 0;
+ int dw, dh;
+
+- ctx->encode_config.version = NV_ENC_CONFIG_VER;
+- ctx->init_encode_params.version = NV_ENC_INITIALIZE_PARAMS_VER;
++ ctx->encode_config.version = ctx->api_vers->config;
++ ctx->init_encode_params.version = ctx->api_vers->initialize_params;
+
+ ctx->init_encode_params.encodeHeight = avctx->height;
+ ctx->init_encode_params.encodeWidth = avctx->width;
+
+ ctx->init_encode_params.encodeConfig = &ctx->encode_config;
+
+- preset_config.version = NV_ENC_PRESET_CONFIG_VER;
+- preset_config.presetCfg.version = NV_ENC_CONFIG_VER;
++ preset_config.version = ctx->api_vers->preset_config;
++ preset_config.presetCfg.version = ctx->api_vers->config;
+
+ #ifdef NVENC_HAVE_NEW_PRESETS
+ ctx->init_encode_params.tuningInfo = ctx->tuning_info;
+@@ -1848,6 +2106,16 @@ static av_cold int nvenc_setup_encoder(AVCodecContext *avctx)
+ else if (ctx->flags & NVENC_LOWLATENCY)
+ ctx->init_encode_params.tuningInfo = NV_ENC_TUNING_INFO_LOW_LATENCY;
+
++#ifdef NVENC_HAVE_UHQ_TUNING
++ if (ctx->init_encode_params.tuningInfo == NV_ENC_TUNING_INFO_ULTRA_HIGH_QUALITY &&
++ (!NVENC_API_AT_LEAST(ctx, 12, 2) ||
++ (avctx->codec->id == AV_CODEC_ID_AV1 && !NVENC_API_AT_LEAST(ctx, 13, 0)))) {
++ av_log(avctx, AV_LOG_ERROR, "UHQ tuning needs nvenc API 12.2 (driver 550.54.14+), "
++ "13.0 (driver 570+) for AV1.\n");
++ return AVERROR(ENOSYS);
++ }
++#endif
++
+ nv_status = p_nvenc->nvEncGetEncodePresetConfigEx(ctx->nvencoder,
+ ctx->init_encode_params.encodeGUID,
+ ctx->init_encode_params.presetGUID,
+@@ -1864,7 +2132,7 @@ static av_cold int nvenc_setup_encoder(AVCodecContext *avctx)
+
+ memcpy(&ctx->encode_config, &preset_config.presetCfg, sizeof(ctx->encode_config));
+
+- ctx->encode_config.version = NV_ENC_CONFIG_VER;
++ ctx->encode_config.version = ctx->api_vers->config;
+
+ compute_dar(avctx, &dw, &dh);
+ ctx->init_encode_params.darHeight = dh;
+@@ -1897,11 +2165,16 @@ static av_cold int nvenc_setup_encoder(AVCodecContext *avctx)
+ ctx->init_encode_params.enableWeightedPrediction = 1;
+
+ #ifdef NVENC_HAVE_SPLIT_FRAME_ENCODING
+- ctx->init_encode_params.splitEncodeMode = ctx->split_encode_mode;
++ if (NVENC_API_AT_LEAST(ctx, 12, 1)) {
++ ctx->init_encode_params.splitEncodeMode = ctx->split_encode_mode;
+
+- if (ctx->split_encode_mode != NV_ENC_SPLIT_DISABLE_MODE) {
+- if (avctx->codec->id == AV_CODEC_ID_HEVC && ctx->weighted_pred == 1)
+- av_log(avctx, AV_LOG_WARNING, "Split encoding not supported with weighted prediction enabled.\n");
++ if (ctx->split_encode_mode != NV_ENC_SPLIT_DISABLE_MODE) {
++ if (avctx->codec->id == AV_CODEC_ID_HEVC && ctx->weighted_pred == 1)
++ av_log(avctx, AV_LOG_WARNING, "Split encoding not supported with weighted prediction enabled.\n");
++ }
++ } else if (ctx->split_encode_mode != NV_ENC_SPLIT_AUTO_MODE) {
++ av_log(avctx, AV_LOG_WARNING, "Split frame encoding needs nvenc API 12.1 "
++ "(driver 530.41.03+), ignoring.\n");
+ }
+ #endif
+
+@@ -2042,7 +2315,7 @@ static av_cold int nvenc_alloc_surface(AVCodecContext *avctx, int idx)
+
+ NVENCSTATUS nv_status;
+ NV_ENC_CREATE_BITSTREAM_BUFFER allocOut = { 0 };
+- allocOut.version = NV_ENC_CREATE_BITSTREAM_BUFFER_VER;
++ allocOut.version = ctx->api_vers->create_bitstream_buffer;
+
+ if (avctx->pix_fmt == AV_PIX_FMT_CUDA || avctx->pix_fmt == AV_PIX_FMT_D3D11) {
+ ctx->surfaces[idx].in_ref = av_frame_alloc();
+@@ -2058,7 +2331,7 @@ static av_cold int nvenc_alloc_surface(AVCodecContext *avctx, int idx)
+ return AVERROR(EINVAL);
+ }
+
+- allocSurf.version = NV_ENC_CREATE_INPUT_BUFFER_VER;
++ allocSurf.version = ctx->api_vers->create_input_buffer;
+ allocSurf.width = avctx->width;
+ allocSurf.height = avctx->height;
+ allocSurf.bufferFmt = ctx->surfaces[idx].format;
+@@ -2146,7 +2419,7 @@ static av_cold int nvenc_setup_extradata(AVCodecContext *avctx)
+ char tmpHeader[NV_MAX_SEQ_HDR_LEN];
+
+ NV_ENC_SEQUENCE_PARAM_PAYLOAD payload = { 0 };
+- payload.version = NV_ENC_SEQUENCE_PARAM_PAYLOAD_VER;
++ payload.version = ctx->api_vers->sequence_param_payload;
+
+ payload.spsppsBuffer = tmpHeader;
+ payload.inBufferSize = sizeof(tmpHeader);
+@@ -2178,7 +2451,7 @@ av_cold int ff_nvenc_encode_close(AVCodecContext *avctx)
+
+ /* the encoder has to be flushed before it can be closed */
+ if (ctx->nvencoder) {
+- NV_ENC_PIC_PARAMS params = { .version = NV_ENC_PIC_PARAMS_VER,
++ NV_ENC_PIC_PARAMS params = { .version = ctx->api_vers->pic_params,
+ .encodePicFlags = NV_ENC_PIC_FLAG_EOS };
+
+ res = nvenc_push_context(avctx);
+@@ -2402,7 +2675,7 @@ static int nvenc_register_frame(AVCodecContext *avctx, const AVFrame *frame)
+ if (idx < 0)
+ return idx;
+
+- reg.version = NV_ENC_REGISTER_RESOURCE_VER;
++ reg.version = ctx->api_vers->register_resource;
+ reg.width = frames_ctx->width;
+ reg.height = frames_ctx->height;
+ reg.pitch = frame->linesize[0];
+@@ -2457,7 +2730,7 @@ static int nvenc_upload_frame(AVCodecContext *avctx, const AVFrame *frame,
+ return res;
+
+ if (!ctx->registered_frames[reg_idx].mapped) {
+- ctx->registered_frames[reg_idx].in_map.version = NV_ENC_MAP_INPUT_RESOURCE_VER;
++ ctx->registered_frames[reg_idx].in_map.version = ctx->api_vers->map_input_resource;
+ ctx->registered_frames[reg_idx].in_map.registeredResource = ctx->registered_frames[reg_idx].regptr;
+ nv_status = p_nvenc->nvEncMapInputResource(ctx->nvencoder, &ctx->registered_frames[reg_idx].in_map);
+ if (nv_status != NV_ENC_SUCCESS) {
+@@ -2477,7 +2750,7 @@ static int nvenc_upload_frame(AVCodecContext *avctx, const AVFrame *frame,
+ } else {
+ NV_ENC_LOCK_INPUT_BUFFER lockBufferParams = { 0 };
+
+- lockBufferParams.version = NV_ENC_LOCK_INPUT_BUFFER_VER;
++ lockBufferParams.version = ctx->api_vers->lock_input_buffer;
+ lockBufferParams.inputBuffer = nvenc_frame->input_surface;
+
+ nv_status = p_nvenc->nvEncLockInputBuffer(ctx->nvencoder, &lockBufferParams);
+@@ -2725,31 +2998,38 @@ static int process_output_surface(AVCodecContext *avctx, AVPacket *pkt, NvencSur
+ NvencDynLoadFunctions *dl_fn = &ctx->nvenc_dload_funcs;
+ NV_ENCODE_API_FUNCTION_LIST *p_nvenc = &dl_fn->nvenc_funcs;
+
+- NV_ENC_LOCK_BITSTREAM lock_params = { 0 };
++ /* The SDK 12.1 NV_ENC_LOCK_BITSTREAM layout is 8 bytes larger than the
++ * 12.2+ layout this is compiled against; keep zeroed spare space after it
++ * for drivers negotiated to API <= 12.1. */
++ struct {
++ NV_ENC_LOCK_BITSTREAM params;
++ uint32_t compat_pad[2];
++ } lock_params_padded = { 0 };
++ NV_ENC_LOCK_BITSTREAM *lock_params = &lock_params_padded.params;
+ NVENCSTATUS nv_status;
+ int res = 0;
+
+ enum AVPictureType pict_type;
+
+- lock_params.version = NV_ENC_LOCK_BITSTREAM_VER;
++ lock_params->version = ctx->api_vers->lock_bitstream;
+
+- lock_params.doNotWait = 0;
+- lock_params.outputBitstream = tmpoutsurf->output_surface;
++ lock_params->doNotWait = 0;
++ lock_params->outputBitstream = tmpoutsurf->output_surface;
+
+- nv_status = p_nvenc->nvEncLockBitstream(ctx->nvencoder, &lock_params);
++ nv_status = p_nvenc->nvEncLockBitstream(ctx->nvencoder, lock_params);
+ if (nv_status != NV_ENC_SUCCESS) {
+ res = nvenc_print_error(avctx, nv_status, "Failed locking bitstream buffer");
+ goto error;
+ }
+
+- res = ff_get_encode_buffer(avctx, pkt, lock_params.bitstreamSizeInBytes, 0);
++ res = ff_get_encode_buffer(avctx, pkt, lock_params->bitstreamSizeInBytes, 0);
+
+ if (res < 0) {
+ p_nvenc->nvEncUnlockBitstream(ctx->nvencoder, tmpoutsurf->output_surface);
+ goto error;
+ }
+
+- memcpy(pkt->data, lock_params.bitstreamBufferPtr, lock_params.bitstreamSizeInBytes);
++ memcpy(pkt->data, lock_params->bitstreamBufferPtr, lock_params->bitstreamSizeInBytes);
+
+ nv_status = p_nvenc->nvEncUnlockBitstream(ctx->nvencoder, tmpoutsurf->output_surface);
+ if (nv_status != NV_ENC_SUCCESS) {
+@@ -2776,7 +3056,7 @@ static int process_output_surface(AVCodecContext *avctx, AVPacket *pkt, NvencSur
+ tmpoutsurf->input_surface = NULL;
+ }
+
+- switch (lock_params.pictureType) {
++ switch (lock_params->pictureType) {
+ case NV_ENC_PIC_TYPE_IDR:
+ pkt->flags |= AV_PKT_FLAG_KEY;
+ case NV_ENC_PIC_TYPE_I:
+@@ -2799,13 +3079,13 @@ static int process_output_surface(AVCodecContext *avctx, AVPacket *pkt, NvencSur
+ }
+
+ ff_encode_add_stats_side_data(pkt,
+- (lock_params.frameAvgQP - 1) * FF_QP2LAMBDA, NULL, 0, pict_type);
++ (lock_params->frameAvgQP - 1) * FF_QP2LAMBDA, NULL, 0, pict_type);
+
+- res = nvenc_set_timestamp(avctx, &lock_params, pkt);
++ res = nvenc_set_timestamp(avctx, lock_params, pkt);
+ if (res < 0)
+ goto error2;
+
+- res = nvenc_retrieve_frame_data(avctx, &lock_params, pkt);
++ res = nvenc_retrieve_frame_data(avctx, lock_params, pkt);
+ if (res < 0)
+ goto error2;
+
+@@ -2948,14 +3228,21 @@ static void reconfig_encoder(AVCodecContext *avctx, const AVFrame *frame)
+ NV_ENCODE_API_FUNCTION_LIST *p_nvenc = &ctx->nvenc_dload_funcs.nvenc_funcs;
+ NVENCSTATUS ret;
+
+- NV_ENC_RECONFIGURE_PARAMS params = { 0 };
++ /* The pre-12.2 NV_ENC_RECONFIGURE_PARAMS layout is 8 bytes larger than
++ * the 12.2+ layout this is compiled against; keep zeroed spare space
++ * after it for drivers negotiated to API < 12.2. */
++ struct {
++ NV_ENC_RECONFIGURE_PARAMS params;
++ uint32_t compat_pad[2];
++ } params_padded = { 0 };
++ NV_ENC_RECONFIGURE_PARAMS *params = &params_padded.params;
+ int needs_reconfig = 0;
+ int needs_encode_config = 0;
+ int reconfig_bitrate = 0, reconfig_dar = 0;
+ int dw, dh;
+
+- params.version = NV_ENC_RECONFIGURE_PARAMS_VER;
+- params.reInitEncodeParams = ctx->init_encode_params;
++ params->version = ctx->api_vers->reconfigure_params;
++ params->reInitEncodeParams = ctx->init_encode_params;
+
+ compute_dar(avctx, &dw, &dh);
+ if (dw != ctx->init_encode_params.darWidth || dh != ctx->init_encode_params.darHeight) {
+@@ -2964,47 +3251,59 @@ static void reconfig_encoder(AVCodecContext *avctx, const AVFrame *frame)
+ ctx->init_encode_params.darWidth,
+ ctx->init_encode_params.darHeight, dw, dh);
+
+- params.reInitEncodeParams.darHeight = dh;
+- params.reInitEncodeParams.darWidth = dw;
++ params->reInitEncodeParams.darHeight = dh;
++ params->reInitEncodeParams.darWidth = dw;
+
+ needs_reconfig = 1;
+ reconfig_dar = 1;
+ }
+
+ if (ctx->rc != NV_ENC_PARAMS_RC_CONSTQP && ctx->support_dyn_bitrate) {
+- if (avctx->bit_rate > 0 && params.reInitEncodeParams.encodeConfig->rcParams.averageBitRate != avctx->bit_rate) {
++ if (avctx->bit_rate > 0 && params->reInitEncodeParams.encodeConfig->rcParams.averageBitRate != avctx->bit_rate) {
+ av_log(avctx, AV_LOG_VERBOSE,
+ "avg bitrate change: %d -> %d\n",
+- params.reInitEncodeParams.encodeConfig->rcParams.averageBitRate,
++ params->reInitEncodeParams.encodeConfig->rcParams.averageBitRate,
+ (uint32_t)avctx->bit_rate);
+
+- params.reInitEncodeParams.encodeConfig->rcParams.averageBitRate = avctx->bit_rate;
++ params->reInitEncodeParams.encodeConfig->rcParams.averageBitRate = avctx->bit_rate;
+ reconfig_bitrate = 1;
+ }
+
+ if (avctx->rc_max_rate > 0 && ctx->encode_config.rcParams.maxBitRate != avctx->rc_max_rate) {
+ av_log(avctx, AV_LOG_VERBOSE,
+ "max bitrate change: %d -> %d\n",
+- params.reInitEncodeParams.encodeConfig->rcParams.maxBitRate,
++ params->reInitEncodeParams.encodeConfig->rcParams.maxBitRate,
+ (uint32_t)avctx->rc_max_rate);
+
+- params.reInitEncodeParams.encodeConfig->rcParams.maxBitRate = avctx->rc_max_rate;
++ params->reInitEncodeParams.encodeConfig->rcParams.maxBitRate = avctx->rc_max_rate;
+ reconfig_bitrate = 1;
+ }
+
+ if (avctx->rc_buffer_size > 0 && ctx->encode_config.rcParams.vbvBufferSize != avctx->rc_buffer_size) {
+ av_log(avctx, AV_LOG_VERBOSE,
+ "vbv buffer size change: %d -> %d\n",
+- params.reInitEncodeParams.encodeConfig->rcParams.vbvBufferSize,
++ params->reInitEncodeParams.encodeConfig->rcParams.vbvBufferSize,
+ avctx->rc_buffer_size);
+
+- params.reInitEncodeParams.encodeConfig->rcParams.vbvBufferSize = avctx->rc_buffer_size;
++ params->reInitEncodeParams.encodeConfig->rcParams.vbvBufferSize = avctx->rc_buffer_size;
+ reconfig_bitrate = 1;
+ }
+
+ if (reconfig_bitrate) {
+- params.resetEncoder = 1;
+- params.forceIDR = 1;
++ if (NVENC_API_AT_LEAST(ctx, 12, 2)) {
++ params->resetEncoder = 1;
++ params->forceIDR = 1;
++ } else {
++ /* SDK 12.2 inserted a reserved word at offset 4 of
++ * NV_ENC_RECONFIGURE_PARAMS, shifting the trailing
++ * resetEncoder/forceIDR bits from byte 1816 to 1808. Pre-12.2
++ * drivers read them at 1816, which is compat_pad[0] of the
++ * padded wrapper (verified against SDK 11.1-12.1 headers:
++ * resetEncoder = bit 0, forceIDR = bit 1). The compiled
++ * bitfields must stay 0: byte 1808 lies in reserved space of
++ * the embedded pre-12.2 reInitEncodeParams. */
++ params_padded.compat_pad[0] = 0x1 | 0x2;
++ }
+
+ needs_encode_config = 1;
+ needs_reconfig = 1;
+@@ -3012,10 +3311,10 @@ static void reconfig_encoder(AVCodecContext *avctx, const AVFrame *frame)
+ }
+
+ if (!needs_encode_config)
+- params.reInitEncodeParams.encodeConfig = NULL;
++ params->reInitEncodeParams.encodeConfig = NULL;
+
+ if (needs_reconfig) {
+- ret = p_nvenc->nvEncReconfigureEncoder(ctx->nvencoder, &params);
++ ret = p_nvenc->nvEncReconfigureEncoder(ctx->nvencoder, params);
+ if (ret != NV_ENC_SUCCESS) {
+ nvenc_print_error(avctx, ret, "failed to reconfigure nvenc");
+ } else {
+@@ -3025,9 +3324,9 @@ static void reconfig_encoder(AVCodecContext *avctx, const AVFrame *frame)
+ }
+
+ if (reconfig_bitrate) {
+- ctx->encode_config.rcParams.averageBitRate = params.reInitEncodeParams.encodeConfig->rcParams.averageBitRate;
+- ctx->encode_config.rcParams.maxBitRate = params.reInitEncodeParams.encodeConfig->rcParams.maxBitRate;
+- ctx->encode_config.rcParams.vbvBufferSize = params.reInitEncodeParams.encodeConfig->rcParams.vbvBufferSize;
++ ctx->encode_config.rcParams.averageBitRate = params->reInitEncodeParams.encodeConfig->rcParams.averageBitRate;
++ ctx->encode_config.rcParams.maxBitRate = params->reInitEncodeParams.encodeConfig->rcParams.maxBitRate;
++ ctx->encode_config.rcParams.vbvBufferSize = params->reInitEncodeParams.encodeConfig->rcParams.vbvBufferSize;
+ }
+
+ }
+@@ -3126,7 +3425,7 @@ static int nvenc_send_frame(AVCodecContext *avctx, const AVFrame *frame)
+ NV_ENCODE_API_FUNCTION_LIST *p_nvenc = &dl_fn->nvenc_funcs;
+
+ NV_ENC_PIC_PARAMS pic_params = { 0 };
+- pic_params.version = NV_ENC_PIC_PARAMS_VER;
++ pic_params.version = ctx->api_vers->pic_params;
+
+ if ((!ctx->cu_context && !ctx->d3d11_device) || !ctx->nvencoder)
+ return AVERROR(EINVAL);
+diff --git a/libavcodec/nvenc.h b/libavcodec/nvenc.h
+index 069ba82bc9..4225c612aa 100644
+--- a/libavcodec/nvenc.h
++++ b/libavcodec/nvenc.h
+@@ -111,6 +111,54 @@ typedef void ID3D11Device;
+ #define NVENC_HAVE_MVHEVC
+ #endif
+
++/*
++ * Runtime NVENC API version negotiation.
++ *
++ * The NVENC driver rejects clients that present a newer API version than it
++ * supports, so an FFmpeg built against new SDK headers normally refuses to
++ * run on older drivers. Instead of failing, we query the driver's maximum
++ * supported API version at runtime and present struct version words (and the
++ * session apiVersion) matching what that driver expects, while disabling
++ * features that need a newer API. This keeps one binary working from driver
++ * 470.57.02 (API 11.1, the last driver branch for Kepler / first-generation
++ * NVENC GPUs) up to current drivers, including AV1 support on drivers that
++ * have it.
++ *
++ * The struct version table and the legacy bit-depth field positions below
++ * were derived from and verified against the exact layouts of nv-codec-headers
++ * n11.1.5.3, n12.0.16.1, n12.1.14.0, n12.2.72.0 and n13.0.19.0. All struct
++ * fields FFmpeg touches sit at identical offsets in all of those versions
++ * (new fields are only ever carved out of reserved space). Building against
++ * any other headers invalidates those assumptions, so refuse it.
++ */
++#if !NVENCAPI_CHECK_VERSION(13, 0) || NVENCAPI_CHECK_VERSION(13, 1)
++#error "The nvenc runtime API negotiation patch requires nv-codec-headers 13.0.x. Re-verify the struct version table and legacy bit-depth field layout before moving to other headers."
++#endif
++
++typedef struct NvencApiVersion {
++ uint32_t packed; // (major << 4) | minor, the NvEncodeAPIGetMaxSupportedVersion encoding
++ uint32_t api; // NVENCAPI_VERSION of that SDK: major | (minor << 24)
++ uint32_t function_list; // NV_ENCODE_API_FUNCTION_LIST_VER
++ uint32_t open_session_params; // NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER
++ uint32_t caps_param; // NV_ENC_CAPS_PARAM_VER
++ uint32_t initialize_params; // NV_ENC_INITIALIZE_PARAMS_VER
++ uint32_t config; // NV_ENC_CONFIG_VER
++ uint32_t preset_config; // NV_ENC_PRESET_CONFIG_VER
++ uint32_t pic_params; // NV_ENC_PIC_PARAMS_VER
++ uint32_t lock_bitstream; // NV_ENC_LOCK_BITSTREAM_VER
++ uint32_t lock_input_buffer; // NV_ENC_LOCK_INPUT_BUFFER_VER
++ uint32_t map_input_resource; // NV_ENC_MAP_INPUT_RESOURCE_VER
++ uint32_t register_resource; // NV_ENC_REGISTER_RESOURCE_VER
++ uint32_t create_input_buffer; // NV_ENC_CREATE_INPUT_BUFFER_VER
++ uint32_t create_bitstream_buffer; // NV_ENC_CREATE_BITSTREAM_BUFFER_VER
++ uint32_t reconfigure_params; // NV_ENC_RECONFIGURE_PARAMS_VER
++ uint32_t sequence_param_payload; // NV_ENC_SEQUENCE_PARAM_PAYLOAD_VER
++} NvencApiVersion;
++
++#define NVENC_API_PACKED(major, minor) (((uint32_t)(major) << 4) | (minor))
++#define NVENC_API_AT_LEAST(ctx, major, minor) \
++ ((ctx)->api_version >= NVENC_API_PACKED(major, minor))
++
+ typedef struct NvencSurface
+ {
+ NV_ENC_INPUT_PTR input_surface;
+@@ -216,6 +264,11 @@ typedef struct NvencContext
+ NvencDynLoadFunctions nvenc_dload_funcs;
+
+ NV_ENC_INITIALIZE_PARAMS init_encode_params;
++ /* The pre-12.2 NV_ENC_INITIALIZE_PARAMS layout is 8 bytes larger than the
++ * 12.2+ layout this is compiled against. Drivers negotiated to API < 12.2
++ * read that larger footprint, so keep zeroed spare space directly after
++ * the struct. Must stay right after init_encode_params and stay zero. */
++ uint32_t init_encode_params_compat_pad[2];
+ NV_ENC_CONFIG encode_config;
+ CUcontext cu_context;
+ CUcontext cu_context_internal;
+@@ -321,6 +374,11 @@ typedef struct NvencContext
+ int cbr_padding;
+ int multiview, multiview_supported;
+ int display_sei_sent;
++
++ /* Runtime-negotiated NVENC API version: the newest version supported by
++ * both the driver and this build. Set in nvenc_load_libraries. */
++ const NvencApiVersion *api_vers;
++ uint32_t api_version; // negotiated, (major << 4) | minor
+ } NvencContext;
+
+ int ff_nvenc_encode_init(AVCodecContext *avctx);
diff --git a/subprojects/packagefiles/ffmpeg/meson.build b/subprojects/packagefiles/ffmpeg/meson.build
new file mode 100644
index 0000000..38df7e9
--- /dev/null
+++ b/subprojects/packagefiles/ffmpeg/meson.build
@@ -0,0 +1,35 @@
+project('ffmpeg', 'c', version : '8.1')
+
+# Neither ffmpeg nor the libraries it depends on use meson, so they are built with their
+# own build systems into a shared prefix and then exposed to gpu-screen-recorder as
+# regular pkg-config dependencies.
+
+install_prefix = meson.current_build_dir() / 'install'
+c_compiler = ' '.join(meson.get_compiler('c').cmd_array())
+build_library = find_program(meson.global_source_root() / 'extra' / 'build_ffmpeg.sh')
+
+# srt links against openssl and ffmpeg links against all of them, so the order matters.
+libraries = ['nv-codec-headers', 'x264', 'opus', 'openssl', 'srt', 'ffmpeg']
+
+foreach library : libraries
+ source_dir = library == 'ffmpeg' ? meson.current_source_dir() : subproject(library).get_variable('source_dir')
+ build_result = run_command(build_library, library, source_dir, meson.current_build_dir() / library, install_prefix, c_compiler, check : true)
+ if build_result.stderr().strip() != ''
+ message(build_result.stderr().strip())
+ endif
+endforeach
+
+ffmpeg_libs = ['libavformat', 'libavfilter', 'libavcodec', 'libswresample', 'libavutil']
+
+pkgconfig = find_program('pkg-config')
+pkgconfig_env = environment()
+pkgconfig_env.prepend('PKG_CONFIG_PATH', install_prefix / 'lib' / 'pkgconfig')
+
+ffmpeg_cflags = run_command(pkgconfig, '--cflags', ffmpeg_libs, env : pkgconfig_env, check : true).stdout().split()
+ffmpeg_ldflags = run_command(pkgconfig, '--static', '--libs', ffmpeg_libs, env : pkgconfig_env, check : true).stdout().split()
+
+ffmpeg_dep = declare_dependency(compile_args : ffmpeg_cflags, link_args : ffmpeg_ldflags)
+
+foreach ffmpeg_lib : ffmpeg_libs
+ meson.override_dependency(ffmpeg_lib, ffmpeg_dep)
+endforeach
diff --git a/subprojects/packagefiles/nv-codec-headers/meson.build b/subprojects/packagefiles/nv-codec-headers/meson.build
new file mode 100644
index 0000000..cc59db7
--- /dev/null
+++ b/subprojects/packagefiles/nv-codec-headers/meson.build
@@ -0,0 +1,3 @@
+project('nv-codec-headers')
+
+source_dir = meson.current_source_dir()
diff --git a/subprojects/packagefiles/openssl/meson.build b/subprojects/packagefiles/openssl/meson.build
new file mode 100644
index 0000000..d7ebb0c
--- /dev/null
+++ b/subprojects/packagefiles/openssl/meson.build
@@ -0,0 +1,3 @@
+project('openssl')
+
+source_dir = meson.current_source_dir()
diff --git a/subprojects/packagefiles/opus/meson.build b/subprojects/packagefiles/opus/meson.build
new file mode 100644
index 0000000..858205d
--- /dev/null
+++ b/subprojects/packagefiles/opus/meson.build
@@ -0,0 +1,3 @@
+project('opus')
+
+source_dir = meson.current_source_dir()
diff --git a/subprojects/packagefiles/srt/meson.build b/subprojects/packagefiles/srt/meson.build
new file mode 100644
index 0000000..3fa707e
--- /dev/null
+++ b/subprojects/packagefiles/srt/meson.build
@@ -0,0 +1,3 @@
+project('srt')
+
+source_dir = meson.current_source_dir()
diff --git a/subprojects/packagefiles/x264/meson.build b/subprojects/packagefiles/x264/meson.build
new file mode 100644
index 0000000..87f3a80
--- /dev/null
+++ b/subprojects/packagefiles/x264/meson.build
@@ -0,0 +1,3 @@
+project('x264')
+
+source_dir = meson.current_source_dir()
diff --git a/subprojects/srt.wrap b/subprojects/srt.wrap
new file mode 100644
index 0000000..583b2e8
--- /dev/null
+++ b/subprojects/srt.wrap
@@ -0,0 +1,6 @@
+[wrap-file]
+directory = srt-1.5.6
+source_url = https://github.com/Haivision/srt/archive/refs/tags/v1.5.6.tar.gz
+source_filename = srt-1.5.6.tar.gz
+source_hash = 2c4980c2c4cfd142d21b829d939dc51db9c6628af5967fff62fd7290769569c7
+patch_directory = srt
diff --git a/subprojects/x264.wrap b/subprojects/x264.wrap
new file mode 100644
index 0000000..f9428cb
--- /dev/null
+++ b/subprojects/x264.wrap
@@ -0,0 +1,6 @@
+[wrap-file]
+directory = x264-b35605ace3ddf7c1a5d67a2eb553f034aef41d55
+source_url = https://code.videolan.org/videolan/x264/-/archive/b35605ace3ddf7c1a5d67a2eb553f034aef41d55/x264-b35605ace3ddf7c1a5d67a2eb553f034aef41d55.tar.bz2
+source_filename = x264-b35605ace3ddf7c1a5d67a2eb553f034aef41d55.tar.bz2
+source_hash = 6eeb82934e69fd51e043bd8c5b0d152839638d1ce7aa4eea65a3fedcf83ff224
+patch_directory = x264