直播协议

HLS

HLS 全称是 HTTP Live Streaming。这是 Apple 提出的直播流协议。目前,IOS 和 高版本 Android 都支持 HLS。那什么是 HLS 呢? HLS 主要的两块内容是 .m3u8 文件和 .ts 播放文件。接受服务器会将接受到的视频流进行缓存,然后缓存到一定程度后,会将这些视频流进行编码格式化,同时会生成一份 .m3u8 文件和其它很多的 .ts 文件。HLS 的基本架构为:

  • 服务器:后台服务器接受视频流,然后进行编码和片段化。
  • 编码:视频格式编码采用 H.264。音频编码为 AAC, MP3, AC-3,EC-3。然后使用 MPEG-2 Transport Stream 作为容器格式。
  • 客户端:使用一个 URL 去下载 m3u8 文件,然后,开始下载 ts 文件,下载完成后,使用 playback software(即时播放器) 进行播放。

masterplaylist:

  • live playlist: 动态列表。顾名思义,该列表是动态变化的,里面的 ts 文件会实时更新,并且过期的 ts 索引会被删除。默认,情况下都是使用动态列表。
  • event playlist: 静态列表。它和动态列表主要区别就是,原来的 ts 文件索引不会被删除,该列表是不断更新,而且文件大小会逐渐增大。它会在文件中,直接添加 #EXT-X-PLAYLIST-TYPE:EVENT 作为标识。
  • VOD playlist: 全量列表。它就是将所有的 ts 文件都列在 list 当中。如果,使用该列表,就和播放一整个视频没有啥区别了。它是使用 #EXT-X-ENDLIST 表示文件结尾。

m3u8文件内容(以下为live playlist,不包含汉字)

1
2
3
4
5
6
7
8
9
10
11
12
#EXTM3U                             m3u文件头
#EXT-X-VERSION:3 PlayList版本
#EXT-X-ALLOW-CACHE:NO 是否允许缓存
#EXT-X-TARGETDURATION:5 分片最大时长,单位秒
#EXT-X-MEDIA-SEQUENCE:1552956728 第一个TS分片的序列号,默认为 0

#EXTINF:3.469, 指定每个媒体段(ts)的持续时间(秒),仅对其后面的URI有效
http://media.test.com/1552956727.ts
#EXTINF:4.145,
http://media.test.com/1552956728.ts
#EXTINF:4.141,
http://media.test.com/1552956729.ts

HLS如何完成直播?

服务端:将接收到的流每缓存一定时间后包装为一个新的ts文件,然后更新m3u8文件。m3u8文件中只保留最新的几个片段。

客户端:直接使用video标签加载m3u8文件(live playlist),video标签会自动解析其内容进行直播播放。

1
<video src="http://hlsa.xxxxx.com/live/test.m3u8" autoplay controls></video>
HLS的优点

支持范围广,使用简单,完美适用于H5,是移动端天生的直播方案

HLS的缺点

由于 HLS 是基于 HTTP 的,所以其直播延迟较高。带来延迟的主要地方有:

  1. TCP 握手
  2. m3u8 文件下载
  3. m3u8 文件下所有 ts 文件下载
HLS的优化方案

减少每个 m3u8 文件中的 ts文件的 数量和时长,但单个ts文件时间变短会增加服务器性能消耗。

RTMP

RTMP即Real Time Messaging Protocol(实时消息传输协议)的首字母缩写。该协议基于TCP,是一个协议族,包括RTMP基本协议及RTMPT/RTMPS/RTMPE等多种变种。RTMP是一种设计用来进行实时数据通信的网络协议,它是基于 FLV 格式进行开发的,主要用来在Flash/AIR平台和支持RTMP协议的流媒体/交互服务器之间进行音视频和数据通信。

  • 纯 RTMP: 直接通过 TCP 连接,端口为 1935
  • RTMPS: RTMP + TLS/SSL,用于安全性的交流。
  • RTMPE: RTMP + encryption。在 RTMP 原始协议上使用,Adobe 自身的加密方法
  • RTMPT: RTMP + HTTP。使用 HTTP 的方式来包裹 RTMP 流,这样能直接通过防火墙。不过,延迟性比较大。
  • RTMFP: RMPT + UDP。该协议常常用于 P2P 的场景中,针对延时有变态的要求。
