1. 申明
本文章属于原创,其中参考的代码及文章在结尾处标明,侵删。
2. 目的
本文是为了解析H264中携带的视频分辨率而写的一个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 H264_SPS_HEAD_FLAG 0x67
/*
当满足0x00 0x00 剔除0x03
@param1 buf: 数据指针
@param2 nstart_bit: 计算因子
*/
static inline void kick_out(unsigned char *buf, uint32_t *nstart_bit)
{
int pos = *nstart_bit / 8;
if (buf[pos] == 0x03 && pos >= 2)
{
if (buf[pos -1] == 0x00 && buf[pos - 2] == 0x00)
*nstart_bit += 8;
}
}
/*
无符号指数哥伦布熵编码
@param1 buf: 数据指针
@param2 len: 数据长度
@param3 nstart_bit: 计算因子
*/
static inline uint32_t ue(unsigned char *buf,
uint32_t len, uint32_t *nstart_bit)
{
//计算0bit的个数
kick_out(buf, nstart_bit);
uint32_t n_zero_num = 0;
while (*nstart_bit < len * 8)
{
if (buf[*nstart_bit / 8] & (0x80 >> (*nstart_bit % 8)))
{
break;
}
n_zero_num++;
++(*nstart_bit);
}
++(*nstart_bit);
//计算结果
uint32_t ret = 0, i = 0;
for (; i < n_zero_num; ++i)
{
ret <<= 1;
if (buf[*nstart_bit / 8] & (0x80 >> (*nstart_bit % 8)))
{
ret += 1;
}
++(*nstart_bit);
}
return (1 << n_zero_num) - 1 + ret;
}
/*
有符号指数哥伦布熵编码
@param1 buf: 数据指针
@param2 len: 数据长度
@param3 nstart_bit: 计算因子
*/
static inline int se(unsigned char *buf, uint32_t len, uint32_t *nstart_bit)
{
int ue_val = ue(buf, len, nstart_bit);
//1.调用库函数取不小于本身的最小整数,
// double k = ue_val;
// int n_value = ceil(k / 2);//ceil函数:ceil函数的作用是求不小于给定实数的最小整数。ceil(2)=ceil(1.2)=cei(1.5)=2.00
// if (ue_val % 2 == 0)
// n_value = -n_value;
//2.取余法
int n_value;
if (ue_val % 2 == 0)
n_value = -(ue_val / 2);
else
n_value = (ue_val / 2) + 1;
return n_value;
}
/*
根据bit_count进行解码
@param1 bit_count: 比特位数
@param2 buf: 数据指针
@param3 nstart_bit: 计算因子
*/
static inline uint64_t u(uint32_t bit_count,
unsigned char *buf, uint32_t *nstart_bit)
{
uint32_t ret = 0, i = 0;
kick_out(buf, nstart_bit);
for (; i < bit_count; ++i)
{
ret <<= 1;
if (buf[(*nstart_bit) / 8] & (0x80 >> ((*nstart_bit) % 8)))
{
ret += 1;
}
++(*nstart_bit);
}
return ret;
}
/*
H264的NAL起始码防竞争机制
@param1 buf: 临时buf
@param2 buf_size: 临时buf大小
*/
void de_emulation_prevention(unsigned char *buf, uint32_t *buf_size)
{
uint32_t i = 0, j = 0;
unsigned char *tmp_ptr = NULL;
uint32_t tmp_buf_size = 0;
uint32_t val = 0;
tmp_ptr = buf;
tmp_buf_size = *buf_size;
for (i = 0; i < (tmp_buf_size - 2); ++i)
{
//check for 0x000003
val = (tmp_ptr[i] ^ 0x00) + (tmp_ptr[i+1] ^ 0x00) + (tmp_ptr[i+2] ^ 0x03);
if (val == 0)
{
//kick out 0x03
for (j = i + 2; j < tmp_buf_size - 1; j++)
tmp_ptr[j] = tmp_ptr[j + 1];
//and so we should devrease bufsize
--(*buf_size);
}
}
}
/*
根据误差调整分辨率
@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;
}
/*
解码H264
@param1 pdata: 原始数据buf的指针
@param2 len: 原始数据的大小
*/
static inline BOOL decode_h264(unsigned char *pdata, uint32_t len)
{
uint32_t start_bit = 0;
//查找SPS起始头
if (!find_sps_first_byte(&pdata, &len, H264_SPS_HEAD_FLAG))
return FALSE;
//H264的NAL起始码防竞争机制,该函数会改变原始码流,替换使用kick_out函数
//de_emulation_prevention(pdata, &len);
//解码 forbidden_zero_bit
u(1, pdata, &start_bit);
//解码 nal_ref_idc
u(2, pdata, &start_bit);
//解码 nal_unit_type
int nal_unit_type = u(5, pdata, &start_bit);
if (nal_unit_type == 7)
{
//解码 profile_idc
int profile_idc = u(8, pdata, &start_bit);
//解码 constraint_set0_flag
u(1, pdata, &start_bit);//(pdata[1] & 0x80)>>7;
//解码 constraint_set1_flag
u(1, pdata, &start_bit);//(pdata[1] & 0x40)>>6;
//解码 constraint_set2_flag
u(1, pdata, &start_bit);//(pdata[1] & 0x20)>>5;
//解码 constraint_set3_flag
u(1, pdata, &start_bit);//(pdata[1] & 0x10)>>4;
//解码 reserved_zero_4bits
u(4, pdata, &start_bit);
//解码 level_idc
u(8, pdata, &start_bit);
//解码 seq_parameter_set_id
ue(pdata, len, &start_bit);
if (profile_idc == 100 || profile_idc == 110 ||
profile_idc == 122 || profile_idc == 144 )
{
//解码 chroma_format_idc
int chroma_format_idc = ue(pdata, len, &start_bit);
if ( chroma_format_idc == 3)
u(1, pdata, &start_bit);//解码 residual_colour_transform_flag
//解码 bit_depth_luma_minus8
ue(pdata, len, &start_bit);
//解码 bit_depth_chroma_minus8
ue(pdata, len, &start_bit);
//解码 qpprime_y_zero_transform_bypass_flag
u(1, pdata, &start_bit);
//解码判断 seq_scaling_matrix_present_flag
if (u(1, pdata, &start_bit))
{
int i = 0;
for (; i < 8; ++i)
u(1, pdata, &start_bit);//解码 seq_scaling_list_present_flag
}
}
//解码 log2_max_frame_num_minus4
ue(pdata, len, &start_bit);
//解码 pic_order_cnt_type
int pic_order_cnt_type = ue(pdata, len, &start_bit);
if (pic_order_cnt_type == 0) {
//解码 log2_max_pic_order_cnt_lsb_minus4
ue(pdata, len, &start_bit);
}
else if (pic_order_cnt_type == 1)
{
//解码 delta_pic_order_always_zero_flag
u(1, pdata, &start_bit);
//解码 offset_for_non_ref_pic
se(pdata, len, &start_bit);
//解码 offset_for_top_to_bottom_field
se(pdata, len, &start_bit);
int i = 0;
//解码作为循环控制条件 num_ref_frames_in_pic_order_cnt_cycle
for (; i < ue(pdata, len, &start_bit); ++i)
se(pdata, len, &start_bit);// 解码 offset_for_ref_frame
}
//解码 num_ref_frames
ue(pdata, len, &start_bit);
//解码 gaps_in_frame_num_value_allowed_flag
u(1, pdata, &start_bit);
//解码分辨率宽 pic_width_in_mbs_minus1
uint32_t width = ue(pdata, len, &start_bit);
//解码分辨率高 pic_height_in_map_units_minus1
uint32_t height = ue(pdata, len, &start_bit);
width =(width + 1) * 16;
height =(height + 1) * 16;
//需要判断场分片还是帧分片
uint64_t frame_mbs_only_flag = u(1, pdata, &start_bit);
height *= (2 - frame_mbs_only_flag);
//解码作判断 frame_mbs_only_flag
if(!frame_mbs_only_flag)
u(1, pdata, &start_bit);//解码 mb_adaptive_frame_field_flag
//解码 direct_8x8_inference_flag
u(1,pdata,&start_bit);
//解码做判断 frame_cropping_flag
if(u(1, pdata, &start_bit))
{//为真则出现了对齐现象,需要在宽或高上减掉对齐的像素
//解码 frame_crop_left_offset
uint32_t frame_crop_left_offset = ue(pdata, len, &start_bit);
//解码 frame_crop_right_offset
uint32_t frame_crop_right_offset = ue(pdata, len, &start_bit);
//解码 frame_crop_top_offset
uint32_t frame_crop_top_offset = ue(pdata, len, &start_bit);
//解码 frame_crop_bottom_offset
uint32_t frame_crop_bottom_offset = ue(pdata, len, &start_bit);
//调整分辨率
if (!adjust_resolution_by_offset(&width, &height,
frame_crop_left_offset + frame_crop_right_offset,
frame_crop_top_offset + frame_crop_bottom_offset)) {
;//不够减
}
}
}
return TRUE;
}
划重点: 为了解析分辨率,需要一层层进行解码,所以上面代码中,只调用u(),ue()而未获取返回值的,可以暂时不用关注,有兴趣可以自己了解。需要强调的是,最后对于补齐的计算,如果不进行补齐的核对,会出现640*368这种分辨率,而实际应用中我们的宽高比为16:9, 所以分辨率应该为640:360, 那么这多出来的8,其实就是在传输过程中,必须以宏块(16的倍数)来传输,为了满足这个要求,高度向上进行了补齐,所以在计算真实值的时候,要根据frame_crop_left_offset,frame_crop_right_offset,frame_crop_top_offset,frame_crop_bottom_offset进行相应的调整。
5. 专栏知识链接
1. 协议知识概述
2. H265分辨率解码概述
3. 以太网Ethernet解码概述
6. 写在最后
本文引用了以下文章作者的代码或思路,
并结合了实际项目中的代码整理出的demo,如有问题欢迎指正。
https://www.jianshu.com/p/b26963f2f926
https://blog.csdn.net/wishfly/article/details/75127050
https://blog.csdn.net/jifukui/article/details/90698872