Skip to content

build: modernize emsdk and third-party library toolchain - #5

Merged
Project516 merged 13 commits into
masterfrom
build/toolchain-upgrade
Sep 24, 2026
Merged

Project516 merged 13 commits into
masterfrom
build/toolchain-upgrade

Conversation

@Project516

@Project516 Project516 commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

What changes

Step 1 of a staged FFmpeg upgrade (stays on FFmpeg 5.1.x; the fftools
scheduler rewrite in FFmpeg 6+ needs real threads and is out of scope
here, see "FFmpeg upgrade plan" in AGENTS.md). This PR modernizes the wasm core build toolchain:

  • Bumps the emsdk base image and every third-party library to its latest
    stable release.
  • For libraries pulled through an ffmpegwasm/* mirror, switches to
    canonical upstream wherever a diff of the mirror against the matching
    upstream tag showed no emscripten-specific patch (x265, libvpx, ogg,
    theora, opus, vorbis, zlib, libwebp, freetype2). x264 and lame stay on
    their mirrors; see "pinned, not bumped" below.
  • Fixes the fallout from the newer clang and library versions: a few C
    dialect checks clang now errors on by default, and drops the
    ffmpeg-core.worker.js codepath that current emsdk no longer emits for
    multi-threaded cores.
  • Ports two upstream bind.js/build fixes by hand (credited via
    co-authored-by): freeing exec()/ffprobe()'s argv and restoring the wasm
    stack pointer (Restore the wasm stack pointer after exec() and ffprobe() ffmpegwasm/ffmpeg.wasm#943), and letting the core-mt
    build's wasm memory grow up to 2GB instead of a fixed 1GB so a single
    4K frame does not OOM (fix: allow core-mt WASM memory to grow to avoid OOM on 4K frames (#946) ffmpegwasm/ffmpeg.wasm#948).

Versions

component before after notes
emsdk 3.1.40 6.0.10 latest stable
FFmpeg n5.1.4 n5.1.10 latest 5.1.x point release
x264 ffmpegwasm/x264 4-cores unchanged pinned, see below
x265 ffmpegwasm/x265 3.4 multicoreware/x265_git 4.2 mirror == upstream content, switched to canonical
libvpx ffmpegwasm/libvpx v1.13.1 webmproject/libvpx v1.17.0 switched to canonical
lame ffmpegwasm/lame master unchanged pinned, see below
ogg ffmpegwasm/Ogg v1.3.4 xiph/ogg v1.3.6 switched to canonical
theora ffmpegwasm/theora v1.1.1 xiph/theora v1.1.1 switched to canonical, no newer release exists
opus ffmpegwasm/opus v1.3.1 xiph/opus v1.6.1 switched to canonical
vorbis ffmpegwasm/vorbis v1.3.3 xiph/vorbis v1.3.7 switched to canonical
zlib ffmpegwasm/zlib v1.2.11 madler/zlib v1.3.2 switched to canonical
libwebp ffmpegwasm/libwebp v1.3.2 webmproject/libwebp v1.6.0 switched to canonical
freetype2 ffmpegwasm/freetype2 VER-2-10-4 freetype/freetype VER-2-14-3 switched to canonical (gitlab.freedesktop.org)
fribidi fribidi/fribidi v1.0.9 fribidi/fribidi v1.0.17 already canonical
harfbuzz harfbuzz/harfbuzz 5.2.0 harfbuzz/harfbuzz 8.5.0 already canonical, pinned below 9.0 (see below)
libass libass/libass 0.15.0 libass/libass 0.17.5 already canonical
zimg sekrit-twc/zimg release-3.0.5 sekrit-twc/zimg release-3.0.6 already canonical

Pinned, not bumped

  • x264: no version tags exist upstream (VideoLAN's repo is a rolling
    stable branch). A shallow-clone diff of the ffmpegwasm mirror's
    4-cores branch against upstream stable shows it has diverged
    years' worth of upstream history around its own patch, so there is no
    small patch to re-derive and reapply on top of current upstream. Kept
    pinned to the mirror as-is.
  • lame: upstream lame has not tagged a release since 3.100 (2017) and
    has no maintained git remote with tags; the ffmpegwasm mirror's
    master is the only usable git source. Kept pinned.
  • harfbuzz: pinned at 8.5.0, the last release that still ships an
    autotools configure.ac (9.0.0 onward is meson-only). Jumping to
    latest (14.5.0) needs build/harfbuzz.sh rewritten around meson plus
    an emscripten cross file, which is a bigger, separable change than a
    version bump; left for a follow-up PR so this one stays focused on the
    toolchain move.

Non-obvious fixes

  • Newer clang errors on old C: emsdk 6.0.10's clang defaults implicit
    function declarations, mismatched function pointer types, and
    int/pointer conversions to hard errors. n5.1.10 and its bundled libs
    still use that looser dialect in places, so CFLAGS now demotes those
    three checks back to warnings (-Wno-error=...) instead of patching
    every call site.

  • bind.js heap/stack leak: exec()/ffprobe() call Module["_ffmpeg"]/
    Module["_ffprobe"], which exit via a thrown exception. That leaves the
    wasm stack pointer wherever the C code left it and never frees the
    malloc'd argv strings/array, so repeated calls exhausted memory over a
    session. Both are now cleaned up in a finally block, ported from
    Restore the wasm stack pointer after exec() and ffprobe() ffmpegwasm/ffmpeg.wasm#943 with the argv free() half added on top
    (that half was never fixed upstream). Needs the new core build in this
    PR to take effect.

  • core-mt memory growth: the multi-threaded build had a fixed
    INITIAL_MEMORY=1024MB with no growth, so a single high-resolution
    frame could OOM even with nothing else wrong. -sALLOW_MEMORY_GROWTH
    with pthreads used to be discouraged, but current emscripten supports
    growable shared memory; added -sMAXIMUM_MEMORY=2GB as the cap so
    small jobs still start from the same 1GB initial allocation and only
    grow when a job actually needs more (fix: allow core-mt WASM memory to grow to avoid OOM on 4K frames (#946) ffmpegwasm/ffmpeg.wasm#948).

  • worker.js removal: emsdk >= 3.1.68 no longer emits
    ffmpeg-core.worker.js for multi-threaded builds (folded into the main
    core script). Removed the .worker.js branch from bind.js's
    _locateFile, the worker.js computation in packages/ffmpeg/src/worker.ts,
    and the ./worker export from packages/core-mt/package.json.
    FFMessageLoadConfig.workerURL stays as an accepted-but-unused,
    @deprecated option so existing callers do not break.

  • pkg-config and static libs: current .pc files list transitive deps
    under Requires.private, so FFmpeg's configure now runs pkg-config with
    --static. That pulled x265's host-only libs (-lgcc_s, -lrt, -ldl,
    ...) into the link, so build/x265.sh strips them from x265.pc.

  • libc++ for x265: current emcc no longer links libc++ unless asked, so
    configure and the final link pass -sDEFAULT_TO_CXX.

  • sem_close/sem_unlink in the st core: x265 4.x references them for
    cross-process shared memory, and Emscripten's single-thread libc lacks
    them. The st build adds two stubs to libx265.a. ffmpeg.wasm never enables
    that x265 feature.

  • zlib 1.3.2: its new CMake builds a shared library and skips zlib.pc
    under SKIP_INSTALL_FILES. It is now built static only, with its .pc.

  • ENVIRONMENT=web,worker: with worker alone, current emsdk loads the
    wasm with a synchronous XHR, which browsers block on a page. Adding web
    lets the core load directly on a page again.

  • UMD pthreads: current emsdk spawns pthreads from self.location.href
    in a worker and ignores mainScriptUrlOrBlob. When the UMD core is
    loaded with importScripts(), that URL is the @project516/ffmpeg
    worker, so every pthread ran the wrapper and load() hung. The UMD build
    now patches that one line to prefer mainScriptUrlOrBlob, and fails if
    the line changes.

  • build/ffmpeg.sh prints ffbuild/config.log when configure fails, so CI
    shows why.

Verification

  • pnpm lint and pnpm build pass locally.
  • CI passes on this PR: js, build-core, build-core-mt, and all three browser
    test pages (ffmpeg-core-st, ffmpeg-st, ffmpeg-mt).

Noticed, not fixed

  • harfbuzz stuck at 8.5.0 pending a meson-based rewrite of
    build/harfbuzz.sh (see "pinned, not bumped" above).
  • x264 stuck on the diverged ffmpegwasm/x264 mirror; no safe path to
    canonical upstream without hand-porting an unknown-scope patch.
  • The FFmpeg 6+ threading/scheduler rewrite (needed to go past 5.1.x) is
    tracked in "FFmpeg upgrade plan" in AGENTS.md; not attempted here.

@coderabbitai

coderabbitai Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 43 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 4cdf4ef5-c1dc-4c29-878c-8af49770d827

📥 Commits

Reviewing files that changed from the base of the PR and between fec7cf7 and 2ebd1f3.

📒 Files selected for processing (13)
  • AGENTS.md
  • Dockerfile
  • build/ffmpeg-wasm.sh
  • build/ffmpeg.sh
  • build/vorbis.sh
  • build/x265.sh
  • build/zlib.sh
  • packages/core-mt/package.json
  • packages/ffmpeg/src/types.ts
  • packages/ffmpeg/src/worker.ts
  • src/bind/ffmpeg/bind.js
  • src/bind/ffmpeg/export-runtime.js
  • src/bind/ffmpeg/export.js

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Project516
Project516 force-pushed the build/toolchain-upgrade branch from 7794813 to d66fd6b Compare September 24, 2026 13:42
@Project516
Project516 marked this pull request as ready for review September 24, 2026 14:41
Project516 and others added 13 commits September 24, 2026 09:42
Bumps every vendored third-party library to its latest stable tag,
switching mirrored libraries (x265, libvpx, ogg, theora, opus, vorbis,
zlib, libwebp, freetype2) to canonical upstream where a diff showed the
ffmpegwasm/* mirror carries no emscripten-specific patch. x264 stays on
the ffmpegwasm mirror (diverged too far from upstream to reconcile) and
lame stays on its mirror (upstream has no maintained git tags). harfbuzz
is pinned at 8.5.0, the last release with an autotools build; 9.0+ is
meson-only and needs build/harfbuzz.sh rewritten separately.

Also demotes a few C dialect checks the newer clang in emsdk 6.0.10
turned into hard errors, and lets the core-mt build's wasm memory grow
(bounded at 2GB) instead of a fixed 1GB, so decoding a single 4K frame
does not abort with OOM (ffmpegwasm#948).

Co-authored-by: Ben Younes <2910651+ousamabenyounes@users.noreply.github.com>
ffmpeg and ffprobe exit via exit(), which Emscripten implements by
throwing. The exception unwinds the JS frames but not the WebAssembly
stack, so every call leaked stack space (ffmpegwasm#943);
separately, the malloc'd argv strings and argv array from stringsToPtr()
were never freed. Repeated calls in one session exhausted the heap and
stack until 'memory access out of bounds'. Both are now cleaned up in a
finally block. Needs a new core build to take effect.

Co-authored-by: Mrmaxmeier <Mrmaxmeier@gmail.com>
emsdk >= 3.1.58 stopped needing a separate worker.js for multi-threaded
cores (folded into the main core script), and >= 3.1.68 stops emitting
even a stub. bind.js's _locateFile no longer routes .worker.js lookups,
worker.ts no longer computes or forwards a workerURL to the core, and
core-mt's package.json drops the now-nonexistent './worker' export.
FFMessageLoadConfig.workerURL stays as an accepted-but-unused, deprecated
option so existing callers do not break.
Emscripten's toolchain file reports CMAKE_SYSTEM_PROCESSOR=x86 for legacy
bitness-check compatibility. x265 4.x's CMakeLists.txt treats any 32-bit
x86 target as real ia32 and force-adds -march=i686, which emcc's clang
rejects outright for wasm32 ('unsupported option -march= for target
wasm32-unknown-emscripten'). Report a processor name x265 has no x86
special case for instead.
Canonical upstream's autogen.sh (autoreconf -if) no longer runs configure
itself the way the old ffmpegwasm mirror's did, so passing configure
flags straight to autogen.sh silently dropped them and left no Makefile.
Canonical vorbis (and likely others) now list transitive deps under
Requires.private in their .pc files instead of the old ffmpegwasm
mirror's plain Requires:. Plain `pkg-config --libs` ignores private
requires, so linking against e.g. vorbisenc alone dropped -lvorbis/-logg
and ffmpeg's configure reported 'vorbisenc not found using pkg-config'.
--pkg-config-flags=--static makes pkg-config include private deps, which
is what every one of these libraries is built as (static-only).
FFmpeg's configure runs pkg-config with --static, which pulls in
Libs.private. x265 lists gcc_s, rt, and dl there; emscripten has none of
them, so the x265 link check failed.
@Project516
Project516 force-pushed the build/toolchain-upgrade branch from 442e64f to 2ebd1f3 Compare September 24, 2026 14:42
Project516 added a commit that referenced this pull request Sep 24, 2026
This PR stacks on build/toolchain-upgrade (#5, not yet merged) instead of
master, so the pull_request trigger needs that base branch listed too or CI
never runs on it.
@Project516

Copy link
Copy Markdown
Owner Author

claude-opus-5-5 responding on behalf of project516

project516-review-bot timed out twice on this PR (free models returned empty replies, then hit rate limits), so a Sonnet review agent reviewed head 2ebd1f3 instead. Verdict: approve, no blocking issues. It hand-checked the bind.js argv free and stack restore, the x265 build ordering, and the upstream source hosts.

Two nits, both deferred on purpose:

  • meson and ninja-build are installed in the base image but unused until harfbuzz moves to meson. Removing them now would invalidate the whole CI layer cache, so they stay for the harfbuzz follow-up, which uses them.
  • Removing the ./worker export from @project516/core-mt will be called out in the 0.13.0 release notes.

@Project516
Project516 merged commit 03cd365 into master Sep 24, 2026
9 checks passed
@Project516
Project516 deleted the build/toolchain-upgrade branch September 24, 2026 15:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant