【iOS】h264视频解码

H.264 中的 NAL 技术

NAL概述
NAL 全称 Network Abstract Layer,即网络抽象层。在 H.264/AVC 视频编码标准中,整个系统框架被分为 了两个层面:视频编码层面(VCL)和网络抽象层面(NAL)。其中,前者负责有效表示视频数据的内容, 而后者则负责格式化数据并提供头信息,以保证数据适合各种信道和存储介质上的传输。 现实中的传输系统是多样化的,其可靠性,服务质量,封装方式等特征各不相同,NAL 这一概念的提出 提供了一个视频编码器和传输系统的友好接口,使得编码后的视频数据能够有效地在各种不同的网络环境 中传输。

NAL单元
NAL 单元是 NAL 的基本语法结构,它包含一个字节的头信息和一系列来自 VCL 的称为原始字节序列载荷 (RBSP)的字节流。头信息中包含着一个可否丢弃的指示标记,标识着该 NAL 单元的丢弃能否引起错误 扩散,一般,如果 NAL 单元中的信息不用于构建参考图像,则认为可以将其丢弃;最后包含的是 NAL 单 元的类型信息,暗示着其内含有效载荷的内容。 送到解码器端的 NAL 单元必须遵守严格的顺序,如果应 用程序接收到的 NAL 单元处于乱序,则必须提供一种恢复其正确顺序的方法。

NAL 实现编解码器与传输网络的结合
NAL 提供了一个编解码器与传输网络的通用接口,而对于不同的网络环境,具体的实现方案是不同的。 对于基于流的传输系统如 H.320、MPEG 等,需要按照解码顺序组织 NAL 单元,并为每个 NAL 单元增加 若干比特字节对齐的前缀以形成字节流;对于 RTP/UDP/IP 系统,则可以直接将编码器输出的 NAL 单元 作为 RTP 的有效载荷;而对于同时提供多个逻辑信道的传输系统,甚至可以根据重要性将不同类型的 NAL 单元在不同服务质量的信道中传输。

结论
为了实现编解码器良好的网络适应性,需要做两方面的工作:
第一、在 Codec 中将 NAL 这一技术完整而 有效的实现;
第二、在遵循 H.264/AVC NAL 规范的前提下设计针对不同网络的最佳传输方案。如果实现 了以上两个目标,所实现的就不仅仅是一种视频编解码技术,而是一套适用范围很广的多媒体传输方案, 该方案适用于如视频会议,数据存储,电视广播,流媒体,无线通信,远程监控等多种领域。

NALU 类型
标识 NAL 单元中的 RBSP 数据类型,其中,nal_unit_type 为 1, 2, 3, 4, 5 的 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:序列参数集扩展
14:带前缀的NAL单元
15:子序列参数集
16–18:保留
19:不采用数据划分的辅助编码图像片段
20:编码片段扩展
21–23:保留
24–31:未规定

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 的同步 码,则从 AVI 文件或 MPEG TS PES 包中读出的一个 H.264 视频帧以下面的形式存在:


屏幕快照 2018-04-28 上午11.37.54.png

而对于 MP4 文件,NAL 单元之前没有同步码,却有若干字节的长度码,来表示 NAL 单元的长度,这个长度 码所占用的字节数由 MP4 文件头给出;此外,从 MP4 读出来的视频帧不包含 PPS 和 SPS,这些信息位于 MP4 的文件头中,解析器必须在打开文件的时候就获取它们。从 MP4 文件读出的一个 H.264 帧往往是下面的形式,(假设长度码为 2 字节):


屏幕快照 2018-04-28 上午11.39.17.png

MP4 格式基本概念

MP4 封装格式核心概念
1.MP4封装格式对应标准为 ISO/IEC 14496-12(信息技术 视听对象编码的第12部分: ISO 基本媒体文 件格式/Information technology Coding of audio-visual objects Part 12: ISO base media file format)

2.MP4封装格式是基于QuickTime容器格式定义,媒体描述与媒体数据分开,目前被广泛应用于封装h.264 视频和 ACC 音频,是高清视频/HDV 的代表。

3.MP4文件中所有数据都封装在box中(对应QuickTime中的atom),即MP4文件是由若干个box组成, 每个box有长度和类型,每个box中还可以包含另外的子box(称container box)。
一个 MP4 文件首先会有且只有一个“ftyp”类型的 box,作为 MP4 格式的标志并包含关于文件的一些信 息;之后会有且只有一个“moov”类型的box(Movie Box),它是一种container box,子box包含了媒 体的metadata信息;MP4文件的媒体数据包含在“mdat”类型的box(Midia Data Box)中,该类型的b ox也是container box,可以有多个,也可以没有(当媒体数据全部引用其他文件时),媒体数据的结 构由 metadata 进行描述。

4.MP4中box存储方式为大端模式。一般,标准的box开头会有四个字节的box size。

5.几个名词

【iOS】h264视频解码_第1张图片
屏幕快照 2018-04-28 下午2.03.37.png
读取本地的h264视频
NSInputStream *inputStream = [[NSInputStream alloc] initWithFileAtPath:[[NSBundle mainBundle] pathForResource:@"abc" ofType:@"h264"]];
[inputStream open];


// reads up to length bytes into the supplied buffer, which must be at least of size len. Returns the actual number of bytes read.
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len;

uint8_t*   inputBuffer = malloc(640 * 480 * 3 * 4);
long        inputSize = 0;
long size = [inputStream read:inputBuffer + inputSize maxLength:inputMaxSize - inputSize];
inputBuffer的值
00 00 00 01 67 XX XX XX 00 00 00 01 68 XX XX XX 00 00 00 01 66 xx xx xx 00 00 00 01 65 XXXXXXXXXXXXXXXXXXXXX

h264视频是以 00 00 00 01 作为起始码
第一个 00 00 00 01 67 代表67后面的是SPS,最后一位可能是27、47、67
第二个 00 00 00 01 68 代表68后面的是PPS,最后一位可能是28、48、68
第三个 00 00 00 01 66 代表66后面的是SEI,可能有可能没有,这个我们可以不管
第三个 00 00 00 01 65 代表65后面的是IDR,后面就是真实的视频数据(I frame 或者是B/P frame)

00 00 00 01 67 XX XX XX
    
00 00 00 01 68 XX XX XX
        
00 00 00 01 66 xx xx xx
        
00 00 00 01 65 XXXXXXXXXXXXXXXXXXXXX

我们要把上面的数据截成这样,然后拿00 00 00 01 后的一位 & 0x1F 得到 nalType

0x27 & 0x1F = 0x07
0x47 & 0x1F = 0x07
0x67 & 0x1F = 0x07

0x28 & 0x1F = 0x08
0x48 & 0x1F = 0x08
0x68 & 0x1F = 0x08

0x25 & 0x1F = 0x05
0x45 & 0x1F = 0x05
0x65 & 0x1F = 0x05

下面这就可以判断起始码后面的数据是SPS还是PPS还是IDR frame还是B/P frame,我们要把起始码后面的数据拿到解码时要用到
switch (nalType) {
            case 0x05:
                YJLog(@"Nal type is IDR frame");
                break;
            case 0x07:
                YJLog(@"Nal type is SPS");
                break;
            case 0x08:
                YJLog(@"Nal type is PPS");
                break;
            default:
                YJLog(@"Nal type is B/P frame");
                break;
        }


你可能感兴趣的:(【iOS】h264视频解码)