Building FFmpeg WebAssembly in Ubuntu 24.04: Step-by-Step Guide

Written by

in

Building FFmpeg for WebAssembly on Ubuntu 24.04 is a practical way to bring media processing into browsers, serverless environments, and JavaScript runtimes without relying on native binaries. The process is not difficult, but it does require a disciplined setup because FFmpeg, Emscripten, compiler flags, threading, and browser limits all interact closely.

TLDR: Install Ubuntu build dependencies, set up the latest Emscripten SDK, download FFmpeg source code, and compile it with WebAssembly-friendly configuration flags. Start with a minimal build before enabling codecs, filters, threads, or SIMD. Verify the resulting .wasm and JavaScript loader in Node.js or a browser before integrating it into production.

Why Build FFmpeg as WebAssembly?

FFmpeg is one of the most respected multimedia toolkits available. It can decode, encode, transcode, inspect, mux, demux, filter, and stream audio and video in many formats. By compiling FFmpeg to WebAssembly, you can run a controlled subset of this functionality inside a web application or JavaScript runtime.

This is especially useful for privacy-focused applications where media files should stay on the user’s device. Instead of uploading a video to a server for conversion, the browser can process it locally. WebAssembly also improves portability: the same compiled module can run across supported browsers and platforms with consistent behavior.

However, this approach has trade-offs. WebAssembly builds are usually larger than native command-line binaries, browser memory is limited, and performance depends heavily on build options. A careful, minimal build is usually better than trying to compile every FFmpeg feature at once.

System Requirements

This guide assumes a clean or reasonably fresh Ubuntu 24.04 LTS system. You should have sudo access, a stable internet connection, and enough disk space for source code, build artifacts, and temporary files. A few gigabytes of free space is recommended.

Before starting, update your package index and installed packages:

sudo apt update
sudo apt upgrade -y

Install the required development tools:

sudo apt install -y \
  git build-essential cmake pkg-config python3 \
  nodejs npm curl xz-utils nasm yasm

Note: Some FFmpeg components use assembly optimizations, but not all of them are relevant when targeting WebAssembly. Still, tools such as nasm and yasm are useful if you also build native dependencies or compare builds.

Install and Activate Emscripten

Emscripten is the standard toolchain for compiling C and C++ projects to WebAssembly. It provides emcc, emconfigure, and emmake, which are essential for building FFmpeg correctly.

Clone the Emscripten SDK repository:

cd ~
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk

Install and activate the latest SDK:

./emsdk install latest
./emsdk activate latest

Load the Emscripten environment into your current shell:

source ./emsdk_env.sh

Confirm that the compiler is available:

emcc -v

If emcc prints version information, the toolchain is ready. For long-term use, you may add the source command to your shell profile, but for controlled builds it is often better to source it manually so you always know which Emscripten version is active.

Download FFmpeg Source Code

Next, download FFmpeg. You can use the official Git repository or a release tarball. For reproducible production builds, a specific release tag is safer than building from the moving master branch.

cd ~
git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg-wasm-src
cd ffmpeg-wasm-src

Optionally, check out a stable release branch or tag:

git tag | tail
git checkout n7.0

If the tag does not exist in your clone or you prefer a newer release, inspect the available tags and select one that matches your maintenance requirements.

Choose a Minimal First Build

FFmpeg has many optional components. A common mistake is enabling too much too early. For a first WebAssembly build, disable most features and enable only what you need to confirm that the compilation works.

The following configuration creates a small, command-style FFmpeg build. It disables many external features, network access, documentation, and native assembly. It also enables basic protocols and muxers/demuxers useful for local file processing.

mkdir -p ~/ffmpeg-wasm-build
cd ~/ffmpeg-wasm-src

emconfigure ./configure \
  --cc=emcc \
  --cxx=em++ \
  --ar=emar \
  --ranlib=emranlib \
  --target-os=none \
  --arch=x86_32 \
  --enable-cross-compile \
  --disable-x86asm \
  --disable-inline-asm \
  --disable-stripping \
  --disable-programs \
  --disable-doc \
  --disable-debug \
  --disable-network \
  --disable-autodetect \
  --enable-avcodec \
  --enable-avformat \
  --enable-avutil \
  --enable-swresample \
  --enable-swscale \
  --enable-demuxer=mov \
  --enable-demuxer=mp3 \
  --enable-demuxer=wav \
  --enable-muxer=mp4 \
  --enable-muxer=mp3 \
  --enable-muxer=wav \
  --enable-protocol=file \
  --prefix=$HOME/ffmpeg-wasm-build

This configuration builds FFmpeg libraries rather than the command-line ffmpeg program. That is often the best starting point if you plan to call FFmpeg APIs from your own C wrapper compiled to WebAssembly.

Compile and Install FFmpeg Libraries

Run the build using emmake:

emmake make -j$(nproc)
emmake make install

If the build succeeds, inspect the installation directory:

ls ~/ffmpeg-wasm-build/lib
ls ~/ffmpeg-wasm-build/include

You should see static libraries such as libavcodec.a, libavformat.a, libavutil.a, libswresample.a, and libswscale.a. These are not yet a complete application. They are WebAssembly-targeted static libraries that you can link into a final module.

Create a Small C Wrapper

