首页 > 代码库 > 实时消息传输协议 RTMP(Real Time Messaging Protocol)

实时消息传输协议 RTMP(Real Time Messaging Protocol)

原文: http://blog.csdn.net/defonds/article/details/17403225

译序:本文是维基百科关于 RTMP 的解释, 关于 RTMP 官方规范参见 RTMP 规范,关于 RTMP 官方规范的中文版,参见《Adobe 官方公布的 RTMP 规范》。以下是维基百科原文:

        实时消息传输协议(RTMP)最初是由 Macromedia 为互联网上 Flash player 和服务器之间传输音频、视频以及数据流而开发的一个私有协议。Adobe 收购 Macromedia 购以后,公布了这个协议的一部分,以备公共使用。

        RTMP 协议有多个变种:
        1. 工作在 TCP 协议之上,并使用默认端口号 1935 的明文协议。
        2. RTMPS 使用 TLS/SSL 连接的 RTMP 协议。
        3. RTMPE 使用 Adobe 自己的安全机制的加密 RTMP。虽然它的实现细节是私有的,但使用的是行业标准加密原语。RTMPE 的设计是有缺陷的,它本身并不提供实际的安全。
        4. RTMPT 封装在 HTTP 请求内部以穿越防火墙的协议。RTMPT 常用于建立 TCP 端口 80 和 443 的请求以绕开很多公司的流量过滤。封装的会话中可能会携带纯 RTMP、RTMPS 或者 RTMPE 包。

        尽管 RTMP 协议的本意是一个用于播放 Flash 视频的协议,但它也往往用于其他应用,比如 Adobe LiveCycle数据服务。

 

        基本操作
        RTMP(RTMFP 除外)是一个基于 TCP 的、持久连接并提供低延迟通信的协议。为了能够顺利地传输流,并且传递尽可能多的信息,RTMP 对流进行分段,客户端和服务器可以对分段长度进行协商,尽管有时分段长度是不变的:对于音频数据默认分段长度是 64 字节,视频数据和大部分其他数据类型默认分段长度是为 128 字节。来自不同流的段会被隔离,并对单一连接的段进行合成。对于比较长的数据块,RTMP 会在每一段中携带一个单字节头,所以开销很小。然而,事实应用中,不同的段并不互相交叉。交叉和合成是在数据包层完成的,RTMP 包穿过不同的活跃通道进行交叉,用这种方法来保证每个通道都满足各自的带宽、延迟以及其他一些服务质量的需求。这种模型下交叉的 RTMP 包被视为不可分割的,并且在分段级别是不交叉的。
        RTMP 定义了一些虚拟通道,通过它们可以发送和接收 RTMP 包,并且这些通道彼此是独立运作的。例如,可能会有一个用于处理 RPC 请求和响应的通道,一个用于视频流数据的通道,一个用于音频流数据的通道,一个用于带外控制信息(分段长度协商,等等)的通道,等等。在一个典型的 RTMP 会话中,在任意给定时间内,可能会有几个通道是同时活跃的。当 RTMP 数据被编码时,会产生一个包头。包头定义了其他一些事项,要发送到通道的 id,这一包产生时的 timestamp (如果需要的话),以及这一包的有效负载。包头后紧跟这一包实际负载的内容,包的内容是在发送给连接前根据当前协商好的分段长度分割好的。包头自己不会分段,并且包头的长度也不会被计入这一包第一个分段的长度中去。换句话说,分段的主题仅仅是 RTMP 的包负载(音频数据)。
        更高一层讲,RTMP 压缩 MP3 或者 AAC 音频和 FLV1 视频多媒体流,并使用 AMF (Action Message Format) 协议进行远程方法调用 (RPCs)。所需的 RPC 服务都是异步的,它们使用单一的 client/server request/response 模型,因此不需要实时通信。
        加密
        RTMP 会话可以使用以下两种方法的任意一种进行加密:
        使用行业标准 TLS/SSL 机制。底层的 RTMP 会话呗简单地封装在一个普通的 TLS/SSL 会话中。
        使用 RTMPE,将 RTMP 会话封装在一个轻量级的加密层里。
        普遍认为,会话开始时的 TLS/SSL 握手是非常计算密集型的。Adobe 开发了 RTMPE 作为一个轻量级的替代,给提供加密服务的高流量的站点一个实用的选择。Adobe 通告 RTMPE 作为一个安全内容传递的方法,以避免模拟客户端的操作,这种说法是错误的。RTMPE 仅使用了 Diffie-Hellman 机制,没有提供任何一方的身份验证,这很容易在会话初始化时受到中间人攻击。
        HTTP 隧道
        在 RTMP 隧道 (RTMPT) 中,RTMP 数据被密封起来并通过 HTTP 进行交换,来自客户端(在这种情况下客户端是为 media player)的信息发送给服务器上的端口 80 (HTTP 默认端口号)。
        由于 HTTP 头的缘故,RTMPT 中的信息要比等效的非通道的 RTMP 信息大,在非通道 RTMP 不可以的场景中,比如当客户端处于一个阻止非 HTTP 和 非 HTTPS 网络流通时,RTMPT 可以便利地使用。
        这个协议通过使用 POST url 发送命令和使用 POST 体发送 AMF 消息进行工作。例子:

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
 
  1. POST /open/1 HTTP/1.1  

 

        用于打开一个连接。

 

        规范文档
        Adobe 公开声明的规范是 2009 年 6 月 15 日发布的 RTMP specification。那个规范,忽略了(应该说没有公开) 协议实现的关键细节。很难单单根据发布的规范将 RTMP 协议整合进我们的程序里,太多必不可少的细节被忽略了,我们只能够通过学习实现了这个协议的应用 (比如 librtmp),以及根据测试 TCP/IP 数据包捕获,来断定其很少的细节。
        Adobe 关于使用这一协议的 license 要求 RTMP 服务器的实现满足这一规范。
        Adobe 发布的规范中缺少的细节包括:

  • 关于 RTMP 握手没有只言片语的描述。如果(关于握手)做的不正确,服务器的实现将无法传递 H.264/AAC 内容。如果握手错误,Flash player 会默默地接收 H.264 内容失败。但是所有的客户端实现能够正常工作,因为 RTMP 服务器在这方面比较宽容(包括 FMS)。
  • 发送的块只以最大块大小发送;超出那个大小的块仍会被发送,这个块带有整个块大小的头,但是当超出最大块大小后,一个类型为 4 的块头会被发送,紧跟其后的是这一块被分割出来的下一部分。
  • 关于流的管理信息的解释缺失(31 和 32)。FMS 会时不时发送这些包。

        

  数据包格式

        数据包由客户端和服务器之间建立的 TCP 连接进行发送。数据包包含一个头和一个体,至于连接和控制命令使用 AMF(Action Message Format)编码。头分为基本报头(在图中显示为分离出来的那块)和块消息报头。基本报头是数据包唯一不变的部分,常常由一个复合字节组成,两个有效位代表块类型(规范中的格式),其余的组成了流 id。根据前者的值,一些消息头字段可以被忽略掉,这些字段由前面的数据包根据后面的值派生出来,基本报头可以使用两个额外字节进行扩展(图中的情况总共有三个字节)。块消息报头包含 meta-data 信息,比如消息大小(以字节为单位),Timestamp 以及消息类型。最后一个值是一个单独的字节,它定义了这个包是一个音频包,或者视频包,或者命令以及 "低层次" RTMP 包比如一个 RTMP Ping。

