视频技术详解:RTMP H5 直播流技术解析

本文聚焦 RTMP 协议的最精华的内容,接进行实际操做 Buffer 的练习和协议的学习。
RTMP 是什么
RTMP 全称便是 Real-Time Messaging Protocol。顾名思义就是用来做为实时通讯的一种协议。该协议是 Adobe 搞出来的。主要是用来传递音视频流的。它经过一种自定义的协议,来完成对指定直播流的播放和相关的操做。和现行的直播流相比,RTMP 主要的特色就是高效,这里,我就很少费口舌了。咱们先来了解一下 RTMP 是如何进行握手的。
RTMP 握手
RTMP 是基于 TCP 三次握手以后的,因此,RTMP 不是和 TCP 一个 level 的。它自己是基于 TCP 的可靠性链接。RTMP 握手的方式如图:算法

(C 表明 Client,S 表明 Server)
它主要是经过两端的字段内容协商,来完成可信度认证的。基本过程以下:
client: 客户端须要发 3 个包。C0,C1,C2
server: 服务端也须要发一样 3 个包。 S0,S1,S2。
整个过程如上图所述,但实际上有些细节须要注意。
握手开始:
【1】 客户端发送 C0,C1 包
此时,客户端处于等待状态。客户端有两个限制:
客户端在未接受到 S1 以前不能发送 C2 包
客户端在未接收到 S2 以前不能发送任何实际数据包
【2】 服务端在接受到 C0,发送 S0,S1 包。也能够等到接受到 C1 以后再一块儿发送,C1 包的等待不是必须的。
此时,服务端处于等待状态。服务端有两个限制:
服务端在未接受到 C1 以前不能发送 S2.
服务端在未接收到 C2 以前不能发送任何实际数据包
【3】客户端接受到 S1/S0 包后,发送 C2 包。
【4】服务端接受到 C2 包后,返回 S2 包,而且此时握手已经完成。
不过,在实际应用中,并非严格按照上面的来。由于 RTMP 并非强安全性的协议,因此,S2/C2 包只须要 C1/S1 中的内容,就能够完成内容的拼接。windows

这么多限制,说白了,其实就是一种通用模式:
C0+C1
S0+S1+S2
C2
接下来,咱们来具体看看 C/S 012 包分别表明什么。
C0 && S0
C0 和 S0 其实区别不大,我这里主要讲解一下 C0,就差很少了。首先,C0 的长度为 1B。它的主要工做是肯定 RTMP 的版本号。
C0:客户端发送其所支持的 RTMP 版本号:3~31。通常都是写 3。
S1:服务端返回其所支持的版本号。若是没有客户端的版本号,默认返回 3。
C1 && S1
C1/S1 长度为 1536B。主要目的是确保握手的惟一性。格式为:安全

time: 发送时间戳,这个其实不是很重要,不过须要记住,不要超出 4B 的范围便可。
zero: 保留值 0.
random: 该字段长尾 1528B。主要内容就是随机值,无论你用什么产生均可以。它主要是为了保证这次握手的惟一性,和肯定握手的对象。
C2 && S2
C2/S2 的长度也是 1536B。至关于就是 S1/C1 的响应值。上图也简单说明了就是,对应 C1/S1 的 Copy 值,不过第二个字段有区别。基本格式为:服务器

time: 时间戳,同上,也不是很重要
time2: C1/S1 发送的时间戳。
random: S1/C1 发送的随机数。长度为 1528B。
这里须要说起的是,RTMP 默认都是使用 Big-Endian 进行写入和读取,除非强调对某个字段使用 Little-Endian 字节序。
上面握手协议的顺序也是根据其中相关的字段来进行制定的。这样,看起来很容易啊哈,可是,咱们并不只仅停留在了解,而是要真正的了解,接下来,咱们来实现一下,若是经过 Buffer 来进行 3 次握手。这里,咱们做为 Client 端来进行请求的发起,假设 Server 端是按照标准进行发送便可。
Buffer 实操握手
咱们使用 Buffer 实操主要涉及两块,一个块是 request server 的搭建,还有一块是 Buffer 的拼接。
Request Server 搭建
这里的 Server 是直接使用底层的 TCP 链接。
以下,一个简易的模板:
const client = new net.Socket();网络

