存档

文章标签 ‘RTMP’

RTMP 协议研究(转载)

2012年2月22日
RTMP 协议研究(转载)已关闭评论

原文地址:http://blog.csdn.net/chenyanxu/archive/2009/09/02/4511087.aspx
正文开始:

RTMP 协议研究

1 协议研究概述
   协议设计和分析一直都是在工作遇到,正好在这里总结一下,说到协议,在这个网络的时代,没有人可以离开它了。他存在我们生活中的任何角落,只不过我们平时,并没有注意到它的存在,可以这么说如果没有协议,我们生活和日常的工作生产都不能进行。如果仔细想想你生活中用到的所有东西,协议已经包含其中。那到底什么是协议呢?说的简单一点就是双方达成的共识,以便更好的交流,理论上协议是什么呢?如果学过《信号与系统》的人都知道有个简单的道理,就是信息在经过一个管道的符号集,到另一个符号集时信息不会丢失。

    任何复杂的事物都有个最简单的本质,网络上的协议也是这样,有个最基本的本质。除去上下层的概念,协议就只剩下通信双方实体的规则。

   一般的协议都包含最基本的协议头,不管是物理层、链路层、还是网络层,这个头就构成了协议的本质东西。通常协议头要包含以下最基本的三项信息:

双方实体的唯一标示,用来标示通信双方的实体。
类型描述或者是净核描述,标志净核的内容。
协议净核的长度,用来在萃取净核的内容应用。
   其中,前两项是必须要有的,没有他们,通信双方的交互根本得不到保证,第三项在不太灵活的通信中可以去掉,而有第二项的类型推出。

    协议的丰富性,有净核的多样性体现。

   协议头除了以上的三项,还可以增加更多的信息(比如控制信息、时间信息等),取决于具体的应用。找到这些基本的东西,再去看协议的时候,能够更好的抓住协议的主体进行分析和设计了。

2 RTMP 协议概述
   RTMP 协议是被 Flash 用于对象、视频、音频的传输。该协议建立在 TCP 协议或者轮询 HTTP 协议之上, RTMP 协议就像一个用来装数据包的容器,这些数据可以是 AMF 格式的数据,也可以是 FLV 中的视 / 音频数据。一个单一的连接可以通过不同的通道传输多路网络流,这些通道中的包都是按照固定大小的包传输的 .

3 RTMP 协议部分

3.1 协议头
struct RTMP_HEAD

{

      char cChannelid : 6;// 第一个字节的后 6 位

      char cCheadsize ; // 第一个字节的头两位

      char cTimer[3];  // 三个字节表示的时间信息

      char cLength[3]; // 三个字节表示的长度

      char cDatatype; // 数据类型

      char sStreamid[4]; // 流标识

};

这里有三个最基本的元素(唯一标示 )、(类型 )和(净核的长度 )分别是: cChannelid 、 cDatatype 和 cLength 。

3.2 数据类型
数据类型 决定了协议上层可以做的具体的事情,和使用协议的人必须遵循的规则。

同时数据类型 说明了净核 的基本内容。

RTMP 数据类型:

0×01,  Chunk Size,  changes the chunk size for packets 
0×02,  Unknown,   anyone know this one? 
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,  anyone know this one? 
0×08,  Audio Data,  packet containing audio 
0×09,  Video Data,  packet containing video data 
0×0A-0×11, Unknown,  anyone know? 
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. 

3.3 协议的净核
   RTMP 的协议净核是用 AMF 格式来描述, AMF 格式本身的产生就是为了 RTMP 协议服务的,最初的 RTMP 采用 XML 的形式传输数据,但 XML 只是字符形式的值对的格式传输数据,而随着应用的普及这完全不能满足要求了,比如对象、结构、数组,甚至可以是数据集,配合 DataGrid 组件可以很方便地显示数据。
为了处理复杂数据类型,采用一种独有的方式使 Flash 与应用服务器间可以来回传送数据势在必行。于是 AMF 应运而生。

