【视频编解码·学习笔记】6. H.264码流分析工程创建
一、准备工作:
新建一个VS工程SimpleH264Analyzer, 修改工程属性参数-> 输出目录:$(SolutionDir)bin\$(Configuration)\
,工作目录:$(SolutionDir)bin\$(Configuration)\
编译一下工程,工程目录下会生成bin
文件夹,其中的debug文件夹中有刚才编译生成的exe文件。将一个.264视频文件拷贝到这个文件夹中(本次使用的仍是学习笔记3中生成的.264文件)。
将这个文件作为输入参数传到工程中:属性 -> 调试 -> 命令参数:test.264 (最后那个文件名根据自己的改)
更改目录结构,并新建两个文件Stream.h
Stream.cpp
,更改后目录结构如下:
在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. 提取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;
}
编译运行后,结果如下:
其所对应的类型为(可从H.264官方文档,表7-1中查到):
三、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 编码方式的解析,会涉及熵编码知识,将在后续笔记中进行介绍。】
【视频编解码·学习笔记】6. H.264码流分析工程创建的更多相关文章
- 【视频编解码·学习笔记】3. H.264视频编解码工程JM的下载与编解码
一.下载JM工程: JM是H.264标准制定团队所认可的官方参考软件.网址如下 http://iphome.hhi.de/suehring/tml/ 从页面中可找到相应的工程源码,本次选择JM 8.6 ...
- 【视频编解码·学习笔记】5. NAL Unit 结构分析
在上篇笔记中通过一个小程序,可以提取NAL Unit所包含的的字节数据.H.264码流中的每一个NAL Unit的作用并不是相同的,而是根据不同的类型起不同的作用.下面将对NAL Unit中的数据进行 ...
- 【视频编解码·学习笔记】10. 序列参数集(SPS)介绍
一.SPS 相关概念: SPS即 "Sequence Paramater Set",又称作序列参数集. SPS中保存了一组编码视频序列(Coded video sequence)的 ...
- 【视频编解码·学习笔记】11. 提取SPS信息程序
一.准备工作: 回到之前SimpleH264Analyzer程序,找到SPS信息,并对其做解析 调整项目目录结构: 修改Global.h文件中代码,添加新数据类型UINT16,之前编写的工程中,UIN ...
- 【视频编解码·学习笔记】2. H.264简介
一.H.264视频编码标准 H.264视频编码标准是ITU-T与MPEG合作产生的又一巨大成果,自颁布之日起就在业界产生了巨大影响.严格地讲,H.264标准是属于MPEG-4家族的一部分,即MPEG- ...
- 【视频编解码·学习笔记】4. H.264的码流封装格式
一.码流封装格式简单介绍: H.264的语法元素进行编码后,生成的输出数据都封装为NAL Unit进行传递,多个NAL Unit的数据组合在一起形成总的输出码流.对于不同的应用场景,NAL规定了一种通 ...
- 【视频编解码·学习笔记】4. H.264的码流封装格式 & 提取NAL有效数据
一.码流封装格式简单介绍: H.264的语法元素进行编码后,生成的输出数据都封装为NAL Unit进行传递,多个NAL Unit的数据组合在一起形成总的输出码流.对于不同的应用场景,NAL规定了一种通 ...
- 【视频编解码·学习笔记】8. 熵编码算法:基本算法列举 & 指数哥伦布编码
一.H.264中的熵编码基本方法: 熵编码具有消除数据之间统计冗余的功能,在编码端作为最后一道工序,将语法元素写入输出码流 熵解码作为解码过程的第一步,将码流解析出语法元素供后续步骤重建图像使用 在H ...
- 【视频编解码·学习笔记】12. 图像参数集(PPS)介绍
一.PPS相关概念: 除了序列参数集SPS之外,H.264中另一重要的参数集合为图像参数集Picture Paramater Set(PPS). 通常情况下,PPS类似于SPS,在H.264的裸码流中 ...
随机推荐
- linphone-android-客户端APP-工程解读
LinphoneLauncherActivity 是APP的入口组件,在这个组件里,它会启动LinphoneService这个后台服务,然后不断地判断这个后台服务是否已经启动完毕,如果已经启动完毕后, ...
- Effective Java 第三版——24. 优先考虑静态成员类
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- eclipse(Version: Mars.2 Release (4.5.2)) groovy plugin install process.
There are two way to install : First: 1.check your eclipse version:Help-->About Eclipse 2.open He ...
- js动态添加-表格逐行添加、删除、遍历取值
关于js对表格进行逐行添加,今天抽空整理了一下:新建一个html文件(没有编辑器的可以新建一个demo.txt文件,然后改后缀名为demo.html),把下面代码全部贴进去即可.功能包括:表格添加一行 ...
- git gui提交无法获知你的身份 20
刚刚学习,请说的详细一些,谢谢 callct | 浏览 3382 次 我有更好的答案 1条回答 你没有定义你的名字和邮箱.你打开git console/shell, #输入下面两句,并且替换成你的名字 ...
- 关于MacOS升级10.13系统eclipse菜单灰色无法使用解决方案
最近,苹果发布了macOS High Sierra,版本为10.13,专门针对mac pro的用户来着,至于好处大家到苹果官网看便是,我就是一个升级新版本系统的受益者,同时也变成了一个受害者:打开ec ...
- 04 整合IDEA+Maven+SSM框架的高并发的商品秒杀项目之高并发优化
Github:https://github.com/nnngu 项目源代码:https://github.com/nnngu/nguSeckill 关于并发 并发性上不去是因为当多个线程同时访问一行数 ...
- 【fail2ban】使用fail2ban进行攻击防范
使用fail2ban进行攻击防范 转自:https://kyle.ai/blog/6215.html 最近总有一些无聊的人,会来扫描一下我的服务器,看有没有啥漏洞可以利用的... 可以看到类似这样的4 ...
- python3 第十三章 - 数据类型之tuple(元组)
元组与列表类似,不同之处在于元组的元素不能修改. 元组使用小括号,列表使用方括号. 元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可: language = ('c', 'c++', 'py ...
- JavaScript ES6 module 模块
在使用JavaScript开发大型项目时,模块开发概念是一个必须考虑的问题.其目的就是通过命名空间对各类业务对象进行一定的封装,防止命名冲突. 本篇着重介绍ES6 module中的export和imp ...