#!/bin/bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" BUILD_DIR="$SCRIPT_DIR/ffmpeg_build" SRC_DIR="$BUILD_DIR/src" INSTALL_DIR="$BUILD_DIR/install" JOBS=$(nproc 2>/dev/null || echo 4) AMF_HEADERS_VERSION="${AMF_HEADERS_VERSION:-1.5.3}" FFMPEG_PROFILE_MARKER="$INSTALL_DIR/share/utun-chatgui-ffmpeg-minimal-v4" case "$(uname -s)" in Linux) PLATFORM=linux ;; MINGW*|MSYS*|CYGWIN*) PLATFORM=windows ;; *) echo "Unsupported platform: $(uname -s)"; exit 1 ;; esac log() { echo "[FFmpeg] $1"; } die() { echo "[FFmpeg] ERROR: $1" >&2; exit 1; } check_cmd() { command -v "$1" >/dev/null 2>&1 || die "$1 not found. Install it first."; } do_clone() { local name="$1" url="$2" tag="$3" if [ ! -d "$SRC_DIR/$name" ]; then log "Clone $name..." for i in 1 2 3; do git clone --depth 1 --branch "$tag" "$url" "$SRC_DIR/$name" 2>&1 && break log "Clone attempt $i failed, retrying..." rm -rf "$SRC_DIR/$name" sleep 2 done [ -d "$SRC_DIR/$name/.git" ] || die "Failed to clone $name after 3 attempts" else log "Skip clone $name: already exists" fi } # ============================================================================ # Step 0: System dependencies # ============================================================================ if [ "$PLATFORM" = "linux" ]; then MISSING="" for pkg in build-essential cmake nasm pkg-config git; do dpkg -s "$pkg" >/dev/null 2>&1 || MISSING="$MISSING $pkg" done if [ -n "$MISSING" ]; then log "Installing build tools:$MISSING" sudo apt update -qq && sudo apt install -y $MISSING fi HW_DEPS="libva-dev libvpl-dev" for pkg in $HW_DEPS; do dpkg -s "$pkg" >/dev/null 2>&1 || { log "Installing $pkg (HW accel)"; sudo apt install -y $pkg; } done else check_cmd pacman for pkg in mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-nasm \ mingw-w64-ucrt-x86_64-pkg-config mingw-w64-ucrt-x86_64-libvpl; do pacman -Qi "$pkg" >/dev/null 2>&1 || { log "Installing $pkg"; pacman -S --noconfirm "$pkg"; } done fi check_cmd cmake check_cmd nasm check_cmd pkg-config check_cmd git check_cmd curl check_cmd patch # ============================================================================ # Step 1: Codec libraries (static .a, built with -fPIC) # ============================================================================ mkdir -p "$SRC_DIR" "$INSTALL_DIR/lib/pkgconfig" export PKG_CONFIG_PATH="$INSTALL_DIR/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" # --- x264 (H.264 encoder) --- if [ ! -f "$INSTALL_DIR/lib/pkgconfig/x264.pc" ]; then do_clone x264 https://code.videolan.org/videolan/x264.git stable cd "$SRC_DIR/x264" log "Configure x264..." ./configure --prefix="$INSTALL_DIR" --enable-static --disable-shared \ --disable-opencl --disable-avs --disable-swscale --disable-lavf \ --disable-ffms --disable-gpac --disable-lsmash \ --extra-cflags="-fPIC" --disable-cli log "Build x264..." make -j"$JOBS" make install log "x264 OK" else log "Skip x264: already built" fi # --- x265 (H.265/HEVC encoder) --- if [ ! -f "$INSTALL_DIR/lib/pkgconfig/x265.pc" ]; then do_clone x265 https://bitbucket.org/multicoreware/x265_git.git master if [ -f "$SRC_DIR/x265/.git/shallow" ]; then log "Fetch x265 history and release tags..." git -C "$SRC_DIR/x265" fetch --unshallow --tags origin fi X265_TAG=$(git -C "$SRC_DIR/x265" describe --abbrev=0 --tags 2>/dev/null || true) [ -n "$X265_TAG" ] || die "Cannot determine x265 release tag; check the x265 Git remote and tags" cd "$SRC_DIR/x265" mkdir -p build && cd build log "Configure x265..." cmake ../source -G "Unix Makefiles" \ -DCMAKE_INSTALL_PREFIX="$INSTALL_DIR" \ -DBUILD_SHARED_LIBS=OFF \ -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ -DENABLE_CLI=OFF -DENABLE_SHARED=OFF \ -DHIGH_BIT_DEPTH=OFF -DENABLE_HDR10_PLUS=OFF log "Build x265..." cmake --build . -j"$JOBS" cmake --install . cat > "$INSTALL_DIR/lib/pkgconfig/x265.pc" </dev/null | grep -Fxq "$LIBAOM_TAG"; then log "Update existing libaom source to $LIBAOM_TAG..." git -C "$SRC_DIR/libaom" fetch --depth 1 origin "refs/tags/$LIBAOM_TAG:refs/tags/$LIBAOM_TAG" git -C "$SRC_DIR/libaom" checkout --detach "$LIBAOM_TAG" fi cd "$SRC_DIR/libaom" mkdir -p build && cd build log "Configure libaom..." cmake .. -G "Unix Makefiles" \ -DCMAKE_INSTALL_PREFIX="$INSTALL_DIR" \ -DBUILD_SHARED_LIBS=OFF -DENABLE_TESTS=OFF \ -DENABLE_TOOLS=OFF -DENABLE_DOCS=OFF \ -DENABLE_NASM=ON log "Build libaom..." cmake --build . -j"$JOBS" cmake --install . log "libaom OK" else log "Skip libaom: already built" fi # --- nv-codec-headers (NVIDIA NVENC/NVDEC headers, no build needed) --- if [ ! -f "$INSTALL_DIR/include/ffnvcodec/nvEncodeAPI.h" ]; then do_clone nv-codec-headers https://github.com/FFmpeg/nv-codec-headers.git master cd "$SRC_DIR/nv-codec-headers" log "Install nv-codec-headers..." make install PREFIX="$INSTALL_DIR" log "nv-codec-headers OK" else log "Skip nv-codec-headers: already installed" fi # ============================================================================ # Step 2: FFmpeg # ============================================================================ if [ "$PLATFORM" = "windows" ] && [ ! -f "$INSTALL_DIR/include/AMF/core/Factory.h" ]; then AMF_ARCHIVE="$SRC_DIR/AMF-headers-v$AMF_HEADERS_VERSION.tar.gz" AMF_EXTRACT_DIR="$SRC_DIR/amf-headers-$AMF_HEADERS_VERSION" mkdir -p "$AMF_EXTRACT_DIR" "$INSTALL_DIR/include/AMF" if [ ! -f "$AMF_ARCHIVE" ]; then log "Download AMD AMF headers v$AMF_HEADERS_VERSION..." curl -fL --retry 3 -o "$AMF_ARCHIVE" \ "https://github.com/GPUOpen-LibrariesAndSDKs/AMF/releases/download/v$AMF_HEADERS_VERSION/AMF-headers-v$AMF_HEADERS_VERSION.tar.gz" fi log "Extract AMD AMF headers..." tar -xzf "$AMF_ARCHIVE" -C "$AMF_EXTRACT_DIR" AMF_FACTORY_HEADER=$(find "$AMF_EXTRACT_DIR" -type f -path '*/AMF/core/Factory.h' -print -quit) [ -n "$AMF_FACTORY_HEADER" ] || die "AMF headers archive does not contain AMF/core/Factory.h" AMF_HEADER_ROOT="${AMF_FACTORY_HEADER%/AMF/core/Factory.h}" cp -a "$AMF_HEADER_ROOT/AMF/." "$INSTALL_DIR/include/AMF/" log "AMD AMF headers installed in $INSTALL_DIR/include/AMF" fi if [ -f "$INSTALL_DIR/lib/pkgconfig/libavcodec.pc" ] && [ -f "$FFMPEG_PROFILE_MARKER" ]; then log "FFmpeg already built, skipping." log "Done: $INSTALL_DIR" exit 0 fi FFMPEG_SRC="$SRC_DIR/ffmpeg" if [ ! -d "$FFMPEG_SRC" ]; then log "Download FFmpeg 7.1.2..." FFMPEG_TARBALL="$SRC_DIR/ffmpeg-7.1.2.tar.xz" if [ ! -f "$FFMPEG_TARBALL" ]; then for url in \ "https://ffmpeg.org/releases/ffmpeg-7.1.2.tar.xz" \ "https://github.com/FFmpeg/FFmpeg/archive/refs/tags/n7.1.2.tar.gz"; do curl -L --retry 3 -o "$FFMPEG_TARBALL" "$url" 2>&1 && break log "Download from $url failed, trying next..." rm -f "$FFMPEG_TARBALL" done [ -f "$FFMPEG_TARBALL" ] || die "Failed to download FFmpeg tarball" log "Download OK ($(du -h "$FFMPEG_TARBALL" | cut -f1))" fi log "Extract FFmpeg..." mkdir -p "$FFMPEG_SRC" tar -xf "$FFMPEG_TARBALL" -C "$FFMPEG_SRC" --strip-components=1 rm -f "$FFMPEG_TARBALL" fi cd "$SRC_DIR/ffmpeg" AMF_QP_PATCH="$SCRIPT_DIR/patches/ffmpeg-7.1.2-amf-live-qp.patch" if ! grep -q 'utun_qp_applied' libavcodec/amfenc.c; then log "Apply live AMF QP ceiling support..." patch --batch --forward -p1 < "$AMF_QP_PATCH" || die "Failed to apply live AMF QP ceiling patch" fi log "Configure FFmpeg..." CONFIGURE_ARGS=( --prefix="$INSTALL_DIR" --disable-everything --disable-network --enable-avcodec --enable-avformat --enable-avutil --enable-swscale --enable-swresample --enable-shared --disable-static --enable-gpl --enable-version3 --pkg-config-flags=--static --extra-cflags="-I$INSTALL_DIR/include" --extra-ldflags="-L$INSTALL_DIR/lib" --enable-libx264 --enable-libx265 --enable-libvpx --enable-libaom --enable-libvpl --enable-nvenc --enable-demuxer=avi --enable-demuxer=matroska --enable-demuxer=mov --enable-protocol=file --enable-parsers # hevc_metadata подключает HEVC CBS parser, необходимый также для trace_headers. --enable-bsf=h264_mp4toannexb --enable-bsf=hevc_mp4toannexb --enable-bsf=trace_headers --enable-bsf=hevc_metadata --disable-programs --disable-doc --disable-avdevice --disable-avfilter --disable-postproc --disable-muxers --disable-ffprobe --disable-ffplay --disable-ffmpeg --disable-debug --enable-optimizations ) if [ "$PLATFORM" = "windows" ]; then CONFIGURE_ARGS+=(--extra-libs="-lpthread -lm") else CONFIGURE_ARGS+=(--extra-libs="-lpthread -lm -ldl") fi DECODERS=(h264 hevc av1 vp8 vp9 mpeg4 mpeg2video mpeg1video mjpeg h263 msmpeg4v3 wmv1 wmv2 vc1 aac mp3 opus vorbis flac ac3 eac3 alac wmav1 wmav2 pcm_s16le pcm_s24le pcm_s32le pcm_u8 pcm_f32le) ENCODERS=(libx264 libx265 libvpx_vp9 libaom_av1 h264_nvenc hevc_nvenc av1_nvenc h264_qsv hevc_qsv vp9_qsv av1_qsv) for decoder in "${DECODERS[@]}"; do CONFIGURE_ARGS+=("--enable-decoder=$decoder"); done for encoder in "${ENCODERS[@]}"; do CONFIGURE_ARGS+=("--enable-encoder=$encoder"); done if [ "$PLATFORM" = "windows" ]; then CONFIGURE_ARGS+=( --enable-amf --enable-d3d11va --enable-encoder=h264_amf --enable-encoder=hevc_amf --enable-encoder=av1_amf ) else CONFIGURE_ARGS+=( --enable-vaapi --enable-encoder=h264_vaapi --enable-encoder=hevc_vaapi --enable-encoder=vp9_vaapi --enable-encoder=av1_vaapi ) fi "./configure" "${CONFIGURE_ARGS[@]}" grep -Eq '^#define CONFIG_CBS_H265 1$' config.h || die "FFmpeg configure did not enable the HEVC header parser" grep -Eq '^#define CONFIG_TRACE_HEADERS_BSF 1$' config_components.h || die "FFmpeg configure did not enable trace_headers" if [ "$PLATFORM" = "windows" ]; then for encoder in CONFIG_H264_AMF_ENCODER CONFIG_HEVC_AMF_ENCODER; do grep -Eq "^#define $encoder 1$" config_components.h \ || die "FFmpeg configure did not enable $encoder; check AMF headers and config.log" done fi log "Build FFmpeg..." make clean make -j"$JOBS" make install mkdir -p "$(dirname "$FFMPEG_PROFILE_MARKER")" touch "$FFMPEG_PROFILE_MARKER" log "===============================================================" log "FFmpeg build SUCCESS" log "Install prefix: $INSTALL_DIR" log "Libraries: $(ls "$INSTALL_DIR/lib/"*.so* "$INSTALL_DIR/lib/"*.dll.a "$INSTALL_DIR/bin/"*.dll 2>/dev/null | tr '\n' ' ')" log "==============================================================="