AMF 是 Adobe 独家开发出来的通信协议,它采用二进制压缩,序列化、反序列化、传输数据,从而为 Flash 播放器与 Flash Remoting 网关通信提供了一种轻量级的、高效能的通信方式。 
AMF 最大的特色在于可直接将 Flash 内置对象,例如 Object, Array, Date, XML ,传回服务器端,并且在服务器端自动进行解析成适当的对象,这就减轻了开发人员繁复工作,同时也更省了开发时间。由于 AMF 采用二进制编码,这种方式可以高度压缩数据,因此非常适合用 来传递大量的资料。数据量越大, Flash Remoting 的传输效能就越高,远远超过 Web Service 。至于 XML, LoadVars 和 loadVariables() ,它们使用纯文本的传输方式,效能就更不能与 Flash Remoting 相提并论了。

注意: Flash Remoting 需要浏览器支持 Binary POST , Flash 播放器在 Netscape 6.x. 环境下运行 Flash Remoting 会不起作用( Flash Remoting 调用没有效果也不返回错误), Netscape 7 已经纠正了这个 bug 。对于早期 Safari 和 Chimera 版的苹果机也有这个问题。
同样是轻量级数据交换协议,同样是通过调用远程服务,同样是基于标准的 HTTP 和 HTTPS 协议, Flash Remoting 为什么选择了使用 AMF 而放弃了 SOAP 与 Flash 播放器通信呢  有如下原因:
SOAP 将数据处理成 XML 格式,相对于二进制的 AFM 太冗长了; 
AMF 能更有效序列化数据;因为 AMF 的初衷只是为了支持 Flash ActionScript 的数据类型,而 SOAP 却致力于提供更广泛的用途; 
AMF 支持 Flash 播放器 6 只需要浏览器增加 4 KB 左右(压缩后)的大小,而 SOAP 就大多了; 

SOAP 的一些头部文件请求在 Flash 播放器 6 不支持。那 Flash 播放器 6 为什么能访问基于 SOAP 的 Web 服务呢?原来 Flash Remoting 网关将 SOAP 请求在服务器端与转换成 AFM 格式,然后利用 AFM 与 Flash 播放器通信。

另外, AMF 包中包含 onResult 事件(比如说 response 事件)和 onStatus 事件(比如说 error 事件),这些事件对象在 Flash 中可以直接使用。 
AMF 从 Flash MX 时代的 AMF0 发展到现在的 AMF3 。 AMF3 用作 Flash Playe 9 的 ActionScript 3.0 的默认序列化格式,而 AMF0 则用作旧版的 ActionScript 1.0 和 2.0 的序列化格式。 在网络传输数据方面, AMF3 比 AMF0 更有效率。 AMF3 能将 int 和 uint 对象作为整数( integer )传输,并且能序列化 ActionScript 3.0 才支持的数据类型 , 比如 ByteArray , XML 和 Iexternalizable 。

AMF 很好的解决了内容的丰富性。(具体 AMF 格式参考附件格式文档)

3.3.1 AMF中的数据类型Data Types

AMF0 supports the following data types (with their type field values):

NUMBER = 0x00
BOOLEAN = 0x01
STRING = 0x02
OBJECT = 0x03
MOVIECLIP = 0x04
NULL_VALUE = 0x05
UNDEFINED = 0x06
REFERENCE = 0x07
ECMA_ARRAY = 0x08
OBJECT_END = 0x09
STRICT_ARRAY = 0x0a
DATE = 0x0b
LONG_STRING = 0x0c
UNSUPPORTED = 0x0d
RECORD_SET = 0x0e
XML_OBJECT = 0x0f
TYPED_OBJECT = 0x10

Binary Format
AMF format for a value/object consists of a type byte (see above) followed by zero or more bytes. This section describes the bytes following the type byte for various types.

NUMBER (type byte: 0x00)
Numbers are stored as 8 byte (big endian) float double. On x86 you can just byteswap a double to encode it correctly.

BOOLEAN (type byte: 0x01)
A boolean is encoded in one byte. FIXME: is true sent as 0xff? 0x01?