RTMP Packet Diagram

        以下示例,一个 flash 客户端执行以下代码时:

[javascript] view plaincopyprint?在CODE上查看代码片派生到我的代码片
 
  1. var stream:NetStream = new NetStream(connectionObject);  


        这将生成以下块:

Hex CodeASCII
03 00 0b 68 00 00 19 14 00 00 00 00 02 00 0C 63 72 65 61 74 65 53 74 72 65 61 6D 00 40 00 00 00 00 00 00 00 05. . @ I . . . . . . . . . . . . c r e a t e S t r e a m . @ . . . . . . . .


        这一包以一个字节的基本报头开始,两个有效位(b00000011) 定义了块头类型 0,其余部分(b00000011) 定义了块的流 ID 是 3。头类型会有 4 种可能的值,他们的意义分别是:

  • b00 = 12 字节头(完整的头)。
  • b01 = 8 字节 - 像类型 b00。不包含消息 ID(后四个字节)。
  • b10 = 4 字节 - 包含有基本报头和 timestamp (3 个字节)。
  • b11 = 1 字节 - 只包含有基本头。

        最后一个类型 (b11) 常用于聚合信息的情况,在上面的例子中,第二个消息会以 id 为 0xC3 (b11000011) 起始,意味着所有消息报头字段要以流 ID 为 3 的消息传递(恰恰是其上的消息)。六个最低的有效位组成流 ID,取值范围是 3 到 65599。一些值具有特殊意义,比如 1 代表一个扩展的 ID 格式,这时会有两个字节跟随。值为 2 用于底层的消息,例如 Ping 和设置客户端带宽。
        接下来的 RTMP 报头的字节(包含以上数据包例子中的值)详解如下:

  • 字节 #1 (0x03) = 块头类型。
  • 字节 #2-4 (0x000b68) = Timestamp。
  • 字节 #5-7 (0x000019) = 包长度 - 在这个例子中是 0x000019 = 25 字节。
  • 字节 #8 (0x14) = 消息类型 ID - 0x14 (20) 定义了一个 AMF0 编码的命令消息。
  • 字节 #9-12 (0x00000000) = 消息流 ID。这个以小端排序(很奇怪)。

        消息类型 ID 字节定义了当前包是否包含音频/视频数据,一个远程对象或者一个命令。一些可能的值如下:

  • 0x01 = 设置包大小消息。
  • 0x04 = Ping 消息。
  • 0x05 = 服务器带宽。
  • 0x06 = 客户端带宽。
  • 0x08 = 音频包。
  • 0x09 = 视频包。
  • 0x11 = 一个 AMF3 类型命令。
  • 0x12 = 调用 (onMetaData 信息会这样发送)。
  • 0x14 = 一个 AMF0 类型的命令。

        紧跟报头,0x02 表示一个大小为 0x000C 的串,0x63 0x72 ... 0x6D ("createStream" 命令)。其后是 0X00 代表 ID 为 2.0 的事务。最后一个字节是 0x05 (null) 表示没有参数。
        Invoke 消息结构 (0x14, 0x11)
        以上所述的几种消息类型,比如 Ping 和设置客户端/服务端带宽,被认为底层 RTMP 协议消息,它们不使用 AMF 编码格式。换句话说,命令消息,无论是 AMF0 (Message Type of 0x14) 还是 AMF3 (0x11),使用这种格式:

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
 
  1. (String) <Command Name>  
  2. (Number) <Transaction Id>  
  3. (Mixed)  <Argument> ex. Null, String, Object: {key1:value1, key2:value2 ... }  


        事务 id 用于有回复的命令。这个值可以是上面例子中的一个字符串,也可以是一个或者多个对象,每个由一个键值对组成,键值对的键经常编码为字符串值,键值对的值可以是任意 AMF 数字类型,包括复杂的类型,比如数组。
        Ping 消息结构 (0x04)
        Ping 消息不是 AMF 编码。它们以一个流 Id 起始,带有一个完整 (类型 0) 报头,并有一个类型为 0x04 的消息。报头后面紧随六个字节:

  • #0-1 - Ping 类型。
  • #2-3 - 第二个参数 (对于特定的 Ping 类型有意义)。
  • #4-5 - 第三个参数 (一样)。

        消息体的前两个字节定义了 Ping 的类型,有六种可能取到的值。

  • 类型 0 - 清除流:当连接已建立而没有带有更多数据时发送。
  • 类型 1 - 清除缓存。
  • 类型 3 - 客户端缓存时间。第三个参数以毫秒为单位持有这个值。
  • 类型 4 - 重置流。
  • 类型 6 - 从服务器 Ping 客户端。第二个参数是当前时间。
  • 类型 7 - 客户端回复的 Pong。第二个参数是客户端接收到 Ping 的时间。

        Pong 是 Ping 回复的名字,它使用以上介绍的值。
        ServerBw/ClientBw 消息结构 (0x05, 0x06)
        这个关联到和客户端向上流、服务器向下流比特率相关的消息。消息体由 4 个字节组成,表示带宽的值,它可能有一个扩展字节来设置 Limit 类型。这个可以有三种可能的值:hard、soft 或者 dynamic (就是 soft 或者 hard 任一)。
        设置块大小 (0x01)
        这个值可以在报体的四个字节里接收到。存在默认值 128 字节,当你想要改变默认值时才会发生这个消息。
        协议
        握手

        建立 TCP 连接后,RTMP 连接会在两端交互三个包的握手之后建立(这在官方文档里也被引用为块)。在官方规范中,这里描述为客户端发送包 C0-2,服务器端发送包 S0-2,不要和 RTMP 包混淆,RTMP 包只会在握手完成之后才能交互。这些包拥有自己的结构,C1 包含一个设置 "epoch" timestamp 的字段,但是因为这个可以设置为 0,正如第三方实现过的,这个包可以被简化。客户端通过发送一个带有代表现有协议版本号常数值 0x03 的 C0 包初始化连接。它可以直接跟随 C1 而无须等待接收到 S0,它带有 1536 字节,前四个字节代表 timestamp,其他的随意 (第三方实现中可以将其设置为 0)。C2 和 S2 分别是 S1 和 C1 的回声,接收到它们之后,握手才被认为结束。
