作者:Jerome Wu
原文链接:Build FFmpeg WebAssembly version (= ffmpeg.wasm): Part.5 ffmpeg.wasm v0.3 — pre-js and live streaming (OUTDATED)
译者:Yodonicc
上一篇文章:编译WebAssembly版本的FFmpeg(ffmpeg.wasm):(4)ffmpeg.wasm v0.2 - 添加Libx264
在这一部分中,你将学习:
- 使用--pre-js来重新定义模块中的函数
- 同时使用ffmpeg.js和网络摄像头
使用--pre-js
来重新定义模块中的函数
FFmpeg有大量的输出,它包含重要的信息,如视频的元数据,编码器/解码器的输出和任务的进展。默认情况下,这些信息是由本地C语言库printf
/sprintf
打印的,虽然你可以在DevTools中看到,但你不能用JavaScript来操作。
幸运的是,在Emscripten中我们可以用--pre-js
或--post-js
重新定义一些默认函数的行为。对于上面的情况,我们需要重新定义的函数是Module['printErr']
(因为FFmpeg的输出使用stderr
),并且用-pre-js
添加到我们的ffmpeg.js中。
下面是我学到的一些经验供你参考。
- 你只能在
--pre-js
中使用ES5语法(没有箭头函数、const、let) - 你需要添加额外的宏来防止你的代码被Closure编译器删除(这里我没有使用Closure编译器)
这里是目前ffmpeg.js中使用的prepend.js
:
var logger = function(){}
Module['setLogger'] = function(_logger) { logger = _logger; };
Module['print'] = function(message) { logger(message, 'stdout'); };
Module['printErr'] = function(message) { logger(message, 'stderr'); };
我们实际上定义了一个新的函数setLogger
来注册我们的自定义记录器函数,并重新定义了两个现有的函数print
和printErr
。有了这个prepend.js,现在我们可以轻松地操作FFmpeg的输出信息,开发更多的功能(如进度条)。
在构建脚本中添加--pre-js
很容易(第54行)
#!/bin/bash -x
set -e -o pipefail
BUILD_DIR=$PWD/build
build_x264() {
cd third_party/x264
emconfigure ./configure
--disable-asm
--disable-thread
--prefix=$BUILD_DIR
emmake make install-lib-static
cd -
}
configure_ffmpeg() {
emconfigure ./configure
--enable-gpl
--enable-libx264
--disable-pthreads
--disable-x86asm
--disable-inline-asm
--disable-doc
--disable-stripping
--disable-ffprobe
--disable-ffplay
--disable-ffmpeg
--prefix=$BUILD_DIR
--extra-cflags="-I$BUILD_DIR/include"
--extra-cxxflags="-I$BUILD_DIR/include"
--extra-ldflags="-L$BUILD_DIR/lib"
--nm="llvm-nm -g"
--ar=emar
--cc=emcc
--cxx=em
--objcc=emcc
--dep-cc=emcc
}
make_ffmpeg() {
NPROC=$(grep -c ^processor /proc/cpuinfo)
emmake make -j${NPROC}
}
build_ffmpegjs() {
emcc
-I. -I./fftools -I$BUILD_DIR/include
-Llibavcodec -Llibavdevice -Llibavfilter -Llibavformat -Llibavresample -Llibavutil -Llibpostproc -Llibswscale -Llibswresample -Llibpostproc -L${BUILD_DIR}/lib
-Qunused-arguments -Oz
-o dist/ffmpeg-core.js fftools/ffmpeg_opt.c fftools/ffmpeg_filter.c fftools/ffmpeg_hw.c fftools/cmdutils.c fftools/ffmpeg.c
-lavdevice -lavfilter -lavformat -lavcodec -lswresample -lswscale -lavutil -lpostproc -lm -lx264
--closure 1
--pre-js javascript/prepend.js # add HERE
-s USE_SDL=2
-s MODULARIZE=1
-s SINGLE_FILE=1
-s EXPORTED_FUNCTIONS="[_ffmpeg]"
-s EXTRA_EXPORTED_RUNTIME_METHODS="[cwrap, FS, getValue, setValue]"
-s TOTAL_MEMORY=33554432
-s ALLOW_MEMORY_GROWTH=1
}
main() {
build_x264
configure_ffmpeg
make_ffmpeg
build_ffmpegjs
}
main "$@"
view rawbuild_ffmpeg-core.js-v0.3.sh hosted with ❤ by GitHub
现在我们在模块对象中有了setLogger
,我们可以在初始化后使用它(第13行)
/**
* This script is loaded by ffmpegwasm-v0.2.js
*/
let Module = null;
let ffmpeg = null;
const load = () => {
global.importScripts('./ffmpeg-core.js');
global.Module()
.then((_Module) => {
Module = _Module;
Module.setLogger(m => console.log(m));
ffmpeg = Module.cwrap('ffmpeg', 'number', ['number', 'number']);
postMessage({ action: 'load', payload: { loaded: true } });
});
};
const transcode = ({ data: _data, outputExt }) => {
/* When an array received from postMessage,
* its format will change to an "ArrayLike" object,
* we need to transform it back to array
*/
const data = Uint8Array.from({ ..._data, length: Object.keys(_data).length });
const args = ['./ffmpeg', '-i', 'file:media', `media.${outputExt}`];
Module.FS.writeFile('media', data);
ffmpeg(args.length, strList2ptr(args));
postMessage({
action: 'transcode',
payload: {
data: Module.FS.readFile(`media.${outputExt}`),
},
});
};
global.addEventListener('message', ({ action, payload }) => {
if (action === 'load') {
load(payload);
} else if (action === 'transcode') {
transcode(payload);
}
});
view rawworker-v0.3.js hosted with ❤ by GitHub
使用ffmpeg.js
与网络摄像头
在这里,我想描述一下如何将ffmpeg用于流媒体直播,这里我们用网络摄像头作为例子,但大多数情况下应该有类似的工作流程。
基本的工作流程是:
- 使用MediaRecorder API将流媒体保存到Blob中
- 将Blob转换为Uint8Array数据
- 使用ffmpeg.js对Uint8Array数据进行转码
步骤1 使用getUserMedia
访问网络摄像头(需要https协议)
<video id="webcam" width="320px" height="180px"></video>
<script>
const webcam = document.getElementById('webcam');
(async () => {
webcam.srcObject = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
await webcam.play();
})();
</script>
第2步:使用MediaRecorder
来录制片段
const startRecording = () => {
const rec = new MediaRecorder(webcam.srcObject);
const chunks = [];
rec.ondataavailable = e => chunks.push(e.data);
rec.onstop = async () => {
transcode(new Uint8Array(await (new Blob(chunks)).arrayBuffer()));
};
rec.start();
};
步骤3 重复使用上一部分的trancode来做转码。
在第五篇文章中,我们学习了如何使用--pre-js
来重新定义/扩展模块的能力,并介绍了一个如何在流媒体直播场景中使用ffmpeg的例子。
在第六篇文章中,我们将对文件系统进行深入研究:编译WebAssembly版本的FFmpeg(ffmpeg.wasm):(6) 深入研究文件系统