Обновление 2020/9: измените историю, чтобы она работала в MacOS.

Обновление 2020/2/11: судя по ответам в этой истории, в настоящее время следующие инструкции не работают в среде Mac. Я не могу решить эту проблему, так как у меня ее нет. Самый простой способ сделать это на Mac - установить дистрибутив Linux, например Ubuntu, на виртуальную машину.

Предыдущая статья: Сборка версии FFmpeg WebAssembly (= ffmpeg.wasm): Часть 1 Подготовка

С этого момента все будет сложнее и труднее для понимания. Возможно, вам потребуются дополнительные знания в Google, если вы не знаете, что произошло (или вы можете оставить ответы, чтобы спросить меня).

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

В этой части вы узнаете:

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

Как настроить среду Emscripten с помощью Docker

В Build FFmpeg WebAssembly version (= ffmpeg.wasm): Part.1 Preparation мы построили исходную версию FFmpeg с GCC, и теперь мы переходим к использованию Emscripten.

Версия Emscripten, которую мы собираемся использовать, - 1.39.18 (trzeci / emscripten: 1.39.18-upstream), вы можете установить Emscripten с помощью официального руководства (в этом руководстве мы setup-emsdk Github Actions в MacOS) или вытащите образ Emscripten из концентратора докеров.

$ docker pull trzeci/emscripten:1.39.18-upstream

Это может занять несколько минут, так как размер изображения составляет около 1 ГБ.

Затем нам нужно обновить build-with-docker.sh, как показано ниже:

Строка 8 не требуется, но она может помочь вам ускорить последующую сборку.

Следующее, что мы собираемся сделать, это найти конфигурацию для создания FFmpeg с emscripten, это процесс проб и ошибок, требующий копания документов и терпения.

Использование emconfigure и emmake & Как исправить проблемы при компиляции FFmpeg с Emscripten

Давайте начнем наш путь к поиску правильной конфигурации. В Части 1 он начинается с ./configure --disable-x86asm, чтобы сделать это с помощью emscripten, вам нужно изменить его на emconfigure ./configure --disable-x86asm. (Подробнее о emconfigure см. ЗДЕСЬ). Поскольку мы выполняем кросс-компиляцию, нам нужно добавить флаги кросс-компиляции, чтобы явно указать FFmpeg.

Давайте обновим build.sh, как показано ниже:

И волшебным образом нет никакой ошибки или чего-то неправильного, так что нам просто нужно набрать emmake make -j, и мы получим FFmpeg.wasm? К сожалению, нет. Одна из наиболее важных задач для emconfigure - заменить компилятор с gcc на emcc (или с g ++ на em ++), но в выводе ./configure мы по-прежнему получаем gcc в качестве нашего компилятора.

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             # Should be 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 в build.sh:

Для собственной сборки убедитесь, что llvm-ranlib, llvm-as и llvm-nm существуют. Если нет, вы можете найти их в $EMSDK_ROOT/upstream/bin.

С этими аргументами для запуска ./configure потребуется больше времени, но в конце концов вы получите желаемый результат.

emscripten sdl2-config called with /emsdk_portable/emscripten/tag-1.39.18/system/bin/sdl2-config --cflags                            
emscripten sdl2-config called with /emsdk_portable/emscripten/tag-1.39.18/system/bin/sdl2-config --libs                                     
install prefix            /usr/local                                                                                                   
source path               .                                                                                                         
C compiler                emcc         # emcc as expected                                                                                    
C library                                                                                                                          
ARCH                      x86 (generic)                                                                                                     
big-endian                no                                                                                                              
runtime cpu detection     yes                                                                                                             
standalone assembly       no

Добавьте emmake make -j4 (вы можете повысить параллелизм до -j8 или просто использовать -j для использования всех ядер) в конце build.sh:

И сразу после выполнения не работает:

...
./libavutil/x86/timer.h:39:24: error: invalid output constraint '=a' in asm
                     : "=a" (a), "=d" (d));
                       ^

Из выходного сообщения мы можем определить, что ошибка связана с asm. Откройте ./libavutil/x86/timer.h, и мы можем подтвердить, что проблема вызвана встроенной сборкой x86, которая несовместима с WebAssembly, поэтому решение состоит в том, чтобы отключить ее в build.sh:

Он работает и продолжает компилироваться, пока мы не встретим еще одну ошибку:

...
CC libavfilter/dnn/dnn_backend_native_layers.o
In file included from libavfilter/aeval.c:26:
In file included from ./libavutil/avassert.h:31:
In file included from ./libavutil/avutil.h:296:
In file included from ./libavutil/common.h:533:
In file included from ./libavutil/internal.h:176:
./libavutil/libm.h:54:32: error: static declaration of 'cbrt' follows non-static declaration
static av_always_inline double cbrt(double x)
                               ^
/emsdk_portable/upstream/emscripten/system/include/libc/math.h:151:13: note: previous declaration is here
double      cbrt(double);
            ^
In file included from libavfilter/aeval.c:26:

На этот раз основная причина не так очевидна, поэтому нам нужно глубже копнуть в том, что пошло не так во время ./configure. Очень полезно проверить файл ffbuild/config.log, он содержит журналы за ./configure, и в большинстве случаев вы можете найти там основную причину.

Выполнив поиск cbrt внутри config.log, мы находим сообщение об ошибке ниже:

...
check_mathfunc cbrt 1                                                                                                                             test_ld cc                                                                                                                                        test_cc                                                                                                                                           BEGIN /tmp/ffconf.syfN4Irw/test.c                                                                                                                     1 #include <math.h>                                                                                                                               2 float foo(float f, float g) { return cbrt(f); }                                                                                             
    3 int main(void){ return (int) foo; }                                                                                                         
