Compiling FFmpeg to WebAssembly (= ffmpeg.js): Part 2 - Compiling with Emscripten













List of translated parts of the series:







  1. Cooking
  2. Compiling with Emscripten (you are here)
  3. Convert avi to mp4



Starting with this part, the material will be more complicated, so do not hesitate to google during the reading, if you do not understand what is happening.







In addition, I will try to document the solution to possible problems so that you can compile the library with your settings.







In this part we will analyze:







  1. How to set up an environment for Emscripten in Docker
  2. Using emconfigure and emmake
  3. How to solve problems when compiling FFmpeg with Emscripten





How to set up an environment for Emscripten in Docker



In the first part, we compiled FFmpeg with gcc and can move on to using the Docker image with emscripten.







I will use trzeci / emscripten version 1.38.45:







$ docker pull trzeci/emscripten:1.38.45
      
      





Since the image takes about 1 GB, the process may take some time.







Now we will find the correct configuration for compiling FFmpeg into emscripten by trial and error, which will require perseverance and reading large volumes of documentation. Run the container with emscripten and mount the FFmpeg sources in the / src directory.







 # ,      FFmpeg $ docker run -it \ -v $PWD:/src \ trzeci/emscripten:1.38.45 \ /bin/bash
      
      





Inside the container, execute ls --color to see something like this:

















Using emconfigure and emmake . How to solve compilation problems



Let's start with the configuration. In the first part we did ./configure --disable-x86asm , in emscripten this is achieved with the emconfigure ./configure --disable-x86asm command . (For details on using emconfigure, see here )







 $ emconfigure ./configure --disable-x86asm
      
      





And since we did not see any errors, it remains only to execute emmake make -j4 and get the coveted FFmpeg.js? Unfortunately not. One of the most important tasks for emconfigure is to replace the gcc compiler with emcc (or g ++ with em ++), but the output from ./configure still produces gcc.







 root@57ab95def750:/src# emconfigure ./configure --disable-x86asm emscripten sdl2-config called with /emsdk_portable/emscripten/tag-1.38.45/system/bin/sdl2-config --cflags emscripten sdl2-config called with /emsdk_portable/emscripten/tag-1.38.45/system/bin/sdl2-config --libs install prefix /usr/local source path . C compiler gcc #    emcc C library glibc ARCH x86 (generic) big-endian no runtime cpu detection yes standalone assembly no x86 assembler nasm
      
      





Any automation has its limits, and in this case, unfortunately, we have to do everything manually. Let's see if there are any arguments to help us:







 $ ./configure --help
      
      





Under the Toolchain options section, we see arguments to indicate the type of compiler.







 root@57ab95def750:/src# ./configure --help Usage: configure [options] Options: [defaults in brackets after descriptions]Help options: ... Toolchain options: ... --nm=NM use nm tool NM [nm -g] --ar=AR use archive tool AR [ar] --as=AS use assembler AS [] --ln_s=LN_S use symbolic link tool LN_S [ln -s -f] --strip=STRIP use strip tool STRIP [strip] --windres=WINDRES use windows resource compiler WINDRES [windres] --x86asmexe=EXE use nasm-compatible assembler EXE [nasm] --cc=CC use C compiler CC [gcc] --cxx=CXX use C compiler CXX [g++] --objcc=OCC use ObjC compiler OCC [gcc] --dep-cc=DEPCC use dependency generator DEPCC [gcc] --nvcc=NVCC use Nvidia CUDA compiler NVCC [nvcc] --ld=LD use linker LD [] ...
      
      





Let's use them in emscripten







 $ emconfigure ./configure \ --disable-x86asm \ --nm="llvm-nm -g" \ --ar=emar \ --cc=emcc \ --cxx=em++ \ --objcc=emcc \ --dep-cc=emcc
      
      





Now executing ./configure will take more time, but as a result we get emcc.







 root@57ab95def750:/src# emconfigure ... emscripten sdl2-config called with /emsdk_portable/emscripten/tag-1.38.45/system/bin/sdl2-config --cflags emscripten sdl2-config called with /emsdk_portable/emscripten/tag-1.38.45/system/bin/sdl2-config --libs install prefix /usr/local source path . C compiler emcc # emcc    C library ARCH x86 (generic) big-endian no runtime cpu detection yes standalone assembly no
      
      





Let's see how the compilation goes.







 $ emmake make -j4
      
      





And immediately a mistake ...







 root@57ab95def750:/src# emmake make -j4 ... ./libavutil/x86/timer.h:39:24: error: invalid output constraint '=a' in asm : "=a" (a), "=d" (d)); ^
      
      





The message shows that the error is somehow related to asm. Open ./libavutil/x86/timer.h to see that the problem is in x86 inline assembler, which is incompatible with WebAssembly, so turn it off.







 $ emconfigure ./configure \ --disable-x86asm \ --disable-inline-asm \ #   asm --nm="llvm-nm -g" \ --ar=emar \ --cc=emcc \ --cxx=em++ \ --objcc=emcc \ --dep-cc=emcc
      
      





Let's try to compile again.







 $ emmake make -j4
      
      





Compilation continues until the next error.







 root@57ab95def750:/src# emmake make -j4 ... AR libavdevice/libavdevice.a AR libavfilter/libavfilter.a AR libavformat/libavformat.a AR libavcodec/libavcodec.a AR libswresample/libswresample.a AR libswscale/libswscale.a AR libavutil/libavutil.a HOSTLD doc/print_options GENTEXI doc/avoptions_format.texi /bin/sh: 1: doc/print_options: Exec format error doc/Makefile:59: recipe for target 'doc/avoptions_format.texi' failed make: *** [doc/avoptions_format.texi] Error 2 make: *** Waiting for unfinished jobs....
      
      