RTMP的优点
  • 延迟小
  • 基于 TCP 长连接,不需要多次建连
  • 只要浏览器支持FlashPlayer就能非常简易的播放。
RTMP的缺点
  • 协议复杂,会提高开发成本。
  • 它是基于 TCP 传输,非公共端口,可能会被防火墙阻拦。
  • RTMP 为 Adobe 私有协议,很多设备无法播放,需要使用第三方解码器才能播放。
  • 有累积延迟,当网络状态差时,服务器会将包缓存起来,导致累积的延迟,解决方案是当客户端的缓冲区很大,就断开重连。

HTTP-FLV

HTTP-FLV将音视频数据封装成 FLV,然后通过 HTTP 协议传输给客户端。

HTTP-FLV 与 RTMPT类似,都是针对于 FLV 视频格式做的直播分发流。

  • 两者相同点

    • 都是针对 FLV 格式
    • 延时低
    • 走的 HTTP 通道
  • 两者不同点

    • HTTP-FLV直接发起长连接,下载对应的FLV文件,且头部信息简单。
    • RTMPT的握手协议过于复杂,分包,组包过程耗费资源大。

因为 RTMP 发的包很容易处理,通常 RTMP 协议会作为视频上传端来处理,然后经由服务器转换为 FLV 文件,通过 HTTP-FLV 下发给用户。

OAuth client

现在市面上,比较常用的就是 HTTP-FLV 进行播放。HTTP-FLV 的使用方式也很简单。和 HLS 一样,只需要添加一个连接即可

1
2
// 这里的.flv指的是FLV直播流,并不是说.flv结尾的都是HTTP-FLV 协议
<object type="application/x-shockwave-flash" src="http://hlsa.xxxxx.com/live/test.flv"></object>

在高版本浏览器中,可是通过MSE(Media Source Extensions)来进行解析,MSE在下面会提到。

HTTP-FLV优点
  • 延迟小
  • 相对于RTMP,HTTP-FLV不会被防火墙墙掉
HTTP-FLV缺点
  • 手机端不支持

协议对比

协议 传输协议 优势 缺陷 延迟
HLS TCP 手机浏览器完美支持 延迟高 10s以上
RTMP HTTP 能推能播,延迟低,私密性好 协议复杂,高并发表现不佳 1s - 3s
HTTP-FLV HTTP 延迟低,直播网站常用 手机端不支持 2s - 3s

MSE(Media Source Extensions)

由于各大浏览器的对 FLV 的围追堵截,导致 FLV 在浏览器的生存状况堪忧,但由于 FLV 格式简单、处理效率高的特点,各大视频站后台开发者都不愿弃用,如果一旦更改的话,就需要对现有视频进行转码,而转码带来的一些列问题都让人无法接受。

而MSE(Media Source Extensions)的出现,解决的这一问题。

在没有 MSE 出现之前,前端对 video 的操作,仅仅局限在对视频文件的操作,并不能对视频流做任何相关的操作。现在 MSE 提供了一系列的接口,使开发者可以直接操作 media stream。

我们以MDN的例子来看下 MSE 是如何完成基本流的处理的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
var video = document.querySelector('video');

var assetURL = 'frag_bunny.mp4';

// 需要针对Blink特定的编解码器
// ./mp4info frag_bunny.mp4 | grep Codec
var mimeCodec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';

// 检测是否支持MediaSource & 是否支持上面的mine编码
if ('MediaSource' in window && MediaSource.isTypeSupported(mimeCodec)) {

// 创建MediaSource实例
var mediaSource = new MediaSource();
// console.log(mediaSource.readyState); // closed 当前状态为关闭

/*
使用URL.createObjectURL,传入MediaSource实例来创建URL
使 MediaSource 与 <video> 建立联系
创建出的URL就是在各直播平台的html代码中能看到的blob形式
blob:https://www.test.com/eb639f5a-4a64-4e9c-819f-e0ecca7d7bf0
*/
video.src = URL.createObjectURL(mediaSource);

// 监听mediaSource的sourceopen事件,再sourceOpen函数中进行下一步操作
mediaSource.addEventListener('sourceopen', sourceOpen);

} else {
console.error('Unsupported MIME type or codec: ', mimeCodec);
}

