H264编码格式
H.264的功能分为两层,视频编码层VCL和网络提取层NAL。VCL数据是编码处理之后的输出,表示被压缩编码后的视频数据序列。当VCL数据进行传输或者存储时,这些编码的VCL数据,会被映射或封装到NAL单元中。
- VCL,Video Coding Layer,视频编码层,编码后纯视频数据内容信息,没有任何冗余header信息;
- NAL,Network Abstraction Layer,网络提取层,网络传输时,负责以恰当的方式进行打包和发送;
简单来说,视频数据传输过程中,VCL数据会被打包到NAL基本单元中,传输NAL单元数据。
NAL
H.264
的主要目标:
- 高的视频压缩比;
- 良好的网络亲和性,即可适用于各种传输网络;
NAL
,Network Abstraction Layer
,定义如下:
:::info
The Network Abstraction Layer is a part of the H.264/ANC and HEVC video coding standards.(视频编码标准)
The main goal of the NAL is the provision(供应) of a “network-friendly” video representation addressing “conversational(双向的)” (video telephony: 视频电话) and “non conversational” (storage, broadcast, or streaming) applications.
NAL has achieved a significant improvement in application flexibility relative to prior video coding standards.
NAL的主要目标是提供network-friendly网络友好的视频表示,解决双向(视频电话)和单向(非视频电话-存储、广播或视频播放)应用。
NAL相对于之前的视频编码标准在应用灵活性方面取得了显着的提升。
:::
因此,NAL
就是为了打包VCL
数据,在网络上,进行更好的传输。
NAL units
编码后的视频数据被打包到NAL units
中,每个NAL units
就是一个包含整数字节的数据包。每一个NAL unit
的第一个字节是header
,其中包含数据类型信息。NAL unit
的结构定义是面向packet
和bitstream
设计的,具有良好的网络传输性能。
在实际的H264数据帧中,往往帧与帧(NAL unit)
之间前面带有00 00 00 01
或 00 00 01
分隔符,称为StartCode
,是一个NALU
的开始,因此,一个原始的NALU
结构包含三部分:
-
StartCode
-
NALU Header
-
NALU Payload(字节序列负荷,RBSP,Raw Byte Sequence Payload)
NALU Header
NALU Header,就是紧跟着StartCode后面的一个字节,其每个bit的含义如下所示:
-
F:forbiden,禁止位,占用NAL头的第一个位,当禁止位值为1是表示语法错误;
-
NRI:参考级别,占用NAL头的第二和三位,值越大,表示该NAL单元越重要;
-
Type:NAL单元类型,占用NAL头的第四位到第八位,常见NAL单元类型如下:
NAL unit
可以分类为VCL
和non-VCL NAL units
。其中VCL NAL unit
中包含视频图像的数据帧,non-VCL NAL unit
中包含的是附加信息,例如SPS
和PPS
参数集信息(important header data that can apply to a large number of VCL NAL units)
和SEI
补充增强信息(timing information and other supplemental data that may enhance usability of the decoded video signal but are not necessary for decoding the values of the samples in the video pictures)
。 -
IDR帧。IDR帧就是I帧,但是I帧不一定是IDR帧。在一个完整的视频流单元中第一个图像帧是IDR帧,IDR帧是强制刷新帧,在解码过程中,当出现了IDR帧时,要更新sps、pps,原因是防止前面I帧错误,导致sps,pps参考I帧导致无法纠正。
-
SEI,Supplemental Enhancement Information,提供向视频流中加入额外信息的方法;
-
SPS,Sequence Parameter Set,SPS中保存了一组编码视频序列(Coded Video Sequence)的全局参数。因此该类型保存的是和编码序列相关的参数。
-
PPS,Picture Paramater Set,该类型保存了整体图像相关的参数。
-
AU分隔符,AU全称Access Unit,它是一个或者多个NALU的集合,代表了一个完整的帧。
解析H264数据
根据上述H264
的编码格式分析,可以根据startcode
,区分每个NAL unit
,然后分析每个NAL unit
的第一个字节,得到NAL unit
的重要程度、NAL Type
和长度信息。具体代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef enum {
NALU_TYPE_SLICE = 1, // 不分区,非IDR图像
NALU_TYPE_DPA, // 片分区A
NALU_TYPE_DPB, // 片分区B
NALU_TYPE_DPC, // 片分区C
NALU_TYPE_IDR, // IDR图像中的片
NALU_TYPE_SEI, // 补充增强信息单元
NALU_TYPE_SPS, // 序列参数集
NALU_TYPE_PPS, // 图像参数集
NALU_TYPE_AUD, // 分界符
NALU_TYPE_EOSEQ, // 序列结束符
NALU_TYPE_EOSTREAM, // 码流结束符
NALU_TYPE_FILL // 填充
} NaluType;
typedef enum {
NALU_PRIORITY_DISPOSABLE = 0,
NALU_PRIORITY_LOW,
NALU_PRIORITY_HIGH,
NALU_PRIORITY_HIGHEST
} NaluPriority;
typedef struct {
int pos; // NALU对应起始码,在文件中的位置
int codelen; // 起始码大小
int header; // (0) + (priority)2bits + (types)5bits
} Nalu_t;
void fill_typestr(char *type_str, int type) {
switch(type) {
case NALU_TYPE_SLICE:sprintf(type_str,"SLICE");break;
case NALU_TYPE_DPA:sprintf(type_str,"DPA");break;
case NALU_TYPE_DPB:sprintf(type_str,"DPB");break;
case NALU_TYPE_DPC:sprintf(type_str,"DPC");break;
case NALU_TYPE_IDR:sprintf(type_str,"IDR");break;
case NALU_TYPE_SEI:sprintf(type_str,"SEI");break;
case NALU_TYPE_SPS:sprintf(type_str,"SPS");break;
case NALU_TYPE_PPS:sprintf(type_str,"PPS");break;
case NALU_TYPE_AUD:sprintf(type_str,"AUD");break;
case NALU_TYPE_EOSEQ:sprintf(type_str,"EOSEQ");break;
case NALU_TYPE_EOSTREAM:sprintf(type_str,"EOSTREAM");break;
case NALU_TYPE_FILL:sprintf(type_str,"FILL");break;
}
}
void fill_priostr(char *priostr, int prio) {
switch(prio) {
case NALU_PRIORITY_DISPOSABLE:sprintf(priostr,"DISPOS");break;
case NALU_PRIORITY_LOW:sprintf(priostr,"LOW");break;
case NALU_PRIORITY_HIGH:sprintf(priostr,"HIGH");break;
case NALU_PRIORITY_HIGHEST:sprintf(priostr,"HIGHEST");break;
}
}
int startcode_len(unsigned char *buf) {
if (buf[0] == 0 && buf[1] == 0) {
if (buf[2] == 1) return 3;
else if(buf[3] == 1) return 4;
}
return -1;
}
void h264_analyse(const char *file) {
FILE *fp = fopen(file, "r");
unsigned char *buf = malloc(30000000); // 根据视频大小设
Nalu_t *nalu = malloc(sizeof(*nalu));
memset(nalu, 0, sizeof(Nalu_t));
// 获取文件大小
fseek(fp, 0, SEEK_END);
int filesize = ftell(fp);
rewind(fp);
// 先读部分数据,解析第一个NALU
fread(buf, 1, 100, fp);
nalu->pos = 0;
nalu->codelen = startcode_len(&buf[nalu->pos]);
printf("| NUM | CODE | PRIO | TYPE | LEN |\n");
int cnt = 0; // nalu个数
int left = 0, right = 100; // left表示当前搜索位置,right表示当前文件偏移
int found, len;
while (1) {
int headidx = nalu->pos + nalu->codelen; // NALU header
nalu->header = buf[headidx];
// int nForbiddenBit = (nFrameType >> 7) & 0x1;//第1位禁止位,值为1表示语法出错
// int nReference_idc = (nFrameType >> 5) & 0x03;//第2~3位为参考级别
// int nType = nFrameType & 0x1f;//第4~8为是nal单元类型
int type = nalu->header & 0x1F; // NALU Type
char type_str[10] = {
0};
fill_typestr(type_str, type);
// int prio = (nalu->header & 0x60)>>5;
int prio = (nalu->header >> 5) & 0x03;
char prio_str[10] = {
0};
fill_priostr(prio_str, prio);
// 找到下一个起始码
found = 0; len = 0;
left += nalu->codelen + 1; // 跳过startcode和header
while (!found) {
if (left < right) {
if (left + 4 >= filesize) // 防止在函数内数组越界
goto clean;
len = startcode_len(&buf[left++]);
if (len > 0) found = 1;
} else {
if (right + 100 < filesize) {
fread(&buf[right], 1, 100, fp);
right += 100;
} else {
// 剩余的数据不到100B字节
fread(&buf[right], 1, filesize - right, fp);
right = filesize;
}
}
}
int nalulen = left - nalu->pos - nalu->codelen; // NALU大小,不包括起始码
printf("|%5d|%6d|%8s|%9s|%5d|\n", cnt, nalu->codelen, prio_str, type_str, nalulen);
cnt++;
nalu->codelen = len;
nalu->pos = left - 1;
}
clean:
free(nalu);
free(buf);
fclose(fp);
}
int main(int argc, char const* argv[])
{
h264_analyse("/opt/nvidia/deepstream/deepstream-6.1/samples/streams/sample_720p.h264");
return 0;
}
文件解析每个NAL unit的信息如下:
| NUM | CODE | PRIO | TYPE | LEN |
| 0| 4| DISPOS| AUD| 3|
| 1| 4| HIGHEST| SPS| 26|
| 2| 4| HIGHEST| PPS| 6|
| 3| 4| DISPOS| SEI| 704|
| 4| 4| HIGHEST| IDR|47583|
| 5| 4| DISPOS| AUD| 3|
| 6| 4| HIGH| SLICE|25063|
| 7| 4| DISPOS| AUD| 3|
| 8| 4| DISPOS| SLICE| 8783|
| 9| 4| DISPOS| AUD| 3|
| 10| 4| HIGH| SLICE| 3004|
| 11| 4| LOW| |23386|
| 12| 4| DISPOS| AUD| 3|
| 13| 4| DISPOS| SLICE| 7633|
...
H264分析工具Elecard StreamEye Tools
The Elecard StreamEye Tools is a set of powerful tools designed for professionals and prosumers in the video compression field. Elecard StreamEye Tools enables the user to perform an effective in-depth analysis of video sequences.
Elecard StreamEye Tools 是一套功能强大的工具,专为视频压缩领域的专业人士和专业消费者而设计。 Elecard StreamEye Tools帮助用户能够对视频序列进行有效的深入分析。
链接:https://pan.baidu.com/s/1MUy6rs3MuDW6PUGkhDHy6g?pwd=of07 提取码:of07
Elecard StreamEye Tools工具包中包含如下五个应用程序:
· Elecard StreamEye
· Elecard YUV Viewer
· Elecard Video QuEst (Quality Estimator)
· Elecard Stream Analyzer
· Elecard Buffer Analyzer
Elecard Stream Analyzer
Elecard Stream Analyzer is a powerful tool designed for syntax analysis(语法分析) of encoded media streams(编码的媒体数据) and presentation of the analysis log in a human readable form. Stream Analyzer operates with MPEG-1 Video/Audio, MPEG-2 Video/Audio, AAC, AC-3 and AVC/H.264 files(支持的数据类型).
H264
的NAL unit
分析
通过与上面的代码分析的结果对比,可知上面的分析代码是正确的。
H264
文件的HEX
查看器
Elecard StreamEye
The StreamEye provides the user with a visual representation of the encoded video features(编码视频特征的可视化表示) and a stream structure analysis(媒体流的结构分析) of MPEG-1/2/4 or AVC/Н.264 Video Elementary Streams (VES), MPEG-1 System Streams (SS), MPEG-2 Program Streams (PS) and MPEG-2 Transport Streams (TS).
部分功能总览:
bitrate
分析
选择bitrate
的状态,显示相应的bitrate
曲线
-
颜色空间
-
文件信息
-
运动向量
StreamEye工具的功能十分强大,可以根据自己的需求进行相应的分析。