END /tmp/ffconf.syfN4Irw/test.c                                                                                                                   
emcc -D_ISOC99_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_POSIX_C_SOURCE=200112 -D_XOPEN_SOURCE=600 -std=c11 -fomit-frame-pointer -pthread -c -o /tmp/ffconf.syfN4Irw/test.o /tmp/ffconf.syfN4Irw/test.c                                                                                  emcc -Wl,-z,noexecstack -o /tmp/ffconf.syfN4Irw/test /tmp/ffconf.syfN4Irw/test.o                                                                  wasm-ld: error: 'atomics' feature is used by /tmp/ffconf.syfN4Irw/test.o, so --shared-memory must be used
...                                         

Этот тест пытался проверить, работает ли cbrt в среде, но не удалось из-за ошибки atomics функции. atomics спрашивается, когда вы используете pthread, поэтому давайте добавим pthread флаги. (Проверьте ЗДЕСЬ, чтобы узнать больше о флагах pthread)

Обновить build.sh:

Он работает и продолжает компилироваться, пока мы не встретим еще одну ошибку:

...
LD      ffplay_g                                                                                                                                  emcc: warning: ignoring unsupported linker flag: `-rpath-link=:libpostproc:libswresample:libswscale:libavfilter:libavdevice:libavformat:libavcodec:libavutil:libavresample` [-Wlinkflags]                                                                                                           
7 warnings generated.                                                      
wasm-ld: error: initial memory too small, 19491744 bytes needed
...                    
make: *** [Makefile:114: ffplay_g] Error 1
make: *** Waiting for unfinished jobs....
emcc: warning: ignoring unsupported linker flag: `-rpath-link=:libpostproc:libswresample:libswscale:libavfilter:libavdevice:libavformat:libavcodec
:libavutil:libavresample` [-Wlinkflags]
...

На этот раз проблема вызвана тем, что исходная память слишком мала (по умолчанию только 16 МБ в Emscripten, а здесь минимальное значение составляет 19+ МБ), поэтому нам нужно увеличить начальную память до более высокого значения, передав -s INITIAL_MEMORY=33554432 (32 МБ) .

После этого исправления все еще возникает ошибка:

LD      ffplay_g
emcc: warning: ignoring unsupported linker flag: `-rpath-link=:libpostproc:libswresample:libswscale:libavfilter:libavdevice:libavformat:libavcodec:libavutil:libavresample` [-Wlinkflags]
6 warnings generated.
LD      ffmpeg_g
emcc: warning: ignoring unsupported linker flag: `-rpath-link=:libpostproc:libswresample:libswscale:libavfilter:libavdevice:libavformat:libavcodec:libavutil:libavresample` [-Wlinkflags]
9 warnings generated.
LD      ffprobe_g
emcc: warning: ignoring unsupported linker flag: `-rpath-link=:libpostproc:libswresample:libswscale:libavfilter:libavdevice:libavformat:libavcodec:libavutil:libavresample` [-Wlinkflags]
STRIP   ffmpeg
strip:ffmpeg_g: file format not recognized
make: *** [Makefile:107: ffmpeg] Error 1
make: *** Waiting for unfinished jobs....

Поскольку мы не можем разделить (это имеет смысл, поскольку это недопустимый двоичный формат), давайте просто отключим разделение с помощью --disable-stripping и снова сделаем:

Наконец, нам удалось успешно выполнить часть emmake make -j, и вы можете увидеть ffplay / ffplay_g, ffprobe / ffprobe_g и ffmpeg / ffmpeg_g, созданные в корневой папке. Выглядит идеально, но есть странный суффикс _g, который делает выходные файлы такими:

  • ffmpeg
  • ffmpeg_g
  • ffmpeg_g.wasm
  • ffmpeg_g.worker.js

И ffmpeg, и ffmpeg_g здесь являются актуальными js-файлами, идеальное именование показано ниже:

  • ffmpeg / ffmpeg_g = ›ffmpeg.js
  • ffmpeg_g.wasm = ›ffmpeg.wasm
  • ffmpeg_g.worker.js = ›ffmpeg.worker.js

Чтобы решить эту проблему, нам нужно создать ее самостоятельно. Команду для создания ffmpeg можно извлечь, запустив 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
...

С небольшой очисткой:

emcc \
  -I. -I./fftools \
  -Llibavcodec -Llibavdevice -Llibavfilter -Llibavformat -Llibavresample -Llibavutil -Llibpostproc -Llibswscale -Llibswresample \
  -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

Поскольку мы создаем нашу собственную версию, давайте добавим --disable-programs и --disable-doc на этапе ./configure, чтобы ускорить сборку, а также добавим некоторые важные флаги при сборке ffmpeg.

Давайте создадим basic.html, чтобы проверить, работает ли ffmpeg.wasm:

Запустите легкий веб-сервер (например, python3 -m http.server 3000), посетите веб-страницу (например, http://localhost:3000/basic.html) и откройте Chrome DevTools.

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

Вы можете посетить репозиторий здесь, чтобы подробнее узнать, как это работает: https://github.com/ffmpegwasm/FFmpeg/tree/n4.3.1-p2

И не стесняйтесь загружать артефакты сборки здесь: https://github.com/ffmpegwasm/FFmpeg/releases/tag/n4.3.1-p2

О том, как усовершенствовать и создать настоящую библиотеку ffmpeg.wasm, см. Сборка версии FFmpeg WebAssembly (= ffmpeg.wasm): Часть 3 ffmpeg.wasm v0.1 - Транскодирование avi в mp4 этой серии рассказов. 😃