H5 的直播协议和视频监控方案
一、流媒体主要实现方式
对上图的说明如下:
- 在设备层,需要以某种方式获得码流,以流协议的方式发送出去。最常用的方式是RTSP/RTP。流的可能获取路径为:
1). 设备直接暴露 RTSP 协议端点,并且发送标准码流
2). 设备 SDK 允许获取标准码流,需要自己以 RTSP 协议发送
3). 设备 SDK 允许获得解码后的逐帧,需要直接编码为 H264,然后以 RTSP 发送 - 流媒体层通常需要引入专门的流媒体服务器,这类服务器能够在内部进行各种流协议的转换,可以解除客户端对特定流协议的依赖
- 客户端和服务器端的传输方式,可以有 TCP、HTTP、P2P(WebRTC)、WebSocket 等多种。其中
1). 直接的 TCP 协议浏览器是不支持的,这意味着 RTSP/RTMP 等协议,在浏览器端必须要有插件才可以使用
2). WebSocket 通常配合 JSMpeg 或者 MSE 使用,由程序向 JSMpeg/MSE 不断 Feed 视频帧 - 客户端解码展示的技术主要有三类:
1). 浏览器内置的解码能力,主要通过 video 标签,MSE 属于此类
2). JavaScript 软解码,主要是 JSMpeg、Broadway
3). 插件机制,例如 Chrome 的 NaCl
二、流媒体技术
2.1 流媒体
所谓多媒体(Multimedia)是指多种内容形式 —— 文本、音频、视频、图片、动画等的组合。
所谓流媒体,就是指源源不断的由提供者产生,并持续的被终端用户接收、展示的多媒体,就像水流一样。现实世界中的媒体,有些天生就是流式的,例如电视、广播,另外一些则不是,例如书籍、CD。
流媒体技术(从传递媒体角度来看)可以作为文件下载的替代品。
流媒体技术关注的是如何传递媒体,而不是如何编码媒体,具体的实现就是各种流媒体协议。封装后的媒体比特流(容器格式)由流媒体服务器递送到流媒体客户端。流媒体协议可能对底层容器格式、编码格式有要求,也可能没有任何要求。
2.2 直播
直播流(Live streaming)和静态文件播放的关键差异:
- 点播的目标文件通常位于服务器上,具有一定的播放时长、文件大小。浏览器可以使用渐进式下载,一边下载一边播放
- 直播不存在播放起点、终点。它表现为一种流的形式,源源不断的从视频采集源通过服务器,传递到客户端
- 直播流通常是自适应的(adaptive),其码率随着客户端可用带宽的变化,可能变大、变小,以尽可能消除延迟
流媒体技术不但可以用于监控画面预览,也可以改善录像播放的用户体验,比起简单的静态文件回放,流式回放具有以下优势:
- 延迟相对较低,播放能够尽快开始
- 自适应流可以避免卡顿
2.3 流协议
主流的用于承载视频流的流媒体协议包括:
2.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>
2.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 低延迟实时流播放
- 兼容主流浏览器
- 资源占用低,可以使用客户端的硬件加速
2.3.3 RTSP 协议
RTSP是一个实时传输流协议,是一个应用层的协议
通常说的RTSP包括RTSP协议、RTP协议、RTCP协议
对于这些协议的作用简单的理解如下
- RTSP协议:负责服务器与客户端之间的请求与响应
- RTP协议:负责传输媒体数据
- RTCP协议:在RTP传输过程中提供传输信息
rtsp承载与rtp和rtcp之上,rtsp并不会发送媒体数据,而是使用rtp协议传输
rtp并没有规定发送方式,可以选择udp发送或者tcp发送
实时流协议(Real Time Streaming Protocol),由 RealNetworks 等公司开发。此协议负责控制通信端点(Endpoint)之间的媒体会话(media sessions) —— 例如播放、暂停、录制。通常需要结合:实时传输协议(Real-time Transport Protocol)、实时控制协议(Real-time Control Protocol)来实现视频流本身的传递
大部分浏览器没有对 RTSP 提供原生的支持
2.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 的高延迟问题也存在。
2.3.5 WebRTC 协议
WebRTC 是一整套 API,为浏览器、移动应用提供实时通信(RealTime Communications)能力。它包含了流媒体协议的功能,但是不是以协议的方式暴露给开发者的
WebRTC 支持 Chrome 23+、Firefox 22+、Chrome for Android,提供 Java / Objective-C 绑定
WebRTC 主要有三个职责:
- 捕获客户端音视频,对应接口 MediaStream(也就是 getUserMedia)
- 音视频传输,对应接口 RTCPeerConnection
- 任意数据传输,对应接口 RTCDataChannel
WebRTC 内置了点对点的支持,也就是说流不一定需要经过服务器中转
2.4 服务器端技术
视频监控通常都是 CS 模式(而非P2P),在服务器端,你需要部署流媒体服务。
2.4.1 GStreamer
这是一个开源的跨平台多媒体框架。通过它你可以构建各种各样的媒体处理组件,包括流媒体组件。通过插件机制,GStreamer 支持上百种编码格式,包括 MPEG-1, MPEG-2, MPEG-4, H.261, H.263, H.264, RealVideo, MP3, WMV, FLV
Kurento、Flumotion 都是基于 GStreamer 构建的流媒体服务器软件。
2.4.2 Live555
Live555 是流媒体服务开发的基础库,支持 RTP/RTCP/RTSP/SIP 等协议,适合在硬件资源受限的情况下使用(例如嵌入式设备)。
基于 Live555 的软件包括:
- Live555媒体服务器,完整的RTSP服务器
- openRTSP,一个命令行程序,支持提供RTSP流、接收RTSP流、把RTSP流中的媒体录像到磁盘
- playSIP,可以进行V oIP 通话
- liveCaster,支持组播的 MP3 流媒体服务
2.4.3 其它
流媒体服务实现有很多,它们中的一些在最初针对特定的流协议,大部分都走向多元化。例如,Red5是一个RTMP流媒体服务器,Wowza是一个综合的流媒体服务器,WebRTC的流媒体服务。
三、HTML5媒体标签
HTML5支持<audio>和 <video>标签(两者都对应了HTMLMediaElement的子类型)以实现视频、音频的播放。
3.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>
3.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>
3.3 <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);
4.1基于 MSE 的框架
4.1.1 wfs
wfs 是一个播放原始 H.264 帧的 HTML5 播放器,它的工作方式是把 H.264 NAL 单元封装为 ISO BMFF(MP4)片,然后Feed给 MSE 处理。
4.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+ 以上版本的直播流 。
4.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/防火墙 穿透
- 在点对点通信失败时,作为中继器使用
5.1 架构图
5.2 流捕获
5.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 元素播放
- 失败后的回调
5.2.2 捕获音频
捕获音频类似:
navigator.getUserMedia( { audio: true }, function ( stream ) {
var audioContext = new AudioContext();
// 从捕获的音频流创建一个媒体源管理
var streamSource = audioContext.createMediaStreamSource( stream );
// 把媒体源连接到目标(默认是扬声器)
streamSource.connect( audioContext.destination );
}, error );
5.2.3 MediaStream
MediaStream对象提供以下方法:
- getAudioTracks(),音轨列表
- getVideoTracks(),视轨列表
每个音轨、视轨都有个label属性,对应其设备名称。
5.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();
5.3 信号处理
在端点之间(Peer)发送流之前,需要进行通信协调、发送控制消息,即所谓信号处理(Signaling),信号处理牵涉到三类信息:
会话控制信息:初始化、关闭通信,报告错误
网络配置:对于其它端点来说,本机的 IP 和 port 是什么
媒体特性:本机能够处理什么音视频编码、多高的分辨率。本机发送什么样的音视频编码
WebRTC 没有对信号处理规定太多,我们可以通过 Ajax/WebSocket 通信,以 SIP、Jingle、ISUP 等协议完成信号处理。点对点连接设立后,流的传输并不需要服务器介入。信号处理的示意图如下:
5.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 ) );
else
// 添加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 );
else
// 针对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 );
5.4 流转发
主要牵涉到的接口是RTCPeerConnection,上面的例子中已经包含了此接口的用法。WebRTC在底层做很多复杂的工作,这些工作对于JavaScript来说是透明的:
- 执行解码
- 屏蔽丢包的影响
- 点对点通信:WebRTC 引入流交互式连接建立(Interactive Connectivity Establishment,ICE)框架。ICE 负责建立点对点链路的建立:
- 首先尝试直接
- 不行的话尝试 STUN(Session Traversal Utilities for NAT)协议。此协议通过一个简单的保活机制确保NAT端口映射在会话期间有效
- 仍然不行尝试 TURN(Traversal Using Relays around NAT)协议。此协议依赖于部署在公网上的中继服务器。只要端点可以访问TURN服务器就可以建立连接
- 通信安全
- 带宽适配
- 噪声抑制
- 动态抖动缓冲(Dynamic jitter buffering),抖动是由于网络状况的变化,缓冲用于收集、存储数据,定期发送
5.5 任意数据交换
通过 RTCDataChannel 完成,允许点对点之间任意的数据交换。RTCPeerConnection 连接创建后,不但可以传输音视频流,还可以打开多个信道(RTCDataChannel)进行任意数据的交换。RTCDataChanel 的特点是:
- 类似于 WebSocket 的API
- 支持带优先级的多通道
- 超低延迟,因为不需要通过服务器中转
- 支持可靠/不可靠传输语义。支持 SCTP、DTLS、UDP 几种传输协议
- 内置安全传输(DTLS)
- 内置拥塞控制
使用 RTCDataChannel 可以很好的支持游戏、远程桌面、实时文本聊天、文件传输、去中心化网络等业务场景。
5.6 WebRTC框架
- PeerJS :简化 WebRTC 的点对点通信、视频、音频调用,提供云端的 PeerServer,你也可以自己搭建服务器
- Sharefest:基于 Web 的 P2P 文件共享
- webRTC.io:WebRTC 的一个抽象层,同时提供了客户端、服务器端 Node.js 组件。服务器端组件抽象了 STUN。类似的框架还有 SimpleWebRTC、easyrtc
- OpenWebRTC:允许你构建能够和遵循 WebRTC 标准的浏览器进行通信的 Native 应用程序,支持Java绑定
- NextRTC:基于 Java 实现的 WebRTC 信号处理服务器
- Janus:这是一个 WebRTC 网关,纯服务器端组件,目前仅仅支持 Linux 环境下安装。Janus本身实现了到浏览器的 WebRTC 连接机制,支持以JSON格式交换数据,支持在服务器端应用逻辑 - 浏览器之间中继 RTP/RTCP 和消息。特殊化的功能有服务器端插件完成。官网地址:https://janus.conf.meetecho.com
- Kurento:这是一个开源的 WebRTC 媒体服务器