Извлечение видеокадров из внешнего интерфейса на основе ffmpeg + Webassembly
Существующее внешнее извлечение видеокадров в основном основано наcanvas
+ video
В методе маркировки после того, как пользователь локально выбирает видеофайл, локальный файл преобразуется вObjectUrl
установлен вvideo
помеченsrc
свойства, затем передатьcanvas
изdrawImage
Интерфейс извлекает кадр видео в текущий момент.
Ограничено форматом кодирования видео, поддерживаемым браузером, даже самый полностью поддерживаемый браузер Chrome может только анализироватьMP4
/WebM
видеофайлы иH.264
/VP8
видеокодировка видео. При столкновении некоторых форматов подавленных и инкапсулированных пользователем, из-за ограничения браузера, обычные видеокадры не могут быть перехвачены. Как показано на рисунке 1,mpeg4
Закодированное видео можно нормально воспроизвести в QQ Video, а вот парсить картинку в браузере вообще нельзя.
Обычно в этом случае видеоизображение может быть извлечено только после того, как видео загружено и декодировано серверной частью.Webassembly
Появление видеокадра дает возможность фронтенду полностью реализовать перехват видеокадров. Итак, наша общая идея дизайна такова:ffmpeg
компилируется вWebassembly
библиотека, затем пройтиjs
Вызовите соответствующий интерфейс для перехвата видеокадра, а затем передайте информацию о перехваченном изображении черезcanvas
Нарисуйте его, как показано на рисунке 2.
1. Васм-модуль
1. Компиляция ffmpeg
первый вubuntu
система, согласноофициальный сайт эмскриптенаустановка документовemsdk
(другие видыlinux
Систему тоже можно установить, но это сложнее, рекомендуется использоватьubuntu
система для установки). Во время установки может потребоваться доступgooglesource.com
Загружайте зависимости, поэтому лучше всего найти машину с прямым выходом во внешнюю сеть, в противном случае вам придется вручную скачивать зеркало для установки. После завершения установки вы можетеemcc -v
Посмотреть версию, эта статья основана на1.39.18версии, как показано на рисунке 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.
2. Кодирование декодера на основе ffmpeg
В основном используются декодирование видео и извлечение изображений.ffmpeg
Интерфейсы, связанные с декапсуляцией, декодированием и преобразованием масштабирования изображения, в основном основаны на следующих библиотеках.
libavcodec - 音视频编解码
libavformat - 音视频解封装
libavutil - 工具函数
libswscale - 图像缩放&色彩转换
После введения библиотеки зависимостей вызывается соответствующий интерфейс для декодирования и извлечения видеокадра.Основной процесс показан на рисунке 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 в основном имеет следующие операции
- Преобразование необработанных данных видеокадра в данные RGB
- Сохраняйте данные 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_t
RGB-данные изображения.
из-за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, если он используется онлайн, он будет долго загружаться и занимать много места в памяти.
Далее мы начинаем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
Файл был сравнен со стандартами для онлайн-использования.
2. оптимизация сборки wasm
ffmpeg
После компиляции и оптимизации вы также можетеwasm
Сборка и загрузка дополнительно оптимизированы. Как показано на рисунке 8, непосредственно используйте построенныйcapture.js
нагрузкаwasm
файл будет повторяться дваждыwasm
файл и распечатать соответствующую информацию о тревоге в консоли
мы можем поставитьemcc
Уровень сжатия в команде сборки изменен наO0
После этого перекомпилируйте для анализа.
Наконец нашел проблему, потому что,capture.js
будет использоваться по умолчаниюWebAssembly.instantiateStreaming
Метод инициализируется и повторно используется после сбоя.ArrayBuffer
способ инициализации. И поскольку многие CDN или прокси-серверы возвращают заголовки ответов, которые неWebAssembly.instantiateStreaming
идентифицируемыйapplication/wasm
, но будетwasm
Файл обрабатывается как обычный двоичный поток, а заголовок ответаContent-Type
в основномapplication/octet-stream
, так что будет повторно использоватьArrayBuffer
Способ повторной инициализации, как показано на рисунке 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. Адрес проекта
6. Последующая оптимизация
Извлечение видеокадров из внешнего интерфейса ffmpeg + Webassembly(2)