1. 申明
本文章属于原创,其中参考的代码及文章在结尾处标明,侵删。
2. 目的
本文是为了解析H265中携带的视频分辨率而写的一个demo。
3. 背景知识
本文及文中所涉及代码,均以TS流作为媒介。
3.1 哥伦比亚指数编码无符号数的解码
(1)首先读取当前位置的bit位,记录连续0的个数N
(2)对于连续0的个数为0值为0,对于连续0的个数不为0 跳过第一个非0的bit位
(3)再读取N个数据,此数据以无符号形式解析num
(4)num=num-1+2的N次方
3.2 哥伦比亚指数编码有符号数的解码
(1)按照3.1的讲述先获取此段数据的哥伦比亚指数编码的无符号形式
(2)如果数据为奇数num=(num+1)/2;如果是偶数num=-(num/2)
3.3 解析参数序列集的过程
描述符一列表示此处数据的类型f和u表示的是无符号的数据类型,ue表示的是哥伦比亚指数编码的无符号数据,se表示的是哥伦比亚指数编码的有符号数据。
4. 代码demo
#define H265_SPS_HEAD_FLAG 0x42
uint8_t getbyte(uint32_t *start_bit,
unsigned char *m_data, uint32_t m_len, uint32_t *m_zeros)
{
if (*start_bit >= m_len)
return 0;
BYTE b = m_data[(*start_bit)++];
// to avoid start-code emulation, a byte 0x03 is inserted
// after any 00 00 pair. Discard that here.
if (b == 0)
{
(*m_zeros)++;
if ((*start_bit < m_len) && ((*m_zeros) == 2) && (m_data[*start_bit] == 0x03))
{
(*start_bit)++;
(*m_zeros) = 0;
}
}
else
{
(*m_zeros) = 0;
}
return b;
};
uint32_t getbit(int *m_bits, uint8_t *m_byte, uint32_t *start_bit, unsigned char *m_data,
uint32_t m_len, uint32_t *m_zeros)
{
if (*m_bits == 0)
{
*m_byte = getbyte(start_bit, m_data, m_len, m_zeros);
*m_bits = 8;
}
*m_bits -= 1;
return (*m_byte >> *m_bits) & 0x1;
};
uint32_t getword(int bits, int *m_bits, uint8_t *m_byte, uint32_t *start_bit, unsigned char *m_data,
uint32_t m_len, uint32_t *m_zeros)
{
uint32_t u = 0;
while (bits > 0)
{
u <<= 1;
u |= getbit(m_bits, m_byte, start_bit, m_data, m_len, m_zeros);
bits--;
}
return u;
};
uint32_t getue(int *m_bits, uint8_t *m_byte, uint32_t *start_bit, unsigned char *m_data,
uint32_t m_len, uint32_t *m_zeros)
{
int zeros = 0;
while (*start_bit < m_len && getbit(m_bits, m_byte, start_bit, m_data, m_len, m_zeros) == 0)
zeros++;
return getword(zeros, m_bits, m_byte, start_bit, m_data, m_len, m_zeros) + ((1 << zeros) - 1);
};
/*
根据误差调整分辨率
@param1 width: 原始宽数据的指针
@param2 hight: 原始高数据的指针
@param3 offset_width: 横向误差和
@param4 offset_hight: 纵向误差和
*/
static inline BOOL adjust_resolution_by_offset(uint32_t *width, uint32_t *hight,
uint8_t offset_width, uint8_t offset_hight)
{
//左右存在对齐,分辨率宽度减去二倍的像素
if (offset_width) {
if ((offset_width * 2) <= *width)
*width -= offset_width * 2;
else
return FALSE;
}
//上下存在对齐,分辨率高度减去二倍的像素
if (offset_hight) {
if ((offset_hight * 2) <= *hight)
*hight -= offset_hight * 2;
else
return FALSE;
}
return TRUE;
}
/*
查找H265 SPS数据起始部分
@param1 buf: 原始数据buf的二级指针
@param2 len: 原始数据大小的指针
@param3 type: 要查找的视频类型起始字节:
H264: 0x67
H265: 0x42
*/
static inline BOOL find_sps_first_byte(unsigned char **buf, uint32_t *len, uint8_t type)
{
unsigned char *pos = (unsigned char *)memchr(*buf, type, *len);
if (NULL == pos)
return FALSE;
*len -= pos - *buf;
*buf = pos;
return TRUE;
}
/*
解码H265
@param1 pdata: 原始数据buf的指针
@param2 len: 原始数据的大小
*/
static inline BOOL decode_h265(unsigned char *pdata, uint32_t len)
{
uint32_t start_bit = 0;
int m_bits = 0;
uint8_t m_byte = 0;
uint32_t m_zeros = 0;
if (!find_sps_first_byte(&pdata, &len, H265_SPS_HEAD_FLAG))
return FALSE;
//解码 forbidden_zero_bit
getword(1, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
//解码 nal_unit_type
getword(6, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
//解码 nuh_layer_id
getword(6, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
//解码 nuh_temporal_id_plus1
getword(3, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
//解码 sps_video_parameter_set_id
getword(4, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
//解码 sps_max_sub_layers_minus1
uint32_t sps_max_sub_layers_minus1 = getword(3, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
if (sps_max_sub_layers_minus1 > 6)
return FALSE;
//解码 temporal_id_nesting_flag
getword(1, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
// profile_tier_level( sps_max_sub_layers_minus1 )
{
getword(2, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
getword(1, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
getword(5, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
getword(32, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
getword(1, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
getword(1, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
getword(1, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
getword(1, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
getword(44, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
getword(8, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
unsigned char sub_layer_profile_present_flag[6] = { 0 };
unsigned char sub_layer_level_present_flag[6] = { 0 };
int i;
for (i = 0; i < sps_max_sub_layers_minus1; i++)
{
sub_layer_profile_present_flag[i] = getword(1, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
sub_layer_level_present_flag[i] = getword(1, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
}
if (sps_max_sub_layers_minus1 > 0)
{
for (i = sps_max_sub_layers_minus1; i < 8; i++)
getword(2, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
}
for (i = 0; i < sps_max_sub_layers_minus1; i++)
{
if (sub_layer_profile_present_flag[i])
{
getword(2, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
getword(1, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
getword(5, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
getword(32, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
getword(1, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
getword(1, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
getword(1, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
getword(1, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
getword(44, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
}
if (sub_layer_level_present_flag[i])
getword(8, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
}
}
// "The value of sps_seq_parameter_set_id shall be in the range of 0 to 15, inclusive."
unsigned long sps_seq_parameter_set_id = getue(&m_bits, &m_byte, &start_bit, pdata,
len, &m_zeros);
if (sps_seq_parameter_set_id > 15)
return FALSE;
// "The value of chroma_format_idc shall be in the range of 0 to 3, inclusive."
unsigned long chroma_format_idc = getue(&m_bits, &m_byte, &start_bit, pdata,
len, &m_zeros);
if (sps_seq_parameter_set_id > 3)
return FALSE;
if (chroma_format_idc == 3)
getword(1, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros);
uint32_t width = getue(&m_bits, &m_byte, &start_bit, pdata,
len, &m_zeros); // pic_width_in_luma_samples
uint32_t height = getue(&m_bits, &m_byte, &start_bit, pdata,
len, &m_zeros); // pic_height_in_luma_samples
if (getword(1, &m_bits, &m_byte, &start_bit, pdata, len, &m_zeros))
{// conformance_window_flag
// conf_win_left_offset
uint32_t conf_win_left_offset = getue(&m_bits, &m_byte, &start_bit, pdata,
len, &m_zeros);
// conf_win_right_offset
uint32_t conf_win_right_offset = getue(&m_bits, &m_byte, &start_bit, pdata,
len, &m_zeros);
// conf_win_top_offset
uint32_t conf_win_top_offset = getue(&m_bits, &m_byte, &start_bit, pdata,
len, &m_zeros);
// conf_win_bottom_offset
uint32_t conf_win_bottom_offset = getue(&m_bits, &m_byte, &start_bit, pdata,
len, &m_zeros);
if (!adjust_resolution_by_offset(&width, &height,
conf_win_left_offset + conf_win_right_offset,
conf_win_top_offset + conf_win_bottom_offset)) {
;//不够减
}
}
return TRUE;
}
划重点: 为了解析分辨率,需要一层层进行解码,所以上面代码中,只调用getword(),getue()而未获取返回值的,可以暂时不用关注,有兴趣可以自己了解。需要强调的是,最后对于补齐的计算,如果不进行补齐的核对,会出现640*368这种分辨率,而实际应用中我们的宽高比为16:9, 所以分辨率应该为640:360, 那么这多出来的8,其实就是在传输过程中,必须以宏块(16的倍数)来传输,为了满足这个要求,高度向上进行了补齐,所以在计算真实值的时候,要根据conf_win_left_offset,conf_win_right_offset,conf_win_top_offset,conf_win_bottom_offset进行相应的调整。
5. 专栏知识链接
1. 协议知识概述
2. H264分辨率解码概述
3. 以太网Ethernet解码概述
6. 写在最后
本文引用了以下文章作者的代码或思路,
并结合了实际项目中的代码整理出的demo,如有问题欢迎指正。
https://blog.youkuaiyun.com/chinabinlang/article/details/89209609
https://blog.youkuaiyun.com/chinabinlang/article/details/93640746?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522160742052619724848117078%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=160742052619724848117078&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v29-6-93640746.nonecase&utm_term=H265%20%E8%A7%A3%E7%A0%81&spm=1018.2118.3001.4449