Something related to the generation of documentation, which we absolutely do not need, so just turn it off.







 $ emconfigure ./configure \ --disable-x86asm \ --disable-inline-asm \ --disable-doc \ #    --nm="llvm-nm -g" \ --ar=emar \ --cc=emcc \ --cxx=em++ \ --objcc=emcc \ --dep-cc=emcc
      
      





We are doing it again.







 $ emmake make -j4
      
      





Now the error has occurred at the strip stage.







 root@57ab95def750:/src# emmake make -j4 ... STRIP ffmpeg STRIP ffprobe strip:ffmpeg_g: File format not recognized strip:ffprobe_g: File format not recognized Makefile:101: recipe for target 'ffmpeg' failed make: *** [ffmpeg] Error 1 make: *** Waiting for unfinished jobs.... Makefile:101: recipe for target 'ffprobe' failed make: *** [ffprobe] Error 1
      
      





Since native cropping is incompatible with our version of WebAssembly, we will also disable it.







 $ emconfigure ./configure \ --disable-x86asm \ --disable-inline-asm \ --disable-doc \ --disable-stripping \ #  strip --nm="llvm-nm -g" \ --ar=emar \ --cc=emcc \ --cxx=em++ \ --objcc=emcc \ --dep-cc=emcc
      
      





Fourth attempt.







 $ emmake make -j4
      
      





Finally, the process ended without errors. But only at the output we got the ffmpeg file, which does not start, and it is not a js file (or wasm file). To get the js file, we need to add -o ffmpeg.js to the emcc command, which can be done in two ways:







  1. Change Makefile of FFmpeg itself
  2. Add additional compilation / linking


We will choose the second way, since I do not want to touch the FFmpeg sources because of possible side effects. So we find how ffmpeg is generated using make. This is where the make option comes in handy to run a dry run.







 $ emmake make -n
      
      





We see the generation team.







 root@57ab95def750:/src# emmake make -n ... printf "LD\t%s\n" ffmpeg_g; emcc -Llibavcodec -Llibavdevice -Llibavfilter -Llibavformat -Llibavresample -Llibavutil -Llibpostproc -Llibswscale -Llibswresample -Wl,--as-needed -Wl,-z,noexecstack -Wl,--warn-common -Wl,-rpath-link=libpostproc:libswresample:libswscale:libavfilter:libavdevice:libavformat:libavcodec:libavutil:libavresample -Qunused-arguments -o ffmpeg_g fftools/ffmpeg_opt.o fftools/ffmpeg_filter.o fftools/ffmpeg_hw.o fftools/cmdutils.o fftools/ffmpeg.o -lavdevice -lavfilter -lavformat -lavcodec -lswresample -lswscale -lavutil -lm -pthread -lm -lm -pthread -lm -lm -lm -pthread -lm printf "CP\t%s\n" ffmpeg; cp -p ffmpeg_g ffmpeg ...
      
      





There’s a lot of unnecessary things, so let's remove the unused arguments (which you will see at the end of the compilation), clean up a bit and rename ffmpeg_g to ffmpeg.js .







 $ emcc \ -Llibavcodec -Llibavdevice -Llibavfilter -Llibavformat -Llibavresample -Llibavutil -Llibpostproc -Llibswscale -Llibswresample \ -Qunused-arguments \ -o ffmpeg.js fftools/ffmpeg_opt.o fftools/ffmpeg_filter.o fftools/ffmpeg_hw.o fftools/cmdutils.o fftools/ffmpeg.o \ -lavdevice -lavfilter -lavformat -lavcodec -lswresample -lswscale -lavutil -lm -pthread
      
      





It should have worked, but we will run into the problem of lack of memory.







 root@57ab95def750:/src# emcc ... shared:ERROR: Memory is not large enough for static data (11794000) plus the stack (5242880), please increase TOTAL_MEMORY (16777216) to at least 17037904
      
      





Add the argument TOTAL_MEMORY to increase the memory size (33554432 Bytes: = 32 MB).







 $ emcc \ -Llibavcodec -Llibavdevice -Llibavfilter -Llibavformat -Llibavresample -Llibavutil -Llibpostproc -Llibswscale -Llibswresample \ -Qunused-arguments \ -o ffmpeg.js fftools/ffmpeg_opt.o fftools/ffmpeg_filter.o fftools/ffmpeg_hw.o fftools/cmdutils.o fftools/ffmpeg.o \ -lavdevice -lavfilter -lavformat -lavcodec -lswresample -lswscale -lavutil -lm -pthread \ -s TOTAL_MEMORY=33554432
      
      





Finally we got our js and wasm files







 root@57ab95def750:/src# ls ffmpeg* ffmpeg ffmpeg.js ffmpeg.js.mem ffmpeg.wasm ffmpeg.worker.js ffmpeg_g
      
      





Create test.html for testing FFmpeg.js







 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <script src="./ffmpeg.js"></script> </head> <body> </body> </html>
      
      





Let's start the easy server (by executing python2 -m SimpleHTTPServer ) and open the resulting page ( http: // localhost: 8000 / test.html ) , then open Chrome DevTools.

















As you can see from the reports, FFmpeg works in half with a sin, so now you can start polishing ffmpeg.js.







The complete build script can be found in this repository (build-with-docker.sh and build-js.sh)





.



All Articles