一、简述:
本教程是基于VLC实现的Rtmp服务器与客户端的播放流程
AMF 命令-命令消息类型
发送端发送时会带有:
- 命令的名字,如 connect
- Transaction ID 表示此次命令的标识
- Command Object 表示相关参数
接受端收到命令后,会返回以下三种消息中的一种:
- _result 消息表示接受该命令,对端可以继续往下执行流程
- _error 消息代表拒绝该命令要执行的操作
- method name 消息代表要在之前命令的发送端执行的函数名称。
这三种回应的消息都要带有收到的命令消息中的 Transaction ID 来表示本次的回应作用于哪个命令。
可以认为发送命令消息的对象有两种:
- 一种是 Net Connection,表示双端的上层连接
- 一种是 Net Stream,表示流信息的传输通道,控制流信息的状态,如 Play 播放,Pause 暂停等。
这些命令直接以 AMF 格式数据放置在 Chunk Data 部分。
协议控制消息
这些协议控制消息必须使用消息流ID 0(又称为控制流)且在块流ID 2中发送。协议控制消息收到时立即生效,他们的时间戳会被直接忽略。
二、服务器与客户端交互流程图(Play过程)
![服务器与客户端交互流程图(Play过程)](https://img-blog.csdnimg.cn/2020030114413718.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4MzA5MTIx,size_16,color_FFFFFF,t_70)
三、具体消息分析
1、Connect(‘stream名’):属于命令消息类型(c—>s)
名称:connect
![](/qrcode.jpg)
作用:客户端发送 connect 命令到服务器端来请求连接到一个服务器应用的实例
包结构:(结构跟publish中的connect一致)
RTMP_header:
fmt(2bit)+csid(6bit-22bit)+timestamp(3byte)+body size(3byte)+Typeid(1byte)+stream id(4byte)
- Type id可能为0x14,代表包将采用AMF0方式进行编码。
- Type id也可为0x11,表示包将采用AMF3方式进行编码。
RTMP_body:
Command Name(命令名字,字符串,设置给 "connect")
Transaction ID(事务ID,数字,总是设置为 1)
Command Object(键值对集合标识命令参数)
Optional User Arguments(用户自定义的额外信息,可选参数)
End of objectMarker(0x00 0x00 0x09) (Object的结尾)
2、Window Acknowledgement Size: 属于协议控制消息(C—>S)
名称:窗口确认大小。
作用:客户端或者服务器端发送这条消息来通知对端发送和应答之间的窗口大小。发送者在发送完窗口大小字节之后期待对端的确认。接收端在上次确认发送后接收到的指示数值后,或者会话建立之后尚未发送确认,必须发送一个确认。
包结构:
RTMP_header:
fmt(2bit)+csid(6bit-22bit)+timestamp(3byte)+bodysize(3byte)+Type id(1byte)+stream id(4byte)
其中,type id是一定的为5
RTMP_body:
WindowAcknowledgement Size(4byte)
3、Acknowledgement(C-->S)
在播放过程中客户端会不断发送这个Acknowledgement窗口确认大小,这个可以不处理,但是如果响应错了,会立马导致客户端主动断开。在各大园中也没有那个文档中说明有这个命令,本人按Window Acknowledgement Size: 属于协议控制消息返回客户端貌似没什么反应,还是照常发送。如有大神知道其中明理希望告知。
4、Set peer bandwidth: 属于协议控制消息(C—>S)(type id=6)
名称:设置对端带宽。
作用:客户端或者服务器端发送这一消息来限制其对端的输出带宽。对端接收到这一消息后,将通过限制这一消息中窗口大小指出的已发送但未被答复的数据的数量以限制其输出带宽。如果这个窗口大小不同于其发送给 (设置对端带宽) 发送者的最后一条消息,那么接收到这一消息的对端应该回复一个窗口确认大小消息。
包结构:
RTMP_header:
fmt(2bit)+csid(6bit-22bit)+timestamp(3byte)+bodysize(3byte)+Type id(1byte)+stream id(4byte)
其中,type id是一定的为6
RTMP_body:
0 1 2 3
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| WindowAcknowledgement Size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Limit type |
+-+-+-+-+-+-+-+-+
Payload for the 'Set Peer Bandwidth' protocol message
WindowAcknowledgement Size(4byte)+Limit type(1byte)
限制类型取以下值之一:
0 - Hard:对端应该限制其输出带宽到指示的窗口大小。
1 - Soft:对端应该限制其输出带宽到指示的窗口大小,或者已经有限制在其作用的话就取两者之间的较小值。
2 - Dynamic:如果先前的限制类型为Hard,处理这个消息就好像它被标记为 Hard,否则的话忽略这个消息。
5、Set Chunk Size:属于协议控制消息.(C—>S)
名称:设置块大小。
作用:协议控制消息 1,设置块大小,以通知对端一个新的最大块大小(set chunk size)。默认的最大块大小是为 128 字节,但是客户端或者服务器可以改变这个大小,并使用这一消息对对端进行更新。例如,假定一个客户端想要发送一个 131 字节的音频数据,当前块大小是默认的 128。在这种情况下,客户端可以发送这种消息到服务器以通知它块大小现在是 131 字节了。这样客户端就可以在单一块中发送整个音频数据了。最大块大小设置的话最少为 128 字节,包含内容最少要一个字节。最大块大小由每个方面 (服务器或者客户端) 自行维护。
包结构:
RTMP_header:
fmt(2bit)+csid(6bit-22bit)+timestamp(3byte)+bodysize(3byte)+Type id(1byte)+stream id(4byte)
RTMP_body: 4个字节标识Chunk块大小
注:从FMS服务器发出来的Chunk大小来分析,其大多都是使用的0x1000==4096,至于为什么我也不清楚了。如果在过程中不发送该消息,则Wireshark分析不出消息,全是Unknown。再者如果不发该消息,视频如果关键帧比较大,例如25寸的电脑屏幕一个关键帧就200万左右字节,分包发送后客户端后是不被客户端认可的,因为对方无法解析出相应的音视频数据。如果是电脑的前置摄像头的话流就比较小,大约也就1万多不到2万字节,直接设置成6万(也是观察得知的数值),直接整个流发送也是可以的。
6、Stream begin 0:属于用户控制消息.(C—>S)
作用:服务器发送这个事件来通知客户端一个流已就绪并可以用来通信。默认情况下,这一事件在成功接收到客户端的应用连接命令之后以 ID 0 发送。这一事件数据为 4 字节,代表了已就绪流的流 ID。
包结构:
RTMP_header:
fmt(2bit)+csid(6bit-22bit)+timestamp(3byte)+bodysize(3byte)+Type id(1byte)+stream id(4byte)
type id是一定的为4
csid是一定的为2
stream id是一定的为0
并且timestamp将被忽略
RTMP_body:
Eventtype:stream begin(0)(2byte)+4byte的数据 (0x00,0x00,0x00,0x00)
7、_result(netconnect.connect.success):属于命令消息类型(响应)(S-->C)
名称:服务器应答命令。
作用:服务器端向客户端发送的关于之前客户端向服务器端请求的命令的响应结果。对方发送控制命令后比如Connect,要返回对应的响应比如_result、或者_error(Get Stream Length 的时候FMS返回的就是_error)。对应的Transaction ID要跟收到的命令的Transaction ID对应上,否则响应无效。如果不发送该消息,对端会一直等待。
包结构:
RTMP_header:
fmt(2bit)+csid(6bit-22bit)+timestamp(3byte)+bodysize(3byte)+Type id(1byte)+stream id(4byte)
其中,Type id可能为0x14,代表包将采用AMF0方式进行编码。也可以为0x11,此时表示包将采用AMF3方式进行编码。
RTMP_body:
字段名 |
类型 |
描述 |
Command Name |
字符串 |
_result 或者 _error;表明回复是一个结果还是错误。 |
Transaction ID |
数字 |
响应所属的命令的 ID。 |
Command Object |
对象 |
如果存在一些命令信息要设置这个对象,否则置空。 |
Stream ID |
数字 |
返回值要么是一个流 ID 要么是一个错误信息对象。 |
注意:在VLC播放中服务器收到connect命令后,要发送如下_result命令:
实际输入如下(从Wireshark中复制出来的实际数据以便交流分析):
0000 03 00 00 00 00 00 f0 14 00 00 00 00 02 00 07 5f
0010 72 65 73 75 6c 74 00 3f f0 00 00 00 00 00 00 03
0020 00 06 66 6d 73 56 65 72 02 00 0d 46 4d 53 2f 33
0030 2c 35 2c 32 2c 36 35 34 00 0c 63 61 70 61 62 69
0040 6c 69 74 69 65 73 00 40 3f 00 00 00 00 00 00 00
0050 04 6d 6f 64 65 00 3f f0 00 00 00 00 00 00 00 00
0060 09 03 00 05 6c 65 76 65 6c 02 00 06 73 74 61 74
0070 75 73 00 04 63 6f 64 65 02 00 1d 4e 65 74 43 6f
0080 6e 6e 65 63 74 69 6f 6e 2e 43 6f 6e 6e 65 63 74
0090 2e 53 75 63 63 65 73 73 00 0b 64 65 73 63 72 69
00a0 70 74 69 6f 6e 02 00 15 43 6f 6e 6e 65 63 74 69
00b0 6f 6e 20 73 75 63 63 65 65 64 65 64 2e 00 0e 6f
00c0 62 6a 65 63 74 45 6e 63 6f 64 69 6e 67 00 00 00
00d0 00 00 00 00 00 00 00 04 64 61 74 61 08 00 00 00
00e0 00 00 07 76 65 72 73 69 6f 6e 02 00 09 33 2c 35
00f0 2c 32 2c 36 35 34 00 00 09 00 00 09
8、_result(netconnect.connect.success):属于命令消息类型(S-->C)
名称:服务器应答命令。
作用:服务器端向客户端发送的关于之前客户端向服务器端请求的命令的响应结果。
包结构:
RTMP_header:
fmt(2bit)+csid(6bit-22bit)+timestamp(3byte)+bodysize(3byte)+Type id(1byte)+stream id(4byte)
其中,Type id可能为0x14,代表包将采用AMF0方式进行编码。也可以为0x11,此时表示包将采用AMF3方式进行编码。
RTMP_body:
字段名 |
类型 |
描述 |
Command Name |
字符串 |
_result 或者 _error;表明回复是一个结果还是错误。 |
Transaction ID |
数字 |
响应所属的命令的 ID。 |
Command Object |
对象 |
如果存在一些命令信息要设置这个对象,否则置空。 |
Stream ID |
数字 |
返回值要么是一个流 ID 要么是一个错误信息对象。 |
注:本条消息与上面那条消息命令虽是同一种命令,但是内容不一样位置也不一样。上面那条本人从抓包分析中得知是响应Connect的,本条是响应Create Stream的。
9、onBWDone():属于命令消息类型(S-->C)
名称:服务器应答命令。
作用:服务器端向客户端发送的关于之前客户端向服务器端请求的命令的响应结果。
包结构:
RTMP_header:
fmt(2bit)+csid(6bit-22bit)+timestamp(3byte)+bodysize(3byte)+Type id(1byte)+stream id(4byte)
其中,Type id可能为0x14,代表包将采用AMF0方式进行编码。也可以为0x11,此时表示包将采用AMF3方式进行编码。
RTMP_body:
注:onBwDone命令本人没有处理(也就是作为服务端没有发送)但是没有影响,具体作用不太了解。
10、creatStream(): 属于协议控制消息(C—>S)
名称:createStream
作用:客户端发送这一命令到服务器端为消息连接创建一个逻辑通道。音频、视频和元数据使用 createStream 命令创建的流通道传输。
包结构:
RTMP_header:
fmt(2bit)+csid(6bit-22bit)+timestamp(3byte)+bodysize(3byte)+Type id(1byte)+stream id(4byte)
其中,Type id可能为0x14,代表包将采用AMF0方式进行编码。也可以为0x11,此时表示包将采用AMF3方式进行编码。
RTMP_body:
字段名 |
类型 |
描述 |
Command Name |
字符串 |
命令名。设置给 "createStream"。 |
Transaction ID |
数字 |
命令的事务 ID。 |
Command Object |
对象 |
如果存在一些命令信息要设置这个对象,否则置空。 |
11、set Buffer Length:属于用户控制消息.(C—>S)
名称:窗口确认大小。
作用:客户端发送这一事件来通知服务器端用于缓存流中任何数据的缓存大小 (以毫秒为单位)。这一事件在服务器端开始处理流之前就发送。这一事件数据的前 4 个字节代表了流 ID 后 4 个字节代表了以毫秒为单位的缓存的长度。
包结构:
RTMP_header:
fmt(2bit)+csid(6bit-22bit)+timestamp(3byte)+bodysize(3byte)+Type id(1byte)+stream id(4byte)
其中,type id是一定的为4.csid是一定的为2.stream id是一定的为0。并且timestamp将被忽略。
RTMP_body:
Eventtype:Set Buffer Length(3)(2byte)+ 流ID(4byte) + 缓存的长度(以毫秒为单位,4byte)
12、Play(‘app名’):属于命令消息类型(c—>s)
名称:play
作用:客户端发送这一命令到服务器端以播放流。也可以多次使用这一命令以创建一个播放列表。
如果你想要创建一个动态的播放列表这一可以在不同的直播流或者录制流之间进行切换播放的话,多次调用 play 方法,并在每次调用时设置Reset的值为 false。相反的,如果你想要立即播放指定流,需要将其他等待播放的流清空,并为将Reset设为true。
包结构:(结构跟publish中的connect一致)
RTMP_header:
fmt(2bit)+csid(6bit-22bit)+timestamp(3byte)+body size(3byte)+Typeid(1byte)+stream id(4byte)
其中:Type id可能为0x14,代表包将采用AMF0方式进行编码。也可以为0x11,此时表示包将采用AMF3方式进行编码。
RTMP_body:
字段名 |
类型 |
描述 |
Command Name |
字符串 |
命令名。设为 "play"。 |
Transaction ID |
数字 |
事务 ID设为 0。 |
Command Object |
Null |
命令信息不存在。设为 null类型。 |
Stream Name |
字符串 |
要播放流的名字。要播放视频 (FLV)文件,使用没有文件扩展名的名字对流名进行定义 (例如,"sample")。要重播 MP3或者 ID3,你必须在流名前加上 mp3:例如,"mp3:sample"。要播放 H.264/AAC文件,你必须在流名前加上 mp4:并指定文件扩展名。例如,要播放 sample.m4v文件,定义"mp4:sample.m4v"。 |
Start |
数字 |
一个可选的参数,直播流交互过程中没有此字段,可能是没有添加。该字段以秒为单位定义开始时间。默认值为 -2,表示用户首先尝试播放流名字段中定义的直播流。如果那个名字的直播流没有找到,它将播放同名的录制流。如果没有那个名字的录制流,客户端将等待一个新的那个名字的直播流,并当其有效时进行播放。如果你在 Start字段中传递-1,那么就只播放流名中定义的那个名字的直播流。如果你在 Start字段中传递 0或一个整数,那么将从 Start 字段定义的时间开始播放流名中定义的那个录制流。如果没有找到录制流,那么将播放播放列表中的下一项。 |
Duration |
数字 |
一个可选的参数,以秒为单位定义了回放的持续时间。默认值为 -1。-1值意味着一个直播流会一直播放直到它不再可用或者一个录制流一直播放直到结束。如果你传递 0 值,它将只播放单一一帧,因为播放时间已经在录制流的开始的Start字段指定了。假定定义在 Start字段中的值大于或者等于0。如果你传递一个正数,将播放 Duration字段定义的一段直播流。之后,变为可播放状态,或者播放 Duration 字段定义的一段录制流。(如果流在 Duration字段定义的时间段内结束,那么流结束时回放结束)。如果你在 Duration字段中传递一个 -1以外的负数的话,它将把你给的值当做 -1处理。 |
Reset |
布尔 |
一个可选的布尔值或者数字定义了是否对以前的播放列表进行 flush。 |
13、onStatus(‘NetStream.Play.Reset’):属于命令消息类型(S-->C)
名称:onStatus
作用:服务器端使用"onStatus" 命令向客户端发送 NetStream 状态。
包结构:
RTMP_header:
fmt(2bit)+csid(6bit-22bit)+timestamp(3byte)+bodysize(3byte)+Type id(1byte)+stream id(4byte)
其中,Type id可能为0x14,代表包将采用AMF0方式进行编码。也可以为0x11,此时表示包将采用AMF3方式进行编码。
RTMP_body:
字段名 |
类型 |
描述 |
Command Name |
字符串 |
命令名 "onStatus"。 |
Transaction ID |
数字 |
事务 ID 设置为 0。 |
Command Object |
Null |
onStatus 消息没有命令对象。 |
Info Object |
对象 |
一个 AMF 对象至少要有以下三个属性。 1."level" (字符串):这一消息的等级,"warning"、"status"、"error" 中的某个值; 2."code" (字符串):消息码,例如 "NetStream.Play.Start"; 3."description" (字符串):关于这个消息人类可读描述。 |
服务器只发这三个就可以满足客户端的需求
14、video data:type id=9=videodata
音视频格式以后专门讲解以为设计到H264与AAC还有Thunk 分包
15、audio data:type id=8=audiodata
音视频格式以后专门讲解以为设计到H264与AAC还有Thunk 分包
下一篇讲解AMF格式。