Извлечение видеокадров из внешнего интерфейса ffmpeg + Webassembly

WebAssembly
Извлечение видеокадров из внешнего интерфейса ffmpeg + Webassembly

Извлечение видеокадров из внешнего интерфейса на основе ffmpeg + Webassembly

Существующее внешнее извлечение видеокадров в основном основано наcanvas + videoВ методе маркировки после того, как пользователь локально выбирает видеофайл, локальный файл преобразуется вObjectUrlустановлен вvideoпомеченsrcсвойства, затем передатьcanvasизdrawImageИнтерфейс извлекает кадр видео в текущий момент.

Ограничено форматом кодирования видео, поддерживаемым браузером, даже самый полностью поддерживаемый браузер Chrome может только анализироватьMP4/WebMвидеофайлы иH.264/VP8видеокодировка видео. При столкновении некоторых форматов подавленных и инкапсулированных пользователем, из-за ограничения браузера, обычные видеокадры не могут быть перехвачены. Как показано на рисунке 1,mpeg4Закодированное видео можно нормально воспроизвести в QQ Video, а вот парсить картинку в браузере вообще нельзя.

图1

Обычно в этом случае видеоизображение может быть извлечено только после того, как видео загружено и декодировано серверной частью.WebassemblyПоявление видеокадра дает возможность фронтенду полностью реализовать перехват видеокадров. Итак, наша общая идея дизайна такова:ffmpegкомпилируется вWebassemblyбиблиотека, затем пройтиjsВызовите соответствующий интерфейс для перехвата видеокадра, а затем передайте информацию о перехваченном изображении черезcanvasНарисуйте его, как показано на рисунке 2.

图2

1. Васм-модуль

1. Компиляция ffmpeg

первый вubuntuсистема, согласноофициальный сайт эмскриптенаустановка документовemsdk(другие видыlinuxСистему тоже можно установить, но это сложнее, рекомендуется использоватьubuntuсистема для установки). Во время установки может потребоваться доступgooglesource.comЗагружайте зависимости, поэтому лучше всего найти машину с прямым выходом во внешнюю сеть, в противном случае вам придется вручную скачивать зеркало для установки. После завершения установки вы можетеemcc -vПосмотреть версию, эта статья основана на1.39.18версии, как показано на рисунке 3.

图3

затем вофициальный сайт ffmpegскачатьffmpegисходный кодreleaseМешок. После попытки скомпилировать несколько версий я обнаружил, что на основе3.3.9Отключено при компиляцииswresampleТакие библиотеки могут быть успешно скомпилированы, но некоторые более новые версии все еще имеют проблему нехватки памяти для компиляции после их отключения. Итак, эта статья основана наffmpeg 3.3.9версия для разработки.

Используйте после загрузкиemccСкомпилируйте, чтобы получить c-зависимую библиотеку и соответствующие заголовочные файлы, необходимые для написания декодера.Здесь некоторые функции, которые не нужны, изначально отключены.wasmСкомпилируйте и снова оптимизируйте для подробной настройки и ознакомления

Конкретная конфигурация компиляции выглядит следующим образом:

emconfigure ./configure \
    --prefix=/data/web-catch-picture/lib/ffmpeg-emcc \
    --cc="emcc" \
    --cxx="em++" \
    --ar="emar" \
    --enable-cross-compile \
    --target-os=none \
    --arch=x86_32 \
    --cpu=generic \
    --disable-ffplay \
    --disable-ffprobe \
    --disable-asm \
    --disable-doc \
    --disable-devices \
    --disable-pthreads \
    --disable-w32threads \
    --disable-network \
    --disable-hwaccels \
    --disable-parsers \
    --disable-bsfs \
    --disable-debug \
    --disable-protocols \
    --disable-indevs \
    --disable-outdevs \
    --disable-swresample
make

make install

Результат компиляции показан на рисунке 4.

图4

2. Кодирование декодера на основе ffmpeg

В основном используются декодирование видео и извлечение изображений.ffmpegИнтерфейсы, связанные с декапсуляцией, декодированием и преобразованием масштабирования изображения, в основном основаны на следующих библиотеках.

