博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【视频编解码·学习笔记】4. H.264的码流封装格式 & 提取NAL有效数据
阅读量:6627 次
发布时间:2019-06-25

本文共 5359 字,大约阅读时间需要 17 分钟。

一、码流封装格式简单介绍:

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数据包的格式封装
  • 使用RTP包格式不需要额外的分割识别码,在RTP包的封装信息中有相应的数据长度信息。
  • 可以在NAL Unit的起始位置用一个固定长度的长度码表示整个NAL Unit的长度

实际应用中字节流格式更为常用,下面的均以字节流格式来介绍。

通过查阅,了解NAL字节流格式(在附录B)

1

有用数据前面会加 0x 00 00 00 01 或 0x 00 00 01,作为起始码,两个起始码中间包含的即为有用数据流

如: 00 00 00 01 43 23 56 78 32 1A 59 2D 78 00 00 00 01 C3 E2 …… 中,红色的部分即为有效数据。

本次使用上一篇笔记中生成的test.264作为例子。

使用Ultra Edit打开此文件,可以看到该文件的数据流:
2
接下来将写一个小程序,从二进制码流文件中截取实际的NAL数据。

二、C++程序 从码流中提取NAL有效数据:

新建一个VS工程,配置工程属性。将【常规-输出目录】和【调试-工作目录】改为$(SolutionDir)bin\$(Configuration)\,【调试-命令参数】改为test.264编译、运行程序。

3
在 bin\debug 目录下可看到生成的exe执行文件

接下来编写程序的功能:

提取起始码之间的有效数据

程序思路:

从码流中寻找 00 00 00 01 或 00 00 01序列,后面就是有效数据流,将之后的数据保存起来,直到遇到下一个(00) 00 00 01 停止。

下面开始编写程序:

① 打开码流文件

使用下面的代码测试,比较简单,不再解释,最后记得要把文件流关掉。

