rtmp协议简单解析以及用其发送h264的flv文件

首先说下rtmp协议包的格式。握手之后,rtmp传输一个数据默认的长度是128bytes,这128bytes不包括包头的长度,只是数据的长度,文档上面没有说明,很憋了我一段时间,数据超过这个长度之后就要分块,超过128bytes的数据放到下一个块中,以此类推。块大小是可配置的,最大块是65535字节,最小块是128字节。块越大CPU使用率越低,但是也导致大的写入,在低带宽下产生其他内容的延迟。

       Rtmp协议是包头加包体(数据)组成,包头可以是4种长度的任意一种:12, 8, 4,  1 byte(s)。包头包含了head type、时间戳、amf size、amf type、streamID。

完整的RTMP包头有12字节,由下面5个部分组成:

用途

大小(Byte)

含义

Head_Type

1

包头

TIMER

3

时间戳

AMFSize

3

数据大小

AMFType

1

数据类型

StreamID

4

流ID

Head_Type占用RTMP包的第一个字节,这个字节里面记录了包的类型和包的ChannelID。Head_Type字节的前两个Bits决定了包头的长度。

Head_Type的前两个Bit和长度对应关系,header length是包头的长度:

Bits

Header Length

00

12 bytes

01

8 bytes

10

4 bytes

11

1 byte

一个视频流的第一个包必须是12bytes,也就是00,要告诉对方这个流的信息。

Head_Type的后面6个Bits记录着ChannelID,其为一下内容:

02

Ping 和ByteRead通道

03

Invoke通道 我们的connect() publish()和自字写的NetConnection.Call() 数据都是在这个通道的

04

Audio和Vidio通道

05 06 07

服务器保留,经观察FMS2用这些Channel也用来发送音频或视频数据

比如传一个视频流,第一个块的head type就是00 00 00 04.(0x04)。实测fms用4或者5在传影视频。官方文档上面说head_type可能会不止一个byte,但实际情况用一个byte也够了。

TiMMER - 时间戳

音视频的播放同步是由时间戳来控制的,单位是毫秒。如果时间戳大于或等于16777215(16进制0x00ffffff),该值必须为16777215,并且扩展时间戳必须出现,4bytes的扩展时间戳出现在包头之后包体之前。这个时间戳一般去flv文件里面每个tag里面的时间戳。

AMFSize - 数据大小

AMFSize占三个字节,这个长度是AMF长度,其实就是本次数据的长度。

AMFType - 数据类型

AMFType是RTMP包里面的数据的类型,占用1个字节。例如音频包的类型为8,视频包的类型为9。下面列出的是常用的数据类型:

0×01

Chunk Size

changes the chunk size for packets

0×02

Unknown

 

0×03

Bytes Read

send every x bytes read by both sides

0×04

Ping

ping is a stream control message, has subtypes

0×05

Server BW

the servers downstream bw

0×06

Client BW

the clients upstream bw

0×07

Unknown

 

0×08

Audio Data

packet containing audio

0×09

Video Data

packet containing video data

0x0A-0x0E

Unknown

 

0x0F

FLEX_STREAM_SEND

TYPE_FLEX_STREAM_SEND

0x10

FLEX_SHARED_OBJECT

TYPE_FLEX_SHARED_OBJECT

0x11

FLEX_MESSAGE 

TYPE_FLEX_MESSAGE 

0×12

Notify

an invoke which does not expect a reply

0×13

Shared Object

has subtypes

0×14

Invoke

like remoting call, used for stream actions too.

0×16

StreamData

这是FMS3出来后新增的数据类型,这种类型数据中包含AudioData和VideoData

StreamID - 流ID

占用RTMP包头的最后4个字节,是一个big-endian的int型数据,每个消息所关联的ID,用于区分其所在的消息流。

 

前面说包头有几种长度,第一个长度是12bytes,包含了全部的头信息,第一个数据流也就是流的开始必须是这个长度。

第二种8bytes的包,没有了streamID,发这种包,对方就默认此streamID和上次相同,一个视频数据在第一个流之后都可以使这种格式,比如一个1M的视频,第一次发128bytes用12bytes的包,之后每次发的数据都应该用8bytes的包。当然如果每次都用12bytes也没有问题。

第三种为4bytes,只有head_type和时间戳,缺少的对方认为与之前的一样,实际应用中很难出现。

第四中就只有head_type一个byte。如果一次数据超过了长度(默认是128bytes),就要分块,第一个块是12bytes或者8bytes的,之后的块就是1byte。因为分的N块,每一块的信息都是一样的,所以只需要告诉对方此次包的长度就行了。

例子:

例如有一个RTMP封包的数据03 00 00 00 00 01 02 14 00 00 00 00 02 00 07 63 6F 6E 6E 65 63 74 00 3F F0 00 00 00 00 00 00 08 ,,,
数据依次解析的含义 
03表示12字节头,channelid=3
000000表示时间戳 Timer=0
000102表示AMFSize=18
14表示AMFType=Invoke 方法调用
 00 00 00 00 表示StreamID = 0