STRING (type byte: 0x02)
A string is encoded as a 2 byte (big endian) count (number of bytes) followed by that many bytes of text. Note: there is no null terminator.

I think the text is assumed to be UTF-8. Can someone double check me on this?

NULL_VALUE (type byte: 0x05)
A null has zero bytes following the type byte

UNDEFINED (type byte: 0x06)
A undefined has zero bytes following the type byte

OBJECT (type byte: 0x08)
An object is encoded as a series of key/value pairs. The key is encoded as a STRING (above) WITH NO TYPE BYTE, and the value is any AMF value.

The object encoding is terminated by 0x000009 (that is a zero length string key, followed by the OBJECT_END type byte described below.

OBJECT_END (type byte: 0x09)
This is not really a value, but a marker for the end of an OBJECT. See above.

STRICT_ARRAY (type byte: 0x0a)
This is the encoding for arrays such as ["foo", "bar", 1, 2, 3]. For a hash (a set of key/value pairs) you’ll need to use OBJECT above.

An array is encoded as 4 byte (big endian) integer which is the number of elements in the array, followed by that many AMF values.

That’s it. There’s no terminator of any kind.

Use in shared object files
While most AMF objects are just a value, there is a special variation used by shared object files for properties. Rather than start with the type field, followed by the length, it starts with a byte count, then the name, and then the regular AMF type field, the length, and then the data.

3.4 客户端和服务器的连接过程

3.4.1客户和服务器的握手

   Flash Player 以系统时间作为种子通过某种算法生成的数字签名,大小是 1537 字节向服务器发起第一次握手,服务器根据客户端的数字签名产生一个 3073 字节的验证包,给客户端,客户端在接受到服务器的回应以后会发送一个 1536 字节的回复。

具体的流程:

发送第一次握手包 handshark1
接收第二次握手包 handshark2
发送的三次握手包 handshark3
第一个握手包 handshark1 和服务器的回复握手包 handshark2 都是以 0X03 开头。这三次握手不是 RTMP 协议本身的内容,所以在这并没有包含 RTMP  的协议头。是服务器的厂家自己产品做验证用的,严格的说就是你必须用  Adobe 的客户端和服务器才能使用我的协议。

3.4.2客户和服务器通信
   具体连接和请求视频的过程

发送 rtmp_connect 命令
发送本地带宽消息 . 默认是 125000
服务器返回服务器带宽信息
服务器返回本地带宽信息
服务器返回连接成功消息 "NetConnection.Connect.Success"
客户端发送创建流请求 encodeCreateStreamPacket
服务器返回创建流成功消息
客户端发送播放文件消息 Rtmp_Play
服务器返回 TYPE_CHUNK_SIZE 消息
服务器返回开始播放消息 "NetStream.Play.Start"
服务器返回视频信息 (TYPE_STREAM_METADATA) ,包括大小,宽高,速率等等信息--文件长度可以在这里推算出来
RTMP 的净核决定了内容服务, adobe 的服务器采用的 AMF 格式的字串命令来控制视频的传输和播放,具体的字串命令信息如下:

(注:字串的定义有厂家( adobe )自己定义,只要满足 AMF 的格式就可以) 
NetConnection.Call.Failed
NetConnection.Call.BadVersion 
NetConnection.Connect.AppShutdown
NetConnection.Connect.Closed
NetConnection.Connect.Rejected
NetConnection.Connect.Success
NetStream.Clear.Success
NetStream.Clear.Failed
NetStream.Publish.Start
NetStream.Publish.BadName
NetStream.Failed
NetStream.Unpublish.Success
NetStream.Record.Start
NetStream.Record.NoAccess
NetStream.Record.Stop
NetStream.Record.Failed
NetStream.Play.InsufficientBW
NetStream.Play.Start
NetStream.Play.StreamNotFound
NetStream.Play.Stop
NetStream.Play.Failed
NetStream.Play.Reset
NetStream.Play.PublishNotify
NetStream.Play.UnpublishNotify
NetStream.Data.Start
Application.Script.Error
Application.Script.Warning
Application.Resource.LowMemory
Application.Shutdown
Application.GC
Play
Pause
demoService.getListOfAvailableFLVs
getStreamLength
connect
app
flashVer
swfUrl
tcUrl
fpad
capabilities
audioCodecs
audioCodecs
videoCodecs
videoFunction
pageUrl
createStream
deleteStream
duration
framerate
audiocodecid
audiodatarate
videocodecid
videodatarate
height
width

3.4.2数据的萃取
      在服务器返回开始播放消息 "NetStream.Play.Start" 之后,服务器就会开始给客户端传输数据了,一般数据的萃取都是先解析协议的头,然后根据协议头中数据类型和净核长度就可以把数据部分取出, RTMP 协议也是这样。

struct RTMP_HEAD

{

      char cChannelid : 6;// 第一个字节的后 6 位

      char cCheadsize ; // 第一个字节的头两位

      char cTimer[3];  // 三个字节表示的时间信息

      char cLength[3]; // 三个字节表示的长度

      char cDatatype; // 数据类型

      char sStreamid[4]; // 流标识

}

      首先判断 cDatatype 是那种类型,然后根据不同的类型进行萃取数据部分,进行不同的处理,获取视频的数据的方式先看是否是一下的类型:

0×08  Audio Data  packet containing audio 
0×09  Video Data  packet containing video data 

根据净核的长度读取出内存中的音视频数据,这里的音视频数据是有一定编码格式的数据,这个取决于应用的具体配置, Flash play 使用的是 FLV 的格式。要对这部分数据进行存取,还有做一部分工作,对 FLV 的视频数据进行去壳,取出数据保存文件就可以了。

————————————————————————————

以下是百度百科上转载过来的,方便与上文对照理解:

rtmp

Real Time Messaging Protocol(实时消息传送协议协议)概述

实时消息传送协议是Adobe Systems公司为Flash播放器和服务器之间音频、视频和数据传输开发的私有协议。它有三种变种:

1)工作在TCP之上的明文协议,使用端口1935;

