咱们的业务是十分养眼的NOW直播,每一场直播结束后,咱们都会保存一段时间的直播回放,每一场直播回放都充满了很多的精彩片断,然而要从二、3小时的直播回放中准确找出这些精彩片断却不是那么容易的事情。因而,故事要从一次需求宣讲提及,咱们的产品但愿能在回放中剪辑出主播的高光时刻,做为前端的咱们原本是听听就好,毕竟长期以来视频裁剪工做都是在后台完成,然而这一次,做为IVWEB的前端,咱们决定拿起wasm去试一试。javascript
在2013年(今年是2020年)的Node Knockout比赛上,有人提出了一个叫 Video Funhouse(年代过久远,我没能找到更多的资料)的设想,后来就有了github上的videoconverter方案。videoconverter将音视频领域中的瑞士军刀ffmpeg经过emscripten(一个能够将C/C++代码生成asm/wasm的编译工具)转化为javascript,实现了在浏览器上对视频的简单操做,包括视频的裁剪/转换。它的demo目前还能运行,地址以下:bgrins.github.io/videoconver…前端
在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读入后实现转码/裁剪的体验仍是比较流畅的。java
然而毕竟是一个6年前的纯js视频方案,而且最终停留在一个demo的状态,对于产品的需求仍是有不少不能知足的地方,好比:c++
咱们业务的直播回放都是hls,videoconverter不能直接支持hlsgit
转换后的js很是大,gzip前的ffmpeg-all-codec.js大小为26m,gzip后也有6.8m的大小github
在6年后的今天,emscripten的版本已经从1.2.1升级到1.38.45,咱们也有了新的方案来实现视频操做,不过videoconverter为咱们提供了实现的思路。web
这篇文章不是webassembly和emscripen的(如下简称wasm)的介绍文,关于wasm这里只说起它的几个核心关键词,二进制字节码,体积更小,运行更快,更多的信息能够参考WebAssembly 不彻底指北。现在的emscripten已经能够轻松的将c/c++代码转换成asm/wasm,经过emscripten的Module对象能够控制wasm代码的执行,实现数据的交互,函数调用。以后会有专门介绍emscripten Module对象的文章。typescript
整个方案实现流程以下图所示:shell
参考videoconverter的方案思路,核心步骤是编译出一个浏览器可用的ffmpeg版本,因此第一步就是去官网下载一个ffmpeg。不能使用brew安装ffmpeg,你须要本身去编译安装。数组
./configure --help
能够查看完整的编译配置。经过 --cc="emcc"
将编译器指定为emcc,将一些不须要的ffmpeg和不支持wasm的模块和特性禁用掉,好比--disable-hwaccels
禁用硬解码。完整的配置在最下面的代码仓库中能够查看。配置好你须要的demuxers/decoders muxers/encoders以及配置连接第三方库,再编译和安装就可能获得你编译的ffmpeg版本。下一步就是经过emcc编译出wasm和胶水js代码。
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仍而后经过编译配置继续减少的空间。
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是一个参数数组,在执行以前须要设置好。
#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构成,能够从新指定文件名字。
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就能够建立咱们须要的虚拟文件系统。
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++去处理,这个方案我没有尝试,有兴趣的同窗能够试一下。
mp4格式是由一个一个的box数据块组成,其中moov box包含了视频文件的全部宏观描述信息,如视频尺寸,帧率等信息。当播放视频的时候,须要先读取moov box的信息,来查找视频和音频数据的位置,若是moov box的位置处于视频的尾部,那就须要加载完整个视频才能开始播放。
对于使用视频流的咱们来讲,这是没法接受的(也有支持seek的方式,让服务器直接seek到视频尾部,不过须要额外的处理)。好在ffmpeg提供了将moov前置的方法,只须要在命令行参数中添加-movflags faststart
。用mp4 info查看咱们生成的mp4文件,能够看到moov已经放置到视频数据mdat以前。
做为一个长期享受修改便可见的web开发来讲,对ffmpeg的编译以及emcc编译这种一等就是半小时的场面还真的没有见过,wasm+ffmpeg的开发调试总体须要更有耐心,不过付出就会有收获,wasm将ffmpeg引入到了web开发领域,相信之后也会看到更多的纯web音视频应用。