libavcodec - 音视频编解码 
libavformat - 音视频解封装
libavutil - 工具函数
libswscale - 图像缩放&色彩转换

После введения библиотеки зависимостей вызывается соответствующий интерфейс для декодирования и извлечения видеокадра.Основной процесс показан на рисунке 5.

图5

3. компиляция васм

После написания соответствующего кода декодера вам необходимо передатьemccскомпилировать декодер и зависимые связанные библиотеки какwasmдля вызова js.emccДоступ к параметрам компиляции можно получить черезemcc --helpЧтобы получить подробные инструкции, конкретная конфигурация компиляции выглядит следующим образом:

export TOTAL_MEMORY=33554432

export FFMPEG_PATH=/data/web-catch-picture/lib/ffmpeg-emcc

emcc capture.c ${FFMPEG_PATH}/lib/libavformat.a ${FFMPEG_PATH}/lib/libavcodec.a ${FFMPEG_PATH}/lib/libswscale.a ${FFMPEG_PATH}/lib/libavutil.a \
    -O3 \
    -I "${FFMPEG_PATH}/include" \
    -s WASM=1 \
    -s TOTAL_MEMORY=${TOTAL_MEMORY} \
    -s EXPORTED_FUNCTIONS='["_main", "_free", "_capture"]' \
    -s ASSERTIONS=1 \
    -s ALLOW_MEMORY_GROWTH=1 \
    -o /capture.js

главным образом через-O3сжимать,EXPORTED_FUNCTIONSЭкспортируйте функцию для вызова js иALLOW_MEMORY_GROWTH=1Позвольте памяти расти.

2. js-модуль

1. перенос памяти wasm

После того, как видеокадр извлечен, данные RGB видеокадра необходимо передать в js, чтобы отрисовать изображение посредством передачи памяти. Здесь wasm в основном имеет следующие операции

  1. Преобразование необработанных данных видеокадра в данные RGB
  2. Сохраняйте данные RGB как данные памяти, которые удобны для вызова js для вызова js

Исходные данные видеокадра обычно имеют видYUVФормат сохраняется, после декодирования видеокадра заданного времени его нужно преобразовать в формат RGB, прежде чем его можно будет отрисовать на канве через js. упомянутый вышеffmpegизlibswscaleобеспечивает такую ​​функциюswsВыведите декодированный видеокадр какAV_PIX_FMT_RGB24Формат (то есть 8-битный формат RGB) данных, конкретный код выглядит следующим образом

sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL);

После декодирования и преобразования данных видеокадра также сохраните данные RGB в памяти и передайте их js для чтения. Вот структура, определенная для сохранения информации об изображении.

typedef struct {
    uint32_t width;
    uint32_t height;
    uint8_t *data;
} ImageData;

Использование структурыuint32_tЧтобы сохранить информацию о ширине и высоте изображения, используйтеuint8_tдля сохранения информации о данных изображения. из-заcanvasДанные, необходимые для чтения и рисования наUint8ClampedArrayт.е. 8-битный беззнаковый массив, в этой структуре также используются данные изображенияuint8_tФормат для хранения, удобный для чтения при последующих вызовах js.

2. js взаимодействует с wasm

Взаимодействие между js и wasm в основном дляwasmПамять пишет, а результат читает. в отinputПосле получения файла прочитайте и сохраните файл какUnit8Arrayи написатьwasmПамять вызывается кодом и должна быть использована в первую очередьModule._mallocВыделить память, затем передатьModule.HEAP8.setЗапишите в память и, наконец, передайте указатель и размер памяти в качестве параметров и вызовите экспортированный метод. Конкретный код выглядит следующим образом


// 将 fileReader 保存为 Uint8Array
let fileBuffer = new Uint8Array(fileReader.result);

// 申请文件大小的内存空间
let fileBufferPtr = Module._malloc(fileBuffer.length);

// 将文件内容写入 wasm 内存
Module.HEAP8.set(fileBuffer, fileBufferPtr);

