前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >IVWEB玩转wasm系列-纯web视频剪辑/转换工具

IVWEB玩转wasm系列-纯web视频剪辑/转换工具

作者头像
腾讯IVWEB团队
发布2020-06-27 22:06:59
2.8K0
发布2020-06-27 22:06:59
举报

我们的业务是十分养眼的NOW直播,每一场直播结束后,我们都会保存一段时间的直播回放,每一场直播回放都充满了不少的精彩片段,然而要从2、3小时的直播回放中准确找出这些精彩片段却不是那么容易的事情。于是,故事要从一次需求宣讲说起,我们的产品希望能在回放中剪辑出主播的高光时刻,作为前端的我们本来是听听就好,毕竟长期以来视频裁剪工作都是在后台完成,然而这一次,作为IVWEB的前端,我们决定拿起wasm去试一试。

1. 多年前的方案

在2013年(今年是2019年)的Node Knockout比赛上,有人提出了一个叫 Video Funhouse(年代太久远,我没能找到更多的资料)的设想,后来就有了github上的videoconverter方案。videoconverter将音视频领域中的瑞士军刀ffmpeg通过emscripten(一个可以将C/C++代码生成asm/wasm的编译工具)转化为javascript,实现了在浏览器上对视频的简单操作,包括视频的裁剪/转换。它的demo目前还能运行,地址如下:http://bgrins.github.io/videoconverter.js/demo

在demo中,通过输入ffmpeg命令行ffmpeg -i input.webm -vf showinfo -strict 2 output.mp4就可以的到输入视频input.webm的mp4格式输入,如果把时间参数带入比如增加-ss 10 -t 60同样可以将视频从第10s开始裁剪,得到一段60s的输出。它利用web worker执行ffmpeg的js版,将本地的input.webm读入后实现转码/裁剪的体验还是比较流畅的。

然而毕竟是一个6年前的纯js视频方案,并且最终停留在一个demo的状态,对于产品的需求还是有很多不能满足的地方,比如:

  1. 我们业务的直播回放都是hls,videoconverter不能直接支持hls
  2. 转换后的js非常大,gzip前的ffmpeg-all-codec.js大小为26m,gzip后也有6.8m的大小

在6年后的今天,emscripten的版本已经从1.2.1升级到1.38.45,我们也有了新的方案来实现视频操作,不过videoconverter为我们提供了实现的思路。

2. wasm重生

这篇文章不是webassembly和emscripen的(以下简称wasm)的介绍文,关于wasm这里只提及它的几个核心关键词,二进制字节码,体积更小,运行更快,更多的信息可以参考WebAssembly 不完全指北。如今的emscripten已经可以轻松的将c/c++代码转换成asm/wasm,通过emscripten的Module对象可以控制wasm代码的执行,实现数据的交互,函数调用。之后会有专门介绍emscripten Module对象的文章。

整个方案实现流程如下图所示:

参考videoconverter的方案思路,核心步骤是编译出一个浏览器可用的ffmpeg版本,所以第一步就是去官网下载一个ffmpeg。不能使用brew安装ffmpeg,你需要自己去编译安装。

  1. 编译ffmpeg 同本地安装ffmpeg一样,也需要先安装第三方依赖,特别是libx264(ffmpeg的encoder中没有h264),然后设置编译参数。在ffmepg目录下./configure --help可以查看完整的编译配置。通过--cc="emcc"将编译器指定为emcc,将一些不需要的ffmpeg和不支持wasm的模块和特性禁用掉,比如--disable-hwaccels禁用硬解码。完整的配置在最下面的代码仓库中可以查看。

配置好你需要的demuxers/decoders muxers/encoders以及配置链接第三方库,再编译和安装就可能得到你编译的ffmpeg版本。下一步就是通过emcc编译出wasm和胶水js代码。

代码语言:javascript
复制
  emcc \
    -O3 \
    -s WASM=1 \
    -s ASSERTIONS=2 \
    -s VERBOSE=1 \
    -s ALLOW_MEMORY_GROWTH=1 \
    -s TOTAL_MEMORY=33554432 \
    -v ffmpeg.bc libx264.bc libvpx.bc libz.bc \
    -o ../ffmpeg.js --pre-js ../ffmpeg_pre.js --post-js ../ffmpeg_post.js

-O3是编译的优化等级,参数TOTAL_MEMORY和ALLOW_MEMORY_GROWTH设定了wasm需要开辟的内存和执行时内存超过TOTAL_MEMORY时允许自动扩容。

--pre-js和--post-js设置了自定义的js文件,作为最终生成的胶水代码的前缀和后缀,wasm执行前执行在pre.js中的逻辑,来设置一些必要的参数,执行返回等等。这两个文件参考videoconverter的代码,在pre.js中设定了ffmpeg的入口函数ffmpeg_run和数据回调函数。