client.connect({架构

port: 1935,
host: "6721.myqcloud.com"},
()=>{
    console.log("connected");
});

client.on('data',(data)=>{app

client.write('hello');

});
不过,为了更好的进行实际演练,咱们经过 EventEmitter 的方式,来作一个筛选器。这里,咱们使用 mitt 模块来作代理。
const Emitter = require('mitt')();
而后,咱们只要分析的就是将要接受到的 S0/1/2 包。根据上面的字节包图,能够清楚的知道包里面的详细内容。这里,为了简单起见,咱们排除其余协议的包头,只是针对 RTMP 里面的包。并且,咱们针对的只有 3 种包,S0/1/2。为了达到这种目的,咱们须要在 data 时间中,加上相应的钩子才行。
这里,咱们借用 Now 直播的 RTMP 流来进行相关的 RTMP 直播讲解。
Buffer 操做
Server 的搭建其实上网搜一搜,应该均可以搜索出来。关键点在于,如何针对 RTMP 的实操握手进行 encode/decode。因此,这里,咱们针对上述操做,来主要讲解一下。
咱们主要的工做量在于如何构造出 C0/1/2。根据上面格式的描述,你们应该能够清楚的知道 C0/1/2 里面的格式分别有啥。
好比,C1 中的 time 和 random,其实并非必须字段,因此,为了简单起见,咱们能够默认设为 0。具体代码以下:
class C {框架

constructor() {
    this.time;
    this.random;
}
C0() {
    let buf = Buffer.alloc(1);
    buf[0] = 3;
    return buf;
}
C1() {
    let buf = Buffer.alloc(1536);
    return buf;
}
/**
 * write C2 package
 * @param {Number} time the 4B Number of time
 * @param {Buffer} random 1528 byte
 */
produceC2(){
    let buf = Buffer.alloc(1536);
    // leave empty value as origin time
    buf.writeUInt32BE(this.time, 4);
    this.random.copy(buf,8,0,1528);

    return buf;
}
get getC01(){
    return Buffer.concat([this.C0(),this.C1()]);
}
get C2(){
    return this.produceC2();
}

}
接下来,咱们来看一下,结合 server 完成的 RTMP 客户端服务。
const Client = new net.Socket();
const RTMP_C = new C();dom

Client.connect({ide

port: 1935,
host: "6721.liveplay.myqcloud.com"

}, () => {

console.log('connected')
Client.write(RTMP_C.getC01);

});

Client.on('data',res=>{

if(!res){
    console.warn('received empty Buffer ' + res);
    return;
}
// start to decode res package
if(!RTMP_C.S0 && res.length>0){
    RTMP_C.S0 = res.readUInt8(0);
    res = res.slice(1);
}

if(!RTMP_C.S1 && res.length>=1536){
    RTMP_C.time = res.readUInt32BE(0);
    RTMP_C.random = res.slice(8,1536);
    RTMP_C.S1 = true;
    res = res.slice(1536);
    console.log('send C2');
    Client.write(RTMP_C.C2);
}

if(!RTMP_C.S2 && res.length >= 1536){
    RTMP_C.S2 = true;
    res = res.slice(1536);
}

})
详细代码能够参考 gist。
RTMP 基本架构
RTMP 整个内容,除了握手,其实剩下的就是一些列围绕 type id 的 message。为了让你们更清楚的看到整个架构,这里简单陈列了一份框架:

在 Message 下的 3 个一级子 Item 就是咱们如今将要大体讲解的内容。
能够看到上面全部的 item 都有一个共同的父 Item–Message。它的基本结构为:
Header: header 部分用来标识不一样的 typeID,告诉客户端相应的 Message 类型。另外,还有个功效就是多路分发。
Body: Body 内容就是相应发送的数据。这个根据不一样的 typeID 来讲,格式就彻底不同了。
下面,咱们先了解一下 Header 和不一样 typeID 的内容:
Header
RTMP 中的 Header 分为 Basic Header 和 Message Header。须要注意,他们二者并非独立的,而是相互联系。Message Header 的结构由 Basic Header 的内容来决定。