2)RTMPT封装在HTTP请求之中,可穿越防火墙;

3)RTMPS类似RTMPT,但使用的是HTTPS连接;

介绍:

RTMP协议是被Flash用于对象,视频,音频的传输.该协议建立在TCP协议或者轮询HTTP协议之上.

RTMP协议就像一个用来装数据包的容器,这些数据可以是AMF格式的数据,也可以是FLV中的视/音频数据.

一个单一的连接可以通过不同的通道传输多路网络流.这些通道中的包都是按照固定大小的包传输的.

网络连接(Connection)

一个Actionscript连接并播放一个流的简单代码:

复制内容到剪贴板代码:

var videoInstance:Video = your_video_instance;

var nc:NetConnection = new NetConnection();

var connected:Boolean = nc.connect("rtmp:/localhost/myapp");

var ns:NetStream = new NetStream(nc);

videoInstance.attachVideo(ns);

ns.play("flvName");

默认端口为1935

握手

Client → Server :向服务器发出握手请求.这不属于协议包一部分,该握手请求第一个字节为(0×03),其后跟着1536个字节.经管看上去这部分的内容对于RTMP协议来说并不是至关重要的,但也不可随意对待.

Server → Client :服务器向客户端回应握手请求.这部分的数据仍然不属于RTMP协议的部分.该回应的其实字节仍然为(0x03),但是后边跟着个长度为1536个字节 (一共为3072 )的包块.第一个1536块看上去似乎可以是任意内容,甚至好像可以是Null都没有关系.第二个1536的代码块,是上一步客户端向服务器端发送的握手请求的内容.

Client→Server:把上一步服务器向客户端回应的第二块1536个字节的数据块.

至此客户端与服务器端的握手结束,下面将发送RTMP协议的包内容.

Client → Server :向服务器发送连接包.

Server → Client :服务器回应.

… …. 等等… …

RTMP 数据类型

0×01 Chunk Size changes the chunk size for packets

0×02 Unknown anyone know this one?

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 anyone know this one?

0×08 Audio Data packet containing audio

0×09 Video Data packet containing video data