最终文件的输出会是ffmpeg.wasm和ffmpeg.js, 胶水代码的大小为250k,ffmpeg.wasm的大小为5m,videoconverter的输出js大小为26m,相比之下小了很多,并且ffmpeg.wasm仍然后通过编译配置继续减小的空间。

  1. 使用命令行 在本地的ffmpeg上使用简单的ffmpeg -i input.m3u8 -c copy output.mp4命令就能把hls视频导出一个mp4文件,如果需要第5到第8分钟的视频,用ffmpeg -i input.m3u8 -ss 300 -t 180 -c copy output.mp4就可以实现。

利用emscripten Module对象的arguments就可以设置ffmpeg wasm版本的命令行参数,Module.arguments是一个参数数组,在执行之前需要设置好。

3. 细节实现

  1. hls文件分析 对于回放hls文件来说,首先是加载m3u8文件,m3u8文件是一个指定了一个个视频文件片段文本,通过解析m3u8可以知道每一个片段的播放开始时间,比如一个m3u8文件,去掉一些版本、序号指定后:
代码语言:javascript
复制
#EXTM3U
...
#EXT-X-PROGRAM-DATE-TIME:2019-09-21T20:24:50+08:00
#EXTINF:5,
122070284_485656995_1.ts?start=0&end=781327&type=mpegts
#EXTINF:5,
122070284_485656995_1.ts?start=781328&end=1351343&type=mpegts
#EXTINF:5
...

第一个片段是122070284_485656995_1.ts?start=0&end=781327&type=mpegts,它的时长为6.002,第二个片段122070284_485656995_1.ts?start=781328&end=1351343&type=mpegts,它的时长为4.005。通过每一片段的时长,我们在解析m3u8后可以通过指定的时间段计算出真正需要的裁剪时间片段,以及从这个时间片段算起的时间偏移量,这样不需要加载所有的ts文件就可以裁剪出需要的视频。比如我们需要8-15s的视频,只需要第二和第三个片段,并且起始时间将变成3s。

除此之外,还需要重构原先的m3u8文件,保存先前的文件头后,文件的ts片段由裁剪所需的ts构成,可以重新指定文件名字。

  1. 生成输入文件 重构了m3u8文件后,整个入口函数的调用为:
代码语言:javascript
复制
ffmpeg_run({
 print: console.log,
 printError: console.error,
 files: [
   {
     name: 'playlist.m3u8'
     data: new Uint8Array(buffer)
   },
   {
     name: 'list0.ts'
     data: new Uint8Array(buffer0)
   },
   {
     name: 'list1.ts'
     data: new Uint8Array(buffer1)
   }
   ...
 ],
 arguments: ['-i', 'playlist.m3u8', '-ss', 重新计算出得起始时间, '-t', '180', input.m3u8', '-c', 'copy', 'output.mp4']
});

回放视频已经拆分成一个个视频片段,那么ffmpeg.wasm应该怎么读取到呢?

emscripen提供了一套文件系统FS来实现虚拟文件,上面提到的输入文件m3u8,ts以及输出文件output.mp4可以用它来实现。利用FS的createDataFile和createFolder就可以创建我们需要的虚拟文件系统。

代码语言:javascript
复制
  Module['files'].forEach(function(file) {
    FS.createDataFile('/', file.name, file.data, true, true);
  }

遍历传入的files,createDataFile传入指定的文件名和文件ArrayBufer数据,就可以创建文件,在ffmpeg.wasm解析m3u8时,就可以读取到,m3u8文件和ts文件。

emscripen也提供了Fetch Api,通过XHR可以实现文件的传输,也可以将文件请求步骤交给c/c++去处理,这个方案我没有尝试,有兴趣的同学可以试一下。

4. 一点点优化

mp4格式是由一个一个的box数据块组成,其中moov box包含了视频文件的所有宏观描述信息,如视频尺寸,帧率等信息。当播放视频的时候,需要先读取moov box的信息,来查找视频和音频数据的位置,如果moov box的位置处于视频的尾部,那就需要加载完整个视频才能开始播放。

对于使用视频流的我们来说,这是无法接受的(也有支持seek的方式,让服务器直接seek到视频尾部,不过需要额外的处理)。好在ffmpeg提供了将moov前置的方法,只需要在命令行参数中添加-movflags faststart。用mp4 info查看我们生成的mp4文件,可以看到moov已经放置到视频数据mdat之前。

5. 总结

作为一个长期享受修改即可见的web开发来说,对ffmpeg的编译以及emcc编译这种一等就是半小时的场面还真的没有见过,wasm+ffmpeg的开发调试整体需要更有耐心,不过付出就会有收获,wasm将ffmpeg引入到了web开发领域,相信以后也会看到更多的纯web音视频应用。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 多年前的方案
  • 2. wasm重生
  • 3. 细节实现
  • 4. 一点点优化
  • 5. 总结
相关产品与服务
云直播
云直播(Cloud Streaming Services,CSS)为您提供极速、稳定、专业的云端直播处理服务,根据业务的不同直播场景需求,云直播提供了标准直播、快直播、云导播台三种服务,分别针对大规模实时观看、超低延时直播、便捷云端导播的场景,配合腾讯云视立方·直播 SDK,为您提供一站式的音视频直播解决方案。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档