接下来,先分开来说解:
Basic Header
BH(基础头部)主要是定义了该 chunk stream ID 和 chunk type。须要注意的是,BH 是变长度的,即,它的长度范围是 1-3B。怎么讲呢?就是根据不一样的 chunk stream ID 来决定具体的长度。CS ID(Chunk Stream ID)自己的支持的范围为 <= 65597 ,差很少为 22bit。固然,为了节省这 3B 的内容。 Adobe 搞了一个比较绕的理论,即,经过以下格式中的 CS ID 来肯定:
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|fmt| cs id |
+-+-+-+-+-+-+-+-+
即,经过 2-7 bit 位来肯定整个 BH 的长度。怎么肯定呢?
RTMP 规定,CS ID 的 0,1,2 为保留字,你在设置 CS ID 的时候只能从 3 开始。
CS ID: 0 ==> 整个 BH 长为 2B,其中能够表示的 Stream ID 数量为 64-319。例如:
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|fmt| 0 | cs id - 64 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
注意上面的 cs id - 64。这个表明的就是,你经过切割第二个 byte 时,是将获得的值加上 64。即:2th byte + 64 = CS ID
CS ID: 1 ==> 整个 BH 长为 3B。能够存储的 Stream ID 数量为 64-65599。例如:
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|fmt| 1 | cs id - 64 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
固然,后面 CS ID 的计算方法也是最后的结果加上 64。
CS ID >2 ==> 整个 BH 长为 1B。能够存储的 Stream ID 数量为 3-63。例如:
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|fmt| cs id |
+-+-+-+-+-+-+-+-+
最后强调一下,由于 RTMP 规定,CS ID 的 0,1,2 为保留字,因此,0,1,2 不做为 CS ID。综上所述,CS ID 的起始位为 3(并不表明它是 3 个 Stream)。
上面我并无提到 fmt 字段,这实际上是用来定义 Message Header 的。
Message Header
根据前面 BH 中 fmt 字段的定义,能够分为 4 种 MH(Message Header)。或者说,就是一种 MH 格式会存在从繁到简 4 种:
fmt: 0
当 fmt 为 0 时,MH 的长度为 11B。该类型的 MH 必需要流的开头部分,这包括当进行快退或者点播时从新获取的流。该结构的总体格式以下:

也就是说,当 fmt 为 0 时,其格式是一个完整的 MH。
timestamp 是绝对时间戳。用来表明当前流编码。
message length: 3B, 发送 message 的长度。
type id: 1B
stream id: 4B, 发送 message stream id 的值。是 little-endian 写入格式!
fmt: 1
当 fmt 为 1 时,MH 的长度为 7B。该类型的 MH 不带 msg stream id。msg stream id 由前面一个 package 决定。该数值主要由前一个 fmt 为 0 的 MH 决定。该类型的 MH 一般放在 fmt 为 0 以后。

fmt: 2
当 fmt 为 2 时,MH 的长度为 3B。该类型的 MH 只包括一个 timestamp delta 字段。其它的信息都是依照前面一个其余类型 MH 决定的。
fmt: 3
当 fmt 为 3时,这其实 RTMP 里面就没有了 MH。官方定义,该类型主要所有都是 payload 的 chunk,其 Header 信息和第一个非 type:3 的头一致。由于这主要用于 chunk 中,这些 chunk 都是从一个包里面切割出来的,因此除了第一个 chunk 外,其它的 chunk 均可以采用这种格式。当 fmt 为 3时,计算它的 timestamp 须要注意几点,若是前面一个 chunk 里面存在 timestrameDelta,那么计算 fmt 为 3 的 chunk 时,就直接相加,若是没有,则是使用前一个 chunk 的 timestamp 来进行相加,用代码表示为:
prevChunk.timeStamp += prevChunk.timeStampDelta || prevChunk.timeStamp;
不过,当 fmt: 3 的状况通常很难遇到。由于,他要求前面几个包必须存在 fmt 为 0/1/2 的状况。
接下来的就是 Message Body 部分。
Message Body
上面说的主要是 Message Header 的公用部分,可是,对于具体的 RTMP Message 来讲,里面的 type 会针对不一样的业务场景有不一样的格式。Message 所有内容如上图所示:

这里,咱们根据流程图的一级子 item 来展开讲解。
PCM
PCM 全称为:Protocol Control Messages(协议控制消息)。主要使用来沟通 RTMP 初始状态的相关链接信息,好比,windows size,chunk size 等。
PCM 中一共有 5 种不一样的 Message 类型,是根据 Header 中的 type ID 决定的,范围是 1~6 (不包括 4)。另外,PCM 在构造的时候须要注意,它 Heaer 中的 message stream id 和 chunk stream id 须要设置为固定值:
message stream ID 为 0
chunk stream ID 为 2
如图所示:

