Building FFmpeg using WSL
Two of the projects that I’m directly involved in are Bash on Windows and FFmpegInterop. For those that don’t know: Bash On Windows is a new feature that runs native Linux binaries directly on the new Windows Subsystem for Linux (WSL). FFmpegInterop is an open-source project that we’ve created to help developers integrate FFmpeg, a popular open-source video library, with Windows store applications (Windows 10 UWP, Windows 8.1 and Windows Phone 8.1). As part of the process of using FFmpegInterop, building FFmpeg for store applications is required. Building FFmpeg on Windows is not trivial by any means. FFmpeg has many Compilation guides for their different platforms, including cross-compiling for Windows, which can be done from within Linux. For now, we’re going to focus on building the FFmpeg binaries using Visual Studio’s compiler tool-chain.
Since we started working on WSL, getting Windows and WSL to talk to each other and be able to drive one another has been on my mind. Recently we announced our Interop story for WSL, allowing users to launch Windows processes directly from the Linux subsystem and vice-versa. I wanted to see how difficult it would be to build FFmpeg inside Bash, using the new interop, as opposed to having to install msys2 & build FFmpeg in Windows.
To build FFmpeg in Bash on Windows, I install/configure the following:
- Windows 10 64-bit Insider Build 14971, or later (to ensure Bash/Windows interop is available)
- WSL & Developer Mode were enabled. You can follow these instructions.
- Visual Studio 2015 and the Windows 10 SDK. This is the toolchain that we’ll use to build FFmpeg.
- For now the Linux distribution needs to be 16.04 (needed for the new version of realpath). You can use
do-release-upgradeon your own to update to 16.04 if you are still running 14.04
With all these things setup, I’ll go through the steps to build FFmpeg for Windows 10 UWP applications, leveraging the scripts that we wrote for FFmpegInterop.
- Open a Bash console
- Navigate to the Windows filesystem, where you’ll clone the FFmpeg repo, for now its important to clone the repository using the linux version of git to get the unix line-endings properly set:
- Update the apt-get package index:
sudo apt-get update
- Install the necessary dev tools in Bash using apt-get (C++ preprocessor, make, the yasm assembler and pkg-config)
sudo apt-get install cpp make yasm pkg-config
- Download the gas-preprocessor for ARM and copy it to /usr/bin so that it is in your path:
wget --no-check-certificate https://raw.githubusercontent.com/FFmpeg/gas-preprocessor/master/gas-preprocessor.pl && chmod +x gas-preprocessor.pl && sudo mv gas-preprocessor.pl /usr/bin
- Clone the FFmpegInterop repo using the gillesk/wsl branch, including the FFmpeg submodule (this could be done on Windows or Linux side):
git clone https://github.com/Microsoft/FFmpegInterop -b gillesk/wsl --recursive
- Apply the FFmpeg patch (until we can try to push it upstream to FFmpeg)
cd FFmpegInterop/FFmpeggit am ../0001-Updating-scripts-to-run-under-WSL.patch
- From a new Windows command prompt, navigate to the project folder and run the BuildFFmpeg.bat script, passing the WSL parameter
C:cd \dev\FFmpegInterop BuildFFmpeg.bat WSL win10 x86
- You should now get a proper build in the FFmpeg/Output/… folder.
Things that had to change to make this possible
While most things work transparently, a few changes to the build scripts for FFmpeg needed to be made to enable the interop. The main set of issues I encountered are centered around handling line endings between the two systems.
- Changed the configure script for FFmpeg (and associated shell scripts) to call tools including their extensions (cl.exe instead of cl for example). This is necessary, because WSL can automatically launch Windows processes from the Linux side but only if the process is in the path and includes the full name. Linux doesn’t automatically append .exe and such to invoke a file.
- Create a patch for FFmpeg and add it to the FFmpegInterop repo. (Until we can get them integrated in FFmpeg).
- Changed the configure script to clean some \r that can get appended when running windows tools through the scripts.
- Updated the FFmpegInterop repo to add a .gitattributes file, so that the FFmpegConfig.sh script using LF (not necessary if cloned or checked out through Linux).
- Updated the FFmpegInterop build script to have a new WSL optional parameter that builds using WSL instead of MSYS2.
- Removed the code to make a symbolic link to the FFmpeg sources and just access the sources through a relative path because at the moment symbolic links don’t cross the subsystem boundary.
Comparing both systems
Both systems do achieve the same results, and the changes to enable WSL for FFmpeg are pretty minor, and work for both Msys and WSL, so hopefully they can get integrated upstream fairly easily.
I found the configuration of the environment easier from the WSL side, using apt-get to install the prerequisites as well as not having to worry about which version of an executable gets invoked (link vs link.exe for example) since we are explicitly adding the extension in our scripts when invoking Windows executables. Getting the proper line endings though has been difficult. Linux shell scripts don’t run properly when they have CR in them; running Git from the Linux side helps to make sure that text files have the proper line endings.
But one huge advantage that I was not expecting was in terms of performance: Building x86, x64 and ARM Windows 10 versions of FFmpeg is almost twice as fast under WSL than it is in Msys. On my HP Z440, I build all three in just over 20 minutes while Msys takes just over 37 minutes. My Surface Pro 3 results are also showing similar gains.
Hopefully more projects can be adapted for WSL to help bridge the gap between Linux and Windows.