前言:
本文是博主在学习流媒体时进行的小结,涉及内容较多。
由于流媒体协议说复杂也复杂,说简单也简单,复杂在需要考虑到每一位比特,简单在现成的轮子很多,只是会用往往已经足够。因此此文面向那些希望对流媒体协议有一定的基础概念的读者,而非注重实操。
另外,在博主总结期间参考了前辈们的诸多文章,在此表示感谢!
NAL单元
前言
与H.264/AVC 类似,H265/HEVC也采用视频编码层(Video Coding Layer,VCL
)和网络适配层(Network Abstract Layer, NAL
)的双层结构,以适应不同网络环境和视频应用。网络适配层的主要任务是对视频压缩后的数据进行划分和封装,并进行必要的标识,使其更好的适应各种网络环境。
承载视频压缩数据的NALU又名VCLU
,即VCL NALU
,而承载其他信息的则称为non-VCLU
。
HEVC中码流的封装结构
视频编码过程中输出包含不同内容的压缩数据比特流片段成为SODB(String of Data Bits)
,添加RBSP尾(rbsp_trailing_bits,由称为RBSP停止比特的1和多个或是零个0组成)之后就是RBSP。换句话说,RBSP就是整数字节化后的SODB。
H.265码流也有两种封装格式,一种是用起始码作为分界的Annex B
格式,另一种则是在NALU头添加NALU长度前缀
的格式,称为HVCC。
HEVC中图像分类
HEVC中,每个VCLU包含一个视频片段(Slice Segment,SS)。
由于时域预测机制使得图像之间具有解码依赖关系,导致图像间的关系变得错综复杂,尤其是采用双向预测的时候。因此,视频流中会间隔地存在一些随机介入点(Intra Random Access Ponit, IRAP
),从IRAP开始,后续的视频流(指播放顺序在IRAP后的图像)就可以独立正确解码,无须参考IRAP之前的视频信息。IRAP是个点,之后的第一帧图像被称为IRAP图像。
- 解码顺序在IRAP之后而播放顺序在之前的图像称为IRAP的前置(Leading)图像(类似B帧)
- 解码顺序在IRAP之后且播放顺序在之后的图像称为IRAP的后置(Trailing)图像
而前置图像又分为RADL(Random Access Decodable Leading)
图像和RASL(Random Access Skipped Leading)
图像。
RADL图像是不依赖IRAP前的码流信息的图像,从IRAP介入后,其可以被解码;
RASL图像是要依赖IRAP前的码流信息的图像,从IRAP介入后,不可以被解码(比如双向预测的B帧)。
HEVC中规定了三种IRAP图像:
-
IDR(Instantaneous Decoding Refresh)图像
IDR图像要求其前置图像必须都是RADL图像,即IDR和其后续(指解码顺序)不用依赖IDR图像之前的视频流进行解码。
-
CRA(Clean Random Access)图像
允许前置图像是RASL,允许参考CRA图像前的视频流使得RASL图像获得更高的编码效率。
当从CRA图像介入时,RASL图像就无法正常解码。
-
BLA(Broken Link Access)图像
明确CRA的RASL图像无须解码的CRA成为BLA图像。
还有两种图像:
TSA(Temporal sub-layer access):该图像可以切换到时域层大于或等于该图像时域层标识号的时域层
STSA(Step wise Temporal sub-layer access):从该图像切换该图像所属的时域层。
吐槽一句:只记住IDR差不多就可以了……
该表详细地给出了图像的分类,对于无前置图像的IDR图像,从该图像开始,后续的所有图像都可以正确解码,而写后序图像的播放顺序都在IDR之后。
HEVC中的参数集 VPS SPS PPS
参考文章:H265学习之NALU头_水笙赵的博客-CSDN博客_h265 nalu
H265/HEVC包含参数集(VPS,SPS,PPS,SEI),相比 h264 多了一个VPS。
-
VPS(视频参数集)
主要用传输视频分级信息,有利于兼容可分级视频以及3D视频,如视频包含最大的层级,也可包含profile,level等信息。一个给定的视频序列,无论它的SPS是否相同,都参考相同的VPS。 -
SPS(序列参数集)
主要包含一个CVS(Code Video Sequence 编码视频序列,指两个IRAP之间的图像,是一个GOP编码后生成的压缩数据)所有编码图像共享的编码参数,SPS通过PPS引用而作用图像。内容大致可以分为几个部分:1、自引ID;2、解码相关信息,如档次级别、分辨率、子层数等;3、某档次中的功能开关标识及该功能的参数;4、对结构和变换系数编码灵活性的限制信息;5、时域可分级信息;6、VUI。
可以根据SPS计算视频的宽和高:
width = sps->pic_width_in_luma_samples; height = sps->pic_height_in_luma_samples;
当窗口有裁剪时(conformance_window_flag为1),计算如下:
sub_width_c = ((1==chroma_format_idc)||(2 == chroma_format_idc))&&(0==separate_colour_plane_flag)?2:1; sub_height_c = (1==chroma_format_idc)&& (0 == separate_colour_plane_flag)?2:1; width -= (sub_width_c*conf_win_right_offset + sub_width_c*conf_win_left_offset); height -= (sub_height_c*conf_win_bottom_offset + sub_height_c*conf_win_top_offset);
H264和H265帧率计算公式相同,如下:
max_framerate = (float)(sps->vui.vui_time_scale) / (float)(sps->vui.vui_num_units_in_tick);
-
PPS(图像参数集)
一副图像序列中的所有Slice Segment
通过引用PPS进行解码,其大致内容包括初始图像控制信息,如初始量化参数(Quantization Parameter,QP)、分块信息等。此外,为了兼容标准在其他应用上的扩展,例如可分级视频编码器、多视点视频编码器。
之间的关系可以见下图:
VPS,SPS,PPS和SS存在各级引用关系,一副图像中的所有SS引用同一个PPS,一个CVS中所有PPS引用同一个SPS。同一个视频序列中的所有SPS引用一个VPS。VPS,SPS,PPS存在相同参数,后者会覆盖前者。
HEVC中字节流的生成过程
字节流方式则是NALU按照解码顺序排列成字节流传输。由于NALU里没有NALU长度信息,所以如果NALU直接连接成字节流就无法区分不同的NALU,为了解决这个问题需要在每个NALU前添加起始码字段。在H.265标准的附录B中定义了相关规范。
NALU字节流生成过程:
-
在每个NALU前插入3字节起始码
start_code_prefix_one_3bytes
,其值为0x000001
-
如果NALU类型为
VPS_NUT
,SPS_NUT
,PPS_NUT
或者AU的第一个NALU,起始码前还要插入zero_byte,其值为0x00 -
在视频流的首个NALU的起始码(可能包含zero_byte)前插入leading_zero_8bits,其值为0x00。注意:leading_zero_8bits只能加在第一个NALU前,否则0x00后面跟上4字节0x00 00 00 01(zero_byte后跟上 leading_zero_8bits)会被认为是前一个NALU的trailing_zero_8bits
-
根据需要在每个NALU后面添加
trailing_zero_8bits
作为填充数据,其值为0x00
字节流的语法格式如下表所示,通过该语法格式可以从字节流中提取NALU。可以看到通过查找起始码0x000001可以确定NALU的前边界,通过查找第一个0x00000001可以确定视频的前边界,通过查找0x00000001可以确定AU的前边界。
HEVC中NAL的作用机制
可以看出,经过编码后的视频码流由于时域上的参考关系,是具有不同的优先级的,优先级就会被封装在NAL的头信息中
HEVC中NAL单元的基本结构
从NAL单元的基本结构可知,NAL单元(NALU)由NALU头和NALU载荷——原始字节序列负载(Raw Byte sequence payload, RBSP)组成。视频编码生成的压缩比特流片段称为SODB(String of Data Bits),SODB可能不是正好是整数字节,需要在其后填充比特变成整字节,填充后的比特流称为原始字节载荷序列(Raw Byte Sequence Payload,RBSP)。
SODB生成RBSP的过程如下:
1、RBSP第1字节取SODB最左端8比特,第2字节取接下来8比特,以此类推直到SODB剩余内容不足8比特。
2、RBSP下一字节首先包含SODB最后几个比特,然后添加比特1,如果该字节还不满8比特后面填充0。
3、后面可能加入若干16比特的cabac_zero_word作为填充比特,其值为0x00 00。RBSP还不能直接作为NALU Body,因为
由于RBSP中可能含有0x00 00 01,与起始码冲突,必须先进行冲突避免处理。
其中0x00 00 02是预留码。
HEVC中NAL头的语法定义
NALU头部结构如下:
其语法定义如下:
在ffmpeg中的定义如下:
typedef struct H265RawNALUnitHeader {
uint8_t forbidden_zero_bit;
uint8_t nal_unit_type;
uint8_t nuh_layer_id;
uint8_t nuh_temporal_id_plus1;
} H265RawNALUnitHeader;
头部算起来,一共16位,两个字节
接下来介绍每一个语法元素的作用:
forbidden_zero_bits(1bit)
默认为0,值为1时表示错误,当网络发现NAL单元有比特错误时可设置该比特为1,以便接收方纠错或丢掉该单元。
nuh_layer_id(6bit)
layerId预留位,取值范围0~55,用来指示VCL数据属于哪个层或者标识non-VLC数据,默认全为0,用于未来扩展,如用来表示可分级视频或3D视频等。
nal_unit_type(6bits)
用来标识NAL单元类型,取值范围[0, 63],占用NALU header的第一个字节的最后一位和第二个字节的前五位。通常NAL单元类型包括参数集(VPS,PPS,SPS,SEI)以及slice 数据(如IDR,其他类型帧等)。
HEVC中0~40都是有明确含义的。
nuh_temporal_id_plus1(3bit)
该字段默认为1。temporal_id表示NAL单元的时域层级,根据图像时域层级就可以确定其重要性,如时域层级小的图像不会参考时域层级大的图像,如通常参数集 或者idr帧的temporal_id为0。
注:HEVC的NAL头结构与h264有明显的不同,HEVC加入了NAL所在的时域层的ID,取去除了nal_ref_idc元素,将此此信息合并到了nal_unit_type中。
H265(HEVC) nal 单元头介绍及rtp发送中的fu分组发送详解_一二三的博客-CSDN博客_nal rtp
该篇文章中讲述了HEVC的NALU Header ,以及分组分片中FU分组的发送过程,写的比较简要,但是还是可以读一读。
AVC中码流的封装结构
与HEVC近似相同,封装层次如下:
SODB + RBSP trailing bits = RBSP
NAL header(1 byte) + RBSP = NALU
RTP封装格式(12个字节) + NALU = 最后f发送出去的完整包
RBSP就是H.264编码后出来的纯码流文件,给文件加上后缀.h264,得到xxx.h264。
RBSP
一段h264的码流其实就是多个sequence组成的,
一个sequence是一秒,如果FPS等于30,就有30帧图像(包括I/P/B帧)
每个sequence均有固定结构单元:1 SPS + 1 PPS + 1 SEI + 1 I 帧 + 若干P帧(加上B帧一共有6种单元情况)
H.264在编码的时候,生成一个序列时,序列中每个单元前面就会加上00 00 00 01
作为分隔符。
看H264的码流结构,NALU头 + RBSP = NALU
SPS
序列参数集:固定14个字节。
序列参数集。SPS中保存了一组编码视频序列(Coded video sequence)的全局参数,所谓的编码视频序列即原始视频的一帧一帧的像素数据经过编码之后的结构组成的序列。而每一帧的编码后数据所依赖的参数保存于图像参数集中。
里面包含宏块编码方式、图像大小尺寸、宏块个数,播放器通过这些参数,调用播放器里面的对应算法去解码。
具体意义参考博客:
H264编解码SPS、PPS参数说明_chen_song_的博客-CSDN博客_h264 sps
h.264中的SPS和PPS_sps pps_thehunters的博客-CSDN博客
H264码流中SPS PPS详解<转> - 瓦楞球 - 博客园 (cnblogs.com)
PPS
图像参数集:固定4个字节
具体意义参考博客:
H264编解码SPS、PPS参数说明_chen_song_的博客-CSDN博客_h264 sps
h.264中的SPS和PPS_sps pps_thehunters的博客-CSDN博客
H264码流中SPS PPS详解<转> - 瓦楞球 - 博客园 (cnblogs.com)
SEI
补充增强信息
具体意义参考博客:SEI(Supplemental Enhancement Information)_拉轰小郑郑的博客-CSDN博客
I帧、P帧、B帧
I帧:I帧是关键,丢了I帧当前sequence就废了,每个sequence有且只有1个I帧
帧内编码帧 ,I帧表示关键帧,你可以理解为这一帧画面的完整保留;
解码时只需要本帧数据就可以完成(因为包含完整画面),
I帧大,说明本身压缩比不高,图像数据更完整,而P帧可以越小,反之I帧越小则P帧则会越大
P帧:前向预测编码帧。P帧表示的是这一帧跟之前的一个关键帧(或P帧)的差别,解码时需要用之前缓存的画面叠加上本帧定义的差别,生成最终画面。(也就是差别帧,P帧没有完整画面数据,只有与前一帧的画面差别的数据)
B帧:双向预测内插编码帧。B帧是双向差别帧,也就是B帧记录的是本帧与前后帧的差别
具体特点参考:https://www.cnblogs.com/cy568searchx/p/6125031.html
AVC中NAL单元的基本结构
下图为NALU头部结构,就1字节,和HEVC的两字节不同:
第1位:禁止位,值为1表示语法出错;
第2~3位:为参考级别nal_ref_idc
,数值越高,级别越高;
第4~8:为是NALU类型nal_unit_type
(比如整个头是0x67,即01100111
,取后5位00111表示sps)
通常情况下,因为互联网是基于分组传输的,接收端收到数据包往往有延迟和乱序。流媒体要流式传输,需要从降低时延和恢复数据包时序入手。发送端为了降低延迟,往往对传输数据进行压缩。而在接收端为了恢复时序,就需要接收缓冲
,即将收到的数据包缓存起来,按照封装信息进行重新排序,最后将重新排序后的数据包放入播放缓冲
。播放缓冲存在的意义是为了应对网络波动,导致数据迟迟无法完整的被下载到接收缓冲区,在播放端无内容可放时,就会出现画面卡顿。
总而言之,控制流媒体数据包到达接收端的时序
RTP协议 + RTCP协议
RTP概念及应用环境
RTP全名是Real-time Transport Protocol(实时传输协议)
。
它是IETF提出的一个标准,对应的RFC文档为RFC3550(RFC1889为其过期版本)。RFC3550不仅定义了RTP,而且定义了配套的相关协议RTCP(Real-time Transport Control Protocol,实时传输控制协议)
。
RTP用来为IP网上的语音、图像、传真等多种需要实时传输的多媒体数据提供端到端的实时传输服务。RTP为Internet上端到端的实时传输提供时间信息和流同步,但并不保证服务质量,服务质量由RTCP来提供。
RTP用于在单播或多播网络中传送实时数据。它们典型的应用场合有如下几个。
-
简单的多播音频会议。
语音通信通过一个多播地址和一对相邻端口来实现。一个用于音频数据(RTP),另一个用于控制包(RTCP)。
-
音频和视频会议。
如果在一次会议中同时使用了音频和视频会议,这两种媒体将分别在不同的RTP会话中传送,每一个会话使用不同的传输地址(IP地址+端口)。如果一个用户同时使用了两个会话,则每个会话对应的RTCP包都使用规范化名字
CNAME(Canonical Name)
。与会者可以根据RTCP包中的CNAME来获取相关联的音频和视频,然后根据RTCP包中的计时信息(Network time protocol)
来实现音频和视频的同步。 -
翻译器和混合器。
翻译器和混合器都是RTP级的中继系统。翻译器用在通过IP多播不能直接到达的用户区,例如发送者和接收者之间存在防火墙。当与会者能接收的音频编码格式不一样,比如有一个与会者通过一条低速链路接入到高速会议,这时就要使用混合器。在进入音频数据格式需要变化的网络前,混合器将来自一个源或多个源的音频包进行重构,并把重构后的多个音频合并,采用另一种音频编码进 行编码后,再转发这个新的RTP包。从一个混合器出来的所有数据包要用混合器作为它们的同步源(SSRC,见RTP的封装)来识别,可以通过贡献源列表(CSRC表,见RTP的封装)可以确认与会者。
RTP的协议层次
该图是一个典型的流媒体传输的体系结构。
RTP被划分在传输层,它建立在UDP上。同UDP协议一样,为了实现其实时传输功能,RTP也有固定的封装形式。RTP用来为端到端的实时传输提供时间信息和流同步,但并不保证服务质量。服务质量由RTCP来提供。
不少人也把RTP归为应用层的一部分,这是从应用开发者的角度来说的。操作系统中的TCP/IP等协议栈所提供的是我们最常用的服务,而RTP的实现还是要靠开发者自己。因此从开发的角度来说,RTP的实现和应用层协议的实现没什么不同,所以可将RTP看成应用层协议。
RTP Over UDP
RTP实现者在发送RTP数据时,需先将数据封装成RTP包,而在接收到RTP数据包,需要将数据从RTP包中提取出来。
从上文中可以推测出,头部中应该有时间戳、数据来源等字段,RTP的固定字节量为12字节,而下图则展示了RTP协议封装时使用的完整头部结构:
-
版本号(V):2比特,用来标志使用的RTP版本。
-
填充位(P):1比特,如果该位置位,则该RTP包的尾部就包含附加的填充字节。
-
扩展位(X):1比特,如果该位置位的话,RTP固定头部后面就跟有一个扩展头部。
-
CSRC计数器(CC):4比特,含有固定头部后面跟着的CSRC的数目。
-
标记位(M):1比特,该位的解释由配置文档(Profile)来承担.
-
载荷类型(PT):7比特,范围为0~127,标识了RTP载荷的类型。具体含义可以参考下面的这篇博文:RTP 有效负载(载荷)类型,RTP Payload。有一些媒体内容出现的比较晚,包括264,所以只能用后面的96~127动态位来表示,例如DynamicRTP-Type-96,即为h264。
-
序列号(SN):16比特,发送方在每发送完一个RTP包后就将该域的值增加1,接收方可以由该域检测包的丢失及恢复包序列。序列号的初始值是随机的。但也只是表示了包发出的先后顺序。
-
时间戳:32比特,记录了该包中数据的第一个字节的采样时刻。在一次会话开始时,时间戳初始化成一个初始值。即使在没有信号发送时,时间戳的数值也要随时间而不断地增加(时间在流逝嘛)。时间戳是去除抖动和实现同步不可缺少的。
时间戳的增量与视频/音频的时钟频率(采样频率)有关,增加值为固定量。
-
以音频负载类型为例,假设采样率为8kHz,则每个采样的持续时间为1/8000秒,即0.125毫秒。因此,如果上一个RTP数据包的时间戳值是T1,则当前数据包的时间戳值应该是T1+采样数*(时钟频率/采样率)。这里,时钟频率指的是RTP时钟频率,通常为90kHz。因此,对于音频负载类型,每个RTP数据包的时间戳值应该增加:
timestamp = T1 + (采样数 * 90000 / 8000)
-
对于视频负载类型,时间戳的增加方式类似,只是单位不同。假设视频帧率为30帧/秒,则每个视频帧的间隔为1/30秒,即33.333毫秒。因此,如果上一个RTP数据包的时间戳值是T1,则当前数据包的时间戳值应该是T1+帧数*(时钟频率/帧率)。因此,对于视频负载类型,每个RTP数据包的时间戳值应该增加:
timestamp = T1 + (帧数 * 90000 / 30)
这里我以抓包H265为例,简单验证一下该表达式:
以上是我抓包的h265码流,可以看到蓝框中不同类型的包的时间戳分别为
...3892,...7422,...10951,...14480,...18010
我们可以计算一下时间戳之间的间隔,分别为
3530, 3529, 3529, 3530
。一开始我以为这是fps,但是原视频fps = 25.5,1 / 25.5 ≈ 0.03921s,对不上这个数字。后来查阅资料后,应该按照上面的式子计算,我们代入参数,计算一下时间戳增加的间隔:
∆timestamp = 1(帧视频)* 90000 / 25.5 ≈ 3529
,如果代入单位的话,即可知时间戳没有单位(该过程省略)。 -
-
同步源标识符(Synchronization sourc*e identifier*, SSRC):32比特,同步源就是指RTP包流的来源。在同一个RTP会话中不能有两个相同的SSRC值。该标识符是随机选取的 RFC1889推荐了MD5随机算法。
-
贡献源列表(CSRC(Contributing sourc*e IDs*) List):0~15项,每项32比特,用来标志对一个RTP混合器产生的新包有贡献的所有RTP包的源。由混合器将这些有贡献的SSRC标识符插入表中。SSRC标识符都被列出来,以便接收端能正确指出交谈双方的身份。
如果只有一个源的话,这项是没有的。
RTP Over TCP
RTP默认是采用UDP发送的,格式为RTP头+RTP载荷,如果是使用TCP,那么需要在RTP头之前再加上四个字节
第一个字节:$,辨识符
第二个字节:通道,在SETUP的过程中获取
第三第四个字节: RTP包的大小,最多只能12位,第三个字节保存高4位,第四个字节保存低8位
RTP载荷 以H264码流为例
下图为H264中载荷头的格式:
载荷格式定义三个不同的基本载荷结构,接收者可以通过RTP荷载的第一个字节后5位(如图2)识别荷载结构。
Nalu_Type | NALU内容 | 备注 |
0 | 未指定 | |
1 | 非IDR图像编码的slice | 比如普通I、P、B帧 |
2 | 编码slice数据划分A | 2类型时,只传递片中最重要的信息,如片头,片中宏块的预测模式等;一般不会用到; |
3 | 编码slice数据划分B | 3类型是只传输残差;一般不会用到; |
4 | 编码slice数据划分C | 4时则只可以传输残差中的AC系数;一般不会用到; |
5 | IDR图像中的编码slice | IDR帧,IDR一定是I帧但是I帧不一定是IDR帧。 |
6 | SEI补充增强信息单元 | 可以存一些私有数据等; |
7 | SPS 序列参数集 | SPS对如标识符、帧数以及参考帧数目、解码图像尺寸和帧场模式等解码参数进行标识记录 |
8 | PPS 图像参数集 | PPS对如熵编码类型、有效参考图像的数目和初始化等解码参数进行标志记录。 |
9 | 单元定界符 | 视频图像的边界 |
10 | 序列结束 | 表明下一图像为IDR图像 |
11 | 码流结束 | 表示该码流中已经没有图像 |
12 | 填充数据 | 哑元数据,用于填充字节 |
13-23 | 保留 | |
24 | 单时间聚合包类型A(SATP-A) | |
25 | 单时间聚合包类型B (STAP-B) | |
26 | 多时间聚合包类型(MTAP)16位位移(MTAP16 ) | |
27 | 多时间聚合包类型(MTAP)24位位移(MTAP24 ) | |
28 | 分片单元FU-A | |
29 | 分片单元FU-B | |
30-31 | 未被定义 |
-
单个NAL包:载荷中只包含一个NAL单元。NAL头类型的域等于原始NAL单元类型,范围在1~23之间
-
聚(组)合包(Aggregation Packet,AP):用于聚合多个NAL单元到单个RTP载荷NALU中,有四种版本
- 单时间聚合包类型A
(SATP-A)
NAL单元型号 = 24 - 单时间聚合包类型B
(STAP-B)
NAL单元型号 = 25 - 多时间聚合包类型(MTAP)16位位移(
MTAP16
) NAL单元型号 = 26 - 多时间聚合包类型(MTAP)24位位移(
MTAP24
) NAL单元型号 = 27
- 单时间聚合包类型A
-
分片分组(Fragmentation Packet,FP):一个分组只承载一个NALU的一部分。现存两个版本
FU-A
,FU-B
,用NAL单元类型 28,29标识-
Q:FU-A和FU-B版本的不同之处?
-
A:回答来自于Chat-GPT,
FU-A是一种将原始NALU分解成若干个小分片单元的方法,并在每个分片单元上添加头部以标识它们的类型和位置。它用于可靠性更高的环境,例如在实时视频传输中。
FU-B是一种将NALU分解成若干个小分片单元的方法,不同的是它不添加头部信息,而是使用隐含的方法来标识分片单元的类型和位置。它用于码率敏感的环境,因为它不需要添加额外的头部信息。
总的来说,FU-A提供了更高的可靠性,而FU-B提供了更高的效率。两者都是在H.264视频编码中广泛使用的。
网络上大多是提及Fu-A,很少有提到Fu-B的。
-
常用的打包时的分包规则是:如果小于MTU采用单个NAL单元包,如果大于MTU就采用FUs分片方式。
因为常用的打包方式就是单个NAL包和FU-A方式,所以我们只解析这两种。
单个NAL包
必须只包含一个NAL单元。聚合包和分片单元不可以用在单个NAL单元包中,RTP序号必须符合NAL的解码顺序,且NAL单元的第一个字节和RTP载荷的头的第一个字节重合。
打包H264码流时,只需在帧前面加上12字节的RTP头即可。
聚合包封装类型
(略)
分片单元(以FU-A为例)
上图表示FU-A的RTP载荷形式。
分片只定义于单个NAL单元,而不用于任何聚合包(将多个NALU置于一个Packet)。
NAL单元的一个分片由整数个连续NAL单元字节组成。每个NAL单元字节必须正好是该NAL单元一个分片的一部分(意思就是不能跨分片)。
相同NAL单元的分片必须使用递增的RTP序号连续顺序发送(第一分片与最后分片之间没有其他的RTP包)。相似地,NAL单元必须按照RTP顺序号的顺序装配。
当一个NALU被分片运送在分片单元(FUs)中时,被引用为分片NAL单元。STAPs, MTAPs(?)不可以被分片。 FUs不可以嵌套, 即一个FU 不可以包含另一个FU。运送FU的RTP时戳被设置成分片NAL单元的时刻。
FU-A由1字节的分片单元indicator(下图左图),1字节的分片单元header(下图右图),和载荷组成。
下面介绍分片单元indicator中各位的作用:
-
F - 1bit 禁止位
值为1表示语法出错;
-
NRI - 2 bit 参考级别
数值越高,级别越高;
-
FU Type - 5 bit
NALU类型
nal_unit_type
(但是这里应当按照分不分NALU做区分
以下介绍分片单元头的各位作用:
-
S -1 bit 起始位
设置成1时,表示分片为NAL单元的开始。
当荷载不是分片NAL单元载荷的开始,就设为0。
-
E - 1 bit 结束位
当设置成1, 结束位指示分片NAL单元的结束,即载荷的最后字节也是分片NAL单元的最后一个字节。
当跟随的 FU荷载不是分片NAL单元的最后分片,结束位设置为0。
-
R - 1 bit 保留位
必须设置为0,接收者必须忽略该位
-
Type - 5 bit 用于标识NALU中(视频数据)的类型
打包时,原始的NAL头的前三位为FU标识的前三位,原始的NAL头的后五位为FU header的后五位。
以该码流信息为例,前十二个字节是RTP Header,7c是FU indicator,85是FU header,转换为二进制如下:
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,说明是IDR帧中的编码Slice(不知道为什么是关键帧请自行谷歌)
打包时,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)为NALU header。
RTP载荷 以H265码流为例
参考文章:H265码流RTP封装方式详解_一二三的博客-CSDN博客_rtp h265
此处还是需要摆一下H265中 NALU 格式的定义,为2字节大小:
相比较H264的NALU头,265中去掉了nal_ref_idc
字段,并加入了nal所在时间层的ID,即TID
一项,各字段的含义在首篇NALU中已经解析过,此处简单提一下:
-
F:禁止位,通常情况下为0, 如果为1,表示该帧无效
-
Type:帧类型,6bits(9~14位),0-31是vcl nal单元;32-63,是非vcl nal单元,VCL是指携带编码数据的数据流,而non-VCL则是控制数据流。
-
LayerID:6 bits,表示NAL所在的Access unit所属的层,该字段是为了HEVC的继续扩展设置,一般为0
-
TID:3bits,一般为1,此字段指定nal单元加1的时间标识符。时间id的值等于tid-1,tid的值为0是非法的,以确保nal单元报头中至少只有一个比特等于1,以便能够在nal单元头和nal单元有效负载数据中独立考虑启动代码仿真(这句话我也没明白)。
此处以VPS包为例,见下图:
Single NAL Unit Packets(SNUs)封装模式
RTP属于应用层协议,封装完之后要置于数据链路层传输,参考文章中说数据帧大小小于MTU,我认为是不对的。
使用协议封装完的数据,在传输层,最大上限应该是MSS。当然,在这里先不是很重要,嘻嘻。
如果传输数据帧小于最大载荷值,可采用单独一帧封装到一个RTP包中,封装格式如下:
PayloadHeader
一般与NALU Header
定义完全一致;
DONL:Decoding Order Number
,当使用多slice编码模式时使用,用于判断一帧的每个slice是否收齐,一般使用单slice就无此字段,所以通常境况下,单一帧模式封装方式与H264一致。
H265帧去掉起始位直接作为负载,这里不做过多介绍。
Aggregation Packets (APs)封装模式
当帧很小,可以多帧组合封装到一个RTP包,比如VPS、SPS、PPS,可以看到帧大小确实很小。
其格式如下:
PayloadHeader 负载头,与H264 NALUheader类似,有F,TYPE,LayerID,TID组成,一般F=0,LayerID=0,TID=1,这里Type必须为48,标识组合包头。
不带DONL的组合包封装模式与H264类似,这里不做多说,一般情况下很少用到组合帧封装方式,小于MTU的帧一般是单一帧封装,减少解封装复杂性。
Fragmentation Units(FUs)封装模式
当视频帧大于最大负载,需要对帧进行分包发送,从而避免IP层分片,这里采用FU分片模式,格式如下:
这里PayloadHeader
中F=0,LayerID=0,TID=1
,Type
必须为49表示FU分片。
FU header
定义与FU-A定义基本一致,由于NALU Type在H265中为6bits表示,所以这里去掉了R,只保留S/E/TYPE
格式如下:
S
:1bit,1-表示是首个分片报文,0 - 非首个分片报文
E
:1bit,1-表示最后一个分片报文,0 -非最后一个分片报文
FuType
:6 bits,对应的NALU type
RTP载荷PS流(有些老旧的知识)
PS流指program streaming
,是使用MPEG-2标准对多媒体数据进行编码的一种方式。
每个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 包由外到内顺序是:
PS header > 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流包头
主要参数有三个:
-
`Pack start code`:包起始码字段,值为0x000001BA的位串,用来标志一个包的开始。
-
`System clock reference base`,`system clock reference extenstion`:系统时钟参考字段。
-
`Pack stuffing length`:包填充长度字段,3 位整数,规定该字段后填充字节的个数
PS system header 系统标题
System header当且仅当pack是第一个数据包时才存在,即PS包头之后就是系统标题。取值0x000001BB的位串,指出系统标题的开始,暂时不需要处理,读取Header Length直接跳过即可。
PS system map 系统映射
System Map也是当且仅当pack是第一个数据包时才存在,即系统标题之后就是节目流映射。取值0x000001BC的位串,指出节目流映射的开始,暂时不需要处理,读取Header Length直接跳过即可。前5字节的结构同System Header。
举例如下:
前14个字节是PS包头(注意,没有扩展);
接下来的00 00 01 bb是系统标题起始码;
接下来的00 0c说明了系统标题的长度(不包括起始码和长度字节本身);
接下来的12个字节是系统标题的具体内容,这里不做解析;
继续看到00 00 01 bc,这是PS System Map起始码;
紧接着的00 1e同样代表长度;跳过e1 ff,基本没用;
接着2个字节代表program_stream_info长度,这里是00 00,即便有值,一般也可以直接跳过;
接下来是00 18,代表基本流长度,说明了后面还有24个字节;
接下来的1b,意思是H264编码格式;
下一个字节e0,意思是视频流;
接下里00 0c,同样代表接下的长度12个字节;
跳过这12个字节,看到90,这是G.711音频格式;
下一个字节是c0,代表音频流;
接下来的00 00同样代表长度,这里是0;
接下来4个字节是CRC,循环冗余校验。
到这里PS system map流解析完毕。
PES header
PES(Packetized Elementary Stream)头是MPEG-2 Program Stream(PS)格式中用于对音频和视频数据进行封装的数据单元。
这个长度确实巨夸张……
一些要介绍的字段如下:
-
Packet start code prefix:值为0x000001的位串,它和后面的stream id 构成了标识分组开始的分组起始码,用来标志一个包的开始。
-
Stream id:在媒体流中,它规定了基本流的号码和类型。0x(C0~DF)指音频,0x(E0~EF)为视频
-
PES packet length:16 位字段,指出了PES 分组中跟在该字段后的字节数目。值为0 表示PES 分组长度要么没有规定要么没有限制。这种情况只允许出现在有效负载包含来源于传输流分组中某个视频基本流的字节的PES 分组中。
-
PTS_DTS:2 位字段。当值为'10'时,PTS 字段应出现在PES 分组标题中;当值为'11'时,PTS 字段和DTS 字段都应出现在PES 分组标题中;当值为'00'时,PTS 字段和DTS 字段都不出现在PES分组标题中。值'01'是不允许的。
-
ESCR:1位。置'1'时表示ESCR 基础和扩展字段出现在PES 分组标题中;值为'0'表示没有ESCR 字段。
-
ESrate:1 位。置'1'时表示ES rate 字段出现在PES 分组标题中;值为'0'表示没有ES rate 字段。
-
DSMtrick mode:1 位。置'1'时表示有8 位特技方式字段;值为'0'表示没有该字段。
-
Additionalinfo:1 位。附加版权信息标志字段。置'1'时表示有附加拷贝信息字段;值为'0'表示没有该字段。
-
CRC:1 位。置'1'时表示CRC 字段出现在PES 分组标题中;值为'0'表示没有该字段。
-
Extensionflag:1 位标志。置'1'时表示PES 分组标题中有扩展字段;值为'0'表示没有该字段。
PES header data length: 8 位。PES 标题数据长度字段。指出包含在PES 分组标题中的可选字段和任何填充字节所占用的总字节数。该字段之前的字节指出了有无可选字段。
RTCP概述
RTCP作为控制协议,其承载的信息量遥远大于RTP本身。
RTCP的主要功能是:服务质量的监视与反馈、媒体间的同步,以及多播组中成员的标识。
在RTP会话期间,各参与者周期性地传送RTCP包。RTCP包中含有已发送的数据包的数量、丢失的数据包的数量等统计资料,因此,各参与者可以利用这些信息动态地改变传输速率,甚至改变有效载荷类型。RTP和RTCP配合使用,它们能以有效的反馈和最小的开销使传输效率最佳化,因而特别适合传送网上的实时数据。
根据此前的协议结构图,可以看到RTCP同样使用UDP进行传输。但RTCP封装的仅仅是一些控制信息,因而分组很短,所以可以将多个RTCP分组封装在一个UDP包中。RTCP有如下五种分组类型。
类型 | 缩写表示 | 用途 |
---|---|---|
200 | SR(Sender Report) | 发送端报告 |
201 | RR(Receiver Report) | 接收端报告 |
202 | SDES(Source Description Items) | 源点描述 |
203 | BYE | 结束传输 |
204 | APP | 特定应用 |
上述五种分组的封装大同小异,本文中以SR类型为例。
发送端报告分组SR(Sender Report)用来使发送端以多播方式向所有接收端报告发送情况。
SR分组的主要内容有:相应的RTP流的SSRC,RTP流中最新产生的RTP分组的时间戳和NTP,RTP流包含的分组数,RTP流包含的字节数。其封装如下图所示:
- 版本(V):同RTP包头域。
- 填充(P):同RTP包头域。
- 接收报告计数器(RC):5比特,该SR包中的接收报告块的数目,可以为零。
- 包类型(PT):8比特,SR包是200。
- 长度域(Length):16比特,其中存放的是该SR包以32比特为单位的总长度减一。
- 同步源(SSRC):SR包发送者的同步源标识符。与对应RTP包中的SSRC一样。
- NTP Timestamp(Network time protocol)SR包发送时的绝对时间值。NTP的作用是同步不同的RTP媒体流。
- RTP Timestamp:与NTP时间戳对应,与RTP数据包中的RTP时间戳具有相同的单位和随机初始值。
- Sender’s packet count:从开始发送包到产生这个SR包这段时间里,发送者发送的RTP数据包的总数. SSRC改变时,这个域清零。
- Sender`s octet count:从开始发送包到产生这个SR包这段时间里,发送者发送的净荷数据的总字节数(不包括头部和填充)。发送者改变其SSRC时,这个域要清零。
- 同步源n的SSRC标识符:该报告块中包含的是从该源接收到的包的统计信息。
- 丢失率(Fraction Lost):表明从上一个SR或RR包发出以来从同步源n(SSRC_n)来的RTP数据包的丢失率。
- 累计的包丢失数目:从开始接收到SSRC_n的包到发送SR,从SSRC_n传过来的RTP数据包的丢失总数。
- 收到的扩展最大序列号:从SSRC_n收到的RTP数据包中最大的序列号,
- 接收抖动(Interarrival jitter):RTP数据包接受时间的统计方差估计
- 上次SR时间戳(Last SR,LSR):取最近从SSRC_n收到的SR包中的NTP时间戳的中间32比特。如果目前还没收到SR包,则该域清零。
- 上次SR以来的延时(Delay since last SR,DLSR):上次从SSRC_n收到SR包到发送本报告的延时。
使用RTP的会话过程并不复杂
当应用程序建立一个RTP会话时,应用程序将确定一对目的传输地址。目的传输地址由一个网络地址和一对端口组成,有两个端口:一个给RTP包,一个给RTCP包,使得RTP/RTCP数据能够正确发送。RTP数据发向偶数的UDP端口,而对应的控制信号RTCP数据发向相邻的奇数UDP端口(偶数的UDP端口+1),这样就构成一个UDP端口对。 RTP的发送过程如下,接收过程则相反。
1) RTP协议从上层接收流媒体信息码流(如H.263),封装成RTP数据包;RTCP从上层接收控制信息,封装成RTCP控制包。
2) RTP将RTP 数据包发往UDP端口对中偶数端口;RTCP将RTCP控制包发往UDP端口对中的奇数端口。
相关协议
-
实时流协议RTSP(Real-Time Streaming Protocol)
是IETF提出的协议,对应的RFC文档为RFC2362。从总结构图中可以看出,RTSP是一个应用层协议(TCP/IP网络体系中)。它以C/S(Client/Server)模式工作,它是一个多媒体播放控制协议,主要用来使用户在播放流媒体时可以像操作本地的影碟机一样进行控制,即可以对流媒体进行暂停/继续、后退和前进等控制。
-
资源预定协议RSVP(Resource Reservation Protocol)
是IETF提出的协议,对应的RFC文档为RFC2208。从总结构图中可以看出,RSVP工作在IP层之上传输层之下,是一个网络控制协议。RSVP通过在路由器上预留一定的带宽,能在一定程度上为流媒体的传输提供服务质量。在某些试验性的系统如网络视频会议工具vic中就集成了RSVP。
常见问题
怎样重组乱序的数据包
可以根据RTP包的序列号来排序。
怎样获得数据包的时序
可以根据RTP包的时间戳来获得数据包的时序。
声音和图像怎么同步
根据声音流和图像流的相对时间(即RTP包的时间戳),以及它们的绝对时间(即对应的RTCP包中的RTCP),可以实现声音和图像的同步。
接收缓冲和播放缓冲的作用
接收缓冲用来排序乱序了的数据包;播放缓冲用来消除播放的抖动,实现等时播放。
参考文章
RTP协议全解析(H264码流和PS流)_对牛乱弹琴的博客-CSDN博客_rtp协议详解
RTP协议分析_彭令鹏的博客-CSDN博客_rtp协议
RTSP协议
RTSP概述
RTSP(Real-Time Streaming Protocol)
,实时流媒体协议,标准编号RFC2326,RTSP 2.0 于2016年发布为RFC 7826。
此前,IETF多媒体传输小组,发布了旧版的RTP,经过更新后,标准编号为RFC3550的RTP标准正式出炉,其是一个基于TCP/UDP的传输层协议,为流媒体数据提供具有实时特征的端对端传送服务,应用于主波或者单播网络环境下的交互式音视频或者流媒体系统中。
但是,RTP本身只用于传输数据,而不提供任何确保及时交付或是服务质量的保证,因此在RFC3550修订版中,定义了RTCP(实时传输控制协议),以便调整、控制传输过程。
其在流媒体传输框架中的层次如下图所示,可见,是与HTTP一样,被划分到应用层协议中,但是,与HTTP不同的是,RTSP是一种有状态的协议,其命令是需要按照顺序来发送,而且客户端和服务器双方都可以发送request请求。
再多提一句,HTTP的默认端口为80,RTSP则为554.
RTSP是一个可扩展的框架,其功能如下:
- 支持控制、按需交付实时音视频数据
- 控制多个数据传输会话
- 提供选择传输方式的能力,可以选择UDP、组播UDP、TCP
- 提供了一种基于RTP的选择传输机制
RTSP包含了两种,一是Normal RSTP,数据通过RTP传输,是较为常见的种类;另一种则是Real RTSP,数据通过RDT传输,专属于RealNetworks公司的RTSP服务器软件。
RTSP只负责传输媒体控制信息,并不负责数据传输,而是使用RTP和RTCP完成数据传输与数据监控。下图可以很好的表示RTSP在协议层中的位置以及简要的工作机制。
RTSP传输的一般是TS、MP4格式的流,其传输一般需要2~3个通道,命令和数据通道分离。使用RTSP协议传输流媒体数据需要有专门的媒体播放器和媒体服务器,也就是需要支持RTSP协议的客户端和服务器。可以看到,RSTP本身是基于TCP进行控制信息的传输的。其会话交互流程如下图所示:
如果客户端要播放RTSP媒体流,就需要知道媒体源的URL,RTSP的URL格式一般如下:
rtsp://host[:port]/[abs_path]/content_name
-
host: 有效的域名或IP地址;
-
port: 端口号,缺省为554,若为缺省可不填写,否则必须写明。
例如,一个完整的RTSP URL可写为:
rtsp://192.168.1.67:554/test.xxx
又如目前市面上常用的海康网络摄像头的RTSP地址格式为:
rtsp://[username]:[password]@[ip]:[port]/[codec]/[channel]/[subtype]/av_stream
示例:
rtsp://admin:12345@192.168.1.67:554/h264/ch1/main/av_stream
rtsp://admin:12345@192.168.1.67/mpeg4/ch1/sub/av_stream
RTSP消息交互过程详解
RTSP是一种基于文本的协议,用CRLF(回车换行)作为每一行的结束符,其好处是,在使用过程中可以方便地增加自定义参数,也方便抓包分析。从消息传送方向上来分,RTSP的报文有两类:请求报文和响应报文。请求报文是指从客户端向服务器发送的请求(也有少量从服务器向客户端发送的请求),响应报文是指从服务器到客户端的回应。
RTSP请求报文的常用方法与作用:
一次基本的RTSP交互过程如下(C表示客户端,S表示服务端):
首先客户端会通过发送OPTION
消息至服务器端,请求服务器可以使用的方法,服务器给予回应。对应上图中的①。
客户端连接到流媒体服务器并发送一个RTSP描述请求(DESCRIBE request)
,服务器通过一个SDP(Session Description Protocol)
描述来进行反馈(DESCRIBE response)
,反馈信息包括流数量、媒体类型等信息。对应上图中的②。
客户端分析该SDP描述,并为会话中的每一个流发送一个RTSP连接建立请求(SETUP request)
,该命令会告诉服务器用于接收媒体数据的端口,服务器响应该请求(SETUP response)
并建立连接之后,就开始传送媒体流(RTP包)到客户端。对应上图中的③。
在播放过程中客户端还可以向服务器发送请求来控制快进、快退和暂停等。最后,客户端可发送一个终止请求(TEARDOWN request)
来结束流媒体会话。参见下图,为RTSP客户端的状态机:
- 初始态(Init): SETUP请求已经发出,等待回复,尚未创建会话
- 就绪态(Ready): 收到SETUP回复,或在播放态收到pause回复,会话已创建好,可以进行数据传输。
- 播放态(Playing):收到PLAY回复,媒体数据开始传输,客户端播放媒体。
- 记录态(Recording): 收到RECORD回复,客户端开始录制数据。
值得一提的是,服务器端是通过调用多次accept()
函数来完成各状态的接收,这么做也正常,如果对方在还没有正常传输数据的时候就断开连接,服务器端也因为长久没有收到SYN就断开连接,也是可以理解的。
RTSP报文重要字段介绍
在正式开始介绍报文信息详解之前,先对信息中比较重要的字段进行介绍。客户端如果一段时间内(默认是60s)没有任何响应,那么rtsp服务器就会关闭该会话,所以客户端需要发送心跳包给服务器。
- RTSP层面上,定期向server发无效的控制信息(要带有session id的cmd,比如,空消息体的get_parameter命令,)
- RTP层面上,定期向server发送rtp包,包内容随意。
Accept:
用于指定客户端可以接受的媒体描述信息类型。比如:
Accept: application/rtsl, application/sdp;level=2
Bandwidth:
用于描述客户端可用的带宽值。
CSeq:
指定了RTSP请求回应对的序列号,在每个请求或回应中都必须包括这个头字段。对每个包含一个给定序列号的请求消息,都会有一个相同序列号的回应消息。
Rang:
用于指定一个时间范围,可以使用SMPTE、NTP或clock时间单元。
Session:
Session头字段标识了一个RTSP会话。Session ID 是由服务器在SETUP的回应中选择的,客户端一当得到Session ID后,在以后的对Session 的操作请求消息中都要包含Session ID.
Transport:
Transport头字段包含客户端可以接受的转输选项列表,包括传输协议,地址端口,TTL等。服务器端也通过这个头字段返回实际选择的具体选项。如:
Transport: RTP/AVP;multicast;ttl=127;mode="PLAY",
RTP/AVP;unicast;client_port=3456-3457;mode="PLAY"
unicast
表示单播
RTSP报文信息详解
首先,我们介绍一下RTSP通信过程中,请求与回应的模板如下所示:
RTSP请求:
[方法] [URI] [RTSP版本]<CR LF>
[消息头]<CR LF><CR LF>
[消息体]<CR LF>
RTSP回应:
[RTSP版本] [状态码] [状态码解释]<CR LF>
[消息头]<CR LF><CR LF>
[消息体]<CR LF>
下面我们通过具体的消息实例,来进一步了解一下RTSP的工作过程:
OPTIONS
OPTIONS请求是客户端向服务器询问可用的方法,请求和回复实例如下:
C->S: OPTIONS rtsp://example.com/media.mp4 RTSP/1.0
CSeq: 1
Require: implicit-play
Proxy-Require: gzipped-messages
S->C: RTSP/1.0 200 OK
CSeq: 1
Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE
DESCRIBE
客户端向服务器请求媒体资源描述,Accept字段中指定客户端可以接受的媒体信息类型。服务器端通过SDP(Session Description Protocol)格式回应客户端的请求(后文我们也会介绍一下SDP协议)。资源描述中会列出所请求媒体的媒体流及其相关信息,典型情况下,音频和视频分别作为一个媒体流传输。实例如下:
C->S: DESCRIBE rtsp://example.com/media.mp4 RTSP/1.0
CSeq: 2
Accept:application/sdp, application/rtsl,application/mheg
S->C: RTSP/1.0 200 OK
CSeq: 2
Content-Base: rtsp://example.com/media.mp4
Content-Type: application/sdp
Content-Length: 460
m=video 0 RTP/AVP 96
a=control:streamid=0
a=range:npt=0-7.741000
a=length:npt=7.741000
a=rtpmap:96 MP4V-ES/5544
a=mimetype:string;"video/MP4V-ES"
a=AvgBitRate:integer;304018
a=StreamName:string;"hinted video track"
m=audio 0 RTP/AVP 97
a=control:streamid=1
a=range:npt=0-7.712000
a=length:npt=7.712000
a=rtpmap:97 mpeg4-generic/32000/2
a=mimetype:string;"audio/mpeg4-generic"
a=AvgBitRate:integer;65790
a=StreamName:string;"hinted audio track"
媒体初始化是任何基于RTSP系统的必要条件,RTSP规范并没有规定它必须通过DESCRIBE方法完成。RTSP客户端可以通过以下方法来接收媒体描述信息:
a) 通过DESCRIBE方法;
b) 其它一些协议(HTTP,email附件,等);
c) 通过命令行或标准输入设备
SETUP
SETUP请求确定了具体的媒体流如何传输,该请求必须在PLAY请求之前发送。SETUP请求包含媒体流的URL和客户端用于接收RTP数据(audio or video)的端口以及接收RTCP数据(meta information)的端口。服务器端的回复通常包含客户端请求参数的确认,并会补充缺失的部分,比如服务器选择的发送端口。每一个媒体流在发送PLAY请求之前,都要首先通过SETUP请求来进行相应的配置。
C->S: SETUP rtsp://example.com/media.mp4/streamid=0 RTSP/1.0
CSeq: 3
Transport: RTP/AVP;unicast;client_port=8000-8001
S->C: RTSP/1.0 200 OK
CSeq: 3
Transport: RTP/AVP;unicast;client_port=8000-8001;server_port=9000-9001;ssrc=1234ABCD
Session: 12345678
重要的是,服务器端还会将该传输会话的ID带给客户端,也就是Session ID
,之后客户端的请求都需要带这个唯一标识码。另外,如果发现setup的client端口已经被强占的话,可以不用关闭会话,只需要再次调用setup来重新配置端口,不过要带上当前的Session ID。
PLAY
客户端通过PLAY请求来播放一个或全部媒体流,PLAY请求可以发送一次或多次,发送一次时,URL为包含所有媒体流的地址,发送多次时,每一次请求携带的URL只包含一个相应的媒体流。
C->S: PLAY rtsp://example.com/media.mp4 RTSP/1.0
CSeq: 4
Range: npt=5-20 //(表示播放第5-20秒的内容
Session: 12345678
S->C: RTSP/1.0 200 OK
CSeq: 4
Session: 12345678
RTP-Info: url=rtsp://example.com/media.mp4/streamid=0;seq=9810092;rtptime=3450012
Range
可能包含一个时间参数,单位是秒,相当于seek操作,该参数以UTC格式指定了播放开始的时间。如果在这个指定时间后收到消息,那么播放立即开始。时间参数可能用来帮助同步从不同数据源获取的数据流。不含Range的PLAY请求也是合法的。它从媒体流开头开始播放,直到媒体流被暂停。如果媒体流通过PAUSE暂停,媒体流传输将在暂停点(the pause point)重新开始。
如果媒体流正在播放,那么这样一个PLAY请求将不起更多的作用,只是客户端可以用此来测试服务器是否存活。
如果要实现快进/快退功能,也是通过PLAY命令向服务器请求,快进 / 快退倍速通过SCALE
字段提供,如下:
C->S PLAY rtsp://video.foocorp.com:554/streams/example.rm RTSP/1.0
CSeq: 4
Session: 12345678
SCALE: 4 // 设置快进/快退倍速
User-Agent: VLC media player (LIVE555 Streaming Media v2005.11.10)
S->C RTSP/1.0 200 OK
CSeq: 4
Session: 12345678
SCALE: 4
PAUSE
PAUSE请求会暂停一个或所有媒体流,后续可通过PLAY请求恢复播放。PAUSE请求中携带所请求媒体流的URL。若参数range存在,则指明在何处暂停,称这个时间为暂停点,若该参数不存在,则暂停立即生效,且暂停时长不确定。
如果请求URL中指定了具体的媒体流,那么只有该媒体流的播放和Record被暂停(halt)。比如,指定暂停音频,播放将会无声。如果请求URL指定了一组流,那么在该组中的所有流的传输将被暂停。
C->S: PAUSE rtsp://example.com/media.mp4 RTSP/1.0
CSeq: 5
Session: 12345678
S->C: RTSP/1.0 200 OK
CSeq: 5
Session: 12345678
TEARDOWN
结束会话请求,该请求会停止所有媒体流,并释放服务器上的相关会话数据。
C->S: TEARDOWN rtsp://example.com/media.mp4 RTSP/1.0
CSeq: 8
Session: 12345678
S->C: RTSP/1.0 200 OK
CSeq: 8
这里的Cseq = 8,相当于中间还有两次PLAY或是其他操作。
GET_PARAMETER
检索指定URI数据中的参数值。不携带消息体的GET_PARAMETER可用来测试服务器端或客户端是否可通(类似ping的功能)。
S->C: GET_PARAMETER rtsp://example.com/media.mp4 RTSP/1.0
CSeq: 9
Content-Type: text/parameters
Session: 12345678
Content-Length: 15
packets_received
jitter
C->S: RTSP/1.0 200 OK
CSeq: 9
Content-Length: 46
Content-Type: text/parameters
packets_received: 10
jitter: 0.3838
SET_PARAMETER
用于设置指定媒体流的参数。
每条请求都应当只包含一个参数以允许客户端在设置失败的时候确认失败原因。如果请求中包含多个参数,服务器必须在所有参数设置成功的情况下生效,否则返回参数错误的key
(如下例子)。
服务器应当允许同一参数多次设置同一值,但是可以拒绝设置为不同的值,由服务器根据时机情况而定。需要注意的是,对于媒体流参数(如端口等)不可通过该方法设置,必须通过SETUP请求来设置。
以下为设置失败的例子,服务器返回"Invalid Parameter"
C->S: SET_PARAMETER rtsp://example.com/media.mp4 RTSP/1.0
CSeq: 10
Content-length: 20
Content-type: text/parameters
barparam: barstuff // 格式<key>:<value>
S->C: RTSP/1.0 451 Invalid Parameter
CSeq: 10
Content-length: 10
Content-type: text/parameters
barparam // 返回参数错误的键
REDIRECT
重定向请求,用于服务器通知客户端新的服务地址,响应报文中必须包含Location头,以指明新的服务器URL,客户端需要向这个新地址重新发起请求。重定向请求中可能包含Range参数,指明重定向生效的时间。
客户端若需向新服务地址发起请求,必须先TEARDOWN
当前会话,再向指定的新主机SETUP
一个新的会话。
S->C: REDIRECT rtsp://example.com/media.mp4 RTSP/1.0
CSeq: 11
Location: rtsp://bigserver.com:8001
Range: clock=19960213T143205Z-
ANNOUNCE
ANNOUNCE请求有两个用途:
(1)C->S:客户端向服务器端发布URL指定的媒体信息描述,如EOF
终止符;
(2) S->C:实时更新对话描述。若媒体表示中新增了一个媒体流,例如在直播过程中,则整个媒体表示的description都要被重新发送,而不是只发送新增部分。
C->S: ANNOUNCE rtsp://example.com/media.mp4 RTSP/1.0
CSeq: 7
Date: 23 Jan 1997 15:35:06 GMT
Session: 12345678
Content-Type: application/sdp
Content-Length: 332
v=0
o=mhandley 2890844526 2890845468 IN IP4 126.16.64.4
s=SDP Seminar
i=A Seminar on the session description protocol
u=http://www.cs.ucl.ac.uk/staff/M.Handley/sdp.03.ps
e=mjh@isi.edu (Mark Handley)
c=IN IP4 224.2.17.12/127
t=2873397496 2873404696
a=recvonly
m=audio 3456 RTP/AVP 0
m=video 2232 RTP/AVP 31
S->C: RTSP/1.0 200 OK
CSeq: 7
RECORD
请求录制指定范围的媒体数据,请求中可指定录制的起止时间戳,UTC格式,包含开始录制点和结束录制点;若未指定时间范围,则使用presentation description
中的开始和结束时间,这种情况下,如果会话已开始,则立即启动录制操作。
服务器决定将录制数据保存在请求URI还是其他URI。
如果是保存在其他URI,则server返回201
并包含描述请求状态和录制资源位置信息。
C->S: RECORD rtsp://example.com/media.mp4 RTSP/1.0
CSeq: 6
Session: 12345678
S->C: RTSP/1.0 200 OK
CSeq: 6
Session: 12345678
以上就是RTSP中常用的命令及其实例介绍。
RTP包传输方式
RTP包的传输方式在SETUP中指定,分为两种:
-
Over UDP传输,默认传输方式,SETUP带的参数为
RTP/AVP/UDP
或者RTP/AVP
,如下:C->S: SETUP rtsp://example.com/foo/bar/baz.rm RTSP/1.0 CSeq: 302 Transport: RTP/AVP;unicast;client_port=4588-4589 S->C: RTSP/1.0 200 OK CSeq: 302 Date: 23 Jan 1997 15:35:06 GMT Session: 47112344 Transport: RTP/AVP;unicast; client_port=4588-4589;server_port=6256-6257
可以看到通过UDP传输,RTP接收端口是4588,对应发送端口为6256;RTCP交互端口为4589,对应端口为6257.
这样的话,对于ts stream,需要创建3个socket(rtsp, ts rtp, ts rtcp)
;
而对于fMP4,需要创建5个socket(rtsp, audio rtp, audio rtcp, video rtp, video rtcp)
。
连接示意图: -
Over TCP传输,SETUP带的参数为
RTP/AVP/TCP
,如下:C->S: SETUP rtsp://foo.com/bar.file RTSP/1.0 CSeq: 2 Transport: RTP/AVP/TCP;interleaved=0-1 S->C: RTSP/1.0 200 OK CSeq: 2 Date: 05 Jun 1997 18:57:18 GMT Transport: RTP/AVP/TCP;interleaved=0-1
通过TCP传输的话,RTSP命令、RTP包和RTCP包是在同一个TCP连接上传输的,示意图如下:
为了区分RTP包和RTCP包,新增了一层RTSP Interleaved Frame:
RTSP传输报文示例
下文过程是通过PC对海康摄像头视频流的拉取和播放,并使用Wireshark抓取的信息流,可以更加直观的了解到整个协议的工作流程:
OPTIONS rtsp://10.3.8.202:554 RTSP/1.0
CSeq: 2
User-Agent: LibVLC/2.2.8 (LIVE555 Streaming Media v2016.02.22)
RTSP/1.0 200 OK
CSeq: 2
Public: OPTIONS, DESCRIBE, PLAY, PAUSE, SETUP, TEARDOWN, SET_PARAMETER, GET_PARAMETER
Date: Mon, Jan 29 2018 16:56:47 GMT
DESCRIBE rtsp://10.3.8.202:554 RTSP/1.0
CSeq: 3
User-Agent: LibVLC/2.2.8 (LIVE555 Streaming Media v2016.02.22)
Accept: application/sdp
RTSP/1.0 401 Unauthorized
CSeq: 3
WWW-Authenticate: Digest realm="IP Camera(10789)", nonce="6b9a455aec675b8db81a9ceb802e4eb8", stale="FALSE"
Date: Mon, Jan 29 2018 16:56:47 GMT
DESCRIBE rtsp://10.3.8.202:554 RTSP/1.0
CSeq: 4
Authorization: Digest username="admin", realm="IP Camera(10789)", nonce="6b9a455aec675b8db81a9ceb802e4eb8", uri="rtsp://10.3.8.202:554", response="3fc4b15d7a923fc36f32897e3cee69aa"
User-Agent: LibVLC/2.2.8 (LIVE555 Streaming Media v2016.02.22)
Accept: application/sdp
RTSP/1.0 200 OK
CSeq: 4
Content-Type: application/sdp
Content-Base: rtsp://10.3.8.202:554/
Content-Length: 551
v=0
o=- 1517245007527432 1517245007527432 IN IP4 10.3.8.202
s=Media Presentation
e=NONE
b=AS:5050
t=0 0
a=control:rtsp://10.3.8.202:554/
m=video 0 RTP/AVP 96
c=IN IP4 0.0.0.0
b=AS:5000
a=recvonly
a=x-dimensions:2048,1536
a=control:rtsp://10.3.8.202:554/trackID=1
a=rtpmap:96 H264/90000
a=fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=Z00AMp2oCAAwabgICAoAAAMAAgAAAwBlCA==,aO48gA==
a=Media_header:MEDIAINFO=494D4B48010200000400000100000000000000000000000000000000000000000000000000000000;
a=appversion:1.0
SETUP rtsp://10.3.8.202:554/trackID=1 RTSP/1.0
CSeq: 5
Authorization: Digest username="admin", realm="IP Camera(10789)", nonce="6b9a455aec675b8db81a9ceb802e4eb8", uri="rtsp://10.3.8.202:554/", response="ddfbf3e268ae954979407369a104a620"
User-Agent: LibVLC/2.2.8 (LIVE555 Streaming Media v2016.02.22)
Transport: RTP/AVP;unicast;client_port=57844-57845
RTSP/1.0 200 OK
CSeq: 5
Session: 1273222592;timeout=60
Transport: RTP/AVP;unicast;client_port=57844-57845;server_port=8218-8219;ssrc=5181c73a;mode="play"
Date: Mon, Jan 29 2018 16:56:47 GMT
PLAY rtsp://10.3.8.202:554/ RTSP/1.0
CSeq: 6
Authorization: Digest username="admin", realm="IP Camera(10789)", nonce="6b9a455aec675b8db81a9ceb802e4eb8", uri="rtsp://10.3.8.202:554/", response="b5abf0b230de4b49d6c6d42569f88e91"
User-Agent: LibVLC/2.2.8 (LIVE555 Streaming Media v2016.02.22)
Session: 1273222592
Range: npt=0.000-
RTSP/1.0 200 OK
CSeq: 6
Session: 1273222592
RTP-Info: url=rtsp://10.3.8.202:554/trackID=1;seq=65373;rtptime=3566398668
Date: Mon, Jan 29 2018 16:56:47 GMT
GET_PARAMETER rtsp://10.3.8.202:554/ RTSP/1.0
CSeq: 7
Authorization: Digest username="admin", realm="IP Camera(10789)", nonce="6b9a455aec675b8db81a9ceb802e4eb8", uri="rtsp://10.3.8.202:554/", response="bb2309dcd083b25991c13e165673687b"
User-Agent: LibVLC/2.2.8 (LIVE555 Streaming Media v2016.02.22)
Session: 1273222592
RTSP/1.0 200 OK
CSeq: 7
Date: Mon, Jan 29 2018 16:56:47 GMT
TEARDOWN rtsp://10.3.8.202:554/ RTSP/1.0
CSeq: 8
Authorization: Digest username="admin", realm="IP Camera(10789)", nonce="6b9a455aec675b8db81a9ceb802e4eb8", uri="rtsp://10.3.8.202:554/", response="e08a15c27d3daac14fd4b4bcab424a5e"
User-Agent: LibVLC/2.2.8 (LIVE555 Streaming Media v2016.02.22)
Session: 1273222592
RTSP/1.0 200 OK
CSeq: 8
Session: 1273222592
Date: Mon, Jan 29 2018 16:57:03 GMT
RTSP扩展
点播的case,如何判断End of stream?
1> 服务器主动发"ANNOUNCE" cmd告诉player码流传输结束。
S->C: ANNOUNCE rtsp://172.16.12.6:5554/… RTSP/1.0
CSeq: 4
Session: 370384394
x-notice: 2101 "End-of-Stream Reached" event-data=20210706T025811Z
2> 可以从DESCRIBE的SDP info中拿到duration,那么播放时长(playtime + 1)达到duration,那就认为数据传输结束。
3> 最新的规范(RTSP 2.0)中,服务器发送"PLAY_NOTIFY"cmd给客户端,通知eos。
S->C: PLAY_NOTIFY rtsp://example.com/… RTSP/2.0
Cseq:854
Notify-Reason: end-of-stream
4>无法拿到duration,那么设置100秒超时,如果100秒内server没有发送播放数据给player,那就认为数据传输结束。
RTSP重定向的情况:
DESCRIBE请求返回303说明要进行重定向,响应头中会说明重定向的url。
客户端需要对重定向url重新进行OPTION和DESCRIBE请求。
C->S:
DESCRIBE rtsp://172.16.74.210:559/2305_3a__3a_MOV00000002305106151_3a__3a_2305106151_f.mpg RTSP/1.0
CSeq: 2
User-Agent: MODPlayer
S->C:
RTSP/1.0 303 See Other
Server: Orbit2x
CSeq: 2
Location: rtsp://172.17.178.142:554/2305_3a__3a_MOV00000002305106151_3a__3a_2305106151_f.mpg
C->S:
OPTIONS rtsp://172.17.178.142:554/2305_3a__3a_MOV00000002305106151_3a__3a_2305106151_f.mpg RTSP/1.0
CSeq: 1
User-Agent: MODPlayer
S->C:
RTSP/1.0 200 OK
Server: Orbit2x
CSeq: 1
Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER
C->S:
DESCRIBE rtsp://172.17.178.142:554/2305_3a__3a_MOV00000002305106151_3a__3a_2305106151_f.mpg RTSP/1.0
CSeq: 2
User-Agent: MODPlayer
S->C:
RTSP/1.0 200 OK
Server: Orbit2x
CSeq: 2
Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER
Content-Type: application/sdp
Content-Length: 178
媒体数据直接基于UDP传输:
transport是RAW/RAW/UDP,传输的数据没有封包为RTP包,直接以UDP包的形式传输。
SETUP rtsp://172.17.245.46:554/378289119.ts RTSP/1.0
Transport: RAW/RAW/UDP;unicast;destination=10.134.37.122;client_port=15500
User-Agent: MODPlayer
CSeq: 3
RTSP/1.0 200 OK
Server: Orbit2x
CSeq: 3
Session: 26931165;timeout=60
Transport: RAW/RAW/UDP;unicast;destination=10.134.37.122;client_port=15500;source=172.17.245.46;server_port=10000
-----------------------------------------------------------------------------------
udp的url是:udp://172.17.245.46:10000 --> 注意这是一个单播地址,不是udp组播地址(224. ~ 239.)
SDP:
SDP(Session Description Protocol)是一个用来描述多媒体会话的应用层控制协议,是一个基于文本的协议。
用来会话建立过程中的媒体类型,以及协商编码方案等。
SDP由许多文本行组成,文本行的格式为<类型>=<值>,<类型>是一个字母,<值>是结构化的文本串,其格式视<类型>而定。
SDP文本行例子:
v=<version> (协议版本)
o=<username> <session id> <version> <network type> <address type> <address> (所有者/创建者和会话标识符)
s=<session name> (会话名称)
i=<session description> (会话信息)
u=<URI> (URI 描述)
e=<email address> (Email 地址)
p=<phone number> (电话号码)
c=<network type> <address type> <connection address> (连接信息)
b=<modifier>:<bandwidth-value> (带宽信息)
t=<start time> <stop time> (会话活动时间)
r=<repeat interval> <active duration> <list of offsets from start-time>(0或多次重复次数)
z=<adjustment time> <offset> <adjustment time> <offset> ....
k=<method>
k=<method>:<encryption key> (加密密钥)
a=<attribute> (0 个或多个会话属性行)
a=<attribute>:<value>
m=<media> <port> <transport> <fmt list> (媒体名称和传输地址)
RTSP2.0
直接搬运RTSP 2.0_aflyeaglenku的博客-CSDN博客的内容
2016年12月,RTSP 2.0协议正式发布,rfc索引是7826
新标准还是有不少修改的,除了完善一些原协议的中的定义,主要修改就是对接口method进行了修改,比如删除了RECORD
和ANNOUNCE
方法,新增了PLAY_NOTIFY
方法。
删除了RECORD
,这表示你不能再通过这个接口来控制服务器进行数据的录制了,可以选择在PLAY
方法中,添加一些参数,来实现服务器对直播数据进行录制,还可以分隔录制。
删除了ANNOUNCE
,这意味着不能像RTMP一样,客户端通过向服务器推送数据,来实现本机数据对外直播了,这可能需要其他的推送途径来进行替代了。
至于PLAY_NOTIFY
,它替代原来Server向Client端发送ANNOUNCE方法,所实现的功能,也就是告诉客户端,需要根据新参数来调整直播播放状态。
删除通过UDP传输RTSP消息的形式(RTSP消息可以基于UDP传输吗?);
删除通过发PLAY消息来keep alive
的方式,改用SET_PARAMETER来做;
RTSP Server也可向Client发TEARDOWN消息;
支持IPV6;
RTSP请求,支持pipelining
的形式,也就是聚合Request。比如可以不等服务器返回,把SETUP和PLAY一起发送,这样可以提高至少一个RTT的启动时间。当然需要在消息里加上相关字段。
重写了状态机,完善了服务器对客户端来说在各个状态之间的转换和行为;
RTSP消息内支持URI了;
扩展了REDIRECT
方法,等等。
参考文章
实时流协议 - 维基百科,自由的百科全书 (wikipedia.org)wiki yyds
网络流媒体协议之——RTSP协议_牧羊女说的博客-CSDN博客_rtsp用了哪种运输层协议
RTSP协议学习笔记_雷霄骅的博客-CSDN博客_range: npt
流媒体网络协议 – RTSP_Ritchie_Lin的博客-CSDN博客_rtsp socket
网络流媒体协议之——RTSP协议 - yooooooo - 博客园 (cnblogs.com)
RTSP 2.0_aflyeaglenku的博客-CSDN博客
RTMP协议
概念与摘要
RTMP协议从属于应用层,被设计用来在适合的传输协议(如TCP)上复用和打包多媒体传输流(如音频、视频和互动内容)。RTMP提供了一套全双工的可靠的多路复用消息服务,类似于TCP协议[RFC0793],用来在一对结点之间并行传输带时间戳的音频流,视频流,数据流。通常情况下,不同类型的消息会被分配不同的优先级,当网络传输能力受限时,优先级用来控制消息在网络底层的排队顺序。
RTMP块流
实时消息传递协议块流(RTMP块流)。它作为一款高级多媒体流协议提供了流的多路复用和打包服务。RTMP块流被设计用来传输实时消息协议,它可以使用任何协议来发送消息流。每个消息都包含时间戳和有效类型标识。RTMP块流和RTMP适用于各种视听传播的应用程序,包括一对一的,和一对多的视频直播、点播服务、互动会议应用程序。
当使用一个可靠的传输协议如TCP[RFC0793]时,RTMP块流提供了一种可以在多个流中,基于时间戳的端到端交付所有消息的方法。RTMP块流不提供任何优先级或类似形式的控制,但可以使用更高级别的协议来提供这样的优先级。例如,一个视频服务器可以根据发送的时间或确认每个消息的时间,来决定为一个网络差的用户丢弃视频信息,以确保音频信息的及时接收。
RTMP块流不仅包含了自己的协议控制信息,同时也提供了一个更高级别的协议机制,用来嵌入用户控制信息。
消息格式
消息格式可以被分割成多个块,用来在更高的协议中支持多路复用。在创建块消息格式时,应该包含以下字段:
-
时间戳
消息的时间戳。这个字段占用4字节。 -
长度
消息的有效长度。如果消息头不能被忽略,它应该包括长度。这个字段在块头中占用3字节。 -
类型ID
各种类型的协议控制消息的ID。这些消息使用RTMP块流协议和更高级别的协议来传输信息。所有其他类型的ID可以用在高级协议,这对于RTMP块流来说,是不透明的。事实上,RTMP块流中没有要求使用这些值作为类型;所有(无协议的)消息可能是相同的类型,或者应用程序使用这个字段来区分多个连接,而不是类型。这个字段在块头中占用1字节。 -
消息流ID
消息流ID可以是任意值。当同一个块流被复用到不同的消息流中时,可以通过消息流ID来区分它们。另外,对于RTMP块流而言,这是一个不透明值。该字段占用4字节,使用小端序。
握手
RTMP连接从握手开始。它包含三个固定大小的块,不像其他的协议,是由头部大小可变的块组成的。
客户端(初始化连接的一端)和服务端发送同样的三个块。为了方便描述,客户端发送的三个块命名为C0,C1,C2;服务端发送的三个块命名为S0,S1,S2。
-
握手序列
客户端通过发送C0和C1消息来启动握手过程。客户端必须接收到S1消息,然后发送C2消息。客户端必须接收到S2消息,然后发送其他数据。
服务端必须接收到C0或者C1消息,然后发送S0和S1消息。服务端必须接收到C1消息,然后发送S2消息。服务端必须接收到C2消息,然后发送其他数据。
-
C0和S0格式
C0和S0包由一个字节组成,下面是C0/S0包内的字段:
0 1 2 3 4 5 6 7 +-+-+-+-+-+-+-+-+ | version | +-+-+-+-+-+-+-+-+ C0 and S0 bits
-
版本(8比特)
在C0包内,这个字段代表客户端请求的RTMP版本号。在S0包内,这个字段代表服务端选择的RTMP版本号。此文档使用的版本是3。版本0-2用在早期的产品中,现在已经被弃用;版本4-31被预留用于后续产品;版本32-255(为了区分RTMP协议和文本协议,文本协议通常以可打印字符开始)不允许使用。如果服务器无法识别客户端的版本号,应该回复版本3。客户端可以选择降低到版本3,或者中止握手过程。
-
C1和S1格式
C1和S1包长度为1536字节,包含以下字段:
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | time (4 bytes) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | zero (4 bytes) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | random bytes | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | random bytes | | (cont) | | .... | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ C1 and S1 bits
-
时间(4字节)
本字段包含一个时间戳,客户端应该使用此字段来标识所有流块的时刻。时间戳取值可以为零或其他任意值。为了同步多个块流,客户端可能希望多个块流使用相同的时间戳。
-
Zero(4字节)
本字段必须为零。
-
随机数据(1528字节)
本字段可以包含任意数据。由于握手的双方需要区分另一端,此字段填充的数据必须足够随机(以防止与其他握手端混淆)。不过没必要为此使用加密数据或动态数据。
-
C2和S2格式
C2和S2包长度为1536字节,作为C1和S1的回应,包含以下字段:
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | time (4 bytes) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | time2 (4 bytes) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | random echo | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | random echo | | (cont) | | .... | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ C2 and S2 bits
-
时间(4字节)
本字段必须包含对端发送的时间戳。
-
时间(4字节)
本字段必须包含时间戳,取值为接收对端发送过来的握手包的时刻。
-
随机数据(1528字节)
本字段必须包含对端发送过来的随机数据。握手的双方可以使用时间1和时间2字段来估算网络连接的带宽和/或延迟,但是不一定有用。
RTMP握手
握手过程示意图
+-------------+ +-------------+
| Client | TCP/IP Network | Server |
+-------------+ | +-------------+
| | |
Uninitialized | Uninitialized
| C0 | |
|------------------->| C0 |
| |-------------------->|
| C1 | |
|------------------->| S0 |
| |<--------------------|
| | S1 |
Version sent |<--------------------|
| S0 | |
|<-------------------| |
| S1 | |
|<-------------------| Version sent
| | C1 |
| |-------------------->|
| C2 | |
|------------------->| S2 |
| |<--------------------|
Ack sent | Ack Sent
| S2 | |
|<-------------------| |
| | C2 |
| |-------------------->|
Handshake Done | Handshake Done
| | |
Pictorial Representation of Handshake
握手示意图
下面是握手示意图中提到的状态:
-
未初始化
协议版本号在此阶段发送。客户端和服务器均处于未初始化状态。客户端发送携带协议版本号的C0包。如果服务器支持此版本,回复S0和S1包。如果服务器不支持此版本,使用适当的动作回复。在RTMP协议中,此动作是中止连接。
注: 在”C0和S0格式”章节中提及,如果服务器不支持客户端的版本号,可以选择降到版本3或中止。 -
发送版本
客户端和服务器双方在未初始化状态后,会进入发送版本状态。之后,客户端等待S1包,服务器等待C1包。待接收到数据包,客户端发送C2包,服务器发送S2包。然后,双方都进入答复状态。客户端等待C2的答复,服务器等待S2的答复。 -
握手完成
客户端和服务器交换消息。
SDP协议
这是一个在众多流媒体协议中会被使用到的协议,webrtc中也用到了该协议,所以单开一个篇幅来介绍。
SDP概述
SDP(SessionDescription Protocol )会话描述协议,用于描述多媒体会话,它为会话通知、会话初始和其它形式的多媒体会话初始等操作提供服务。
SDP的设计宗旨是通用性协议,所有它可以应用于很大范围的网络环境和应用程序,但 SDP 不支持会话内容或媒体编码的协商操作。
SDP信息包括:
- 会话名称和目标;
- 会话活动时间;
- 构成会话的媒体;
- 有关接收媒体的信息、地址等。
SDP格式
SDP 由一个会话级描述(session level description)和多个媒体级描述(media level description)组成。会话级描述的作用域是整个会话,在 SDP 中,从 v=
行开始到第一个 m=
行之前都是属于会话级描述的内容。媒体级描述对某个媒体流的内容进行描述,例如某个音频流或者某个视频流,从某个 “m=” 行开始到下个 “m=” 行之前是属于一个媒体级描述的内容。如下图所示:
SDP 信息是文本信息,UTF-8 编码采用 ISO 10646 字符设置。SDP 会话描述如下(标注*符号的表示可选字段):
常规描述
- v= (协议版本)
- o= (所有者/创建者和会话标识符)
- s= (会话名称)
- i=* (会话信息)
- u=* (URI 描述)
- e=* (Email 地址)
- p=* (电话号码)
- c=* (连接信息 ― 如果包含在所有媒体中,则不需要该字段)
- b=* (带宽信息)
(1个或更多)时间描述:
- z=* (时间区域调整)
- k=* (加密密钥)
- a=* (0个或多个会话属性线路)
- t= (会话活动时间)
- r=* (0或多次重复次数)
(0个或更多)媒体描述
- m= (媒体名称和传输地址)
- i=* (媒体标题)
- c=* (连接信息 — 如果包含在会话层则该字段可选)
- b=* (带宽信息)
- k=* (加密密钥)
- a=* (0个或多个会话属性线路)
SDP格式详解
1 - v: protocol version
v=0
v 的含义是 SDP 协议的版本号,目前 v 都是 0。
2 - o: owner/creator and session identifier
o=<username> <session-id> <session-version> <nettype> <addrtype> <unicast-address>
会话所有者有关的参数,包括用户名、session 信息,地址信息等。
- username: 会话发起者的名称。如果不提供则用"-"表示,用户名不能包含空格;
- session-id: 主叫方的会话标识符;
- session-version: 会话版本号,一般为 0;
- nettype: 网络类型,目前仅使用 IN 来表示 Internet 网络类型;
- addrtype: 地址类型,可以是 IPV4 和 IPV6 两种地址类型;
- unicast-address:会话发起者的 IP 地址。
3 - s: session name
s=<session name>
本次会话的标题或会话的名称(Session name)。
4 - t: time the session is active
t=<start-time> <stop-time>
会话的起始时间和结束时间(Time session starts and stops),如果没有规定这两个时间的话,都写为 0 即可。
5 - m: media name
m=<media> <port>/<number of ports> <proto> <fmt> ...
媒体行,描述了发送方所支持的媒体类型等信息(Media information)。
-
media:媒体类型,可以为 “audio”、“video”、“text”、“application”、“message”,表示音频类型、视频类型、文本类型、应用类型、消息类型等,以后也可能扩展其他类型;
-
port/number of ports: 流传输端口号。表示在对应的本地端口上发送流;
-
proto:流传输协议。举例说明:
-
RTP/SAVPF 表示用 UDP 传输 RTP 包;
-
TCP/RTP/SAVPF 表示用 TCP 传输 RTP 包;
-
UDP/TLS/RTP/SAVPF 表示用 UDP 来传输 RTP 包,并且使用 TLS 加密;
注:最后的 SAVPF 还有其他几种值:AVP, SAVP, AVPF, SAVPF。
- AVP 意为 AV profile
- S 意为 secure
- F 意为 feedback
-
fmt 表示媒体格式描述,它可能是一串数字,代表多个媒体,这个字段的含义与 proto 字段的类型相关。在后面,可以使用"a=rtpmap:"、“a=fmtp:”、“a=rtcp-fb” 等扩展字段来对 fmt 进行说明。
-
6 - c: connection data
c=<nettype> <addrtype> <connection-address>
一个会话描述必须在每个媒体层都包含“c=”字段或者在会话层包含一个“c=”字段。如果这两个层都出现的话,则媒体层出现的“c=”会覆盖会话层出现的“c=”字段的值。
- nettype: 是一个文本字符串,目前只定义了“IN”,表示“Internet”,未来会定义其他值。
- addrtype: 目前只定义了 IP4 和 IP6。
- connection-address: 标志连接的地址。取决于 addrtype 字段的不同,在 connection-address 之后可能也会跟随其他的字段。
7 - b: bandwidth
b=<bwtype>:<bandwidth>
这个字段的意思是本会话或者媒体所需占用的带宽。 bwtype 可以为 “CT” 或者 “AS”,给出了 bandwidth(单位 kbps)数字所代表的含义:
CT:表示会话所占的所有的带宽的大小。当用于 RTP 会话时,表示所有的 RTP 会话所占用的带宽。
AS:这个带宽类型是针对特定应用的。通常,这表示某应用所占用的最大带宽。当用于 RTP 会话时,表示单一 RTP 会话所占用的带宽.
可以理解为 CT 代表的是整个通话过程的带宽,AS 代表的是某个流的带宽。
8 - Encryption Keys (“k=”)
k=<method>
k=<method>:<encryption key>
如果在一个安全的信道上传输 SDP 消息,那么 SDP 之中也可以携带密钥,携带的方式就是采用字段 “k=”。当然这种方式目前已经不推荐了。
字段 “k=” 可以是全局的,也可以是放在某个 “m=” 中的,分别代表应用于所有的媒体流,或者单独应用于某条媒体流。定义格式有如下几种:
- k=clear:
在这种方法中,密钥是没有经过任何转换的。除非传输通道是绝对安全的,否则不应当使用这种方法。
- k=base64:
在这种方法中,密钥经过 base64 的编码。除非保证传输通道绝对安全,否则不应当使用这种方法。
- k=uri:
在这种方法中,给出一个 URI。通过这个 URI,可以获得密钥,访问 URI 的过程中可能还需要认证。
- k=prompt
在这种方法中,没有给出密钥。但是加上这个字段后,当用户加入会话时会提示其输入密钥。这种方式目前也不推荐。
9 - a: attributes
a=<attribute>
a=<attribute>:<value>
a 表示的是属性。a 字段是扩展 SDP 的主要方式,有会话层属性和媒体层属性。会话层的属性应用于所有的媒体流,媒体层的属性只应用于当前的媒体流。
属性有两种方式:
- 特性属性,a= 表示,例如:“a=recvonly”
- 值属性,以a=:表示,例如"a=ice-ufrag:khLS"
常用的属性列表如下:
表列 A | 表列 B | 表列 C |
---|---|---|
a=rtpmap: | RTP/AVP(Audio Video Profile) list | m=audio 54278 RTP/SAVPF 111 103 104 0 8 106 105 13 126 a=rtpmap:111 opus/48000/2 |
a=fmtp: | Format transport | a=fmtp:111 minptime=10 |
a=rtcp: | Explicit RTCP port (and address) | a=rtcp:54278 IN IP4 180.6.6.6 |
a=mid: | Media identification grouping | a=mid:audio |
a=ssrc: | “ssrc” indicates a property (known as a"source-level attribute") of a media source (RTP stream) within an RTP session | a=ssrc:189858836 msid:GUKF430Khp9jEQiPrdYe0LbTAALiNAKAIfl2 ea392930-e126-4573-bea3-bfba519b4d59 |
a=ssrc-group: | Ssrc identification grouping | a=ssrc-group:SIMULCAST 32040 32142 a=ssrc:32040 imageattr:96 [x=1280,y=720] a=ssrc:32142 imageattr:96 [x=640,y=480] |
a=ice-ufrag: | a=ice-pwd: The “ice-ufrag” and “ice-pwd” attributes convey the username fragment and password used by ICE for message integrity | a=ice-ufrag:kwlYyWNjhC9JBe/V a=ice-pwd:AU/SQPupllyS0SDG/eRWDCfA |
a=ice-pwd: | ||
a=fingerprint: | A certificate fingerprint is a secure one-way hash of the DER (distinguished encoding rules) form of the certificate. | a=fingerprint:sha-256 D1:2C:BE:AD:C4:F6:64:5C:25:16:11:9C:AF:E7:0F:73:79:36:4E:9C:1E:15:54:39:0C:06:8B:ED:96:86:00:39 |
a=candidate: | It contains a transport address for a candidate that can be used for connectivity checks. | a=candidate:4022866446 1 udp 2113937151 192.168.0.197 36768 typ host generation 0 |
a=ptime: | Length of time in milliseconds for each packet | a=ptime:20 |
a=recvonly | Receive only mode | a=recvonly |
a=sendrecv | Send and receive mode | a=sendrecv |
a=sendonly | Send only mode | a=sendonly |
a=type: | Type of conference | |
a=sdplang: | Language for the session description | |
a=framerate: | Maximum video frame rate in frames per second | a=framerate:15 |
a=inactive | Inactive mode | a=inactive |
SDP示例
Anatomy of a WebRTC SDP - webrtcHacks
sdp会话描述
由一个会话级描述
和多个媒体级描述
组成。会话级描述的作用域是整个会话,媒体级描述描述的是一个视频流或者音频流
会话级描述
由v=
开始到第一个媒体级描述结束
媒体级描述
由m=
开始到下一个媒体级描述结束
这个网站里面鼠标移动到 SDP 某一行时,就会显示这一行 SDP 的具体含义。
以下是一个非常非常完整的SDP示例,建议使用查询字段的方法:
// SDP 版本信息
v=0
// session 信息
// o=<username> <session-id> <session-version> <nettype> <addrtype> <unicast-address>
o=- 1873022542326151139 2 IN IP4 127.0.0.1
// s=<session name>
s=-
// t=<start-time> <stop-time>,如果不规定开始和结束时间,两个都填 0 即可
t=0 0
// 使用 "a=" 来扩展的 bundle 属性,其含义是 audio 和 video 使用同一个端口发送/接收,具体可以参考下方的 RFC 文档:
// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54
a=group:BUNDLE audio video
// 列出当前SDP中所有的 media stream id,以空格分割
// WMS 的含义是这里面的 media stream id 适配 webrtc 的 media stream
// 参考 RFC 文档: https://datatracker.ietf.org/doc/html/draft-ietf-mmusic-msid-01#section-3
a=msid-semantic: WMS 34b34ced3c5623ea4213vx3
// m=<media> <port> <proto> <fmt> ...
// port=10 无实际含义,真正通信使用的端口由 ICE Candidate 指定
// proto=UDP/TLS/RTP/SAVP 表示用 UDP 来传输 RTP 包,并使用 DTLS 加密
// 后面的一串数字是 fmt,表示所有 codec 的 payloadtype
m=audio 10 UDP/TLS/RTP/SAVPF 111 114 115 116 123 124 125
// c=<nettype> <addrtype> <connection-address>
c=IN IP4 0.0.0.0
// a=rtcp:<port> [nettype addrtype connection-address]
a=rtcp:10 IN IP4 0.0.0.0
// ICE 信息,参考 RFC 文档: https://tools.ietf.org/html/rfc5245#section-15.4
a=ice-ufrag:aZ/b
a=ice-pwd:3tFwvgPAA2PK3pPWoJjVz4FJ
a=ice-options:trickle renomination
// DTLS 信息,参考 RFC 文档: https://tools.ietf.org/html/rfc4572#section-5
a=fingerprint:sha-256 5F:78:37:05:D7:83:46:05:F7:3F:17:35:2A:7E:81:D3:2D:26:71:87:8B:9F:57:02:53:30:E3:3E:B6:3E:49:D5
// a=setup:<role>
// role可选active/passive/actpass/holdconn,
// 分别表示端点将发起一个传出连接、端点将接受传入连接、
// 端点愿意接受传入连接或启动传出连接、端点暂时不想建立连接
// 参考 rfc: https://tools.ietf.org/html/rfc4145#section-4
a=setup:actpass
// a=mid:<token>
// 这个 token 在 a=group 那一行中也有出现,
// 也就是说这里描述的媒体正是需要被 bundle 的
// 参考 rfc: https://tools.ietf.org/html/rfc5888#section-6
a=mid:audio
// 以下是这个媒体支持的所有 RTP 扩展头,
// 参考rfc: https://tools.ietf.org/html/rfc8285
// a=extmap:<value>["/"<direction>] <URI> <extensionattributes>
// value=ID
// direction 可选 sendonly/recvonly/sendrecv/inactive,默认值 sendrecv
// URI 就是这个扩展头的 URI,通信双方可以通过 URI 标明扩展头的含义让双方都能理解
// 这里表示 ID=1 的扩展头是 audio level 扩展头,表示 RTP 包中会携带音频包音量大小
// 参考 https://tools.ietf.org/html/rfc6464#section-4
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
// rtp stream 信息,参考 rfc: https://tools.ietf.org/html/draft-ietf-avtext-rid-09
a=extmap:13 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
// 流的方向,sendrecv 表示可以收也可以发
// 参考 rfc:https://tools.ietf.org/html/rfc3264
a=sendrecv
// 这一行表示 rtcp 和 rtp 复用一个端口,
// 参考 rfc:https://tools.ietf.org/html/rfc5761
// 和 rfc:https://tools.ietf.org/html/rfc8035
a=rtcp-mux
// a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>]
// opus codec 的 payload,
// 表明 fmt=111 就是用来传输 opus 数据的
// 参考 rfc: https://datatracker.ietf.org/doc/html/rfc7587
a=rtpmap:111 opus/48000/2
// a=rtcp-fb:<payload type> [...]
// 表示支持的 rtcp 反馈报文类型
// 这个反馈报文是 tcc 带宽探测用的
// 参考 https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=rtcp-fb:111 transport-cc
// nack,表示 fmt=111 支持 nack 重传包
a=rtcp-fb:111 nack
// a=fmtp 用来描述 codec 的一些特性,例如这里表示期望的 opus 最小打包时间是 10ms,并且使用 inbandfec
a=fmtp:111 minptime=10;useinbandfec=1
// 指明了音频 RTX 包的 payloadtype
// 参考 rfc:https://tools.ietf.org/html/rfc4588#section-8.6
a=rtpmap:114 rtx/48000/2
// apt 表示 fmt=114 的 RTX 包是用来重传 fmt=111 音频的
a=fmtp:114 apt=111
// 指明了 rsfec 包的 payloadtype
a=rtpmap:123 rsfec/48000/2
// 指明了 red 包的 payloadtype
// 参考 https://tools.ietf.org/html/rfc2198
a=rtpmap:124 red/48000/2
// 指明了音频 RTX 包的 payloadtype
a=rtpmap:125 rtx/48000/2
// apt 表示 fmt=125 的 RTX 包是用来重传 fmt=124 的 red 包的
a=fmtp:125 apt=124
// ssrc-group 指明了一组 ssrc 之间的关系,FID 表明后一个 ssrc 是前一个 ssrc 的 rtx
// https://tools.ietf.org/html/rfc5576#section-4.2
a=ssrc-group:FID 2952055605 1713037948
// cname 的内容是一个 16 位 Base64 字符串,含义是传输级的标识符,同一个 PeerConnection 的值相同
// 参考 https://datatracker.ietf.org/doc/html/rfc8834#section-4.9
a=ssrc:2952055605 cname:vqdagKn92E0lhuXn
// 这里出现了两个字符串,
// 前一个是 media stream id,后一个是 sender track id
// media stream 主要用于音视频同步,每个 track 以 media stream id 作为 sync label 进行同步
// 参考 https://datatracker.ietf.org/doc/html/draft-ietf-mmusic-msid
a=ssrc:2952055605 msid:34b34ced3c5623ea4213vx3 34b34ced3c5623ea4213vx3a0
// media stream id
a=ssrc:2952055605 mslabel:34b34ced3c5623ea4213vx3
// sender track id
a=ssrc:2952055605 label:34b34ced3c5623ea4213vx3a0
// video media
m=video 10 UDP/TLS/RTP/SAVPF 96 97 101 102 103
c=IN IP4 0.0.0.0
a=rtcp:10 IN IP4 0.0.0.0
a=ice-ufrag:aZ/b
a=ice-pwd:3tFwvgPAA2PK3pPWoJjVz4FJ
a=ice-options:trickle renomination
a=fingerprint:sha-256 5F:78:37:05:D7:83:46:05:F7:3F:17:35:2A:7E:81:D3:2D:26:71:87:8B:9F:57:02:53:30:E3:3E:B6:3E:49:D5
a=setup:actpass
a=mid:video
// 传输时间偏移扩展头
// 参考 https://datatracker.ietf.org/doc/html/rfc5450
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
// abs-send-time 扩展头,gcc 带宽探测用的
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
// 视频朝向扩展头
// 参考 https://datatracker.ietf.org/doc/html/rfc6184
a=extmap:4 urn:3gpp:video-orientation
// transport-cc 扩展头,tcc 带宽探测用的
a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
// 扩展头的内容是对播放延迟限制的值
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
// 视频内容类型扩展头
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
// 这个扩展头用于传输每帧的时间信息
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
// 视频的色域空间扩展头
a=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/color-space
// 传输视频 SDES 信息的扩展头
// 参考:https://datatracker.ietf.org/doc/html/draft-ietf-avtext-rid-06
a=extmap:13 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=sendrecv
a=rtcp-mux
// 支持 rtcp 压缩
// 参考 https://datatracker.ietf.org/doc/html/rfc5506#section-1
a=rtcp-rsize
// 指明 fmt=96 就是用来传输 H264 编码的视频的
a=rtpmap:96 H264/90000
// remb 反馈报文,gcc 带宽探测用的
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
// FIR(完整帧内请求)反馈报文
// 参考 https://datatracker.ietf.org/doc/html/rfc5104
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
// PLI NACK 反馈报文
// 参考 https://datatracker.ietf.org/doc/html/rfc5104
a=rtcp-fb:96 nack pli
// 后面的是一些 H264 的参数
a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96;packetization-mode=1
a=rtpmap:101 red/90000
a=fmtp:101 packetization-mode=1
a=rtpmap:102 rtx/90000
a=fmtp:102 apt=101;packetization-mode=1
a=rtpmap:103 rsfec/90000
a=fmtp:103 packetization-mode=1
// ssrc-group:SIM 表示后面的这些 ssrc 是同一个流的 simulcast
a=ssrc-group:SIM 2955842370 1032318052
a=ssrc-group:FID 2955842370 521905126
a=ssrc-group:FID 1032318052 1492521545
a=ssrc:2955842370 cname:vqdagKn92E0lhuXn
a=ssrc:2955842370 msid:34b34ced3c5623ea4213vx3 34b34ced3c5623ea4213vx3v0
a=ssrc:2955842370 mslabel:34b34ced3c5623ea4213vx3
a=ssrc:2955842370 label:34b34ced3c5623ea4213vx3v0
a=ssrc:1032318052 cname:vqdagKn92E0lhuXn
a=ssrc:1032318052 msid:34b34ced3c5623ea4213vx3 34b34ced3c5623ea4213vx3
a=ssrc:1032318052 mslabel:34b34ced3c5623ea4213vx3
a=ssrc:1032318052 label:34b34ced3c5623ea4213vx3v0
a=ssrc:521905126 cname:vqdagKn92E0lhuXn
a=ssrc:521905126 msid:34b34ced3c5623ea4213vx3 34b34ced3c5623ea4213vx3v0
a=ssrc:521905126 mslabel:34b34ced3c5623ea4213vx3
a=ssrc:521905126 label:34b34ced3c5623ea4213vx3v0
a=ssrc:1492521545 cname:vqdagKn92E0lhuXn
a=ssrc:1492521545 msid:34b34ced3c5623ea4213vx3 34b34ced3c5623ea4213vx3v0
a=ssrc:1492521545 mslabel:34b34ced3c5623ea4213vx3
a=ssrc:1492521545 label:34b34ced3c5623ea4213vx3v0
// 使用的 rsfec 的版本
a=rsfec-version:1
参考文献
RTSP协议学习笔记_雷霄骅的博客-CSDN博客_range: npt
音视频 - 玩转 WebRTC 通信:一文读懂 SDP 协议 - 个人文章 - SegmentFault 思否
从零开始写一个RTSP服务器(一)RTSP协议讲解_JT同学的博客-CSDN博客_rtsp 服务器
低延迟协议总结
参考文章
低延迟流媒体协议SRT、WebRTC、LL-HLS、UDP、TCP、RTMP详解 - 掘金 (juejin.cn)
弱网对抗相关
参考文章
音视频学习 – 弱网对抗技术相关实践 - 专栏 - 声网 RTE 开发者社区 (rtcdeveloper.cn)
弱网优化_dog head的博客-CSDN博客