回到以前SimpleH264Analyzer
程序,找到SPS信息,並對其作解析html
調整項目目錄結構: 編程
修改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
目錄下,添加代碼:ui
#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) 中添加:spa
#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的輸出
新建類CSeqParamSet,將生成的CSeqParamSet.h
、CSeqParamSet.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主函數中的代碼複製過來進行測試,能正確輸出解碼結果便可。
將UALUnit中的語法元素,按照協議規定解析爲SPS中各個成員變量的值 在NALUnit.h
和NALUnit.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_mbs,pic_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,即寬十一、高9。這兩個參數吻合,基本代表程序沒有問題。