【视频编解码·学习笔记】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 编码方式的解析,会涉及熵编码知识,将在后续笔记中进行介绍。】

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

时间: 2024-10-07 16:57:33

【视频编解码·学习笔记】6. H.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;学习笔记】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

视频编解码技术详解(H.264、MPEG-4)

一.视频编解码概述 1. 应用场景 视频编码的目的就是压缩视频的占用空间,提高存储和传输的效率,在获得有效的压缩效果的同时,使得压缩过程引起的失真最小.视频压缩算法是通过去除时间.空间的冗余来实现的.通过去除不同类型的冗余,可以明显的压缩数据,代价就是一部分信息失真,可以通过熵编码器(如哈夫曼编码等)进行编码可以获得更高的压缩比.目前主流的图像/视频压缩标准为:JPEG,MPEG,H26X等标准. 2. 主流视频编码标准简介 MPEG-4和H.264(也叫AVC)是目前较为主流的编码标准.每个标

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

一.准备工作: 回到之前SimpleH264Analyzer程序,找到SPS信息,并对其做解析 调整项目目录结构: 修改Global.h文件中代码,添加新数据类型UINT16,之前编写的工程中,UINT8和UINT32都为小写表示,为了更符合编程规范,将其改为全大写(可使用ctrl+H在整个解决方案内进行替换). typedef unsigned char UINT8; typedef unsigned short UINT16; typedef unsigned int UINT32; 之后编

【视频编解码&#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;学习笔记】8. 熵编码算法:基本算法列举 &amp; 指数哥伦布编码

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

H.264码流与帧结构

参考连接:http://blog.csdn.net/dxpqxb/article/details/7631304 H264以NALU(NAL unit)为单位来支持编码数据在基于分组交换技术网络中传输. NALU定义了可用于基于分组和基于比特流系统的基本格式,同时给出头信息,从而提供了视频编码和外部世界的接口. H264编码过程中的三种不同的数据形式: SODB 数据比特串-->最原始的编码数据,即VCL数据: RBSP 原始字节序列载荷-->在SODB的后面填加了结尾比特(RBSP trai

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

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

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

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