This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,152 @@
<audio id="audio" title="31 | 一对多直播系统RTMP/HLS你该选哪个" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/71/11/71d9be8956f6d4463a7e2939f13fb311.mp3"></audio>
近年来,随着智能手机的普及以及移动互联网的飞速发展,音视频技术在各个行业得到了广泛的应用。尤其是娱乐直播在前几年着实火了一把,像映客、斗鱼这类以展示才艺为主的直播产品非常受大家的欢迎。
从技术角度来讲,映客、斗鱼这类娱乐直播与在线教育、音视频会议直播有着非常大的区别。在线教育、音视频会议这类直播属于**实时互动直播**,主要考虑**传输的实时性**,因此一般使用 **UDP** 作为底层传输协议;而娱乐直播对实时性要求不高,更多关注的是画面的质量、音视频是否卡顿等问题,所以一般采用 **TCP** 作为传输协议。我们称前者为实时互动直播,后者为传统直播。
本专栏的前两个模块都是介绍实时互动直播的,而从今天开始我们会讲解传统直播技术。
**传统直播技术使用的传输协议是 RTMP 和 HLS**。其中RTMP是由 Adobe 公司开发的,虽然不是国际标准,但也算是工业标准,在 PC 占有很大的市场而HLS是由苹果公司开发的主要用在它的 iOS 平台不过Android 3 以后的平台也是默认支持 HLS 协议的。
接下来,我们先看一下传统音视频直播系统的基本架构,让你对传统直播架构的“内幕”有一个初步的了解。
## 传统直播基本架构
商业级直播系统的规模、结构是非常复杂的,除了最核心的音视频直播外,还包括用户管理、认证系统、直播间管理、打赏、红包、私信等很多功能,不过这些更多的是一些业务逻辑,在本文中我们不会对它们进行讲解,而是聚焦在**最核心的音视频直播技术**上。
我们先来看一下传统直播的基本架构图,如下图所示:
<img src="https://static001.geekbang.org/resource/image/65/24/65e38dc6f7d1ca989571ee685da25324.png" alt="">
从图中可以看出,传统直播架构由**直播客户端、信令服务器和CDN网络**这三部分组成。下面我们就来一一分析下这每个模块的功能以及它们彼此之间的联系。
**直播客户端**主要包括音视频数据的采集、编码、推流、拉流、解码与播放这几个功能。
但实际上,这几个功能并不是放在同一个客户端中实现的。为什么呢?因为作为主播来说,他不需要看到观众的视频或听到观众的声音;而作为观众来讲,他们与主播之间是通过文字进行互动的,因此也不需要分享自己的音视频。
所以用过快手、映客等这类产品的同学都知道,客户端按用途可分为两类,一类是主播使用的客户端,包括音视频数据采集、编码和推流功能;另一类是观众使用的客户端,包括拉流、解码与渲染(播放)功能。
对于主播客户端来说,它可以从 PC 或移动端设备的摄像头、麦克风采集数据,然后对采集到的音视频数据进行编码,最后将编码后的音视频数据按 RTMP 协议推送给 CDN 源节点RTMP 接入服务器)。
对于观众客户端来说,它首先从直播管理系统中获取到房间的流媒体地址,然后通过 RTMP 协议从边缘节点拉取音视频数据,并对获取到的音视频数据进行解码,最后进行视频的渲染与音频的播放。
通过上面的描述,看上去主播端与观众端的开发工作量差不多。但实际上,观众端的开发工作量要小得多。其原因是,观众端要实现的就是一个播放器功能,而目前开源界有两个比较有名而又成熟的开源项目 Ijkplayer 和 VLC所以只要将这两个开源项目中的一个集成到你自己的项目中基本上就完成了观众端的所有开发工作。
**信令服务器**,主要用于接收信令,并根据信令处理一些和业务相关的逻辑,如创建房间、加入房间、离开房间、送礼物、文字聊天等。
实际上,这部分功能并不是很复杂,但有一点需要你特别注意,那就是聊天消息的处理。我们来举个例子,在一个有 10000 人同时在线的房间里,如果其中一个用户发送了文字消息,那么服务端收到该消息之后就要给 10000 人转发。如果主播说“请能听到我声音的人回复 1”那这时 10000 人同时发消息,服务端要转发多少条呢?要转发 10000 * 10000 = 1亿条消息。这对于任何一台服务器来说都会产生灾难性的后果。所以在开发直播系统的信令服务器时**一定要关注和防止消息的洪泛**。
**CDN网络**主要用于媒体数据的分发。它内部的实现非常复杂我们姑且先把它当作是一个黑盒子然后只需要知道传给它的媒体数据可以很快传送给全世界每一个角落。换句话说你在全世界各地只要接入了CDN 网络,你都可以快速看到你想看到的“节目”了。
介绍完直播客户端、信令服务器和 CDN 网络之后,我们再来来看看**主播到底是如何将自己的音视频媒体流进行分享的**。
主播客户端在分享自己的音视频媒体流之前首先要向信令服务器发送“创建房间”的信令红圈标出的步骤1信令服务器收到该信令后给主播客户端返回一个推流地址CDN 网络源站地址);此时,主播客户端就可以通过音视频设备进行音视频数据的采集和编码,生成 RTMP 消息,最终将媒体流推送给 CDN 网络红圈标出的步骤2
无论主播端使用的是PC机还是移动端其推流步骤都是一样的所以从上面的架构图中我们也可以看出步骤3、步骤4与步骤1、步骤2是一致的。
当观众端想看某个房间里的节目时,首先也要向信令服务器发消息,不过发送的可不是 “创建房间” 消息了,而是 “加入房间”也就是步骤5服务端收到该信令后会根据用户所在地区分配一个与它最接近的“CDN边缘节点”观众客户端收到该地址后就可以从该地址拉取媒体流了即步骤6。
需要注意的是,在传统直播系统中,一般推流都使用的 RTMP 协议,而拉流可以选择使用 RTMP协议或者 HLS 协议。
以上就是传统的直播架构,下面我们再来简要介绍一下 CDN 网络。
## CDN网络的实现
CDN 网络的构造十分复杂如下图所示一般情况下它先在各运营商内构建云服务然后再将不同运营商的云服务通过光纤连接起来从而实现跨运营商的全网CDN云服务。
<img src="https://static001.geekbang.org/resource/image/c3/46/c31165d01a6772bbcc44a0981a6be446.png" alt="">
而每个运营商云服务内部包括了多个节点按功能分为3类。
- 源节点,用于接收用户推送的媒体流。
- 主干结点,起到媒体数据快速传递的作用,比如与其他运营商传送媒体流。
- 过缘节点,用于用户来主动接流。一般边缘节点的数量众多,但机子的性能比较低,它会被布署到各地级市,主要解决网络最后一公里的问题。
接下来我们简要描述一下CDN网络的处理流程。
当一个主播想将自己的音视频共享出去的时候首先通过直播系统的信令服务器获取到可以推送媒体流的CDN源节点。CDN网络从源节点接收到媒体数据后会主动向各个主干结点传送流媒体数据这样主干结点就将媒体数据缓存起来了。当然这个缓冲区的大小是有限的随着时间流逝缓冲区中的数据也在不断更替中。
当有观众想看某个主播的节目时,会从直播系统的信令服务器获取离自己最近的 CDN 边缘节点然后到这个边缘节点去拉流。由于他是第一个在该节点拉流的用户因此该CDN边缘节点还没有用户想到的媒体流怎么办呢那就向主干结点发送请求。主干结点收到请求后从自己的缓冲区中取出数据流源源不断地发给边缘节点这时边缘节点再将媒体数据发给观众。
当第二个观众再次到该CDN边缘节点接流时该节点发现该流已经在自己的缓存里了就不再向主干结点请求直接将媒体流下发下去了。因此观众在使用 CDN 网络时会发现,第一个观众在接流时需要花很长时间才能将流拉下来,可是后来的用户很快就将流拉下来进行播放了。
以上就是 CDN 网络的基本原理,接下来我们再来看看 RTMP协议与 HLS 协议的比较。
## RTMP 介绍
RTMP全称Real Time Messaging Protocol ,即**实时消息协议。但它实际上并不能做到真正的实时,一般情况最少都会有几秒到几十秒的延迟,底层是基于 TCP 协议的**。
RTMP 的传输格式为 **RTMP Chunk Format**,媒体流数据的传输和 RTMP 控制消息的传输都是基于此格式的。
需要注意的是,在使用 RTMP 协议传输数据之前RTMP 也像 TCP 协议一样先进行三次握手才能将连接建立起来。当RTMP连接建立起来后你可以通过RTMP协议的控制消息为通信的双方设置传输窗口的大小缓冲区大小、传输数据块的大小等。具体细节可以参考[文档rtmp_specification_1.0](http://wwwimages.adobe.com/www.adobe.com/content/dam/acom/en/devnet/rtmp/pdf/rtmp_specification_1.0.pdf),或者参考下一篇专门介绍 RTMP 协议细节的文章,本文就不再详述了。
### 1. 优势
RTMP协议在苹果公司宣布其产品不支持RTMP协议且推出 HLS 技术来替代 RTMP 协议的“打压”下,已停止更新。但协议停止更新后,这么多年仍然屹立不倒,说明该协议肯定有它独特的优势。那有哪些呢?
- **RTMP协议底层依赖于 TCP 协议**,不会出现丢包、乱序等问题,因此音视频业务质量有很好的保障。
- **使用简单,技术成熟**。有现成的 RTMP 协议库实现如FFmpeg项目中的 librtmp 库,用户使用起来非常方便。而且 RTMP 协议在直播领域应用多年,技术已经相当成熟。
- **市场占有率高**。在日常的工作或生活中,我们或多或少都会用到 RTMP 协议。如常用的FLV 文件,实际上就是在 RTMP消息数据的最前面加了FLV文件头。
- **相较于 HLS 协议,它的实时性要高很多**。
### 2. 劣势
RTMP有优势也有劣势。在RTMP的众多劣势中我认为最为关键的有两条。
- 苹果公司的 iOS 不支持 RTMP 协议,按苹果官方的说法, RTMP 协议在安全方面有重要缺陷。
- 在苹果的公司的压力下Adobe已经停止对 RTMP 协议的更新了。
可以看出 RTMP 协议已经失去了未来,只是由于目前没有更好的协议可以直接代替它,所以它还能“苟延残喘”存活几年。但它最终一定会消亡,这是毋庸置疑的。
## HLS 介绍
HLS全称 HTTP Live Streaming是苹果公司实现的基于 HTTP 的流媒体传输协议。它可以支持流媒体的直播和点播,主要应用在 iOS 系统和 HTML5 网页播放器中。
HLS 的基本原理非常简单,它是将多媒体文件或直接流进行切片,形成一堆的 ts 文件和 m3u8 索引文件并保存到磁盘。
当播放器获取 HLS 流时,它首先根据时间戳,通过 HTTP 服务,从 m3u8 索引文件获取最新的 ts 视频文件切片地址,然后再通过 HTTP 协议将它们下载并缓存起来。当播放器播放HLS 流时,播放线程会从缓冲区中读出数据并进行播放。
通过上面的描述我们可以知道,**HLS 协议的本质就是通过 HTTP 下载文件,然后将下载的切片缓存起来**。由于切片文件都非常小所以可以实现边下载边播的效果。HLS规范规定播放器至少下载一个ts切片才能播放所以HLS理论上至少会有一个切片的延迟。
### 1. 优势
HLS 是为了解决 RTMP 协议中存在的一些问题而设计的,所以,它自然有自己的优势。主要体现在以下几方面:
- RTMP 协议没有使用标准的 HTTP 接口传输数据,在一些有访问限制的网络环境下,比如企业网防火墙,是没法访问外网的,因为企业内部一般只允许 80/443 端口可以访问外网。而 HLS 使用的是 HTTP 协议传输数据所以HLS协议天然就解决了这个问题。
- HLS 协议本身实现了码率自适应,不同带宽的设备可以自动切换到最适合自己码率的视频进行播放。
- 浏览器天然支持 HLS 协议而RTMP协议需要安装 Flash 插件才能播放 RTMP 流。
### 2. 不足
HLS 最主要的问题就是**实时性差**。由于HLS往往采用 10s 的切片所以最小也要有10s 的延迟一般是2030s的延迟有时甚至更差。
HLS 之所以能达到 2030s 的延迟,主要是由于 HLS 的实现机制造成的。HLS 使用的是 HTTP 短连接且HTTP 是基于 TCP 的,所以这就意味着 HLS 需要不断地与服务器建立连接。TCP 每次建立连接时都要进行三次握手,而断开连接时,也要进行四次挥手,基于以上这些复杂的原因,就造成了 HLS 延迟比较久的局面。
## 如何选择 RTMP 和 HLS
分析完RTMP 和 HLS 各自的优势和劣势后,接下来我们就结合项目实践,对这二者的使用场景做一些建议。
- 流媒体接入,也就是推流,应该使用 RTMP 协议。
- 流媒体系统内部分发使用 RTMP 协议。因为内网系统网络状况好,使用 RTMP 更能发挥它的高效本领。
- 在PC 上,尽量使用 RTMP 协议,因为 PC 基本都安装了 Flash 播放器,直播效果要好很多。
- 移动端的网页播放器最好使用 HLS 协议。
- iOS 要使用 HLS 协议,因为不支持 RTMP 协议。
- 点播系统最好使用 HLS 协议。因为点播没有实时互动需求,延迟大一些是可以接受的,并且可以在浏览器上直接观看。
## 小结
本文我们首先详细介绍了传统直播系统的架构,并展示了主播端与观众端是如何通过该系统进行直播的。
然后我们又对 RTMP协议和 HLS 协议进行了比较,给出了一些使用建议,这些只能说是一些经验,具体实践时还得根据产品实际需求来考虑。比如,有些产品只需要网页播放,那就考虑 HLS 协议;有些产品很简单,是私有部署,不需要考虑互联网需求,那直接用 RTMP 协议即可。所以说,如何选择还是得根据产品需求来定。
总体来看,随着人们对实时性、互动性的要求越来越高,人们对传统直播技术的需求越来越少。本专栏前两个模块中介绍的 WebRTC 技术正是为了解决人们对实时性、互动性需求而提出的新技术。所以,以 WebRTC 为代表的实时直播系统必然会成为未来的主流。另一方面,随着 5G 的落地与推广,带宽问题会得到极大的解决,因此人们在享受实时性、互动性便利的同时,音视频服务质量也会得到极大的改善,这将加速传统的基于 RTMP /HLS 的直播技术的淘汰。
当然,基于 RTMP/HLS 的直播技术的淘汰不是一蹴而就的,因为在大负载方面,使用 CDN 技术的传统直播目前还有一定优势,所以短期内可以考虑 WebRTC 的实时直播技术与使用RTMP 协议为代表的传统直播技术相结合的方案,既可以满足用户对于实时性、互动性的需求,又可以满足大负载的需求,这也是各大厂积极推动的一套方案。
## 思考时间
我们前面讲过 WebRTC 采用 UDP/RTP 进行媒体数据传输,而 RTMP 却使用了 TCP 协议,那它为什么不使用 UDP协议呢
欢迎在留言区与我分享你的想法,也欢迎你在留言区记录你的思考过程。感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给更多的朋友。

View File

@@ -0,0 +1,140 @@
<audio id="audio" title="32 | HLS实现一对多直播系统的必备协议" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/28/37/28930131a783ad1ad4960ff58c5ece37.mp3"></audio>
在[上一篇文章](https://time.geekbang.org/column/article/140181)中 ,我们对 RTMP 协议和 HLS 协议的优势与劣势进行了比较。从比较的结果我们可以看出RTMP作为传统的直播传输技术在实时性方面要比 HLS 好很多,所以它还是有一定优势的。
不过随着Chrome浏览器宣布不再对Flash 插件提供支持、Adobe 公司停止对 RTMP 协议更新以及苹果公司声称 iOS 上不允许使用 RTMP 协议等一系列事件的发生,我们可以断定 RTMP 协议已失去了未来。
而 HLS 协议则恰恰相反,它在未来会有更广阔的应用前景。我们可以通过以下几点来得到这个结论:
- HLS 是苹果开发的协议,苹果产品原生支持此协议;
- HLS 是基于 HTTP 的,可以不受防火墙限制,所以它的连通性会更好;
- HLS 还能根据客户的网络带宽情况进行自适应码率的调整,这对于很多用户来说是非常有吸引力的。
基于以上原因我们有必要从HLS 直播架构、FFmpeg 生成 HLS 切片、HLS m3u8 格式和HLS TS 格式这四个方面对 HLS 协议的细节做一下介绍。
## HLS 直播架构
下面我们来看一下 HLS 直播系统的架构图,如下所示:
<img src="https://static001.geekbang.org/resource/image/c8/7a/c824a7d2fc85aa9583e10bc0dbff407a.png" alt="">
我们在[上一篇文章](https://time.geekbang.org/column/article/140181)中讲过传统直播系统大致分为三部分直播客户端、信令服务和CDN网络使用HLS 协议也是如此。只不过在我们这里为了简化流程,去掉了信令服务系统。
如上图所示,客户端采集媒体数据后,通过 RTMP 协议将音视频流推送给 CDN网络的源节点接入节点。源节点收到音视频流后再通过 Convert 服务器将 RTMP 流切割为 HLS 切片文件,即 .ts 文件。同时生成与之对应的 m3u8 文件即HLS播放列表文件。
切割后的 HLS 分片文件(.ts 文件)和 HLS 列表文件(.m3u8文件经CDN网络转发后客户端就可以从离自己最近的 CDN 边缘节点拉取 HLS 媒体流了。
在拉取 HLS 媒体流时,客户端首先通过 HLS 协议将 m3u8 索引文件下载下来,然后按索引文件中的顺序,将 .ts 文件一片一片下载下来,然后一边播放一边缓冲。此时,你就可以在 PC、手机、平板等设备上观看直播节目了。
对于使用 HLS 协议的直播系统来说,最重要的一步就是**切片**。源节点服务器收到音视频流后,先要数据缓冲起来,保证到达帧的所有分片都已收到之后,才会将它们切片成 TS 流。
为了便于分析,本文是通过 FFmpeg 工具将 MP4 文件切割成 HLS 格式的文件切片。但不管选择使用哪一种切割文件的方法或工具,生成的切片和索引文件的格式都是一致的。
勿在浮沙筑高台,为了让你在工作中做到得心应手、心中有数,接下来就让我们一起探索 HLS 协议的一些具体细节吧。
## FFmpeg 生成 HLS 切片
这里我们是通过 FFmpeg 工具将一个 MP4 文件转换为 HLS 切片和索引文件的。所以,你需要预先准备一个 MP4 文件,并且下载好 FFmpeg 工具。你可以从[FFmpeg 官网](http://www.ffmpeg.org/download.html)下载二进制包也可以通过下载源码自行编译出FFmpeg工具。FFmpeg用于将 MP4 切片成 HLS的命令如下
```
ffmpeg -i test.mp4 -c copy -start_number 0 -hls_time 10 -hls_list_size 0 -hls_segment_filename test%03d.ts index.m3u8
```
该命令参数说明如下:
- -i ,输入文件选项,可以是磁盘文件,也可以是媒体设备。
- -c copy表示只是进行封装格式的转换。不需要将多媒体文件中的音视频数据重新进行编码。
- -start_number表示 .ts 文件的起始编号,这里设置从 0 开始。当然,你也可以设置其他数字。
- -hls_time表示每个 .ts 文件的最大时长,单位是秒。这里设置的是 10s表示每个切片文件的时长为10秒。当然由于没有进行重新编码所以这个时长并不准确。
- -hls_list_size表示播放列表文件的长度0 表示不对播放列表文件的大小进行限制。
- -hls_segment_filename表示指定 TS 文件的名称。
- index.m3u8表示索引文件名称。
执行完这条命令后,在当前路径下会生成一系列 .ts 文件和 index.m3u8 文件。下面,我们再分别分析一下 .m3u8 文件格式和 .ts 文件格式。
## m3u8 格式分析
正如前面讲到HLS 必须要有一个 .m3u8的索引文件 。它是一个播放列表文件,文件的编码必须是 UTF-8 格式。这里我们将前面生成的 .m3u8 文件内容展示一下,以便让你有个感观的认识。内容如下:
```
#EXTM3U
#EXT-X-VERSION:3 // 版本信息
#EXT-X-TARGETDURATION:11 //每个分片的目标时长
#EXT-X-MEDIA-SEQUENCE:0 //分片起始编号
#EXTINF:10.922578, //分片实际时长
test000.ts //分片文件
#EXTINF:9.929578, //第二个分片实际时长
test001.ts //第二个分片文件
...
```
这里截取了分片列表文件开头部分的内容,可以看出文件内容要么是以`#`字母开头,要么就是没有`#`字母。关于文件格式规范,[RFC8216 草案](https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4)第四节有详细的说明,你可以到那里查看详细的内容。
RFC8216 规定,.m3u8 文件内容以`#`字母开头的行是注释和 TAG其中 TAG 必须是`#EXT` 开头,如上面示例中的内容所示。
接下来我们对这几个TAG做一下说明
- `EXTM3U` 表示文件是第一个扩展的 M3U8 文件,此 TAG 必须放在索引文件的第一行。
- `EXT-X-VERSION: n` 表示索引文件支持的版本号,后面的数字 n 是版本号数字。需要注意的是,一个索引文件只能有一行版本号 TAG否则播放器会解析报错。
- `EXT-X-TARGETDURATION: s` 表示 .ts 切片的最大时长单位是秒s
- `EXT-X-MEDIA-SEQUENCE: number` 表示第一个 .ts 切片文件的编号。若不设置此项,就是默认从 0 开始的。
- `EXTINF: duration, title` 表示 .ts 文件的时长和文件名称。文件时长不能超过`#EXT-X-TARGETDURATION`中设置的最大时长,并且时长的单位应该采用浮点数来提高精度。
## TS 格式分析
TS 流最早应用于数字电视领域其格式非常复杂包含的配置信息表多达十几个。TS流中的视频格式是 MPEG2 TS ,格式标准是在 ISO-IEC 13818-1 中定义的。
苹果推出的 HLS 协议对 MPEG2 规范中的 TS 流做了精减,只保留了两个最基本的配置表 PAT 和 PMT再加上音视频数据流就形成了现在的HLS协议。也就是说 HLS 协议是由 PAT + PMT + TS数据流组成的。其中TS 数据中的视频数据采用 H264 编码,而音频数据采用 AAC/MP3 编码。TS数据流示意图如下所示
<img src="https://static001.geekbang.org/resource/image/21/3b/218e0c1907aa9454fc52f09971f72d3b.png" alt="">
我们再进一步细化TS 数据流由 TS Header 和 TS Payload 组成。其中TS Header 占 4 字节TS Payload 占 184 字节,即 TS数据流总长度是 188 字节。
TS Payload 又由 PES Header 和 PES Payload 组成。其中PES Payload 是真正的音视频流,也称为 ES 流。
- PESPacket Elementary Stream是将 ES 流增加 PES Header 后形成的数据包。
- ESElementary Stream中文可以翻译成**基流**,是编码后的音视频数据。
下面我们就来分析一下 TS 数据流的格式,如下图所示:
<img src="https://static001.geekbang.org/resource/image/da/b3/daa454df3de7549315e23c8c6aba90b3.png" alt="">
这是 TS Header 各个字段的详细说明,图中数字表示长度,如果数字后面带有 bytes ,单位就是 bytes否则单位都是 bit。
TS Header 分为 8 个字段,下面我们分别解释一下:
<img src="https://static001.geekbang.org/resource/image/69/d5/694380ee55bd65f07bc8add74d7cf6d5.png" alt="">
PES Packet 作为 TS 数据流的 Payload也有自己的 Header如下图所示
<img src="https://static001.geekbang.org/resource/image/53/6f/53b0c557047c1074cc649abe34159e6f.png" alt="">
下面我们就对这些常用的字段一一做下解释,当然也还有很多不常用的字段,我们这里就不列出来了,如有需求,可参考 ISO-IEC 13818-1 2.4.3.7 节。
PES Header 长度是 6 字节,字段说明如下:
<img src="https://static001.geekbang.org/resource/image/da/96/da31564a8c42e24a1b3538ccdf307e96.png" alt="">
另外PTSPresentation Tmestamp 字段总共包含了 40 bit高 4 个bit 固定取值是 0010剩下的 36 个 bit 分三部分分别是3 bit+1 bit 标记位15 bit+1 bit 标记位15 bit+1 bit 标记位。
通过以上的描述我们就将 HLS 协议中最重要的 TS数据流向你介绍清楚了。
## 小结
本文我们首先讲述了采用 HLS 协议的直播架构。实际上该直播架构与我们上文中介绍的直播架构是一致的,只不过我们这里为了强调 HLS 协议对之前的直播架构做了简化。同时,我们还通过该直播架构模型向你介绍了传统直播系统中,从用户推流到服务端切片、再到用户拉流的基本过程。
随后,我们借助 FFmpeg 工具向你讲解了如何将 MP4 文件转换成 HLS 文件,并向你展示了 .m3u8 文件的基本结构。最后还重点介绍了 TS 数据流的格式。
通过本文的学习我相信你应该已经对HLS协议有了非常清楚的认知了。实际上作为应用级的开发人员来说你并不需要了解到文中所介绍的那么细只需要对 HLS 协议有个基本的理解就可以了。因为目前有很多不错的开源库已经完成了大部分的工作,你只需要将这些开源库使用好即可。
比如在播放 HLS 流时,就有很多开源库可用。在移动端可以使用 Ijkplayer在浏览器上可以使用 video.js在PC端可以使用 VLC。而服务端的 HLS 切片则是由 CDN 网络完成的你只需要向CDN网络推流就可以了CDN网络会直接将上传的流进行HLS切片。而在 CDN 网络内部,它就是使用我们上面所介绍的 FFmpeg 开源库编译好的工具来完成切片工作的。
## 思考时间
每个 TS 格式数据包是 188 字节长,不够 188 字节就需要用 Padding 填充,那为什么要限制成 188 字节呢?
欢迎在留言区与我分享你的想法,也欢迎你在留言区记录你的思考过程。感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给更多的朋友。

View File

@@ -0,0 +1,90 @@
<audio id="audio" title="33 | FLV适合录制的多媒体格式" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/9f/5b/9f9a52a2ae6d56361692bfa23c8be15b.mp3"></audio>
虽然苹果拒绝使用 RTMP 协议并推出了自己的 HLS 技术,但大多数用户仍然还是使用 RTMP 协议作为传统直播系统的传输协议。在 Adobe 宣布不再对 RTMP技术进行支持的情况下仍然还有这么多用户在使用它说明 RTMP 协议具有其他协议不可比拟的优势。
这里我们做个对比,你就知道 RTMP 协议的优势在哪里了。
- 首先,与 HLS 技术相比RTMP协议在传输时延上要比 HLS 小得多。主要原因在于 HLS 是基于切片(几秒钟视频的小文件)、然后缓存的技术,这种技术从原理上就比直接进行数据传输慢很多,事实也证明了这一点。
- 其次,相对于 RTP 协议RTMP底层是基于 TCP 协议的,所以它不用考虑数据丢包、乱序、网络抖动等问题,极大地减少了开发人员的工作量;而使用 RTP 协议,网络质量的保障都需要自己来完成。
- 最后,与现在越来越火的 WebRTC 技术相比RTMP也有它自己的优势。虽然在实时传输方面 WebRTC 甩 RTMP 技术几条街但对于实时性要求并没有那么高的传统直播来说RTMP协议在音视频的服务质量上普遍要好于 WebRTC 的音视频服务质量。
这下你知道RTMP协议为什么会存活这么久了吧。那说了这么多RTMP协议与 FLV 又有什么关系呢实际上FLV文件与 RTMP 之间是“近亲”关系,甚至比“近亲”还要近,亲得就好像是“一个人”似的。
## FLV文件格式
我们先来看一下 FLV 的文件格式,如下图所示:
<img src="https://static001.geekbang.org/resource/image/4a/d9/4aad7046a08b2a4d6ca39963a46506d9.png" alt="">
这是我阅读[FLV 格式规范文档](https://www.adobe.com/content/dam/acom/en/devnet/flv/video_file_format_spec_v10.pdf)后,总结出的 FLV 文件格式结构图。从图中我们可以看出FLV 文件格式由 FLV Header 和 FLV Body 两部分组成。其中FLV Header 由 9 个字节组成Body由 Pre TagSize 和 Tag 组成。
为使你对它们有一个更清楚的认知,下面我们就来详细介绍一下 FLV Header 和 FLV Body。
### 1. FLV Header
它由 9 个字节组成3个字节的 “F”“L”“V”字母用于标记该文件是 FLV 文件1个字节的 Version指明使用的 FLV 文件格式的版本1个字节的 Type 标识用于表明该FLV文件中是否包括音频或视频4个字节的 FLV Header 长度,由于 FLV 文件格式头是固定 9 个字节,所以这个字段设置得有点多余。
Type 标识TypeFlag又可以细分为 1bit 用于标识FLV文件中是否有音频数据1bit标识FLV文件中是否有视频数据如果两个 bit 位同时置 1说明该FLV 文件中既有音频数据又有视频数据,这也是通常情况下 FLV Header 的设置;除了两个 bit 的音视频数据标识外其他位都是预留位必须全部置0。详细的含义可以参考下面张图表
<img src="https://static001.geekbang.org/resource/image/c7/5e/c71c4156528bf0cc59cc92d5e796a05e.png" alt="">
这张图表清晰地表达了 FLV Header 中每个域所占的字节以及该域的具体含义。另外,如果你使用的是 Windows 系统,就可以安装 **FlvAnalyzer** 工具,该工具是一款功能非常强大的 FLV 文件分析工具。使用它打开任何一个 FLV 文件,就可以看到该文件的 FLV 格式。
### 2. FLV Body
从“FLV文件格式结构图”我们可以看出FLV Body 是由多个 Previous TagSize 和 Tag 组成的。其含义如下图表所示其中PreviousTagSize 占 4个字节表示前一个 Tag 的大小。这里需要注意的是,第一个 Previous TagSize 比较特殊,由于它前面没有 Tag 数据,所以它的值必须为 0。
<img src="https://static001.geekbang.org/resource/image/d2/fd/d230f853c81c1dd185bddcb2edc2a9fd.png" alt="">
接下来我们再来看一下 FLV 中的 Tag从 FLV文件格式结构图中我们可以看到 Tag 由两部分组成,即 Tag Header 和 Tag Data 。
Tag Header 各字段的含义如下图所示:
<img src="https://static001.geekbang.org/resource/image/ab/79/ab30c1ae85a041f9262bc7b197c34779.png" alt="">
- TagType占1个字节表示该Tag的类型可以是音频、视频和脚本。如果类型为音频说明这个Tag存放的是音频数据如果类型是视频说明存放的是视频数据。
- DataSize占3个字节表示音频/视频数据的长度。
- Timestamp和扩展Timestamp一共占4个字节表示数据生成时的时间戳。
- StreamID占3个字节总是为 0。
而Tag Data 中存放的数据,是根据 TagType 中的类型不同而有所区别的。也就是说,假如 TagType 指定的是音频,那么 Tag Data中存放的就是音频数据如果 TagType 指定的是视频则Tag Data中存放的就是视频数据。
另外无论TagData中存放的是音频数据还是视频数据它们都是由 Header 和 Data 组成。也就是说,如果该 Tag 是一个音频 Tag 那么它的数据就是由“AudioHeader + AudioData”组成如果是一个视频 Tag则它的数据是由“VideoHeader + VideoData”组成。
特别有意思的一点是,如果你翻看[RTMP协议](http://wwwimages.adobe.com/www.adobe.com/content/dam/acom/en/devnet/rtmp/pdf/rtmp_specification_1.0.pdf),查看它的 6.1.1 小节,你会发现它定义的 RTMP Message Header 与 Tag Header 是一模一样的。下图是我从 RTMP 协议中截取的协议头:
<img src="https://static001.geekbang.org/resource/image/19/f7/19860afa57dd97d75902c59720f832f7.png" alt="">
因此,我们可以说**FLV文件就是由“FLV Header + RTMP数据”构成的**。这也揭开了 FLV与 RTMP之间的关系秘密**FLV 是在 RTMP 数据之上加了一层“马甲”**
## 为什么 FLV 适合录制
通过上面的描述你会发现,**FLV 文件是一个流式的文件格式**。该文件中的数据部分是由多个 “PreviousTagSize + Tag”组成的。这样的文件结构有一个天然的好处就是你可以将音视频数据随时添加到 FLV 文件的末尾,而不会破坏文件的整体结构。
在众多的媒体文件格式中,只有 FLV 具有这样的特点。像 MP4、MOV 等媒体文件格式都是结构化的也就是说音频数据与视频数据是单独存放的。当服务端接收到音视频数据后如果不通过MP4的文件头你根本就找不到音频或视频数据存放的位置。
正是由于 FLV 是流式的文件格式,所以它特别适合在音视频录制中使用。这里我们举个例子,在一个在线教育的场景中,老师进入教室后开启了录制功能,服务端收到信令后将接收到的音视数据写入到 FLV 文件。在上课期间老师是可以随时将录制关掉的老师关闭录制时FLV 文件也就结束了。当老师再次开启录制时,一个新的 FLV 文件被创建,然后将收到的音视频数据录制到新的 FLV 文件中。 这样当一节课结束后,可能会产生多个 FLV 文件,然后在收到课结束的消息后,录制程序需要将这几个 FLV 文件进行合并,由于 FLV 文件是基于流的格式所以合并起来也特别方便只需要按时间段将后面的FLV直接写到前面 FLV 文件的末尾即可。
使用 FLV 进行视频回放也特别方便,将生成好的 FLV直接推送到CDN云服务在CDN 云服务会将 FLV 文件转成 HLS切片这样用户就可以根据自己的终端选择使用 FLV 或 HLS 协议回放录制好的视频。
而对于回放实时性要求比较高的业务,还可以将 FLV 按 35 分钟进行切片,这样就可以在直播几分钟后看到录制好的内容了。
另外FLV 相较 MP4 等多媒体文件,它的文件头是固定的,音视频数据可以随着时间的推移随时写入到文件的末尾;而 MP4之类的文件文件头是随着数据的增长而增长的并且体积大处理时间长。因此 **FLV 文件相较于其他多媒体文件特别适合于在录制中使用**
## 小结
本文首先向你详细介绍了 FLV 文件格式,通过对该文件格式的讲解,你可以发现 FLV 与 RTMP 协议的关系十分密切,在 RTMP 数据之上加个 FLV 头就成了 FLV 文件。
在本文的后半段,我们还讲解了 FLV 用在服务端录制上的好处。由于它是基于流式的文件存储结构,可以随时将音视频数据写入到 FLV 文件的末尾且文件头不会因文件数据的大小而发生变化所以不管是在录制时还是进行回放时相较于MP4之类的多媒体格式FLV都更有优势。
虽然 FLV 协议比较简单,但如果我们自己去实现 FLV 格式分析的话,还是要做很多的工作。庆幸的是,目前已经有很多非常不错的处理 FLV 文件格式的开源库了,如 FLVParser。我们只需要拿来直接使用即可。同时也有很多不错的 FLV 分析工具,比如前面提及的 FlvAnalyzer 就是一款功能非常强大的 FLV 文件格式分析工具,通过这些工具可以让我们更快速地理解 FLV 协议。
RTMP 与 FLV 就像是同一个人,只不过穿的“衣服”不同而已,所以在 RTMP与 FLV 相互转换时也很容易。比如,我们想将 FLV 以 RTMP 方式进行推流就非常简单,而且还有一个专门的 RTMP 库,即 librtmp可以帮我们完成底层转换的处理逻辑。
## 思考时间
你知道有哪些有名的能够播放 FLV 格式文件的开源播放器吗?
欢迎在留言区与我分享你的想法,也欢迎你在留言区记录你的思考过程。感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给更多的朋友。

View File

@@ -0,0 +1,265 @@
<audio id="audio" title="34 | 如何使用Nginx搭建最简单的直播服务器" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/77/d5/77999540b90634a8cff0ecaae90833d5.mp3"></audio>
在前面三篇文章中我们介绍了传统直播系统架构、HLS协议、RTMP协议相关的知识那今天我们就来具体实操一下根据前面所学到的知识搭建出一套最简单的音视频直播系统。
今天我们要搭建的这套直播系统相较于[《31 | 一对多直播系统RTMP/HLS你该选哪个](https://time.geekbang.org/column/article/140181)一文中介绍的直播系统要简单得多。该系统不包括客户端、没有 CDN分发只包括最基本的推流、转发及拉流功能。虽然它简单了一点但麻雀虽小五脏俱全通过这样一个实战操作我们就可以将前面讲解的理论与实际结合到一起了。
当然,作为一个直播系统来说,客户端是必不可少的。但由于时间和篇幅的原因,我们只能借助一些现成的或者开源的客户端对我们的直播系统进行测试了,所以客户端的界面可能会简陋一些。也正因为如此,我才没有将它们算作咱们这个直播实验平台之中。
实际上,我们完全可以以这个直播系统实验平台为原型,逐步地将一些功能添加进去,这样很快就可以构建出一套商业可用的传统直播系统了。
## 直播系统架构
在正式开始实战之前,我们先来简要介绍一下这个直播系统的架构,如下图所示:
<img src="https://static001.geekbang.org/resource/image/68/e3/68f233981343fca6aac7557baa79b1e3.png" alt="">
这个直播架构非常简单,由两部分组成,即**媒体服务器**和**客户端**。
媒体服务器有两个功能:
- 推流功能,可以让客户端通过 RTMP 协议将音视频流推送到媒体服务器上;
- 拉流功能,可以让客户端从媒体服务器上拉取 RTMP/HLS 流。
实际上,这个架构与我们前面介绍的传统直播架构相比是有变化的,减少了信令服务器,同时将 CDN 网络变成了一台流媒体服务器。但理解了整个直播架构的原理后,我们就可以快速地将这个简单的直播架构恢复成一个正式的、可商用的直播系统。
那对于我们这个简化的直播系统来说,如何实现架构中的媒体服务器呢?
这里我们使用了目前最流行的 Nginx 来实现它。之所以选用 Nginx 主要出于以下两方面的原因:
- **Nginx的性能是很优越**。在众多的 Web 服务器中Nginx之所以能够脱颖而出就是因为它性能优越。阅读过 Nginx 代码的同学应该知道Nginx在异步IO事件处理上的优化几乎做到了出神入化的地步。其架构设计也极其巧妙不光如此它将所有使用到的库都重新再造对CPU、内存的优化都做到了极致。
- **Nginx 有现成的实现 RTMP 推拉流的模块**。只需要在Nginx安装一个 RTMP 处理模块,它就立马变成了我们想要的流媒体服务器了,操作非常简单。
接下来我们就具体实操一下,看看如何实现上面所说的流媒体服务器。
## 搭建流媒体服务端
在搭建直播平台之前我们首先要有一台Linux/Mac系统作为RTMP流媒体服务器的宿主机然后再按下列步骤来搭建实验环境。
从[Nginx](http://nginx.org)官方网站上下载最新发布的 Nginx 源码[nginx-1.17.4](http://nginx.org/en/download.html),地址如下:[http://nginx.org/download/nginx-1.17.4.tar.gz](http://nginx.org/download/nginx-1.17.4.tar.gz) 。我们可以使用 wget 命令将其下载下来,命令如下:
```
wget -c http://nginx.org/download/nginx-1.17.4.tar.gz
```
通过上面的命令就可以将 Nginx 源码下载下来了。当然,我们还需要将它进行解压缩。命令如下:
```
tar -zvxf nginx-1.17.4.tar.gz
```
要想搭建 RTMP 流媒体服务器,除了需要 Nginx 之外,还需要另外两个库,即 Nginx RTMP Module 和 OpenSSL。
### 1. RTMP Module
Nginx RTMP Module 是Nginx 的一个插件。它的功能非常强大:
- 支持 RTMP/HLS/MPEG-DASH 协议的音视频直播;
- 支持FLV/MP4文件的视频点播功能
- 支持以推拉流方式进行数据流的中继;
- 支持将音视频流录制成多个 FLV 文件;
- ……
Nginx RTMP Module 模块的源码地址为:[https://github.com/arut/nginx-rtmp-module.git](https://github.com/arut/nginx-rtmp-module.git) 。你可以使用下面的命令进行下载,存放的位置最好与 Nginx 源码目录并行。这样就可以将 Nginx RTMP Module 模块下载下来了。
```
git clone https://github.com/arut/nginx-rtmp-module.git
```
### 2. OpenSSL
OpenSSL 的功能和作用我们这里就不多讲了,在第一个模块的 [《22 | 如何保证数据传输的安全(下)?》](https://time.geekbang.org/column/article/131276)一文中我们已经对它做过介绍了,记不清的同学可以再去回顾一下。
对于我们的 RTMP 流媒体服务器来说,也需要 OpenSSL 的支持。因此,我们需要将 OpenSSL 的源码一并下载下来,以便在编译 Nginx 和 Nginx RTMP Module 时可以顺利编译通过。
OpenSSL的源码地址为[https://www.openssl.org/source/openssl-1.1.1.tar.gz](https://www.openssl.org/source/openssl-1.1.1.tar.gz) 有可能需要VPN。我们仍然使用 wget对其进行下载命令如下
```
wget -c https://www.openssl.org/source/openssl-1.1.1.tar.gz --no-check-certificate
```
通过上面的命令就可以将 OpenSSL源码下载下来了。同样我们也需要将下载后的 OpenSSL 进行解压缩,命令如下:
```
tar -zvxf openssl-1.1.1.tar.gz
```
### 3. 编译 OpenSSL &amp; Nginx
通过上面的描述,我们就将所有需要的源码都准备好了,下面我们开始编译 Nginx。在编译 Nginx 之前,我们首先要将 OpenSSL 库编译并安装好,其编译安装过程有如下两个步骤。
第一步,生在 Makefile 文件。
```
cd openssl-1.1.1
./config
```
第二步,编译并安装 OpenSSL 库。
```
make &amp;&amp; sudo make install
```
经过上面的步骤就将 OpenSSL 库安装好了。下面我们开始编译带 RTMP Module 功能的 Nginx只需要执行下面的命令即可命令执行完成后就会生成 Nginx 的 Makefile 文件。
```
cd nginx-1.17.4
./configure --prefix=/usr/local/nginx --add-module=../nginx-rtmp-module --with-http_ssl_module --with-debug
```
下面我们对这里的参数做个介绍。
- prefix指定将编译好的Nginx安装到哪个目录下。
- add-module指明在生成 Nginx Makefile 的同时,也将 nginx-rtmp-module 模块的编译命令添加到 Makefile 中。
- http_ssl_module指定 Ngnix 服务器支持 SSL 功能。
- with-debug输出debug信息由于我们要做的是实验环境所以输出点信息是没问题的。
**需要注意的是,在编译 Nginx 时可能还需要其他基础库,我们只需要根据执行 configure 命令时提示的信息将它们安装到宿主机上就好了。**
Nginx 的 Makefile 生成好后,我们就可以进行编译与安装了。具体命令如下:
```
make &amp;&amp; sudo make install
```
这里需要强调的是,上面命令中的 `&amp;&amp;` 符号表示前面的 Make 命令执行成功之后,才可以执行后面的命令;`sudo` 是指以 root 身份执行后面的 `make install` 命令。之所以要以root 身份安装编译好文件是因为我们安装的目录只有root才有权限访问。
当上面的命令执行完成后,编译好的 Nginx 将被安装到 `/usr/local/nginx` 目录下。此时,我们距离成功只差最后一步了。
### 4. 配置 Nginx
上面将 Nginx 安装好后,我们还需要对它进行配置,以实现 RTMP 流媒体服务器。Nginx 的配置文件在`/usr/local/nginx/conf/` 目录下配置文件为nginx.conf。在 nginx.conf 文件中增加以下配置信息:
```
...
events {
...
}
#RTMP 服务
rtmp {
server{
#指定服务端口
listen 1935; //RTMP协议使用的默认端口
chunk_size 4000; //RTMP分块大小
#指定RTMP流应用
application live //推送地址
{
live on; //打开直播流
allow play all;
}
#指定 HLS 流应用
application hls {
live on; //打开直播流
hls on; //打开 HLS
hls_path /tmp/hls;
}
}
}
http {
...
location /hls {
# Serve HLS fragments
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /tmp;
add_header Cache-Control no-cache;
}
...
}
...
```
我们来对上面的配置信息做一下简单的描述,整体上可以将这个配置文件分成三大块:
- events用来指明 Nginx 中最大的 worker 线程的数量;
- rtmp用来指明 RTMP 协议服务;
- http用来指明 HTTP/HTTPS 协议服务。
其中最关键的是 RTMP 协议的配置。我们可以看到,在 rtmp 域中定义了一个 server表明该Nginx可以提供 RTMP 服务,而在 server 中又包括了两个 application ,一个用于 RTMP 协议直播另一个用于HLS协议的直播。
通过上面的配置,我们就将 RTMP流媒体服务器配置好了。接下来我们可以通过下面的命令将配置好的流媒体服务器启动起来提供服务了
```
/usr/local/nginx/sbin/nginx
```
至此,我们的 RTMP 流媒体服务器就算搭建好了。我们可以在 Linux 系统下执行下面的命令来查看1935端口是否已经打开
```
netstat -ntpl | grep 1935
```
## 音视频共享与观看
RTMP 流媒体服务器配置好后,下面我们来看一下该如何向 RTMP 流媒体服务器推流又如何从该流媒体服器上拉RTMP流或 HLS 流。
### 1. 音视频共享
向 RTMP 服务器推流有好几种方式,最简单是可以使用 FFmpeg 工具向 RTMP流媒体服务器推流具体的命令如下
```
ffmpeg -re -i xxx.mp4 -c copy -f flv rtmp://IP/live/stream
```
通过这个命令就可以将一个多媒体文件推送给 RTMP 流媒体服务器了。下面我们来解释一下这命令中各参数的含义:
- -re代表按视频的帧率发送数据否则FFmpeg会按最高的速率发送数据
- -i ,表示输入文件;
- -c copy表示不对输入文件进行重新编码
- -f flv表示推流时按 RTMP 协议推流;
- rtmp://……,表示推流的地址。
另外,关于推流地址 `rtmp://IP/live/stream` 有三点需要你注意。
- 第一点,推流时一定要使用 `rtmp://` 开头,而不是 `http://`,有很多刚入门的同学在这块总容易出错。
- 第二点IP后面的子路径 live 是在 Nginx.conf 中配置的 application 名字。所以在Nginx 配置文件中你写的是什么名字,这里就写什么名字。
- 第三点live 子路径后面的 test 是可以任意填写的流名。你可以把它当作一个房间号来看待,在拉流时你要指定同样的名字才可以看到该节目。
### 2. 观看
RTMP 的推流工具介绍完后,现在我们再来看看如何观看推送的音视频流。你可以使用下面这几个工具来观看“节目”。
- Flash客户端。你可以在 IE 浏览器上使用下面的 Flash 客户端进行观看,地址为:[http://bbs.chinaffmpeg.com/1.swf](http://bbs.chinaffmpeg.com/1.swf) 。需要注意的是,目前最新的 Chrome 浏览器已经不支持 Flash 客户端了。
- VLC。在 PC 机上可以使用 VLC 播放器观看,操作步骤是点击右侧的`openmedia-&gt;网络-&gt;输入rtmp://IP/live/test`
- FFplay。它是由FFmpeg实现的播放器可以说是目前世界上最牛的播放器了。使用 FFplay 的具体命令如下:`ffplay rtmp://host/live/test`
当然拉流也是一样的,你也可以利用 librtmp库自己实现一个观看端。
## 小结
在本文我们全面介绍了如何通过 Nginx 搭建一套最简单的 RTMP/HLS 流媒体服务器。这套流媒体服务器功能还是很齐全的,基本上涵盖了传统流媒体服务器方方面面的知识。
我们还介绍了如何搭建流媒体服务器的原型,以及多种推流、拉流的测试工具。这些测试工具都极具价值,并且大多数都是开源项目,如果你能力比较强的话,可以直接阅读它们的代码。理解它们的代码实现,对于你实现自己的直播推/拉流工具有非常大的益处。
另一方面,虽然可以通过 Nginx 实现传统直播系统的原型,但如果你想将它转化为商业用途的话,那还有非常远的距离。因为商用的直播系统需要对流媒体服务器做各种性能优化,对它的宿主机的操作系统做优化,对它的传输网络做优化,等等。除此之外,一个商业的直播系统是由多个子系统构成的,如日志系统、计费系统、监控告警、质量检测系统、云存储系统、工单系统等。
通过本文你可以知道直播原理与实际应用之间的距离并不遥远。直播的原理搞清楚后,你只需要几个小时就可以将传统直播流媒体服务器的实验环境搭建出来。但应用与商用之间却还有一条鸿沟,这条鸿沟是由无数的细节构成的,而这些细节除了音视频直播的知识外,还有算法、操作系统、网络协议等方面的知识。所以说,知识是相通的,道路是曲折的。
## 思考时间
那除了 Nginx 之外,你还知道其他开源的 RTMP 流媒体服务器吗?
欢迎在留言区与我分享你的想法,也欢迎你在留言区记录你的思考过程。感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给更多的朋友。

View File

@@ -0,0 +1,128 @@
<audio id="audio" title="35 | 如何构建云端一对多直播系统?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/c7/9e/c7439b7f5f60c4fb5152d0af9610d09e.mp3"></audio>
在上一篇文章中,我们介绍了如何通过 Nginx 搭建传统直播系统的实验环境,知道了理论是如何与实际结合到一起的。但同时我们也提到,要想通过 Nginx 来实现商用的直播系统还有很长的路要走。
那么今天我们就来介绍一下快速构建商用直播系统的方案。要想实现真正商用的直播系统我们必须踩在巨人肩膀上这个“巨人”就是CDN网络。
可以这么说现在99%的传统直播系统都是在CDN网络的基础上搭建出来的。因此我们本文主要介绍的内容就是如何通过CDN网络实现商用的直播系统。我们平时所说的“万人直播”就是使用的这种技术。
## 万人直播的原理
我们首先来看一下万人直播架构图,如下所示:
<img src="https://static001.geekbang.org/resource/image/15/14/152d2b86619a8a35e28967043f0c5a14.png" alt="">
通过该图我们可以看到它与上一篇文章所介绍的直播架构是非常类拟的包括了共享端、观看端和CDN网络。唯一的变化就是把之前架构中的流媒体服务器换成了现在的 CDN 网络。
对于共享端和观看端来说,使用 CDN 网络与使用流媒体服务器没什么区别。它们都是将流推送给指定的流媒体服务器,然后从指定的播放地址拉流,并最终进行展示这样一个过程。
关于 CDN 网络的基本知识,我们在[《31 | 一对多直播系统RTMP/HLS你该选哪个](https://time.geekbang.org/column/article/140181)一文中已经向你做过详细介绍了,它是由源节点、主干结点和边缘节点组成。它们的具体作用我这里就不做赘述了,记不清的同学可以回顾一下那篇文章。
当共享端将媒体流分享到 CDN 网络后,媒体流在 CDN 内部的流转过程是十分复杂的。现在我们只要简单理解为CDN网络通过主干结点将流推送到各边缘节点观看端就近接入到CDN网的边缘节点就可以获取到它想观看的“节目”了。
观看端就近接入到 CDN 的边缘节点使得它与CDN节点之间能够使用最好的网络线路传输音视频数据从而达到非常好的音视频服务质量。
## CDN就近接入
CDN网络为了保证用户的音视频服务质量它会让观众在观看节目时就近接入到距离最近的CDN边缘节点。
咱们来举个例子,一名主播使用上海电信的网络分享它的“节目”,那么使用北京联通网络的观众想看该节目时,如何能得到更好的用户体验呢?换句话说,是观众从上海电信的节点上拉取到的音视频流的服务质量好呢,还是从北京联通的节点上拉取到的音视频流的服务质量好呢?我想答案是不言而喻的!
那么CDN 网络是如何让用户就近接入到北京联通的边缘节点上的呢?这里的关键点就是 DNS 解析。
### 1. DNS 解析原理
要想解开CDN让用户就近接入的秘密我们就必须要了解 DNS 的解析原理。下面这张图就是 DNS 解析原理的示意图:
<img src="https://static001.geekbang.org/resource/image/50/a9/504b7cbf63f82f616e4bc8e5baa767a9.png" alt="">
接下来,我们就对这张图做一下解析,看看 DNS 是如何工作的。假设你要访问 [learningrtc.cn](http://www.learningrtc.cn) 这个网址浏览器收到你的命令后会向本地DNS如 Google 的 DNS 服务器 8.8.8.8 的 53 端口TCP/UDP都使用同样的端口发送域名解析请求。
本地DNS收到该请求后首先查看自己的缓存中是否已经有该域名的记录了。如果有则直接返回该域名的 IP 地址。如果没有它会按下列步骤查询域名所对应的IP地址。
- 步骤1本地DNS向根域名发送查询请求。
- 步骤2根域名一般都会说“我不知道”但可以再问问 .net 顶级域名的服务地址。
- 步骤3本地DNS获取到 [.net](https://learning.net) 顶级域名服务地址后就向顶级域名服务发送查询域名IP地址的请求。
- 步骤4如果顶级域名也不知道要查询域名的IP地址的话它会给本地 DNS 返回二级域名服务地址。
通过这样的步骤经过本地DNS一级一级的查询最终才能确定浏览器访问域名的IP地址是多少。
### 2. CDN就近接入原理
当用户访问 CDN 网络时它首先通过智能DNS获取与用户距离最近的边缘节点如下图所示
<img src="https://static001.geekbang.org/resource/image/00/3d/002237a6925afbdab0d05660fae6693d.png" alt="">
那么智能 DNS 是如何找到距离用户最近的CDN边缘节点的呢
智能DNS与传统DNS解析略有不同。传统DNS不判断访问者来源当有多个IP地址可用时DNS会随机选择其中一个IP地址返回给访问者。而智能DNS会判断访问者的来源也就是说它会对访问者的IP地址做判断对不同的访问者它会返回不同的IP地址。智能DNS可以根据用户的IP地址找到它所在的地区、使用的运营商等。通过这些信息它就可以让访问者在访问服务的时候获得最优的CDN 边缘节点,从而提升服务的质量。
## CDN的使用
了解了上面的原理后,接下来我们就来具体实操一下,看看如何通过 CDN 网络实现万人直播。
### 1. 开通 CDN 业务
在使用CDN业务之前我们需要先在所使用的云服务厂商那里开通CDN服务。各云厂商开通CDN服务的步骤可能略有不同但大同小异我们只需要在云厂商提供的管理界面进行开通即可。
云厂商的CDN服务可以用于多种业务如图片加速、文件加速、点播、直播等等。而我们这里是将它用于直播系统所以在开通 CDN 的时候,一定要选对业务类型,这样它才能真正为我们提供服务。
另外一般情况下CDN业务开通后还不能直接使用还需要对账户进行充值。在管理界面生成推/拉流地址时,如果账户中没有钱,它就不给你产生推拉流地址。必竟天下没有免费的午餐,这一点是需要你特别注意的。
### 2. 申请域名
要想通过 CDN 来实现万人直播除了开通CDN业务之外还必须要有自己的域名。比如我们分享了一个直播间地址给观众观众只要访问该地址就可收听/观看我们的节目了,那这个地址一定是我们自己的域名构成的。
申请域名的方法非常简单你可以到任何一家云服务厂商去购买如阿里、腾讯、亚马逊或Google等等。域名的价格有高有低因为我们只是做实验使用所以购买最便宜的即可。
另外,需要注意的是,如果你是购买的国内域名,则还需要进行备案,域名在没有备案成功之前是无法使用的。备案的主要目的是为了确认你要通过该域名做些什么事情。
### 3. 域名解析
域名申请好并备案成功后接下来我们就可以对域名进行配置了。域名的配置主要分为两步一是对CDN直播域名的配置二是在域名管理中对前面配置好的直播域名进行映射。
首先,我们来看一下如何对 CDN 直播域名进行配置:打开直播控制台的域名管理,点击添加域名,在弹出的添加域名界面中,添加上我们的直播域名即可。比如我的域名为 learningrtc.cn因此我可以将直播域名写成 pushcdn.learningrtc.cn。
通过上面步骤添加好直播域名后,云厂商的直播管理系统会给我们返回真正的直播域名。比如我添加的 pushcdn.learningrtc.cn 直播域名,其实它真正的 CDN 域名地址为 pushcdn.learningrtc.cn.w.alikunlun.net。也就是说 pushcdn.learningrtc.cn 只不过是一个伪域名。
通过上面的描述我们可以知道,要想让用户访问在 pushcdn.learningrtc.cn 域名时转到CDN真正的直播域名pushcdn.learningrtc.cn.w.alikunlun.net那么我们还需要在云厂商的域名管理系统中配置一条 DNS CNAME 的记录。这样,在 DNS 解析时就会进行跳转,从而解析出真正提供服务的 IP 地址。
### 4. 生成推/拉流地址
域名配置好之后,我们就可能生成推/拉流地址了。在云厂商的直播管理界面中,专门有用于生成推/拉流地址的操作界面。
其操作非常简单,首先是选择要使用的直播域名,该域名就是我们上面配置好的域名,然后填入你的 Application 名字,最后是填入直播流(房间)名字。这些配置信息填好后,点击生成按钮,就可以生成直播的推流地址和拉流(播放)地址了。
通过上面的描述我们可以知道,在 CDN 网络的内部应该也是使用的 Nginx 或与 Nginx 相类似的流媒体服务器。因为从它的配置上看,与我们在上一篇文章中所介绍的配置是一致的。
下图是生成推流与拉流地址的拼接格式:
<img src="https://static001.geekbang.org/resource/image/ef/84/ef17751079eb90181246b786dc724e84.png" alt="">
### 5. 推流与播放
配置好CDN的推/拉流地址后,接下来我们就可以进行测试了。
相关的推流工具有 FFmpeg、OBS、自己通过 librtmp库实现的推流客户端等拉流工具包括 VLC、FFplay、Flash、flv.js 、video.js等。因为这些工具我们在上一篇文章中已经介绍过了所以这里就不再赘述了。
需要特别强调的是, OBS 这个推流工具是非常专业的推流工具并且各个操作系统上都有对应的OBS客户端。比如在实际的测试中我们可能会发现使用 FFmpeg 推流时,声音的质量非常差,但如果使用 OBS 工具推流的话,音质就非常好了。
## 小结
本文我们首先介绍了 CDN 实现万人直播的架构。通过该架构你会发现,它与我们上一篇文章介绍的架构没有什么区别,只是将 RTMP流媒体服务器换成了 CDN 网络而已,而这正是本质的差别。
之所以 CDN 网络可以支持万人直播,就是因为在 CDN 网络内部做了大量的工作如网络、操作系统、CPU内存等的优化。正是通过这点点滴滴的优化才能做到上万人、甚至几十万人同时在线观看节目的效果。
之后,我又详细向你介绍 DNS 解析原理以及 CDN 就近接入原理。了解 DNS 解析原理对于你理解 CDN是至关重要的只有对它理解清楚了之后你才能理解智能DNS而理解了智能 DNS后你才可以理解用户是如何实现就近接入 CDN 边缘节点的。
中国的互联网分家为北联通、南电信之后,网络环境越来越复杂。各大运营商之间、各地区之间的自我保护导致我们在跨运营商、跨地区传输数据时会遇到各种各样的网络状况,如主动丢包、延迟增大、传输抖动等等。解决这个问题的最好办法就是“就近接入”,让用户在自己地区所在的运营商接入网络,而跨运营商、跨地区的数据传输交由 CDN 网络使用专用网进行传输,这样就可以让用户享受到更好的音视频服务质量了。
在文章的最后,我们还讲述了如何开通 CDN 服务,以及如何使用 CDN 网络。这部分的知识可能有点琐碎但却是我们实践CDN网络必不可少的一步所以搞清楚这部分内容依然很重要。
## 思考时间
你知道配置DNS记录时它都有哪些类型吗各不同类型的含义是什么呢
欢迎在留言区与我分享你的想法,也欢迎你在留言区记录你的思考过程。感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给更多的朋友。

View File

@@ -0,0 +1,152 @@
<audio id="audio" title="36 | 如何使用 flv.js 播放 FLV 多媒体文件呢?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b3/51/b32bdc224d684d0b72a3d06649ac9551.mp3"></audio>
flv.js 是由 bilibili 公司开源的项目。它可以解析FLV文件从中取出音视频数据并转成 BMFF片段一种MP4格式然后交给HTML5 的`&lt;video&gt;`标签进行播放。通过这种方式,使得浏览器在不借助 Flash 的情况下也可以播放 FLV 文件了。
目前各大浏览器厂商默认都是禁止使用Flash插件的。之前常见的 Flash 直播方案,到现在已经遇到极大的挑战。因为它需要用户在浏览器上主动开启 Flash 选项之后才可以正常使用,这种用户体验是非常糟糕的,而 flv.js 的出现则彻底解决了这个问题。
flv.js 是由 JavaScript 语言开发的,该播放器的最大优势是,即使不安装 Flash 插件也可以在浏览器上播放 FLV 文件。虽说 Adobe 公司早已不再为 Flash 提供支持了,但 FLV 多媒体文件格式不会就此而消亡。因此,在没有 Flash 的时代里,能实现在浏览器上播放 FLV 文件就是 flv.js 项目的最大意义。
## flv.js 基本工作原理
flv.js 的工作原理非常简单,它首先将 FLV 文件转成 ISO BMFFMP4 片段)片段,然后通过浏览器的 Media Source Extensions 将 MP4 片段播放出来。具体的处理过程如下图所示:
<img src="https://static001.geekbang.org/resource/image/44/7e/44d56d4a72b5baa6fac37279b0e4e87e.png" alt="">
从上图我们可以看出flv.js 播放器首先通过 Fetch Stream Loader 模块从云端获取 FLV 数据;之后由 IO Controller 模块控制数据的加载;数据加载好后,调用 FLV Demux 将 FLV 文件进行解封装,得到音视频数据;最后,将音视频数据交由 MP4 Remux 模块重新对音视频数据封装成MP4格式。
将封装好的 MP4 片段交由浏览器的 Media Source Extensions 处理后最终我们就可以看到视频并听到声音了。所以总体来说flv.js 最主要的工作是做了媒体格式的转封装工作,具体的播放工作则是由浏览器来完成的。下面我们就对架构图中的每个模块分别做一下说明。
首先我们来看一下 flv.js 播放器,它包括以下四部分:
- Fetch Stream Loader指通过 URL 从互联网获取HTTP-FLV媒体流。其主要工作就是通过 HTTP 协议下载媒体数据,然后将下载后的数据交给 IO Controller。
- IO Controller ,一个控制模块,负责数据的加载、管理等工作。它会将接收到的数据传给 FLV Demux。
- FLV Demux ,主要的工作是去掉 FLV 文件头、TAG 头等,拿到 H264/AAC 的裸流。关于FLV文件格式你可以参考[《33 | FLV适合录制的多媒体格式》](https://time.geekbang.org/column/article/141743) 一文。
- MP4 Remux ,它的工作是将 H264/AAC 裸流加上 MP4 头,采用的多媒体格式协议是 BMFF。它会将封装好格式的文件传给浏览器的 Data Source 对象。
经过以上四步flv.js 就完成了自己的使命。
接下来的工作就是浏览器需要做的了,那我们再看一下浏览器各模块的主要作用。
- Data Source用来接收媒体数据的对象收到媒体数据后传给 Demux 模块。
- Demux解封装模块作用是去掉 MP4 头,获取 H264/AAC 裸流。
- Video Decoder视频解码模块将压缩的视频数据解码变成可显示的帧格式。
- Audio Decoder音频解码模块将压缩的音频数据解码变成可播放的格式。
- Video Renderer视频渲染模块用于显示视频。
- Audio Renderer音频播放模块用于播放音频。
- Video Display视频、图形显示设备的抽象对象。
- Sound Card声卡设备的抽象对象。
从上面的过程可以看出flv.js 主要的工作就是进行了 FLV 格式到 MP4 格式的转换。之所以这样,是因为 flv.js 是通过 HTML5 的 `&lt;video&gt;` 标签播放视频,而此标签支持的是 MP4 格式。
## 关于 `&lt;video&gt;` 标签
`&lt;video&gt;` 标签是 HTML5 新支持的元素,用于在浏览器中播放音视频流。实际上,我们在介绍 WebRTC 知识的时候就已经对它做过一些介绍了。今天我们再来简单重温一下这部分知识。
`&lt;video&gt;` 标签的使用与其他HTML5标签的使用是一样的我们这里就不多讲了主要讲一下`&lt;video&gt;`标签支持的几个属性:
- autoplay如果设置该属性则视频在就绪后就马上播放。
- src要播放的视频的 URL。
- srcObject用于播放实时音视频流该属性与 src 互斥。
- ……
`&lt;video&gt;`标签支持的属性比较多,你可以查看文末参考一节,那里将`&lt;video&gt;`标签的所有属性都做了详细说明。关于这些属性,我建议你写一个简单的页面分别测试一下,以加深你对它们的认知。
另外,对于该标签的测试有两点是需要你注意的:
- 媒体文件建议用 MP4 文件,你系统的本地文件就可以用来测试`&lt;video&gt;`标签。
- 在测试 autoplay 属性时候,如果发现没有效果,建议加上 muted 属性,浏览器保证静音状态是能 autoplay 的。
## 使用 flv.js
首先,我们需要将 flv.js 源码下载下来。主要有两种方式:一种是通过 `git clone` 命令从 GitHub 上拉取最新的代码;另一种是通过 NPM 命令从资源库中获取 flv.js 源码。这里我们采用的是第二种方式,具体命令如下:
```
npm install --save flv.js
```
源码下载下来后,我们还要将 flv.js 的依赖包下载下来,通过 gulp 工具将 flv.js 进行打包、压缩。具体步骤如下:
```
npm install # 安装 flv.js 依赖的包
npm install -g gulp # 安装 gulp 构建工具
gulp release # 打包、压缩 工程 js 文件
```
其中第1条命令的作用是下载 flv.js 的依赖包第2条命令是安装JavaScript打包工具gulp第3条命令是使用 gulp 对 flv.js 进行构建。
需要注意的是,在执行 `gulp release`命令 时,有可能会遇到如下的错误:
```
gulp release[4228]: src\node_contextify.cc:633: Assertion `args[1]-&gt;IsString()' failed.
```
这时可以通过安装 Node.js 的 natives 模块来解决该问题,安装 natives 的命令如下:
```
npm install natives
```
通过上面的步骤,我们就可以构建出 flv.min.js 文件了。 该文件就是我们在项目中需要引用的flv.js文件。
接下来,我们就展示一下该如何使用 flv.js。你可以在本地创建一个 HTML 文件如play_flv.html其代码如下
```
&lt;!-- 引入 flv.js 库 --&gt;
&lt;script src=&quot;flv.min.js&quot;&gt;&lt;/script&gt;
&lt;!-- 设置 video 标签 --&gt;
&lt;video id=&quot;flv_file&quot; controls autoplay&gt;
You Browser doesn't support video tag
&lt;/video&gt;
&lt;script&gt;
//通过 JavaScript 脚本创建 FLV Player
if (flvjs.isSupported()) {
var videoElement = document.getElementById('flv_file');
var flvPlayer = flvjs.createPlayer({
type: 'flv',
url: 'http://localhost:8000/live/test.flv'
});
flvPlayer.attachMediaElement(videoElement);
flvPlayer.load();
flvPlayer.play();
}
&lt;/script&gt;
```
上面代码的逻辑非常简单,它主要做了三件事:第一是在 HTML 中引入了 flv.js 库;第二是设置了一个 `&lt;video&gt;` 标签,用于播放 FLV 文件;第三是一段 JavaScript 代码片段,用于创建 FLV Player 对象,并将这与上面定义的`&lt;video&gt;`绑定到一起,实现对 `&lt;video&gt;`标签的控制。
flv.js 的实现架构以及暴露的 API 接口在 flv.js/docs 目录下面有一个简单的介绍你可以参考一下。flv.js 暴露的接口不多但由于没有暴露接口的任何文档所以你只能直接查看源码去了解每个接口的具体作用与含义。幸运的是flv.js 架构设计得非常合理,代码实现也非常优秀,所以对于 JavaScript 开发人员来说,查看 flv.js 源码不是太难的事儿。
## 小结
本文我们首先介绍了 flv.js 播放器的基本工作原理,通过该原理我们可以知道,它主要的工作就是将 FLV 文件格式转换成 MP4 文件格式。
紧接着我们通过一个例子向你介绍了如何使用 flv.js 。flv.js 的使用还是比较简单的,你只要按文中的步骤就可以很快写出一个利用 flv.js 播放FLV文件的 Demo 出来。
另外需要注意的是,我们本文对 flv.js 的测试需要用于流媒体服务器,而流媒体服务器的搭建我们在前两篇文件中已经向你做过介绍了。你既可以使用 CDN 搭建流媒体服务器,也可以使用 Nginx 在自己的本机搭建流媒体服器。其具体过程这里我就不再重复了。
至于推流工具前面的文章中我们也详细介绍过你可以任选FFmpeg或OBS作为你的推流工作具体的操作方式请参考前面的文章。
总体来说flv.js 是一个非常优秀的 Web 开源播放器,它的代码写得非常漂亮,可读性也高。但它的文档相对匮乏,所以我们在开发过程中如果遇到一些问题,建议直接查看其源码。
从本专栏的第三个模块开始到现在我们已经介绍了 FLV 文件格式、HLS 协议、流媒体服务器,今天我们又学习了 flv.js 播放器,至此我们就可以用 CDN 做流媒体服务转发、用 OBS 进行推流、用flv.js播放构建出自己的直播系统了。
## 思考时间
今天留给你的思考题是flv.js 中用到的 BMFF 格式是什么样的呢?
欢迎在留言区与我分享你的想法,也欢迎你在留言区记录你的思考过程。感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给更多的朋友。
## 参考
`&lt;video&gt;` 标签属性
<img src="https://static001.geekbang.org/resource/image/fa/a7/face9d5cd0c417686e19eee1034a77a7.png" alt="">

View File

@@ -0,0 +1,194 @@
<audio id="audio" title="37 | 如何使用 video.js 播放多媒体文件?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/8d/d2/8d311e676eb37dbd912f6996262991d2.mp3"></audio>
在上一篇文章中,我们介绍了 flv.js 播放器。那今天我们再来介绍另一款非常有名的 JavaScript 播放器——video.js。
我们首先来比较一下这两款播放器看看它们之间有什么不同在我看来flv.js 更聚焦在多媒体格式方面,其主要是将 FLV 格式转换为 MP4 格式,而对于播放器的音量控制、进度条、菜单等 UI 展示部分没有做特别的处理。而 video.js 对音量控制、进度条、菜单等 UI 相关逻辑做了统一处理,对媒体播放部分设计了一个插件框架,可以集成不同媒体格式的播放器进去。所以**相比较而言video.js 更像是一款完整的播放器**。
video.js 对大多数的浏览器做了兼容。它设计了自己的播放器 UI接管了浏览器默认的`&lt;video&gt;`标签,提供了统一的 HTML5/CSS 皮肤。因此,通过 video.js 实现的播放器,在大多数浏览器上运行时都是统一的风格和操作样式,这极大地提高了我们的开发效率。
除了上面介绍的特点外video.js 还有以下优势:
- 开源、免费的。不管你是学习、研究还是产品应用video.js都是不错的选择。
- 轻量。浏览器 UI 的展现全部是通过 HTML5/CSS 完成,没有图片的依赖。
- 完善的 API 接口文档,让你容易理解和使用。
- 统一的 UI 设计,不管在哪个浏览器,你都看不出任何差异。
- 皮肤可以任意更换,很灵活。
- 开放灵活的插件式设计,让你可以集成各种媒体格式的播放器。
- 支持多种文字语言,如中文、英文等。
既然有这么多优势那接下来我们就来详细讲解一下video.js 的相关内容吧。
## video.js 的架构
HTML5 为媒体播放新增了很多新的元素,比如`&lt;audio&gt;``&lt;video&gt;``&lt;source&gt;`等,这些内置标签基本上可以满足我们日常的需求。而 video.js 把这些组件统统都实现了一遍,其主要目的是为了适配不同浏览器的差异,为各浏览器提供统一的 UI 展示和个性化定制。
接下来,我们来看看 video.js 都包含了哪些主要组件,如下图所示:
<img src="https://static001.geekbang.org/resource/image/a1/fc/a1bce32b2e8d47b6a13214cda9d5fdfc.png" alt="">
通过该图可以看到video.js主要包括**对多种文字语言支持、CSS 样式定制、控件部分、媒体内嵌元素部分**和**外部插件**五大部分。下面我们来简要介绍下这每一部分的相关信息。
**第一部分是Language**。它在 video.js/language 目录下面,支持多种文字语言切换。
**第二部分是 CSS 样式**。video.js 的 CSS 样式是可以更换的,支持个性化定制。
**第三部分是Component**。Component 是 video.js 中 UI 控件的抽象类在Component中封装了 HTML 元素。Control Bar、Menu、Slider、Tech 都是继承自 Component叫做子组件子组件也可以有子组件这样就形成了一棵树。这样设计的目的就是将播放器相关控件模拟成 DOM 树模型。下面是子组件的功能:
- Control Bar播放器的控制模块。调节音量、播放进度拖动等都由该模块完成。
- Menu播放器右键菜单的实现。
- Slider滚动条控件。可以是垂直滚动条也可以是水平滚动条。音量滚动条、进度滚动条都是它的子类。
- Tech是 Technique 的缩写表示采用的播放器技术。其实它就是为播放器插件提供抽象接口。video.js 默认使用的是 HTML5 播放器。
**第四部分是EventTarget**。HTML5 除了提供了 `&lt;audio&gt;``&lt;video&gt;``&lt;source&gt;`这些可见元素,还包括了 Media Source 的概念。像 AudioTrack、VideoTrack、TextTrack、Track 都继承自 Media Sourcevideo.js 把它们也都进行了抽象,这些对象都统一实现了 EventTarget 接口。这几个 Track 的作用如下:
- AudioTrack音频轨也是音频媒体源。
- VideoTrack视频轨也是视频媒体源。
- TextTrack文字轨也是文字媒体源。比如给视频添加字幕就可以使用它对应于 `&lt;track&gt;` 标签。
- Track ,媒体源的公共抽象。你一定要将它与 `&lt;track&gt;` 区分开来,这两个不是同一回事。这一点需要你注意一下。
**第五部分是插件**。video.js 支持播放器插件开发目前已经有很多插件实现了。在上图中我们只列举了3个插件
- HTTP Streaming可以播放 HLS协议、DASH协议的媒体流。
- Flash用于播放 RTMP 媒体流。但目前各大浏览器默认都是禁止使用Flash的。经测试Chrome浏览器和 IE 新版本浏览器都已不能使用 Flash 播放 RTMP流了。
- YouTube是企业定制插件。
## video.js 安装部署
[video.js 的文档](https://docs.videojs.com)非常详细包括了安装、部署、API 说明、FAQ 等,只要按照文档手册的步骤进行安装部署就可以了。使用 video.js 主要有以下三种方式。
**第一种方式,通过源码安装部署。**
首先,从 GitHub 下载源码。命令如下:
```
git clone https://github.com/videojs/video.js.git
```
然后,安装 video.js 依赖的文件。命令如下:
```
cd video.js
npm install
```
最后,构建 video.js。运行命令如下
```
npm run-script build
```
通过以上三步,在 video.js/dist 目录下面就会生成 video.min.js、video.min.css 、语言、字体等相关文件,你只需要将它们引入到你的 JavaScript 工程中即可。
**第二种方式,从 npm 仓库获取。**
```
npm install video.js
```
**第三种方式,通过 CDN 直接下载官方构建好的文件。**
```
&lt;link href=&quot;https://unpkg.com/video.js/dist/video-js.min.css&quot; rel=&quot;stylesheet&quot;&gt;
&lt;script src=&quot;https://unpkg.com/video.js/dist/video.min.js&quot;&gt;&lt;/script&gt;
```
总之你可以根据自己的喜好选择其中一种方式使用video.js。这里需要说明一下本文后续的例子都是采用的第三种方式。
## video.js 播放 MP4
接下来我们就来实战一下,使用 video.js 播放一个本地 MP4 文件,具体代码如下:
```
//引入video.js库
&lt;link href=&quot;https://unpkg.com/video.js/dist/video-js.min.css&quot; rel=&quot;stylesheet&quot;&gt;
&lt;script src=&quot;https://unpkg.com/video.js/dist/video.min.js&quot;&gt;&lt;/script&gt;
//使用 video 标签描述MP4文件
&lt;video
id=&quot;local_mp4&quot;
class=&quot;video-js&quot;
controls
preload=&quot;auto&quot;
poster=&quot;//vjs.zencdn.net/v/oceans.png&quot;
data-setup='{}'&gt;
&lt;source src=&quot;d:/test.mp4&quot; type=&quot;video/mp4&quot;&gt;&lt;/source&gt;
&lt;/video&gt;
```
下面我们来对上面的代码片段做下简要说明:
- `&lt;link&gt;` 标签,从 CDN 获取 video.js 的 CSS 文件。
- `&lt;script&gt;` 标签,从 CDN 获取 video.js 文件。注意,这种方式获取的是最新发布的版本。
- `&lt;video&gt;` 标签,对于 video.js 来讲,需要设置 id 属性。
- data-setup 属性,是 video.js 特有的,此标签用于播放器的自动加载。这里我们的代码中传入的是一个空的 json 对象。你也可以参考[官网文档选项参数](https://docs.videojs.com/tutorial-options.html),按照需求传入配置参数对象。
- `&lt;source&gt;` 标签,指定播放 URL 和媒体类型。我们的代码中指定的是本地磁盘 MP4 文件,`test.mp4` 是预先准备好的文件,类型是 `video/mp4`
我们可以预先准备好一个 MP4 文件, 然后再将上面的代码片段拷贝在一个 HTML 文件中,比如`play_local_mp4.html`,最后在浏览器中打开,就可以看到播放画面了。
## 本地流媒体服务器搭建
接下来,我们再来介绍一下如何使用 video.js 播放 HLS。对于推流工具我们可以使用 FFmpeg 或 OBS进行推流对于这些工具体的使用我在前面的文章中都有向你做过详细介绍所以这里就不再赘述了。
对于流媒体服务器,你可以使用 Nginx 在你的本机上搭建流媒体服务器,也可以通过前面文章所介绍的 CDN 网络做为流媒体服务器。对于实验来说,它们都是可行的方案。
对于流媒体服务器的搭建的过程,前面我们已经通过两篇文章做了详细介绍。这里同样也不再进行赘述了。
## video.js 播放 HLS
在使用 video.js 播放 HLS 媒体流之前,我们需要先创建一个 HTML5 文件,如 play_hls.html在 HTML5 文件中的内容如下:
```
//引入 video.js 库
&lt;link href=&quot;https://unpkg.com/video.js/dist/video-js.min.css&quot; rel=&quot;stylesheet&quot;&gt;
&lt;script src=&quot;https://unpkg.com/video.js/dist/video.min.js&quot;&gt;&lt;/script&gt;
//设置video标签
&lt;video id=&quot;my-hls-player&quot; class=&quot;video-js&quot;&gt;
&lt;source src=&quot;http://localhost:8000/live/test/index.m3u8&quot; type=&quot;application/x-mpegURL&quot;&gt;&lt;/source&gt;
&lt;/video&gt;
&lt;script&gt;
//创建 HLS 播放器实例
var player = videojs('my-hls-player', {
controls:true,
autoplay:true,
preload:'auto'
});
player.ready(function(){
console.log('my-hls-player ready...');
});
&lt;/script&gt;
```
video.js 的官方文档对如何使用 video.js 描述得非常详细,你可以自行查阅使用手册。这里我们只简要分析一下代码片段中用到的接口:
- 从官方指定的 CDN 获取 video.min.js 和 video-js.min.css 文件。需要注意的是,从 video.js 7 开始HLS 插件默认包含在 video.js 里了。此时,我们就不需要再单独引入 HLS 插件了。
- `&lt;video&gt;` 标签中需要指定 ID这个ID是可以随便起的我们这里设置的是 my-hls-player。 `&lt;video&gt;`标签的 CSS 样式采用的是官方提供的默认样式 video-js。当然你也可以定制自己喜欢的样式。
- `&lt;source&gt;` 标签中的 src 属性,指定了 m3u8 播放地址。我们这里设置的地址是 [http://localhost:8000/live/test/index.m3u8](http://localhost:8000/live/test/index.m3u8) 。需要注意的是type 属性必须是application/x-mpegURL。
- 在代码的最后实现了player 实例的 ready 回调函数口,这样当播放器加载完成后触发 ready事件时player的ready函数就会被调用。
现在我们就来测试一下吧。通过 FFmpeg 工具向地址 rtmp://IP/live/test推流而 HLS 协议媒体流的播放地址为 [http://IP](http://IP):port/live/test/index.m3u8再通过浏览器打开 play_hls.html页面就可以看到 HLS协议的画面了。
## 小结
本文我们首先对 video.js 的架构做了简要介绍,了解了 video.js 的几个重要组成模块,以及这些模块的主要作用。 紧接着,我们展示了如何通过 video.js 播放本地 MP4 文件。最后,我们还介绍了如何通过 video.js 播放 HLS 直播流。
可以说 video.js 是目前在浏览器上最好用、最著名的开源流媒体播放器。它的功能非常强大既可以处理多种多媒体格式如MP4、FLV等又可以支持多种传输协议如 HLS、 RTMP。因此它是播放音视频直播媒体流必不可少的播放工具。
在本文中,我们为了简单,只在本地建了一个使用 video.js 的Demo。但在真实的直播系统中我们应该实现一个直播客户端在直播客户端中引入video.js 来播放直播流。此外,该直播客户端还应该通过 WWW 服务发布。当用户想进入房间观看节目时,首先从 WWW 服务上下载客户端在浏览器将其加载后通过信令服务获取到直播地址并最终拉取直播流进行展示。这样就将video.js播放器与我们前面讲的万人直播系统组合到一起最终实现可商用的直播系统了。
## 思考时间
今天留给你的思考题是video.js 是如何通过 data-setup 属性完成自动加载的呢?
欢迎在留言区与我分享你的想法,也欢迎你在留言区记录你的思考过程。感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给更多的朋友。

View File

@@ -0,0 +1,95 @@
<audio id="audio" title="38 | 实战推演:带你实现一个支持万人同时在线的直播系统" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/55/5e/5587c617bc93dd7c6d54ce5744fc3b5e.mp3"></audio>
本文我们将第三模块所讲的知识做一次梳理,让你在整体上了解“万人直播”到底是怎样实现的。我们将从万人直播的整体架构、主播客户端架构、观众客户端架构和流量统计这四个方面向你讲述万人直播的构建。
通过这几个方面的介绍,我想你就知道了一个真正的“万人直播”是如何构建起来的。
## 万人直播架构
下面这张万人直播架构图与[《31 | 一对多直播系统RTMP/HLS你该选哪个](https://time.geekbang.org/column/article/140181)一文中介绍的直播架构图很类似,它们之间最大的不同在于真正的万人直播系统中并不会只使用一家 CDN网络而是接入多家 CDN网络。在使用它们时你可以按照一定的比例将“节目”分配到不同的CDN网络上。
<img src="https://static001.geekbang.org/resource/image/4e/bf/4e52d2ea1567a9821e1188f7bc6befbf.png" alt="">
多家 CDN 网络的管理一般是由信令服务器控制的。当要接入某家CDN网络时你可以通过信令服务器的注册界面进行注册。在注册界面中一般要求填入CDN厂商的名字、分配比例、CDN API操作地址等信息。
比如当某个主播要分享一个节目时信令服务器首先根据分配比例决定使用哪个CDN网络然后获得该CDN网络的API操作地址。获取到 CDN 网络的API操作地址后它就可以调用该CDN网络提供的 HTTP/HTTPS API 进行操作了创建域名、生成直播地址、生成拉流地址等等。有了这些地址后就可以将音视频流推送到CDN网络观众端获取到观看地址后就可以“观看”节目了。
除了接入多家 CDN 网络之外,其他功能与我们之前文章中介绍的是一样的,这里我就不再赘述了。
## 主播客户端架构
了解了万人直播架构后,接下来我们再看一下主播端的架构。之前我们介绍过,如果在 PC 端进行直播的话直接使用OBS工具就可以了这个工具是一个非常专业的 PC 端推流工具,功能非常强大,很多主播都使用该工具进行推流。
但如果是移动端的话,就需要我们自己来实现了。主播端的架构如下图所示,按层级分为三层,即**传输层、编码与展示层**和**采集层**。
<img src="https://static001.geekbang.org/resource/image/5e/b6/5e0f7b59e401a1939ce3a618082e55b6.png" alt="">
下面我们对各层级的模块分别做下说明。
**传输层**包括了信令处理和RTMP数据传输。信令处理与业务逻辑关系紧密比如创建房间、获得推流地址、聊天、送礼物等都属于信令处理的范畴。RTMP 数据传输就更简单了,当从信令模块获取到推流地址后,就可以将编码后的音视频数据通过 RTMP 协议推送给 CDN 网络了。
**编码与展示层**,该层包括的功能比较多,包括音频检测、音频编码、视频编码、视频预览以及视频美颜。接下来,我们再对这每个功能模块做一下说明。
- 音频检测,主要用于在直播开始前检测音频设备是否可用。
- 音频编码用于将音频采集模块采集的PCM数据进行压缩编码。常见的音频编码有 AAC、MP3、Opus等。一般情况下使用 AAC 编码方式。另外,编码也分为两种,一种是软编,一种是硬编。所谓软编,就是指通过 CPU 执行压缩算法进行编码会对CPU造成很大损耗硬编是指通过 GPU 或固定的集成电路进行数据的压缩它可以将CPU资源节省出来。
- 视频编码用于将视频采集模块采集的YUV数据进行压缩编码。常见的视频编码有 H264/H265、VP8/VP9等。大多数情况下我们都使用 H264 对视频进行编码。视频编码同样分为软编和硬编。在移动端一般我建议使用硬件编码这样可以大大节省CPU资源。
- 视频预览,将采集的视频展示出来,以便让主播看到从移动端采集到的视频数据的效果。由于视频帧每秒要刷新很多次,并且视频的数据都比较大,所以在处理视频渲染时如果没有处理好,会引起性能问题,如手机反应迟顿、发烫等问题。一般情况下,我们都会使用 OpenGL ES/ Metal 对视频渲染进行加速,从而解决反应迟顿、发烫等问题。
- 视频美颜,美颜目前可以说是娱乐直播的必备功能,尤其是对于美女主播就显得更为重要。如美白、瘦脸、长腿等这些功能是美女主播最喜欢用的功能。这种特效通常也是通过 OpenGL/Metal 来实现的。当然随着技术的发展现在一些AR技术也逐渐应用于各种视频效果中如雨滴、飘雪等等。
**采集层**,包括音频采集和视频采集。这两个功能模块非常简单,一个用于采集音频数据,另一个用于采集视频数据。它们都是与硬件打交道。
除了上面这些内容外在主播端还会有数据统计模块。比如什么时候创建房间、什么时候开始推流、推流时音视频的码率是多少等等这些信息都是由数据统计模块记录的。有了这个模块之后一是便于我们分析问题二是便于我们进行计费核算在与各CDN厂商结算费用时会非常有用。
## 观众端架构
介绍完主播端的功能后,接下来我们再来看看观众端(如下图)。观众端与主播端是很类似的,但实际上我们自己并不会将每个模块都亲手实现,因为对于观众端来说,大部分模块都属于播放器的功能。因此,我们在实现观众端的时候,只需要实现信令处理模块、数据统计模块,并将播放器集成进来就可以了。
<img src="https://static001.geekbang.org/resource/image/9e/f8/9eb7c783f2ef7ae9158148d34819e1f8.png" alt="">
对于移动端我们一般都使用Ijkplayer作为观众端播放器。 Ijkplayer是由bilibili公司开源的项目地址为[https://github.com/bilibili/ijkplayer.git](https://github.com/bilibili/ijkplayer.git) 。它既可以用于 Android 端又可以用于 iOS端使用起来非常方便。
实际上Ijkplayer就是 FFmpeg 中 FFplay 的变种它们的基本逻辑是一致的只不过Ijkplayer 更容易移植到移动端上。另外作为 iOS 端,它自带的 player 非常成熟,你也可以直接使用它自己的 player 来实现观众端。
在 PC 端你可以集成 VLC它与Ijkplayer一样也是非常优秀的一款播放器。该播放器的集成也非常简单你在网上可以找到非常多的资料所以这里我就不对它做更多的介绍了。
当然Web端也是必不可少的你可以使用我们前面文章中介绍的 flv.js 或 video.js 作为Web 端的播放器就可以了。
## 流量统计
下面我们再简要介绍一下流量统计。当在我们的直播系统中接入 CDN 网络时一个非常重要的工作就是费用结算。一般情况下都是按流量进行结算的。也就是说CDN厂商会根据我们或我们的用户使用了多少CDN流量进行费用的结算所以我们一定要知道自己用了多少流量。
另一方面,除了流量,我们还要监控我们的服务质量,比如是否出现了卡顿?每小时卡顿了多少次?引起卡顿的原因是什么?是否发生过分辨率切换?缓冲区还有多少数据没有播放出来?这些都与服务质量有着密切的关系。
那么接下来我们就来讲讲该如何采集数据,以达到监控服务质量和费用结算的目的。根据我的实际经验,以下信息是你在每个用户使用 CDN 网络时必须要记录下来的信息:
- 视频分辨率、帧率、码率相关信息
- 开启播放时间
- 关闭播放时间
- 暂停播放时间
- 恢复播放时间
- 缓冲区为0的时间
- 拖放时间以及拖放到的时间
- 分辨率切换的时间
- ……
通过以上这些信息你就可以将用户使用的流量精确地计算出来了这样再与CDN厂商核对数据时你心里就有谱了。
举个例子,当我们想统计一个用户使用了多少流量时,最简单的公式是(关闭播放时间-开启播放时间)* 码率,通过这个公式,你就可以计算出结果了;如果中间有暂停,则计算公式就变成了((暂停播放时间-开启播放时间)+ (关闭播放时间-恢复播放时间)* 码率;如果是多次暂停和恢复,那公式就更加复杂了,你需要按照上面的方法一步一步去推导就可以了。
另外,由于统计数据是一个特别精细的活儿,把这件事儿做好不容易,而且又涉及到费用问题,所以在后端统计时一定要细致,否则当一个节目观看人数众多时,就很容易出现比较大的偏差。
通过上面的介绍,我想在你脑海中一定有了一个统计流量的雏形了,你可以在这个雏形的基础上不断完善你的算法,就可以实现商用的统计系统了。
## 小结
本文我们从四个方面向你全面介绍了传统直播系统是如何实现的。首先讲解了传统直播系统的架构该架构与我们前面介绍的架构是类似的最重要的差别是可以接入多个CDN网络。在真正商用的系统中这个CDN网络都是按一定比例分配资源当有某个CDN网络出现问题时还可以进行CDN网络的切换。然后我们又详细阐述了主播客户端与观众客户端是如何实现的。最后还讲述了流量统计模块因为它关系到统计计费与服务质量所以是直播系统中必不可少的一个模块。
目前国内的CDN厂商特别多如阿里、腾讯、金山等当然也还有很多老牌的CDN厂商如蓝汛、网宿等它们的质量具体如何还需要你多进行测试。从我个人经验来讲阿里无论价格还是质量都还不错。
## 思考时间
今天留给你的思考题:如何判断用户在播放音视频流时是否出现过卡顿?
欢迎在留言区与我分享你的想法,也欢迎你在留言区记录你的思考过程。感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给更多的朋友。