RTMP Handshake Diagram
        连接
        这一点上,客户端和服务器会通过交互 AMF 编码的消息进行协商连接。这些包含关系到建立连接所需要的变量的键值对。一个来自客户端的消息例子如下:

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
 
  1. (Invoke) “connect”  
  2. (Transaction ID) 1.0  
  3. (Object1) { app: “sample”, flashVer: “MAC 10,2,153,2”, swfUrl: null,  
  4.               tcUrl: “rtmpt://127.0.0.1/sample “, fpad: false,  
  5.               capabilities: 9947.75 , audioCodecs: 3191, videoCodecs: 252,  
  6.               videoFunction: 1 , pageUrl: null, objectEncoding: 3.0 }  


        FMS 和其他市县使用一个 "app" 的概念来概念化为音频/视频和其他内容定义一个容器,具体实现是,在服务根目录下的一个文件夹,其下含有将要被流化的媒体文件。第一个变量含有这一 app 的名 "sample",这个是由 Wowza 服务器用于测试所提供的名字。flashVer 字符串和 Action-script 的 getversion() 方法返回的值一样。audioCodec 和 videoCodec 使用 double 编码,它们的含义可以在原始规范里找到。videoFunction 的值是 true,在这里明显是 SUPPORT_VID_CLIENT_SEEK 变量。特别有趣的是 objectEncoding,它定义了通信的其余部分是否会使用扩展的 AMF3 格式。因为当前默认版本为 3,flash 客户端必须被以 Action-script 代码显式告知去使用 AMF0。服务器会用 ServerBW 回复,一个 ClientBW 和一个 SetPacketSize 消息序列,最终跟随一个 Invoke,用一个实例消息。

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
 
  1. (Invoke) “_result”  
  2. (transaction ID) 1.0  
  3. (Object1) { fmsVer: "FMS/3,5,5,2004", capabilities: 31.0, mode: 1.0 }  
  4. (Object2) { level: “status”, code: “NetConnection.Connect.Success",  
  5.                    description: “Connection succeeded”,  
  6.                    data: (array) { version: “3,5,5,2004” },  
  7.                    clientId: 1728724019, objectEncoding: 3.0 }  


        上面的一些值会被连续加载到一个通用的 Action-script Object,这个对象随后被传递给 NetConnection 事件监听者。clientId 将会为这个连接开启的会话建立一个编号。Object 编码必须匹配前面设定的值。
        播放视频
        要开始一个视频流,客户端发送一个由一个 ping 消息跟随的 "createStream" 调用,其后再跟随一个以文件名为参数的 "play" 调用。服务器随后以一系列的 "onStatus" 命令和带有视频数据的 RTMP 消息进行回复。

        连接建立以后,媒体由封装为 FLV tag 内容的 RTMP 消息对类型 8 和 类型 9 的音频和视频交叠发送。

 

        HTTP 隧道 (RTMPT)
        这一节讲解 RTMP 的 HTTP 隧道式版本。它交互在端口 80,并在 HTTP POST 请求和回复内部传递 AMF 数据。连接的时序如下:

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
 
  1. POST /fcs/ident2 HTTP/1.1  
  2. Content-Type: application/x-fcs\r\n  
  3.   
  4. HTTP/1.0 404 Not Found  
  5.   
  6. POST /open/1 HTTP/1.1  
  7. Content-Type: application/x-fcs\r\n  
  8.   
  9. HTTP/1.1 200 OK  
  10. Content-Type: application/x-fcs\r\n  
  11.     1728724019  


        第一个请求有一个路径 /fcs/ident2,正确的返回是 404 无法查找错误。客户端然后发送了一个 /open/1 请求,服务器以附加一个代表 session 标识的随机数的 200 ok 返回。在以上例子中,返回体中返回 1728724019。

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
 
  1. POST /idle/1728724019/0 HTTP/1.1  
  2. HTTP/1.1 200 OK  
  3.    0x01  


        从此开始,/idle/<session id>/<sequence #> 会轮询请求,session id 是由服务器生成并返回,sequence 是一个为以 1 开始以后每次请求递增的数字。正确的回复是一个报体中带有指示内部时间的整数,200 OK。AMF 数据通过 /send/<session id>/<sequence #> 发送。
        软件实现
        客户端软件

        最广泛采用的 RTMP 客户端软件是 Adobe Flash Player,它能够支持来自 RTMP 服务器的音视频流的回放(当它被安装为一个 web 浏览器的插件时)。
        只能部分支持 RTMP 的客户端软件宝库开源的 media player XBMC,它提供了播放 RTMP (不包括 RTMPE) 流的初步支持。
        开源的命令行工具 rtmpdump 用于回放或者将整个 RTMP 流 (包括 Adobe 用于加密的 RTMPE) 保存到磁盘。RTMPdump 可以运行在 Linux、Android、Solaris、MacOSX 以及大部分其他的 Unix 派生操作系统,当然也可以运行在微软 Windows。最初支持所有 32 位版本的 Windows 系统,包括 Windows 98,从版本 2.2 起,这款软件只能泡在 Windows XP 或者更高版本的 Windows 系统之上(尽管早期版本保持功能齐全)。
        RTMPdump 的一个分叉,没有包含 Adobe 声称违反了美国 DMCA 的 RTMPdump 代码,以 FLVstreamer 发布了。它的开发是 2008 年 Adobe 抵制 RTMPdump 的一个直接反应。FLVstreamer 可以将来自任意一台 RTMP 服务器的音频流或者视频流保存到磁盘,只要流没有开启 RTMPE。FLVstreamer 运行于 Windows XP 或者更高版本的 Windows 系统之上,但不支持早期版本的 Windows。
        2009 年十月,在美国以外的国家,MPlayer 网站 重新启动了 RTMPdump 的研发。现有版本大大改进了功能,并且使用了 C 语言重写,大大利用了 C 的优势。尤其是,主要功能被内置到一个库 (librtmp) 中,其他程序可以很容易使用这些功能。RTMPdump 的开发者们也为其他一些开源项目 (诸如 MPlayer、FFmpeg、XBMC、cURL、VLC) 提供了 librtmp 的支持。这些项目对 librtmp 的使用,拥有了完全 RTMP 的支持。
        开源的 Gnash,一个在 Linux 平台上对于 Macromedia Flash Player 的替代,拟就为 Linux 支持 RTMP streaming。
        服务器端软件
        一些全面执行 RTMP 的服务器有:

  • Adobe FMS。
  • Adobe 生命周期数据服务。
  • 亚马逊 S3 和亚马逊 Cloudfront 可以使用 RTMP 流。
  • haXeVideo 是一个完全由 haXe 语言开发的多流 FLV 流媒体服务器。
  • RealNetworks 的 Helix Universal Server 可以支持 RTMP、RTMPT 和 RTMPS 流的直播和点播。
  • Red5 Media Server 是一个 Java 开源项目,为 Adobe Flash Player 和其他客户端技术提供了一个强大的视频流和多用户解决方案。
  • Erlyvideo 具有广泛的功能:不仅仅是文件流化,而且可以使用 RTMP 对 MPEG-TS 或者 Shoutcast 为 flash 客户端重新流化。
  • Unreal Media Server 支持实时和缓存 RTMP 流的直播。
  • Wowza Media Server。
  • WebORB Integration Server (交流版和云版,为 .NET 和 Java 企业版提供了 RTMP/RTMPT/RTMPS 消息和媒体流化支持)。
  • FreeSWITCH RTMP 流媒体 mod_rtmp 可用,并允许和其他 VoIP 协议 (SIP, H.323) 互连。
  • FFmpeg
  • Nginx with RTMP Module
  • XSplit Broadcaster

        探索和研发

  • crtmpserver 探索者们对 RTMFP 协议进行逆向工程。现在还是个半成品。
  • Blue5 - 一个意图创建开源版本的 RTMPE 和 RTMFP 的项目。
  • kbmMW 为 Delphi/C++Builder 支持 RTMP 的企业版多层次开发工具。

        另请参阅

  • RTMPDump
  • Protected Streaming Info about RTMPS and RTMPE
  • Real Time Media Flow Protocol(RTMFP),基于 UDP
  • Video on Demand (VoD)

        参考资料

  • http://help.adobe.com/en_US/flashlite/dev/4/WSa2ec538c80d45833-4e519ada123e088b6aa-8000.html
  • Using RPC services in Flex Data Services 2
  • http://livedocs.adobe.com/flashmediaserver/3.0/docs/help.html?content=01_overview_basics_13.html
  • http://blogs.adobe.com/flashmedia/2010/02/understanding_ado
  • http://trac.red5.org/wiki/Documentation/Tutorials/Ping
  • "Updates:2009-11-01"
  • "Linux Funding"
  • erlyvideo website

        外部链接

  • Adobe Developer page - RTMP - 官方规范
  • OSFlash - RTMP OS

原文链接:http://en.wikipedia.org/wiki/Real_Time_Messaging_Protocol。

实时消息传输协议 RTMP(Real Time Messaging Protocol)