Skip to content

Commit 72f2555

Browse files
committed
Build windows aarch64
1 parent e73e8ca commit 72f2555

3 files changed

Lines changed: 152 additions & 52 deletions

File tree

.github/workflows/build-ffmpeg.yml

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,28 @@ jobs:
2020
fail-fast: false
2121
matrix:
2222
include:
23-
- os: macos-14
24-
arch: arm64
25-
shell: bash
26-
- os: macos-15-intel
27-
arch: x86_64
28-
shell: bash
29-
- os: ubuntu-24.04-arm
30-
arch: aarch64
31-
shell: bash
32-
- os: ubuntu-24.04
33-
arch: x86_64
34-
shell: bash
23+
# - os: macos-14
24+
# arch: arm64
25+
# shell: bash
26+
# - os: macos-15-intel
27+
# arch: x86_64
28+
# shell: bash
29+
# - os: ubuntu-24.04-arm
30+
# arch: aarch64
31+
# shell: bash
32+
# - os: ubuntu-24.04
33+
# arch: x86_64
34+
# shell: bash
3535
- os: windows-latest
3636
arch: x86_64
3737
shell: 'msys2 {0}'
3838
msys_prefix: mingw-w64-x86_64
3939
msys_system: MINGW64
40+
- os: windows-11-arm
41+
arch: arm64
42+
shell: 'msys2 {0}'
43+
msys_prefix: mingw-w64-clang-aarch64
44+
msys_system: CLANGARM64
4045
defaults:
4146
run:
4247
shell: ${{ matrix.shell }}
@@ -62,14 +67,18 @@ jobs:
6267
brew install yasm
6368
fi
6469
- uses: msys2/setup-msys2@v2
65-
if: matrix.os == 'windows-latest'
70+
if: runner.os == 'Windows'
6671
with:
67-
install: base-devel openssl-devel ${{ matrix.msys_prefix }}-gcc ${{ matrix.msys_prefix }}-nasm
72+
install: >-
73+
base-devel
74+
openssl-devel
75+
${{ matrix.msys_prefix }}-pkgconf
76+
${{ matrix.msys_system == 'CLANGARM64' && format('{0}-clang', matrix.msys_prefix) || format('{0}-gcc {0}-nasm', matrix.msys_prefix) }}
6877
msystem: ${{ matrix.msys_system }}
6978
path-type: inherit
7079
- name: Build FFmpeg
7180
env:
72-
CIBW_ARCHS: ${{ matrix.msys_prefix && 'AMD64' || matrix.arch }}
81+
CIBW_ARCHS: ${{ matrix.msys_system == 'CLANGARM64' && 'ARM64' || (matrix.msys_prefix && 'AMD64' || matrix.arch) }}
7382
CIBW_BEFORE_BUILD: python scripts/build-ffmpeg.py /tmp/vendor
7483
CIBW_BEFORE_BUILD_WINDOWS: python scripts\build-ffmpeg.py C:\cibw\vendor
7584
CIBW_BUILD: cp311-*
@@ -91,7 +100,8 @@ jobs:
91100
strategy:
92101
fail-fast: false
93102
matrix:
94-
build: [ "manylinux_", "musllinux_" ]
103+
build: [ "musllinux_" ]
104+
# build: [ "manylinux_", "musllinux_" ]
95105
# arch: [ "loongarch64", "ppc64le", "riscv64", "s390x" ]
96106
arch: [ "ppc64le" ]
97107
steps:

scripts/build-ffmpeg.py

