1. 程式人生 > >【視頻編解碼·學習筆記】11. 提取SPS信息程序

【視頻編解碼·學習筆記】11. 提取SPS信息程序

上一個 img http config efi 技術分享 小寫 strong 類型

一、準備工作:

回到之前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。這兩個參數吻合,基本表明程序沒有問題。

【視頻編解碼·學習筆記】11. 提取SPS信息程序