[Перевод] Компилируем FFmpeg в WebAssembly (=ffmpeg.js): Часть 2 — Компиляция с Emscripten

899a0ba50105cb44cf23fe8cc40b8e42.png

Список переведённых частей серии:


  1. Приготовления
  2. Компиляция с Emscripten (вы тут)


Начиная с этой части, материал будет посложнее, так что не стесняйтесь гуглить по ходу чтения, если не понимаете, что происходит.

К тому же я постараюсь задокументировать решение возможных проблем, чтобы вы смогли скомпилировать бибилиотеку со своими настройками.

В этой части мы разберём:


  1. Как настроить окружение для Emscripten в Docker
  2. Использование emconfigure и emmake 
  3. Как решать проблемы, возникающие при компиляции FFmpeg с Emscripten


В первой части мы собрали 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, чтобы увидеть что-то подобное:


8c347c87c6b34354a3429ef2f1bc0681.png

Начнём с конфигурации. В первой части мы выполняли ./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, что можно сделать двумя способами:


  1. Изменить Makefile самого FFmpeg
  2. Добавить дополнительную компиляцию/линковку

Мы выберем второй путь, так как я не хочу трогать исходники 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 




  
  
  














Запустим лёгенький сервер (выполнив python2 -m SimpleHTTPServer) и откроем получившуюся страницу (http://localhost:8000/test.html), после чего откроем Chrome DevTools.


b6d054b03a00813a5bfc12c4d41ffa83.png

Как видно по сообщениям, FFmpeg с грехом пополам работает, так что теперь можно приступать к полировке ffmpeg.js.

Полностью скрипт для сборки можно найти в этом репозитории: https://github.com/ffmpegjs/FFmpeg (build-with-docker.sh и build-js.sh)

© Habrahabr.ru