// 执行导出的 _capture 函数,分别传入内存指针,内存大小,时间点
let imgDataPtr = Module._capture(fileBufferPtr, fileBuffer.length, (timeInput.value) * 1000)

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

существуетImageDataВ конструкции и ширина, и высотаuint32_tТип, то есть очень удобно, чтобы первые 4 байта указателя возвращали память для представления ширины, следующие 4 байта для представления высоты, а следующие — этоuint8_tRGB-данные изображения.

из-заwasmВозвращаемый указатель представляет собой один байт и одну единицу, поэтому читайте в jsImageDataСтруктура нужна толькоimgDataPtr /4может получитьImageDataсерединаwidthадрес и так далее можно получить соответственноheightа такжеdata, конкретный код выглядит следующим образом


// Module.HEAPU32 读取 width、height、data 的起始位置
let width = Module.HEAPU32[imgDataPtr / 4],
    height = Module.HEAPU32[imgDataPtr / 4 + 1],
    imageBufferPtr = Module.HEAPU32[imgDataPtr / 4 + 2];

// Module.HEAPU8 读取 uint8 类型的 data
let imageBuffer = Module.HEAPU8.subarray(imageBufferPtr, imageBufferPtr + width * height * 3);

На данный момент мы получили данные ширины, высоты и RGB изображения соответственно.

3. Рисунок данных изображения

После получения данных ширины, высоты и RGB изображения вы можете передатьcanvasнарисовать соответствующее изображение. Здесь также следует отметить, что сwasmДанные, полученные вcanvasВам нужно сделать канал А раньше, а затем пройтиcanvasизImageDataКлассы набираются вcanvas, конкретный код выглядит следующим образом

function drawImage(width, height, imageBuffer) {
    let canvas = document.createElement('canvas');
    let ctx = canvas.getContext('2d');

    canvas.width = width;
    canvas.height = height;

    let imageData = ctx.createImageData(width, height);

    let j = 0;
    for (let i = 0; i < imageBuffer.length; i++) {
        if (i && i % 3 == 0) {
            imageData.data[j] = 255;
            j += 1;
        }
        imageData.data[j] = imageBuffer[i];
        j += 1;
    }
    ctx.putImageData(imageData, 0, 0, 0, 0, width, height);
}

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

3. Васм-оптимизация

После реализации функции нужно обратить внимание на общую производительность. Включая объем, память, потребление ЦП и т. д., давайте сначала посмотрим на начальную производительность. Поскольку использование ЦП и потребление времени различаются в разных моделях, мы сначала сосредоточимся на объеме и использовании памяти, как показано на рисунке 6.

Исходный размер файла wasm11.6M, размер после gzip4M, инициализировать память как220M, если он используется онлайн, он будет долго загружаться и занимать много места в памяти.

图6

Далее мы начинаемwasmоптимизировать.

к вышеизложенномуwasmАнализ команды компиляции можно увидеть, мы скомпилировалиwasmФайл в основном состоит изcapture.cа такжеffmpegСкомпилировано из многих библиотечных файлов, поэтому наши идеи по оптимизации в основном включаютffmpegОптимизация компиляции иwasmОптимизация сборки.

1. оптимизация компиляции ffmpeg

надffmpegКонфигурация компиляции просто выполняет некоторые простые настройки и отключает некоторые функции, которые обычно не используются. По сути, в процессе извлечения видеокадров мы используем толькоlibavcodec,libavformat,libavutil,libswscaleЧасть функций этих четырех библиотек, поэтому вffmpegОптимизация компиляции Здесь вы можете дополнительно оптимизировать с помощью подробной настройки компиляции, тем самым уменьшая размер скомпилированного исходного файла.

бегать./configure --helpможно будет увидеть позжеffmpegВарианты компиляции очень богаты.Вы можете выбрать общие форматы кодирования и упаковки в соответствии с нашими бизнес-сценариями и создать на их основе подробные конфигурации оптимизации компиляции.Конкретные оптимизированные конфигурации компиляции приведены ниже.