int _tmain(int argc, _TCHAR* argv[]){    FILE *pFile_in = NULL;    // 打开刚才导入的二进制码流文件    _tfopen_s(&pFile_in, argv[1], _T("rb"));        // 判断文件是否打开成功    if (!pFile_in)    {        printf("Error: Open File failed. \n");    }    fclose(pFile_in);    return 0;}

② 寻找起始码

  • 使用数据类型unsigned char数据类型来存储单个字节码
  • 为了减少内存使用,使用数组 refix,存储连续的三个字节码
  • 数组循环使用,新进来的数据放在弹出那位数据的位置上
  • 即:数组的存数顺序为 [0][1][2],下一个字符放在[0]的位置上,此时数据顺序为[1][2][0],再下一次[2][0][1]以此类推
  • 由于起始码有两种格式00 00 01 和 00 00 00 01,因此需要有两个判断分别对应

代码如下:

typedef unsigned char uint8;static int find_nal_prefix(FILE **pFileIn){    FILE *pFile = *pFileIn;    // 00 00 00 01 x x x x x 00 00 00 01    // 以下方法为了减少内存,及向回移动文件指针的操作    uint8 prefix[3] = { 0 };    /*    依次比较 [0][1][2] = {0 0 0};  若不是,将下一个字符放到[0]的位置 -> [1][2][0] = {0 0 0} ; 下次放到[1]的位置,以此类推    找到三个连0之后,还需判断下一个字符是否为1, getc() = 1  -> 00 00 00 01    以及判断 [0][1][2] = {0 0 1} -> [1][2][0] = {0 0 1} 等,若出现这种序列则表示找到文件头    */    // 标记当前文件指针位置    int pos = 0;    // 标记查找的状态    int getPrefix = 0;    // 读取三个字节    for (int idx = 0; idx < 3; idx++)    {        prefix[idx] = getc(pFile);    }    while (!feof(pFile))    {        if ((prefix[pos % 3] == 0) && (prefix[(pos + 1) % 3] == 0) && (prefix[(pos + 2) % 3] == 1))        {            // 0x 00 00 01 found            getPrefix = 1;            break;        }        else if((prefix[pos % 3] == 0) && (prefix[(pos + 1) % 3] == 0) && (prefix[(pos + 2) % 3] == 0))        {            if (1 == getc(pFile))            {                // 0x 00 00 00 01 found                getPrefix = 2;                break;            }        }        else        {            fileByte = getc(pFile);            prefix[(pos++) % 3] = fileByte;        }    }    return getPrefix;}

③ 提取有效数据

  • 使用容器vector 存储有效数据
  • 函数find_nal_prefix() 添加参数 vector &nalBytes
  • 每次读取的数据都直接push到nalBytes中,若遇到起始码再把起始码pop掉
  • 本函数需要重复执行,第一次文件指针移动到有效数据起始位置;第二次提取两段起始码间的有效数据;第三次在移动到下一个起始码后;第四次提取有效数据... 以此类推。

函数调整为:

static int find_nal_prefix(FILE **pFileIn, vector
&nalBytes){ FILE *pFile = *pFileIn; // 00 00 00 01 x x x x x 00 00 00 01 // 以下方法为了减少内存,及向回移动文件指针的操作 uint8 prefix[3] = { 0 }; // 表示读进来字节的数值 uint8 fileByte; /* 依次比较 [0][1][2] = {0 0 0}; 若不是,将下一个字符放到[0]的位置 -> [1][2][0] = {0 0 0} ; 下次放到[1]的位置,以此类推 找到三个连0之后,还需判断下一个字符是否为1, getc() = 1 -> 00 00 00 01 以及判断 [0][1][2] = {0 0 1} -> [1][2][0] = {0 0 1} 等,若出现这种序列则表示找到文件头 */ nalBytes.clear(); // 标记当前文件指针位置 int pos = 0; // 标记查找的状态 int getPrefix = 0; // 读取三个字节 for (int idx = 0; idx < 3; idx++) { prefix[idx] = getc(pFile); // 每次读进来的字节 都放入vector中 nalBytes.push_back(prefix[idx]); } while (!feof(pFile)) { if ((prefix[pos % 3] == 0) && (prefix[(pos + 1) % 3] == 0) && (prefix[(pos + 2) % 3] == 1)) { // 0x 00 00 01 found getPrefix = 1; // 这三个字符没用,pop掉 nalBytes.pop_back(); nalBytes.pop_back(); nalBytes.pop_back(); break; } else if((prefix[pos % 3] == 0) && (prefix[(pos + 1) % 3] == 0) && (prefix[(pos + 2) % 3] == 0)) { if (1 == getc(pFile)) { // 0x 00 00 00 01 found getPrefix = 2; // 这三个字符没用,pop掉 (最后那个1没填到vector中,不用pop) nalBytes.pop_back(); nalBytes.pop_back(); nalBytes.pop_back(); break; } } else { fileByte = getc(pFile); prefix[(pos++) % 3] = fileByte; nalBytes.push_back(fileByte); } } return getPrefix;}

主函数调整为:

#include "stdafx.h"#include 
#include
typedef unsigned char uint8;using namespace std;int _tmain(int argc, _TCHAR* argv[]){ FILE *pFile_in = NULL; // 打开刚才导入的二进制码流文件 _tfopen_s(&pFile_in, argv[1], _T("rb")); // 判断文件是否打开成功 if (!pFile_in) { printf("Error: Open File failed. \n"); } vector
nalBytes; find_nal_prefix(&pFile_in, nalBytes); find_nal_prefix(&pFile_in, nalBytes); for (int idx = 0; idx < nalBytes.size(); idx++) { printf("%x ", nalBytes.at(idx)); } printf("\n"); find_nal_prefix(&pFile_in, nalBytes); for (int idx = 0; idx < nalBytes.size(); idx++) { printf("%x ", nalBytes.at(idx)); } printf("\n"); fclose(pFile_in); return 0;}

以第一节最后数据流为例,执行以上代码后,程序输出结果如下:

4

转载于:https://www.cnblogs.com/shuofxz/p/8416222.html

你可能感兴趣的文章
解决IE6-IE7下li上下间距
查看>>
聚集索引更新后会不会马上重新排序
查看>>
幸运大抽奖
查看>>
Post请求
查看>>
labview 中activex的初步使用方法
查看>>
JSP与JavaBeans
查看>>
解决Android中TextView首行缩进的问题
查看>>
oracle 查询哪些表分区
查看>>
Java排序算法(三):直接插入排序
查看>>
Python 列表 min() 方法
查看>>
C语言中 Float 数据结构的存储计算
查看>>
HSF源码阅读
查看>>
【死磕jeesite源码】Jeesite配置定时任务
查看>>
程序8
查看>>
TBluetoothLEDevice.UpdateOnReconnect
查看>>
QtTableView 简介
查看>>
Liferay 6开发学习(二十六):数据库连接相关问题
查看>>
【20170506】贝业新兄弟IT总监李济宏:第三方家居物流的IT架构探索
查看>>
poj3517
查看>>
iphone http下载文件
查看>>