Building FFmpeg from source for iOS on macOS | Qt Multimedia | Qt 6.11.1
Description: This document describes how to build FFmpeg from source code.

- Qt 6.11
- Qt Multimedia
- Building Qt Multimedia from sources
- Building FFmpeg from source
- Building FFmpeg from source for iOS on macOS
- Qt 6.11.1 Reference Documentation

Building FFmpeg on Linux ◦ Building FFmpeg for Android on Windows

# Building FFmpeg from source for iOS on macOS

This page explains how to configure and build FFmpeg for iOS as frameworks. Compilation for iOS is a cross-compilation and presumes using macOS as a host system. The required steps are:

- Preparing build environment
- Getting the FFmpeg source code
- Configuring FFmpeg from the command line
- Building dynamic libraries
- Creating iOS frameworks from FFmpeg libraries
- Creating XCFrameworks
- Embedding frameworks

Note: The following documentation assumes that you work with FFmpeg libraries and code under `~/ffmpeg`.

## Preparing build environment

The build environment for iOS is provided by Apple's Xcode application, which includes the toolchain (compiler, linker, and other tools), and the iOS platform-SDK (headers and libraries) that you build and link against.

## Getting the FFmpeg source code

You can get the FFmpeg source code in these ways:

- Download from the FFmpeg download page.
- Clone from git. For example, these commands clone the version 7.1.3 of the FFmpeg sources to `~/ffmpeg/ffmpeg_src`.

       cd ~/ffmpeg/
       git clone --branch n7.1.3 https://git.ffmpeg.org/ffmpeg.git ffmpeg_src

Note: It is recommended to use the same FFmpeg version as documented in the Qt Multimedia main page.

## Configuring and building FFmpeg

Create a `build` and `installed` directories inside the `~/ffmpeg` directory and navigate into `build`:

     mkdir ~/ffmpeg/build
     mkdir ~/ffmpeg/installed
     cd ~/ffmpeg/build

To configure FFmpeg, run:

     ../ffmpeg_src/configure --disable-programs --disable-doc --enable-network --enable-shared --disable-static \
         --sysroot="$(xcrun --sdk iphoneos --show-sdk-path)" \
         --enable-cross-compile \
         --arch=arm64 \
         --prefix=../installed \
         --cc="xcrun --sdk iphoneos clang -arch arm64" \
         --cxx="xcrun --sdk iphoneos clang++ -arch arm64" \
         --extra-cflags="-miphoneos-version-min=16.0" \
         --extra-cxxflags="-miphoneos-version-min=16.0" \
         --extra-objcflags="-miphoneos-version-min=16.0" \
         --extra-ldflags="-miphoneos-version-min=16.0" \
         --install-name-dir='@rpath' \
         --disable-audiotoolbox

The command line programs and documentation are not needed; network features should be enabled. We build shared libraries and don't need static libraries (omit `--disable-static` to build static libraries). `--sysroot` specifies the root of cross-build tree. The `--prefix` argument specifies a path where the FFmpeg libraries are installed after building. We build on macOS host for iOS target so we enable cross compilation. `--extra-ldflags` set the minimum version of iOS. `--install-name-dir` is a string specifying the directory portion of the "install_name" field of shared libraries on Apple platforms for installed targets. AudioToolBox is disabled, since FFmpeg is using parts of AudioToolBox framework which are not available on iOS.

Note: In the example above, replacing 'iphoneos' with 'iphonesimulator', and 'miphoneos-version' with 'mios-simulator-version-min', configures FFmpeg to be built as libraries suitable for iOS simulator.

To get help on other configuration options, run:

     ../ffmpeg_src/configure --help

After configuration is complete, build FFmpeg libraries:

     make -j install

## Creating iOS frameworks from FFmpeg libraries

To work in iOS applications, FFmpeg dynamic libraries have to be converted into frameworks and embedded into application bundles. A framework is a hierarchical directory that encapsulates resources such as a dynamic library, image files, localized strings, header files, and reference documentation in a single package. In our case, FFmpeg frameworks only contain dynamic libraries and corresponding Info.plist files. For example, libavcodec converted to framework becomes `libavcodec.framework` directory with these contents:

- Info.plist (containing the description of the framework)
- libavcodec (dynamic library)

Unlike dynamic libraries we built in the previous step, dynamic libraries inside iOS frameworks are unversioned and have no 'dylib' extensions. This requires fixing library identification names and dependencies in our dynamic libraries. The `otool` utility helps us to find such names. For example:

     otool -L ../installed/lib/libavcodec.dylib

gives us these names (we only show FFmpeg-related names):

     @rpath/libavcodec.61.dylib (compatibility version 61.0.0, current version 61.19.100)
     @rpath/libswresample.5.dylib (compatibility version 5.0.0, current version 5.3.100)
     @rpath/libavutil.59.dylib (compatibility version 59.0.0, current version 59.39.100)