function sourceOpen (_) {
//console.log(this.readyState); // open 当前状态已经开启
var mediaSource = this;
/*
设置相关的编码器
接收一个 mimeType 表示该流的编码格式,返回一个具体的视频流sourceBuffer。
sourceBuffer 是直接与视频流相关的API。
*/
var sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);

// 异步拉取相关的音视频流
fetchAB(assetURL, function (buf) {
/*
添加sourceBuffer的updateend事件的监听
sourceBuffer.appendBuffer会触发updateend
*/
sourceBuffer.addEventListener('updateend', function (_) {
// 停止接收视频流(注意:并不是中断的意思)
mediaSource.endOfStream();
// 播放视频
video.play();
//console.log(mediaSource.readyState); // ended 当前状态为结束
});

// 将`视频流`添加到sourceBuffer中
sourceBuffer.appendBuffer(buf);

});

};

// 异步拉取相关的音视频流
function fetchAB (url, cb) {
console.log(url);
var xhr = new XMLHttpRequest;
xhr.open('get', url);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
cb(xhr.response);
};
xhr.send();
};

以上代码大致可分为3步(具体可看代码注释):

  1. 异步拉取数据
  2. MediaSource 处理数据
  3. 将数据流交给 audio/video 标签播放

以上代码只是播放了一段视频,如果想要做到直播,那么我们就需要不停的获取最新的视频流,然后再合适的时候调用sourceBuffer.appendBuffer(buf)添加到sourceBuffer中。

将代码做一下修改,多添加几个视频流,是不是就可以做到直播了呢?:

1
2
3
4
5
6
7
8
9
10
sourceBuffer.addEventListener('updateend', function (e) {
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
mediaSource.endOfStream()
video.play()
}
})
sourceBuffer.appendBuffer(buf)
// 多添加几个视频流
sourceBuffer.appendBuffer(buf)
sourceBuffer.appendBuffer(buf)

运行代码我们发现报错了:Failed to execute 'appendBuffer' on 'SourceBuffer': This SourceBuffer is still processing an 'appendBuffer' or 'remove' operation.意思是,SourceBuffer现在正在添加appendBuffer,还没加完,不要再往里面加了,这就非常尴尬了。

正确的做法是应该在sourceBuffer的updateend事件中进行appendBuffer操作。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var i = 1
sourceBuffer.addEventListener('updateend', function (e) {
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
mediaSource.endOfStream()
video.play()
}
假如一段视频5s长,我们在第4s的时候appendBuffer
if (mediaSource.readyState === 'ended') {
setTimeout(() => {
try {
设置添加的视频流播放的起始时间
sourceBuffer.timestampOffset = 5 * i
sourceBuffer.appendBuffer(arrayBuffer)
} catch (e) {
console.error(e)
} finally {
i++
}
}, 4000);
}
})
sourceBuffer.appendBuffer(buf)

这样一直appendBuffer,就可以达到直播效果。

注:chrome 的 SourceBuffer 大小为 音频 12 MB, 视频 150 MB,如果一直添加SourceBuffer是会满的,会捕获到The SourceBuffer is full, and cannot free space to append additional buffers的报错。

关于如何定时获取直播视频、如何正确的处理 SourceBuffer 的缓存内容,以及相关错误处理,这里不做了解。

总结

  • 手机端:

    • 简单粗暴,直接使用 HLS (.m3u8) 来直播。
  • PC端:

    • 对于高版本浏览器,可直接使用 Media Source Extensions 处理视频流,加 video 直播。
    • 对于不支持的MSE的浏览器可降级为 HTTP-FLV 或 RTMP 直播。

补充:

国内90%的直播平台都是采用的RTMP和HTTP-FLV的混合,HLS很少,而国外大部分采用的DASH,少部分用HLS和其他协议。

参考:

https://cloud.tencent.com/developer/article/1020510

https://cloud.tencent.com/developer/article/1005457

https://segmentfault.com/a/1190000010440054

https://www.jianshu.com/p/1bfe4470349b