• 【视频编解码·学习笔记】11. 提取SPS信息程序


    一、准备工作:

    回到之前SimpleH264Analyzer程序,找到SPS信息,并对其做解析

    调整项目目录结构:
    1

    修改Global.h文件中代码,添加新数据类型UINT16,之前编写的工程中,UINT8和UINT32都为小写表示,为了更符合编程规范,将其改为全大写(可使用ctrl+H在整个解决方案内进行替换)。

    typedef unsigned char  UINT8;
    typedef unsigned short UINT16;
    typedef unsigned int   UINT32;
    

    之后编写的程序会有越来越多的输出,如果全部输入到控制台中,会非常杂乱。因此输出变成两种方式,一种在控制台输出,另一种输出到日志文件中。步骤如下:

    1 新建Configuration.h文件,放到1.Application 目录下,添加代码:

    #ifdef _CONFIGURATION_H_
    #define _CONFIGURATION_H_
    
    #include <fstream>
    
    #define TRACE_CONFIG_CONSOLE 1
    #define TRACE_CONFIG_LOGOUT	 1
    
    extern std::ofstream g_traceFile;
    
    #endif
    

    2 新建Configuration.cpp,放到1.Application 目录下,添加代码:

    #include "stdafx.h"
    #include "Configuration.h"
    
    #if TRACE_CONFIG_LOGOUT
    
    std::ofstream g_traceFile;
    
    #endif
    

    3 在stdafx.h中添加引用库:

    #include <string>
    #include "Configuration.h"
    

    4 是否写入日志文件定义在Stream.cpp中的构造函数中:
    CStreamFile::CStreamFile(TCHAR * fileName) 中添加:

    #if TRACE_CONFIG_LOGOUT
    	g_traceFile.open(L"trace.txt");
    	if (!g_traceFile.is_open())
    	{
    		file_error(1);
    	}
    	g_traceFile << "Trace file:" << endl;
    #endif
    

    析构函数CStreamFile::~CStreamFile()中添加:

    #ifdef TRACE_CONFIG_LOGOUT
    	if (g_traceFile.is_open())
    	{
    		g_traceFile.close();
    	}
    #endif
    

    当日志文件打开失败时,调用函数file_error(1),因此修改void CStreamFile::file_error(int idx) 函数,在其中添加错误代码1的方案:

    case 1:
    		wcout << L"Error: opening trace file failed." << endl;
    		break;
    

    完成以上配置后编译运行程序,在 inDebug 目录下会生成一个trace.txt文件,写入了这个字符串“Trace file:”

    为了替换之前在控制台直接输出,在CStreamFile类中新建一个函数,首先在Stream.h文件中声明函数(private)

    void    dump_NAL_type(UINT8 nalType);
    

    Stream.cpp中添加这个函数的实现

    void CStreamFile::dump_NAL_type(UINT8 nalType)
    {
    #if TRACE_CONFIG_CONSOLE
    	wcout << L"NAL Unit Type: " << nalType << endl;
    #endif
    
    #if TRACE_CONFIG_LOGOUT
    	g_traceFile << "NAL Unit Type: " << to_string(nalType) << endl;
    #endif
    }
    

    Parse_h264_bitstream() 函数中 wcout输出改为调用新函数:

    dump_NAL_type(nalType);
    

    重新编译运行,由于此时控制台和日志文件输出开关均打开,因此可在控制台和trace.txt中看到NAL Unit Type的输出

    二、定义SPS类:

    新建类CSeqParamSet,将生成的CSeqParamSet.hCSeqParamSet.cpp放到 “3.NAL Unit” 目录下
    按照上一个笔记中官方文档中提到的编码结构,将所有语法元素一一定义出来,并设置setter函数:
    修改SeqParamSet.h

    #ifndef _SEQ_PARAM_SET_H_
    #define _SEQ_PARAM_SET_H_
    
    class CSeqParamSet
    {
    public:
    	CSeqParamSet();
    	~CSeqParamSet();
    
    	void  Set_profile_level_idc(UINT8 profile, UINT8 level);
    	void  Set_sps_id(UINT8 spsID);
    	void  Set_chroma_format_idc(UINT8 chromaFormatIdc);
    	void  Set_bit_depth(UINT8 bit_depth_luma, UINT8 bit_depth_chroma);
    
    	void  Set_max_frame_num(UINT32 maxFrameNum);
    	void  Set_poc_type(UINT8 pocType);
    	void  Set_max_poc_cnt(UINT32 maxPocCnt);
    	void  Set_max_num_ref_frames(UINT32 maxRefFrames);
    	void  Set_sps_multiple_flags(UINT32 flags);
    	void  Set_pic_reslution_in_mbs(UINT16 widthInMBs, UINT16 heightInMapUnits);
    	void  Set_frame_crop_offset(UINT32 offsets[4]);
    
    private:
    	UINT8  m_profile_idc;
    	UINT8  m_level_idc;
    	UINT8  m_sps_id;
    
    	// for uncommon profile...
    	UINT8  m_chroma_format_idc;
    	bool   m_separate_colour_plane_flag;
    	UINT8  m_bit_depth_luma;
    	UINT8  m_bit_depth_chroma;
    	bool   m_qpprime_y_zero_transform_bypass_flag;
    	bool   m_seq_scaling_matrix_present_flag;
    	// ...for uncommon profile
    
    	UINT32 m_max_frame_num;
    	UINT8  m_poc_type;
    	UINT32 m_max_poc_cnt;
    	UINT32 m_max_num_ref_frames;
    	bool   m_gaps_in_frame_num_value_allowed_flag;
    	UINT16 m_pic_width_in_mbs;
    	UINT16 m_pic_height_in_map_units;
    	UINT16 m_pic_height_in_mbs;	// 图像实际高度 not defined in spec, derived...
    	bool   m_frame_mbs_only_flag;
    	bool   m_mb_adaptive_frame_field_flag;
    	bool   m_direct_8x8_inference_flag;
    	bool   m_frame_cropping_flag;
    	UINT32 m_frame_crop_offset[4];
    	bool   m_vui_parameters_present_flag;
    
    	// UINT32 m_reserved;
    };
    
    #endif
    

    SeqParamSet.cpp文件中实现所有的setter函数,就是一个简单的赋值过程:

    #include "stdafx.h"
    #include "SeqParamSet.h"
    
    CSeqParamSet::CSeqParamSet()
    {
    }
    
    CSeqParamSet::~CSeqParamSet()
    {
    }
    
    void CSeqParamSet::Set_profile_level_idc(UINT8 profile, UINT8 level)
    {
    	m_profile_idc = profile;
    	m_level_idc = level;
    }
    
    void CSeqParamSet::Set_sps_id(UINT8 sps_id)
    {
    	m_sps_id = sps_id;
    }
    
    void CSeqParamSet::Set_chroma_format_idc(UINT8 chromaFormatIdc)
    {
    	m_chroma_format_idc = chromaFormatIdc;
    }
    
    void CSeqParamSet::Set_bit_depth(UINT8 bit_depth_luma, UINT8 bit_depth_chroma)
    {
    	m_bit_depth_luma = bit_depth_luma;
    	m_bit_depth_chroma = bit_depth_chroma;
    }
    
    void CSeqParamSet::Set_max_frame_num(UINT32 maxFrameNum)
    {
    	m_max_frame_num = maxFrameNum;
    }
    
    void CSeqParamSet::Set_poc_type(UINT8 pocType)
    {
    	m_poc_type = pocType;
    }
    
    void CSeqParamSet::Set_max_poc_cnt(UINT32 maxPocCnt)
    {
    	m_max_poc_cnt = maxPocCnt;
    }
    
    void CSeqParamSet::Set_max_num_ref_frames(UINT32 maxRefFrames)
    {
    	m_max_num_ref_frames = maxRefFrames;
    }
    
    void CSeqParamSet::Set_sps_multiple_flags(UINT32 flags)
    {
    	m_separate_colour_plane_flag = flags & (1 << 21);
    	m_qpprime_y_zero_transform_bypass_flag = flags & (1 << 20);
    	m_seq_scaling_matrix_present_flag = flags & (1 << 19);
    
    	m_gaps_in_frame_num_value_allowed_flag = flags & (1 << 5);
    	m_frame_mbs_only_flag = flags & (1 << 4);
    	m_mb_adaptive_frame_field_flag = flags & (1 << 3);
    	m_direct_8x8_inference_flag = flags & (1 << 2);
    	m_frame_cropping_flag = flags & (1 << 1);
    	m_vui_parameters_present_flag = flags & 1;
    }
    
    void CSeqParamSet::Set_pic_reslution_in_mbs(UINT16 widthInMBs, UINT16 heightInMapUnits)
    {
    	m_pic_width_in_mbs = widthInMBs;
    	m_pic_height_in_map_units = heightInMapUnits;
    	m_pic_height_in_mbs = m_frame_mbs_only_flag ? m_pic_height_in_map_units : 2 * m_pic_height_in_map_units;
    }
    
    void CSeqParamSet::Set_frame_crop_offset(UINT32 offsets[4])
    {
    	for (int idx = 0; idx < 4; idx++)
    	{
    		m_frame_crop_offset[idx] = offsets[idx];
    	}
    }
    

    三、无符号指数哥伦布数据解码:

    学习笔记9中实现的无符号指数哥伦布解码部分完全相同,仅将代码放在下面(笔记9中有详细解释):
    0.Global目录下,新建Utils.h,定义指数哥伦布编码中两个必要的函数:

    #ifndef _UTILS_H_
    #define _UTILS_H_
    #include "Global.h"
    
    int Get_bit_at_position(UINT8 *buf, UINT8 &bytePosition, UINT8 &bitPosition);
    int Get_uev_code_num(UINT8 *buf, UINT8 &bytePosition, UINT8 &bitPosition);
    
    #endif
    

    0.Global目录下,新建Utils.cpp,实现上面两个函数:

    #include "stdafx.h"
    #include "Utils.h"
    
    // 根据bytePosition和bitPosition 获取当前比特位二进制数值  返回0/1
    int Get_bit_at_position(UINT8 * buf, UINT8 & bytePosition, UINT8 & bitPosition)
    {
    	UINT8 mask = 0, val = 0;
    
    	mask = 1 << (7 - bitPosition);
    	val = ((buf[bytePosition] & mask) != 0);
    	if (++bitPosition > 7)
    	{
    		bytePosition++;
    		bitPosition = 0;
    	}
    
    	return val;
    }
    
    // 将接下来一个指数哥伦布编码 转换成十进制数值
    int Get_uev_code_num(UINT8 * buf, UINT8 & bytePosition, UINT8 & bitPosition)
    {
    	assert(bitPosition < 8);
    	UINT8 val = 0, prefixZeroCount = 0;
    	int prefix = 0, surfix = 0;
    
    	while (true)
    	{
    		val = Get_bit_at_position(buf, bytePosition, bitPosition);
    		if (val == 0)
    		{
    			prefixZeroCount++;
    		}
    		else
    		{
    			break;
    		}
    	}
    	prefix = (1 << prefixZeroCount) - 1;
    	for (size_t i = 0; i < prefixZeroCount; i++)
    	{
    		val = Get_bit_at_position(buf, bytePosition, bitPosition);
    		surfix += val * (1 << (prefixZeroCount - i - 1));
    	}
    
    	prefix += surfix;
    
    	return prefix;
    }
    

    可将学习笔记9主函数中的代码复制过来进行测试,能正确输出解码结果即可。

    四、解析NALUnit中SPS数据:

    将UALUnit中的语法元素,按照协议规定解析为SPS中各个成员变量的值
    NALUnit.hNALUnit.cpp中添加函数,**Parse_as_seq_param_set() **用于解析语法元素,代码如下。(均按照学习笔记10中官方文档顺序解析即可)

    int CNalUnit::Parse_as_seq_param_set(CSeqParamSet * sps)
    {
    	UINT8  profile_idc = 0;
    	UINT8  level_idc = 0;
    	UINT8  sps_id = 0;
    
    	UINT8  chroma_format_idc = 0;
    	bool   separate_colour_plane_flag = 0;
    	UINT8  bit_depth_luma = 0;
    	UINT8  bit_depth_chroma = 0;
    	bool   qpprime_y_zero_transform_bypass_flag = 0;
    	bool   seq_scaling_matrix_present_flag = 0;
    
    	UINT32 max_frame_num = 0;
    	UINT8  poc_type = 0;
    	UINT32 max_poc_cnt = 0;
    	UINT32 max_num_ref_frames = 0;
    	bool   gaps_in_frame_num_value_allowed_flag = 0;
    	UINT16 pic_width_in_mbs = 0;
    	UINT16 pic_height_in_map_units = 0;
    	UINT16 pic_height_in_mbs = 0;	// 图像实际高度 not defined in spec, derived...
    	bool   frame_mbs_only_flag = 0;
    	bool   mb_adaptive_frame_field_flag = 0;
    	bool   direct_8x8_inference_flag = 0;
    	bool   frame_cropping_flag = 0;
    	UINT32 frame_crop_offset[4] = { 0 };
    	bool   vui_parameters_present_flag = 0;
    
    	UINT8 bytePosition = 3, bitPosition = 0;
    	UINT32 flags = 0;	//会检索到各种flag元素,每个元素占一个比特,最终按先后顺序放到flags中
    
    	profile_idc = m_pSODB[0];
    	// 第二个字节是constraint_set_flag 暂时用不到,空过去m_pSODB[1]
    	level_idc = m_pSODB[2];
    	sps_id = Get_uev_code_num(m_pSODB, bytePosition, bitPosition);	//这里是一个无符号指数哥伦布编码,用前面写好的函数提取
    
    	if (profile_idc == 100 || profile_idc == 110 || profile_idc == 122 || profile_idc == 244 || profile_idc == 44 ||
    		profile_idc == 83 || profile_idc == 86 || profile_idc == 118 || profile_idc == 128)
    	{
    		chroma_format_idc = Get_uev_code_num(m_pSODB, bytePosition, bitPosition);
    		if (chroma_format_idc == 3)
    		{
    			separate_colour_plane_flag = Get_bit_at_position(m_pSODB, bytePosition, bitPosition);
    			// 提取到的单个flag,放到flag集合中的(可用的最高位上)
    			flags |= (separate_colour_plane_flag << 21);
    		}
    		bit_depth_luma = Get_uev_code_num(m_pSODB, bytePosition, bitPosition) + 8;
    		bit_depth_chroma = Get_uev_code_num(m_pSODB, bytePosition, bitPosition) + 8;
    
    		qpprime_y_zero_transform_bypass_flag = Get_bit_at_position(m_pSODB, bytePosition, bitPosition);
    		flags |= (qpprime_y_zero_transform_bypass_flag << 20);
    
    		seq_scaling_matrix_present_flag = Get_bit_at_position(m_pSODB, bytePosition, bitPosition);
    		flags |= (seq_scaling_matrix_present_flag << 19);
    		if (seq_scaling_matrix_present_flag)
    		{
    			// 这个部分暂时用不到,先返回一个错误码代替
    			return -1;
    		}
    	}
    
    	// 下面不求log2_max_frame_num,而是直接将原来的数字求出来
    	max_frame_num = 1 << (Get_uev_code_num(m_pSODB, bytePosition, bitPosition) + 4);
    	poc_type = Get_uev_code_num(m_pSODB, bytePosition, bitPosition);
    	if (0 == poc_type)
    	{
    		max_poc_cnt = 1 << (Get_uev_code_num(m_pSODB, bytePosition, bitPosition) + 4);
    	}
    	else
    	{
    		// 暂时不考虑这种情况
    		return -1;
    	}
    
    	max_num_ref_frames = Get_uev_code_num(m_pSODB, bytePosition, bitPosition);
    	gaps_in_frame_num_value_allowed_flag = Get_bit_at_position(m_pSODB, bytePosition, bitPosition);
    	flags |= (gaps_in_frame_num_value_allowed_flag << 5);	//中间跳过了好多位,为本该有却没实现的flag留出位置
    
    	pic_width_in_mbs = Get_uev_code_num(m_pSODB, bytePosition, bitPosition) + 1;
    	pic_height_in_map_units = Get_uev_code_num(m_pSODB, bytePosition, bitPosition) + 1;
    	frame_mbs_only_flag = Get_bit_at_position(m_pSODB, bytePosition, bitPosition);
    	flags |= (frame_mbs_only_flag << 4);
    	if (!frame_mbs_only_flag)
    	{
    		mb_adaptive_frame_field_flag = Get_bit_at_position(m_pSODB, bytePosition, bitPosition);
    		flags |= (mb_adaptive_frame_field_flag << 3);
    	}
    
    	direct_8x8_inference_flag = Get_bit_at_position(m_pSODB, bytePosition, bitPosition);
    	flags |= (direct_8x8_inference_flag << 2);
    	frame_cropping_flag = Get_bit_at_position(m_pSODB, bytePosition, bitPosition);
    	flags |= (direct_8x8_inference_flag << 1);
    	if (frame_cropping_flag)
    	{
    		for (int idx = 0; idx < 4; idx++)
    		{
    			frame_crop_offset[idx] = Get_uev_code_num(m_pSODB, bytePosition, bitPosition);
    		}
    	}
    	vui_parameters_present_flag = Get_bit_at_position(m_pSODB, bytePosition, bitPosition);
    	flags |= vui_parameters_present_flag;
    	// 解析码流完成
    
    	sps->Set_profile_level_idc(profile_idc, level_idc);
    	sps->Set_sps_id(sps_id);
    	sps->Set_chroma_format_idc(chroma_format_idc);
    	sps->Set_bit_depth(bit_depth_luma, bit_depth_chroma);
    	sps->Set_max_frame_num(max_frame_num);
    	sps->Set_poc_type(poc_type);
    	sps->Set_max_poc_cnt(max_poc_cnt);
    	sps->Set_max_num_ref_frames(max_num_ref_frames);
    	sps->Set_sps_multiple_flags(flags);
    	sps->Set_pic_reslution_in_mbs(pic_width_in_mbs, pic_height_in_map_units);
    	if (frame_cropping_flag)
    	{
    		sps->Set_frame_crop_offset(frame_crop_offset);
    	}
    	return 0;
    }
    

    五、添加调用部分:

    回到Stream.cpp中,找到Parse_h264_bitstream() 函数,在学习笔记6中,已经完成了nalType的提取,并得到了SODB数据,在后面添加解析序列参数集sps的部分。

    CNalUnit nalUint(&m_nalVec[1], m_nalVec.size() - 1);
    switch (nalType)
    {
        case 7:
            // 解析SPS NAL 数据
            if (m_sps)
            {
                delete m_sps;
            }
            m_sps = new CSeqParamSet;
            nalUint.Parse_as_seq_param_set(m_sps);
            break;
        default:
            break;
    }
    

    可对其进行单步调试,重点看这两个参数 pic_width_in_mbspic_height_in_map_units,分别是以宏块为单位的宽、高分辨率。本次调试使用的视频仍是学习笔记3使用的视频,之前设置的参数为:

    SourceWidth           = 176    # Image width in Pels, must be multiple of 16
    SourceHeight          = 144    # Image height in Pels, must be multiple of 16
    

    宏块分辨率要在原来基础上除16,即宽11、高9。这两个参数吻合,基本表明程序没有问题。

  • 相关阅读:
    [C#]LDAP验证用户名和密码
    如何为 Go 设计一个通用的日志包
    使用 Go 的 struct tag 来解析版本号字符串
    Referrer Policy 介绍
    《计算机操作系统》MOOC笔记1-计算机系统概论
    C语言的Bit fields
    【转】gcc编译优化---likely()与unlikely()函数的意义
    三向切分的快速排序
    Codeforces Round #334 (Div. 2) C. Alternative Thinking
    基于相邻元素交换的排序算法的下界
  • 原文地址:https://www.cnblogs.com/shuofxz/p/8544799.html
Copyright © 2020-2023  润新知