• 【视频编解码·学习笔记】6. H.264码流分析工程创建


    一、准备工作:

    新建一个VS工程SimpleH264Analyzer, 修改工程属性参数-> 输出目录:$(SolutionDir)bin$(Configuration),工作目录:$(SolutionDir)bin$(Configuration)

    编译一下工程,工程目录下会生成bin文件夹,其中的debug文件夹中有刚才编译生成的exe文件。将一个.264视频文件拷贝到这个文件夹中(本次使用的仍是学习笔记3中生成的.264文件)。

    将这个文件作为输入参数传到工程中:属性 -> 调试 -> 命令参数:test.264 (最后那个文件名根据自己的改)

    更改目录结构,并新建两个文件Stream.h Stream.cpp,更改后目录结构如下:
    1 目录结构

    Stream.h头文件中,新建一个类CStreamFile,用来表示.264文件,其中包括构造函数、私有成员变量,及自定义函数。代码如下:

    #ifndef _STREAM_H_
    #define _STREAM_H_
    #include <vector>
    
    class CStreamFile
    {
    public:
    	CStreamFile(TCHAR *fileName);
    	~CStreamFile();
    	// Open API
    	int Parse_h264_bitstream();
    
    private:
    	FILE *m_InputFile;
    	TCHAR *m_fileName;
    	std::vector<uint8> m_nalVec;
    	
    	// 用来打印日志
    	void file_info();
    	void file_error(int dex);
    	// 提取NAL有效数据
    	int find_nal_prefix();
    };
    
    #endif
    

    在Stream.cpp文件中,实现其构造方法及成员函数:

    #include "stdafx.h"
    #include "Stream.h"
    #include <iostream>
    using namespace std;
    
    // 构造函数完成打开文件操作
    CStreamFile::CStreamFile(TCHAR * fileName)
    {
    	m_fileName = fileName;
    	file_info();
    	// 打开视频文件(只读二进制)
    	_tfopen_s(&m_InputFile, m_fileName, _T("rb"));
    	if (NULL == m_InputFile)
    	{
    		file_error(0);
    	}
    }
    
    // 析构函数完成关闭文件操作
    CStreamFile::~CStreamFile()
    {
    	if (NULL != m_InputFile)
    	{
    		fclose(m_InputFile);
    		m_InputFile = NULL;
    	}
    }
    
    int CStreamFile::Parse_h264_bitstream()
    {
    	return 0;
    }
    
    int CStreamFile::find_nal_prefix()
    {
    	return 0;
    }
    
    // 打印文件信息
    void CStreamFile::file_info()
    {
    	if (m_fileName)
    	{
    		wcout << L"File name: " << m_fileName << endl;
    	}
    }
    
    // 打印错误信息
    void CStreamFile::file_error(int idx)
    {
    	switch (idx)
    	{
    	case 0:
    		wcout << L"Error: opening input file failed." << endl;
    		break;
    	default:
    		break;
    	}
    }
    

    之后在主函数中,编写打开文件代码,测试以上代码能否正常执行:

    #include "stdafx.h"
    #include "Stream.h"
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	CStreamFile h264stream(argv[1]);
    
    	// 此函数作为最上层函数,执行所有功能(暂时还未写任何功能实现)
    	h264stream.Parse_h264_bitstream();
        return 0;
    }
    

    编译执行后,在cmd窗口中,能够打印出文件名称,即为正确执行。

    接下来,设置一个全局的头文件,用来定义所有文件中都会用到的数据类型。
    Application目录下,新建Global.h头文件,输入以下代码:

    #ifndef _GLOBAL_H_
    #define _GLOBAL_H_
    
    typedef unsigned char  uint8;
    typedef unsigned int   uint32;
    
    #endif // !_GLOBAL_H_
    

    stdafx.h文件中,引入刚才新建的头文件:

    #include "Global.h"
    

    二、提取NAL Unit:

    1. 提取NAL有效数据:

    实现find_nal_prefix()函数。实现方法与学习笔记4中代码基本相同,仅修改一些变量名称。(学习笔记4中有详细讲解,这里不再说明)。Stream.cpp文件中,函数实现如下:

    int CStreamFile::find_nal_prefix()
    {
    	uint8 prefix[3] = { 0 };
    	uint8 fileByte;
    
    
    	m_nalVec.clear();
    
    	// 标记当前文件指针位置
    	int pos = 0;
    	// 标记查找的状态
    	int getPrefix = 0;
    	// 读取三个字节
    	for (int idx = 0; idx < 3; idx++)
    	{
    		prefix[idx] = getc(m_InputFile);
    		// 每次读进来的字节 都放入vector中
    		m_nalVec.push_back(prefix[idx]);
    	}
    
    	while (!feof(m_InputFile))
    	{
    		if ((prefix[pos % 3] == 0) && (prefix[(pos + 1) % 3] == 0) && (prefix[(pos + 2) % 3] == 1))
    		{
    			// 0x 00 00 01 found
    			getPrefix = 1;
    			m_nalVec.pop_back();
    			m_nalVec.pop_back();
    			m_nalVec.pop_back();
    			break;
    		}
    		else if ((prefix[pos % 3] == 0) && (prefix[(pos + 1) % 3] == 0) && (prefix[(pos + 2) % 3] == 0))
    		{
    			if (1 == getc(m_InputFile))
    			{
    				// 0x 00 00 00 01 found
    				getPrefix = 2;
    				m_nalVec.pop_back();
    				m_nalVec.pop_back();
    				m_nalVec.pop_back();
    				break;
    			}
    		}
    		else
    		{
    			fileByte = getc(m_InputFile);
    			prefix[(pos++) % 3] = fileByte;
    			m_nalVec.push_back(fileByte);
    		}
    	}
    
    	return getPrefix;
    }
    

    修改Stream.cpp中Parse_h264_bitstream()函数,循环调用find_nal_prefix()函数,不断获取起始码之间数据。

    int CStreamFile::Parse_h264_bitstream()
    {
    	int ret = 0;
    	do
    	{
    		ret = find_nal_prefix();
    	} while (ret);
    
    	return 0;
    }
    

    对此文件编译、调试,查看以上所写代码是否有问题:
    第一次循环时,文件指针移动到第一个起始码后;第二次循环时,读取到两个起始码间的有效数据,通过调试可看到如下数据,与test.264中第一组有效数据相同:
    2 调试数据

    2. 提取NAL Unit 类别:

    ① 首先提取每一个NAL Unit的类别,修改Parse_h264_bitstream()函数如下:

    int CStreamFile::Parse_h264_bitstream()
    {
    	int ret = 0;
    	do
    	{
    		ret = find_nal_prefix();
    		// 解析NAL UNIT
    		// 第一次执行循环的时候,m_nalVec为空,因此加个判断
    		if (m_nalVec.size())
    		{
    			// 识别NAL Unit类别
    			// NAL Unit第一个字节为NAL Header,后面5位表示NAL Type(使用按位与运算,截取后面五位数据)
    			uint8 nalType = m_nalVec[0] & 0x1F;
    			wcout << L"NAL Unit Type: " << nalType << endl;
    		}
    	} while (ret);
    	return 0;
    }
    

    编译运行后,结果如下:
    3
    其所对应的类型为(可从H.264官方文档,表7-1中查到):
    4

    三、NAL Unit 解封装:

    1. EBSP -> RBSP:

    去除竞争校验位(详细概念看学习笔记5
    简而言之,就是去除两个连零后面的03。00 00 03 xx xx xx (其中的03即为竞争校验位,在拆包的时候需要去除)

    CStreamFile 类中添加私有函数 void ebsp_to_rbsp();
    函数实现如下:

    void CStreamFile::ebsp_to_rbsp()
    {
    	// 00 00 03 连续两个00后面的03是防止竞争校验字节,需要去掉
    	// 在序列中找03,在查看前面两个是不是00,如果是,就去掉03
    	if (m_nalVec.size() < 3)
    	{
    		return;
    	}
    
    	for (vector<uint8>::iterator itor = m_nalVec.begin() + 2; itor != m_nalVec.end(); )
    	{
    		// 迭代器增长幅度为空,写在循环内部,方便删除元素
    		if ((3 == *itor) && (0 == *(itor - 1)) && (0 == *(itor - 2)))
    		{
    			// 此处使用erase()时需要注意:
    			// 1、当调用erase()后Itor迭代器就失效了,变成了一野指针
    			// 2、而erase()这个函数会返回一个指针,仍指向清除元素的位置,只不过后面所有的数据都向前移动
    			itor = m_nalVec.erase(itor);
    		}
    		else
    		{
    			itor++;
    		}
    	}
    
    }
    

    2. RBSP -> SODB:

    这里本应还有RBSP -> SODB的部分,也就是去除 rbsp_trailing_bits ,但对于分析 NAL Body 内部语法元素不会造成实际影响,这部分暂时空缺,有兴趣的可以自己实现一下。





    【对于NAL Body 编码方式的解析,会涉及熵编码知识,将在后续笔记中进行介绍。】

  • 相关阅读:
    csp-s模拟110
    csp-s模拟109
    留念
    csp-s 2019 游记
    HEOI2020
    CSP-S2019记
    堆积的$TIPS$
    低错复习
    倍增并查集
    4.26
  • 原文地址:https://www.cnblogs.com/shuofxz/p/8443392.html
Copyright © 2020-2023  润新知