To use the libraries from JavaScript, it is common to create a small C interface. The wrapper exposes simple functions that JavaScript can call. For example, create a file named ffmpeg_info.c:

cat > ~/ffmpeg_info.c <<'EOF'
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <emscripten/emscripten.h>

EMSCRIPTEN_KEEPALIVE
const char* ffmpeg_version_info() {
    return av_version_info();
}

EMSCRIPTEN_KEEPALIVE
unsigned int avcodec_version_number() {
    return avcodec_version();
}
EOF

Now link it with the FFmpeg libraries:

emcc ~/ffmpeg_info.c \
  -I$HOME/ffmpeg-wasm-build/include \
  -L$HOME/ffmpeg-wasm-build/lib \
  -lavformat -lavcodec -lavutil -lswresample -lswscale \
  -s WASM=1 \
  -s MODULARIZE=1 \
  -s EXPORT_NAME=FFmpegModule \
  -s EXPORTED_FUNCTIONS='["_ffmpeg_version_info","_avcodec_version_number"]' \
  -s EXPORTED_RUNTIME_METHODS='["ccall","cwrap","UTF8ToString"]' \
  -o ~/ffmpeg_info.js

This produces ffmpeg_info.js and ffmpeg_info.wasm. The JavaScript file loads the WebAssembly module and exposes the runtime helpers configured above.

Test the Module with Node.js

Create a simple test file:

cat > ~/test_ffmpeg_info.mjs <<'EOF'
import FFmpegModule from './ffmpeg_info.js';

const module = await FFmpegModule();

const ptr = module.ccall(
  'ffmpeg_version_info',
  'number',
  [],
  []
);

console.log('FFmpeg version:', module.UTF8ToString(ptr));

const codecVersion = module.ccall(
  'avcodec_version_number',
  'number',
  [],
  []
);

console.log('libavcodec version number:', codecVersion);
EOF

Run it:

cd ~
node test_ffmpeg_info.mjs

If the version values print correctly, your FFmpeg WebAssembly library build is functional. From here, you can expand the wrapper to inspect files, decode packets, convert audio, or expose higher-level workflows.

Building the FFmpeg Command-Line Program

Some projects prefer a WebAssembly version of the familiar ffmpeg command. This is possible, but it usually requires more care because the program expects a filesystem, command-line arguments, and standard input and output behavior.

To experiment, remove –disable-programs and enable the ffmpeg program. You may also need to adjust exported runtime methods and filesystem support when linking. In browser environments, Emscripten’s virtual filesystem is used to mount files into memory or persistent browser storage.

For serious applications, consider whether you truly need the command-line binary. A purpose-built wrapper is usually smaller, easier to secure, and easier to test.

Optional Features: Threads and SIMD

WebAssembly supports advanced performance options, but they should be enabled deliberately.

  • Threads: Requires Emscripten pthread support and browser cross-origin isolation headers. Without correct headers, threaded builds will fail in browsers.
  • SIMD: Can improve performance for some workloads, but test carefully across your target browsers and devices.
  • Memory growth: -s ALLOW_MEMORY_GROWTH=1 may help with large media files, though it can affect performance predictability.

A threaded build might require linker options such as:

-s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=4

A SIMD build may use:

-msimd128

Do not enable these flags blindly. Validate them with realistic files, on real browsers, under realistic memory conditions.

Common Build Problems

  • emcc not found: Run source ~/emsdk/emsdk_env.sh in the same terminal before configuring FFmpeg.
  • Configure detects native libraries: Use –disable-autodetect to prevent accidental linkage against incompatible system libraries.
  • Undefined symbols during linking: Check library order. With static libraries, dependent libraries often need to appear after the object files that reference them.
  • Huge output files: Disable unnecessary codecs, muxers, filters, protocols, and programs. WebAssembly payload size matters.
  • Browser memory errors: Reduce input size, enable controlled memory growth, or split processing into smaller operations.

Security and Licensing Considerations

FFmpeg is powerful, but media parsing is complex. Treat all user-provided media as untrusted input. Keep FFmpeg updated, avoid enabling unnecessary decoders, and test malformed files where possible.

Licensing is also important. FFmpeg can be built under different licensing conditions depending on enabled components. Some codecs and libraries may introduce GPL or nonfree licensing implications. Before distributing a WebAssembly build, review your configuration and confirm that it matches your legal and commercial requirements.

Recommended Production Workflow

  1. Pin the Emscripten version and FFmpeg release tag.
  2. Maintain a documented configure script in version control.
  3. Start with the smallest possible feature set.
  4. Add codecs, muxers, demuxers, and filters one at a time.
  5. Run automated tests against representative media samples.
  6. Measure startup time, memory use, and conversion speed.
  7. Review licensing before public distribution.

This approach avoids fragile builds and makes future upgrades much safer.

Conclusion

Building FFmpeg WebAssembly on Ubuntu 24.04 is a reliable process when handled methodically. Install Emscripten, configure FFmpeg for a WebAssembly target, compile a minimal set of libraries, and link them through a small C interface that JavaScript can call.

The most important principle is restraint. FFmpeg can do almost everything, but a WebAssembly application should only include what it truly needs. A focused build is smaller, safer, easier to debug, and more suitable for real-world browser deployment.