最近学习H264的编解码,因此先学习了解H264的结构。我是通过h264分析开源库的源码进行学习的。首先先从数据结构体入手,通过了解重要的数据结构体来认识H264!
我们首先认识几个概念:
VCL:video code layer(视频编码层) NAL:network abstract layer(网络提取层)
NALU:coded h264 data is stored or transmitted as a series of packets knowns as Network Abstract Layer Unit 网络提取层单元
RBSP:a NALU contains a Raw Byte Sequence Payload(原始字节序列负载,按字节对齐了的)
SODB:String Of Data Bits(原始数据比特流,长度不一定是8的倍数)
SODB + trailing bits(尾部补齐字节数) = RBSP
NAL header(1 byte) + RBSP = NALU
... + StartCode(3 byte) + NALU + StartCode(3 byte) + NALU + ... = h264 stream
再介绍一下NAL header
NAL Header:占用了一个字节,按照比特自高至低排列可以表示如下:
forbidden_bit(1位) | nal_reference_bit(2位) | nal_unit_type(5位) |
forbidden_bit:为禁止位
nal_reference_bit:优先级,00b 表示可丢弃(比如SEI,B slice),非零表示不可丢弃,如SPS、PPS、I Slice、P Slice等
nal_unit_type:此NAL的类型,也就是RBSP的数据到底是什么。
nal_unit_type不同的值,代表不同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:未规定
因此可以发现,SODB的数据不一定都是视频图像数据,也有可能是其他信息(为解码提供相关信息),nal_unit_type取值1-5的NAL,称为VCL的NAL单元;其他类型的NAL,
成为非VCL的NAL单元。
这里的nal_t结构体的成员包括nal的头部信息和一个指向rbsp的指针。
/**
Network Abstraction Layer (NAL) unit
@see 7.3.1 NAL unit syntax
@see read_nal_unit
@see write_nal_unit
@see debug_nal
*/
/*
NAL单元数据结构: NALU header + NALU payload(这里是一个指向该数据的指针,payload的数据定义在h264_stream_t结构体中)
int read_nal_unit(h264_stream_t* h, uint8_t* buf, int size);函数中实现了本数据结构体的填充
*/
typedef struct
{
int forbidden_zero_bit; //禁止位 nalu header(从1byte中读取这三个句法元素)
int nal_ref_idc; //优先级
int nal_unit_type; //nalu类型
void* parsed; // FIXME //指向nal payload nalu payload
int sizeof_parsed; //nal payload数据结构大小
//uint8_t* rbsp_buf;
//int rbsp_size;
} nal_t;
/*
Read a NAL unit from a byte buffer.
The buffer must start exactly at the beginning of the nal (after the start prefix).
The NAL is read into h->nal and into other fields within h depending on its type (check h->nal->nal_unit_type after reading).
@param[in,out] h the stream object
@param[in] buf the buffer
@param[in] size the size of the buffer
@return the length of data actually read, or -1 on error
*/
/*
此函数从一个字节流buf中读取一个NAL单元,并将数据填充到h264_stream_t结构体中
这里需要注意:数据buf必须从其实码后开始,因此,buf的开头应该是:NALU header(1 byte)+NALU payload(rbsp)
h264_stream_t* h: stream结构体
uint8_t* buf: 一个NALU单元的数据内容(不包括开始码)
int size: 数据长度
*/
//7.3.1 NAL unit syntax
int read_nal_unit(h264_stream_t* h, uint8_t* buf, int size)
{
nal_t* nal = h->nal;
bs_t* b = bs_new(buf, size);
nal->forbidden_zero_bit = bs_read_f(b,1); //读取第1个位:禁止位
nal->nal_ref_idc = bs_read_u(b,2); //读取第2,3位:优先级
nal->nal_unit_type = bs_read_u(b,5); //读取第4-8位:NALU类型
nal->parsed = NULL;
nal->sizeof_parsed = 0;
bs_free(b);
int nal_size = size;
int rbsp_size = size;
uint8_t* rbsp_buf = (uint8_t*)malloc(rbsp_size); //申请一个rbsp数据空间,用来存储rbsp
int rc = nal_to_rbsp(buf, &nal_size, rbsp_buf, &rbsp_size); //从一个nal单元读取rbsp
if (rc < 0) { free(rbsp_buf); return -1; } // handle conversion error
b = bs_new(rbsp_buf, rbsp_size);
switch ( nal->nal_unit_type ) //NALU类型判断,采取不同处理
{
case NAL_UNIT_TYPE_CODED_SLICE_IDR:
case NAL_UNIT_TYPE_CODED_SLICE_NON_IDR:
case NAL_UNIT_TYPE_CODED_SLICE_AUX:
read_slice_layer_rbsp(h, b);
nal->parsed = h->sh;
nal->sizeof_parsed = sizeof(slice_header_t);
break;
case NAL_UNIT_TYPE_SEI:
read_sei_rbsp(h, b);
nal->parsed = h->sei;
nal->sizeof_parsed = sizeof(sei_t);
break;
case NAL_UNIT_TYPE_SPS:
read_seq_parameter_set_rbsp(h, b);
nal->parsed = h->sps;
nal->sizeof_parsed = sizeof(sps_t);
break;
case NAL_UNIT_TYPE_PPS:
read_pic_parameter_set_rbsp(h, b);
nal->parsed = h->pps;
nal->sizeof_parsed = sizeof(pps_t);
break;
case NAL_UNIT_TYPE_AUD:
read_access_unit_delimiter_rbsp(h, b);
nal->parsed = h->aud;
nal->sizeof_parsed = sizeof(aud_t);
break;
case NAL_UNIT_TYPE_END_OF_SEQUENCE:
read_end_of_seq_rbsp(h, b);
break;
case NAL_UNIT_TYPE_END_OF_STREAM:
read_end_of_stream_rbsp(h, b);
break;
//case NAL_UNIT_TYPE_FILLER:
//case NAL_UNIT_TYPE_SPS_EXT:
//case NAL_UNIT_TYPE_UNSPECIFIED:
//case NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_A:
//case NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_B:
//case NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_C:
default:
// here comes the reserved/unspecified/ignored stuff
nal->parsed = NULL;
nal->sizeof_parsed = 0;
return 0;
}
if (bs_overrun(b)) { bs_free(b); free(rbsp_buf); return -1; }
bs_free(b);
free(rbsp_buf);
return nal_size;
}
/*
IDR(Instantaneous Decoding Refresh)--即时解码刷新。
I和IDR帧都是使用帧内预测的。
它们都是同一个东西而已,在编码和解码中为了方便,要首个I帧和其他I帧区别开,所以才把第一个首个I帧叫IDR,这样就方便控制编码和解码流程。
IDR帧的作用是立刻刷新,使错误不致传播,从IDR帧开始,重新算一个新的序列开始编码。
而I帧不具有随机访问的能力,这个功能是由IDR承担。
IDR会导致DPB(DecodedPictureBuffer 参考帧列表——这是关键所在)清空,而I不会。
IDR图像一定是I图像,但I图像不一定是IDR图像。
一个序列中可以有很多的I图像,I图像之后的图像可以引用I图像之间的图像做运动参考。
IDR图像是很重要的,一旦丢失怎个序列的所有帧都没有办法解码!
*/
//Table 7-1 NAL unit type codes
#define NAL_UNIT_TYPE_UNSPECIFIED 0 // 未使用
#define NAL_UNIT_TYPE_CODED_SLICE_NON_IDR 1 // 非IDR图像中不采用数据划分的片
#define NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_A 2 // 非IDR图像中A类数据划分的片
#define NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_B 3 // 非IDR图像中B类数据划分的片
#define NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_C 4 // 非IDR图像中C类数据划分的片
#define NAL_UNIT_TYPE_CODED_SLICE_IDR 5 // IDR图像的片
#define NAL_UNIT_TYPE_SEI 6 // 补充增强信息单元
#define NAL_UNIT_TYPE_SPS 7 // 序列参数集
#define NAL_UNIT_TYPE_PPS 8 // 图片参数集
#define NAL_UNIT_TYPE_AUD 9 // 分界符
#define NAL_UNIT_TYPE_END_OF_SEQUENCE 10 // 序列结束
#define NAL_UNIT_TYPE_END_OF_STREAM 11 // 码流结束
#define NAL_UNIT_TYPE_FILLER 12 // 填充数据
#define NAL_UNIT_TYPE_SPS_EXT 13 // Sequence parameter set extension
// 14..18 // Reserved
#define NAL_UNIT_TYPE_CODED_SLICE_AUX 19 // Coded slice of an auxiliary coded picture without partitioning