HTML5 視頻直播(二)
原文 http://www.imququ.com/post/html5-live-player-2.html
上篇博客中,我介紹了目前移動端唯一可行的 HTML5 直播方案:HLS。實際上,HLS 除了上回提到過的延遲很大這個缺點之外,在 iOS 的 Safari 瀏覽器中還只能全屏播放,也無法做到自動播放,這個是 iOS 系統對 Video 標簽統一做的限制。有沒有什么辦法解決這些問題呢?
我們換個思路,既然原生 Video 有這樣那樣的問題,不如直接拋棄它。利用 Web Sockets 實現視頻流的實時傳輸,使用純 JS 進行視頻解碼,再用 Canvas 逐幀畫出圖像,這不就實現了直播么。當我有個這個想法之后,初步覺得可行,立馬開始一番搜索,收獲頗豐。
本文要用到的 Web Sockets 在移動端支持度如下表:
瀏覽器 | 支持新標準的版本 |
---|---|
iOS Safari | 6.1+ |
Android Browser(Webview) | 4.4+ |
Chrome for Android | 42+ |
(數據來源: caniuse )
另外,轉換視頻格式、生成視頻流還需要用到一個神器: FFmepg 。
Mac 下最簡單的做法是通過 Homebrew 安裝,直接brew install ffmpeg就可以了。Ubuntu 下可以先添加這個源:
sudo add-apt-repository ppa:mc3man/trusty-media
再sudo apt-get install ffmpeg,也能輕松搞定。
Decoder in JavaScript
純 JS 實現的視頻解碼器我找到了兩個可用的: Broadway 和 jsmpeg 。
Broadway是一個 H.264 解碼器,使用 Emscripten 工具從 Android 的 H.264 解碼器轉化而成,它還針對 WebGL 做了一些優化。
這個解碼器支持 mp4 后綴的視頻文件,有一些限制:不支持 weighted prediction for P-frames 和 CABAC entropy encoding。例如 iPhone 拍攝的視頻它就不支持,可以用 FFmpeg 轉一下:
ffmpeg -i in.mp4 -vcodec libx264 -pass 1 -coder 0 -bf 0 -flags -loop -wpredp 0 out.mp4
下面是 H.264 解碼示例,視頻來自于我的 iPhone 拍攝。用閱讀器的同學請點到原文查看。
點擊播放
這里還有一個長一點的 Demo,點擊查看(加載完 6M 多的 mp4 文件才開始播放,請耐心等待,流量黨慎入)。
jsmpeg則是一個 MPEG1 解碼器,它是由作者從頭編寫出來的,并不像 Broadway 那樣是從其他語言翻譯而成,所以代碼可讀性要好很多,代碼也更輕量級。
jsmpeg 也對視頻文件編碼方式有一些要求:不支持 B-Frames,視頻寬度必須是 2 的倍數。還是可以用 FFmpeg 來轉換:
ffmpeg -i in.mp4 -f mpeg1video -vf "crop=iw-mod(iw\,2):ih-mod(ih\,2)" -b 0 out.mpg
下面是 MPEG1 解碼示例,視頻來自于網上。用閱讀器的同學請點到原文查看。
點擊播放
這里也有一個長一點的 Demo,點擊查看(加載完 3M 多的 mpg 文件才開始播放。其實沒什么好看的,內容跟上面一樣,編碼格式不同而已)。
Live Streaming
看到這里,大家肯定會說,這不是要一次性下完全部內容么,怎能稱之為直播。是的,要實現直播,還要用 Web Sockets 實現一個實時傳輸流的服務。FFmpeg 支持很多直播流格式,但不支持 Web Sockets。解決方案是用 FFmpeg 開一個 HTTP 直播流,再開個 Node 服務轉一下。
詳細一點的過程是這樣的,用 NodeJS 監聽 FFmpeg 的 HTTP 直播地址,把收到的數據 通過 Web Sockets 廣播給所有客戶端。核心代碼就是下面這幾行:
//HTTP Server to accept incomming MPEG Stream var streamServer = require('http').createServer( function(request, response) { request.on('data', function(data){ socketServer.broadcast(data, {binary:true}); }); }).listen(STREAM_PORT);
這段代碼來自于上面介紹的 jsmpeg 項目,完整代碼在 這里 。啟動這個服務試試(需先裝好 ws 模塊):
node ~/live/stream-server.js ququ 9091 9092
三個參數分別是加密串、HTTP 端口、WS 端口。啟動后,屏幕上會顯示兩個地址:
Listening for MPEG Stream on http://127.0.0.1:9091/<secret>/<width>/<height> Awaiting WebSocket connections on ws://127.0.0.1:9092/
好了,現在就可以使用 FFmpeg 來推送 HTTP 視頻流了:
ffmpeg -re -i fox.mpg -codec copy -f mpeg1video http://qgy18.imququ.com:9091/ququ/640/360
-re參數表示以視頻實際播放速率解碼,不加這個參數一般會導致直播速率變得飛快。ququ是啟動 Node 服務時指定的加密串,這樣做個簡單校驗,避免 Node 轉發不認識的流。最后的640和360是視頻的寬高,可以根據實際情況指定。
最后,稍微改一下前面的 Demo,讓 jsmpeg 從 WS 流中獲取數據就可以實現直播了:
var canvas = document.getElementById('videoCanvas'); var client = new WebSocket('ws://qgy18.imququ.com:9092/'); var player = new jsmpeg(client, {canvas:canvas, autoplay: true});
(完整示例地址)
Capture Webcam
上面演示的是從文件中獲取視頻流進行直播,如果把數據源換成攝像頭也很容易。FFmpeg 官方 wiki 上有在 Windows / MacOS / Linux 下讀取攝像頭的詳細指南。
以 Mac 為例,它支持 AVFoundation 和 QTKit 兩種不同的技術讀取攝像頭,在比較新的系統(10.7+)上,推薦使用 AVFoundation,QTKit 后續可能會被廢棄。
我的 Mac 系統是最新的,直接使用 AVFoundation。首先查看可用攝像頭列表:
ffmpeg -f avfoundation -list_devices true -i "" [AVFoundation input device @ 0x7fdd53c228c0] AVFoundation video devices: [AVFoundation input device @ 0x7fdd53c228c0] [0] FaceTime HD Camera [AVFoundation input device @ 0x7fdd53c228c0] [1] Capture screen 0 [AVFoundation input device @ 0x7fdd53c228c0] AVFoundation audio devices: [AVFoundation input device @ 0x7fdd53c228c0] [0] Built-in Microphone
可以看到,編號為 0 的 video 設備正是我想要的攝像頭(編號為 1 的設備是桌面,直播桌面也挺好玩),下面這行命令就可以捕捉它,并把視頻流推到之前的 Node 服務上:
ffmpeg -f avfoundation -i "0" -f mpeg1video -b 0 -r 30 -vf scale=640:360 http://qgy18.imququ.com:9091/ququ/640/360
這樣,我攝像頭拍攝到的畫面,就被 FFMpeg 捕捉下來實時推給遠端 Node 服務,實時轉化成 WS 數據流,廣播給所有終端播放。雖然過程很曲折,但是基本上看不到延遲,體驗還是很不錯的。
最后,說幾個問題:
- 首先,最大的問題是:這種方案只實現了 Canvas 渲染畫面部分,無法支持聲音(沒聲音還好意思叫直播,摔!!!);
- 其次,JS 解碼能力還是稍微有點弱,最后的示例中我有意指定 scale 參數縮小了畫面,但在 iPhone 6P 非自帶瀏覽器中還會卡(iOS 第三方 APP 趕緊放棄老系統,果斷的把好內核用起來吧~~~);
- 第三,好像略微有點蛋疼(好,這次就寫這么多,收工。。。);