
Список переведённых частей серии:
Начиная с этой части, материал будет посложнее, так что не стесняйтесь гуглить по ходу чтения, если не понимаете, что происходит.
К тому же я постараюсь задокументировать решение возможных проблем, чтобы вы смогли скомпилировать бибилиотеку со своими настройками.
В этой части мы разберём:
- Как настроить окружение для Emscripten в Docker
- Использование emconfigure и emmake
- Как решать проблемы, возникающие при компиляции FFmpeg с Emscripten
Как настроить окружение для Emscripten в Docker
В первой части мы собрали FFmpeg с gcc и можем перейти к использованию образа Докера с emscripten.
Я буду использовать trzeci/emscripten версии 1.38.45:
$ docker pull trzeci/emscripten:1.38.45
Так как образ занимает около 1 Гб, процесс может занять некоторое время.
Теперь найдём правильную конфигурацию для компиляции FFmpeg в emscripten методом проб и ошибок, что потребует усидчивости и чтения больших объёмов документации. Запустим контейнер с emscripten и монтируем исходники FFmpeg в каталог /src.
# Убедитесь, что вы в корне репозитория FFmpeg $ docker run -it \ -v $PWD:/src \ trzeci/emscripten:1.38.45 \ /bin/bash
Внутри контейнера выполните ls --color, чтобы увидеть что-то подобное:

Использование emconfigure и emmake. Как решать проблемы, возникающие при компиляции
Начнём с конфигурации. В первой части мы выполняли ./configure --disable-x86asm, в emscripten это достигается командой emconfigure ./configure --disable-x86asm. (Детали использования emconfigure смотрите здесь)
$ emconfigure ./configure --disable-x86asm
И так как ошибок мы не увидели, осталось лишь выполнить emmake make -j4 и получить заветный FFmpeg.js? К сожалению, нет. Одной из наиболее важных задач для emconfigure является замена компилятора gcc на emcc (или g++ на em++), но результат выполнения ./configure всё ещё выдаёт 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
У любой автоматизации есть свои пределы и в данном случае, к сожалению, нам придётся делать всё вручную. Давайте посмотрим, есть ли какие-нибудь аргументы нам в помощь:
$ ./configure --help
Под разделом Toolchain options мы видим аргументы для указания типа компилятора.
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 [] ...
Давайте используем их в emscripten
$ emconfigure ./configure \ --disable-x86asm \ --nm="llvm-nm -g" \ --ar=emar \ --cc=emcc \ --cxx=em++ \ --objcc=emcc \ --dep-cc=emcc
Теперь выполнение ./configure займёт больше времени, но в результате мы получим 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
Посмотрим, как пройдёт компиляция.
$ emmake make -j4
И сразу ошибка…
root@57ab95def750:/src# emmake make -j4 ... ./libavutil/x86/timer.h:39:24: error: invalid output constraint '=a' in asm : "=a" (a), "=d" (d)); ^
Из сообщения видно, что ошибка как-то связана с asm. Откроем ./libavutil/x86/timer.h чтобы увидеть, что проблема в инлайновом ассемблере x86, который несовместим с WebAssembly, так что отключим его.
$ emconfigure ./configure \ --disable-x86asm \ --disable-inline-asm \ # Отключаем инлайн asm --nm="llvm-nm -g" \ --ar=emar \ --cc=emcc \ --cxx=em++ \ --objcc=emcc \ --dep-cc=emcc
Попробуем скомпилировать вновь.
$ emmake make -j4
Компиляция продолжается до следующей ошибки
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....
Что-то связанное с генерацией документации, которая нам совершенно не нужна, так что просто отключим её.
$ emconfigure ./configure \ --disable-x86asm \ --disable-inline-asm \ --disable-doc \ # Отключаем генерацию документации --nm="llvm-nm -g" \ --ar=emar \ --cc=emcc \ --cxx=em++ \ --objcc=emcc \ --dep-cc=emcc
Вновь выполняем.
$ emmake make -j4
Теперь ошибка возникла на этапе strip.
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
Раз нативная обрезка несовместима с нашей версией WebAssembly, отключим и её.
$ 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
Четвёртая попытка.
$ emmake make -j4
Наконец-то процесс завершился без ошибок. Вот только на выходе мы получили файл ffmpeg, который не запускается, да и не является js файлом (или wasm файлом). Чтобы получить js файл, нам нужно добавить -o ffmpeg.js в комманду emcc, что можно сделать двумя способами:
- Изменить Makefile самого FFmpeg
- Добавить дополнительную компиляцию/линковку
Мы выберем второй путь, так как я не хочу трогать исходники FFmpeg из-за возможных побочных эффектов. Так что найдём как генерируется ffmpeg с помощью make. Здесь пригодится возможность make запустить сухой прогон (dry-run).
$ emmake make -n
Видим команду генерации.
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 ...
Много всего ненужного, так что давайте уберём неиспользуемые аргументы (которые вы увидите в конце компиляции), немного приберёмся и переименуем ffmpeg_g в 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
Должно было сработать, но мы столкнёмся с проблемой отсутствия памяти.
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
Добавим аргумент TOTAL_MEMORY для увеличения размера памяти (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
Наконец-то мы получили наши js и wasm файлы
root@57ab95def750:/src# ls ffmpeg* ffmpeg ffmpeg.js ffmpeg.js.mem ffmpeg.wasm ffmpeg.worker.js ffmpeg_g
Создадим test.html для тестирования FFmpeg.js
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <script src="./ffmpeg.js"></script> </head> <body> </body> </html>
Запустим лёгенький сервер (выполнив python2 -m SimpleHTTPServer) и откроем получившуюся страницу (http://localhost:8000/test.html), после чего откроем Chrome DevTools.

Как видно по сообщениям, FFmpeg с грехом пополам работает, так что теперь можно приступать к полировке ffmpeg.js.
Полностью скрипт для сборки можно найти в этом репозитории (build-with-docker.sh и build-js.sh)
.