build: bundle a static ffmpeg, libsrt and librist (closes #6)#4
build: bundle a static ffmpeg, libsrt and librist (closes #6)#4datagutt wants to merge 12 commits into
Conversation
The plugin linked whatever ffmpeg the host obs shipped, which caused two problems. obs-deps pins libsrt 1.5.2 (2022) and librist 0.2.7, so every transport fix since then was missing. And obs bumped ffmpeg 7 (avcodec-61) to 8.1 (avcodec-62) between the 32.1 and 32.2 lines, so a binary linked against one would not load where the other was present, forcing a separate release artifact per obs line. deps/build-deps.sh now builds a pinned decode-only ffmpeg 8.1.2 with libsrt 1.5.6, librist 0.2.18 and mbedtls 3.6.4, linked statically. libobs is the only version sensitive link left, and it gates modules on (major, minor) <= host, so one artifact per platform covers 32.1 and every newer line. Symbol isolation is load-bearing: obs has already loaded its own ffmpeg exporting the same names, so an exported avcodec_open2 here would resolve to obs's copy and run against another library's structs. The module is built with hidden visibility, --exclude-libs,ALL and a version script that exports only obs_module_*. scripts/verify-plugin.sh asserts this after every build, since a successful compile does not prove it. Two silent failures worth recording. ffmpeg's configure ignores unknown names in an --enable-* list, so --enable-protocol=srt (the component is libsrt) produced a build with CONFIG_LIBSRT=yes and no srt protocol at all; build-deps.sh now asserts every required component against config.mak. And librist's meson found the host's shared libmbedcrypto instead of ours, which fix_mbedtls_pc corrects for both librist and libsrt.
|
Warning Review limit reached
Next review available in: 28 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Free Run ID: 📒 Files selected for processing (1)
WalkthroughThe plugin now builds a pinned static media dependency stack, links it through CMake with symbol isolation, verifies platform binaries, and publishes one artifact per platform. Build, release, and project documentation reflect the bundled stack and OBS compatibility model. ChangesBundled media stack
Project documentation
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant CI as Build workflow
participant Deps as deps/build-deps.sh
participant CMake as CMake
participant Verify as verify-plugin.sh
participant Release as Release workflow
CI->>Deps: Build bundled static dependencies
CI->>CMake: Configure and build plugin
CI->>Verify: Check dependencies and exports
Verify-->>CI: Return verification status
CI->>Release: Provide platform artifact
Release->>Release: Package platform archive
Poem
Note 🎁 Summarized by CodeRabbit FreeYour organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login. Comment |
Three findings from the first CI run. meson aborts on Windows with "Found GNU link.exe instead of MSVC link.exe": MSYS2 ships a coreutils /usr/bin/link.exe that shadows MSVC's linker on PATH. CMake is unaffected because it resolves the linker next to the compiler, but meson probes PATH. cl.exe and link.exe share a directory, so build-deps.sh now puts that directory first rather than modifying MSYS2. The macOS export check misparsed nm output. nm renders an indirect symbol with an empty address column, which shifts every field and made "(indirect" look like an exported symbol. Use nm -gUj and drop the column parsing; the build was correct, the assertion was not. The macOS artifact carried an absolute /opt/homebrew/opt/jansson/lib/libjansson.4.dylib load command, so the shipped plugin required Homebrew on the user's machine. The plugin never calls jansson; it is only reachable from the OBS headers, so the include path is needed and the library is not. verify-plugin.sh now also rejects any load command outside @rpath, /usr/lib and /System so this cannot regress.
meson names its static library librist.a even under MSVC, but FFmpeg's msvc toolchain rewrites the -lrist in librist.pc into rist.lib, so configure failed with "librist >= 0.2.7 not found using pkg-config" even though the library had just built and installed. Install the .lib name alongside it. Patching the .pc back to an absolute path would also work, but that is the link-ordering trap fix_mbedtls_pc exists to undo. FFmpeg's configure only reports "X not found using pkg-config" on failure, which does not distinguish a missing library from one whose name or link order the toolchain got wrong. Dump the tail of config.log so the next failure of this class is diagnosable from the CI log alone.
FFmpeg's configure failed to link against librist and mbedcrypto with
unresolved __imp_strdup, __imp_fopen and friends. Not a missing library: a
C runtime mismatch. cl.exe defaults to the static CRT when given no flag,
while CMake and meson both emit /MD, so FFmpeg silently built /MT against
/MD dependencies and the __imp_* import thunks had nothing to resolve to.
Mixing them is worse than a link error. /MD and /MT binaries get separate
heaps and separate stdio state, so a buffer allocated in one and freed in
the other corrupts the process. The plugin DLL is /MD (CMake's default, and
what libobs uses), so /MD is the target and all three build systems are now
pinned to it explicitly instead of left to their defaults. CMP0091 has to be
forced too, or a dependency declaring cmake_minimum_required below 3.15 gets
the old policy and ignores the setting.
Also two latent bugs in the same area. meson_common is seeded with the
options every platform needs so it is never empty, because macOS still ships
bash 3.2 where expanding an empty array under set -u aborts. And the system
library dedupe no longer uses the "${arr[@]:-}" idiom to assign, which
yields a single empty element on an empty array and would have written an
empty entry into the CMake link list.
libsrt builds srt_static.lib and meson builds librist.a, but FFmpeg's msvc toolchain rewrites the -lsrt and -lrist it reads from their .pc files into srt.lib and rist.lib. The mismatch surfaces as "not found using pkg-config" from a library that installed successfully seconds earlier, which is a misleading enough error to be worth handling once rather than per library. ensure_msvc_lib_name replaces the librist specific copy added earlier and now covers both, failing loudly with a directory listing if it cannot find any candidate rather than leaving configure to misreport it.
mbedTLS draws entropy from BCryptGenRandom on Windows but its generated .pc files do not declare bcrypt, so a static link through pkg-config fails on that single symbol. FFmpeg reports it as "ERROR: mbedTLS not found", which points at the wrong thing entirely. Also give configure an explicit include and library search path. Not every probe goes through pkg-config; when FFmpeg falls back to linking a bare -lmbedtls it has no search path and fails with "cannot open input file mbedtls.lib" for a library that is sitting right there.
FFmpeg's configure reached the zlib check for the first time and failed with "zlib requested but not found". Linux and macOS supply zlib as a system library; Windows has none. Building it is preferable to dropping --enable-zlib on Windows: a codec or container feature that silently differs per platform is a worse outcome than one more short build step. ZLIB joins the asserted component list so it cannot go missing quietly either. ensure_msvc_lib_name now takes extra candidate basenames, since zlib's CMake names its static output zlibstatic while FFmpeg asks for z.lib. The stdbit.h error just above the failure in the log is unrelated: that is FFmpeg probing for a C23 header MSVC does not have, and it falls back.
FFmpeg now configures on Windows (16 hwaccels, every asserted component present) and fails in its own compile instead: zconf.h(486): fatal error C1083: Cannot open include file: 'unistd.h' zconf.h.cmakein still carries an autoconf-era block that defines Z_HAVE_UNISTD_H from #ifdef HAVE_UNISTD_H. CMake probes for unistd.h, correctly does not find it under MSVC, and leaves Z_HAVE_UNISTD_H undefined earlier in the file, but that block then defines it anyway: FFmpeg's config.h contains "#define HAVE_UNISTD_H 0" and #ifdef is true for a value of zero. The header goes on to include a file MSVC does not have. zlib's own ./configure rewrites this line for exactly this reason; its CMake path does not, so patch it after install. The two remaining paths that could redefine Z_HAVE_UNISTD_H are gated on __WATCOMC__ and on _LARGEFILE64_SOURCE && !_WIN32, neither of which can fire here.
…line The deps build now completes on Windows, FFmpeg included, and the plugin link exposed two problems in emit_cmake. LNK1104: cannot open file 'ibpath:...lib.lib.lib'. The -l* arm is greedy enough to read the -libpath:C:/x that --extra-ldflags puts into FFmpeg's .pc files as a library named "ibpath:C:/x". Match the MSVC search-path spellings alongside -L, above that arm. zlib reached neither list. The Windows .pc chain never surfaces it as a -l flag, so it was dropped entirely and would have surfaced as unresolved inflate/deflate once the link got that far. It is ours only on Windows, so list it explicitly there, after its consumers. Static library candidates are now probed .lib first so a Windows link uses one naming convention rather than mixing librist.a with the rist.lib beside it. On Linux only the lib*.a form exists, so the order changes nothing.
The plugin link came down to a single unresolved external: avformat.lib(tls_mbedtls.o): unresolved external symbol gmtime_r referenced in function mbedtls_gen_x509_cert Upstream bug rather than anything about this build. tls_mbedtls.c calls gmtime_r without including libavutil/time_internal.h, which is where FFmpeg keeps the ff_gmtime_r fallback for platforms lacking the POSIX function. Every target except MSVC resolves the real gmtime_r, so the missing include never shows up there. Applied on all platforms rather than gated on Windows: the include is a no-op where HAVE_GMTIME_R is set, and one source state everywhere beats a platform-specific divergence. The patch verifies its own anchor and fails loudly if a future FFmpeg bump moves it.
A field crash on Windows resolved obs-irl-source.dll frames only to hex offsets because the plugin's PDB was never uploaded. Ship it alongside the DLL in the CI artifact (not the release zip). OBS's crash handler reads a PDB sitting next to the module, so the next crash log names the faulting function instead of a base+offset.
Links a pinned FFmpeg, libsrt, librist and mbedTLS into the plugin statically instead of using whatever the host OBS ships.
Why
Two problems, one fix.
Stale transport libraries. obs-deps pins libsrt 1.5.2 (September 2022) and librist 0.2.7. Every SRT and RIST fix since then was missing.
The FFmpeg major split the releases. OBS 32.1 bundles FFmpeg 7 (
avcodec-61), 32.2 bundles 8.1 (avcodec-62). A binary linked against one will not load where the other is present, so every release shipped a separate Windows and macOS artifact per OBS line.One artifact per platform
libobs was never the constraint.
obs_init_modulegates a plugin on(mod.ver() & 0xFFFF0000) <= LIBOBS_API_VER, so major and minor only, with no equality check, and it looks up nothing butobs_module_*. Building against the oldest supported line yields one binary that loads on that line and every newer one.OBS_VERSIONat the top ofbuild.ymlpins that oldest line.OBS_DEPS_VERSIONsurvives only because libobs itself needs obs-deps to build; the plugin no longer touches it.Symbol isolation
This is load-bearing, not cosmetic. OBS has already loaded its own FFmpeg, exporting the same symbol names as the archives linked here. An exported
avcodec_open2in the plugin would resolve through the global scope to OBS's copy, so the plugin's calls would run against a different library's structs.The module is built with hidden visibility,
--exclude-libs,ALLand a version script exporting only theobs_module_*entry points.scripts/verify-plugin.shasserts this after every build, with adumpbinequivalent in the Windows job, because a successful compile does not prove it.Verified against a harness using the identical link line, with a system FFmpeg loaded
RTLD_GLOBALfirst to simulate OBS:DT_NEEDEDreduces to libstdc++, libm, libz, libgcc_s, libc. Nolibav*, and nolibmbedcrypto, which confirms librist bound the static copy. Stripped size roughly 12 MB.Two silent failures found on the way
Both now guarded, and worth knowing about if anyone touches the component lists.
--enable-protocol=srtis a no-op: FFmpeg names the componentlibsrt. Configure ignores unknown names in an--enable-*list without a diagnostic, and produced a build withCONFIG_LIBSRT=yesnext to!CONFIG_LIBSRT_PROTOCOL=yes, meaning the library was linked in and SRT was completely dead.build-deps.shnow asserts every required component against the generatedffbuild/config.mak.librist's meson located mbedTLS with
cc.find_libraryand bound the host's sharedlibmbedcrypto.so, which would have reintroduced a runtime dependency and mismatched the copy libsrt uses.fix_mbedtls_pcrewrites bothlibrist.pcandsrt.pcto resolve out of the prefix.Component scope
The bundled FFmpeg is decode only (
--disable-everythingplus an explicit list) and carries exactly what the README advertises: H.264, HEVC, AV1, VP9, AAC, Opus, MP3, AC3/E-AC3 and PCM; MPEG-TS, FLV, MP4, Matroska, HLS and RTSP; SRT, RIST, RTMP(S), HTTP(S), TCP, UDP and RTP. Hardware decode is VAAPI and NVDEC on Linux, D3D11VA/DXVA2 and NVDEC on Windows, VideoToolbox on macOS.Trimming that list further is a user-visible feature removal, not a size optimisation.
deps/README.mdsays so.Licensing
FFmpeg is configured LGPLv3 (
--enable-version3, no--enable-gpl), compatible with the plugin's AGPL-3.0. Nothing here needs GPL components since the plugin decodes and never encodes. libsrt is MPL-2.0, librist BSD-2-Clause, mbedTLS Apache-2.0, all compatible with (A)GPLv3.Verification status
CI is green on all three platforms (run 30029171320): Linux x64, Windows x64, macOS ARM64. Each job builds the bundled stack, builds the plugin, and runs the isolation checks. Windows reports 16 hwaccels compiled in, no FFmpeg DLL imports, no leaked bundled symbols, and
obs_module_loadexported.Artifact sizes: Linux 16 MB (unstripped, with debug info), Windows 4 MB, macOS 3 MB.
The isolation guarantee was also validated locally against a harness using the identical link line, with a system FFmpeg loaded
RTLD_GLOBALfirst to simulate OBS:Not yet done: loading the artifacts in a real OBS install on each platform (the RELEASING.md smoke test). CI proves the binary builds, isolates, and exports correctly; it does not prove a stream plays. That is the manual pre-publish step.
Commit history
The first commit is the feature; the remaining ten are toolchain fixes that CI surfaced, almost all Windows (MSVC/MSYS2 CRT, static library naming, pkg-config quirks, one upstream FFmpeg
gmtime_rinclude bug). Happy to squash to a single commit on merge if you would rather not carry the fix-forward trail.