流媒体协议RTP、RTCP、H264详解
2016-12-15 21:05
295 查看
1 实时传输协议(RTP)为数据提供了具有实时特征的端对端传送服务,如在组播或单播网络服务下的交互式视频音频或模拟数据。应用程序通常在 UDP 上运行 RTP 以便使用其多路结点和校验服务;这两种协议都提供了传输层协议的功能。但是 RTP 可以与其它适合的底层网络或传输协议一起使用。如果底层网络提供组播方式,那么
RTP 可以使用该组播表传输数据到多个目的地。
RTP 本身并没有提供按时发送机制或其它服务质量(QoS)保证,它依赖于低层服务去实现这一过程。 RTP 并不保证传送或防止无序传送,也不确定底层网络的可靠性。 RTP 实行有序传送, RTP 中的序列号允许接收方重组发送方的包序列,同时序列号也能用于决定适当的包位置,例如:在视频解码中,就不需要顺序解码。
RTP 由两个紧密链接部分组成:
RTP ― 传送具有实时属性的数据;
RTP 控制协议(RTCP) ― 监控服务质量并传送正在进行的会话参与者的相关信息。RTCP 第二方面的功能对于“松散受控”会话是足够的,也就是说,在没有明确的成员控制和组织的情况下,它并不非得用来支持一个应用程序的所有控制通信请求。
协议结构 |
V ― 版本。识别 RTP 版本。 P ― 间隙(Padding)。设置时,数据包包含一个或多个附加间隙位组,其中这部分不属于有效载荷。 X ― 扩展位。设置时,在固定头后面,根据指定格式设置一个扩展头。 CSRC Count ― 包含 CSRC 标识符(在固定头后)的编号。 M ― 标记。标记由 Profile 文件定义。允许重要事件如帧边界在数据包流中进行标记。 Payload Type ― 识别 RTP 有效载荷的格式,并通过应用程序决定其解释。Profile 文件规定了从 Payload 编码到 Payload 格式的缺省静态映射。另外的 Payload Type 编码可能通过非 RTP 方法实现动态定义。 Sequence Number ― 每发送一个 RTP 数据包,序列号增加1。接收方可以依次检测数据包的丢失并恢复数据包序列。 Timestamp ― 反映 RTP 数据包中的第一个八位组的采样时间。采样时间必须通过时钟及时提供线性无变化增量获取,以支持同步和抖动计算。 SSRC ― 同步源。该标识符随机选择,旨在确保在同一个 RTP 会话中不存在两个同步源具有相同的 SSRC 标识符。 CSRC ― 贡献源标识符。识别该数据包中的有效载荷的贡献源。 RTP(Real-timeTransportProtocol)是用于Internet上针对多媒体数据流的一种传输协议。RTP被定义为在一对一或一对多的传输情况下工作,其目的是提供时间信息和实现流同步。RTP通常使用UDP来传送数据,但RTP也可以在TCP或ATM等其他协议之上工作。当应用程序开始一个RTP会话时将使用两个端口:一个给RTP,一个给RTCP。RTP本身并不能为按顺序传送数据包提供可靠的传送机制,也不提供流量控制或拥塞控制,它依靠RTCP提供这些服务。通常RTP算法并不作为一个独立的网络层来实现,而是作为应用程序代码的一部分。实时传输控制协议RTCP。RTCP(Real-timeTransportControlProtocol)和RTP一起提供流量控制和拥塞控制服务。在RTP会话期间,各参与者周期性地传送RTCP包。RTCP包中含有已发送的数据包的数量、丢失的数据包的数量等统计资料,因此,服务器可以利用这些信息动态地改变传输速率,甚至改变有效载荷类型。RTP和RTCP配合使用,它们能以有效的反馈和最小的开销使传输效率最佳化,因而特别适合传送网上的实时数据。 1.1 RTP数据传输协议 RTP提供端对端网络传输功能,适合通过组播和点播传送实时数据,如视频、音频和仿真数据。RTP没有涉及资源预订和质量保证等实时服务,RTCP扩充数据传输以允许监控数据传送,提供最小的控制和识别功能。RTP与RTCP设计成独立传输和网络层。 1.1.1 RTP固定头 RTP 头格式如下: ----------------------------------------------------------------------------------------------- |V=2|P|X| CC |M| PT | 系列号 | ----------------------------------------------------------------------------------------------- | 时标 | ----------------------------------------------------------------------------------------------- | 同步源标识(SSRC) | ----------------------------------------------------------------------------------------------- | 作用标识 (CSRC) | | .... | ----------------------------------------------------------------------------------------------- 开始12个八进制出现在每个RTP包中,而CSRC标识列表仅出现在混合器插入时。 1.1.2 复用 RTP 连接 为使协议有效运行,复用点数目应减至最小。RTP中,复用由定义RTP连接的目的传输地址(网络地址与端口号)提供。例如,对音频和视频单独编码的远程会议,每个媒介被携带在单独RTP连接中,具有各自的目的传输地址。目标不在将音频和视频放在单一RTP连接中,而根据SSRC段载荷类型进行多路分解。使用同一SSRC ,而具有不同载荷类型的交叉包将带来几个问题: 如一种载荷类型在连接期间切换,没有办法识别新值将替换那一个旧值。 SSRC定义成用于标识单个计时和系列号空间。如媒体时钟速率不同,而要求不同系列号空间以说明那种载荷类型有丢包,交叉复用载荷类型将需要不同计时空间。 RTCP发送和接收报告可能仅描述每个SSRC的计时和系列号空间,而不携带载荷类型段。 RTP混合器不能将不兼容媒体流合并成一个流。 在一个RTP连接中携带多个媒介阻止几件事:使用不同网络路径或网络资源分配;接受媒介子集。 对每种媒介使用不同SSRC,但以相同RTP连接发送可避免前三个问题,但不能避免后两个问题。 1.1.3 对RTP头特定设置的修改 可以认为,现用RTP数据包头对RTP支持的所有应用类共同需要的功能集是完整的。然而,为维持ALF设计原则,头可通过改变或增加设置来裁剪,并仍允许设置无关监控和记录工具起作用。标记位与载荷类型段携带特定设置信息,但由于很多应用需要它们,否则要容纳它们,就要增加另外32位字,故允许分配在固定头中。包含这些段的八进制可通过设置重新定义以适应不同要求,如采用更多或更少标记位。如有标记位,既然设置无关监控器能观察包丢失模式和标记位间关系,我们就可以定位八进制中最重要的位。 其它特殊载荷格式(视频编码)所要求的信息应该携带在包的载荷部分。可出现在头,总是在载荷部分开始处,或在数据模式的保留值中指出。如特殊应用类需要独立载荷格式的附加功能,应用运行的设置应该定义附加固定段跟随在现存固定头SSRC之后。这些应用将能迅速而直接访问附加段,同时,与监控器和记录器无关设置仍能通过仅解释开始12个八进制处理RTP包。如证实附加功能是所有设置共同需要的,新版本RTP应该对固定头作出明确改变。
|
nal_unit_type | NAL类型 | C |
0 | 未使用 | |
1 | 不分区、非IDR的片 | 2,3,4 |
2 | 片分区A | 2 |
3 | 片分区B | 3 |
4 | 版分区C | 4 |
5 | IDR图像中的片 | 2,3 |
6 | 补充增强信息单元(SEI) | 5 |
7 | 序列参数集 | 0 |
8 | 图像参数集 | 1 |
9 | 分界符 | 6 |
10 | 序列结束 | 7 |
11 | 码流结束 | 8 |
12 | 填充 | 9 |
13..23 | 保留 | |
24..31 | 不保留 |
3) 分片单元:用于分片单个NAL单元到多个RTP包。现存两个版本FU-A,FU-B,用NAL单元类型 28,29标识
常用的打包时的分包规则是:如果小于MTU采用单个NAL单元包,如果大于MTU就采用FUs分片方式。
因为常用的打包方式就是单个NAL包和FU-A方式,所以我们只解析这两种。
1.2.1、单个NAL单元包
图3
定义在此的NAL单元包必须只包含一个。这意味聚合包和分片单元不可以用在单个NAL 单元包中。并且RTP序号必须符合NAL单元的解码顺序。NAL单元的第一字节和RTP荷载头第一个字节重合。如图3。
打包H264码流时,只需在帧前面加上12字节的RTP头即可。
1.2.2、分片单元(FU-A)
图4
分片只定义于单个NAL单元不用于任何聚合包。NAL单元的一个分片由整数个连续NAL单元字节组成。每个NAL单元字节必须正好是该NAL单元一个分片的一部分。相同NAL单元的分片必须使用递增的RTP序号连续顺序发送(第一和最后分片之间没有其他的RTP包)。相似,NAL单元必须按照RTP顺序号的顺序装配。
当一个NAL单元被分片运送在分片单元(FUs)中时,被引用为分片NAL单元。STAPs,MTAPs不可以被分片。 FUs不可以嵌套。 即, 一个FU 不可以包含另一个FU。运送FU的RTP时戳被设置成分片NAL单元的NALU时刻。
图 4 表示FU-A的RTP荷载格式。FU-A由1字节的分片单元指示(如图5),1字节的分片单元头(如图6),和分片单元荷载组成。
S: 1 bit 当设置成1,开始位指示分片NAL单元的开始。当跟随的FU荷载不是分片NAL单元荷载的开始,开始位设为0。
E: 1 bit 当设置成1, 结束位指示分片NAL单元的结束,即, 荷载的最后字节也是分片NAL单元的最后一个字节。当跟随的 FU荷载不是分片NAL单元的最后分片,结束位设置为0。
R: 1 bit 保留位必须设置为0,接收者必须忽略该位
打包时,原始的NAL头的前三位为FU indicator的前三位,原始的NAL头的后五位为FU header的后五位。
取一段码流分析如下:
80 60 01 0f 00 0e 10 00 00 0000 00 7c 85 88
82 €`..........|???
00 0a 7f ca 94 05 3b7f 3e 7f fe 14 2b 27 26 f8 ...??.;.>.?.+'&?
89 88 dd 85 62 e1 6dfc 33 01 38 1a 10 35 f2 14 ????b?m?3.8..5?.
84 6e 21 24 8f 72 62f0 51 7e 10 5f 0d 42 71 12 ?n!$?rb?Q~._.Bq.
17 65 62 a1 f1 44 dc df 4b 4a 38 aa 96 b7 dd 24 .eb??D??KJ8????$
前12字节是RTP Header
7c是FU indicator
85是FU Header
FU indicator(0x7C)和FU Header(0x85)换成二进制如下
0111 1100 1000 0101
按顺序解析如下:
0 是F
11 是NRI
11100 是FU Type,这里是28,即FU-A
1 是S,Start,说明是分片的第一包
0 是E,End,如果是分片的最后一包,设置为1,这里不是
0 是R,Remain,保留位,总是0
00101 是NAl Type,这里是5,说明是关键帧(不知道为什么是关键帧请自行谷歌)
打包时,FUindicator的F、NRI是NAL Header中的F、NRI,Type是28;FU Header的S、E、R分别按照分片起始位置设置,Type是NAL Header中的Type。
解包时,取FU indicator的前三位和FU Header的后五位,即0110 0101(0x65)为NAL类型。
1.2.3、RTP荷载PS流
针对H264 做如下PS 封装:每个IDR NALU 前一般都会包含SPS、PPS 等NALU,因此将SPS、PPS、IDR 的NALU 封装为一个PS 包,包括ps 头,然后加上PS system header,PS system map,PES header+h264 raw data。所以一个IDR NALU PS 包由外到内顺序是:PSheader| PS system header | PS system Map | PES header | h264 raw data。对于其它非关键帧的PS
包,就简单多了,直接加上PS头和PES 头就可以了。顺序为:PS header | PES header | h264raw data。以上是对只有视频video 的情况,如果要把音频Audio也打包进PS 封装,也可以。当有音频数据时,将数据加上PES header 放到视频PES 后就可以了。顺序如下:PS 包=PS头|PES(video)|PES(audio),再用RTP 封装发送就可以了。
GB28181 对RTP 传输的数据负载类型有规定(参考GB28181 附录B),负载类型中96-127
RFC2250 建议96 表示PS 封装,建议97 为MPEG-4,建议98 为H264
即我们接收到的RTP 包首先需要判断负载类型,若负载类型为96,则采用PS 解复用,将音视频分开解码。若负载类型为98,直接按照H264 的解码类型解码。
注:此方法不一定准确,取决于打包格式是否标准
PS 包中的流类型(stream type)的取值如下:
1) MPEG-4 视频流: 0x10;
2) H.264 视频流: 0x1B;
3) SVAC 视频流: 0x80;
4) G.711 音频流: 0x90;
5) G.722.1 音频流: 0x92;
6) G.723.1 音频流: 0x93;
7) G.729 音频流: 0x99;
8) SVAC音频流: 0x9B。
1.3.1、PS包头
图7
1) Pack start code:包起始码字段,值为0x000001BA的位串,用来标志一个包的开始。
2) System clock reference base,system clock reference extenstion:系统时钟参考字段。
3) Pack stuffing length :包填充长度字段,3 位整数,规定该字段后填充字节的个数
80 60 53 1f 00 94 89 00 00 0000 00 00 00 01 ba €`S..??........?
7e ff 3e fb 44 01 00 5f 6b f8 00 00 01 e0 14 53 ~.>?D.._k?...?.S
80 80 05 2f bf cf bed1 1c 42 56 7b 13 58 0a 1e €€./????.BV{.X..
08 b1 4f 33 69 35 0453 6d 33 a8 04 15 58 d9 21 .?O3i5.Sm3?..X?!
9741 b9 f1 75 3d 94 2b 1f bc 0b b2 b4 97 bf 93 ?A??u=?+.?.?????
前12位是RTP Header,这里不再赘述;
000001ba是包头起始码;
接下来的9位包括了SCR,SCRE,MUXRate,具体看图7
最后一位是保留位(0xf8),定义了是否有扩展,二进制如下
1111 1000
前5位跳过,后3位指示了扩展长度,这里是0.
1.3.2、系统标题
图8
Systemheader当且仅当pack是第一个数据包时才存在,即PS包头之后就是系统标题。取值0x000001BB的位串,指出系统标题的开始,暂时不需要处理,读取Header Length直接跳过即可。
1.3.3、节目映射流
Systemheader当且仅当pack是第一个数据包时才存在,即系统标题之后就是节目流映射。取值0x000001BC的位串,指出节目流映射的开始,暂时不需要处理,读取Header Length直接跳过即可。前5字节的结构同系统标题,见图8。
取一段码流分析系统标题和节目映射流
00 00 01 ba 45 a9 d4 5c 34 0100 5f 6b f8 00 00 ...?E??\4.._k?..
01 bb 00 0c 80 cc f5 04 e1 7f e0 e0 e8 c0 c0 20 .?..€??.?.?????
00 00 01 bc 00 1e e1 ff00
00 00 18 1b e0 00
0c ...?..?......?..
2a 0a 7f ff 00 00 0708 1f fe a0 5a 90 c0 00 00 *........??Z??..
00 00 00 00 00
00 01 e0 7f e0 80 80 0521 6a 75 .......?.?€€.!ju
前14个字节是PS包头(注意,没有扩展);
接下来的00 00 01 bb是系统标题起始码;
接下来的00 0c说明了系统标题的长度(不包括起始码和长度字节本身);
接下来的12个字节是系统标题的具体内容,这里不做解析;
继续看到00 00 01 bc,这是节目映射流起始码;
紧接着的00 1e同样代表长度;
跳过e1 ff,基本没用;
接下来是00 18,代表基本流长度,说明了后面还有24个字节;
接下来的1b,意思是H264编码格式;
下一个字节e0,意思是视频流;
接下里00 0c,同样代表接下的长度12个字节;
跳过这12个字节,看到90,这是G.711音频格式;
下一个字节是c0,代表音频流;
接下来的00 00同样代表长度,这里是0;
接下来4个字节是CRC,循环冗余校验。
到这里节目映射流解析完毕。(好累
)。
原创不易,转载请附上链接,谢谢http://blog.csdn.net/chen495810242/article/details/39207305
好戏还在后头呢。
1.3.4、PES分组头部
图9
别被这么长的图吓到,其实原理相同,但是,你必须处理其中的每一位。
1) Packet start code prefix:值为0x000001的位串,它和后面的stream id 构成了标识分组开始的分组起始码,用来标志一个包的开始。
2) Stream id:在节目流中,它规定了基本流的号码和类型。0x(C0~DF)指音频,0x(E0~EF)为视频
3) PES packet length:16 位字段,指出了PES 分组中跟在该字段后的字节数目。值为0 表示PES 分组长度要么没有规定要么没有限制。这种情况只允许出现在有效负载包含来源于传输流分组中某个视频基本流的字节的PES 分组中。
4) PTS_DTS:2 位字段。当值为'10'时,PTS 字段应出现在PES 分组标题中;当值为'11'时,PTS 字段和DTS 字段都应出现在PES 分组标题中;当值为'00'时,PTS 字段和DTS 字段都不出现在PES分组标题中。值'01'是不允许的。
5) ESCR:1位。置'1'时表示ESCR 基础和扩展字段出现在PES 分组标题中;值为'0'表示没有ESCR 字段。
6) ESrate:1 位。置'1'时表示ES rate 字段出现在PES 分组标题中;值为'0'表示没有ES rate 字段。
7) DSMtrick mode:1 位。置'1'时表示有8 位特技方式字段;值为'0'表示没有该字段。
8) Additionalinfo:1 位。附加版权信息标志字段。置'1'时表示有附加拷贝信息字段;值为'0'表示没有该字段。
9) CRC:1 位。置'1'时表示CRC 字段出现在PES 分组标题中;值为'0'表示没有该字段。
10) Extensionflag:1 位标志。置'1'时表示PES 分组标题中有扩展字段;值为'0'表示没有该字段。
PES header data length: 8 位。PES 标题数据长度字段。指出包含在PES 分组标题中的可选字段和任何填充字节所占用的总字节数。该字段之前的字节指出了有无可选字段。
老规矩,上码流:
00 00 01 e0 21 33 80 80 05 2b
5f df 5c 95 71 84 ...?!3€€.+_?\?q?
aa e4 e9 e9 ec 40 cc17 e0 68 7b 23 f6 89 df 90 ?????@?.?h{#????
a9d4 be 74 b9 67 ad 34 6d f0 92 0d 5a 48 dd 13 ???t?g?4m??.ZH?.
00 00 01是起始码;
e0是视频流;
21 33 是帧长度;
接下来的两个80 80见下面的二进制解析;
下一个字节05指出了可选字段的长度,前一字节指出了有无可选字段;
接下来的5字节是PTS;
第7、8字节的二进制如下:
1000 0000 1000 0000
按顺序解析:
第7个字节:
10 是标志位,必须是10;
00 是加扰控制字段,‘00’表示没有加密,剩下的01,10,11由用户自定义;
0 是优先级,1为高,0为低;
0 是数据对齐指示字段;
0 是版权字段;
0 是原始或拷贝字段。置'1'时表示相关PES分组有效负载的内容是原始的;'0'表示内容是一份拷贝;
第8个字节:
10 是PTS_DTS字段,这里是10,表示有PTS,没有DTS;
0 是ESCR标志字段,这里为0,表示没有该段;
0 是ES速率标志字段,,这里为0,表示没有该段;
0 是DSM特技方式标志字段,,这里为0,表示没有该段;
0 是附加版权信息标志字段,,这里为0,表示没有该段;
0 是PESCRC标志字段,,这里为0,表示没有该段;
0 是PES扩展标志字段,,这里为0,表示没有该段;
本段码流只有PTS,贴一下解析函数
[cpp] view
plaincopy
unsigned long parse_time_stamp (const unsigned char *p)
{
unsigned long b;
//共33位,溢出后从0开始
unsigned long val;
//第1个字节的第5、6、7位
b = *p++;
val = (b & 0x0e) << 29;
//第2个字节的8位和第3个字节的前7位
b = (*(p++)) << 8;
b += *(p++);
val += ((b & 0xfffe) << 14);
//第4个字节的8位和第5个字节的前7位
b = (*(p++)) << 8;
b += *(p++);
val += ((b & 0xfffe) >> 1);
return val;
}
其他字段可参考协议解析
文档我都放在了我的资源里面,有1个下载积分,大家不要吝啬,绝对值得!
《RTP Payload Format for H.264 Video》
http://download.csdn.net/detail/chen495810242/7904367
《MPEG2-2(13818中文版)》
http://download.csdn.net/detail/chen495810242/7904401
RTP荷载H264的代码参考:
http://blog.csdn.net/dengzikun/article/details/5807694
RTP荷载PS流的代码参考:
http://www.pudn.com/downloads33/sourcecode/windows/multimedia/detail105823.html
http://www.oschina.net/code/snippet_99626_23737
其他参考:
http://blog.csdn.net/duanbeibei/article/details/1698183
http://blog.csdn.net/wwyyxx26/article/details/15224879
3 RTP控制协议-- RTCP
RTCP协议将控制包周期发送给所有连接者,应用与数据包相同的分布机制。低层协议提供数据与控制包的复用,如使用单独的UDP端口号。RTCP执行下列四大功能:
主要是提供数据发布的质量反馈。是作为RTP传输协议的一部分,与其他传输协议的流和阻塞控制有关。反馈对自适应编码控制直接起作用,但IP组播经验表明,从发送者收到反馈对诊断发送错误是致关重要的。给所有参加者发送接收反馈报告允许问题观察者估计那些问题是局部的,还是全局的。诸如IP组播等发布机制使网络服务提供商类团体可能接收反馈信息,充当第三方监控者来诊断网络问题。反馈功能由RTCP发送者和接收者报告执行。
RTCP带有称作规范名字(CNAME)的RTP源持久传输层标识。如发现冲突,或程序重新启动,既然SSRC标识可改变,接收者需要CNAME跟踪参加者。接收者也需要CNAME 与相关RTP连接中给定的几个数据流联系
前两种功能要求所有参加者发送RTCP包,因此,为了RTP扩展到大规模数量,速率必须受到控制。让每个参加者给其它参加者发送控制包,就大独立观察参加者数量。该数量用语计算包发送的速率。
第四个可选功能是传送最小连接控制信息,如参加者辨识。最可能用在"松散控制"连接,那里参加者自由进入或离开,没有成员控制或参数协调,RTCP充当通往所有参加者的方便通道,但不必支持应用的所有控制通讯要求。高级连接控制协议超出本书范围。
在IP组播场合应用RTP时,前3个功能是必须的,推荐用于所有情形。RTP应用设计人员必须避免使用仅在单播模式下工作的机制,那将导致无法扩展规模。
3.1 RTCP 包格式
下面定义几个携带不同控制信息的RTCP包类型:
SR:
发送报告,当前活动发送者发送、接收统计。
RR:
接收报告,非活动发送者接收统计。
SDES:
源描述项,包括CNAME。
BYE:
表示结束。
APP:
应用特定函数。
类似于RTP数据包,每个RTCP包以固定部分开始,紧接着的是可变长结构元素,但以一个32位边界结束。包含安排要求和固定部分中长度段,使RTCP包可堆叠。不需要插入任何分隔符将多哥RTCP包连接起来形成一个RTCP组合包,以低层协议用单一包发送出去。由于需要低层协议提供提供整体长度来决定组合包的结尾,在组合包中没有单个RTCP包显式计数。
组合包中每个RTCP包可独立处理,不需要根据包组合顺序。但未了执行协议功能,强加如下约束:
接收统计(在SR或RR中)应该经常发送,只要带宽允许,因此每个周期发送的组合RTCP 包应包含报告包。
新接收者需要接收CNAME,并尽快识别源,开始联系媒介进行同步,因此每个包应该包含SDES CNAME。
出现在组合包前面的是包类型数量,其增长应该受到限制,以提高常数位数量,提高成功确认RTCP包对错误地址RTP数据包或其他无关包的概率。
因此,所有RTCP包至少必须以两个包组合形式发送,推荐格式如下:
加密前缀(Encryption prefix):
仅当组合包被加密,才加上一个32位随机数用于每个组合包发送。
SR或RR:
组合包中第一个RTCP包必须总为一个报告包,方便头的确认。即使没有数据发送,也没有接收到数据,也要发送一个空RR,那怕组合包中RTCP包为BYE。
附加RR:
如报告统计源数目超过31,在初始报告包后应该有附加RR 包。
SDES:
包含CNAME 项的SDES包必须包含在每个组合RTCP包中。如应用要求,其他源描述项可选,但受到带宽限制。
BYE或APP:
其它RTCP包类型可以任意顺序排列,除了BYE应作为最后一个包发送,包类型出现可不止一次。
建议转换器或混合器从多个源组合单个RTCP包。如组合包整体长度超过网络路径最大传输单元,可分成多个较短组合包用低层协议以单个包形式发送。注意,每个组合包必须以SR或RR包开始。附加RTCP包类型可在Internet Assigned Numbers Authority (IANA)处注册。
3.2 RTCP传输间隔
RTP设计成允许应用自动扩展,连接数可从几个到上千个。例如,音频会议中,数据流量是内在限制的,因为同一时刻只有一两个人说话;对组播,给定连接数据率仍是常数,独立于连接数,但控制流量不是内在限制的。如每个参加者以固定速率发送接收报告,控制流量将随参加者数量线性增长,因此,速率必须按比例下降。
一旦确认地址有效,如后来标记成未活动,地址的状态应仍保留,地址应继续计入共享RTCP带宽地址的总数中,时间要保证能扫描典型网络分区,建议为30分钟。注意,这仍大于RTCP报告间隔最大值的五倍。
这个规范定义了除必需的CNAME外的几个源描述项,如NAME(人名)和EMAIL(电子邮件地址)。它也为定义新特定应用RTCP包类型的途径。给附加信息分配控制带宽应引起注意,因为它将降低接收报告和CNAME发送的速率而损害协议的性能。建议分配给单个参加者用于携带附加信息的RTCP带宽不要超过20%。而且并没有有意让所有SDES项包含在每个应用中。
3.3 发送者与接收者报告
RTP接收者使用RTCP报告包提供接收质量反馈,报告包根据接收者是否是发送者而采用两种格式中的一种。除包类型代码外,发送者报告与接收者报告间唯一的差别是发送者报告包含一个20个字节发送者信息段。如某地址在发出最后或前一个报告间隔期间发送数据包,就发布SR;否则,就发出RR;SR和RR都可没有或包括多个接收报告块。发布报告不是为列在CSRC列表上的起作用的源,每个接收报告块提供从特殊源接收数据的统计。既然最大可有31个接收报告块嵌入在SR 或 RR包中,
丢失包累计数差别给出间隔期间丢掉的数量,而所收到扩展的最后一个系列号的差别给出间隔期间希望发送的包数量,两者之比等于经过间隔期间包丢失百分比。如两报告连续,比值应该等于丢失段部分;否则,就不等。每秒包丢失绿可通过NTP时标差除以丢失部分得到。
从发送者信息,第三方监控器可计算载荷平均数据速率与没收到数据间隔的平均包速率,两者比值给出平均载荷大小。如假设包丢失与包大小无关,那么特殊接收者收到的包数量给出此接收者收到的表观流量。
3.4 SDES: 源描述RTCP包
SDES 包为三层结构,由头与数据块组成,数据块可以没有,也可有多个,组成项描述块所表明的源。项描述如下:
版本(V)、填充(P)、长度:
如SR包中所描述。
包类型(PT):
8位,包含常数202,识别RTCP SDES包。
源计数(SC):
5位,包含在SDES包中的SSRC/CSRC块数量,零值有效,但没有意义。
源描述项内容如下:
CNAME: 规范终端标识SDES项
CNAME标识属性如下:
如发生冲突或重启程序,由于随机分配的SSRC标识可能发生变化,需要CNAME项提供从SSRC标识到仍为常量的源标识的绑定。
象SSRC标识,CNAME标识在RTP连接的所有参加者中应是唯一的。
为了提供一套相关RTP连接中某个参加者所采用的跨多媒体工具间的绑定,CNAME应固定为那个参加者。
为方便第三方监控,CNAME应适合程序或人员定位源。
NAME:用户名称SDES项
这是用于描述源的真正的名称,如"John Doe, Bit Recycler, Megacorp",可是用户想要的任意形式。对诸如会议应用,这种名称也许是参加者列表显示最适宜的形式,它将是除CNAME外发送最频繁的项目。设置可建立这样的优先级别。NAME值至少在连接期间仍希望保持为常数。它不该成为连接的所有参加者中唯一依赖。
EMAIL:电子邮件地址SDES项
邮件地址格式由RFC822规定,如"John.Doe@megacorp.com"。连接期间,电子邮件仍希望保持为常数。
PHONE:电话号码SDES项
电话号码应带有加号,代替国际接入代码,如"+1 908 555 1212"即为美国电话号码。
LOC:用户地理位置SDES项
根据应用,此项具有不同程度的细节。对会议应用,字符串如"Murray Hill, New Jersey"就足够了。然而,对活动标记系统,字符串如"Room 2A244, AT&T BL MH"也许就适用。细节留给实施或用户,但格式和内容可用设置指示。在连接期间,除移动主机外,LOC值期望仍保留为常数。
TOOL:应用或工具名称SDES项
是一个字符串,表示产生流的应用的名称与版本,如"videotool 1.2"。这部分信息对调试很有用,类似于邮件或邮件系统版本SMTP头。TOOL值在连接期间仍保持常数。
NOTE: 通知/状态SDES项
该项的推荐语法如下所述,但这些或其它语法可在设置中显式定义。NOTE 项旨在描述源当前状态的过渡信息,如"on the phone, can't talk",或在讲座期间用于传送谈话的题目。它应该只用于携带例外信息,而不应包含在全部参加者中,因为这将降低接收报告和CNAME发送的速度,因此损害协议的性能。特殊情况下,它不应作为用户设置文件的项目,也不是自动产生。
当其为活动时,由于NOTE项对显示很重要,其它非CNAME项(如NAME)传输速率将会降低,结果使NOTE项占用RTCP部分带宽。若过渡信息不活跃,NOTE项继续以同样的速度重复发送几次,但以一个串长为零的字符串通知接收者。然而,如对小倍数的重复或约20-30 RTCP间隔也没有接收到,接收者也应该考虑NOTE项是不活跃的。
PRIV: 专用扩展SDES项
该项用于定义实验或应用特定的SDES扩展,它包括由长字符串对组成的前缀,后跟填充该项其他部分和携带所需信息的字符串值。前缀长度段为8位。前缀字符串是定义PRIV项人员选择的名称,唯一对应应用接收到的其它PRIV项。应用实现者可选择使用应用名称,如有必要,外加附加子类型标识。另外,推荐其它人根据其代表的实体选择名称,然后,在实体内部协调名称的使用。
注意,前缀消耗了总长为255个八进制项的一些空间,因此,前缀应尽可能的短。这个设备和受到约束的RTCP带宽不应过载,其目的不在于满足所有应用的全部控制通讯要求。SDES PRIV前缀没在IANA处注册。如证实某些形式的PRIV项具有通用性, IANA应给它分配一个正式的SDES项类型,这样就不再需要前缀。这简化了应用,并提高了传输的效率。
3.5 BYE:断开RTCP包
如混合器接收到一个BYE包,混合器转发BYE包,而不改变SSRC/CSRC 标识。如混合器关闭,它也应该发出一个BYE包,列出它所处理的所有源,而不只是自己的SSRC标识。作为可选项,BYE包可包括一个8位八进制计数,后跟很多八进制文本,表示离开原因,如:"camera malfunction"或"RTP loop detected"。字符串具有同样的编码,如在SDES 中所描述的。如字符串填充包至下32位边界,字符串就不以空结尾;否则,BYE包以空八进制填充。
3.6 APP:定义应用的RTCP包
APP包用于开发新应用和新特征的实验,不要求注册包类型值。带有不可识别名称的APP包应被忽略掉。测试后,如确定应用广泛,推荐重新定义每个APP包,而不用向IANA注册子类型和名称段。
4 打包解包代码案例
H264视频RTP打包、解包的文档和代码。写了H264 RTP打包类、解包类,实现了单个NAL单元包和FU_A分片单元包。对于丢包处理,采用简单的策略:丢弃随后的所有数据包,直到收到关键帧。测试效果还不错,代码贴上来,两个类的使用说明如下(省略了错误处理过程):
DWORD H264SSRC ;
CH264_RTP_PACK pack ( H264SSRC ) ;
BYTE *pVideoData ;
DWORD Size, ts ;
bool IsEndOfFrame ;
WORD wLen ;
pack.Set ( pVideoData, Size, ts, IsEndOfFrame ) ;
BYTE *pPacket ;
while ( pPacket = pack.Get ( &wLen ) )
{
// rtp packet process
// ...
}
HRESULT hr ;
CH264_RTP_UNPACK unpack ( hr ) ;
BYTE *pRtpData ;
WORD inSize;
int outSize ;
BYTE *pFrame = unpack.Parse_RTP_Packet ( pRtpData, inSize, &outSize ) ;
if ( pFrame != NULL )
{
// frame process
// ...
}
[cpp] view
plain copy
//////////////////////////////////////////////////////////////////////////////////////////
// class CH264_RTP_PACK start
class CH264_RTP_PACK
{
#define RTP_VERSION 2
typedef struct NAL_msg_s
{
bool eoFrame ;
unsigned char type; // NAL type
unsigned char *start; // pointer to first location in the send buffer
unsigned char *end; // pointer to last location in send buffer
unsigned long size ;
} NAL_MSG_t;
typedef struct
{
//LITTLE_ENDIAN
unsigned short cc:4; /* CSRC count */
unsigned short x:1; /* header extension flag */
unsigned short p:1; /* padding flag */
unsigned short v:2; /* packet type */
unsigned short pt:7; /* payload type */
unsigned short m:1; /* marker bit */
unsigned short seq; /* sequence number */
unsigned long ts; /* timestamp */
unsigned long ssrc; /* synchronization source */
} rtp_hdr_t;
typedef struct tagRTP_INFO
{
NAL_MSG_t nal; // NAL information
rtp_hdr_t rtp_hdr; // RTP header is assembled here
int hdr_len; // length of RTP header
unsigned char *pRTP; // pointer to where RTP packet has beem assembled
unsigned char *start; // pointer to start of payload
unsigned char *end; // pointer to end of payload
unsigned int s_bit; // bit in the FU header
unsigned int e_bit; // bit in the FU header
bool FU_flag; // fragmented NAL Unit flag
} RTP_INFO;
public:
CH264_RTP_PACK(unsigned long H264SSRC, unsigned char H264PAYLOADTYPE=96, unsigned short MAXRTPPACKSIZE=1472 )
{
m_MAXRTPPACKSIZE = MAXRTPPACKSIZE ;
if ( m_MAXRTPPACKSIZE > 10000 )
{
m_MAXRTPPACKSIZE = 10000 ;
}
if ( m_MAXRTPPACKSIZE < 50 )
{
m_MAXRTPPACKSIZE = 50 ;
}
memset ( &m_RTP_Info, 0, sizeof(m_RTP_Info) ) ;
m_RTP_Info.rtp_hdr.pt = H264PAYLOADTYPE ;
m_RTP_Info.rtp_hdr.ssrc = H264SSRC ;
m_RTP_Info.rtp_hdr.v = RTP_VERSION ;
m_RTP_Info.rtp_hdr.seq = 0 ;
}
~CH264_RTP_PACK(void)
{
}
//传入Set的数据必须是一个完整的NAL,起始码为0x00000001。
//起始码之前至少预留10个字节,以避免内存COPY操作。
//打包完成后,原缓冲区内的数据被破坏。
bool Set ( unsigned char *NAL_Buf, unsigned long NAL_Size, unsigned long Time_Stamp, bool End_Of_Frame )
{
unsigned long startcode = StartCode(NAL_Buf) ;
if ( startcode != 0x01000000 )
{
return false ;
}
int type = NAL_Buf[4] & 0x1f ;
if ( type < 1 || type > 12 )
{
return false ;
}
m_RTP_Info.nal.start = NAL_Buf ;
m_RTP_Info.nal.size = NAL_Size ;
m_RTP_Info.nal.eoFrame = End_Of_Frame ;
m_RTP_Info.nal.type = m_RTP_Info.nal.start[4] ;
m_RTP_Info.nal.end = m_RTP_Info.nal.start + m_RTP_Info.nal.size ;
m_RTP_Info.rtp_hdr.ts = Time_Stamp ;
m_RTP_Info.nal.start += 4 ; // skip the syncword
if ( (m_RTP_Info.nal.size + 7) > m_MAXRTPPACKSIZE )
{
m_RTP_Info.FU_flag = true ;
m_RTP_Info.s_bit = 1 ;
m_RTP_Info.e_bit = 0 ;
m_RTP_Info.nal.start += 1 ; // skip NAL header
}
else
{
m_RTP_Info.FU_flag = false ;
m_RTP_Info.s_bit = m_RTP_Info.e_bit = 0 ;
}
m_RTP_Info.start = m_RTP_Info.end = m_RTP_Info.nal.start ;
m_bBeginNAL = true ;
return true ;
}
//循环调用Get获取RTP包,直到返回值为NULL
unsigned char* Get ( unsigned short *pPacketSize )
{
if ( m_RTP_Info.end == m_RTP_Info.nal.end )
{
*pPacketSize = 0 ;
return NULL ;
}
if ( m_bBeginNAL )
{
m_bBeginNAL = false ;
}
else
{
m_RTP_Info.start = m_RTP_Info.end; // continue with the next RTP-FU packet
}
int bytesLeft = m_RTP_Info.nal.end - m_RTP_Info.start ;
int maxSize = m_MAXRTPPACKSIZE - 12 ; // sizeof(basic rtp header) == 12 bytes
if ( m_RTP_Info.FU_flag )
maxSize -= 2 ;
if ( bytesLeft > maxSize )
{
m_RTP_Info.end = m_RTP_Info.start + maxSize ; // limit RTP packetsize to 1472 bytes
}
else
{
m_RTP_Info.end = m_RTP_Info.start + bytesLeft ;
}
if ( m_RTP_Info.FU_flag )
{ // multiple packet NAL slice
if ( m_RTP_Info.end == m_RTP_Info.nal.end )
{
m_RTP_Info.e_bit = 1 ;
}
}
m_RTP_Info.rtp_hdr.m = m_RTP_Info.nal.eoFrame ? 1 : 0 ; // should be set at EofFrame
if ( m_RTP_Info.FU_flag && !m_RTP_Info.e_bit )
{
m_RTP_Info.rtp_hdr.m = 0 ;
}
m_RTP_Info.rtp_hdr.seq++ ;
unsigned char *cp = m_RTP_Info.start ;
cp -= ( m_RTP_Info.FU_flag ? 14 : 12 ) ;
m_RTP_Info.pRTP = cp ;
unsigned char *cp2 = (unsigned char *)&m_RTP_Info.rtp_hdr ;
cp[0] = cp2[0] ;
cp[1] = cp2[1] ;
cp[2] = ( m_RTP_Info.rtp_hdr.seq >> 8 ) & 0xff ;
cp[3] = m_RTP_Info.rtp_hdr.seq & 0xff ;
cp[4] = ( m_RTP_Info.rtp_hdr.ts >> 24 ) & 0xff ;
cp[5] = ( m_RTP_Info.rtp_hdr.ts >> 16 ) & 0xff ;
cp[6] = ( m_RTP_Info.rtp_hdr.ts >> 8 ) & 0xff ;
cp[7] = m_RTP_Info.rtp_hdr.ts & 0xff ;
cp[8] = ( m_RTP_Info.rtp_hdr.ssrc >> 24 ) & 0xff ;
cp[9] = ( m_RTP_Info.rtp_hdr.ssrc >> 16 ) & 0xff ;
cp[10] = ( m_RTP_Info.rtp_hdr.ssrc >> 8 ) & 0xff ;
cp[11] = m_RTP_Info.rtp_hdr.ssrc & 0xff ;
m_RTP_Info.hdr_len = 12 ;
/*!
* /n The FU indicator octet has the following format:
* /n
* /n +---------------+
* /n MSB |0|1|2|3|4|5|6|7| LSB
* /n +-+-+-+-+-+-+-+-+
* /n |F|NRI| Type |
* /n +---------------+
* /n
* /n The FU header has the following format:
* /n
* /n +---------------+
* /n |0|1|2|3|4|5|6|7|
* /n +-+-+-+-+-+-+-+-+
* /n |S|E|R| Type |
* /n +---------------+
*/
if ( m_RTP_Info.FU_flag )
{
// FU indicator F|NRI|Type
cp[12] = ( m_RTP_Info.nal.type & 0xe0 ) | 28 ; //Type is 28 for FU_A
//FU header S|E|R|Type
cp[13] = ( m_RTP_Info.s_bit << 7 ) | ( m_RTP_Info.e_bit << 6 ) | ( m_RTP_Info.nal.type & 0x1f ) ; //R = 0, must be ignored by receiver
m_RTP_Info.s_bit = m_RTP_Info.e_bit= 0 ;
m_RTP_Info.hdr_len = 14 ;
}
m_RTP_Info.start = &cp[m_RTP_Info.hdr_len] ; // new start of payload
*pPacketSize = m_RTP_Info.hdr_len + ( m_RTP_Info.end - m_RTP_Info.start ) ;
return m_RTP_Info.pRTP ;
}
private:
unsigned int StartCode( unsigned char *cp )
{
unsigned int d32 ;
d32 = cp[3] ;
d32 <<= 8 ;
d32 |= cp[2] ;
d32 <<= 8 ;
d32 |= cp[1] ;
d32 <<= 8 ;
d32 |= cp[0] ;
return d32 ;
}
private:
RTP_INFO m_RTP_Info ;
bool m_bBeginNAL ;
unsigned short m_MAXRTPPACKSIZE ;
};
// class CH264_RTP_PACK end
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
// class CH264_RTP_UNPACK start
class CH264_RTP_UNPACK
{
#define RTP_VERSION 2
#define BUF_SIZE (1024 * 500)
typedef struct
{
//LITTLE_ENDIAN
unsigned short cc:4; /* CSRC count */
unsigned short x:1; /* header extension flag */
unsigned short p:1; /* padding flag */
unsigned short v:2; /* packet type */
unsigned short pt:7; /* payload type */
unsigned short m:1; /* marker bit */
unsigned short seq; /* sequence number */
unsigned long ts; /* timestamp */
unsigned long ssrc; /* synchronization source */
} rtp_hdr_t;
public:
CH264_RTP_UNPACK ( HRESULT &hr, unsigned char H264PAYLOADTYPE = 96 )
: m_bSPSFound(false)
, m_bWaitKeyFrame(true)
, m_bPrevFrameEnd(false)
, m_bAssemblingFrame(false)
, m_wSeq(1234)
, m_ssrc(0)
{
m_pBuf = new BYTE[BUF_SIZE] ;
if ( m_pBuf == NULL )
{
hr = E_OUTOFMEMORY ;
return ;
}
m_H264PAYLOADTYPE = H264PAYLOADTYPE ;
m_pEnd = m_pBuf + BUF_SIZE ;
m_pStart = m_pBuf ;
m_dwSize = 0 ;
hr = S_OK ;
}
~CH264_RTP_UNPACK(void)
{
delete [] m_pBuf ;
}
//pBuf为H264 RTP视频数据包,nSize为RTP视频数据包字节长度,outSize为输出视频数据帧字节长度。
//返回值为指向视频数据帧的指针。输入数据可能被破坏。
BYTE* Parse_RTP_Packet ( BYTE *pBuf, unsigned short nSize, int *outSize )
{
if ( nSize <= 12 )
{
return NULL ;
}
BYTE *cp = (BYTE*)&m_RTP_Header ;
cp[0] = pBuf[0] ;
cp[1] = pBuf[1] ;
m_RTP_Header.seq = pBuf[2] ;
m_RTP_Header.seq <<= 8 ;
m_RTP_Header.seq |= pBuf[3] ;
m_RTP_Header.ts = pBuf[4] ;
m_RTP_Header.ts <<= 8 ;
m_RTP_Header.ts |= pBuf[5] ;
m_RTP_Header.ts <<= 8 ;
m_RTP_Header.ts |= pBuf[6] ;
m_RTP_Header.ts <<= 8 ;
m_RTP_Header.ts |= pBuf[7] ;
m_RTP_Header.ssrc = pBuf[8] ;
m_RTP_Header.ssrc <<= 8 ;
m_RTP_Header.ssrc |= pBuf[9] ;
m_RTP_Header.ssrc <<= 8 ;
m_RTP_Header.ssrc |= pBuf[10] ;
m_RTP_Header.ssrc <<= 8 ;
m_RTP_Header.ssrc |= pBuf[11] ;
BYTE *pPayload = pBuf + 12 ;
DWORD PayloadSize = nSize - 12 ;
// Check the RTP version number (it should be 2):
if ( m_RTP_Header.v != RTP_VERSION )
{
return NULL ;
}
/*
// Skip over any CSRC identifiers in the header:
if ( m_RTP_Header.cc )
{
long cc = m_RTP_Header.cc * 4 ;
if ( Size < cc )
{
return NULL ;
}
Size -= cc ;
p += cc ;
}
// Check for (& ignore) any RTP header extension
if ( m_RTP_Header.x )
{
if ( Size < 4 )
{
return NULL ;
}
Size -= 4 ;
p += 2 ;
long l = p[0] ;
l <<= 8 ;
l |= p[1] ;
p += 2 ;
l *= 4 ;
if ( Size < l ) ;
{
return NULL ;
}
Size -= l ;
p += l ;
}
// Discard any padding bytes:
if ( m_RTP_Header.p )
{
if ( Size == 0 )
{
return NULL ;
}
long Padding = p[Size-1] ;
if ( Size < Padding )
{
return NULL ;
}
Size -= Padding ;
}*/
// Check the Payload Type.
if ( m_RTP_Header.pt != m_H264PAYLOADTYPE )
{
return NULL ;
}
int PayloadType = pPayload[0] & 0x1f ;
int NALType = PayloadType ;
if ( NALType == 28 ) // FU_A
{
if ( PayloadSize < 2 )
{
return NULL ;
}
NALType = pPayload[1] & 0x1f ;
}
if ( m_ssrc != m_RTP_Header.ssrc )
{
m_ssrc = m_RTP_Header.ssrc ;
SetLostPacket () ;
}
if ( NALType == 0x07 ) // SPS
{
m_bSPSFound = true ;
}
if ( !m_bSPSFound )
{
return NULL ;
}
if ( NALType == 0x07 || NALType == 0x08 ) // SPS PPS
{
m_wSeq = m_RTP_Header.seq ;
m_bPrevFrameEnd = true ;
pPayload -= 4 ;
*((DWORD*)(pPayload)) = 0x01000000 ;
*outSize = PayloadSize + 4 ;
return pPayload ;
}
if ( m_bWaitKeyFrame )
{
if ( m_RTP_Header.m ) // frame end
{
m_bPrevFrameEnd = true ;
if ( !m_bAssemblingFrame )
{
m_wSeq = m_RTP_Header.seq ;
return NULL ;
}
}
if ( !m_bPrevFrameEnd )
{
m_wSeq = m_RTP_Header.seq ;
return NULL ;
}
else
{
if ( NALType != 0x05 ) // KEY FRAME
{
m_wSeq = m_RTP_Header.seq ;
m_bPrevFrameEnd = false ;
return NULL ;
}
}
}
///////////////////////////////////////////////////////////////
if ( m_RTP_Header.seq != (WORD)( m_wSeq + 1 ) ) // lost packet
{
m_wSeq = m_RTP_Header.seq ;
SetLostPacket () ;
return NULL ;
}
else
{
// 码流正常
m_wSeq = m_RTP_Header.seq ;
m_bAssemblingFrame = true ;
if ( PayloadType != 28 ) // whole NAL
{
*((DWORD*)(m_pStart)) = 0x01000000 ;
m_pStart += 4 ;
m_dwSize += 4 ;
}
else // FU_A
{
if ( pPayload[1] & 0x80 ) // FU_A start
{
*((DWORD*)(m_pStart)) = 0x01000000 ;
m_pStart += 4 ;
m_dwSize += 4 ;
pPayload[1] = ( pPayload[0] & 0xE0 ) | NALType ;
pPayload += 1 ;
PayloadSize -= 1 ;
}
else
{
pPayload += 2 ;
PayloadSize -= 2 ;
}
}
if ( m_pStart + PayloadSize < m_pEnd )
{
CopyMemory ( m_pStart, pPayload, PayloadSize ) ;
m_dwSize += PayloadSize ;
m_pStart += PayloadSize ;
}
else // memory overflow
{
SetLostPacket () ;
return NULL ;
}
if ( m_RTP_Header.m ) // frame end
{
*outSize = m_dwSize ;
m_pStart = m_pBuf ;
m_dwSize = 0 ;
if ( NALType == 0x05 ) // KEY FRAME
{
m_bWaitKeyFrame = false ;
}
return m_pBuf ;
}
else
{
return NULL ;
}
}
}
void SetLostPacket()
{
m_bSPSFound = false ;
m_bWaitKeyFrame = true ;
m_bPrevFrameEnd = false ;
m_bAssemblingFrame = false ;
m_pStart = m_pBuf ;
m_dwSize = 0 ;
}
private:
rtp_hdr_t m_RTP_Header ;
BYTE *m_pBuf ;
bool m_bSPSFound ;
bool m_bWaitKeyFrame ;
bool m_bAssemblingFrame ;
bool m_bPrevFrameEnd ;
BYTE *m_pStart ;
BYTE *m_pEnd ;
DWORD m_dwSize ;
WORD m_wSeq ;
BYTE m_H264PAYLOADTYPE ;
DWORD m_ssrc ;
};
// class CH264_RTP_UNPACK end
//////////////////////////////////////////////////////////////////////////////////////////
相关文章推荐
- 流媒体协议介绍(rtp/rtcp/rtsp/rtmp/mms/hls)
- 流媒体传输协议(rtp/rtcp/rtsp/rtmp/mms/hls)
- 流媒体协议引见(rtp/rtcp/rtsp/rtmp/mms/hls)
- 流媒体协议介绍(rtp/rtcp/rtsp/rtmp/mms/hls)
- 流媒体协议介绍(rtp/rtcp/rtsp/rtmp/mms/hls)
- 流媒体协议介绍(rtp/rtcp/rtsp/rtmp/mms/hls)
- 流媒体协议介绍(rtp/rtcp/rtsp/rtmp/mms/hls)
- 流媒体协议介绍(rtp/rtcp/rtsp/rtmp/mms/hls)
- RTP学习(一):流媒体协议(RTP/RTCP/RTSP)
- 流媒体传输协议综述(RTP-RTCP RTSP RTMP HTTP)
- 流媒体协议引见(rtp/rtcp/rtsp/rtmp/mms/hls)
- 流媒体传输协议系列之----RTP/RTCP协议解析
- 流媒体协议介绍(rtp/rtcp/rtsp/rtmp/mms/hls)
- 流媒体协议介绍(rtp/rtcp/rtsp/rtmp/mms/hls)
- 流媒体协议介绍(rtp/rtcp/rtsp/rtmp/mms/hls)
- RTP 与RTCP 解释. 含同步时间戳 RTP协议是real-time transport protocol的缩写,被设计来传输流媒体数据,有着广泛的应用,其它相关介绍自己去看RFC,我不打算
- 流媒体协议介绍(RTP/RTCP/RTSP/MMS/HLS/HTTP progressive streaming)
- 流媒体协议介绍(rtp/rtcp/rtsp/rtmp/mms/hls
- 流媒体协议介绍(rtp/rtcp/rtsp/rtmp/mms/hls)
- 流媒体协议介绍(rtp/rtcp/rtsp/rtmp/mms/hls)