To fix these names, use the `install_name_tool` utility. The utility options are:

- `-id` - allows changing the shared library identification name.
- `-change` - allows changing the dependent shared library install name.

This script converts the previously built dynamic libraries to frameworks. The code assumes that you run it from the `build` directory and the frameworks are located in `~/ffmpeg/installed/framework`:

     #!/usr/bin/env bash

     # Creates an Info.plist file for a given framework:
     build_info_plist() {
         local file_path="$1"
         local framework_name="$2"
         local framework_id="$3"

         # Minimum version must be the same we used when building FFmpeg.
         local minimum_version_key="MinimumOSVersion"
         local minimum_os_version="16.0"

         local supported_platforms="iPhoneOS"

         info_plist="<?xml version=\"1.0\" encoding=\"UTF-8\"?>
     <!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
     <plist version=\"1.0\">
     <dict>
         <key>CFBundleDevelopmentRegion</key>
         <string>en</string>
         <key>CFBundleExecutable</key>
         <string>${framework_name}</string>
         <key>CFBundleIdentifier</key>
         <string>${framework_id}</string>
         <key>CFBundleInfoDictionaryVersion</key>
         <string>6.0</string>
         <key>CFBundleName</key>
         <string>${framework_name}</string>
         <key>CFBundlePackageType</key>
         <string>FMWK</string>
         <key>CFBundleShortVersionString</key>
         <string>7.0.2</string>
         <key>CFBundleVersion</key>
         <string>7.0.2</string>
         <key>CFBundleSignature</key>
         <string>????</string>
         <key>${minimum_version_key}</key>
         <string>${minimum_os_version}</string>
         <key>CFBundleSupportedPlatforms</key>
         <array>
             <string>${supported_platforms}</string>
         </array>
         <key>NSPrincipalClass</key>
         <string></string>
     </dict>
     </plist>"
         echo $info_plist | tee ${file_path} 1>/dev/null
     }

     dylib_regex="^@rpath/.*\.dylib$"

     # Creates framework from a dylib file:
     create_framework() {
         local framework_name="$1"
         local ffmpeg_library_path="../installed"
         local framework_complete_path="${ffmpeg_library_path}/framework/${framework_name}.framework/${framework_name}"

         # Create framework directory and copy dylib file to this directory:
         mkdir -p "${ffmpeg_library_path}/framework/${framework_name}.framework"
         cp "${ffmpeg_library_path}/lib/${framework_name}.dylib" "${ffmpeg_library_path}/framework/${framework_name}.framework/${framework_name}"

         # Change the shared library identification name, removing version number and 'dylib' extension;
         # \c Frameworks part of the name is needed since this is where frameworks will be installed in
         # an application bundle:
         install_name_tool -id @rpath/Frameworks/${framework_name}.framework/${framework_name} "${framework_complete_path}"

         # Add Info.plist file into the framework directory:
         build_info_plist "${ffmpeg_library_path}/framework/${framework_name}.framework/Info.plist" "${framework_name}" "io.qt.ffmpegkit."${framework_name}
         otool -L "$framework_complete_path" | awk '/\t/ {print $1}' | egrep "$dylib_regex" | while read -r dependency_path; do
             found_name=$(tmp=${dependency_path/*\/}; echo ${tmp/\.*})
             if [ "$found_name" != "$framework_name" ]
             then
                 # Change the dependent shared library install name to remove version number and 'dylib' extension:
                 install_name_tool -change "$dependency_path" @rpath/Frameworks/${found_name}.framework/${found_name} "${framework_complete_path}"
             fi
         done
     }

     ffmpeg_libs="libavcodec libavformat libavutil libswresample libswscale"

     for name in $ffmpeg_libs; do
         create_framework $name
     done

## Creating a multiplatform binary framework bundle

An XCFramework bundle is a package that includes frameworks and libraries necessary to build for multiple platforms, for example, iOS and iOS simulator. To create such a framework, use the `xcodebuild` utility. For example, if you have frameworks for iOS and simulator located in `~/ffmpeg/installed/arm64` and `~/ffmpeg/installed/arm64-simulator` directories, the utility arguments look like follows:

     xcodebuild -create-xcframework -framework ../installed/arm64/libavcodec.framework -framework ../installed/arm64-simulator/libavcodec.framework -output ../installed/framework/libavcodec.xcframework

## Embedding frameworks

Embed the FFmpeg frameworks into an application bundle. For information on how to embed frameworks using XCode, refer to

Embedding Frameworks In An App.

Building FFmpeg on Linux ◦ Building FFmpeg for Android on Windows