0x0A – 0×11 Unknown anyone know?

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.

Shared Object 数据类型

0×01 Connect

0×02 Disconnect

0×03 Set Attribute

0×04 Update Data

0×05 Update Attribute

0×06 Send Message

0×07 Status

0×08 Clear Data

0×09 Delete Data

0x0A Delete Attribute

0x0B

Initial Data

RTMP包结构

RTMP包 包含一个固定长度的包头和一个最长为128字节的包体.包头可以是下面4种长度的任意一种:12, 8, 4, or 1 byte(s).

第一个字节的前两个Bit很重要,它决定了包头的长度.它可以用掩码0xC0进行"与"计算.下面的表格罗列了可能的包头长度:Bits Header Length

00 12 bytes

01 8 bytes

10 4 bytes

11 1 byte

我们在这里讨论关RTMP包结构的问题并不是非常的详细.我们在以后有时间会讨论关于AMF的问题(敬请期待…:loveliness:),其实RTMP包结构就是使用了AMF格式.

关于流的操作我们需要进一步研究,在论坛中的http://www.openred5.com/bbs /viewthread.php?tid=175&extra=page%3D1这篇文章研究的还是不错的,大家可以参考.不过下面可以列一个关于客户端向服务器端发送流的流程:

Client→Server :发送一个创建流的请求.

Server→Client :返回一个表示流的索引号.

Client→Server :开始发送.

Client→Server :发送视音频数据包(这些包在同一个频道(channel)并用流的索引号来唯一标识).

IT技术 ,

windows下编译librtmp、rtmpdump(转载)

2012年2月22日
windows下编译librtmp、rtmpdump(转载)已关闭评论

原文网站:http://zhaostudy2.blog.163.com/blog/static/1353502052011182538414/

    这段时间做实时视频的网页直播遇到了很多困难。

    开始时,迫于项目时间的压力,觉得没有足够的时间学习和分析如何将实时视频发送到RTMP流媒体服务器作为实时流,只好使用最粗糙的做法是:先把获取到的实时视频以RTP包的形式 发送给本机,然后本机程序中调用ffmpeg将接收到的RTP包 以RTMP的形式转发到Red5,最后,从网页上获取播放列表,播放实时视频。

    这种做法中存在很多问题:(1)多了一层rtp包到rtmp服务器的转发,浪费很多处理器的时间。(2)多了一层转发,系统稳定性很有问题。在视频流转发了一定时间后,ffmpeg会奇怪地停止转发,原因不明。(3)ffmpeg的视频流播放控制难以实现。在网页上停止播放和继续播放视频时,既要控制发送 RTP包,又要控制RTMP包,很麻烦。

    后来,分析了一下ffmpeg的源代码,发现FFmpeg中对RTMP的支持部分就是使用了RTMPDump中的librtmp。于是,我就打算直接使用librtmp与Red5建立rtmp连接,将实时视频直接发到Red5。

    最近过年,在家里闲着,就认真研究一下如何使用librtmp直接将实时视频发送到Red5。我们首先要做的就是编译出librtmp的动态库和静态库。

    RTMPDump项目官方网站在:http://rtmpdump.mplayerhq.hu/ 。对RTMP协议的实现在其中librtmp中。这是一个匈牙利人在2009年,Adobe公司还没有公开RTMP协议的情况下对RTMP协议的实现得。 官方网站中只提供了程序源代码和动态链接库(dll),要在开发中方便地使用RTMPDump,还需要自己编译它的静态库(lib)。

