Slim FFmpeg xcframeworks for Apple platforms.
Demux, decode, and a thin HLS-fMP4 mux path for AVPlayer bridging. No network stack, no CLI binaries.
Full FFmpeg builds for iOS land at 40-70 MB because they bundle a TLS stack, encoders, filters, and a dozen protocols your app will never use. For a player, most of that is dead weight. Apple already ships HTTP/3, URLSession, Network.framework, VideoToolbox and AVFoundation. So this build strips out everything you don't need and keeps what you do.
~10 MB per architecture, zero network dependencies, one build script.
| Library | What it does |
|---|---|
| libavformat | Demux MKV, MP4, WebM, MPEG-TS, MPEG-PS (VOB / DVD), DASH, AVI, OGG, FLV, plus raw elementary streams |
| libavcodec | Decode video + audio (with VideoToolbox bridge) |
| libavutil | Shared primitives |
| libswresample | Audio resampling / channel remap / format convert |
| libswscale | Pixel-format convert (YUV → NV12 / P010) for the SW-decode path |
| libavfilter | Trimmed filter set: zscale + tonemap + colorspace for HDR → SDR still extraction, bwdif + yadif for deinterlacing on the SW-decode path |
| dav1d | Fast AV1 software decoder (separate xcframework) |
| zimg | zscale's resampling / colorspace backend (separate xcframework, link-only) |
Anything the app layer should already handle or doesn't need:
- Network / TLS: FFmpeg reads from an
avio_alloc_contextcallback, you wireURLSessionto it - Encoders, except FLAC and EAC3 (kept for the audio bridge that re-encodes non-streamable sources like TrueHD / DTS / DTS-HD MA. FLAC for the lossless 7.1 path, EAC3 5.1 for the default soundbar-compat path that surfaces surround via HDMI bitstream tunnel)
- Muxers, except MP4 / MOV / HLS (kept for the HLS-fMP4 producer that wraps streams for AVPlayer)
- libavdevice (libavfilter is included but trimmed to a handful of filters, see In)
- Most filters: libavfilter ships only buffer / buffersink / format / scale / zscale / tonemap / colorspace / bwdif / yadif
- Programs (
ffmpeg,ffplay,ffprobe) - Hardware accel layers other than VideoToolbox
- Text subtitle rendering (do that in SwiftUI)
./build.sh # all platforms, dynamic frameworks (the shipped shape)
./build.sh static # static variant, for apps that can meet LGPL 6(a) themselves
./build.sh package # repackage frameworks without recompiling
./build.sh clean # wipe everythingNeeds Xcode 16+ and roughly 10-30 minutes depending on your machine. All sources (FFmpeg, dav1d, zimg, libzvbi) clone on first run.
Output lands in Sources/ as xcframeworks, ready to consume via Swift Package Manager. The shipped xcframeworks contain dynamic frameworks (dylib-in-framework, @rpath install names); Xcode embeds and signs them in the app bundle automatically when you link the package. That is what keeps the LGPL relink requirement satisfiable for closed-source apps, see License below.
// Package.swift
dependencies: [
.package(url: "https://github.com/superuser404notfound/FFmpegBuild", from: "1.0.0")
]
// Target:
.product(name: "FFmpegBuild", package: "FFmpegBuild")Pin branch: "main" instead of a version if you want to track the latest rebuilds (that is how AetherEngine consumes it).
Then import the modules you need: Libavformat, Libavcodec, Libavutil, Libswresample, Libswscale, Libavfilter, Libdav1d. (Libzimg is a link-only backend for zscale; you don't import it directly.) The umbrella FFmpegBuild product links all of them plus the system frameworks (AudioToolbox, CoreMedia, CoreVideo, VideoToolbox) in one shot.
- Video (hardware via VideoToolbox): H.264, HEVC up to Main10 (HDR10 / DV Profile 8)
- Video (software): AV1 (dav1d), VP9, VP8, MPEG-2, MPEG-4, VC-1
- Audio: AAC, AC3, EAC3 (incl. JOC detection for Atmos), FLAC, MP2, MP3, Opus, Vorbis, TrueHD, MLP, DTS, ALAC, PCM (incl. Blu-ray LPCM via
pcm_bluray) - Subtitles: SRT, ASS, SSA, WebVTT, PGS, DVB, DVD
HDR metadata (BT.2020, SMPTE ST 2084 / PQ, HLG, DV RPU) is preserved end-to-end so the decode pipeline can tag frames correctly.
Release configuration, dynamic framework binaries as embedded in the app:
| Target | FFmpeg | dav1d | Total |
|---|---|---|---|
| iOS / tvOS arm64 | ~8.7 MB | ~0.8 MB | ~9.5 MB |
| macOS universal (arm64 + x86_64) | ~18.1 MB | ~2.4 MB | ~20.5 MB |
Assembly-optimized paths are enabled where the Apple toolchain permits.
This package is vibe-coded, assembled and maintained by Vincent Herbst in close pair-programming with Claude (Anthropic). The commit log is the receipt: nearly every commit carries a Co-Authored-By: Claude trailer.
LGPL-2.1-or-later (LICENSE), matching upstream FFmpeg's default license. The build enables neither --enable-gpl nor --enable-version3, so no GPL or LGPL-3.0 components are compiled in. Per component:
| Component | License |
|---|---|
| FFmpeg (all six libraries) | LGPL-2.1-or-later |
| dav1d | BSD-2-Clause |
| zimg | WTFPL |
| libzvbi (library sources) | LGPL-2.0-or-later, ure.c MIT |
| Build scripts / SPM stubs (this repo) | LGPL-2.1-or-later |
libzvbi's three GPL-2 source files (packet-830.c, pdc.c, exp-vtx.c) are excluded from the build and the two referenced entry points are replaced with LGPL stubs (build.sh, patch_zvbi), so the shipped binaries contain no GPL code. All license texts live in LICENSES/.
The xcframeworks are dynamic frameworks on purpose: LGPL section 6 requires that end users can swap in a modified version of the library. With dynamic linking your app binary stays yours (closed source is fine) and the obligations reduce to:
- Link the package normally; Xcode embeds the frameworks in
YourApp.app/Frameworks/. Do not merge them into the app binary (no mergeable-library trickery), that would recreate static linking. - Reproduce the license texts from LICENSES/ somewhere reasonable (acknowledgements screen, bundled file).
- State that your app uses FFmpeg and friends, and link to the source of the exact build you ship (a tagged release of this repo, or your fork if you modified it).
If you build the static variant instead, those steps are not sufficient: LGPL 6(a) then requires you to provide your app's object files (or full source) so users can relink. That is realistic for open-source apps and rarely anything else, which is why static is not the shipped shape.
Used by AetherEngine.