前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >编译WebAssembly版本的FFmpeg(ffmpeg.wasm):(5)ffmpeg.wasm v0.3 - pre.js与实时音视频流

编译WebAssembly版本的FFmpeg(ffmpeg.wasm):(5)ffmpeg.wasm v0.3 - pre.js与实时音视频流

原创
作者头像
智影Yodonicc
发布2022-04-16 02:10:14
2.8K3
发布2022-04-16 02:10:14
举报
文章被收录于专栏:智影Yodonicc智影Yodonicc

作者: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

在这一部分中,你将学习:

  1. 使用--pre-js来重新定义模块中的函数
  2. 同时使用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:

代码语言:javascript
复制
var logger = function(){}

Module['setLogger'] = function(_logger) { logger = _logger; };
Module['print'] = function(message) { logger(message, 'stdout'); };
Module['printErr'] = function(message) { logger(message, 'stderr'); };

我们实际上定义了一个新的函数setLogger来注册我们的自定义记录器函数,并重新定义了两个现有的函数printprintErr。有了这个prepend.js,现在我们可以轻松地操作FFmpeg的输出信息,开发更多的功能(如进度条)。

在构建脚本中添加--pre-js很容易(第54行)

代码语言:shell
复制
#!/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行)

代码语言:javascript
复制
/**
 * 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用于流媒体直播,这里我们用网络摄像头作为例子,但大多数情况下应该有类似的工作流程。

基本的工作流程是:

  1. 使用MediaRecorder API将流媒体保存到Blob中
  2. 将Blob转换为Uint8Array数据
  3. 使用ffmpeg.js对Uint8Array数据进行转码

步骤1 使用getUserMedia访问网络摄像头(需要https协议)

代码语言:html
复制
<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来录制片段

代码语言:javascript
复制
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) 深入研究文件系统😃

代码目录:

注:特别感谢技术指导dazhao(赵达)对本文翻译的审阅指正

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 使用--pre-js来重新定义模块中的函数
  • 使用ffmpeg.js与网络摄像头
相关产品与服务
云直播
云直播(Cloud Streaming Services,CSS)为您提供极速、稳定、专业的云端直播处理服务,根据业务的不同直播场景需求,云直播提供了标准直播、快直播、云导播台三种服务,分别针对大规模实时观看、超低延时直播、便捷云端导播的场景,配合腾讯云视立方·直播 SDK,为您提供一站式的音视频直播解决方案。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档