IVWEB玩轉wasm系列-純web視頻剪輯/轉換工具

咱們的業務是十分養眼的NOW直播,每一場直播結束後,咱們都會保存一段時間的直播回放,每一場直播回放都充滿了很多的精彩片斷,然而要從二、3小時的直播回放中準確找出這些精彩片斷卻不是那麼容易的事情。因而,故事要從一次需求宣講提及,咱們的產品但願能在回放中剪輯出主播的高光時刻,做爲前端的咱們原本是聽聽就好,畢竟長期以來視頻裁剪工做都是在後臺完成,然而這一次,做爲IVWEB的前端,咱們決定拿起wasm去試一試。javascript

1. 多年前的方案

在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++

  1. 咱們業務的直播回放都是hls,videoconverter不能直接支持hlsgit

  2. 轉換後的js很是大,gzip前的ffmpeg-all-codec.js大小爲26m,gzip後也有6.8m的大小github

在6年後的今天,emscripten的版本已經從1.2.1升級到1.38.45,咱們也有了新的方案來實現視頻操做,不過videoconverter爲咱們提供了實現的思路。web

2. wasm重生

這篇文章不是webassembly和emscripen的(如下簡稱wasm)的介紹文,關於wasm這裏只說起它的幾個核心關鍵詞,二進制字節碼,體積更小,運行更快,更多的信息能夠參考WebAssembly 不徹底指北。現在的emscripten已經能夠輕鬆的將c/c++代碼轉換成asm/wasm,經過emscripten的Module對象能夠控制wasm代碼的執行,實現數據的交互,函數調用。以後會有專門介紹emscripten Module對象的文章。typescript

整個方案實現流程以下圖所示:shell

參考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代碼。

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文件,去掉一些版本、序號指定後:
#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文件後,整個入口函數的調用爲:
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++去處理,這個方案我沒有嘗試,有興趣的同窗能夠試一下。

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音視頻應用。

相關文章
相關標籤/搜索