OK,咱们接下来一个一个来介绍一下:
Set Chunk Size(1)
看名字你们应该都能猜到这类信息是用来干啥的。该类型的 PCM 就是用来设置 server 和 client 之间正式传输信息的 chunk 的大小,type ID 为 1。那这有啥用呢?
SCS(Set Chunk Size) 是针对正式发送数据而进行数据大小的发送限制。通常默认为 128B。不过,若是 server 以为过小了,想发送更大的包给你,好比 132B,那么 server 就须要给你发送一个 SCS,告知你,接下来“我发送给你的数据大小是 132B”。

0: 只能设为 0 ,用来表示当前的 PCM 的类型。
chunk size: 用来表示后面发送正式数据时的大小。范围为 1-16777215。
以下,提供过 wireshark 抓包的结果:

Abort Message(2)
该类 PCM 是用来告诉 client,丢弃指定的 stream 中,已经加载到一半或者还未加载完成的 Chunk Message。它须要指定一个 chunk stream ID。
基本格式为:

chunk stream id: 指定丢弃 chunk message 的 stream
Acknowledgement(3)
该协议信息其实就是一个 ACK 包,在实际使用是并无用到,它主要是用来做为一个 ACK 包,来表示两次 ACK 间,接收端所能接收的最大字节数。
它基本格式为:

sequence number[4B]: 大小为 4B
不过,该包在实际应用中,没有多高的出现频率。
Window Acknowledgement Size(5)
这是用来协商发送包的大小的。这个和上面的 chunk size 不一样,这里主要针对的是客户端可接受的最大数据包的值,而 chunk size 是指每次发送的包的大小。也能够叫作 window size。通常电脑设置的大小都是 500000B。
详细格式为:

经过,wireshark 抓包的结果为:

Set Peer Bandwidth(6)
这是 PCM 中,最后一个包。他作的工做主要是根据网速来改变发送包的大小。它的格式和 WAS 相似,不事后面带上了一个 Type 用来标明当前带宽限制算法。当一方接收到该信息后,若是设置的 window size 和前面的 WAS 不一致,须要返回一个 WAS 来进行显示改变。
基本格式为:

其中 Limit Type 有 3 个取值:
0: Hard,表示当前带宽须要和当前设置的 window size 匹配
1: Soft,将当前宽带设置为该信息定义的 window size,或者已经生效的 window size。主要取决于谁的 window size 更小
2: Dynamic,若是前一个 Limit Type 为 Hard 那么,继续使用 Hard 为基准,不然忽略该次协议信息。
实际抓包状况能够参考:

UCM
全称为:User Control Message(用户控制信息)。它的 Type ID 只能为 4。它主要是发送一些对视频的控制信息。其发送的条件也有必定的限制:
msg stream ID 为 0
chunk stream ID 为 2
它的 Body 部分的基本格式为:

UCM 根据 Event Type 的不一样,对流进行不一样的设置。它的 Event Type 一共有 6 种格式 Stream Begin(0),Stream EOF(1),StreamDry(2),SetBuffer Length(3),StreamIs Recorded(4),PingRequest(6),PingResponse(7)。
这里,根据重要性划分,只介绍 Begin,EOF,SetBuffer Length 这 3 种。
Stream Begin: Event Type 为 0。它经常出如今,当客户端和服务端成功 connect 后发送。Event Data 为 4B,内容是已经能够正式用来传输数据的 Stream ID(实际没啥用)。

Stream EOF: Event Type 为 1。它经常出如今,当音视频流已经所有传输完时。 Event Data 为 4B,用来表示已经发送完音视频流的 Stream ID(实际没啥用)。
Set Buffer Length: Event Type 为 3。它主要是为了通知服务端,每毫秒用来接收流中 Buffer 的大小。Event Data 的前 4B 表示 stream ID,后面 4B 表示每毫秒 Buffer 的大小。一般为 3000ms
OK 剩下就是 Command Msg 里面的内容了。
Command Msg
Command Msg 里面的内容,其 type id 涵盖了 8~22 之间的值。具体内容,能够参考下表:

须要注意,为何有些选项里面有两个 id,这主要和 AMF 版本选择有关。第一个 ID 表示 AMF0 的编解码方式,第二个 ID 表示 AMF3 的编解码方式。 其中比较重要的是 command Msg,video,audio 这 3 个 Msg。为了让你们更好的理解 RTMP 流的解析,这里,先讲解一下 video 和 audio 两个 Msg。
Video Msg
由于 RTMP 是 Adobe 开发的。理所固然,内部的使用格式确定是 FLV 格式。不过,这和没说同样。由于,FLV 格式内部有不少的 tag 和相关的描述信息。那么,RTMP 是怎么解决的呢?是直接传一整个 FLV 文件,还自定义协议来分段传输 FLV Tag 呢?
这个其实很好回答,由于 RTMP 协议是一个长链接,若是是传整个 FLV 文件,根本不必用到这个,并且,RTMP 最经常使用在直播当中。直播中的视频都是分段播放的。综上所述,RTMP 是根据本身的自定义协议来分段传输 FLV Tag 的。那具体的协议是啥呢?
这个在 RTMP 官方文档中其实也没有给出。它只是告诉咱们 Video Msg 的 type ID 是 9 而已。
由于,RTMP 只是一个传输工具,里面传什么仍是由具体的流生成框架来决定的。因此,这里,我选择了一个很是具备表明性的 RTMP 直播流来进行讲解。
经过 wireshark 抓包,能够捕获到如下的 RTMP 包数据:

这里须要说起一点,由于 RTMP 是主动将 Video 和 Audio 分开传输,因此,它须要交叉发布 Video 和 Audio,以保证音视频的同步。那么具体每一个 Video Data 里面的数据都是同样的吗?
若是看 Tag 的话,他们传输的都是 VideoData Tag。先看一下 FLV VideoData Tag 的内容:

这是 FLV Video 的协议格式。但,遇到第一个字段 FrameType 的时候,咱们就可能懵逼了,这 TM 有 5 种状况,难道 RTMP 会给你 5 种不一样的包吗?
答案是,有可能,可是,很大状况下,咱们只须要支持 1/2 便可。由于,视频中最重要的是 I 帧,它对应的 FrameType 就是 1。而 B/P 则是剩下的 2。咱们只要针对 1/2 进行软解,便可实现视频全部信息的获取。
因此,在 RTMP 中,也主要(或者大部分)都是传输上面两种 FrameType。咱们经过实际抓包来说解一下。
这是 KeyFrame 的包,注意 Buffer 开头的 17 数字。你们能够找到上面的 FrameType 对应找一找,看结果是否是一致的:

这是 Inter-frame 的包。同上,你们也能够对比一下:

Audio Tag
Aduio Tag 也是和 Video Tag 同样的蜜汁数据。经过观察 FLV Audio Tag 的内容:

上面这些字段全是相关的配置值,换句话说,你必须实现知道这些值才行。这里,RTMP 发送 Audio Tag 和 Video Tag 有点不一样。由于 Audio Tag 已经不可能再细分为 Config Tag,因此,RTMP 会直接传递 上面的 audio Tag 内容。详细能够参考抓包内容:

这也是全部的 Audio Msg 的内容。
由于 Audio 和 Video 是分开发送的。因此,在后期进行拼接的时候,须要注意二者的同步。说道这里,顺便补充一下,音视频同步的相关知识点。
音视频同步
音视频同步简单来讲有三种:
以 Audio 为准,Video 同步 Audio
以 Video 为准,Audio 同步 Video
之外部时间戳为准,AV 同时同步
主要过程变量参考就是 timeStamp 和 duration。由于,这里主要是作直播的,推荐你们采用第二种方法,以 Video 为准。由于,在实际开发中,会遇到 MP4 文件生成时,必需要求第一帧为 keyframe,这就形成了,以 Audio 为参考的,会遇到两个变量的问题。一个是 timeStamp 一个是 keyframe。固然,解决办法也是有的,就是检查最后一个拼接的 Buffer 是否是 Keyframe,而后判断是否移到下一次同步处理。
这里,我简单的说一下,以 Video 为准的同步方法。以 Video 同步,不须要管第一帧是否是 keyframe,也不须要关心 Audio 里面的数据,由于,Audio 数据是很是简单的 AAC 数据。下面咱们经过伪代码来讲明一下:
// known condition
video.timeStamp && video.perDuration && video.wholeDuration
audio.timeStamp && audio.perDuration