emconfigure ./configure \
    --prefix=/data/web-catch-picture/lib/ffmpeg-emcc \
    --cc="emcc" \
    --cxx="em++" \
    --ar="emar" \
    --cpu=generic \
    --target-os=none \
    --arch=x86_32 \
    --enable-gpl \
    --enable-version3 \
    --enable-cross-compile \
    --disable-logging \
    --disable-programs \
    --disable-ffmpeg \
    --disable-ffplay \
    --disable-ffprobe \
    --disable-ffserver \
    --disable-doc \
    --disable-swresample \
    --disable-postproc  \
    --disable-avfilter \
    --disable-pthreads \
    --disable-w32threads \
    --disable-os2threads \
    --disable-network \
    --disable-everything \
    --enable-demuxer=mov \
    --enable-decoder=h264 \
    --enable-decoder=hevc \
    --enable-decoder=mpeg4 \
    --disable-asm \
    --disable-debug \

make

make install

делать исходя из этогоffmpegПосле оптимизации компиляции размер файла и использование памяти показаны на рисунке 7.

Исходный размер файла wasm2.8M, размер после gzip0.72M, инициализировать память как112M, что примерно вдвое превышает объем памяти, занимаемой домашней страницей QQ Music, открытой в той же среде.Эквивалентно открытию 2 музыкальных домашних страниц QQ.можно сказать, что оптимизированныйwasmФайл был сравнен со стандартами для онлайн-использования.

图7

2. оптимизация сборки wasm

ffmpegПосле компиляции и оптимизации вы также можетеwasmСборка и загрузка дополнительно оптимизированы. Как показано на рисунке 8, непосредственно используйте построенныйcapture.jsнагрузкаwasmфайл будет повторяться дваждыwasmфайл и распечатать соответствующую информацию о тревоге в консоли

图8

мы можем поставитьemccУровень сжатия в команде сборки изменен наO0После этого перекомпилируйте для анализа.

Наконец нашел проблему, потому что,capture.jsбудет использоваться по умолчаниюWebAssembly.instantiateStreamingМетод инициализируется и повторно используется после сбоя.ArrayBufferспособ инициализации. И поскольку многие CDN или прокси-серверы возвращают заголовки ответов, которые неWebAssembly.instantiateStreamingидентифицируемыйapplication/wasm, но будетwasmФайл обрабатывается как обычный двоичный поток, а заголовок ответаContent-Typeв основномapplication/octet-stream, так что будет повторно использоватьArrayBufferСпособ повторной инициализации, как показано на рисунке 9

图9

Проанализировав исходный код, мы можем найти решение этой проблемы, т. е. черезModule.instantiateWasmспособ настройкиwasmФункция инициализации, используйте напрямуюArrayBufferСпособ инициализации, конкретный код выглядит следующим образом.

Module = {
    instantiateWasm(info, receiveInstance) {
        fetch('/wasm/capture.wasm')
            .then(response => {
                return response.arrayBuffer()
            }
            ).then(bytes => {
                return WebAssembly.instantiate(bytes, info)
            }).then(result => {
                receiveInstance(result.instance);
            })
    }
}

Таким образом, можно настроитьwasmЗагрузка и чтение файлов. а такжеModuleЕсть еще много интерфейсов, которые можно назвать и переписать, и это нужно изучать в будущем.

4. Резюме

WebassemblyЭто значительно расширяет сценарии приложений браузера, и некоторые сценарии, которые не могут быть реализованы с помощью js или имеют проблемы с производительностью, могут быть рассмотрены для этого решения. а такжеffmpegКак мощная аудио- и видеобиблиотека, извлечение видеокадров — это лишь малая часть ее функций, и это еще не все.ffmpeg + Webassemblyсценарии применения могут быть изучены.

5. Адрес проекта

GitHub.com/J или Monarch/I…

6. Последующая оптимизация

Извлечение видеокадров из внешнего интерфейса ffmpeg + Webassembly(2)

Справочная статья

  1. Supported Media for Google Cast Developers.Google.com/cast/docs/no…
  2. emscripten em script en.org/docs/Everyday Physical Energy…
  3. ffmpeg ffmpeg.org/download.Контракты…