学习目标:
H265编码分析学习内容:
H265出现的原因:
-
我们视频的分辨率 从 720p到1080p,而且电视的屏幕也越来越大
-
视频帧率从30帧 到60帧,再到120帧
这就会导致我们cpu在编解码的时候,会出现宏块个数爆发式增长,运动矢量复杂度增加的后果,直接导致我们编码后的视频文件依旧很大,所以H264编码方式已经不满足现在的需求了,H265也就应运而生。
下面是通过Elecard HEVCAnalyzer 软件分析宏块的效果,可以看到我们的宏块大小在颜色差异大的地方越小,这样我们的画面在界面明显差异的地方就会越清晰。
H265和H264编码数据比较: -
编码后H265 I帧数据大小 比H264 I帧数据大
-
编码H265 P帧、B帧数据大小 比 H264P帧、B帧数据小
所以H265的编码数据小的主要原因在于 P帧、B帧数据很小。
下图是类型的占用字节数。(前面依旧是四个字节的分隔符)
H265-NALU-Type
接下来看一下H265的NALU类型的种类。如下图。
接下来直接来分析H265编码数据中的 NALU_TYPE;
来举个例子,解释怎么计算的吧。
40 01
//根据上面的NAL_unit_header_type
16进制:01000000 000 00001
//第二位包括第二位往后6位都是nal_Type,所以计算40 里的 6位 就行了
01000000 & 0x7E << 1
计算结果:32
//查表得知
这个帧类型是 vps
上面是热身,接下来就进入实战,我们来解析sps里的高度和宽度。
首先我们必须知道那两个字段是高度和宽度,这就要查找官方文档了。
首先我们需要看几个字段的含义,不然看文档的时候会一脸懵
名称 | 含义 |
---|---|
ae(v) | 基于上下文自适应的二元算术编码 |
b(8) | 读进连续的 8 bit |
f(n) | 读进连续的nbit |
se(v) | 有符号指数 Golomb 熵编码 |
u(n) | 读进连续的 n bit,且它们解码后的值为无符号整数 |
e(n) | 无符号指数 Golomb熵编码 |
好的我们了解了这几个字符之后,就可以看懂点文档了,贴sps编码格式
从图中我们可以很容易的发现,宽度和高度在sps编码格式的所处位置和占用字节数,我们只需要按照这张表格的规范,逐个解析即可,但是,我们需要注意,当我们解析的时候遇到 00 00 03的数值时,我们需要跳过03
,接下来,看一个表格(sps模拟数据读取宽高表格分解图)
上图就是我截取的sps数据(一段真实的视频编码),图中灰色的区域,我们解析的时候需要跳过这个字段,不去解析,这个是转义符的意思
。红色字体就是我们的宽度(pic_width_in_luma_samples),而橘黄色字体就是我们的高度(pic_height_in_luma_samples),图中其他不同颜色的是解析的时候比较混淆的地方,我标出来,可以更好的区分。
解析代码如下:
#include <iostream>
#include <memory.h>
#include <bh.h>
typedef signed char int8;
typedef unsigned char uint8;
typedef unsigned short uint16;
typedef unsigned long uint32;
typedef signed char int8;
typedef signed short int16;
typedef signed long int32;
struct vc_params_t
{
long width,height;
int8 profile, level;
int8 nal_length_size;
void clear()
{
memset(this, 0, sizeof(*this));
}
};
class NALBitstream
{
public:
int m_idx;
NALBitstream() : m_data(NULL), m_len(0), m_idx(0), m_bits(0), m_byte(0), m_zeros(0)
{
};
NALBitstream(void * data, int len)
{
Init(data, len);
};
void Init(void * data, int len)
{
m_data = (LPBYTE)data;
m_len = len;
m_idx = 0;
m_bits = 0;
m_byte = 0;
m_zeros = 0;
};
BYTE GetBYTE()
{
if ( m_idx >= m_len )
return 0;
BYTE b = m_data[m_idx++];
if ( b == 0 )
{
m_zeros++;
if ( (m_idx < m_len) && (m_zeros == 2) && (m_data[m_idx] == 0x03) )
{
m_idx++;
m_zeros=0;
}
}
else m_zeros = 0;
return b;
};
UINT32 GetBit()
{
if (m_bits == 0)
{
m_byte = GetBYTE();
m_bits = 8;
}
m_bits--;
return (m_byte >> m_bits) & 0x1;
};
UINT32 GetWord(int bits)
{
UINT32 u = 0;
while ( bits > 0 )
{
u <<= 1;
u |= GetBit();
bits--;
}
printf("end index is :%d\n",m_idx);
return u;
};
//返回哥伦布编码对应的实际值
UINT32 GetUE()
{
int zeros = 0;
while (m_idx < m_len && GetBit() == 0 ) zeros++;
UINT32 value=GetWord(zeros) + ((1 << zeros) - 1);
return value;
};
INT32 GetSE()
{
UINT32 UE = GetUE();
bool positive = UE & 1;
INT32 SE = (UE + 1) >> 1;
if ( !positive )
{
SE = -SE;
}
return SE;
};
private:
LPBYTE m_data;
int m_len;
int m_bits;
BYTE m_byte;
int m_zeros;
};
bool ParseSequenceParameterSet(BYTE* data,int size, vc_params_t& params)
{
if (size < 20)
{
return false;
}
NALBitstream bs(data, size);
// seq_parameter_set_rbsp()
int test =bs.GetWord(4);// sps_video_parameter_set_id
int sps_max_sub_layers_minus1 = bs.GetWord(3);
if (sps_max_sub_layers_minus1 > 6)
{
return false;
}
test =bs.GetWord(1);
{
test =bs.GetWord(2);
test =bs.GetWord(1);
test = params.profile = bs.GetWord(5);
test = bs.GetWord(32);//6
test =bs.GetWord(1);//
test =bs.GetWord(1);//
test = bs.GetWord(1);//
test =bs.GetWord(1);//
test = bs.GetWord(44);//
test= params.level = bs.GetWord(8);// general_level_idc
uint8 sub_layer_profile_present_flag[6] = {0};
uint8 sub_layer_level_present_flag[6] = {0};
for (int i = 0; i < sps_max_sub_layers_minus1; i++) {
sub_layer_profile_present_flag[i]= bs.GetWord(1);
sub_layer_level_present_flag[i]= bs.GetWord(1);
}
if (sps_max_sub_layers_minus1 > 0)
{
for (int i = sps_max_sub_layers_minus1; i < 8; i++) {
uint8 reserved_zero_2bits = bs.GetWord(2);
printf("------");
}
}
for (int i = 0; i < sps_max_sub_layers_minus1; i++)
{
if (sub_layer_profile_present_flag[i]) {
test =bs.GetWord(2);
test = bs.GetWord(1);
test = bs.GetWord(5);
test =bs.GetWord(32);
test = bs.GetWord(1);
test = bs.GetWord(1);
test = bs.GetWord(1);
test = bs.GetWord(1);
test = bs.GetWord(44);
}
if (sub_layer_level_present_flag[i]) {
test = bs.GetWord(8);// sub_layer_level_idc[i]
}
}
}
uint32 sps_seq_parameter_set_id= bs.GetUE();
if (sps_seq_parameter_set_id > 15) {
return false;
}
uint32 chroma_format_idc = bs.GetUE();
if (sps_seq_parameter_set_id > 3) {
return false;
}
if (chroma_format_idc == 3) {
test = bs.GetWord(1);//
}
params.width = bs.GetUE(); // pic_width_in_luma_samples
params.height = bs.GetUE(); // pic_height_in_luma_samples
if (bs.GetWord(1)) {
bs.GetUE();
bs.GetUE();
bs.GetUE();
bs.GetUE();
}
uint32 bit_depth_luma_minus8= bs.GetUE();
uint32 bit_depth_chroma_minus8= bs.GetUE();
if (bit_depth_luma_minus8 != bit_depth_chroma_minus8) {
return false;
}
return true;
}
int main() {
vc_params_t params = {0};
BYTE Sps[41] = {0X42 ,0X01 ,0X01 ,0X01 ,0X60 ,0X00 ,0X00 ,0X03 ,0X00 ,0X90 ,0X00 ,
0X00 ,0X03 ,0X00 ,0X00 ,0X03 ,0X00 ,0X78,0XA0 ,0X04 ,0XB2 ,0X00 ,
0XC8 ,0X59 ,0X65 ,0X66 ,0X92 ,0X4C ,0XAF ,0X01 ,0X6C ,0X08 ,0X00 ,
0X00 ,0X03 ,0X00 ,0X08 ,0X00};
ParseSequenceParameterSet(Sps,41,params);
printf("%d-%d-%d\n",params.width,params.height,params.level);
system("pause");
return 0;
}
好了,大概就这些,欢迎指正交流。