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

一、准备工作:

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

调整项目目录结构:

修改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;

完成以上配置后编译运行程序,在 \bin\Debug 目录下会生成一个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。这两个参数吻合,基本表明程序没有问题。

原文地址:https://www.cnblogs.com/shuofxz/p/8544799.html

时间: 2024-11-09 21:30:13

【视频编解码·学习笔记】11. 提取SPS信息程序的相关文章

【视频编解码&#183;学习笔记】8. 熵编码算法:基本算法列举 &amp; 指数哥伦布编码

一.H.264中的熵编码基本方法: 熵编码具有消除数据之间统计冗余的功能,在编码端作为最后一道工序,将语法元素写入输出码流 熵解码作为解码过程的第一步,将码流解析出语法元素供后续步骤重建图像使用 在H.264的标准协议中,不同的语法元素指定了不同的熵编码方法.在协议文档中共指定了10种语法元素的描述符,这些描述符表达了码流解析为语法元素值的方法,其中包含了H.264标准所支持的所有熵编码方法: 语法元素描述符 编码方法 b(8) 8位二进制比特位串,用于描述rbsp_byte() f(n) n位

【视频编解码&#183;学习笔记】6. H.264码流分析工程创建

一.准备工作: 新建一个VS工程SimpleH264Analyzer, 修改工程属性参数-> 输出目录:$(SolutionDir)bin\$(Configuration)\,工作目录:$(SolutionDir)bin\$(Configuration)\ 编译一下工程,工程目录下会生成bin文件夹,其中的debug文件夹中有刚才编译生成的exe文件.将一个.264视频文件拷贝到这个文件夹中(本次使用的仍是学习笔记3中生成的.264文件). 将这个文件作为输入参数传到工程中:属性 -> 调试

【视频编解码&#183;学习笔记】3. H.264视频编解码工程JM的下载与编解码

一.下载JM工程: JM是H.264标准制定团队所认可的官方参考软件.网址如下 http://iphome.hhi.de/suehring/tml/ 从页面中可找到相应的工程源码,本次选择JM 8.6版本,此版本为经典版本: http://iphome.hhi.de/suehring/tml/download/old_jm/ 二.配置编码环境: 下载后打开工程目录中tml.sln文件,VS中会有三个工程,其中rtpdump没用,删掉.另外两个ldecod和lencod分别为解码和编码工程. 首先

【视频编解码&#183;学习笔记】4. H.264的码流封装格式 &amp; 提取NAL有效数据

一.码流封装格式简单介绍: H.264的语法元素进行编码后,生成的输出数据都封装为NAL Unit进行传递,多个NAL Unit的数据组合在一起形成总的输出码流.对于不同的应用场景,NAL规定了一种通用的格式适应不同的传输封装类型. 通常NAL Unit的传输格式分两大类:字节流格式和RTP包格式 字节流格式: 大部分编码器的默认输出格式 每个NAL Unit以规定格式的起始码分割 起始码:0x 00 00 00 01 或 0x 00 00 01 RTP数据包格式: NAL Unit按照RTP数

【视频编解码&#183;学习笔记】5. NAL Unit 结构分析

在上一节中通过一个小程序,可以提取NAL Unit所包含的的字节数据.H.264码流中的每一个NAL Unit的作用并不是相同的,而是根据不同的类型起不同的作用.下面将对NAL Unit中的数据进行解析. 一.NAL Unit结构 一个NAL Unit都是由一个NAL Header和一个NAL Body组成.对于基本版本的H.264标准(不考虑SVC和MVC扩展),一个NAL Header的长度固定为1,即8bit.这8bit的含义分别为: forbidden_zero_bit:每一个NAL H

各种音视频编解码学习详解

各种音视频编解码学习详解 媒体业务是网络的主要业务之间.尤其移动互联网业务的兴起,在运营商和应用开发商中,媒体业务份量极重,其中媒体的编解码服务涉及需求分析.应用开发.释放license收费等等.最近因为项目的关系,需要理清媒体的codec,比较搞的是,在豆丁网上看运营商的规范 标准,同一运营商同样的业务在不同文档中不同的要求,而且有些要求就我看来应当是历史的延续,也就是现在已经很少采用了.所以豆丁上看不出所以然,从 wiki上查.中文的wiki信息量有限,很短,而wiki的英文内容内多,删减版

视频编解码学习之四:视频处理及编码标准

1.视频处理 在视频压缩前后,对视频图像质量增强的操作 视频编解码系统输出的图像主观质量不仅与压缩算法的性能有关,还受视频处理的影响 压缩之前对视频的处理称作预处理(Pre-processing) 压缩之后对视频的处理称作后处理(Post-processing) 2. 预处理 预处理的目的 为了减少原图像受到的损害,保持原图像的重要特征,使原图像能被高效的压缩 噪声污染 光照差 抖动 为了进行视频格式转换 去隔行 空间缩放 帧率转换 去噪声处理 去隔行(Deinterlace) 隔行扫描的视频图

视频编解码学习之二:编解码框架

第四章 视频编码基础 1. 压缩码流 语法:码流中各个元素的位置关系 01001001… 图像编码类型(01),宏块类型(00),编码系数1001等 语义:每个语法元素所表达的意义. 例如:图像编码类型 2. 编码层次 序列(Sequence) 图像组(Group of Pictures,GOP) 图像(Picture) 条带(Slice) 宏块(Macroblock,MB) 块(Block) 3. 码流结构 3. PB帧编码 4. 序列编码对象 (1)IBBP序列 序列:一段连续编码的并具有相

视频编解码学习之三:变换,量化与熵编码

第6章 变换编码 1. 变换编码 变换编码的目的 去除空间信号的相关性 将空间信号的能力集中到频域的一小部分低频系数上 能量小的系数可通过量化去除,而不会严重影响重构图像的质量 块变换和全局变换 块变换:离散余弦变换(Discrete Cosine Transform,DCT),4x4,8x8,16x16 全局变换:小波变换(Wavelet) 变换的能量集中特性 DCT编码 2. 变换类型 K-L变换 傅里叶变换 余弦变换 小波变换 3. KL变换 最优变换 基函数根据具体图像而确定 没有快速算