Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions .github/workflows/build-ffmpeg.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,19 @@ jobs:
shell: 'msys2 {0}'
msys_prefix: mingw-w64-x86_64
msys_system: MINGW64
- os: windows-11-arm
arch: arm64
shell: 'msys2 {0}'
msys_prefix: mingw-w64-clang-aarch64
msys_system: CLANGARM64
defaults:
run:
shell: ${{ matrix.shell }}
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: "3.13"
python-version: "3.14"
- name: Set deployment target
if: runner.os == 'macOS'
run: |
Expand All @@ -62,14 +67,18 @@ jobs:
brew install yasm
fi
- uses: msys2/setup-msys2@v2
if: matrix.os == 'windows-latest'
if: runner.os == 'Windows'
with:
install: base-devel openssl-devel ${{ matrix.msys_prefix }}-gcc ${{ matrix.msys_prefix }}-nasm
install: >-
base-devel
openssl-devel
${{ matrix.msys_prefix }}-pkgconf
${{ matrix.msys_system == 'CLANGARM64' && format('{0}-clang', matrix.msys_prefix) || format('{0}-gcc {0}-nasm', matrix.msys_prefix) }}
msystem: ${{ matrix.msys_system }}
path-type: inherit
- name: Build FFmpeg
env:
CIBW_ARCHS: ${{ matrix.msys_prefix && 'AMD64' || matrix.arch }}
CIBW_ARCHS: ${{ matrix.msys_system == 'CLANGARM64' && 'ARM64' || (matrix.msys_prefix && 'AMD64' || matrix.arch) }}
CIBW_BEFORE_BUILD: python scripts/build-ffmpeg.py /tmp/vendor
CIBW_BEFORE_BUILD_WINDOWS: python scripts\build-ffmpeg.py C:\cibw\vendor
CIBW_BUILD: cp311-*
Expand Down
69 changes: 55 additions & 14 deletions scripts/build-ffmpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,13 +278,17 @@ def main():
args = parser.parse_args()
dest_dir = os.path.abspath(args.destination)

machine = platform.machine().lower()
is_arm = machine in {"arm64", "aarch64"}

use_alsa = plat == "Linux"
use_cuda = plat in {"Linux", "Windows"}
use_amf = plat in {"Linux", "Windows"}
# CUDA, AMF, and Intel VPL are not available on ARM64 Windows
use_cuda = plat in {"Linux", "Windows"} and not is_arm
use_amf = plat in {"Linux", "Windows"} and not is_arm

# Use Intel VPL (Video Processing Library) if supported to enable Intel QSV (Quick Sync Video)
# hardware encoders/decoders on modern integrated and discrete Intel GPUs.
use_libvpl = plat in {"Linux", "Windows"}
use_libvpl = plat in {"Linux", "Windows"} and not is_arm

# Use GnuTLS only on Linux, FFmpeg has native TLS backends for macOS and Windows.
use_gnutls = plat == "Linux"
Expand All @@ -303,19 +307,25 @@ def main():
# install packages
available_tools = set()
if plat == "Windows":
available_tools.update(["nasm"])
if not is_arm:
available_tools.update(["nasm"])

# print tool locations
print("PATH", os.environ["PATH"])
for tool in ["gcc", "g++", "curl", "ld", "nasm", "pkg-config"]:
if is_arm:
# CLANGARM64 uses clang instead of gcc
tools = ["clang", "clang++", "curl", "ld", "pkg-config"]
else:
tools = ["gcc", "g++", "curl", "ld", "nasm", "pkg-config"]
for tool in tools:
run(["where", tool])

with log_group("install python packages"):
run(["pip", "install", "cmake==3.31.10", "meson", "ninja"])

# build tools
build_tools = []
if "nasm" not in available_tools and platform.machine() not in {"arm64", "aarch64"}:
if "nasm" not in available_tools and platform.machine().lower() not in {"arm64", "aarch64"}:
build_tools.append(
Package(
name="nasm",
Expand Down Expand Up @@ -388,6 +398,15 @@ def main():
]
)

if plat == "Windows" and is_arm:
ffmpeg_package.build_arguments.extend(
[
"--cc=clang",
"--cxx=clang++",
"--arch=aarch64",
]
)

