基于 H5 的直播协议和视频监控方案
一、引言
安防类项目中通常都有视频监控方面的需求。视频监控客户端主要是 Native应 用的形式,在Web 端需要利用 NPAPI、ActiveX 之类的插件技术实现。
但是,IE式微,Chrome 也放弃了 NPAPI,另一方面,监控设备硬件厂商的视频输出格式则逐渐标准化。这让基于开放、标准化接口的 Web 视频监控成为可能。
本文讨论以 HTML5 及其衍生技术为基础的 B/S 架构实时视频监控解决方案。主要包括两方面的内容:
- 视频编码、流媒体基础知识,以及相关的库、框架的介绍
- 介绍可以用于视频监控的HTML5特性,例如媒体标签、MSE、WebRTC,以及相关的库、框架
二、音视频编码
音频、视频的编码(Codec,压缩)算法有很多,不同浏览器对音视频的编码算法的支持有 差异 。H264 这样的监控设备常用的视频编码格式,主流浏览器都有某种程度的支持。
常见的音频编码算法包括: MP3, Vorbis, AAC;常见的视频编码算法包括:H.264, HEVC, VP8, VP9。
编码后的音频、视频通常被封装在一个比特流容器格式(container)中,这些格式中常见的有:MP4, FLV, WebM, ASF, ISMA 等。
2.1 JSMpeg
视频解码工作通常由浏览器本身负责,配合 video 实现视频播放。
现代浏览器的 JS 引擎性能较好,因此出现了纯粹由 JS 实现的解码器 JSMpeg ,它能够解码视频格式 MPEG1、音频格式 MP2。支持通过 Ajax 加载静态视频文件,支持低延迟(小于50ms)的流式播放(通过 WebSocket)。JSMpeg包括以下组件:
- MPEG-TS分流器(demuxer)。muxer 负责把视频、音频、字幕打包成一种容器格式,demuxer 则作相反的工作
- MPEG1 视频解码器
- MP2 音频解码器
- WebGL 渲染器、Canvas 渲染器
- WebAudio 音频输出组件
JSMpeg的优势在于兼容性好,几乎所有现代浏览器都能运行JSMpeg。
2.1.1 性能
JSMpeg不能使用硬件加速。在 iPhone 5S 这样的设备上,JSMpeg 能够处理720p@30fps视频。
比起现代解码器,MPEG1 压缩率较低,因而需要更大的带宽。720p 的视频大概占用 250KB/s的带宽。
2.1.2 示例
下面我们尝试利用ffmpeg编码本地摄像头视频,并通过JSMpeg播放。
创建一个NPM项目,安装依赖:
npm install jsmpeg --save
npm install ws --save
JSMpeg 提供了一个中继器,能够把基于 HTTP 的 MPEG-TS 流转换后通过 WebSocket 发送给客户端。此脚本需要 到 Github 下载 。 下面的命令启动一个中继器:
node ./app/websocket-relay.js 12345 8800 8801
# Listening for incomming MPEG-TS Stream on http://127.0.0.1:8800/<secret>
# Awaiting WebSocket connections on ws://127.0.0.1:8801/
# 实际上在所有网络接口上监听,并非仅仅loopback
下面的命令捕获本地摄像头(Linux),并编码为 MPEG1 格式,然后发送到中继器:
# 从摄像头/dev/video0以480的分辨率捕获原始视频流
# 输出为原始MPEG-1视频(JSMpeg可用),帧率30fps,比特率800kbps
ffmpeg -s 640x480 -f video4linux2 -i /dev/video0 -f mpegts -codec:v mpeg1video -b 800k -r 30 http://127.0.0.1:8800/12345
上述命令执行后,中继器控制台上打印:
Stream Connected: ::ffff:127.0.0.1:42399
客户端代码:
var player = new JSMpeg.Player('ws://127.0.0.1:8801/', {
canvas: document.getElementById('canvas'),
autoplay: true
2.2 Broadway
Broadway 是一个基于JavaScript 的 H.264 解码器,其源码来自于 Android 的 H.264 解码器,利用 Emscripten 转译成了JavaScript,之后利用 Google 的 Closure 编译器优化,并针对WebGL进一步优化。
注意:Broadway 仅仅支持 Baseline 这个 H.264 Profile。
h264-live-player 是基于 Broadway 实现的播放器,允许通过 WebSocket 来传输 NAL 单元(原始H.264帧),并在画布上渲染。我们运行一下它的示例应用:
git clone https://github.com/131/h264-live-player.git
cd h264-live-player
npm install
因为我的机器是 Linux,所以修改 h264-live-player/lib/ffmpeg.js, 把 ffpmeg的 参数改为:
var args = [
"-f", "video4linux2",
"-i", "/dev/video0" ,
"-framerate", this.options.fps,
"-video_size", this.options.width + 'x' + this.options.height,
'-pix_fmt', 'yuv420p',
'-c:v', 'libx264',
'-b:v', '600k',
'-bufsize', '600k',
'-vprofile', 'baseline',
'-tune', 'zerolatency',
'-f' ,'rawvideo',
然后运行 node server-ffmpeg,打开 http:// 127.0.0.1:8080 ,可以看到自己摄像头传来的H.264 码流。
2.3 服务器端技术
2.3.1 ffpmeg
老牌的编解码库,支持很多的音频、视频格式的编解码,支持多种容器格式,支持多种流协议。关于 ffpmeg 的详细介绍参见 Linux命令知识集锦 。
ffpmeg 除了提供开发套件之外,还有一个同名的命令行工具,直接使用它就可以完成很多编解码、流转换的工作。
类似的库是 libav,ffpmeg 和它的功能非常相似,特性更多一些。
2.3.2 x264
官网自称是最好的 H.264 编码器。特性包括:
- 提供一流的性能、压缩比。特别是性能方面,可以在普通PC上并行编码 4 路或者更多的1080P 流
- 提供最好的视频质量,具有最高级的心理视觉优化
- 支持多种不同应用程序所需要的特性,例如电视广播、蓝光低延迟视频应用、Web视频
三、流媒体技术
有了上面介绍的 HTML5 标签、合理编码的视频格式,就可以实现简单的监控录像回放了。但是,要进行实时监控画面预览则没有这么简单,必须依赖流媒体技术实现。
3.1 流媒体
所谓多媒体(Multimedia)是指多种内容形式 —— 文本、音频、视频、图片、动画等的组合。
所谓流媒体,就是指源源不断的由提供者产生,并持续的被终端用户接收、展示的多媒体,就像水流一样。现实世界中的媒体,有些天生就是流式的,例如电视、广播,另外一些则不是,例如书籍、CD。
流媒体技术(从传递媒体角度来看)可以作为文件下载的替代品。
流媒体技术关注的是如何传递媒体,而不是如何编码媒体,具体的实现就是各种流媒体协议。封装后的媒体比特流(容器格式)由流媒体服务器递送到流媒体客户端。流媒体协议可能对底层容器格式、编码格式有要求,也可能没有任何要求。
3.2 直播
直播流(Live streaming)和静态文件播放的关键差异:
- 点播的目标文件通常位于服务器上,具有一定的播放时长、文件大小。浏览器可以使用渐进式下载,一边下载一边播放
- 直播不存在播放起点、终点。它表现为一种流的形式,源源不断的从视频采集源通过服务器,传递到客户端
- 直播流通常是自适应的(adaptive),其码率随着客户端可用带宽的变化,可能变大、变小,以尽可能消除延迟
流媒体技术不但可以用于监控画面预览,也可以改善录像播放的用户体验,比起简单的静态文件回放,流式回放具有以下优势:
- 延迟相对较低,播放能够尽快开始
- 自适应流可以避免卡顿
3.3 流协议
主流的用于承载视频流的流媒体协议包括:
3.3.1 HLS 协议
HTTP 实时流(HTTP Live Streaming),由苹果开发,基于 HTTP 协议
HLS 的工作原理是,把整个流划分成一个个较小的文件,客户端在建立流媒体会话后,基于HTTP 协议下载流片段并播放。客户端可以从多个服务器(源)下载流。
在建立会话时,客户端需要下载 Extended M3U (m3u8) 播放列表文件,其中包含了 MPEG-2 TS(Transport Stream)容器格式的视频的列表。在播放完列表中的文件后,需要再次下载m3u8,如此循环
此协议在移动平台上支持较好,目前的 Android、iOS 版本都支持
此协议的重要缺点是高延迟(5s以上通常),要做到低延迟会导致频繁的缓冲(下载新片段)并对服务器造成压力,不适合视频监控
播放 HLS 流的 HTML 代码片段:
<video src="http://movie.m3u8" height="329" width="480"></video>
3.3.2 RTMP 协议
实时消息协议(Real Time Messaging Protocol),由 Macromedia(Adobe)开发。此协议实时性很好,需要 Flash 插件才能在客户端使用,但是Adobe已经打算在不久的将来放弃对Flash的支持了
有一个开源项目 HTML5 FLV Player ,它支持在没有Flash插件的情况下,播放 Flash 的视频格式 FLV。此项目依赖于 MSE ,支持以下特性:
- 支持 H.264 + AAC/MP3 编码的FLV容器格式的播放
- 分段(segmented)视频播放
- 基于 HTTP 的 FLV 低延迟实时流播放
- 兼容主流浏览器
- 资源占用低,可以使用客户端的硬件加速
3.3.3 RTSP 协议
实时流协议(Real Time Streaming Protocol),由 RealNetworks 等公司开发。此协议负责控制通信端点(Endpoint)之间的媒体会话(media sessions) —— 例如播放、暂停、录制。通常需要结合:实时传输协议(Real-time Transport Protocol)、实时控制协议(Real-time Control Protocol)来实现视频流本身的传递
大部分浏览器没有对 RTSP 提供原生的支持
RTSP 2.0版本目前正在开发中,和旧版本不兼容
3.3.4 MPEG-DASH
基于HTTP的动态自适应流(Dynamic Adaptive Streaming over HTTP),它类似于 HLS,也是把流切分为很小的片段。DASH 为支持为每个片段提供多种码率的版本,以满足不同客户带宽
协议的客户端根据自己的可用带宽,选择尽可能高(避免卡顿、重新缓冲)的码率进行播放,并根据网络状况实时调整码率
DASH 不限制编码方式,你可以使用 H.265, H.264, VP9 等视频编码算法
Chrome 24+、Firefox 32+、Chrome for Android、IE 10+支持此格式
类似于 HLS 的高延迟问题也存在
3.3.5 WebRTC 协议
WebRTC 是一整套 API,为浏览器、移动应用提供实时通信(RealTime Communications)能力。它包含了流媒体协议的功能,但是不是以协议的方式暴露给开发者的
WebRTC 支持 Chrome 23+、Firefox 22+、Chrome for Android,提供 Java / Objective-C 绑定
WebRTC 主要有三个职责:
- 捕获客户端音视频,对应接口 MediaStream(也就是 getUserMedia)
- 音视频传输,对应接口 RTCPeerConnection
- 任意数据传输,对应接口 RTCDataChannel
WebRTC 内置了点对点的支持,也就是说流不一定需要经过服务器中转
3.4 服务器端技术
视频监控通常都是 CS 模式(而非P2P),在服务器端,你需要部署流媒体服务。
3.4.1 GStreamer
这是 一个开源的跨平台多媒体框架。通过它你可以构建各种各样的媒体处理组件,包括流媒体组件。通过插件机制,GStreamer 支持上百种编码格式,包括 MPEG-1, MPEG-2, MPEG-4, H.261, H.263, H.264, RealVideo, MP3, WMV, FLV
Kurento 、 Flumotion 都是基于 GStreamer 构建的流媒体服务器软件。
3.4.2 Live555
Live555 是流媒体服务开发的基础库,支持 RTP/RTCP/RTSP/SIP 等协议,适合在硬件资源受限的情况下使用(例如嵌入式设备)。
基于 Live555 的软件包括:
- Live555媒体服务器,完整的RTSP服务器
- openRTSP,一个命令行程序,支持提供RTSP流、接收RTSP流、把RTSP流中的媒体录像到磁盘
- playSIP,可以进行V oIP 通话
- liveCaster,支持组播的 MP3 流媒体服务
3.4.3 其它
流媒体服务实现有很多,它们中的一些在最初针对特定的流协议,大部分都走向多元化。例如,Red5是一个RTMP流媒体服务器,Wowza是一个综合的流媒体服务器,支持WebRTC的流媒体服务在后面的章节介绍。
四、HTML5媒体标签
HTML5支持 <audio>和 <video>标签(两者都对应了HTMLMediaElement的子类型)以实现视频、音频的播放。
4.1 <audio>
此标签用于在浏览器中创建一个纯音频播放器。播放静态文件的示例:
<audio controls preload="auto">
<source src="song.mp3" type="audio/mpeg">
<!-- 备选格式,如果浏览器不支持mp3 -->
<source src="song.ogg" type="audio/ogg">
<!-- 如果浏览器不支持audio标签,显示下面的连接 -->
<a href="audiofile.mp3">download audio</a>
</audio>
4.2 <video>
此标签用于在浏览器中创建一个视频播放器。播放静态文件的示例:
<!-- poster指定预览图,autoplay自动播放,muted静音 -->
<video controls width="640" height="480" poster="movie.png" autoplay muted>
<source src="movie.mp4" type="video/mp4">
<!-- 备选格式,如果浏览器不支持mp4 -->
<source src="movie.webm" type="video/webm">
<!-- 可以附带字幕 -->
<track src="subtitles_en.vtt" kind="subtitles" srclang="en" label="English">
<!-- 如果浏览器不支持video标签,显示下面的连接 -->
<a href="videofile.mp4">download video</a>
</video>
<canvas>
在画布中,你可以进行任意的图形绘制,当然可以去逐帧渲染视频内容。
编程方式创建
音频、视频播放器标签也可以利用JavaScript编程式的创建,示例代码:
var video = document.createElement('video');
if (video.canPlayType('video/mp4')) {
video.setAttribute('src', 'movie.mp4' );
} else if (video.canPlayType('video/webm')) {
video.setAttribute('src', 'movie.webm');
video.width = 640;
video.height = 480;
五、MSE
媒体源扩展(Media Source Extensions,MSE)是一个 W3C 草案, 桌面浏览器对MSE的支持较好 。MSE扩展流 video/audio 元素的能力,允许你通过 JavaScript 来生成(例如从服务器抓取)媒体流供 video/audio 元素播放。使用MSE你可以:
- 通过JavaScript 来构建媒体流,不管媒体是如何捕获的
- 处理自适应码流、广告插入、时间平移(time-shifting,回看)、视频编辑等应用场景
- 最小化 JavaScript 中处理媒体解析的代码
MSE 定义支持的(你生成的) 媒体格式 ,只有符合要求的容器格式、编码格式才能被 MSE 处理。通常容器格式是 ISO BMFF(MP4),也就是说你需要生成 MP4 的片断,然后 Feed 给MSE 进行播放。
MediaSource 对象作为 video/audio 元素的媒体来源,它可以具有多个 SourceBuffer 对象。应用程序把数据片段(segment)附加到 SourceBuffer 中,并可以根据系统性能对数据片段的质量进行适配。SourceBuffer 中包含多个 track buffer —— 分别对应音频、视频、文本等可播放数据。这些数据被音频、视频解码器解码,然后在屏幕上显示、在扬声器中播放:
要把 MediaSource 提供给 video/audio 播放,调用:
video.src = URL.createObjectURL(mediaSource);
5.1 基于 MSE 的框架
5.1.1 wfs
wfs 是一个播放原始 H.264 帧的 HTML5 播放器,它的工作方式是把 H.264 NAL 单元封装为 ISO BMFF(MP4)片,然后Feed给 MSE 处理。
5.1.2 flv.js
flv.js 是一个 HTML5 Flash 视频播放器,基于纯 JS,不需要 Flash 插件的支持。此播放器将FLV 流转换为 ISO BMFF(MP4)片断,然后把 MP4 片断提供给 video 元素使用。
flv.js 支持 Chrome 43+, FireFox 42+, Edge 15.15048+ 以上版本的直播流 。
5.1.3 Streamedian
Streamedian 是一个 HTML5 的 RTSP 播放器。实现了 RTSP 客户端功能,你可以利用此框架直接播放 RTSP 直播流。此播放器把RTP协议下的 H264/AAC 在转换为 ISO BMFF 供 video 元素使用。Streamedian 支持 Chrome 23+, FireFox 42+, Edge 13+,以及 Android 5.0+。不支持 iOS 和 IE。
在服务器端,你需要安装 Streamedian 提供的代理(此代理收费),此代理将 RTSP转换为WebSocket。Streamedian 处理视频流的流程如下:
六、WebRTC
WebRTC 是一整套 API,其中一部分供 Web 开发者使用,另外一部分属于要求浏览器厂商实现的接口规范。WebRTC 解决诸如客户端流媒体发送、点对点通信、视频编码等问题。 桌面浏览器对WebRTC的支持较好 ,WebRTC 也很容易和 Native 应用集成。
使用 MSE 时,你需要自己构建视频流。使用 WebRTC 时则可以直接捕获客户端视频流。
使用 WebRTC 时,大部分情况下流量不需要依赖于服务器中转,服务器的作用主要是:
- 在信号处理时,转发客户端的数据
- 配合实现 NAT/防火墙 穿透
- 在点对点通信失败时,作为中继器使用
6.1 架构图
6.2 流捕获
6.2.1 捕获视频
主要是捕获客户端摄像头、麦克风。在视频监控领域用处不大,这里大概了解一下。流捕获通过 navigator.getUserMedia 调用实现:
<script type="text/javascript">
navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.getUserMedia;
var success = function (stream) {
var video = document.getElementById('camrea');
// 把MediaStream对象转换为Blob URL,提供给video播放
video.src = URL.createObjectURL( stream );
video.play();
var error = function ( err ) {
console.log( err )
// 调用成功后,得到MediaStream对象
navigator.getUserMedia( { video: true, audio: true }, success, error );
</script>
<video id="camrea" width="640" height="480"/>
三个调用参数分别是:
- 约束条件 ,你可以指定媒体类型、分辨率、帧率
- 成功后的回调,你可以在回调中解析出 URL 提供给 video 元素播放
- 失败后的回调
6.2.2 捕获音频
捕获音频类似:
navigator.getUserMedia( { audio: true }, function ( stream ) {
var audioContext = new AudioContext();
// 从捕获的音频流创建一个媒体源管理
var streamSource = audioContext.createMediaStreamSource( stream );
// 把媒体源连接到目标(默认是扬声器)
streamSource.connect( audioContext.destination );
}, error );
6.2.3 MediaStream
MediaStream对象提供以下方法:
- getAudioTracks(),音轨列表
- getVideoTracks(),视轨列表
每个音轨、视轨都有个label属性,对应其设备名称。
6.2.4 Camera.js
Camera.js 是对 getUserMedia 的简单封装,简化了API 并提供了跨浏览器支持:
camera.init( {
width: 640,
height: 480,
fps: 30, // 帧率
mirror: false, // 是否显示为镜像
targetCanvas: document.getElementById( 'webcam' ), // 默认null,如果设置了则在画布中渲染
onFrame: function ( canvas ) {
// 每当新的帧被捕获,调用此回调
onSuccess: function () {
// 流成功获取后
onError: function ( error ) {
// 如果初始化失败
onNotSupported: function () {
// 当浏览器不支持camera.js时
} );
// 暂停
camera.pause();
// 恢复
camera.start();
掠食者视觉 是基于 Camera 实现的一个好玩的例子(移动侦测)。
6.3 信号处理
在端点之间(Peer)发送流之前,需要进行通信协调、发送控制消息,即所谓信号处理(Signaling),信号处理牵涉到三类信息:
- 会话控制信息:初始化、关闭通信,报告错误
- 网络配置:对于其它端点来说,本机的 IP 和 port 是什么
- 媒体特性:本机能够处理什么音视频编码、多高的分辨率。本机发送什么样的音视频编码
WebRTC 没有对信号处理规定太多,我们可以通过 Ajax/WebSocket 通信,以 SIP、Jingle、ISUP 等协议完成信号处理。点对点连接设立后,流的传输并不需要服务器介入。信号处理的示意图如下:
6.3.1 示例代码
下面的代表片段包含了一个视频电话的信号处理过程:
// 信号处理通道,底层传输方式和协议自定义
var signalingChannel = createSignalingChannel();
var conn;
// 信号通过此回调送达本地,可能分多次送达
signalingChannel.onmessage = function ( evt ) {
if ( !conn ) start( false );
var signal = JSON.parse( evt.data );
// 会话描述协议(Session Description Protocol),用于交换媒体配置信息(分辨率、编解码能力)
if ( signal.sdp )
// 设置Peer的RTCSessionDescription
conn.setRemoteDescription( new RTCSessionDescription( signal.sdp ) );
// 添加Peer的Candidate信息
conn.addIceCandidate( new RTCIceCandidate( signal.candidate ) );
// 调用此方法启动WebRTC,获取本地流并显示,侦听连接上的事件并处理
function start( isCaller ) {
conn = new RTCPeerConnection( { /**/ } );
// 把地址/端口信息发送给其它Peer。所谓Candidate就是基于ICE框架获得的本机可用地址/端口
conn.onicecandidate = function ( evt ) {
signalingChannel.send( JSON.stringify( { "candidate": evt.candidate } ) );
// 当远程流到达后,在remoteView元素中显示
conn.onaddstream = function ( evt ) {
remoteView.src = URL.createObjectURL( evt.stream );
// 获得本地流
navigator.getUserMedia( { "audio": true, "video": true }, function ( stream ) {
// 在remoteView元素中显示
localView.src = URL.createObjectURL( stream );
// 添加本地流,Peer将接收到onaddstream事件
conn.addStream( stream );
if ( isCaller )
// 获得本地的RTCSessionDescription
conn.createOffer( gotDescription );
// 针对Peer的RTCSessionDescription生成兼容的本地SDP
conn.createAnswer( conn.remoteDescription, gotDescription );
function gotDescription( desc ) {
// 设置自己的RTCSessionDescription
conn.setLocalDescription( desc );
// 把自己的RTCSessionDescription发送给Peer
signalingChannel.send( JSON.stringify( { "sdp": desc } ) );
} );
// 通信发起方调用:
start( true );
6.4 流转发
主要牵涉到的接口是RTCPeerConnection,上面的例子中已经包含了此接口的用法。WebRTC在底层做很多复杂的工作,这些工作对于JavaScript来说是透明的:
- 执行解码
- 屏蔽丢包的影响
-
点对点通信:WebRTC 引入流交互式连接建立(Interactive Connectivity Establishment,ICE)框架。ICE 负责建立点对点链路的建立:
1). 首先尝试直接
2). 不行的话尝试 STUN(Session Traversal Utilities for NAT)协议。此协议通过一个简单的保活机制确保NAT端口映射在会话期间有效
3). 仍然不行尝试 TURN(Traversal Using Relays around NAT)协议。此协议依赖于部署在公网上的中继服务器。只要端点可以访问TURN服务器就可以建立连接 - 通信安全
- 带宽适配
- 噪声抑制
- 动态抖动缓冲(Dynamic jitter buffering),抖动是由于网络状况的变化,缓冲用于收集、存储数据,定期发送
6.5 任意数据交换
通过 RTCDataChannel 完成,允许点对点之间任意的数据交换。RTCPeerConnection 连接创建后,不但可以传输音视频流,还可以打开多个信道(RTCDataChannel)进行任意数据的交换。RTCDataChanel 的特点是:
- 类似于 WebSocket 的API
- 支持带优先级的多通道
- 超低延迟,因为不需要通过服务器中转
- 支持可靠/不可靠传输语义。支持 SCTP、DTLS、UDP 几种传输协议
- 内置安全传输(DTLS)
- 内置拥塞控制
使用 RTCDataChannel 可以很好的支持游戏、远程桌面、实时文本聊天、文件传输、去中心化网络等业务场景。
6.6 adapter.js
WebRTC adapter 是一个垫片库,使用它开发 WebRTC 应用时,不需要考虑不同浏览器厂商的 API前缀差异 。
6.7 WebRTC示例
本节列出一些 WebRTC 的代码示例,这些例子都使用 adapter.js。
6.7.1 限定分辨率
// 指定分辨率
// adapter.js 支持Promise
navigator.mediaDevices.getUserMedia( { video: { width: { exact: 640 }, height: { exact: 480 } } } ).then( stream => {
let video = document.createElement( 'video' );
document.body.appendChild( video );