一、H264码流可以分为两层
1、VCL(Video Coding Layer):视频编码层,处理编码数据的输出,表示被压缩编码后的视频数据系列
2、NAL(Network Abstraction Layer):网络提取层,在VCL层数据存储或者传输之前,这些编码后的数据要被封装在NAL单元里面,所以我们一般接触的编码过后的数据都是NAL层的数据
二、H264编码过程中的三种不同的数据形式
1、SODB(String ofData Bits)数据比特串,最原始的编码过后的数据,也就是VLC层的数据
2、RBSP(Raw ByteSequence Payload)原始字节序列载荷,在SODB后面加上结尾比特(RBSP trailing bits),一个比特1,若干个比特0,以便于字节对齐
3、EBSP(EncapsulationByte Sequence Packets)扩展字节序列载荷,在RBSP里面加上仿校验字节(0x03),原因是因为在NALU添加到Annexb的时候,要在每个NALU的前面加上StartCode,如果NALU对应的slice是一帧的开始或者是SPS,PPS,则使用4字节表示(0x00000001),否则用3字节表示(0x000001),解码器检测码流中的每一个起始码,作为一个NALU的起始标识,当检测到下一个起始码的时候,当前NALU结束,那么对于如果当前NALU数据中出现0x000001或者0x00000001的起始码的时候,为了防止和起始码之间起冲突,如果遇到连续的两个0x00字节,则在该两个字节后面添加一个0x03字节。在解码的时候将该0x03字节去掉,也称为脱壳操作。
三、H264码流的打包方式
1、Annexb:每个帧的开头是StartCode
2、AVCC:就是开始的若干字节(1,2,4字节)是NAL的长度,而不是StartCode,此时必须借助某个全局的数据来获得编码器的profile、level、PPS、SPS等信息才可以解码,一般是视频播放文件格式,跳转方便快速
四、NALU介绍
NALU,网络传输单元,也是Annexb格式码流的组成部分,每一个NALU都是独立解码的,每一个NALU由一字节的NALU Header和若干字节的EBSP数据组成,所以NALU = NALU Header + EBSP。
NALU Header的结构(1个字节8bit)
forbidden_bit(1bit)+ nal_reference_bit(2bit) + nal_unit_type(5bit)
1、forbidden_bit:禁止位,初始为0,当网络发现NAL单元有比特错误时可设置该比特为1,以便接收方纠错或丢掉该单元
2、nal_reference_bit:nal重要性指示,标志该NAL单元的重要性,值越大,越重要
3、nal_unit_type:表示NALU的类型,计算方法 header & 0x1f = nal_unit_type
五、NALU类型介绍
nal_unit_type | NALU类型说明 |
0 | 未使用 |
1 | 非IDR图像中不采用数据划分的slice(SLICE) |
2 | 非IDR图像中A类数据划分的slice(SLICE_DPA) |
3 | 非IDR图像中B类数据划分的slice(SLICE_DPB) |
4 | 非IDR图像中C类数据划分的slice(SLICE_DPC) |
5 | IDR图像的slice(SLICE_IDR) |
6 | 补充增强信息(SEI) |
7 | 序列参数集(SPS) |
8 | 图像参数集(PPS) |
9 | 分隔符 |
10 | 序列结束符 |
11 | 流结束符 |
12 | 填充数据 |
13-23 | 保留 |
24-31 | 未规定 |
六、C++代码解析h264码流,输入内容:h264文件
#include <cstdio>
#include <cstdint>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <string>
#include <fstream>
const uint32_t kBufferSize = 200000;
std::ifstream fin;
struct NALU {
int start_code_len;
uint32_t len;
int forbidden_bit;
int nal_reference_idc;
int nal_unit_type;
char* data;
};
enum NaluType{
NALU_TYPE_SLICE = 1,
NALU_TYPE_DPA = 2,
NALU_TYPE_DPB = 3,
NALU_TYPE_DPC = 4,
NALU_TYPE_IDR = 5,
NALU_TYPE_SEI = 6,
NALU_TYPE_SPS = 7,
NALU_TYPE_PPS = 8,
NALU_TYPE_AUD = 9,
NALU_TYPE_EOSEQ = 10,
NALU_TYPE_EOSTREAM = 11,
NALU_TYPE_FILL = 12,
};
bool IsStartCode(char* buffer) {
// std::cout << buffer[0] << std::endl;
if (buffer[0] == 0x00 && buffer[1] == 0x00 && buffer[2] == 0x00 && buffer[3] == 0x01) {
return true;
}
if (buffer[0] == 0x00 && buffer[1] == 0x00 && buffer[2] == 0x01) {
return true;
}
return false;
}
int GetNALDara(NALU* nalu) {
char start_code[4] = "";
char* buffer = new char[kBufferSize];
fin.read(start_code, 3);
if (fin.gcount() != 3) {
std::cout << "h264 stream error" << std::endl;
delete buffer;
return 0;
}
if (!IsStartCode(start_code)) {
fin.read(start_code + 3, 1);
if (!IsStartCode(start_code)) {
std::cout << "not found start code" << std::endl;
delete buffer;
return 0;
}
nalu->start_code_len = 4;
}
else {
nalu->start_code_len = 3;
}
int pos = 0;
// 找到start code
int rewind = 0;
while (true) {
// 读取数据,每次读取一个字节
if (fin.eof()) {
// 表示是最后一个数据了
nalu->len = pos;
memcpy(nalu->data, buffer, nalu->len);//
nalu->forbidden_bit = nalu->data[0] & 0x80; //1 bit
nalu->nal_reference_idc = nalu->data[0] & 0x60; // 2 bit
nalu->nal_unit_type = (nalu->data[0]) & 0x1f;// 5 bit
delete buffer;
return pos + nalu->start_code_len;
}
fin.read(buffer+pos, 1);
pos++;
if (buffer[pos-1] != 0x01) {
continue;
}
// std::cout << "0x01" << std::endl;
if (IsStartCode(buffer + pos- 4)) {
// 读取结束了
rewind = -4;
break;
}
if (IsStartCode(buffer + pos - 3)) {
rewind = -3;
break;
}
}
// std::cout << "rewind:" << rewind << std::endl;
// 回退
fin.seekg(rewind,std::ios::cur);
nalu->len = pos + rewind;
memcpy(nalu->data, buffer, nalu->len);//
nalu->forbidden_bit = nalu->data[0] & 0x80; //1 bit
nalu->nal_reference_idc = nalu->data[0] & 0x60; // 2 bit
nalu->nal_unit_type = (nalu->data[0]) & 0x1f;// 5 bit
delete buffer;
return pos + rewind + nalu->start_code_len;
}
void ParseH264Stream(const std::string& filename) {
fin.open(filename, std::ios::binary | std::ios::in);
if (!fin) {
std::cout << "open file failed" << std::endl;
return;
}
// 定义一个NALU
NALU nalu;
nalu.data = new char[kBufferSize];
int data_offset = 0;
int nal_num = 0;
std::cout << "-----+-------- NALU Table ------+---------+" << std::endl;
std::cout << " NUM | POS | IDC | TYPE | LEN |" << std::endl;
std::cout << "-----+---------+--------+-------+---------+" << std::endl;
// 读取数据
while (!fin.eof()) {
int len = GetNALDara(&nalu); // len = start_code + nalu_len
if (len < 1) {
std::cout << "get NALU data error" << std::endl;
break;
}
std::string type = "";
switch (nalu.nal_unit_type) {
case NALU_TYPE_SLICE:
type = "SLICE";
break;
case NALU_TYPE_DPA:
type = "DPA";
break;
case NALU_TYPE_DPB:
type = "DPB";
break;
case NALU_TYPE_DPC:
type = "DPC";
break;
case NALU_TYPE_IDR:
type = "IDR";
break;
case NALU_TYPE_SEI:
type = "SEI";
break;
case NALU_TYPE_SPS:
type = "SPS";
break;
case NALU_TYPE_PPS:
type = "PPS";
break;
case NALU_TYPE_AUD:
type = "AUD";
break;
case NALU_TYPE_EOSEQ:
type = "EOSEQ";
break;
case NALU_TYPE_EOSTREAM:
type = "EOSTREAM";
break;
case NALU_TYPE_FILL:
type = "FILL";
break;
default:
break;
}
std::cout << std::setw(5) << nal_num++ << "|" << std::setw(9) << data_offset << "|"
<< std::setw(8) << (nalu.nal_reference_idc >> 5) << "|"
<< std::setw(7) << type << "|" << std::setw(9)
<< len-nalu.start_code_len << "|" << std::endl;
data_offset = data_offset + len;
}
if (nalu.data) {
delete nalu.data;
}
fin.close();
}
int main(void) {
std::string filename = "test.h264";
ParseH264Stream(filename);
getchar();
return 0;
}