Compile FFmpeg to WebAssembly with Emscripten

Compiling FFmpeg to WebAssembly (WASM) allows you to perform client-side audio and video processing, transcoding, and filtering directly inside a web browser without dedicated server resources. This guide walks through setting up the Emscripten SDK, configuring FFmpeg source code with WebAssembly-compatible build flags, compiling the libraries, and exporting the final JavaScript and WASM artifacts.

1. Prerequisites and Environment Setup

To compile FFmpeg to WASM, you need a Unix-like environment (Linux, macOS, or WSL on Windows) equipped with git, make, pkg-config, and Python.

First, install and activate the Emscripten SDK (emsdk):

# Clone the Emscripten repository
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk

# Download and install the latest SDK tools
./emsdk install latest

# Set up the SDK in the current shell
./emsdk activate latest
source ./emsdk_env.sh

2. Download FFmpeg Source Code

Clone the official FFmpeg repository. It is recommended to check out a stable release branch (such as n6.1 or n7.0) rather than the unstable master branch.

git clone https://github.com/FFmpeg/FFmpeg.git
cd FFmpeg
git checkout n6.1

3. Configure FFmpeg for Emscripten

FFmpeg requires specific compilation flags to target WebAssembly. Native assembly optimizations, multi-threading (unless specifically configured with pthreads), and hardware acceleration must be disabled.

Use emconfigure to run FFmpeg's configure script:

emconfigure ./configure \
  --target-os=none \
  --arch=x86_32 \
  --enable-cross-compile \
  --disable-x86asm \
  --disable-inline-asm \
  --disable-stripping \
  --disable-programs \
  --disable-doc \
  --disable-all \
  --enable-avcodec \
  --enable-avformat \
  --enable-avutil \
  --enable-swresample \
  --enable-swscale \
  --enable-demuxer=mov,matroska,mp3,wav \
  --enable-decoder=h264,aac,mp3,pcm_s16le \
  --enable-protocol=file \
  --cc="emcc" \
  --cxx="em++" \
  --ar="emar" \
  --ranlib="emranlib" \
  --nm="emnm" \
  --extra-cflags="-O3"

Key configuration options:

  • --disable-all: Strips all features to keep the binary small. Enable only the specific demuxers, decoders, and filters your application requires.
  • --disable-x86asm and --disable-inline-asm: Disables CPU-specific assembly instructions incompatible with WASM.
  • --cc="emcc": Directs the build system to use Emscripten's C compiler.

4. Build Static Libraries

Run emmake make to build the core FFmpeg static libraries (.a files):

emmake make -j$(nproc)

Upon completion, static libraries like libavcodec/libavcodec.a, libavformat/libavformat.a, and libavutil/libavutil.a will be generated in their respective directories.

To generate the .wasm file and its JavaScript loader, compile a C entry point or link the static libraries directly using emcc.

Here is an example command to link the static libraries into a standalone WebAssembly module with Emscripten's virtual filesystem:

emcc -O3 \
  -s WASM=1 \
  -s ALLOW_MEMORY_GROWTH=1 \
  -s INITIAL_MEMORY=67108864 \
  -s EXPORTED_RUNTIME_METHODS='["FS", "cwrap", "ccall"]' \
  -s MODULARIZE=1 \
  -s EXPORT_NAME="createFFmpegCore" \
  libavformat/libavformat.a \
  libavcodec/libavcodec.a \
  libswresample/libswresample.a \
  libswscale/libswscale.a \
  libavutil/libavutil.a \
  -o ffmpeg-core.js

Compilation flags explained:

  • -s WASM=1: Emits WebAssembly instead of asm.js.
  • -s ALLOW_MEMORY_GROWTH=1: Dynamically increases WASM memory when loading large files.
  • -s INITIAL_MEMORY=67108864: Allocates an initial 64MB of heap memory.
  • -s MODULARIZE=1: Encapsulates output in a closure returning a Promise.
  • -o ffmpeg-core.js: Produces ffmpeg-core.js and ffmpeg-core.wasm.

6. Using the Output in the Browser

Import the generated JavaScript file into your web application to initialize the module:

<script src="ffmpeg-core.js"></script>
<script>
  createFFmpegCore().then((Module) => {
    console.log("FFmpeg WASM loaded successfully");
    // You can now interact with the Emscripten FS API
    // Module.FS.writeFile('input.mp4', data);
  });
</script>