Build and debug FFmpeg's own source (ffmpeg.c and friends) directly in Xcode, on your Mac, from a from-scratch build of the current FFmpeg release — with breakpoints, stepping, and full symbol access into FFmpeg's actual C source, not a black-box binary.
Works natively on both Apple Silicon and Intel Macs.
Most of the time, "using FFmpeg on macOS" means grabbing a prebuilt ffmpeg binary (Homebrew, a static build from a third party, etc.) and treating it as an opaque tool. That's fine until you need to:
- step through FFmpeg's own command-line tool logic in a real debugger,
- understand why a particular input trips up the CLI,
- prototype a change to
ffmpeg.citself, or - embed a modified/instrumented build of the FFmpeg CLI into something else.
This project sets up an Xcode command-line-tool target whose Sources are FFmpeg's own fftools/*.c files (pulled in via a git submodule), linked against a static build of FFmpeg's libraries (libavcodec, libavformat, libavfilter, libavutil, libavdevice, libswscale, libswresample) that Xcode builds for you as part of the build. Open the project, set a breakpoint anywhere in ffmpeg.c, hit Run, and you're debugging FFmpeg's real source with Xcode's LLDB integration — no separate toolchain, no attaching to a prebuilt binary.
The resulting ffmpeg executable is statically linked (no external .dylib dependencies to ship), includes H.264/HEVC encoding via libx264/libx265, and can additionally use macOS's hardware encoders through VideoToolbox.
FFmpegis a git submodule pointing at the upstream FFmpeg repository, currently pinned to releasen9.0.1.- The Xcode target has a "Make FFmpeg" Run Script build phase that runs before compiling anything. It:
- Verifies the FFmpeg submodule and required Homebrew packages are present.
- Runs FFmpeg's
./configurefor a static build, targeting whichever architecture and macOS deployment target the active Xcode build settings specify (so it always matches the Mac you're building on). - Runs
makeandmake installto produce the static.alibraries. - Regenerates two small embedded-resource C files (
generated/graph_html.c,generated/graph_css.c) that FFmpeg's own build normally produces via an internalbin2ctool — needed because this project compiles FFmpeg's CLI sources directly as an Xcode target instead of going through FFmpeg's Makefile.
- The Sources build phase then compiles FFmpeg's
fftools/*.cfiles (the actualffmpegCLI implementation: option parsing, muxing, demuxing, filtering, scheduling, etc.) as part of the Xcode target, with the header search path pointed straight at the FFmpeg submodule's source tree — matching how FFmpeg builds these files internally, so no manual header-copying is needed. - The Frameworks build phase links the freshly built static FFmpeg libraries,
libx264/libx265(copied in from Homebrew), and the macOS system frameworks FFmpeg's macOS backends need (AVFoundation, VideoToolbox, AudioToolbox, CoreAudio, CoreImage, CoreMedia, CoreVideo, OpenGL, AppKit, Security).
- macOS 11.0 (Big Sur) or later — Apple Silicon or Intel.
- Xcode with the command-line tools installed.
- Homebrew, plus:
If any of these are missing, the build phase will fail with a clear error telling you which one.
brew install nasm pkg-config x264 x265
git clone --recurse-submodules https://github.com/rsalesas/FFmpegXcodeLocal.git(If you already cloned without --recurse-submodules, run git submodule update --init inside the repo.)
Open FFmpegXcodeLocal.xcodeproj, select the ffmpeg scheme, and Run or Build (⌘R / ⌘B). The first build will configure and compile all of FFmpeg from source, which takes a few minutes; subsequent builds only rebuild what changed.
To debug the CLI itself, set a breakpoint in fftools/ffmpeg.c (or any of the other fftools/*.c files in the project navigator) and pass real arguments via the scheme's Run → Arguments tab, e.g.:
-i input.mp4 -c:v libx264 -crf 23 output.mp4
Then hit Run — Xcode will stop at your breakpoint inside FFmpeg's actual source.
The exact ./configure invocation lives in the target's "Make FFmpeg" Run Script build phase (Target → Build Phases). By default it enables libx264/libx265 and disables SDL2, bzlib, zlib, X11/xcb, and building FFmpeg's own CLI programs (since Xcode builds the CLI itself instead). To add another library (e.g. libmp3lame, libvpx, libsdl2):
brew installthe library.- Add the relevant
--enable-*flag to the./configurecall in the script, and, if it doesn't already get picked up automatically, copy its static library into${PROJECT_DIR}/libthe same waylibx264.a/libx265.aare copied. - Re-run the build — FFmpeg will reconfigure and rebuild with the new option.
- The build always targets the single architecture Xcode is actively building for (matching
ONLY_ACTIVE_ARCH), not a universal binary — this keeps the FFmpeg configure/build step simple and fast, since FFmpeg's static libraries are single-arch. Building on an Intel Mac produces anx86_64binary; building on Apple Silicon producesarm64. - Only the
ffmpegCLI target is built and debuggable this way;ffprobe.cis included in the project for reference but isn't currently wired into a buildable target. lib/,include/,Frameworks/, andgenerated/at the project root are build output, not something to hand-edit — they're regenerated by the build phase and are git-ignored.
This repository's own files (the Xcode project and build scripts) are licensed under the GNU General Public License v3.0 — see LICENSE. This matches the license of the ffmpeg binary the project actually produces: it's built here with --enable-gpl --enable-version3 and links libx264/libx265, both GPL-licensed encoders, so the resulting binary is GPLv3-licensed as a whole regardless of how the surrounding project files are licensed. See FFmpeg's legal page for the full picture before redistributing a built binary.
This project exists because building FFmpeg for macOS with the usual routes — Homebrew, third-party static builds, a fully manual build — makes it hard to actually step through FFmpeg's own source when something needs debugging. Wiring FFmpeg's CLI sources directly into an Xcode target, with FFmpeg's libraries built from scratch as part of the same build, solves that: you get a real, debuggable, statically-linked build with nothing hidden behind a prebuilt binary.
If you find this useful, contributions and issues are welcome.