==> 编译librtmp静态库

    从官方网站http://rtmpdump.mplayerhq.hu/ 下载RTMPDump源代码。

    要编译librtmp,还需要另外3个库:zlib、OpenSSL、PolarSSL。

    zlib是用于数据压缩的函数库,数据压缩效果比较好,早在1995年就发布了第一版,目前仅支持LZ77变种算法、DEFLATE算法。(http://www.zlib.net/)。

    OpenSSL和PolarSSL 是对SSL(Security Socket Layer,加密套接字协议层)的实现。(http://www.openssl.org/ http://polarssl.org/)。

    (1)使用VC++6.0新建一个静态库工程,命名为librtmp,如下图所示:

[原创] 实时视频在网页直播--windows下编译librtmp、rtmpdump - 小小研究院 - 小小研究院

     (2)把RTMPDump源代码目录下的librtmp目录下的所有文件 复制到工程目录librtmp\下,并在VC++6.0中的Source Files和Header Files文件夹中添加librtmp相应的文件,如下图所示:

[原创] 实时视频在网页直播--windows下编译librtmp、rtmpdump - 小小研究院 - 小小研究院

     (3)下载zlib开发包 http://download.csdn.net/source/3013660。把其中的zdll.lib、zlib.def、zlib.h、zconf.h放到新建的工程目录librtmp\下。

     (4)下载openssl开发包 http://download.csdn.net/source/3013684。把其中的libeay32.lib、ssleay32.lib 及openssl文件夹 复制到工程目录librtmp\下,并在VC++6.0的“工具”->“选项”->“目录”-> “ Include files ”中添加当前的工程目录librtmp\。如下图所示:

[原创] 实时视频在网页直播--windows下编译librtmp、rtmpdump - 小小研究院 - 小小研究院

    (5) 下载PolarSSL源代码 http://download.csdn.net/source/3013696。解压出来,用VC++6.0打开\visualc\polarssl.dsw ,可以编译出静态库(polarssl.lib)。然后将头文件所在的文件夹polarssl\ 和polarssl.lib复制到工程目录librtmp\下。

    (6)编译静态库工程,这时会在多个文件中出现这样一个错误: error C2065: ‘__FUNCTION__’ : undeclared identifier 。解决办法是,在存在这个错误的.c文件的中添加一个宏定义:#define __FUNCTION__ "" 。问题就解决了。再编译工程即可得到librtmp.lib,如下图所示:

[原创] 实时视频在网页直播--windows下编译librtmp、rtmpdump - 小小研究院 - 小小研究院

[原创] 实时视频在网页直播--windows下编译librtmp、rtmpdump - 小小研究院 - 小小研究院

     (7)但是,这样编译出来的librtmp.lib在使用的时候会出现很多个外部符号未定义的错误。如下图所示:

[原创] 实时视频在网页直播--windows下编译librtmp、rtmpdump - 小小研究院 - 小小研究院

    这是librtmp的条件编译导致的问题,解决方法是:在rtmp_sys.h中把代码:

#ifdef _XBOX
#include <xtl.h>
#include <winsockx.h>
#define snprintf _snprintf
#define strcasecmp stricmp
#define strncasecmp strnicmp
#define vsnprintf _vsnprintf

#else /* !_XBOX */
#include <winsock2.h>
#include <ws2tcpip.h>
#endif

    改为

#include <winsock2.h>
#include <ws2tcpip.h>

#define snprintf _snprintf
#define strcasecmp stricmp
#define strncasecmp strnicmp
#define vsnprintf _vsnprintf

    然后,删除rtmp.c中的如下代码:

#ifdef _DEBUG
  fwrite(buf, 1, len, netstackdump);
#endif

#ifdef _DEBUG
extern FILE *netstackdump;
extern FILE *netstackdump_read;
#endif

#ifdef _DEBUG
      fwrite(ptr, 1, nBytes, netstackdump_read);
#endif

(8)编译rtmp.c即可得到librtmp.lib

    我已经将rtmpdump编译好的静态库、动态库以及源代码打成一个包,放到 http://download.csdn.net/source/3014336。如果不想自己编译,可以从这里下载,也可以直接下载下面的附件:http://dl.iteye.com/topics/download/3d9886b0-7e7a-3264-ba43-c23d207f719c

———————————-

林祥杰点评:

openssl和PolarSSL只需要用到一个,若不使用ssl都可以不添加,源代码里有宏CRYPTO可以关闭

IT技术 , ,

RTMP协议详解(转)

2012年2月22日
RTMP协议详解(转)已关闭评论

Real Time Messaging Protocol(实时消息传送协议协议)是Adobe Systems公司为Flash播放器和服务器之间音频、视频和数据传输开发的私有协议。

具体使用RTMP的AS代码大概如下:

var videoInstance:Video = your_video_instance;

var nc:NetConnection = new NetConnection();

var connected:Boolean = nc.connect("rtmp://localhost/myapp");

var ns:NetStream = new NetStream(nc);

videoInstance.attachVideo(ns);

ns.play("flvName");

Adobe也在官方网站已经提供了RTMP协议的官方文档说明,为什么要写这个系列文章最大的原因只是对前一段工作的一个总结和回顾,最近两个月,实现了一个RTMP Server的c++版本,把公司的流媒体服务和flash无缝对接起来。希望我的文字能给后来研究这个协议的同学有一定的帮助。

RTMP协议是一个基于TCP的高层协议族,当然这个玩意据说还有UDP协议版本的,不过现在还没有出来,好像Adobe下一版本的FMS会提供支持。下文将要描述的是TCP协议版本的协议。

RTMP协议的概要理解:

RTMP协议是为了和flash之间交换信令以及媒体数据。为了提高使用效率信令和媒体数据都是使用相同的机制。因为是相同的机制Adobe就整出来了一些比较搞人的概念,当然每个协议第一次接触都是比较难理解的。

在RTMP协议中信令和媒体数据都称之为Message,在网络中传输这些Message,为了区分它们肯定是要加一个Message head的,所以RTMP协议也有一个Message head,还有一个问题因为RTMP协议是基于TCP的,由于TCP的包长度是有限制的(一般来说不超过1500个字节),而RTMP的Message长度是有可能很大的,像一个视频帧的包可能会有几十甚至几千K,这个问题就必然有一个分片的问题,在RTMP协议中对应的说法就是chunk,每一个Message + head都是由一个和多个chunk组成的。到这里对RTMP协议的概要理解就算完了。

RTMP的字节序:
       RTMP的字节序和大多数网络协议一样是大端序,也有一些字段是小端序的,不过都有特殊的说明。
RTMP的head组成

         RTMP的head在协议中的表现形式是chunk head,前面已经说到一个Message + head可以分成一个和多个chunk,为了区分这些chunk,肯定是需要一个chunk head的,具体的实现就把Message head的信息和chunk head的信息合并在一起以chunk head的形式表现。

一个完整的chunk的组成如下图所示

Chunk basic header:

该字段包含chunk的stream ID和 type 。chunk的Type决定了消息头的编码方式。该字段的长度完全依赖于stream ID,该字段是一个可变长的字段。

Chunk Msg Header:0, 3 ,7, 11

该字段包含了将要发送的消息的信息(或者是一部分,一个消息拆成多个chunk的情况下是一部分)该字段的长度由chunk basic header中的type决定。

Extend Timestamp: 0 ,4 bytes

该字段发送的时候必须是正常的时间戳设置成0xffffff时,当正常时间戳不为0xffffff时,该字段不发送。当时间戳比0xffffff小该字段不发送,当时间戳比0xffffff大时该字段必须发送,且正常时间戳设置成0xffffff。

Chunk Data
        实际数据(Payload),可以是信令,也可以是媒体数据。
Chunk basic header:
   chunk basic head的长度为1~3个字节,具体长度主要是依赖chunk stream ID的长度,所谓chunk stream ID是flash server用来管理连接的客户端的信令交互的标识,在red5的文档中称之为channel ID,协议最大支持65597个streamID 从3~65599。ID 0,1,2为协议保留,0代表ID是64~319(第二个byte + 64);1代表chunk stream ID为64~65599((第三个byte)* 256 + 第二个byte + 64)(小端表示);2代表该消息为低层的协议(在RTMP协议中控制信令的chunk stream ID都是2)。3~63的chunk stream ID就是该byte的值。没有附加的字段来标识chunk stream streamID。在这里要指出的是虽然RTMP的chunk stream ID理论是可以达到65599,但是目前使用的chunk stream ID很少,2~7都是约定的,8是用来传输publish play等命令,其他的chunk stream ID目前好像没有使用,至少我不知道用来干嘛的。
      所以目前chunk basic head的长度一般为1个字节。这一个字节由两部分组成
                           +++++++++++++++++++
                            +fmt    + cs id               +
                            +++++++++++++++++++
      fmt占两个bit用来标识紧跟其后的chunk Msg Header的长度,cs id占六个bit。
      两位的fmt取值为 0~3,分别代表的意义如下:
      case 0:chunk Msg Header长度为11;
      case 1:chunk Msg Header长度为7;
      case 2:chunk Msg Header长度为3;
      case 3:chunk Msg Header长度为0;
      所以 只有一个字节的chunk basic header取值为 chunk basic header = (fmt << 6) | (cs id).

Chunk Msg Header:

Chunk Msg Header的长度是可变的,Chunk Msg Header可变的原因是为了压缩传输的字节数,把一些相同类型的chunk的head去掉一些字节,换句话说就是四种类型的包头都可以通过一定的规则还原成11个字节,这个压缩和还原在RTMP协议中称之为复用/解复用。

那我们以11个字节的完整包头来解释Chunk Msg Header,如图所示

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+ timestamp + message length + message type id + message stream id +

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Timestamp:3bytes

对于type 0的chunk,绝对时间戳在这里表示,如果时间戳值大于等于0xffffff(16777215),该值必须是0xffffff,且时间戳扩展字段必须发送,其他情况没有要求。

message length:3bytes

Message的长度,注意这里的长度并不是跟随chunk head其后的chunk data(Payload)的长度,而是前文提到的一条信令或者一帧视频数据或音频数据的长度。前文提到过信令或者媒体数据都称之为Message,一条 Message可以分为一条或者多条chunk。

message type id:1byte

Message的类型ID,具体的值将在后文专门来讨论。

message stream id:4bytes

message stream id的字节序是小端序,这个字段是为了解复用而设计的,RTMP文档上说的相当的模糊,

message stream ID可以使任意值,不同的消息流复用成相同的chunk stream,基于它们的ID能够解复用。于chunk stream 是相关的,这个字段是一个不透明的值没有整明白什么意思,我的理解就是用来标识和服务器连接的flash端的序号。

长度是7 bytes 的chunk head,该类型不包含stream ID,该chunk的streamID和前一个chunk的stream ID是相同的,变长的消息,例如视频流格式,在第一个新的chunk以后使用这种类型,注意其中时间戳部分是相对时间,为何上一个绝对时间之间的差值 如图所示:

++++++++++++++++++++++++++++++++++++++++++++++++++++++

+ timestamp    delta + message length + message type id +

++++++++++++++++++++++++++++++++++++++++++++++++++++++

        3 bytes的chunk head,该类型既不包含stream ID 也不包含消息长度,这种类型用于stream ID和前一个chunk相同,且有固定长度的信息,例如音频流格式,在第一个新的chunk以后使用该类型。如图所示:

                           ++++++++++++++++++++

                           + timestamp    delta +

                           ++++++++++++++++++++

        0 bytes的chunk head,这种类型的chunk从前一个chunk得到值信息,当一个单个消息拆成多个chunk时,这些chunk除了第一个以外,其他的都应该使用这种类型,

chunk的长度:

chunk的长度初始长度固定为 128个字节,但是这个值并不是不可变的,在客户端和服务端建立连接以后,客户端和服务端都可以通过发送信令的方式来通知对端修改chunk的长度,理论上来说可以修改chunk的最长长度为65536。这里chunk的长度是指chunk的数据部分的长度,即chunk data(payload)的长度,如果一条Message的数据长度超过了chunk的长度,就必须把Message分割成多条chunk,即如果一条视频类型Message长度为2000个byte,chunk长度为1500,则该Message将会分割成两条chunk,第一条的chunk data长度为1500,第二条的chunk data长度为500。当然这两条chunk的chunk head肯定是不同的,其中第二条chunk的chunk head就是0字节的。

转自《RTMP协议详解(一) (二) (三)

IT技术 , , ,