Lines changed: 71 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,13 @@ def calculate_sha256(filename: str) -> str:
9797
requires=["nasm"],
9898
build_system="meson",
9999
),
100-
Package(
101-
name="libsvtav1",
102-
source_url="https://gitlab.com/AOMediaCodec/SVT-AV1/-/archive/v3.1.2/SVT-AV1-v3.1.2.tar.bz2",
103-
sha256="802e9bb2b14f66e8c638f54857ccb84d3536144b0ae18b9f568bbf2314d2de88",
104-
build_system="cmake",
105-
build_arguments=["-DBUILD_APPS=OFF", "-DENABLE_NASM=ON"],
106-
),
100+
# Package(
101+
# name="libsvtav1",
102+
# source_url="https://gitlab.com/AOMediaCodec/SVT-AV1/-/archive/v3.1.2/SVT-AV1-v3.1.2.tar.bz2",
103+
# sha256="802e9bb2b14f66e8c638f54857ccb84d3536144b0ae18b9f568bbf2314d2de88",
104+
# build_system="cmake",
105+
# build_arguments=["-DBUILD_APPS=OFF", "-DENABLE_NASM=ON"],
106+
# ),
107107
Package(
108108
name="vpx",
109109
source_url="https://github.com/webmproject/libvpx/archive/refs/tags/v1.15.2.tar.gz",
@@ -148,12 +148,12 @@ def calculate_sha256(filename: str) -> str:
148148
source_filename="openh264-2.6.0.tar.gz",
149149
build_system="meson",
150150
),
151-
Package(
152-
name="opencore-amr",
153-
source_url="https://downloads.sourceforge.net/project/opencore-amr/opencore-amr/opencore-amr-0.1.6.tar.gz",
154-
sha256="483eb4061088e2b34b358e47540b5d495a96cd468e361050fae615b1809dc4a1",
155-
build_arguments=["--disable-dependency-tracking"],
156-
),
151+
# Package(
152+
# name="opencore-amr",
153+
# source_url="https://downloads.sourceforge.net/project/opencore-amr/opencore-amr/opencore-amr-0.1.6.tar.gz",
154+
# sha256="483eb4061088e2b34b358e47540b5d495a96cd468e361050fae615b1809dc4a1",
155+
# build_arguments=["--disable-dependency-tracking"],
156+
# ),
157157
Package(
158158
name="x264",
159159
source_url="https://code.videolan.org/videolan/x264/-/archive/b35605ace3ddf7c1a5d67a2eb553f034aef41d55/x264-b35605ace3ddf7c1a5d67a2eb553f034aef41d55.tar.bz2",
@@ -278,13 +278,17 @@ def main():
278278
args = parser.parse_args()
279279
dest_dir = os.path.abspath(args.destination)
280280

281+
machine = platform.machine().lower()
282+
is_arm = machine in {"arm64", "aarch64"}
283+
281284
use_alsa = plat == "Linux"
282-
use_cuda = plat in {"Linux", "Windows"}
283-
use_amf = plat in {"Linux", "Windows"}
285+
# CUDA, AMF, and Intel VPL are not available on ARM64 Windows
286+
use_cuda = plat in {"Linux", "Windows"} and not is_arm
287+
use_amf = plat in {"Linux", "Windows"} and not is_arm
284288

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

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

308313
# print tool locations
309314
print("PATH", os.environ["PATH"])
310-
for tool in ["gcc", "g++", "curl", "ld", "nasm", "pkg-config"]:
315+
if is_arm:
316+
# CLANGARM64 uses clang instead of gcc
317+
tools = ["clang", "clang++", "curl", "ld", "pkg-config"]
318+
else:
319+
tools = ["gcc", "g++", "curl", "ld", "nasm", "pkg-config"]
320+
for tool in tools:
311321
run(["where", tool])
312322

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

316326
# build tools
317327
build_tools = []
318-
if "nasm" not in available_tools and platform.machine() not in {"arm64", "aarch64"}:
328+
if "nasm" not in available_tools and platform.machine().lower() not in {"arm64", "aarch64"}:
319329
build_tools.append(
320330
Package(
321331
name="nasm",
@@ -344,11 +354,11 @@ def main():
344354
"--enable-gnutls" if use_gnutls else "--disable-gnutls",
345355
"--enable-libdav1d",
346356
"--enable-libmp3lame",
347-
"--enable-libopencore-amrnb",
348-
"--enable-libopencore-amrwb",
357+
# "--enable-libopencore-amrnb",
358+
# "--enable-libopencore-amrwb",
349359
"--enable-libopus",
350360
"--enable-libspeex",
351-
"--enable-libsvtav1",
361+
# "--enable-libsvtav1",
352362
"--enable-libvorbis",
353363
"--enable-libvpx",
354364
"--enable-libwebp",
@@ -388,6 +398,15 @@ def main():
388398
]
389399
)
390400

401+
if plat == "Windows" and is_arm:
402+
ffmpeg_package.build_arguments.extend(
403+
[
404+
"--cc=clang",
405+
"--cxx=clang++",
406+
"--arch=aarch64",
407+
]
408+
)
409+
391410
ffmpeg_package.build_arguments.extend(
392411
[
393412
"--disable-encoder=avui,dca,mlp,opus,s302m,sonic,sonic_ls,truehd,vorbis",
@@ -412,6 +431,14 @@ def main():
412431
packages += codec_group
413432
packages += [ffmpeg_package]
414433

434+
# Disable runtime CPU detection for opus on Windows ARM64
435+
# (no CPU detection method available for this platform)
436+
if plat == "Windows" and is_arm:
437+
for pkg in packages:
438+
if pkg.name == "opus":
439+
pkg.build_arguments.append("--disable-rtcd")
440+
break
441+
415442
download_tars(build_tools + packages)
416443
for tool in build_tools:
417444
builder.build(tool, for_builder=True)
@@ -437,19 +464,33 @@ def main():
437464
)
438465

439466
# copy some libraries provided by mingw
467+
machine = platform.machine().lower()
468+
is_arm64 = machine in {"arm64", "aarch64"}
469+
compiler = "clang" if is_arm64 else "gcc"
440470
mingw_bindir = os.path.dirname(
441-
subprocess.run(["where", "gcc"], check=True, stdout=subprocess.PIPE)
471+
subprocess.run(["where", compiler], check=True, stdout=subprocess.PIPE)
442472
.stdout.decode()
443473
.splitlines()[0]
444474
.strip()
445475
)
446-
for name in (
447-
"libgcc_s_seh-1.dll",
448-
"libiconv-2.dll",
449-
"libstdc++-6.dll",
450-
"libwinpthread-1.dll",
451-
"zlib1.dll",
452-
):
476+
if is_arm64:
477+
# CLANGARM64 uses clang/libc++ instead of gcc/libstdc++
478+
dll_names = (
479+
"libc++.dll",
480+
"libiconv-2.dll",
481+
"libunwind.dll",
482+
"libwinpthread-1.dll",
483+
"zlib1.dll",
484+
)
485+
else:
486+
dll_names = (
487+
"libgcc_s_seh-1.dll",
488+
"libiconv-2.dll",
489+
"libstdc++-6.dll",
490+
"libwinpthread-1.dll",
491+
"zlib1.dll",
492+
)
493+
for name in dll_names:
453494
shutil.copy(os.path.join(mingw_bindir, name), os.path.join(dest_dir, "bin"))
454495

455496
# find libraries

scripts/cibuildpkg.py

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ def run(cmd: list[str], env=None) -> None:
7474
subprocess.run(cmd, check=True, env=env, stderr=subprocess.PIPE, text=True)
7575
except subprocess.CalledProcessError as e:
7676
print(f"stderr: {e.stderr}")
77+
# Print config.log tail if it exists (for ffmpeg configure debugging)
78+
config_log = os.path.join(os.getcwd(), "ffbuild", "config.log")
79+
if os.path.exists(config_log):
80+
print(f"\n=== Tail of {config_log} ===")
81+
with open(config_log, "r") as f:
82+
lines = f.readlines()
83+
print("".join(lines[-100:]))
7784
raise e
7885

7986

@@ -204,20 +211,31 @@ def _build_with_autoconf(self, package: Package, for_builder: bool) -> None:
204211
env = self._environment(for_builder=for_builder)
205212
prefix = self._prefix(for_builder=for_builder)
206213
configure_args = [
207-
"--disable-static",
208214
"--enable-shared",
209215
"--libdir=" + self._mangle_path(os.path.join(prefix, "lib")),
210216
"--prefix=" + self._mangle_path(prefix),
211217
]
212218

219+
if package.name == "x264":
220+
# Disable asm on Windows ARM64 (no nasm available)
221+
if platform.system() == "Windows" and platform.machine().lower() in {"arm64", "aarch64"}:
222+
configure_args.append("--disable-asm")
223+
# Specify host to ensure correct resource compiler target
224+
configure_args.append("--host=aarch64-w64-mingw32")
225+
213226
if package.name == "vpx":
214227
if platform.system() == "Darwin":
215228
if platform.machine() == "arm64":
216229
configure_args += ["--target=arm64-darwin20-gcc"]
217230
elif platform.machine() == "x86_64":
218231
configure_args += ["--target=x86_64-darwin20-gcc"]
219232
elif platform.system() == "Windows":
220-
configure_args += ["--target=x86_64-win64-gcc"]
233+
if platform.machine().lower() in {"arm64", "aarch64"}:
234+
configure_args += ["--target=arm64-win64-gcc"]
235+
# Link pthread for ARM64 Windows
236+
prepend_env(env, "LDFLAGS", "-lpthread")
237+
else:
238+
configure_args += ["--target=x86_64-win64-gcc"]
221239
elif platform.system() == "Linux":
222240
if "RUNNER_ARCH" in os.environ:
223241
prepend_env(env, "CFLAGS", "-pthread")
@@ -229,9 +247,24 @@ def _build_with_autoconf(self, package: Package, for_builder: bool) -> None:
229247
prepend_env(
230248
env,
231249
"PKG_CONFIG_PATH",
232-
"/c/msys64/usr/lib/pkgconfig",
233-
separator=":",
250+
"C:/msys64/usr/lib/pkgconfig",
251+
separator=";",
252+
)
253+
# Debug: print pkg-config info
254+
print(f"PKG_CONFIG_PATH: {env.get('PKG_CONFIG_PATH')}")
255+
print(f"PKG_CONFIG: {env.get('PKG_CONFIG')}")
256+
import glob
257+
pc_files = glob.glob(os.path.join(prefix, "lib", "pkgconfig", "*.pc"))
258+
print(f"PC files in {prefix}/lib/pkgconfig: {pc_files}")
259+
# Test pkgconf directly
260+
import subprocess
261+
result = subprocess.run(
262+
["pkgconf", "--modversion", "dav1d"],
263+
env=env,
264+
capture_output=True,
265+
text=True
234266
)
267+
print(f"pkgconf dav1d test: returncode={result.returncode}, stdout={result.stdout}, stderr={result.stderr}")
235268

236269
# build package
237270
os.makedirs(package_build_path, exist_ok=True)
@@ -419,18 +452,34 @@ def _environment(self, *, for_builder: bool) -> dict[str, str]:
419452
prepend_env(
420453
env, "LDFLAGS", "-L" + self._mangle_path(os.path.join(prefix, "lib"))
421454
)
455+
# Use ; as separator on Windows, : on Unix
456+
# Don't mangle PKG_CONFIG_PATH on Windows - pkgconf expects native paths
457+
pkg_config_sep = ";" if platform.system() == "Windows" else ":"
458+
pkg_config_path = os.path.join(prefix, "lib", "pkgconfig")
459+
if platform.system() != "Windows":
460+
pkg_config_path = self._mangle_path(pkg_config_path)
422461
prepend_env(
423462
env,
424463
"PKG_CONFIG_PATH",
425-
self._mangle_path(os.path.join(prefix, "lib", "pkgconfig")),
426-
separator=":",
464+
pkg_config_path,
465+
separator=pkg_config_sep,
427466
)
428467

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

473+
# Use clang on Windows ARM64 (CLANGARM64 environment)
474+
if platform.system() == "Windows" and platform.machine().lower() in {"arm64", "aarch64"}:
475+
env["CC"] = "clang"
476+
env["CXX"] = "clang++"
477+
# Use LLVM windres to compile Windows resources for ARM64
478+
env["RC"] = "llvm-windres"
479+
env["WINDRES"] = "llvm-windres"
480+
# Use MinGW pkgconf instead of MSYS pkg-config
481+
env["PKG_CONFIG"] = "pkgconf"
482+
434483
return env
435484

436485
def _mangle_path(self, path: str) -> str:

0 commit comments

Comments
 (0)