//到此,12字节RTMP头结束下面的是AMF数据分析 
02表示String
0007表示String长度7
63 6F 6E 6E 65 63 74 是String的Ascall值"connect"
00表示Double
3F F0 00 00 00 00 00 00 表示double的0.0 
08表示Map数据开始

官方文档上面的例子:

例1展示一个简单的音频消息流。这个例子显示了信息的冗余。

         

 

Message Stream ID

Message Type ID

Time

Length

Msg # 1

  12345

  8 

1000

32

Msg # 2

  12345

  8 

1020

32

Msg # 3

  12345

  8 

1040

32

Msg # 4

  12345

  8 

1060

32

            

下表显示了这个流产生的块。从消息3开始,数据传输开始优化。在消息3之后,每个消息只有一个字节的开销。

     

 

Chunk Stream ID

Chunk Type

Header Data

No.of Bytes After Header

Total No.of

Bytes in the Chunk

Chunk#1

3

0

delta: 1000

length: 32

type: 8

stream ID:1234

(11bytes)

32

44

Chunk#2

3

2

20 (3 bytes)

32

36

Chunk#3

3

3

none(0 bytes)

32

33

Chunk#4

3

3

none(0 bytes)

32

33

 

   演示一个消息由于太长,而被分割成128字节的块。

 

Message Stream ID

Message TYpe ID

Time

Length

Msg # 1

12346

9 (video)

1000

307

 

下面是产生的块。

 

Chunk Stream ID

Chunk Type

Header Data

No. of Bytes after Header

Total No. of bytes in  the chunk

Chunk#1

4

0

delta: 1000

length: 307

type: 9

streamID: 12346

(11 bytes)

128

140 

Chunk#2

4

3

none (0 bytes)

128  

129

Chunk#3

4

3

none (0 bytes)

51

52

 

数据传输的限定大小默认是128bytes,可以通过控制消息来改变,RTMP的约定是当Chunk流ID为2,消息流ID为0的时候,被认为是控制消息。

 

 

具体传输的过程是这样的,首先双方先进行握手,握手过程官方文档上有说明,但是在flash10.1之后,adobe公司改了握手,文档上那个握手不能用了,至少播放AVC和ACC不能用,这东西太坑人了,改了又不说一声,而且一个本来简单的握手改的很是复杂,居然要依赖openssl加密,有必要吗。网上找不到有关文章,我只有看rtmpserver开源项目源码来弄。

       握手步骤没有变,但内容完全不一样,以前叫sample handshake,现在叫复杂握手(complex handshake)。

它的步骤是由三个固定大小的块组成,而不是可变大小的块加上头。握手开始于客户端发送C0,C1块。在发送C2之前客户端必须等待接收S1。在发送任何数据之前客户端必须等待接收S2。服务端在发送S0和S1之前必须等待接收C0,也可以等待接收C1。服务端在发送S2之前必须等待接收C1。服务端在发送任何数据之前必须等待接收C2。

网上有一个人说出了complex handshake的方式,http://blog.csdn.net/winlinvip/article/details/7714493,以下是他的说明:

 

scheme1和scheme2这两种方式,是包结构的调换。

 

 

key和digest的主要算法是:C1的key为128bytes随机数。C1_32bytes_digest = HMACsha256(P1+P2, 1504, FPKey, 30) ,其中P1为digest之前的部分,P2为digest之后的部分,P1+P2是将这两部分拷贝到新的数组,共1536-32长度。S1的key根据C1的key算出来,算法如下:

 

在Rtmpserver的sources\thelib\src\protocols\rtmp\inboundrtmpprotocol.cpp中有握手的代码,看他的说明也马马虎虎,我也看源码后才清楚具体过程。具体过程和他说的差不多,只是有些计算要用到openssl加密,所以要安装openssl,我也是从rtmpserver上面扣出来的代码然后自己改一下。

 

       握手之后就开始一些交互信令,这个如果按照文档上的来,根本不行,文档上省略的一些东西,坑人。

       我的交互过程是按照抓的FMS3.5的包来做的。握手之后首先client会发送一个connect消息过来。另外,RTMP交互的消息格式官方说明上有,还是比较清楚的。

字段名

类型

描述

命令名

字符串

命令名。设置为”connect”

传输ID

数字

总是设为1

命令对象

对象

含有名值对的命令信息对象

可选的用户变量

对象

任何可选信息

   下面是在连接命令的命令对象中使用的名值对的描述:

属性

类型

描述

示例值

App

字符串

客户端要连接到的服务应用名

Testapp

Flashver

字符串

Flash播放器版本。和应用文档中getversion()函数返回的字符串相同。