// start
refDuration = video.timeStamp + video.wholeDuration
delta = refDuration - audio.timeStamp
audioCount = Math.round(delta/audio.perDuration);
audDemuxArr = this._tmpArr.splice(0,audioCount);

// begin to demux
this._remuxVideo(vidDemuxArr);
this._remuxAudio(audDemuxArr);
上面算法能够避免判断 Aduio 和 Video timeStamp 的比较,保证 Video 一直在 Audio 前面并相差不远。下面,咱们回到 RTMP 内容。来看看 Command Msg 里面的内容。
Command Msg
Command Msg 是 RTMP 里面的一个主要信息传递工具。经常用在 RTMP 前期和后期处理。Command Msg 是经过 AMF 的格式进行传输的(其实就是相似 JSON 的二进制编码规则)。Command Msg 主要分为 net connect 和 net stream 两大块。它的交流方式是双向的,即,你发送一次 net connect 或者 stream 以后,另一端都必须返回一个 _result 或者 _error 以表示收到信息。详细结构能够参考下图:

后续,咱们分为两块进行讲解:
netConnection
netStream
里面的 _result 和 _error 会穿插在每一个包中进行讲解。
NetConnection
netConnection 能够分为 4 种 Msg,connect,call,createStream,close。
connect
connect 是客户端向 Server 端发送播放请求的。里面的字段内容有:
Command Name[String]: 默认为 connect。表示信息名称
Transaction ID[Number]: 默认为 1。
Command Object: 键值对的形式存放相关信息。
Optional: 可选值。通常没有
那,Command Object 里面又能够存放些什么内容呢?
app[String]: 服务端链接应用的名字。这个主要根据你的 RTMP 服务器设定来设置。好比:live。
flashver[String]: Flash Player 的版本号。通常根据本身设备上的型号来肯定便可。也能够设置为默认值:LNX 9,0,124,2。
tcUrl[String]: 服务端的 URL 地址。简单来讲,就是 protocol://host/path。好比:rtmp://6521.liveplay.myqcloud.com/live。
fpad[Boolean]: 表示是否使用代理。通常为 false。
audioCodecs[Number]: 客户端支持的音频解码。后续会介绍。默承认以设置为 4071
videoCodecs[Number]: 客户端支持的视频解码。有自定义的标准。默承认以设置为 252
videoFunction[Number]: 代表在服务端上调用那种特别的视频函数。默承认以设置为 1
简单来讲,Command Object 就是起到 RTMP Route 的做用。用来请求特定的资源路径。实际数据,能够参考抓包结果:

上面具体的取值主要是根据 rtmp 官方文档来决定。若是懒得查,能够直接使用上面的取值。上面的内容是兼容性比较高的值。当该包成功发送时,另一端须要获得一个返回包来响应,具体格式为:
Command Name[String]: 为 _result 或者 _error。
Transaction ID[Number]: 默认为 1。
Command Object: 键值对的形式存放相关信息。
Information[Object]: 键值对的形式,来描述相关的 response 信息。里面存在的字段有:level,code,description
能够参考:

connect 包发送的位置,主要是在 RTMP 握手结束以后。以下:

call
call 包主要做用是用来远程执行接收端的程序(RPC, remote procedure calls)。不过,在我解 RTMP 的过程当中,并无实际用到过。这里简单介绍一下格式。它的内容和 connect 相似:
Procedure Name[String]: 调用处理程序的名字。
Transaction ID[Number]: 若是想要有返回,则咱们须要制定一个 id。不然为 0。
Command Object: 键值对的形式存放相关信息。AMF0/3
Optional: 可选值。通常没有
Command Object 里面的内容主要是针对程序,设置相关的调用参数。由于内容不固定,这里就不介绍了。
call 通常是须要有 response 来代表,远端程序是否执行,以及是否执行成功等。返回的格式为:
Command Name[String]: 根据 call 中 Command Object 参数来决定的。
Transaction ID[Number]: 若是想要有返回,则咱们须要制定一个 id。不然为 0。
Command Object: 键值对的形式存放相关信息。AMF0/3
Response[Object]: 响应的结果值
createStream
createStream 包只是用来告诉服务端,咱们如今要建立一个 channel 开始进行流的交流了。格式和内容都不复杂:
Procedure Name[String]: 调用处理程序的名字。
Transaction ID[Number]: 本身制定一个。通常能够设为 2
Command Object: 键值对的形式存放相关信息。AMF0/3
当成功后,服务端会返回一个 _result 或者 _error 包来讲明接收成功,详细内容为:
Command Name[String]: 根据 call 中 Command Object 参数来决定的。
Transaction ID[Number]: 若是想要有返回,则咱们须要制定一个 id。不然为 0。
Command Object: 键值对的形式存放相关信息。AMF0/3。通常为 Null
Stream ID: 返回的 stream ID 值。
它的返回值很随意,参考抓包内容:

