对图像的处理即是:帧内预测编码
其预测值P,是由已编码的图像做参考,经运动补偿得到的。预测图像P和当前帧Fn相减,得到两图像的残差值Dn,Dn在经过转换T,量化Q,去处空间冗余,得到系数X,将X重排(使数据更加紧凑),熵编码(加入运动矢量。。。一些图像相关得信息),得到nal数据。
对视频序列的处理:帧间预测编码
预测值P,是由当前片中,己编码的宏块预测得到的(亮度4×4或者16×16预测,色度8×8预测)。当前待处理的块,减去预测值P,得残差值Dn,Dn在经过转换T,量化Q,得到系数X,将X重排(使数据更加紧凑),熵编码,得到nal数据
场:隔行扫描的图像,偶数行成为顶场行。奇数行成为底场行。
所有顶场行称为顶场。所有底场行称为底场.
帧:逐行扫描的图像。
图像:场和帧都可认为是图像。
宏块(MB):一个宏块由一个16×16亮度块、一个8×8Cb和一个8×8Cr组成。
片(slice):一个图像可以划分成一个或多个片,一个片由一个或多个宏块组成。
H.264从框架结构上分为视频编码层(VCL)和网络抽象层(NAL),VCL功能是进行视频编解码,包括运动补偿预测,变换编码和熵编码等功能;NAL用于采用适当的格式对VCL视频数据进行封装打包。这样划分主要有两个目的:
其一,可以定义VCL视频压缩处理与NAL网络传输机制的接口,这样允许视频编码层VCL的设计可以在不同的处理器平台进行移植,而与NAL层的数据封装格式无关;
其二,VCL和NAL都被设计成工作于不同的传输环境,异构的网络环境并不需要对VCL比特流进行重构和重编码。
VCL数据即被压缩编码后的视频数据序列,在VCL数据封装到NAL单元中之后,才可以用来传输或存储。
NAL单元(NALU)是NAL的基本语法结构,它包含一个字节的头信息和一系列来自VCL的称为原始字节序列载荷(RBSP)的字节流。
Nal头 |
Rbsp |
Nal头 |
Rbsp |
Nal头 |
Rbsp |
由于NAL的语法中没有给出长度信息,实际的传输、存储系统需要增加额外的头实现各个NAL单元的定界。
其中,AVI文件和MPEG TS广播流采取的是字节流的语法格式,即在NAL单元之前增加 0x00000001 的同步码,则从AVI文件或MPEG TS PES包中读出的一个H.264视频帧以下面的形式存在:
00 00 00 01 06 ... 00 00 00 01 67 ... 00 00 00 01 68 ... 00 00 00 01 65 ... SEI信息 SPS PPS IDR Slice
如果NALU对应的Slice为一帧的开始,则用4字节表示,即0x00000001;否则用3字节表示,0x000001。
而对于MP4文件,NAL单元之前没有同步码,却有若干字节的长度码(从下面的例子来看是2字节长度码),来表示NAL单元的长度,这个长度码所占用的字节数由MP4文件头给出;此外,从MP4读出来的视频帧不包含PPS和SPS,这些信息位于MP4的文件头中,解析器必须在打开文件的时候就获取它们。从MP4文件读出的一个H.264帧往往是下面的形式(假设长度码为2字节):
00 19 06 [... 25 字节...] 24 aa 65 [... 9386 字节...] SEI信息 IDR Slice
上例中长度计算如下:
0x0019 = 250x24aa = 9386
NALU头格式如下:
NALU 头由一个字节组成, 它的格式如下:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
F(禁止位): 1 个比特.
禁止位在编码中默认值为0,当网络识别此单元中存在比特错误时,可将其设为1,以便接收方丢掉该单元,主要用于适应不同种类的网络环境(比如有线无线相结合的环境)。例如对于从无线到有线的网关,一边是无线的非IP环境,一边是有线网络的无比特错误的环境。假设一个NAL单元到达无线那边时,校验和检测失败,网关可以选择从NAL流中去掉这个NAL单元,也可以把已知被破坏的NAL单元前传给接收端。在这种情况下,智能的解码器将尝试重构这个NAL单元(已知它可能包含比特错误)。而非智能的解码器将简单地抛弃这个NAL单元。
NAL单元结构规定了用于面向分组或用于流的传输子系统的通用格式。在H.320和MPEG-2系统中,NAL单元的流应该在NAL单元边界内,每个NAL单元前加一个3字节的起始前缀码。在分组传输系统中,NAL单元由系统的传输规程确定帧界,因此不需要上述的起始前缀码。一组NAL单元被称为一个接入单元,定界后加上定时信息(SEI),形成基本编码图像。该基本编码图像(PCP)由一组已编码的NAL单元组成,其后是冗余编码图像(RCP),它是PCP同一视频图像的冗余表示,用于解码中PCP丢失情况下恢复信息。如果该编码视频图像是编码视频序列的最后一幅图像,应出现序列NAL单元的end,表示该序列结束。一个图像序列只有一个序列参数组,并被独立解码。如果该编码图像是整个NAL单元流的最后一幅图像,则应出现流的end。
H.264采用上述严格的接入单元,不仅使H.264可自适应于多种网络,而且进一步提高其抗误码能力。序列号的设置可发现丢的是哪一个VCL单元,冗余编码图像使得即使基本编码图像丢失,仍可得到较“粗糙”的图像。
NRI: 2 个比特.
nal_ref_idc. 取 00 ~ 11, 指示这个 NALU 的重要性, 用于在重构过程中标记一个NAL单元的重要性,值越大,越重要。值为0表示这个NAL单元没有用于预测,因此可被解码器抛弃而不会有错误扩散;值高于0表示此NAL单元要用于无漂移重构,且值越高,对此NAL单元丢失的影响越大。
Type: 5 个比特,如下:
这用来标识NAL单元中的RBSP数据类型,其中,nal_unit_type为1, 2, 3, 4, 5的NAL单元称为VCL的NAL单元,其他类型的NAL单元为非VCL的NAL单元。
常用的NAL头的取值如:
0x67: SPS 0x68: PPS 0x65: IDR 0x61: non-IDR Slice 0x01: B Slice 0x06: SEI 0x09: AU Delimiter
(1)NAL Units:视频数据封装在整数字节的NALU中,它的第一个字节标志该单元中数据的类型,H.264定义了两种封装格式:
基于包交换的网络(如H.323系统)可以使用RTP封装格式封装NALU。
而另外一些系统可能要求将NALU作为顺序比特流传送,为此H.264定义了一种比特流格式的传输机制,使用start_code_prefix将NALU封装起来,从而确定NAL边界。
每个NAL单元是一个一定语法元素的可变长字节字符串,包括一个字节的头信息(用来表示数据类型),以及若干整数字节的负荷数据。一个NAL单元可以携带一个编码片、A/B/C型数据分割或一个序列或图像参数集。NAL单元按RTP序列号按序传送。
(2)参数集:以往视频编解码标准中GOB\GOP\图像等头信息是至关重要的,包含这些信息的包的丢失常导致与这些信息相关的图像不能解码。为此H.264将这些很少变化并且对大量VCL NALU起作用的信息放在参数集中传送。参数集分为两种,即序列参数集和图像参数集。为适应多种网络环境,参数集可以带内传送,也可以采用带外方式传送。
序列的参数集(SPS):包括了一个图像序列的所有信息,
图像的参数集(PPS):包括了一个图像所有片的信息。
在实际的H264数据帧中,往往帧前面带有00 00 00 01 或 00 00 01分隔符,一般来说编码器编出的首帧数据为PPS与SPS,接着为I帧……
如下图:
综上,判断是否为I帧的算法为: (NALU类型 & 0001 1111) = 5 即 NALU类型 & 31 = 5
比如0x65 & 31 = 5,为I帧。
H.264能够在基于RTP/UDP/IP、H.323/M、MPEG-2传输和H.320协议的网络中使用
H.264的RTP封装参考RFC 3550,载荷类型(PT)域未作规定。
通常情况下,一个宏块的数据是存放在一起而组成片的,数据划分使得一个片中的宏块数据重新组合,把宏块语义相关的数据组成一个划分,由划分来组装片。在H.264中有三种不同的数据划分。
(1)头信息划分:包含片中宏块的类型,量化参数和运动矢量,是片中最重要的信息。
(2)帧内信息划分:包含帧内CBPs和帧内系数,帧内信息可以阻止错误的蔓延。
(3)帧间信息划分:包含帧间CBPs和帧间系数,通常比前两个划分要大得多。
帧内信息划分结合头信息解出帧内宏块,帧间信息划分结合头信息解出帧间宏块。帧间信息划分的重要性最低,对重同步没有贡献。当使用数据划分时,片中的数据根据其类型被保存到不同的缓存,同时片的大小也要调整,使得片中最大的划分小于MTU尺寸。
解码端若获得所有的划分,就可以完整重构片;解码端若发现帧内信息或帧间信息划分丢失,可用的头信息仍然有很好的错误恢复性能。这是因为宏块类型和宏块的运动矢量含有宏块的基本特征。
通过设置宏块次序映射表(MBAmap)来任意地指配宏块到不同的片组,FMO模式打乱了原宏块顺序,降低了编码效率,增加了时延,但增强了抗误码性能。FMO模式划分图像的模式各种各样,重要的有棋盘模式、矩形模式等。当然FMO模式也可以使一帧中的宏块顺序分割,使得分割后的片的大小小于无线网络的MTU尺寸。经过FMO模式分割后的图像数据分开进行传输,以棋盘模式为例,当一个片组的数据丢失时可用另一个片组的数据(包含丢失宏块的相邻宏块信息)进行错误掩盖。实验数据显示,当丢失率为(视频会议应用时)10%时,经错误掩盖后的图像仍然有很高的质量。
RTP用于在单播或多播网络中传送实时数据。它们典型的应用场合有如下几个:
简单的多播音频会议。语音通信通过一个多播地址和一对端口来实现。一个用于音频数据(RTP),另一个用于控制包(RTCP)。
音频和视频会议。如果在一次会议中同时使用了音频和视频会议,这两种媒体将分别在不同的RTP会话中传送,每一个会话使用不同的传输地址(IP地址+2个端口)。如果一个用户同时使用了两个会话,则每个会话对应的RTCP包都使用规范化名字CNAME(Canonical Name)。与会者可以根据RTCP包中的CNAME来获取相关联的音频和视频,然后根据RTCP包中的计时信息(Network time protocol)来实现音频和视频的同步。
RTP传输服务使用者之间的连接被称为RTP会话,就每一个会话参加者而言,会话由一对传输层地址(即一个网络层地址加上 两个端口地址,一个端口为 RTP 报文的发送/接收所占用,另一个端口为 RTCP 报文的发送/接收所占用)标识。在 IP 多播方式中,每个参与者的目的地运输层地址对可以都相同;在单播方式中,每个参与者的地址对均不相同,因为每个人的网络层地址都不相同。在多媒体会话中,每个媒体信号由不同的 RTP 会话传送,有其自己的 RTCP 分组。各 RTP 会话由不同的端口对 (和/或) 不同的多播地址区分。
翻译器和混合器都是RTP级的中继系统。
翻译器用在通过IP多播不能直接到达的用户区,例如发送者和接收者之间存在防火墙。
当与会者能接收的音频编码格式不一样,比如有一个与会者通过一条低速链路接入到高速会议,这时就要使用混合器。在进入音频数据格式需要变化的网络前,混合器将来自一个源或多个源的音频包进行重构,并把重构后的多个音频合并,采用另一种音频编码进行编码后,再转发这个新的RTP包。从一个混合器出来的所有数据包要用混合器作为它们的同步源(SSRC,见RTP的封装)来识别,可以通过贡献源列表(CSRC表,见RTP的封装)可以确认谈话者。
流媒体是指Internet上使用流式传输技术的连续时基媒体。当前在Internet上传输音频和视频等信息主要有两种方式:下载和流式传输两种方式。
下载情况下,用户需要先下载整个媒体文件到本地,然后才能播放媒体文件。在视频直播等应用场合,由于生成整个媒体文件要等直播结束,也就是用户至少要在直播结束后才能看到直播节目,所以用下载方式不能实现直播。
流式传输是实现流媒体的关键技术。使用流式传输可以边下载边观看流媒体节目。由于Internet是基于分组传输的,所以接收端收到的数据包往往有延迟和乱序(流式传输构建在UDP上)。要实现流式传输,就是要从降低延迟和恢复数据包时序入手。在发送端,为降低延迟,往往对传输数据进行预处理(降低质量和高效压缩)。在接收端为了恢复时序,采用了接收缓冲;而为了实现媒体的流畅播放,则采用了播放缓冲。
使用接收缓冲,可以将接收到的数据包缓存起来,然后根据数据包的封装信息(如包序号和时戳等),将乱序的包重新排序,最后将重新排序了的数据包放入播放缓冲播放。
为什么需要播放缓冲呢?容易想到,由于网络不可能很理想,并且对数据包排序需要处理时耗,我们得到排序好的数据包的时间间隔是不等的。如果不用播放缓冲,那么播放节目会很卡,这叫时延抖动。相反,使用播放缓冲,在开始播放时,花费几十秒钟先将播放缓冲填满(例如PPLIVE),可以有效地消除时延抖动,从而在不太损失实时性的前提下实现流媒体的顺畅播放。
到目前为止,Internet 上使用较多的流式视频格式主要有以下三种:
RealNetworks 公司的RealMedia ,Apple 公司的QuickTime 以及Microsoft 公司的Advanced Streaming Format (ASF) 。
上面在谈接收缓冲时,说到了流媒体数据包的封装信息(包序号和时戳等),这在后面的RTP封装中会有体现。另外,RealMedia这些流式媒体格式只是编解码有不同,但对于RTP来说,它们都是待封装传输的流媒体数据而没有什么不同。
不少人也把RTP归为应用层的一部分,这是从应用开发者的角度来说的。操作系统中的TCP/IP等协议栈所提供的是我们最常用的服务,而RTP的实现还是要靠开发者自己。因此从开发的角度来说,RTP的实现和应用层协议的实现没有不同,所以可将RTP看成应用层协议。
RTP实现者在发送RTP数据时,需先将数据封装成RTP包,而在接收到RTP数据包,需要将数据从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。
从图 1可以看出,RTSP是一个应用层协议(TCP/IP网络体系中)。它以C/S模式工作,它是一个多媒体播放控制协议,主要用来使用户在播放流媒体时可以像操作本地的影碟机一样进行控制,即可以对流媒体进行暂停/继续、后退和前进等控制。
资源预定协议RSVP
资源预定协议RSVP(Resource Reservation Protocol)是IETF提出的协议,对应的RFC文档为RFC2208。
从图 1可以看出,RSVP工作在IP层之上传输层之下,是一个网络控制协议。RSVP通过在路由器上预留一定的带宽,能在一定程度上为流媒体的传输提供服务质量。在某些试验性的系统如网络视频会议工具vic中就集成了RSVP。
(这里和上面的说法有一点点区别)
根据声音流和图像流的相对时间(即RTP包的时间戳),以及它们的绝对时间(即对应的RTCP包中的RTCP)(应该还有RTCP中的CNAME),可以实现声音和图像的同步。
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X| CC |M| PT | sequence number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| synchronization source (SSRC) identifier |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
| contributing source (CSRC) identifiers |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
图1
前12字节是固定的,CSRC可以有多个。
1) V:RTP协议的版本号,占2位,当前协议版本号为2
2) P:填充标志,占1位,如果P=1,则在该报文的尾部填充一个或多个额外的八位组,它们不是有效载荷的一部分。
3) X:扩展标志,占1位,如果X=1,则在RTP报头后跟有一个扩展报头
4) CC:CSRC计数器,占4位,指示CSRC 标识符的个数
5) M: 标记,占1位,不同的有效载荷有不同的含义,对于视频,标记一帧的结束;对于音频,标记会话的开始。
6) PT(payload type): 有效荷载类型,占7位,用于说明RTP报文中有效载荷的类型,如GSM音频、JPEM图像等,在流媒体中大部分是用来区分音频流和视频流的,这样便于客户端进行解析。
7) 序列号:占16位,用于标识发送者所发送的RTP报文的序列号,每发送一个报文,序列号增1。这个字段当下层的承载协议用UDP的时候,网络状况不好的时候可以用来检查丢包。当出现网络抖动的情况可以用来对数据进行重新排序。序列号的初始值是随机的,同时音频包和视频包的sequence 是分别记数的。
8) 时戳(Timestamp):占32位,必须使用90 kHz 时钟频率(程序中的90000)。时戳反映了该RTP报文的第一个八位组的采样时刻。接收者使用时戳来计算延迟和延迟抖动,并进行同步控制。可以根据RTP包的时间戳来获得数据包的时序。
9) 同步信源(SSRC)标识符:占32位,用于标识同步信源。同步信源是指产生媒体流的信源,它通过RTP报头中的一个32位数字SSRC标识符来标识,而不依赖于网络地址,接收者将根据SSRC标识符来区分不同的信源,进行RTP报文的分组。
该标识符是随机选择的,参加同一视频会议的两个同步信源不能有相同的SSRC。
此标识不是随机选择的,目的在于使同一RTP包连接中没有两个同步源有相同的SSRC标识。尽管多个源选择同一个标识的概率很低,所有RTP实现都必须探测并解决冲突。如源改变源传输地址,也必须选择一个新SSRC标识以避免插入成环行源。
10) 提供信源(CSRC)标识符:每个CSRC标识符占32位,可以有0~15个CSRC。每个CSRC标识了包含在该RTP报文有效载荷中的所有提供信源。
提供信源用来标志对一个RTP混合器产生的新包有贡献的所有RTP包的源。是指当混合器接收到一个或多个同步信源的RTP报文后,经过混合处理产生一个新的组合RTP报文,并把混合器作为组合RTP报文的SSRC,而将原来所有的SSRC都作为CSRC传送给接收者,使接收者知道组成组合报文的各个SSRC。
注:基本的RTP说明并不定义任何头扩展本身,如果遇到X=1,需要特殊处理
如果扩展标志被置位则说明紧跟在报头后面是一个头扩展,其格式如下:
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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | defined by profile | length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | header extension | | .... |
取一段码流如下:
80 e0 00 1e 00 00 d2 f0 00 00 00 00 41 9b 6b 49 €?....??....A?kI
e1 0f 26 53 02 1a ff06 59 97 1d d2 2e 8c 50 01 ?.&S....Y?.?.?P.
cc 13 ec 52 77 4e e50e 7b fd 16 11 66 27 7c b4 ?.?RwN?.{?..f'|?
f6 e1 29 d5 d6 a4 ef3e 12 d8 fd 6c 97 51 e7 e9 ??)????>.??l?Q??
cfc7 5e c8 a9 51 f6 82 65 d6 48 5a 86 b0 e0 8c ??^??Q??e?HZ????RTP头后是RTP载荷,RTP载荷第一个字节格式跟NALU头一样:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
F和NRI也跟NALU头一样,只有Type有些不一样:
0 没有定义
1-23 NAL单元 单个 NAL 单元包.
24 STAP-A 单一时间的组合包
25 STAP-B 单一时间的组合包
26 MTAP16 多个时间的组合包
27 MTAP24 多个时间的组合包
28 FU-A 分片的单元
29 FU-B 分片的单元
30-31 没有定义
载荷格式定义三个不同的基本荷载结构,接收者可以通过RTP荷载的第一个字节后5位(Type)识别荷载结构:
1) 单个NAL单元包:荷载中只包含一个NAL单元。NAL头类型域等于原始 NAL单元(NALU)类型,即Type在范围1到23之间。
2) 聚合包(组合包):本类型用于聚合多个NAL单元到单个RTP荷载中。本包有四种版本,单时间聚合包类型A (STAP-A),单时间聚合包类型B (STAP-B),多时间聚合包类型(MTAP)16位位移(MTAP16), 多时间聚合包类型(MTAP)24位位移(MTAP24)。赋予STAP-A, STAP-B, MTAP16, MTAP24的NAL单元类型号(Type)分别是 24,25, 26, 27
3) 分片包:用于分片单个NAL单元到多个RTP包。现存两个版本FU-A,FU-B,用NAL单元类型 (Type)28,29标识。
常用的打包时的分包规则是:如果小于MTU采用单个NAL单元包,如果大于MTU就采用FUs分片方式。对于 NALU(NAL单元)的长度小于 MTU 大小的包, 一般采用单一 NAL 单元模式.
定义在此的NAL单元包必须只包含一个。RTP序号必须符合NAL单元的解码顺序。这种情况下,NAL单元的第一字节和RTP荷载头第一个字节重合。如上图。
对于一个原始的 H.264 NALU 单元常由 [Start Code] [NALU Header] [NALU Payload] 三部分组成, 其中 Start Code 用于标示这是一个 NALU 单元的开始, 必须是 "00 00 00 01" 或 "00 00 01", NALU 头仅一个字节, 其后都是 NALU 单元载荷。
打包时去除 "00 00 01" 或 "00 00 00 01" 的开始码, 把其他数据封装成 RTP 包即可。
如有一个 H.264 的 NALU 是这样的:
[00 00 00 01 67 42 A0 1E 23 56 0E 2F ... ]
这是一个序列参数集 NAL 单元。 [00 00 00 01] 是四个字节的开始码, 67 是 NALU 头, 42 开始的数据是 NALU 载荷.
封装成 RTP 包将如下:
[ RTP Header ] [ 67 42 A0 1E 23 56 0E 2F ... ]
即只要去掉 4 个字节的开始码就可以了.
当 NALU 的长度特别小时, 可以把几个 NALU 单元封在一个 RTP 包中.
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|STAP-A NAL HDR | NALU 1 Size | NALU 1 HDR |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 1 Data |
: :
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | NALU 2 Size | NALU 2 HDR |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 2 Data |
: :
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| FU indicator | FU header | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |
| FU payload |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
FU-A RTP 载荷格式
其中 FU indicator 8位格式为:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
FU header 格式:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+---------------+
当 NALU 的长度超过 MTU 时, 就必须对 NALU 单元进行分片封包. 也称为 Fragmentation Units (FUs).
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字节的分片单元指示( FU indicator)、1字节的分片单元头( FU header)和分片单元荷载组成。S(开始位): 1 bit, 当设置成1,指示分片NAL单元的开始。当跟随的FU荷载不是分片NAL单元荷载的开始,开始位设为0。
E(结束位): 1 bit, 当设置成1,指示分片NAL单元的结束,即,荷载的最后字节也是分片NAL单元的最后一个字节。当跟随的 FU荷载不是分片NAL单元的最后分片,结束位设置为0。
R(保留位): 1 bit, 保留位必须设置为0,接收者必须忽略该位。
Type(类型):5 bit, 是NAL Header中的Type。
打包时,原始的NAL头的前三位为FU indicator的前三位,原始的NAL头的后五位(Type)为FU header的后五位(Type)。80 60 01 0f 00 0e 10 00 00 00 00 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????$打包时,FU indicator的F、NRI是NAL Header中的F、NRI,Type是28(FU-A);FU Header的S、E、R分别按照分片起始位置设置,Type是NAL Header中的Type。
解包时,取FU indicator的前三位和FU Header的后五位,即0110 0101(0x65)为NAL类型。针对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。图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.
取一段码流分析系统标题和节目映射流
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,循环冗余校验。
到这里节目映射流解析完毕。(好累)。
好戏还在后头呢。
图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?.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,贴一下解析函数
其他字段可参考协议解析
ps:
遇到00 00 01 bd的,这个是私有流的标识
ps:
另外,有的hk摄像头回调然后解读出来的原始h.264码流,有的一包里只有分界符数据(nal_unit_type=9)或补充增强信息单元(nal_unit_type=6),如果直接送入解码器,有可能会出现问题,这里的处理方式要么丢弃这两个部分,要么和之后的数据合起来,再送入解码器里,如有遇到的朋友可以交流一下:)
写在后面:
第一次发原创,在这里感谢 @cmengwei 的无私帮助,提供了很多帮助,非常感谢。
文档我都放在了我的资源里面,有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
原文:http://blog.csdn.net/chen495810242/article/details/39207305
http://blog.csdn.net/dreamxiang68/article/details/7183889
http://blog.chinaunix.net/uid-23883288-id-3034586.html
http://m.blog.csdn.net/blog/CodeFoxTiger/13767545
http://blog.csdn.net/jefry_xdz/article/details/8461343
http://www.cnblogs.com/foxmin/archive/2012/03/13/2393349.html