前言
h264帧通常包含多个nalu,当我们需要封装为mp4的时候,就需要获取这些nalu,读取其中的sps和pps信息,以及视频帧。h264的打包格式有2种,一种是Annex-B,另一种是AVCC,本文提供Annex-B的解析方法。
一、原理说明
1.h264-Annex-B的结构
h264内部包含多个nalu,Annex-B格式的h264是通过startcode区分每个nalu。startcode的值为001或者0001,结构如下:
2.如何解析?
由于h264的’防止竞争 emulation prevention"机制确保了nalu内部不会出现startcode:001or0001,所以h264是可以按流解析的。其解析方法也很简单,首先查找startcode作为起点,接着查找下一个startcode作为终点,然后完成一个nalu的解析,循环往复。
二、代码实现
NaluParse.h
#pragma once
#include<vector>
namespace AC {
class Nalu
{
friend class NaluParse;
public:
unsigned char* GetData();
int GetDataLength();
private:
unsigned char* _data;
int _dataLength;
};
class NaluParse {
public:
void SendH264Stream(unsigned char* h264Stream, int len);
bool ReceiveNalu(Nalu& nalu);
private:
static bool GetNextNalu(unsigned char* h264Data, int len, int& offset, int& lastNaluLen, int& startcodeLen);
std::vector<unsigned char>_nalu;
unsigned char* _h264Stream;
int _len;
int _offset;
int _startcodeLength;
int _state = 0;
};
}
NaluParse .cpp
#include"NaluParse.h"
namespace AC {
unsigned char* Nalu::GetData()
{
return _data;
}
int Nalu::GetDataLength()
{
return _dataLength;
}
void NaluParse::SendH264Stream(unsigned char* h264Stream, int len)
{
_h264Stream = h264Stream;
_len = len;
_offset = 0;
}
bool NaluParse::ReceiveNalu(Nalu& nalu)
{
while (1)
{
switch (_state)
{
case 0:
{
if (GetNextNalu(_h264Stream, _len, _offset, nalu._dataLength, _startcodeLength))
{
_state = 2;
}
else
{
int i = _offset - 2;
if (i < 0)
i = 0;
for (i; i < _len; i++)
_nalu.push_back(_h264Stream[i]);
_state = 1;
return false;
}
break;
}
case 1:
{
for (int i = _nalu.size(); i < 2; i++)
{
if (_offset < _len)
{
_nalu.push_back(_h264Stream[_offset++]);
}
else
{
return false;
}
}
if (_offset < _len)
{
if (_nalu[_nalu.size() - 2] == 0 && _nalu[_nalu.size() - 1] == 0 && _h264Stream[_offset] == 1)
{
_offset++;
_state = 2;
break;
}
else
{
_nalu.erase(_nalu.begin());
}
}
else
{
return false;
}
if (_offset + 1 < _len)
{
if (_nalu[_nalu.size() - 1] == 0 && _h264Stream[_offset] == 0 && _h264Stream[_offset + 1] == 1)
{
_offset += 2;
_state = 2;
break;
}
else
{
_nalu.clear();
}
}
else
{
_nalu.push_back(_h264Stream[_offset++]);
return false;
}
_state = 0;
break;
}
case 2:
{
if (GetNextNalu(_h264Stream, _len, _offset, nalu._dataLength, _startcodeLength))
{
nalu._data = &_h264Stream[_offset - _startcodeLength - nalu._dataLength];
_state = 2;
return true;
}
else
{
if (nalu._dataLength > 0)
{
_state = 3;
_nalu.resize(nalu._dataLength + 4);
_nalu[3] = 1;
memcpy(_nalu.data() + 4, &_h264Stream[_offset - _startcodeLength - nalu._dataLength], nalu._dataLength);
}
return false;
}
break;
}
case 3:
for (int i = _nalu.size(); i < 6; i++)
{
if (_offset < _len)
{
_nalu.push_back(_h264Stream[_offset++]);
}
else
{
return false;
}
}
if (_offset < _len)
{
if (_nalu[_nalu.size() - 2] == 0 && _nalu[_nalu.size() - 1] == 0 && _h264Stream[_offset] == 1)
{
if (_nalu[_nalu.size() - 3] == 0)
{
nalu._dataLength = _nalu.size() - 3 - 4;
}
else
{
nalu._dataLength = _nalu.size() - 2 - 4;
}
nalu._data = _nalu.data() + 4;
_state = 2;
_offset += 1;
return true;
}
}
if (_offset + 1 < _len)
{
if (_nalu[_nalu.size() - 1] == 0 && _h264Stream[_offset] == 0 && _h264Stream[_offset + 1] == 1)
{
if (_nalu[_nalu.size() - 2] == 0)
{
nalu._dataLength = _nalu.size() - 2 - 4;
}
else
{
nalu._dataLength = _nalu.size() - 1 - 4;
}
nalu._data = _nalu.data() + 4;
_state = 2;
_offset += 2;
return true;
}
}
if (_offset + 2 < _len)
{
if (_nalu[_nalu.size() - 1] == 0 && _h264Stream[_offset] == 0 && _h264Stream[_offset + 1] == 0 && _h264Stream[_offset + 2] == 1)
{
nalu._dataLength = _nalu.size() - 1 - 4;
nalu._data = _nalu.data() + 4;
_state = 2;
_offset += 3;
return true;
}
}
bool find = GetNextNalu(_h264Stream, _len, _offset, nalu._dataLength, _startcodeLength);
if (nalu._dataLength > 0)
{
int oldSize = _nalu.size();
_nalu.resize(_nalu.size() + nalu._dataLength);
memcpy(_nalu.data() + oldSize, &_h264Stream[_offset - _startcodeLength - nalu._dataLength], nalu._dataLength);
}
if (find)
{
_state = 2;
nalu._dataLength = _nalu.size() - 4;
nalu._data = _nalu.data() + 4;
return true;
}
else
{
return false;
}
break;
}
}
}
bool NaluParse::GetNextNalu(unsigned char* h264Data, int len, int& offset, int& lastNaluLen, int& startcodeLen)
{
int oldOffset = offset;
offset += 3;
startcodeLen = 0;
while (offset <= len)
{
if (h264Data[offset - 3] == 0 && h264Data[offset - 2] == 0 && h264Data[offset - 1] == 1)
{
if (offset > 3) {
if (h264Data[offset - 4] == 0) {
startcodeLen = 4;
}
else
{
startcodeLen = 3;
}
}
else {
startcodeLen = 3;
}
lastNaluLen = offset - oldOffset - startcodeLen;
return true;
}
offset++;
}
if (offset > len)
offset = len;
lastNaluLen = offset - oldOffset;
return false;
}
}
三、使用示例
1.解析h264文件
#include<stdio.h>
#include"NaluParse.h"
int main(int argc, char* argv[])
{
FILE* f;
AC::NaluParse parse;
AC::Nalu nalu;
unsigned char fullBuffer[1028] = { 0,0,0,0,0 };
unsigned char* buffer = fullBuffer + 4;
f = fopen("test.h264", "rb+");
if (!f)
{
printf("ERROR:Open h264 file fialed.\n");
return -1;
}
while (1)
{
int size = fread(buffer, 1, 1024, f);
if (size < 1)
{
break;
}
parse.SendH264Stream(buffer, size);
while (parse.ReceiveNalu(nalu))
{
switch (nalu.GetData()[0] & 0x1F)
{
case 01:
case 02:
case 03:
case 04:
case 05:
{ unsigned char* pNalu = nalu.GetData();
pNalu -= 4;
pNalu[0] = (nalu.GetDataLength() >> 24) & 0xFF;
pNalu[1] = (nalu.GetDataLength() >> 16) & 0xFF;
pNalu[2] = (nalu.GetDataLength() >> 8) & 0xFF;
pNalu[3] = (nalu.GetDataLength() >> 0) & 0xFF;
}
break;
case 7:
{
}
break;
case 8:
{
}
break;
default:
break;
}
}
}
fclose(f);
return 0;
}
总结
以上就是今天要讲的内容,对h264-Annex-B的解析原理上是比较简单的而且易于理解,但是在实现上却不太容易,由于是基于数据流解析的,很多情况都需要考虑。比如startcode刚好被上下两部分数据分割,就需要特殊判断。又比如流数据每次只能获取1byte时需要建立额外的缓冲存储流数据,达到基本长度后才能解析。还有流数据的长度随机不固定的时候也需要一定的处理等等。总的来说,实现起来并不容易,但是实现之后使用效果还是挺好的。
|