下面,咱们来看一下 RTMP 中第二个比较重要的 command msg – netStream msg。
NetStream Msg
NetStream 里面的 Msg 有不少,但在直播流中,比较重要的只有 play 包。因此,这里咱们着重介绍一下 play 包。
play
play 包主要是用来告诉 Server 正式播放音视频流。并且,因为 RTMP 自然是作多流分发的。若是遇到网络出现相应的波动,客户端能够根据网络条件屡次调用 play 命令,来切换不一样模式的流。
其基本格式为:
Command Name[String]: 根据 call 中 Command Object 参数来决定的。
Transaction ID[Number]: 默认为 0。也能够设置为其余值
Command Object: 不须要该字段,在该命令中,默认设为 Null
Stream Name[String]: 用来指定播放的视频流文件。由于,RTMP 天生是支持 FLV 的,因此针对 FLV 文件来讲,并不须要加额外的标识,只须要写明文件名便可。好比:
StreamName: '6721_75994f92ce868a0cd3cc84600a97f75c'
不过,若是想要支持其它的文件,那么则须要额外的表示。固然,音频和视频须要不一样的支持:
若是是播放音频文件,好比 mp3,那么则须要额外的前缀标识符-mp3。例如:mp3:6721_75994f9。
若是涉及到视频文件的话,不只须要前缀,还须要后缀。好比播放的是 MP4 文件,则标识为:mp4:6721_75994f9.mp4。

startNumber: 这个字段其实有点意思。它能够分为 3 类来说解:-2,-1,>=0。
-2: 若是是该标识符,服务端会首先寻找是否有对应的 liveStream。没有的话,就找 record_stream。若是尚未的,此次请求会暂时挂起,直到获取到下一次 live_stream。
-1: 只有 live_stream 才会播放。
=0: 至关于就是 seek video。它会直接找到 record_stream,而且根据该字段的值来肯定播放开始时间。若是没有的话,则播放 list 中的下一个 video。

durationNumber: 用来设置播放时长的。它里面也有几个参数须要讲解一下,-1,0,>0。
-1: 会一直播放到 live_stream 或者 record_stream 结束。
0: 会播放一段一段的 frame。通常不用。
0: 会直接播放指定 duration 以内的流。若是超出,则会播放指定时间段内容的 record_stream。

reset[Boolean]: 该字段没啥用,通常能够忽略。用来表示否是抛弃掉前面的 playlist。
整个 play 包内容就已经介绍完了。咱们能够看看实际的 play 抓包结果:

那 play 包是在那个环节发送,发送完以后需不须要对应的 _result 包呢?
play 包比较特殊,它是不须要 _result 回包的。由于,一旦 play 包成功接收后。server 端会直接开始进行 streamBegin 的操做。
整个流程为:

到这里,后续就能够开始正式接收 video 和 audio 的 stream。
转载自https://www.villianhr.com/201... H5 直播流技术解析

想要阅读更多技术干货文章,欢迎关注网易云信博客。
了解网易云信,来自网易核心架构的通讯与视频云服务。

网易云信(NeteaseYunXin)是集网易18年IM以及音视频技术打造的PaaS服务产品,来自网易核心技术架构的通讯与视频云服务,稳定易用且功能全面,致力于提供全球领先的技术能力和场景化解决方案。开发者经过集成客户端SDK和云端OPEN API,便可快速实现包含IM、音视频通话、直播、点播、互动白板、短信等功能。