H5直播方案总结
背景
最近因为工作原因对H5直播方案进行了调研,也算是有所收获,特将此记录下来,如有不对还请指正。此文写于2020年3月,开发与测试环境如下:
平台 | 版本 |
---|---|
MacBook | macOS 10.15.1 |
iPhone | iOS13.3.1 |
Android | 9.0 |
概述
一个直播系统由前端与后端两部分组成,此文仅探讨前端部分。前端根据视频产生的过程又可以分为视频采集、视频发送与视频播放。对于PC端的浏览器基本上都支持采集、发送和播放,但是对于移动端的浏览器就比较尴尬了,例如iOS的Safari就不支持视频采集(也许是我的方法不对)。
视频采集
网页实现视频采集主要是通过[MediaDevices.getUserMedia()](https://developer.mozilla.org/zh-
CN/docs/Web/API/MediaDevices/getUserMedia)方法,不同的浏览器对此的支持程度不同,经测试,IE和iOS的Safari不支持此方法。
采集配置
我们可以手动的选择需要采集的数据源,例如是否需要音频/视频,或者视频的大小,甚至是视频的帧率。
navigator.mediaDevices.getUserMedia({ audio: true, video: { width: 1280, height: 720, frameRate: { ideal: 20, max: 25 } } })
获取采集的数据
getUserMedia()方法返回一个表示[MediaStream](https://developer.mozilla.org/en-
US/docs/Web/API/MediaStream)的[Promise](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/Promise)对象,使用[URL.createObjectURL()](https://developer.mozilla.org/en-
US/docs/Web/API/URL/createObjectURL)可以将其转化为网页标签可以识别的地址,例如将摄像头数据直接显示到video标签上面。
var video = document.getElementById(video);
navigator.mediaDevices.getUserMedia({ audio: true, video: { width: 1280, height: 720, frameRate: { ideal: 20, max: 25 } } })
.then(function (stream) {
// Older browsers may not have srcObject
if (srcObject in video) {
video.srcObject = stream;
} else {
// Avoid using this in new browsers, as it is going away.
video.src = window.URL.createObjectURL(stream);
}
video.onloadedmetadata = function (e) {
video.play();
};
})
.catch(function (err) {
console.log(err.name + : + err.message);
});
视频发送
获取了视频数据之后,我们还需要将视频发送给服务器或者其他客户端,这里有两个方案分别是[WebRTC](https://developer.mozilla.org/zh-
CN/docs/Glossary/WebRTC)和[WebSockets](https://developer.mozilla.org/zh-
CN/docs/Web/API/WebSockets_API),而WebSocket根据发送的内容又可以分为图片和视频两种。对于这几种方案主要优缺点如下表所示。
方案 | 优点 | 缺点 |
---|---|---|
WebRTC | 延迟低,占用带宽小 | 观众过多时不支持 |
WebSockets发送图片 | 方便帧进行单独分析处理,延迟较低 | 占用带宽较大 |
WebSockets发送视频 | 支持视频和音频同时传输,占用带宽少 | 切换视频片段时容易卡顿 |
WebRTC
(Web Real Time
Communication)WebRTC即网页实时通信协议,可实现浏览器对浏览器的点对点通信,适合用来做视频/语音通话。因为是点对点通信,所以当观众数量太多时就会很复杂。目前网上一些开源的流媒体服务器支持WebRTC协议,可以用来对视频进行处理和转发,本文暂时没有对此进行研究😓。
WebSockets
WebSockets是一种先进的技术。它可以在用户的浏览器和服务器之间打开交互式通信会话。使用此API,您可以向服务器发送消息并接收事件驱动的响应,而无需通过轮询服务器的方式以获得响应。
WebSockets发送图片
将视频绘制在Canvas上面,然后按照固定的帧率捕获Canvas画面导出为Jpeg,并发送给服务器。这种方案实现简单,但是音频需要单独发送,音视频同步也比较麻烦。
var video = document.getElementById(video);
var canvas = document.getElementById(canvas);
var ctx = canvas.getContext(2d);
setInterval(function () {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
var img = canvas.toDataURL(image/jpeg, 1);
socket.send(img);
}, 1000 / 20);
WebSockets发送视频
将Canvas的图像和麦克风的音频组合录制成视频,然后发送给服务器,录制结束之后立马开始录制新的一个视频。为了减少延迟可以将视频的时长设置短一点,例如2秒。
var video = document.getElementById(video);
var canvas = document.getElementById(canvas);
var ctx = canvas.getContext(2d);
const mediaRecorder = new mediaRecorder(canvas.captureStream(), {
mimeType: video/webm;codecs=vp9,
audioBitsPerSecond: 128000,
videoBitsPerSecond: 1280 * 720
});
const reader = new FileReader();
mediaRecorder.ondataavilable = function (e) {
reader.addEventListener(loadend, function () {
socket.send(reader.result);
});
reader.readAsArrayBuffer(e.data);
}
setInterval(function () {
mediaRecorder.stop();
mediaRecorder.start();
}, 2000);
视频播放
视频播放是H5直播方案里面的重点,实现视频播放,主要有这几种方案,分别是RTMP、HLS和JSmpeg/Broadway。
RTMP
RTMP(Real Time Messaging
Protocol)即实时消息传输协议,由Adobe公司提出,需要依赖浏览器的Flash插件进行播放,可使用开源的video.js直接播放RTMP流。在如今所有的浏览器都抛弃Flash的基础上,此方案兼容性有很大的问题,但是现在主流的视频直播网站似乎都是这个方案。
HLS
HLS(Http Live
Streaming)是Apple的动态码率自适应技术,包括一个m3u(8)的索引文件,TS媒体分片文件和key加密串文件。其原理就是将一个视频切分为多个单独的小视频,将小视频的地址记录到一个文件里面(m3u8)发送给浏览器,浏览器按顺序播放每一个视频。这种方案在各大视频网站用的很多,因为可以很方便的插入广告。使用浏览器播放HLS视频十分简单,只需要将m3u8的地址放在video标签即可,如下:
<video controls preload=auto autoplay=autoplay loop=loop webkit-playsinline>
<source src=http://localhost:8089/hls/test.m3u8 type=application/vnd.apple.mpegurl />
<p class=warning>Your browser does not support HTML5 video.</p>
</video>
JSMpeg/Broadway
JSMpeg是用JS编写的Mpeg1/MP2解码器,而Broadway是用JS编写的H264解码器。之所以将它们视为同一个方案是因为它们都是基于JS解码视频流用Canvas显示,不过JSMpeg支持视频和音频同时解码,而Broadway仅支持视频,所以实际使用JSMpeg会方便些。
FFmpeg参数设置
JSmpeg的Github主页里面有一个服务端推流的demo,利用FFmpeg将视频转码并发送到中转服务器,然后再通过WebSockets发送给网页。这里面对于FFmpeg的参数需要注意一下,以免推流之后的视频无法播放,我使用的参数如下:
ffmpeg -re -i input.mp4 -f mpegts -codec:v mpeg1video -codec:a mp2 -s 640x480 -r 25 http://localhost:8081/<secret>
具体参数的意思见下表:
参数 | 说明 |
---|---|
-re | 非常重要 必须放在-i前面,表示按照视频的实际帧率发送数据 |
-i | 输入视频源 |
-f | 视频容器格式 |
-codec:v | 视频编码格式 |
-codec:a | 音频编码格式 |
-s | 视频大小 |
-r | 视频帧率 |