306 lines
13 KiB
Bash
Executable File
306 lines
13 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Build & install a perf-optimized ffmpeg (LTO + libx264 + native arch).
|
|
# Mirrors the team playbook's flags verbatim per stage, with three
|
|
# deliberate deviations made necessary by our build host:
|
|
#
|
|
# 1. --disable-libxcb --disable-xlib ffmpeg's auto-detect linked
|
|
# libxcb at build time → binary
|
|
# wouldn't even start at runtime.
|
|
# 2. LIBRARY_PATH / LD_LIBRARY_PATH conda-forge gcc wrappers search
|
|
# + -L$INSTALL_PREFIX/lib early $CONDA_PREFIX/lib implicitly,
|
|
# leaking an older libx264 into
|
|
# ffmpeg's link. Forcing our prefix
|
|
# first makes the resolver pick
|
|
# our just-built lib.
|
|
# 3. MAKE_JOBS cap (default 16) very high -j (e.g. nproc=96 on
|
|
# NVL72) tripped a race in
|
|
# ffmpeg's recursive recipes.
|
|
#
|
|
# Usage:
|
|
# bash scripts/install_native_ffmpeg.sh
|
|
#
|
|
# Knobs (env vars, all optional):
|
|
# INSTALL_PREFIX install destination default: $HOME/opt/ffmpeg-native
|
|
# SOURCE_DIR build workspace default: $HOME/src/ffmpeg-native
|
|
# X264_REF x264 git ref default: stable
|
|
# FFMPEG_REF FFmpeg git ref default: n7.1
|
|
# NV_CODEC_REF nv-codec-headers ref default: master
|
|
# CUDA_PREFIX CUDA toolkit root default: /usr/local/cuda
|
|
# ENABLE_NVENC build with NVENC/NVDEC default: 1 (1|0)
|
|
# MAKE_JOBS parallel make jobs default: min(nproc, 16)
|
|
# FFMPEG_NATIVE_CC explicit C compiler command for native builds
|
|
# FFMPEG_NATIVE_CXX explicit C++ compiler command for native builds
|
|
#
|
|
# Toolchain selection (precedence, highest first):
|
|
# 1. FFMPEG_NATIVE_CC / FFMPEG_NATIVE_CXX, if set — explicit override.
|
|
# 2. The conda-forge triplet matching `uname -m`
|
|
# (`x86_64-conda-linux-gnu-cc` or `aarch64-conda-linux-gnu-cc`),
|
|
# if present on PATH. Pinning the triplet sidesteps a bug where
|
|
# conda envs with BOTH `gcc_linux-64` and `gcc_linux-aarch64`
|
|
# installed export the cross-compiler triplet on every
|
|
# `conda activate` (the `aarch64` activation script sorts later
|
|
# and wins), silently breaking x264's compiler probe on the
|
|
# opposite host.
|
|
# 3. System `gcc` / `g++` — fallback so the script also works in a
|
|
# plain venv or bare shell with no conda toolchain installed.
|
|
# Inherited bare CC / CXX from the caller environment are ignored in
|
|
# cases (2) and (3); use FFMPEG_NATIVE_CC/CXX to inject a non-host
|
|
# toolchain.
|
|
set -euo pipefail
|
|
|
|
# ─── Defaults (override via env) ──────────────────────────────────────────
|
|
INSTALL_PREFIX="${INSTALL_PREFIX:-$HOME/opt/ffmpeg-native}"
|
|
SOURCE_DIR="${SOURCE_DIR:-$HOME/src/ffmpeg-native}"
|
|
X264_REF="${X264_REF:-stable}"
|
|
FFMPEG_REF="${FFMPEG_REF:-n7.1}"
|
|
NV_CODEC_REF="${NV_CODEC_REF:-master}"
|
|
CUDA_PREFIX="${CUDA_PREFIX:-/usr/local/cuda}"
|
|
ENABLE_NVENC="${ENABLE_NVENC:-1}"
|
|
case "${ENABLE_NVENC}" in
|
|
0|1) ;;
|
|
*) echo "[install_native_ffmpeg] ENABLE_NVENC must be 0 or 1, got '${ENABLE_NVENC}'" >&2; exit 1 ;;
|
|
esac
|
|
NPROC="$(nproc)"
|
|
MAKE_JOBS="${MAKE_JOBS:-$(( NPROC < 16 ? NPROC : 16 ))}"
|
|
|
|
# ─── Per-platform, per-stage flags (verbatim from the playbook) ───────────
|
|
ARCH="$(uname -m)"
|
|
case "$ARCH" in
|
|
x86_64)
|
|
CONDA_CC=x86_64-conda-linux-gnu-cc
|
|
CONDA_CXX=x86_64-conda-linux-gnu-c++
|
|
AS=nasm
|
|
X264_CFLAGS="-O3 -march=native -mtune=native -fPIC -flto"
|
|
X264_LDFLAGS="-flto -fuse-linker-plugin"
|
|
FFMPEG_CFLAGS="-O3 -march=native -mtune=native -fPIC -flto"
|
|
FFMPEG_LDFLAGS="-flto -Wl,-rpath,$INSTALL_PREFIX/lib"
|
|
;;
|
|
aarch64)
|
|
CONDA_CC=aarch64-conda-linux-gnu-cc
|
|
CONDA_CXX=aarch64-conda-linux-gnu-c++
|
|
unset AS # GNU as on ARM
|
|
X264_CFLAGS="-O3 -mcpu=native -fPIC -flto"
|
|
X264_LDFLAGS="-flto -fuse-linker-plugin"
|
|
FFMPEG_CFLAGS="-O3 -mcpu=native -fPIC -flto -fno-tree-vectorize"
|
|
FFMPEG_LDFLAGS="-flto -Wl,-rpath,$INSTALL_PREFIX/lib"
|
|
;;
|
|
*)
|
|
echo "[install_native_ffmpeg] unsupported arch: $ARCH" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Prefer the conda-forge triplet when it's actually on PATH; otherwise fall
|
|
# back to system gcc/g++ so a plain venv works too. FFMPEG_NATIVE_CC/CXX
|
|
# overrides both.
|
|
if command -v -- "$CONDA_CC" >/dev/null 2>&1 \
|
|
&& command -v -- "$CONDA_CXX" >/dev/null 2>&1; then
|
|
DEFAULT_CC="$CONDA_CC"
|
|
DEFAULT_CXX="$CONDA_CXX"
|
|
default_source="conda-forge ($ARCH triplet)"
|
|
else
|
|
DEFAULT_CC=gcc
|
|
DEFAULT_CXX=g++
|
|
default_source="system gcc/g++"
|
|
fi
|
|
|
|
CC="${FFMPEG_NATIVE_CC:-$DEFAULT_CC}"
|
|
CXX="${FFMPEG_NATIVE_CXX:-$DEFAULT_CXX}"
|
|
if [[ -n "${FFMPEG_NATIVE_CC:-}" || -n "${FFMPEG_NATIVE_CXX:-}" ]]; then
|
|
toolchain_source="FFMPEG_NATIVE_CC/CXX override"
|
|
else
|
|
toolchain_source="$default_source"
|
|
fi
|
|
|
|
require_compiler() {
|
|
local name="$1" compiler="$2"
|
|
if [[ -z "$compiler" ]]; then
|
|
echo "[install_native_ffmpeg] $name is empty" >&2
|
|
exit 1
|
|
fi
|
|
if ! command -v -- "$compiler" >/dev/null 2>&1; then
|
|
echo "[install_native_ffmpeg] $name is unavailable: $compiler" >&2
|
|
echo "[install_native_ffmpeg] install a compiler, or set FFMPEG_NATIVE_CC/FFMPEG_NATIVE_CXX" \
|
|
"to compiler commands on PATH." >&2
|
|
exit 1
|
|
fi
|
|
if ! "$compiler" --version >/dev/null 2>&1; then
|
|
echo "[install_native_ffmpeg] $name failed sanity check: $compiler --version" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
require_compiler CC "$CC"
|
|
require_compiler CXX "$CXX"
|
|
export CC CXX
|
|
[[ -n "${AS:-}" ]] && export AS
|
|
echo "[install_native_ffmpeg] toolchain: CC=$CC CXX=$CXX AS=${AS:-<gnu-as>} (uname -m=$ARCH, source: $toolchain_source)"
|
|
|
|
# ─── Step 0: probe required tools ─────────────────────────────────────────
|
|
required=("$CC" "$CXX" make pkg-config git)
|
|
[[ "$(uname -m)" == "x86_64" ]] && required+=(nasm)
|
|
missing=()
|
|
for cmd in "${required[@]}"; do
|
|
command -v -- "$cmd" >/dev/null 2>&1 || missing+=("$cmd")
|
|
done
|
|
if (( ${#missing[@]} > 0 )); then
|
|
echo "[install_native_ffmpeg] missing required tools: ${missing[*]}" >&2
|
|
echo "[install_native_ffmpeg] install the missing tools. FFMPEG_NATIVE_CC/FFMPEG_NATIVE_CXX" \
|
|
"only override compiler selection; they do not provide make, pkg-config, git, or nasm." >&2
|
|
if [[ " ${missing[*]} " == *" nasm "* ]]; then
|
|
echo "[install_native_ffmpeg] nasm is required on x86_64 for x264 SIMD; install nasm" \
|
|
"(for example via apt or conda-forge) and retry." >&2
|
|
fi
|
|
exit 1
|
|
fi
|
|
|
|
# ─── Step 0.5: destructive-path guards ────────────────────────────────────
|
|
guard_path() {
|
|
local name="$1" value="$2"
|
|
case "$value" in
|
|
"") echo "[install_native_ffmpeg] $name is empty" >&2; exit 1 ;;
|
|
"/") echo "[install_native_ffmpeg] refusing to wipe '/'" >&2; exit 1 ;;
|
|
"$HOME") echo "[install_native_ffmpeg] refusing to wipe \$HOME" >&2; exit 1 ;;
|
|
esac
|
|
[[ "$value" == /* ]] || {
|
|
echo "[install_native_ffmpeg] $name must be absolute, got: $value" >&2; exit 1; }
|
|
[[ "$value" == *ffmpeg-native* ]] || {
|
|
echo "[install_native_ffmpeg] $name must contain 'ffmpeg-native' for safety, got: $value" >&2
|
|
exit 1; }
|
|
}
|
|
guard_path INSTALL_PREFIX "$INSTALL_PREFIX"
|
|
guard_path SOURCE_DIR "$SOURCE_DIR"
|
|
|
|
# ─── Step 1: clean ────────────────────────────────────────────────────────
|
|
echo "[install_native_ffmpeg] cleaning prior install + sources"
|
|
rm -rf "$INSTALL_PREFIX" "$SOURCE_DIR/x264" "$SOURCE_DIR/ffmpeg" "$SOURCE_DIR/nv-codec-headers"
|
|
mkdir -p "$SOURCE_DIR" "$INSTALL_PREFIX/lib"
|
|
|
|
if [[ "$ENABLE_NVENC" == "1" ]]; then
|
|
if [[ ! -f "$CUDA_PREFIX/include/cuda.h" ]]; then
|
|
echo "[install_native_ffmpeg] ENABLE_NVENC=1 but cuda.h not at $CUDA_PREFIX/include/cuda.h" >&2
|
|
echo "[install_native_ffmpeg] set CUDA_PREFIX env to your CUDA toolkit root, or ENABLE_NVENC=0 to skip" >&2
|
|
exit 1
|
|
fi
|
|
echo "[install_native_ffmpeg] NVENC build enabled (CUDA_PREFIX=$CUDA_PREFIX)"
|
|
fi
|
|
|
|
# Deviation 2: force our prefix to win over conda-forge gcc's implicit
|
|
# library search (otherwise an older libx264 from conda's lib dir leaks
|
|
# into ffmpeg's link, producing a binary that needs *two* x264 SONAMEs).
|
|
export LIBRARY_PATH="$INSTALL_PREFIX/lib${LIBRARY_PATH:+:$LIBRARY_PATH}"
|
|
export LD_LIBRARY_PATH="$INSTALL_PREFIX/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
|
|
|
|
# ─── Step 2: build x264 (LTO, shared lib, no CLI) ─────────────────────────
|
|
echo "[install_native_ffmpeg] cloning x264 ($X264_REF)"
|
|
git clone --depth 1 --branch "$X264_REF" \
|
|
https://code.videolan.org/videolan/x264.git "$SOURCE_DIR/x264"
|
|
(
|
|
cd "$SOURCE_DIR/x264"
|
|
export CFLAGS="$X264_CFLAGS"
|
|
export CXXFLAGS="$X264_CFLAGS"
|
|
export LDFLAGS="$X264_LDFLAGS"
|
|
echo "[install_native_ffmpeg] configuring x264"
|
|
./configure --prefix="$INSTALL_PREFIX" --enable-shared --enable-pic --disable-cli
|
|
echo "[install_native_ffmpeg] building x264 (-j$MAKE_JOBS)"
|
|
make -j"$MAKE_JOBS"
|
|
make install
|
|
)
|
|
|
|
# ─── Step 2.5: nv-codec-headers (NVENC/NVDEC API headers, no CUDA libs) ──
|
|
# Required for ffmpeg's --enable-cuda --enable-nvenc --enable-cuvid configure
|
|
# flags. Installs ffnvcodec.pc + headers into $INSTALL_PREFIX so ffmpeg's
|
|
# pkg-config picks them up alongside libx264. The runtime libraries
|
|
# (libcuda.so, libnvcuvid.so, libnvidia-encode.so) come from the NVIDIA
|
|
# driver, not from these headers.
|
|
if [[ "$ENABLE_NVENC" == "1" ]]; then
|
|
echo "[install_native_ffmpeg] cloning nv-codec-headers ($NV_CODEC_REF)"
|
|
git clone --depth 1 --branch "$NV_CODEC_REF" \
|
|
https://git.videolan.org/git/ffmpeg/nv-codec-headers.git \
|
|
"$SOURCE_DIR/nv-codec-headers"
|
|
(
|
|
cd "$SOURCE_DIR/nv-codec-headers"
|
|
make PREFIX="$INSTALL_PREFIX" install
|
|
)
|
|
fi
|
|
|
|
# ─── Step 3: build ffmpeg (LTO, libx264, shared, optional NVENC) ──────────
|
|
echo "[install_native_ffmpeg] cloning FFmpeg ($FFMPEG_REF)"
|
|
git clone --depth 1 --branch "$FFMPEG_REF" \
|
|
https://github.com/FFmpeg/FFmpeg.git "$SOURCE_DIR/ffmpeg"
|
|
(
|
|
cd "$SOURCE_DIR/ffmpeg"
|
|
export PKG_CONFIG_PATH="$INSTALL_PREFIX/lib/pkgconfig"
|
|
export CFLAGS="$FFMPEG_CFLAGS"
|
|
export CXXFLAGS="$FFMPEG_CFLAGS"
|
|
export LDFLAGS="$FFMPEG_LDFLAGS"
|
|
[[ "$(uname -m)" == "x86_64" ]] && which nasm
|
|
pkg-config --modversion x264
|
|
echo "[install_native_ffmpeg] configuring ffmpeg"
|
|
ffmpeg_configure_flags=(
|
|
--prefix="$INSTALL_PREFIX"
|
|
--enable-gpl
|
|
--enable-libx264
|
|
--enable-lto
|
|
--enable-shared
|
|
--disable-static
|
|
--disable-debug
|
|
--disable-doc
|
|
--disable-ffplay
|
|
--disable-libxcb
|
|
--disable-xlib
|
|
--extra-cflags="$CFLAGS"
|
|
--extra-cxxflags="$CXXFLAGS"
|
|
--extra-ldflags="-L$INSTALL_PREFIX/lib $LDFLAGS"
|
|
)
|
|
if [[ "$ENABLE_NVENC" == "1" ]]; then
|
|
pkg-config --modversion ffnvcodec
|
|
ffmpeg_configure_flags+=(
|
|
--enable-cuda
|
|
--enable-nvenc
|
|
--enable-cuvid
|
|
--enable-nvdec
|
|
--extra-cflags="-I$CUDA_PREFIX/include"
|
|
--extra-ldflags="-L$CUDA_PREFIX/lib64"
|
|
)
|
|
fi
|
|
./configure "${ffmpeg_configure_flags[@]}"
|
|
echo "[install_native_ffmpeg] building ffmpeg (-j$MAKE_JOBS)"
|
|
make -j"$MAKE_JOBS"
|
|
make install
|
|
)
|
|
|
|
# ─── Step 4: sanity check ──────────────────────────────────────────────────
|
|
ffmpeg_bin="$INSTALL_PREFIX/bin/ffmpeg"
|
|
echo "[install_native_ffmpeg] verifying $ffmpeg_bin"
|
|
"$ffmpeg_bin" -hide_banner -buildconf | grep -i -E 'libx264|lto'
|
|
"$ffmpeg_bin" -hide_banner -encoders | grep -i libx264
|
|
"$ffmpeg_bin" -hide_banner -h encoder=libx264 2>&1 | grep -i preset
|
|
if [[ "$ENABLE_NVENC" == "1" ]]; then
|
|
"$ffmpeg_bin" -hide_banner -encoders | grep -E 'h264_nvenc|hevc_nvenc' || {
|
|
echo "[install_native_ffmpeg] ENABLE_NVENC=1 but built ffmpeg has no h264_nvenc/hevc_nvenc encoder" >&2
|
|
exit 1
|
|
}
|
|
fi
|
|
|
|
# ─── Step 5: emit env file ─────────────────────────────────────────────────
|
|
# FASTVIDEO_VIDEO_CODEC stays at libx264 by default to preserve runtime
|
|
# behavior; NVENC is selected at deploy time via dreamverse-deploy.sh
|
|
# --nvenc, which exports FASTVIDEO_VIDEO_CODEC=h264_nvenc.
|
|
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
env_file="$script_dir/ffmpeg-env.sh"
|
|
{
|
|
printf '#!/usr/bin/env bash\n'
|
|
printf 'export FASTVIDEO_FFMPEG_BIN=%q\n' "$ffmpeg_bin"
|
|
printf 'export FASTVIDEO_VIDEO_CODEC=libx264\n'
|
|
} > "$env_file"
|
|
chmod +x "$env_file"
|
|
|
|
echo
|
|
echo "[install_native_ffmpeg] ✓ done."
|
|
echo "[install_native_ffmpeg] binary: $ffmpeg_bin"
|
|
echo "[install_native_ffmpeg] env: $env_file"
|
|
echo "[install_native_ffmpeg] source it before running the demo:"
|
|
echo "[install_native_ffmpeg] source apps/dreamverse/scripts/ffmpeg-env.sh"
|