H264码流解析(一):划分每一个NALU

一、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_typeNALU类型说明
0未使用
1非IDR图像中不采用数据划分的slice(SLICE)
2非IDR图像中A类数据划分的slice(SLICE_DPA)
3非IDR图像中B类数据划分的slice(SLICE_DPB)
4非IDR图像中C类数据划分的slice(SLICE_DPC)
5IDR图像的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;
}

 

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值