ffmpeg_package.build_arguments.extend(
[
"--disable-encoder=avui,dca,mlp,opus,s302m,sonic,sonic_ls,truehd,vorbis",
Expand All @@ -412,6 +431,14 @@ def main():
packages += codec_group
packages += [ffmpeg_package]

# Disable runtime CPU detection for opus on Windows ARM64
# (no CPU detection method available for this platform)
if plat == "Windows" and is_arm:
for pkg in packages:
if pkg.name == "opus":
pkg.build_arguments.append("--disable-rtcd")
break

download_tars(build_tools + packages)
for tool in build_tools:
builder.build(tool, for_builder=True)
Expand All @@ -437,19 +464,33 @@ def main():
)

# copy some libraries provided by mingw
machine = platform.machine().lower()
is_arm64 = machine in {"arm64", "aarch64"}
compiler = "clang" if is_arm64 else "gcc"
mingw_bindir = os.path.dirname(
subprocess.run(["where", "gcc"], check=True, stdout=subprocess.PIPE)
subprocess.run(["where", compiler], check=True, stdout=subprocess.PIPE)
.stdout.decode()
.splitlines()[0]
.strip()
)
for name in (
"libgcc_s_seh-1.dll",
"libiconv-2.dll",
"libstdc++-6.dll",
"libwinpthread-1.dll",
"zlib1.dll",
):
if is_arm64:
# CLANGARM64 uses clang/libc++ instead of gcc/libstdc++
dll_names = (
"libc++.dll",
"libiconv-2.dll",
"libunwind.dll",
"libwinpthread-1.dll",
"zlib1.dll",
)
else:
dll_names = (
"libgcc_s_seh-1.dll",
"libiconv-2.dll",
"libstdc++-6.dll",
"libwinpthread-1.dll",
"zlib1.dll",
)
for name in dll_names:
shutil.copy(os.path.join(mingw_bindir, name), os.path.join(dest_dir, "bin"))

# find libraries
Expand Down
57 changes: 51 additions & 6 deletions scripts/cibuildpkg.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ def run(cmd: list[str], env=None) -> None:
subprocess.run(cmd, check=True, env=env, stderr=subprocess.PIPE, text=True)
except subprocess.CalledProcessError as e:
print(f"stderr: {e.stderr}")
# Print config.log tail if it exists (for ffmpeg configure debugging)
config_log = os.path.join(os.getcwd(), "ffbuild", "config.log")
if os.path.exists(config_log):
print(f"\n=== Tail of {config_log} ===")
with open(config_log, "r") as f:
lines = f.readlines()
print("".join(lines[-100:]))
raise e


Expand Down Expand Up @@ -204,20 +211,31 @@ def _build_with_autoconf(self, package: Package, for_builder: bool) -> None:
env = self._environment(for_builder=for_builder)
prefix = self._prefix(for_builder=for_builder)
configure_args = [
"--disable-static",
"--enable-shared",
"--libdir=" + self._mangle_path(os.path.join(prefix, "lib")),
"--prefix=" + self._mangle_path(prefix),
]

if package.name == "x264":
# Disable asm on Windows ARM64 (no nasm available)
if platform.system() == "Windows" and platform.machine().lower() in {"arm64", "aarch64"}:
configure_args.append("--disable-asm")
# Specify host to ensure correct resource compiler target
configure_args.append("--host=aarch64-w64-mingw32")

if package.name == "vpx":
if platform.system() == "Darwin":
if platform.machine() == "arm64":
configure_args += ["--target=arm64-darwin20-gcc"]
elif platform.machine() == "x86_64":
configure_args += ["--target=x86_64-darwin20-gcc"]
elif platform.system() == "Windows":
configure_args += ["--target=x86_64-win64-gcc"]
if platform.machine().lower() in {"arm64", "aarch64"}:
configure_args += ["--target=arm64-win64-gcc"]
# Link pthread for ARM64 Windows
prepend_env(env, "LDFLAGS", "-lpthread")
else:
configure_args += ["--target=x86_64-win64-gcc"]
elif platform.system() == "Linux":
if "RUNNER_ARCH" in os.environ:
prepend_env(env, "CFLAGS", "-pthread")
Expand All @@ -229,9 +247,24 @@ def _build_with_autoconf(self, package: Package, for_builder: bool) -> None:
prepend_env(
env,
"PKG_CONFIG_PATH",
"/c/msys64/usr/lib/pkgconfig",
separator=":",
"C:/msys64/usr/lib/pkgconfig",
separator=";",
)
# Debug: print pkg-config info
print(f"PKG_CONFIG_PATH: {env.get('PKG_CONFIG_PATH')}")
print(f"PKG_CONFIG: {env.get('PKG_CONFIG')}")
import glob
pc_files = glob.glob(os.path.join(prefix, "lib", "pkgconfig", "*.pc"))
print(f"PC files in {prefix}/lib/pkgconfig: {pc_files}")
# Test pkgconf directly
import subprocess
result = subprocess.run(
["pkgconf", "--modversion", "dav1d"],
env=env,
capture_output=True,
text=True
)
print(f"pkgconf dav1d test: returncode={result.returncode}, stdout={result.stdout}, stderr={result.stderr}")

# build package
os.makedirs(package_build_path, exist_ok=True)
Expand Down Expand Up @@ -419,18 +452,30 @@ def _environment(self, *, for_builder: bool) -> dict[str, str]:
prepend_env(
env, "LDFLAGS", "-L" + self._mangle_path(os.path.join(prefix, "lib"))
)
# Use ; as separator on Windows, : on Unix
# Don't mangle PKG_CONFIG_PATH on Windows - pkgconf expects native paths
pkg_config_sep = ";" if platform.system() == "Windows" else ":"
pkg_config_path = os.path.join(prefix, "lib", "pkgconfig")
if platform.system() != "Windows":
pkg_config_path = self._mangle_path(pkg_config_path)
prepend_env(
env,
"PKG_CONFIG_PATH",
self._mangle_path(os.path.join(prefix, "lib", "pkgconfig")),
separator=":",
pkg_config_path,
separator=pkg_config_sep,
)

if platform.system() == "Darwin" and not for_builder:
arch_flags = os.environ["ARCHFLAGS"]
for var in ["CFLAGS", "CXXFLAGS", "LDFLAGS"]:
prepend_env(env, var, arch_flags)

if platform.system() == "Windows" and platform.machine().lower() in {"arm64", "aarch64"}:
env["CC"] = "clang"
env["CXX"] = "clang++"
env["RC"] = "llvm-windres"
env["WINDRES"] = "llvm-windres"

return env

def _mangle_path(self, path: str) -> str:
Expand Down
Loading