一码流封装格式简单介绍:
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 的长度
实际应用中字节流格式更为常用, 下面的均以字节流格式来介绍
通过查阅 H.264 官方说明文档, 了解 NAL 字节流格式(在附录 B)
有用数据前面会加 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 打开此文件, 可以看到该文件的数据流:
接下来将写一个小程序, 从二进制码流文件中截取实际的 NAL 数据
二 C++ 程序 从码流中提取 NAL 有效数据:
新建一个 VS 工程, 配置工程属性将 [常规 - 输出目录] 和[调试 - 工作目录]改为
$(SolutionDir) bin\$(Configuration)\
, 编译运行程序
在 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 数据类型来存储单个字节码
为了减少内存使用, 使用数组 refix3, 存储连续的三个字节码
数组循环使用, 新进来的数据放在弹出那位数据的位置上
即: 数组的存数顺序为 02, 下一个字符放在 [0] 的位置上, 此时数据顺序为 1[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
每次读取的数据都直接 push 到 nalBytes 中, 若遇到起始码再把起始码 pop 掉
本函数需要重复执行, 第一次文件指针移动到有效数据起始位置; 第二次提取两段起始码间的有效数据; 第三次在移动到下一个起始码后; 第四次提取有效数据... 以此类推
函数调整为:
- static int find_nal_prefix(FILE **pFileIn, vector<uint8> &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 <stdio.h>
- #include <vector>
- 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<uint8> 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;
- }
以第一节最后数据流为例, 执行以上代码后, 程序输出结果如下:
来源: https://www.cnblogs.com/shuofxz/p/8416222.html