Build FFmpeg from a current release (n7.1) instead of the 2020 fork - #81
Conversation
… 2020 fork The image builds ffmpeg from EnvelopSound/ffmpeg @ earshot-v0.1, which reports "ffmpeg version 4.3.git ... Copyright (c) 2000-2020" - frozen around 2021-04 and 26k+ commits behind upstream. That fork exists for one thing: PCE-aware AAC so a 16-channel (hexadecagonal) OBS Music Edition contribution survives the RTMP leg. Six years on, that no longer needs a fork: - DECODE of 16ch PCE AAC is upstream. earshot's core job - decode the contribution AAC and re-encode to 16ch Opus for DASH - runs unmodified on stock ffmpeg. - ENCODE of 16ch AAC is native in the 7.1 release: libavcodec/aacenc.c's aac_pce_configs[] table includes AV_CHANNEL_LAYOUT_HEXADECAGONAL at n7.1. (It was dropped from master around c92c6cbf during the channel-layout API rework, so n7.1 is the right target; a separate upstream report covers that regression.) So switching FFMPEG_VERSION to the upstream n7.1 tag closes six years of CVE/hardening on the demuxer and decoder paths that meet contribution bytes (aacdec, flvdec, mpegts), drops a 6-year-old third-party fork dependency, and gains modern muxer behaviour - notably Opus-in-fMP4 for DASH, so audio and copied-through H.264 video can share one fMP4 container instead of a mixed fMP4-video / WebM-audio manifest that FFmpeg-based players cannot demux cleanly. The one local patch this Dockerfile carries - the DASH suggestedPresentationDelay floor sed on libavformat/dashenc.c - applies to n7.1 verbatim (the target line and its `c->last_duration / AV_TIME_BASE` expression are unchanged), and its build-time post-check still passes. Verified on a build of this image: ffmpeg 7.1; encodes 16ch (hexadecagonal) AAC and decodes it back; decodes 16ch AAC -> 16ch Opus; produces Opus-in-fMP4 DASH under -dash_segment_type mp4; the DASH SPD floor works (live manifest shows PT30S); and a full synthetic RTMP contribution -> 16-ch Opus DASH pipeline passes with all 16 channels in their correct slots. Browser playback of the Opus-in-fMP4 output confirmed on Chrome and Firefox (dash.js/MSE), all 16 channels decoding.
There was a problem hiding this comment.
Pull request overview
Updates the container build to use an upstream FFmpeg release tag (n7.1) rather than a long-stale third-party fork, aligning the image with modern upstream fixes and behavior while keeping the existing local DASH muxer patch flow.
Changes:
- Switch
FFMPEG_VERSIONfromearshot-v0.1to upstreamn7.1. - Update the FFmpeg source download URL to pull the upstream tag archive (and name the tarball deterministically).
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| wget -O ${FFMPEG_VERSION}.tar.gz https://github.com/FFmpeg/FFmpeg/archive/refs/tags/${FFMPEG_VERSION}.tar.gz && \ | ||
| tar zxf ${FFMPEG_VERSION}.tar.gz && rm ${FFMPEG_VERSION}.tar.gz |
There was a problem hiding this comment.
Fair point, and worth doing properly rather than quickly. Two notes on scope: this PR doesn't introduce the gap - the line it replaces fetched a tag archive with no verification either - and it actually improves the source trust by moving from a stale third-party fork to the official FFmpeg repository.
On the fix itself: pinning a sha256 of a GitHub auto-generated archive is unfortunately fragile, since GitHub has changed archive compression before and broken such checksums. The robust version is to pull ffmpeg.org's signed release tarball (ffmpeg-7.1.tar.xz with its published .sha256 and GPG signature), which is a larger change to the build stage (different URL and extracted directory name). I'd rather do that as a focused follow-up than bolt a brittle checksum onto this one.
…etire the 2020 fork)
The FFMPEG_FLAGS fallback gains -dash_segment_type mp4, so the committed H.264 passthrough path now packages BOTH tracks as fMP4 .m4s (video/mp4 avc1 plus audio/mp4 Opus) rather than fMP4 video alongside WebM audio. The rule this adopts: the segment container follows the video codec's native container, and audio follows video, so a manifest is never mixed-container. Passthrough gives all-fMP4; the VP9 opt-in in .env.example already carried -dash_segment_type webm and gives all-WebM. The old mixed output was self-inconsistent: the manifest advertised an ISO-BMFF profile while serving WebM audio. Browsers and VLC tolerate it, but it is not a shape worth defending, and unifying it is what the ffmpeg upgrade in EnvelopSound/Earshot#81 made possible at all: the 2020 fork's DASH muxer could not put Opus in fMP4. Three test scripts read the audio init segment by a hardcoded .webm name and would have failed against a perfectly healthy stack, reporting the stream as absent. They now glob init-stream1.* so they survive the container changing again, which is exactly what caught them here. The browser capability page accepted WebM Opus only, so it would have reported a false negative on the default path; it now accepts Opus in either container. tests/av-sync/avmeter.js reads EBML and cannot work on fMP4 at all, so it now checks the magic and aborts rather than scanning MP4 bytes and returning a plausible wrong offset. telemetry already globbed .m4s and needed only its comment corrected. Verified on the committed default: test-pipeline.sh passes with all 16 tones in their own channels, test-srt-ingest.sh passes all six stages, the manifest reads mimeType video/mp4 + audio/mp4 with 16 channels, and Opus-in-fMP4 appends cleanly through MSE in a headless browser.
Follow-up to #81. That PR built from the upstream n7.1 source but fetched it from GitHub's /archive/refs/tags/ URL, a tarball generated on demand whose bytes are not stable (GitHub has changed archive compression before), so there is nothing a checksum can meaningfully pin against. Switch the source to the ffmpeg.org release artifact and verify it against a pinned SHA-256 before extracting or compiling. - Source: ffmpeg.org/releases/ffmpeg-7.1.tar.xz, the signed immutable release tarball, in place of the GitHub archive URL. - Verify: sha256sum -c against a pinned hash aborts the build on mismatch rather than compiling unexpected source. The hash is the one FFmpeg's release signing key (FCF9 86EA 15E6 E293 A564 4F10 B432 2F04 D676 58D8) signs for 7.1. - Container: the release is .tar.xz, so add xz to the build deps and decompress with xz -dc | tar. The extract dir is lowercase ffmpeg-7.1 (the release convention), updated in its references. FFMPEG_VERSION goes n7.1 -> 7.1 to match the release naming; a FFMPEG_SHA256 arg holds the pin. The DASH suggestedPresentationDelay floor sed and everything downstream are unchanged: same 7.1 source, fetched from the release channel and verified.
The previous version said this investigation 'started from a mixed- container bug report', but that bug (Keven Ma's report, fixed as EnvelopSound/Earshot#81) drove the separate ffmpeg-modernization work, not this one. The iOS/Safari investigation was requested directly. Named the two real, adjacent bug reports (Earshot#81, Chromium 547065816) instead of implying a causal link that did not exist.
Two lines in the Dockerfile: build ffmpeg from the upstream
n7.1release tag instead ofEnvelopSound/ffmpegatearshot-v0.1, which reportsffmpeg version 4.3.git ... Copyright (c) 2000-2020and is frozen around 2021-04, roughly 26k commits behind upstream.The fork exists for one thing: PCE-aware AAC, so a 16-channel (hexadecagonal) OBS Music Edition contribution survives the RTMP leg. Six years on, that is no longer a reason to carry a fork, and both halves check out against real builds rather than assumption:
libavcodec/aacenc.c'saac_pce_configs[]carriesAV_CHANNEL_LAYOUT_HEXADECAGONALat the n7.1 tag. It was dropped frommasteraroundc92c6cbfduring the channel-layout API rework, which is a separate upstream regression and the reason this targets the n7.1 release specifically rather than a newer one.What the move buys, beyond retiring a third-party fork last touched in 2022: it closes roughly six years of CVE and hardening work on exactly the demuxer and decoder paths that meet contribution bytes (
aacdec,flvdec,mpegts), and it gains modern muxer behaviour. The one that matters in practice is Opus-in-fMP4 for DASH: under-dash_segment_type mp4the audio and the copied-through H.264 video share a single fMP4 container, rather than the mixed fMP4-video / WebM-audio manifest the 2020 muxer forces, which FFmpeg-based players cannot demux without stuttering.The one local patch this Dockerfile carries, the DASH
suggestedPresentationDelayfloorsedonlibavformat/dashenc.c, applies to n7.1 with no change: the target line and itsc->last_duration / AV_TIME_BASEexpression are byte-identical at the tag, and the build-time post-check that guards it still passes.Verified on real builds of this image, amd64 and a native arm64 build on a Raspberry Pi 4: ffmpeg reports 7.1; it encodes 16-ch hexadecagonal AAC and decodes it back; it decodes 16-ch AAC to 16-ch Opus (the live transcode); it produces Opus-in-fMP4 DASH under
-dash_segment_type mp4; the SPD floor works, with a live manifest showingPT30S; a full synthetic RTMP contribution to 16-ch Opus DASH pipeline passes with all sixteen channels in their correct slots; and the Opus-in-fMP4 output plays in-browser on Chrome and Firefox through dash.js/MSE with all sixteen channels decoding.