MP4文件中的NALU解析

来源:互联网 发布:vk7201数据 编辑:程序博客网 时间:2024/06/11 18:49
1.什么是NAL
NAL全称Network Abstract Layer,即网络抽象层。
在H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:视频编码层面(VCL)和网络抽象层面(NAL)。其中,前者负责有效表示视频数据的内容,而后者则负责格式化数据并提供头信息,以保证数据适合各种信道和存储介质上的传播。VCL(Video Coding Layer)是H.264/AVC的规格,意思是压缩后、去冗余(Visual Redundancy)的影像资料,其技术核心包括动作估计、转换编码、预测编码、去区块效应滤波、及熵编码等。
视讯编码层往往与网络抽象层(NAL)相互配合,标准的NAL-unit总共规范(profile)有12种,这12种型式可粗分成VCL NAL-unit及non-VCL NAL-unit,其中 VCL NAL-unit是指NAL-unit中存放的完全是VCL的影像资料。
现实中的传输系统是多样化的,其可靠性,服务质量,封装方式等特征各不相同,NAL这一概念的提出提供了一个视频编码器和传输系统的友好接口,使得编码后的视频数据能够有效的在各种不同的网络环境中传输。
2.NALU/Sample/帧(frame)/分片(slice)/宏块(MB)的关系
(1)可以认为一个NALU就是一个sample,即NALU=sample。错错错!!!大错特错!!!我自己阅读了ISO-14496-15,里面明确说明,一个sample有多个NALU!!!而且我亲自解析了一个mp4文件,确实是一个sample有多个NALU。否则,NALU的长度就没有意义了,因为MP4文件的头中包含了每个sample的size信息。
(2)Slice是片的意思,H264中把图像分成一帧(frame)或两场(field),而帧又可以分成一个或几个片(Slilce);片由宏块(MB)组成。宏块是编码处理的基本单元。
(3)一个frame是可以分割成多个Slice来编码的,而一个Slice编码之后被打包进一个NAL单元,不过NAL单元除了容纳Slice编码的码流外,还可以容纳其他数据,比如序列参数集SPS。
3.RBSP、SODB、EBSP三者的区别和联系
(1)SODB:最原始的编码数据,没有任何附加数据
(2)RBSP:在 SODB 的基础上加了rbsp_stop_ont_bit(bit 值为 1)并用 0 按字节补位对齐
(3)EBSP:在 RBSP 的基础上增加了防止伪起始码字节(0X03)
(4)NALU是对RBSP的封装。而RTP之类的是对NALU的封装。
4.NALU/NAL Units/NAL 单元
NALU是一种封装的模组,并拥有NAL-unit 标头(Header),此Header内又有许多资讯,包括 Type。一个完整的NALU是标头(Header)加上位元流(bitstream)。多个NAL-units组成一个存储单元(access unit)。
5.NALU的类型
标识NAL单元中数据类型,其中,nal_unit_type为1, 2, 3, 4, 5及12的NAL单元称为VCL的NAL单元,其他类型的NAL单元为非VCL的NAL单元。
0:未规定
1:非IDR图像中不采用数据划分的片段
2:非IDR图像中A类数据划分片段
3:非IDR图像中B类数据划分片段
4:非IDR图像中C类数据划分片段
5:IDR图像的片段
6:补充增强信息 (SEI)
7:序列参数集/SPS
8:图像参数集/PPS
9:分割符
10:序列结束符
11:流结束符
12:填充数据
13 – 23:保留
24 – 31:未规定
即认为SPS和PPS都是特殊的NALU。一个MP4文件只有一个SPS,但是有很多PPS,SPS必须在所有NALU的最开头。
补充:
(1)I frame 是自己独立编码,不依赖于其他frame 数据。
P frame 依赖 I frame 数据。
B frame 依赖 I frame, P frame 或其他 B frame 数据。
(2)IDR帧属于I帧。解码器收到IDR帧时,将所有的参考帧队列丢弃(用x264_reference_reset函 数实现——在encoder.c文件中)。这点是所有I帧共有的特性,但是收到IDR帧时,解码器另外需要做的工作就是:把所有的PPS和SPS参数进行更新。由此可见,在编码器端,每发一个IDR,就相应地发一个 PPS&SPS_nal_unit。
(3)分ABC片主要目的是为了对重要程度不同的数据进行不同程度的保护。
6.NALU的顺序要求
H.264/AVC标准对送到解码器的NAL单元顺序是有严格要求的,如果NAL单元的顺序是混乱的,必须将其重新依照规范组织后送入解码器,否则解码器不能够正确解码。
(1)序列参数集NAL单元(nal_unit_type为7)必须在传送所有以此参数集为参考的其他NAL单元之前传送,不过允许这些NAL单元中间出现重复的序列参数集NAL单元。所谓重复的详细解释为:序列参数集NAL单元都有其专门的标识,如果两个序列参数集NAL单元的标识相同,就可以认为后一个只不过是前一个的拷贝,而非新的序列参数集。
(2)图像参数集NAL单元(nal_unit_type为8)必须在所有以此参数集为参考的其他NAL单元之先,不过允许这些NAL单元中间出现重复的图像参数集NAL单元,这一点与上述的序列参数集NAL单元是相同的。
(3)不同基本编码图像中的片段(slice)单元和数据划分片段(data partition)单元在顺序上不可以相互交叉,即不允许属于某一基本编码图像的一系列片段(slice)单元和数据划分片段(data partition)单元中忽然出现另一个基本编码图像的片段(slice)单元片段和数据划分片段(data partition)单元。
(4)参考图像的影响:如果一幅图像以另一幅图像为参考,则属于前者的所有片段(slice)单元和数据划分片段(data partition)单元必须在属于后者的片段和数据划分片段之后,无论是基本编码图像还是冗余编码图像都必须遵守这个规则
(5)基本编码图像的所有片段(slice)单元和数据划分片段(data partition)单元必须在属于相应冗余编码图像的片段(slice)单元和数据划分片段(data partition)单元之前。
(6)如果数据流中出现了连续的无参考基本编码图像,则图像序号小的在前面。
(7)如果arbitrary_slice_order_allowed_flag置为1,一个基本编码图像中的片段(slice)单元和数据划分片段(data partition)单元的顺序是任意的,如果arbitrary_slice_order_allowed_flag置为零,则要按照片段中第一个宏块的位置来确定片段的顺序,若使用数据划分,则A类数据划分片段在B类数据划分片段之前,B类数据划分片段在C类数据划分片段之前,而且对应不同片段的数据划分片段不能相互交叉,也不能与没有数据划分的片段相互交叉。
(8)如果存在SEI(补充增强信息) 单元的话,它必须在它所对应的基本编码图像的片段(slice)单元和数据划分片段(data partition)单元之前,并同时必须紧接在上一个基本编码图像的所有片段(slice)单元和数据划分片段(data partition)单元后边。假如SEI属于多个基本编码图像,其顺序仅以第一个基本编码图像为参照。
(9)如果存在图像分割符的话,它必须在所有SEI 单元、基本编码图像的所有片段slice)单元和数据划分片段(data partition)单元之前,并且紧接着上一个基本编码图像那些NAL单元。
(10)如果存在序列结束符,且序列结束符后还有图像,则该图像必须是IDR(即时解码器刷新)图像。序列结束符的位置应当在属于这个IDR图像的分割符、SEI 单元等数据之前,且紧接着前面那些图像的NAL单元。如果序列结束符后没有图像了,那么它的就在比特流中所有图像数据之后。
(11)流结束符在比特流中的最后。
①NALU(Network Abstract Layer Unit):H264标准中的比特流是以NAL为单位,每个NAL单元包含一个RBSP(raw byte sequence payload,原始字节序列载荷),NALU的头信息定义了RBSP所属类型。类型一般包括序列参数集(SPS)、图像参数集(PPS)、增强信息(SEI)、条带(Slice)等,其中,SPS和PPS属于参数集,两标准采用参数集机制是为了将一些主要的序列、图像参数(解码图像尺寸、片组数、参考帧数、量化和滤波参数标记等)与其他参数分离,通过解码器先解码出来。此外,为了增强图像的清晰度,AVS-M添加了图像头(Picture head)信息。读取NALU流程中,每个NALU前有一个起始码0x000001,为防止 内部0x000001序列竞争,H.264编码器在最后一字节前插入一个新的字节——0x03,所以解码器检测到该序列时,需将0x03删掉,而AVS-M只需识别出起始码0x000001。
②读取宏块类型(mb type)和宏块编码模板(cbp):编解码图像以宏块划分,一个宏块由一个16*16亮度块和相应的一个8*8cb和一个8*8cr色度块组成。
7.NALU的语法/结构
(1)NALU的结构及含义
nal_unit( NumBytesInNALunit ) {
forbidden_zero_bit // forbidden_zero_bit 等于 0表示网络传输没有出错
nal_ref_idc // 指示当前 NAL 的优先级。取值范围为 0-3, 值越高,表示当前 NAL 越重要,需要优先受到保护。H.264 规定如果当前 NAL 是属于参考帧的片,或是序列参数集,或是图像参数集这些重要的数据单位时,本句法元素必须大于 0。
nal_unit_type // NAL类型 指明当前 NAL unit 的类型
NumBytesInRBSP = 0
for( i = 1; i <</span> NumBytesInNALunit; i++ ) {
if( i + 2 <</span> NumBytesInNALunit && next_bits( 24 ) = = 0x000003 ) {
rbsp_byte[ NumBytesInRBSP++ ]
rbsp_byte[ NumBytesInRBSP++ ]
i += 2
//emulation_prevention_three_byte NAL 内部为防止与起始码竞争而引入的填充字节,值为 0x03。
emulation_prevention_three_byte
} else
rbsp_byte[ NumBytesInRBSP++ ]
}
}
(2)第1位禁止位,值为1表示语法出错;第2~3位为参考级别;第4~8为是nal单元类型
举例:如果某个NAL的第一个字节值为0x67,0x68,0x65
其中0x67的二进制码为:
0110 0111
4-8为00111,转为十进制7,7对应序列参数集SPS
其中0x68的二进制码为:
0110 1000
4-8为01000,转为十进制8,8对应图像参数集PPS
其中0x65的二进制码为:
0110 0101
4-8为00101,转为十进制5,5对应IDR图像中的片(I帧)
(3)NAL头的进一步说明
NAL的头占用了一个字节,按照比特自高至低排列可以表示如下:
0AABBBBB
其中,AA用于表示该NAL是否可以丢弃(有无被其后的NAL参考),00b表示没有参考作用,可丢弃,如B slice、SEI等,非零——包括01b、10b、11b——表示该NAL不可丢弃,如SPS、PPS、I Slice、P Slice等。常用的NAL头的取值如:
0x67: SPS
0x68: PPS
0x65: IDR
0x61: non-IDR Slice
0x01: B Slice
0x06: SEI
0x09: AU Delimiter
由于NAL的语法中没有给出长度信息,实际的传输、存储系统需要增加额外的头实现各个NAL单元的定界。
其中,AVI文件和MPEG TS广播流采取的是字节流的语法格式,即在NAL单元之前增加0x00000001的同步码(即看到0x00000001,便知道到了NALU的开头),则从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
而对于MP4文件,NAL单元之前没有同步码,却有若干字节的长度码,来表示NAL单元的长度,这个长度码所占用的字节数由MP4文件头给出;此外,从MP4读出来的视频帧不包含PPS和SPS,这些信息位于MP4的文件头中,解析器必须在打开文件的时候就获取它们。从MP4文件读出的一个H.264帧往往是下面的形式(假设长度码为2字节):
00 19 06 [... 25 字节...] 24 aa 65 [... 9386 字节...]
SEI信息 IDR Slice
备注:上面这个例子是我在网上看到的,我认为不对,因为经过我亲自测试,发现,length应该是包括NALU的头部的长度的。
8.MP4文件中的NAL
MP4文件中的一个sample由多个NALU组成。大体结构如下:Length1+NALU1+Length2+NALU2+。。。。其中Length表示下一个NALU的长度(不包括Length本身的长度),而NALU的第一个字节是表示该NALU的头部。通过NALU头部的信息可以得到该NALU的类型。每个sample的总长度可以由mp4中的stsz中的条目得到。至于PPS和SPS,存储在mp4的头部moov-trak-mdia-minf-stbl-stsd-avc1-avcC中。
关于Length的长度:Length的长度是1/2/4字节中的一种,且在一个mp4文件中是固定的,存储在mp4的头部moov-trak-mdia-minf-stbl-stsd-avc1-avcC中。
avcC的结构如下图所示:
X


我自己解析了两个mp4文件,均没有发现所谓的avc1和avcc,不知道问题在哪里。
0 0