FMSc/1.0

SwfUrl

字符串

发起连接的swf文件的url

file://C:/ FlvPlayer.swf

TcUrl

字符串

服务url。有下列的格式。protocol://servername:port/appName/appInstance

rtmp://localhost::1935/testapp/instance1

fpad

布尔值

是否使用代理

true or false

audioCodecs

数字

指示客户端支持的音频编解码器

SUPPORT_SND_MP3

videoCodecs

数字

指示支持的视频编解码器

SUPPORT_VID_SORENSON

pageUrl

字符串

SWF文件被加载的页面的Url

http:// somehost/sample.html

objectEncoding

数字

AMF编码方法

kAMF3

 

 

上图中app的vod就是请求文件的路径。

表示使用amf0编码格式传输命令,如果是3就是表示用amf3。

 

之后server回复windows acknowledgement size-    set peer bandwidth- user control message(begin 0)- result(connect response)

User control message (用户控制信息)的格式官方文档上有,消息数据的头两个字节用于标识事件类型,0表示Stream Begin。事件类型之后是事件数据。事件数据字段是可变长的,此时的数据时00。

 

在发送了windows acknowledgement size之后client会回一个windows adknowledgement size,此时需要接收

 

然后server接收client发的一个create stream过来

 

 

接收到之后server返回一个_result

 

 

Server接收client发来的recv set buffer length - play commcand from

 

字段名

类型

描述

命令名

字符串

命令名,设为”play”

传输ID

数字

设置为0

命令对象

NULL

命令信息不存在,设为NULL

类型

流名

字符串

要播放的流名。对于播放一个FLV文件,则流名不带文件后缀(例如,"sample”)。对于回放MP3或ID3标签,必须在流名前加MP3(例如:”MP3:Sample”)。对于播放H264/AAC文件,必须在流名前加MP4前缀,并且指定扩展名,例如播放sample.m4v,则指定”mp4:sample.m4v”。

开始

数字

一个指定开始时间的可选参数。默认值是-2,意味着用户首先尝试播放流名中指定的直播流。如果流名字段中指定的直播流不存在,则播放同名的录制流。如果本字段设置为-1,则只播放流名字段中指定的直播流。如果,本字段为0,或正值,则在本字段指定的时间,播放流名字段中指定的录制流。如果指定的录制流不存在,则播放播放列表中的下一项。

时长

数字

指定播放时长的可选字段。默认值是-1。-1意味着播放一个直播流,直到没有数据可以活到,或者播放一个录制流知道结束。如果本字段位0,则播放一个录制流中从start字段中指定的时间开始的单个帧。假设,start字段中指定的是大于或等于0的值。如果本字段为正值,则以本字段中的值为时间周期播放直播流。之后,则以时长字段中指定的时间播放录制流?。(如果一个流在时长字段中指定的时间之前结束,则回放结束。)。如果传递一个非-1的负值,则把值当作-1。

Reset

布尔值

指定是否刷新先前的播放列表的BOOL值或数值。

 

Client发的play命了中包含的请求的文件名,上图是1(string ‘1’ 这个数据)。

 

然后server发送set chunk size - user control message(stream is recorded) - onstatus-play reset (AMF0 Command) - user control message(begin1) - onstatus-play start (AMF0 Command) - RtmpSampleAccess (AMF0 Data) –空audio数据 - onstatus-data start (AMF0 Data)

 

 

Set chunk size这里就是改默认128字节的消息,这里改成了4096,当然也可以不发次消息。注意,在改之前发送的消息如果超过了128bytes都要分块发送的之后就可以每块大小事4096了。

Begin这里,这个前2bytes是0,表示stream begin,和上面的begin是个类型,只是数据段是1,这个抓包有点问题,没有显示数据段而已,此begin 1之后,所有的包头里面的streamID都是1了,之前的都是0

字段

类型

描述

命令名

字符串

命令名。如果播放成功,则命令名设为响应状态。

描述

字符串

如果播放命令成功,则客户端从服务端接收NetStream.Play.Start状态响应消息。如果指定的流没有找到,则接收到NetStream.Play.StreamNotFound

响应状态消息。

 

 

这里的加载amf数据和解析amf数据(也就是交互的消息)可以用我写的一个amf0的解析模块来做。

 

然后就发送flv的元数据(metadata)了,如果没有也可以不发这个。之后再发音视频的配置信息项。最后就可以发送音视频数据了。

发送音视频数据的时候可以用以前的方式发:包头的AMFType是8或者9,数据是flv的 tag data(没有tag header)

 

也可以按照新的类型amf type为0x16,数据就是一个tag (header + data)来发。

 

其中的amf0数据请看这里

 

要注意下就是rtmp都是大端模式,所以发送数据,包头、交互消息都要填成大端模式的,但是只有streamID是小端模式,这个也是文档没有说明,rtmp坑人的地方。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值