build: build FFmpeg on every platform and make the media module part of the normal build - #292
Merged
devopvoid merged 33 commits intoSep 26, 2026
Merged
Conversation
The media module had no platform profiles beyond Windows, so a Linux or macOS build had no compiler or sysroot for its target and CMake simply built for the host. It now takes a toolchain file the way webrtc-jni does, and its profiles mirror webrtc-jni's by name and by the file each one selects, so both native libraries of a given classifier are built for the same target the same way. Keeping one set of toolchain files rather than a second copy is deliberate: they are where this project says which compiler and which sysroot a target uses, and two answers would drift. What configure is told about the target is worked out from what the toolchain file already set -- the architecture from CMAKE_SYSTEM_PROCESSOR, the target OS from the platform, the compiler from CMAKE_C_COMPILER, the target flags from CMAKE_C_FLAGS, the sysroot from CMAKE_SYSROOT -- so a target CMake can already build for needs no new case. Apple is the exception it has to make: there the architecture is a compiler flag rather than a different compiler. Cross-compiled arm and aarch64 start with --disable-asm. An assembler for a foreign architecture is a build prerequisite of its own, and nothing here is fast enough yet to need one; correctness on every platform first. Only windows-x86_64 is verified, because it is the only target this machine can build. It still builds from nothing and its configure line is unchanged apart from an explicit --arch=x86_64, and the module's 19 tests pass. Everything else waits on CI.
The media module is about to become part of the normal build, so every native build job has to be able to produce its FFmpeg, and none of them could. Windows gets MSYS2 with make and nasm, because FFmpeg's configure and makefiles are shell scripts and building it needs a POSIX shell even with MSVC. MSYS2_ROOT is exported so the media module's CMake finds it wherever the runner image put it. Linux and macOS get nasm, which is what FFmpeg assembles its x86 code with; macOS needs it even though the runner is Apple Silicon, because the Intel build is cross-compiled on it. The FFmpeg install directory is cached the same way the WebRTC checkout already is, keyed by FFmpeg version, platform and the hash of the dependency script and the module pom, so only the first build of a platform pays for it. The three native build jobs in each workflow now check out submodules, since the FFmpeg source is one. test-natives does not, because it runs against downloaded natives and never builds the module.
The module was opt-in behind a profile while it was being brought up. It now builds with everything else, which is also what lets CI prove it on the platforms this machine cannot build for. That collapses the awkward part of the examples module. The media example needed a source directory, a compile execution and an output directory of its own, purely because a JPMS "requires" cannot be made conditional and the module it names has to exist. With the module always there, the example moves in beside the others and the whole profile becomes one dependency and one "requires", exactly as its comment said it would. Anything building this project now needs the third-party/ffmpeg submodule checked out, and make and nasm to build it. The README and the guide say so where they used to describe the profile.
Every CI job failed at dependency resolution before a single mojo ran: Could not find artifact dev.onvoid.webrtc:webrtc-java-media:jar:windows-x86_64:0.19.0-SNAPSHOT The module declared a dependency on its own classifier artifact so that anything using it would get the natives along the way. That cannot work on a clean machine: the artifact is built and attached by this same module, so at the moment its dependencies are resolved nothing has produced it yet. It only passed locally because an earlier install had left the artifact in the local repository. webrtc-java can do this because webrtc-jni, a module earlier in the reactor, is what builds its natives, and because the artifact has been published besides. Neither is true here. So the dependency is gone, and anything using the module asks for the natives itself with a second dependency carrying the platform classifier. webrtc-examples does, and the guide says so. The module's own tests never needed it: Surefire is given the directory the CMake build collects the natives into. Verified by emptying dev/onvoid/webrtc/webrtc-java-media out of the local repository first, which reproduced the CI failure exactly, and then building clean: 168 tests in webrtc and 19 in webrtc-java-media, all five modules green.
Every job failed configuring the media module:
CMake Error at dependencies/ffmpeg/CMakeLists.txt:162 (set):
when parsing string
C:\Users\runneradmin/ffmpeg/windows-x86_64
Invalid character escape '\U'
Two things had to line up for this, which is why it never showed up
locally. Maven hands the install directory over as ${user.home} plus a
suffix, and on Windows that is a backslash path, which CMake reads as
the start of an escape sequence. And to_shell_path was a macro, so its
argument was substituted into the body as text rather than passed as a
value, leaving CMake to parse C:\Users\... as code.
It stayed hidden because that code only runs when FFmpeg has to be
built. This machine already had it installed, so every local build took
the short-circuit and never reached the macro. CI, starting from
nothing, reached it immediately.
The path is now normalised with file(TO_CMAKE_PATH) as it arrives, and
to_shell_path is a function, which passes its arguments as values.
Either change alone would have been enough; both are worth having,
since the first fixes this input and the second fixes the class of it.
Verified the way it should have been the first time: by deleting the
FFmpeg install directory so that the build path is the one that runs.
The failure reproduces exactly beforehand, and afterwards
"mvn clean package -DskipTests" builds FFmpeg from nothing and every
module succeeds, with the module's 19 tests green.
… with The first CI run that reached FFmpeg turned up three separate faults, all of them from reusing webrtc-jni's toolchain files. Those files are not neutral descriptions of a target: they encode how webrtc-jni links WebRTC's C++ ABI, and this module links no C++ ABI at all. On Linux they compile with -nostdinc++, because webrtc-jni supplies WebRTC's own libc++ through include paths of its own. This module inherited the flag and none of the paths, so its first #include <string> failed. It now uses toolchain files of its own that name the distribution's cross compilers and nothing else. linux-x86_64 needs no file at all, being built natively on the runner. Cross builds also tripped over stripping. make install strips what it installs with whichever strip is on the PATH, and the host's cannot read a binary for another architecture: "unable to recognise the format of the input file". FFmpeg is configured with --disable-stripping when cross-compiling, and the toolchain files name the cross binutils. On macOS the toolchain file set CMAKE_SYSTEM_NAME, which makes CMake report a cross build even when host and target agree, so configure was handed --enable-cross-compile for a native build along with --arch=aarch64. Apple's clang knows that architecture as arm64 and rejects the other outright, which configure reports only as being unable to create an executable. Neither macOS target is a cross build -- the Intel one runs under "arch -x86_64" -- so neither takes a file now, and the architecture is spelled the way Apple spells it. windows-aarch64 passed and keeps webrtc-jni's file, which suits it: there the C++ standard library comes from MSVC either way. Verified as far as this machine can: windows-x86_64 unaffected, with the full reactor green. The other targets wait on CI again.
windows-x86_64 built everything, including the media module, and then failed in webrtc-java-examples: Could not find artifact dev.onvoid.webrtc:webrtc-java-media:jar:windows-x86_64:0.19.0-SNAPSHOT The examples asked for the media module's natives as a dependency. CI runs "mvn package" and then "mvn -B jar:jar surefire:test", and the second of those invokes goals directly rather than running a lifecycle, so nothing reaches the package phase where that jar is attached and there is nothing to resolve against. The first command had succeeded moments earlier, which is why only the second failed. webrtc-java survives the same pattern only because its classifier artifact has been published and is already in the local repository. So the natives are off the dependency graph entirely. The example needs them at run time, and exec-maven-plugin is pointed at the directory the CMake build collects them into, which is how the media module's own tests have always found them. Verified by running both CI commands in order against a local repository with the media artifacts deleted: 168 tests in webrtc and 19 in webrtc-java-media, every module green, and the example still plays.
Both macOS jobs still stopped at the same place: cc is unable to create an executable file. C compiler test failed. The configure line was right by then -- --arch=arm64, no spurious cross-compile -- and the only unusual thing left on it was --cc, naming the compiler inside the Xcode toolchain: --cc=/Applications/Xcode_16.1.app/.../XcodeDefault.xctoolchain/usr/bin/cc Calling that binary directly skips the /usr/bin/cc shim, and the shim is what runs xcrun to point SDKROOT at an SDK. Without one there is no libSystem to link against, so the link test fails and configure reports it as being unable to create an executable. So --cc is no longer passed on Apple: configure's own default is plain "cc", which goes through the shim. Linux still gets it, since there the compiler is chosen deliberately and there is no shim to lose. That diagnosis came from reading a configure line and reasoning about it, which is a poor way to fix a build. configure writes the real reason into ffbuild/config.log and says almost nothing on the console, and on a build machine nobody can reach that is the same as writing it nowhere. The tail of that file is now printed when the build fails, so the next one of these is read rather than guessed at. Windows and Linux are unaffected: the argument was already withheld on Windows, and the rest only runs after a failure. Verified by rebuilding windows-x86_64 from nothing.
The media natives were built with the distribution's compilers and no sysroot, so they needed a newer glibc than webrtc-java's do. A machine could have run one and not the other, which is not a sensible thing to ship in the same release. They now build through webrtc-jni's toolchain files, which is what the plan said in the first place, and so against the same sysroot. That also means -nostdinc++, since everything Linux in this project uses the libc++ WebRTC bundles rather than whatever the build machine happens to have. Not having understood that is what sent this down the distribution toolchain road: the flag arrived without the include paths that go with it and the first #include <string> failed, which looked like a reason to abandon those files rather than to finish using them. So this module now takes libc++ from webrtc.install.dir, and webrtc-jni has to be built before it on Linux. That is the only thing it takes from that build, and it is a C++ standard library, not any part of WebRTC: no WebRTC symbol is linked here, and the two native libraries still meet only through the C function table. The build says so plainly if the headers are not there. While in here, cross-compilation is decided by comparing host and target rather than by CMAKE_CROSSCOMPILING, which every toolchain file in this project sets simply by naming a system. Believing it is what handed configure --enable-cross-compile for a native macOS build earlier, and it would have done the same for linux-x86_64. Verified on windows-x86_64, which is unaffected and still builds FFmpeg from nothing with 19 tests green. Linux waits on CI.
The test-natives lane existed to prove that a cross compiled native library actually runs on the machine it was built for, and it only ever did that for webrtc-java. The media natives were built, packaged, uploaded nowhere and never loaded on a real arm or arm64 machine, so nothing said whether FFmpeg and the media library work there or merely compile. Both jars are uploaded now. The lane installs webrtc-java's as before, and unpacks the media one into the directory the media module's tests already read natives from, which is where its own build collects them. Nothing else had to be taught where to look. The native build is skipped there with -DskipNativeBuild, which turns the two CMake executions off by moving them to no phase. Building FFmpeg again on a runner whose whole job is to run it would prove nothing and cost a quarter of an hour. webrtc is installed rather than only tested, so that the media module resolves it from the repository. Both in one reactor does not work, and the reason is worth writing down because it is the second time it has come up: Maven substitutes a module's own output for a dependency on it, and a classifier artifact of that same module, which is where the natives live, never reaches the class path. That is the same shape as the failure that broke the examples lane. Verified by doing locally what the lane does: unpacking the classifier jar into target/natives and running both commands. 168 tests in webrtc, 19 in webrtc-java-media, no CMake in the second, and the media tests load the unpacked libraries rather than any this machine built. The first attempt, both modules in one reactor, failed exactly as described above, which is how the reason is known rather than guessed.
The two arm lanes failed compiling the module, every translation unit dying inside libc++ before it reached a line of ours: _LIBCPP_HARDENING_MODE_DEFAULT is not defined. This definition should be set at configuration time linux-x86_64 passed, and the difference is in the toolchain files: x86_64-linux-clang.cmake sets -nostdinc++ and the hardening mode itself, while aarch64-linux-clang.cmake and aarch32-linux-clang.cmake set neither. webrtc-jni never notices, because it links the webrtc target and takes both from its PUBLIC flags. Nothing carries them to a module that links no WebRTC target, which is this one by design. So the module sets them on itself. The bundled libc++ has wanted its hardening mode chosen at configuration time since branch-heads/7977, and -nostdinc++ is what keeps clang from putting the GCC libstdc++ headers it finds in the sysroot on the implicit search path, where they collide with the bundled ones. This is the same lesson as the last two rounds, arriving once more: webrtc-jni's toolchain files describe half of an arrangement, and the other half lives on the webrtc target. Reading only the file leaves the half that matters behind. Windows is untouched and still builds with 19 tests green. The arm lanes wait on CI.
All three test-natives lanes failed, and neither half of what I wrote survived contact with that runner. Installing webrtc so that the media module could resolve it walked into its package phase, where an antrun task attaches the host natives from webrtc-jni/target. On a runner that only downloaded a jar there is no such directory: attach-artifact on project webrtc-java: File does not exist: ../webrtc-jni/target/webrtc-java-0.19.0-SNAPSHOT-linux-aarch64.jar That is why the original lane stopped at test rather than going further, and it stops there again. Both modules are back in one reactor, which is what makes webrtc-java resolvable, and the reason that failed before is now handled where it belongs. webrtc-java's natives are a classifier artifact of a module in that same reactor, and Maven answers a dependency on such a thing with the module's output directory, which holds no native library at all. Both libraries are therefore unpacked into the directory the media tests already read from, and are found as files rather than resolved as artifacts. Verified by the comparison that shows it: the same reactor command fails with NoClassDefFound on AudioDeviceModule when only the media natives are staged there, and passes with 168 and 19 tests when webrtc's are staged beside them. The local repository was identical in both runs, so the staging is what carries them.
CustomVideoSource extends AdaptedVideoTrackSource but handed every frame straight to OnFrame without calling AdaptFrame. The encoder's requests for a lower resolution or frame rate never took effect, so when the bitrate could not carry the pushed frames, the encoder kept receiving them at full size and dropped most of them instead. A 1080p file played through a peer connection arrived at around 5 fps. Frames are now adapted the way the desktop and camera sources already do: dropped when the requested frame rate says so, and cropped and scaled to the requested size otherwise.
The AVI demuxer was enabled, but not the decoders for what AVI files typically hold beyond MPEG-4: Microsoft's MPEG-4 variants (MPG4, MP42, DIV3), and MP2, AC-3 and ADPCM audio. Such a file opened, then failed as soon as playback tried to decode it. E-AC-3 is enabled alongside AC-3, whose decoder calls into its tables and which MSVC otherwise fails to link. A configure that found FFmpeg already installed skipped the build regardless of which components it had been built with, so a new decoder never reached an existing install. The install now records its version and components, and a mismatch rebuilds it. The test asset is the first quarter second of Big Buck Bunny as the AVI it is distributed in, cut without re-encoding.
One thread decodes both streams in the order the container stores them, and it blocked whenever the audio queue held its second of audio. Coarse interleaving, and a frame-threaded decoder that returns each frame several packets after it was fed, can put video more than that behind audio in the file. The video queue then ran dry and frames arrived late and in bursts, which WebRTC's encoder answers by dropping all but the last frame of each: an H.264 MOV played at 5 to 10 fps although every frame was decoded. Audio may now run past its capacity, up to ten seconds, while the video queue is below half full. The test asset stores all of its audio ahead of its video, in Matroska, which is read strictly in file order.
MediaFilePlayerExample plays a media file through two peer connections in the same process, without a signaling server, and shows the received video in a Swing window while the received audio plays on the speakers. Start and Stop set up and tear down the whole session. Beside the video, a panel shows what the file contains, where playback is, and what the receiver gets for each track, read from its statistics: codec, resolution, frame rate, bitrate, packet loss, jitter and audio level. The video sender's maximum bitrate is raised above WebRTC's default of about 2 Mbit/s, which would otherwise hold a 1080p file to a fraction of its resolution.
The window used Swing's cross-platform default. It now switches to the system look and feel before the first component is created, and falls back to the default with a warning if that is not available.
devopvoid
added this pull request to stack #293
September 25, 2026 22:33
…failed coarseInterleavingKeepsVideoEven counted frames that arrived close together, which also catches a runner that is merely slow for a moment: both macOS builds saw 17 and 18 such frames. A player that starves its video delivers it seconds late instead, about two seconds with this asset. The test now measures how late the worst frame is against the first audio chunk, which shares its timeline, and fails above a second, printing when each frame arrived. lowBitrateScalesDown failed on the armv7 runner with nothing to say why. It now waits up to 30 seconds for slow runners, and its failure message carries the sender's last outbound stats: whether it encoded at all, with which encoder, and what limited it.
RTCStats::toJava handed NewObject the webrtc::Timestamp itself where the Java constructor declares a long. NewObject takes C varargs, so nothing converted it. On 64-bit targets the object travels in the same register as a jlong would and the value came through intact. On 32-bit ARM a jlong is aligned to a register pair and the object is not, so every argument after it was shifted: each stats entry reached Java with a garbage type, a corrupted id string and garbage attributes. The report still had entries, which was all the existing test checked, but no entry could be found by its type, so the armv7 runner never saw any outbound RTP stats. The getStats test now checks that every entry carries a type, the id the report files it under, a timestamp and its attributes.
The platform jars carried FFmpeg's and WebRTC's binaries but none of the license texts those licenses ask to travel with them. The media platform jars now carry the LGPL text and a notice under META-INF/licenses/ffmpeg. The notice names the FFmpeg release the libraries are built from, unmodified, where its source is, and where the build configuration that selects its components lives, and points out that some of the formats it decodes may be covered by patents. The webrtc platform jars now carry, under META-INF/licenses/webrtc, the licenses of WebRTC and of the third-party code linked into it, generated from the build with WebRTC's own tool, and WebRTC's patent grant, which the tool leaves out. How to regenerate the file when the WebRTC branch changes is noted where it is installed. NOTICE, the media module's README and the media guide say where to find them.
…player The package was named after the library behind it rather than what it offers. The classes read media files and network streams and play them into a peer connection, so they now live in dev.onvoid.webrtc.media.player. dev.onvoid.webrtc.media itself cannot be used: the webrtc.java module already owns that package, and a second module carrying it would be a split package that the module system rejects. The native entry points, the MediaInfo lookup, the examples and the media guide follow the new name.
Listener calls arrive on the player's decode thread. Closing the player from one, which is the natural thing to do in onEndOfStream, released the native player on that same thread: it joined itself, which throws and takes the JVM down, and it deleted the player while the player was still on the stack below the call. MediaFileSource.close() did the same. A close from within a listener call now closes the player at once, so every later command does nothing and the state reads as closed, and leaves the release to another thread. That thread first waits for any command still running, since that command may be what made the call, and then releases the player without holding the lock, so the listener stays free to call the player while its thread is waited for.
A player pushes into its custom sources from a thread of its own, long after it was given their handles, but held no reference to them. Disposing of a source and its track while the player still ran freed the native source under the pacing thread, which then pushed into freed memory. The extension interface gains retain and release functions for both kinds of source, appended to the table as its versioning allows, so the version stays 1 and an extension tells from the size whether they are there. The pacer holds a reference to each source it feeds for as long as it exists, and the media module refuses a table too short to provide them.
When the decoders failed to open, the IOException was thrown first and the half made player released afterwards. Releasing it reported the closed state to the Java observer, a call into Java with an exception pending, and the observer then cleared what it took for an exception of its own: the IOException. The constructor returned normally with a player that did nothing. The player is now released before the exception is thrown, with its observer detached, since nothing listens yet. The observer also no longer calls into Java at all while an exception is pending, so it can neither make such a call nor swallow an exception that is not its own.
swresample was set up once, from the format the stream started with, and every frame was handed to it as if it still had that format. A stream that changes its channel layout, sample format or sample rate along the way, as live streams and some files do, had its frames read with the wrong layout: a stereo setup reading a mono frame reads past its end, and splits its samples between the two channels. The decoder now remembers the format swresample was set up for, and sets it up again for the first frame that differs. The output keeps its channel count, since the source it feeds expects one.
A native library is extracted to a temporary file to be loaded. Windows keeps a loaded library locked until the process ends, so deleting it on exit never succeeds, and every run left a copy behind: the core library under a random name, and since the media module the FFmpeg libraries beside it. They add up to gigabytes. Each process now extracts into a directory of its own, under the libraries' own names. Where a loaded library can be deleted, as on Linux and macOS, the directory goes as soon as loading is done. On Windows the process holds a lock on a file in it until it ends, and the first load in every process sweeps away the directories whose lock is free. It waits until a directory is a minute old, so that it never sweeps one a process has only just created and not yet locked. The sweep also takes the randomly named copies earlier versions left behind; one still loaded somewhere cannot be deleted, and is left. Loading is synchronized as well, so that two classes initialized on different threads no longer extract and load the same library twice.
A failure to read or decode ended the player's thread, but left it reporting that it was playing, and every later play or seek did nothing, since there was no thread left to act on it. A failure now stops playback where it happened, as a pause does, and is reported as such before the error itself. The thread stays, so playing again carries on past what failed, and a seek can move away from it. The playing flag, the pacer and the recorded state now change together under the player's lock, since the failure is reported from the decode thread and could otherwise interleave with a play from another. The observer is still called only once the lock is released.
swresample holds the last samples of a stream back, waiting for more input to filter them with. Nothing asked for them at the end, so audio that had to be resampled ended a few milliseconds short, and the gap was filled with silence. The decoder now drains swresample at the end of the stream, and before setting it up again for a change of format, where the samples it holds belong before the new frame. A seek or a loop resets it instead, since what it holds belongs to the position being left.
The submodule moves to n8.1, and the libraries the module loads to the major versions that release carries: avutil 60, avcodec and avformat 62, swresample 6 and swscale 9. FFmpeg 8 removed libpostproc, and with it the configure option that turned it off. Everything else the build enables is still there under the same name. Updating showed that neither the install nor the jar ever lost a library. Rebuilding FFmpeg installed over the earlier release, and the jar's staging directory only ever gained files, so the first build shipped the 7.1.1 libraries beside the new ones. A rebuild now starts from an empty install directory, cleared only when the record of an earlier build of ours shows it is ours to clear, and the staging directory is filled afresh on every build. A test now checks that the loaded FFmpeg is the release the pom pins, and that it is an LGPL build.
CI reads the FFmpeg version out of the media module's pom by the name of its element, and the property that hands the version to the tests had that same name. The lookup found both, and wrote the second one, unexpanded, into the environment of every build, which rejected it before anything was built. The tests now get the version under a name of their own.
FFmpeg names its version after the git tag it finds in its source, and the shallow submodule checkout CI makes has no tags, so the libraries built there called themselves 9047fa1 rather than n8.1, and the test checking that the pinned release is loaded failed on every platform that runs it. The build now hands make the release the source says it is, read from its RELEASE file, so the name is the same however the source was checked out. It goes to make as REVISION: the makefile sets the revision version.sh reads from that variable, over anything in the environment.
The README gains a feature entry and a short section on sending media files: what the module does, a five line example with MediaFileSource, the formats it reads, and a link to the guide. The formats listed are the ones the FFmpeg build enables, and only files are named, since the build leaves networking out. The documentation home gets a feature card for it, and the guide overview now lists the Media Files guide, which only the sidebar did so far. The link to MediaFileExample's source pointed into a source directory that does not exist; it points to the example now.
A source can now be a live stream behind an rtsp:// URL, such as an IP camera's, as well as a local file. The FFmpeg build enables networking, the RTSP demuxer and the RTP, UDP and TCP protocols. FFmpeg asks for RTP over UDP first and falls back to TCP, interleaved on the RTSP connection, when the server refuses UDP or nothing arrives. A network source can stall or vanish, so every call that waits on one now runs under a deadline, enforced through FFmpeg's interrupt callback: opening, probing, each read, seeking and closing. Running out of time reads as a timeout. The timeout is 10 seconds unless another is passed to MediaReader or MediaFileSource. Closing a player interrupts its reader first, so it never waits on a stalled stream, and the error that interruption causes is not reported to the listener. Every source is opened with a protocol whitelist. The RTSP demuxer brings the HTTP protocol along for tunnelling, and a URL passed on from a user must not be able to fetch through it. Testing against a server showed three more things to fix. An error opening a source quoted its URL, password included, and now leaves the credentials out. A looping player ignored a failed rewind, so a live stream that ended while looping reported a connection error; a source that cannot rewind now simply ends. And network errors read as bare numbers on Windows, where the C runtime has no words for them and FFmpeg passes most Winsock codes through; the common ones are now named, in a single helper that replaces three copies. The examples take stream URLs as well: the player shows a stream's URL without its credentials and disables looping for a live source, and both describe how to run them against a camera. The Media Files guide covers live streams, their timeouts and their limits. RtspStreamTest plays against a small RTSP server in the test sources, which refuses UDP and streams PCM audio over TCP.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #291, which adds the media module itself. This is the part that makes it build everywhere rather than only on the machine it was written on.
What this does
Cross-compilation for all seven targets. The media module had no platform profiles beyond Windows, so a Linux or macOS build had no compiler or sysroot for its target and CMake simply built for the host. It now takes a toolchain file the way
webrtc-jnidoes, and its profiles mirrorwebrtc-jni's by name and by the file each one selects, so both native libraries of a given classifier are built for the same target the same way.Reusing those toolchain files rather than writing a second set is deliberate: they are where this project says which compiler and which sysroot a target uses, and two answers would drift apart.
What configure is told about the target is worked out from what the toolchain file already set — the architecture from
CMAKE_SYSTEM_PROCESSOR, the target OS from the platform, the compiler fromCMAKE_C_COMPILER, the target flags fromCMAKE_C_FLAGS, the sysroot fromCMAKE_SYSROOT— so a target CMake can already build for needs no new case here. Apple is the exception it has to make: there the architecture is a compiler flag rather than a different compiler.Cross-compiled arm and aarch64 start with
--disable-asm. An assembler for a foreign architecture is a build prerequisite of its own and nothing here is fast enough yet to need one; correctness on every platform first, speed where it is measured.CI. Windows gets MSYS2 with make and nasm, because FFmpeg's configure and makefiles are shell scripts and building it needs a POSIX shell even with MSVC;
MSYS2_ROOTis exported so the module's CMake finds it wherever the runner image put it. Linux and macOS get nasm, which macOS needs even though the runner is Apple Silicon, because the Intel build is cross-compiled on it.The FFmpeg install directory is cached the same way the WebRTC checkout already is, keyed by FFmpeg version, platform and the hash of the dependency script and the module pom, so only the first build of a platform pays for it. The three native build jobs in both workflows now check out submodules, since the FFmpeg source is one;
test-nativesdoes not, because it runs against downloaded natives and never builds the module.The module joins the normal build. That also collapses the awkward part of the examples module. The media example needed a source directory, a compile execution and an output directory of its own, purely because a JPMS
requirescannot be made conditional and the module it names has to exist. With the module always there, the example moves in beside the others and the whole profile becomes one dependency and onerequires.What is verified, and what is not
Verified on this machine:
mvn verifygreen: 168 tests inwebrtc, 19 inwebrtc-java-media, all five modules succeedingwindows-x86_64, its configure line unchanged apart from an explicit--arch=x86_64MediaFileExampleruns from a plainmvn exec:java, with no profile and no extra class pathNot verified: six of the seven platforms. Only
windows-x86_64can be built where this was written — no ARM64 cross tools, no Linux, no macOS. Everything for the other six is written from FFmpeg's documented cross-compile flags and this project's existing toolchain files, and CI is the only thing that can prove it. Expect the first run to want corrections.Worth deciding before merge
The last commit makes the media module part of the normal build, which means anything building this project now needs the
third-party/ffmpegsubmodule checked out, plus make and nasm to build it. That is a new prerequisite for every contributor, not just for those who want the media module.The original plan put that step after every platform builds in CI, not before. It is here so that CI builds the module at all and can prove the other six targets. If CI is not green, or if the prerequisite is not wanted yet,
4399bb2is the tip of the branch and drops cleanly on its own; the first two commits stand without it.