HEVC解码器HM源码阅读(三)读取一个NALU
程序员文章站
2022-07-07 14:06:29
...
读取一个NALU
视频数据的两种存储传输方式
视频的压缩数据,有两种存储传输方式:
1、存放在本地文件中,就是所谓的字节流应用(本章节讨论),也就是我们常说的比特流。
2、把数据发送到网络上,就是所谓的分组流应用(涉及到RTSP、rtmp等等封装协议,这里不细讲)。
NALU和比特流之间的关系
常见的比特流如下图所示:
2、比特流的NALU之间存在若干字节,用于对NALU进行分隔
3、比特流以leading_zero_8bits开始,其值是0x00
4、如果NALU存放的是VPS、SPS、PPS或者第一块压缩数据,在NALU的前面添加zero_byte,值是0x00
5、在zero_byte(如果存在)与NALU之间添加start_code_prefix_one_3bytes,值是0x000001
6、根据需要可以在NALU的后面添加trailing_zero_8bits用于填充,值为0x00
NALU的读取流程
读取流程:
1、打开数据文件
2、使用InputByteStream对数据文件的操作进行封装
3、循环读取文件
(1)定义一个AnnexBStats对象,用于统计比特流中的一些信息
(2)定义一个InputNALUnit对象,表示NALU单元的头部
(3)定义一个vector<uint8_t>对象nalUnit,表示NALU的数据部分
(4)调用byteStreamNALUnit/_byteStreamNALUnit函数,读取一个NALU
(5)调用read函数,把NALU的头部解析出来
读取一个NALU
NALU存放在比特流中,需要一个一个的读取出来
// 从比特流中把一个NALU读取出来
static void _byteStreamNALUnit(
InputByteStream& bs,
vector<uint8_t>& nalUnit,
AnnexBStats& stats)
{
// 一直读取,直到遇到zero_byte(可选)+start_code_prefix_one_3bytes
while ((bs.eofBeforeNBytes(24/8) || bs.peekBytes(24/8) != 0x000001)
&& (bs.eofBeforeNBytes(32/8) || bs.peekBytes(32/8) != 0x00000001))
{
uint8_t leading_zero_8bits = bs.readByte();
assert(leading_zero_8bits == 0);
stats.m_numLeadingZero8BitsBytes++;
}
// 如果zero_byte存在
if (bs.peekBytes(24/8) != 0x000001)
{
uint8_t zero_byte = bs.readByte();
assert(zero_byte == 0);
stats.m_numZeroByteBytes++;
}
// 读取start_code_prefix_one_3bytes
uint32_t start_code_prefix_one_3bytes = bs.readBytes(24/8);
assert(start_code_prefix_one_3bytes == 0x000001);
stats.m_numStartCodePrefixBytes += 3;
// 读取NALU(包括头部和载荷)
while (bs.eofBeforeNBytes(24/8) || bs.peekBytes(24/8) > 2)
{
nalUnit.push_back(bs.readByte());
}
// 如果存在trailing_zero_8bits,那么就一直读取,并丢弃
while ((bs.eofBeforeNBytes(24/8) || bs.peekBytes(24/8) != 0x000001)
&& (bs.eofBeforeNBytes(32/8) || bs.peekBytes(32/8) != 0x00000001))
{
uint8_t trailing_zero_8bits = bs.readByte();
assert(trailing_zero_8bits == 0);
stats.m_numTrailingZero8BitsBytes++;
}
}
分离NALU的头部和载荷
NALU由头部和载荷组成,载荷也叫做RBSP
/*
** 从NALU中把头部和载荷分离出来
*/
void read(InputNALUnit& nalu, vector<uint8_t>& nalUnitBuf)
{
/* perform anti-emulation prevention */
TComInputBitstream *pcBitstream = new TComInputBitstream(NULL);
// 把载荷(Payload)转换成RBSP,因为荷载是经过格式化的数据
convertPayloadToRBSP(nalUnitBuf, pcBitstream, (nalUnitBuf[0] & 64) == 0);
nalu.m_Bitstream = new TComInputBitstream(&nalUnitBuf);
nalu.m_Bitstream->setEmulationPreventionByteLocation(pcBitstream->getEmulationPreventionByteLocation());
delete pcBitstream;
// 读取NALU的头部
readNalUnitHeader(nalu);
}
把载荷转换成原始数据
编码器生成的原始数据的长度不一定是整数个字节,需要添加若干比特形成整数个字节,然后经过格式化,形成RBSP
/*
** 把载荷转换成RBSP,因为荷载是经过格式化的数据
** RBSP表示整字节化的SODB(String Of Data Bits,也就是编码器生成的数据)
** 编码器生成的原始数据的长度不一定是整数个字节,需要添加若干比特形成整数个字节
*/
static void convertPayloadToRBSP(vector<uint8_t>& nalUnitBuf, TComInputBitstream *bitstream, Bool isVclNalUnit)
{
UInt zeroCount = 0;
vector<uint8_t>::iterator it_read, it_write;
UInt pos = 0;
bitstream->clearEmulationPreventionByteLocation();
for (it_read = it_write = nalUnitBuf.begin(); it_read != nalUnitBuf.end(); it_read++, it_write++, pos++)
{
// 移除在SODB中添加的0x03字节(该字节用于格式化)
assert(zeroCount < 2 || *it_read >= 0x03);
if (zeroCount == 2 && *it_read == 0x03)
{
bitstream->pushEmulationPreventionByteLocation( pos );
pos++;
it_read++;
zeroCount = 0;
if (it_read == nalUnitBuf.end())
{
break;
}
}
zeroCount = (*it_read == 0x00) ? zeroCount+1 : 0;
*it_write = *it_read;
}
assert(zeroCount == 0);
// 把cabac_zero_word移除
if (isVclNalUnit)
{
// Remove cabac_zero_word from payload if present
Int n = 0;
while (it_write[-1] == 0x00)
{
it_write--;
n++;
}
if (n > 0)
{
printf("\nDetected %d instances of cabac_zero_word", n/2);
}
}
nalUnitBuf.resize(it_write - nalUnitBuf.begin());
}
解析NALU的头部
/*
** 从NALU中解析出头部(用InputNALUnit表示头部)
** 头部的长度固定是2字节
*/
Void readNalUnitHeader(InputNALUnit& nalu)
{
TComInputBitstream& bs = *nalu.m_Bitstream;
// 头部的forbidden_zero_bit,它的值固定是0,长度是1 比特
Bool forbidden_zero_bit = bs.read(1); // forbidden_zero_bit
assert(forbidden_zero_bit == 0);
// 解析NALU的类型
nalu.m_nalUnitType = (NalUnitType) bs.read(6); // nal_unit_type
// 解析reservedZero6Bits,值固定是0
nalu.m_reservedZero6Bits = bs.read(6); // nuh_reserved_zero_6bits
assert(nalu.m_reservedZero6Bits == 0);
// 解析NALU的时域层ID
nalu.m_temporalId = bs.read(3) - 1; // nuh_temporal_id_plus1
// 判断时域层id的合法性
if ( nalu.m_temporalId )
{
assert( nalu.m_nalUnitType != NAL_UNIT_CODED_SLICE_BLA_W_LP
&& nalu.m_nalUnitType != NAL_UNIT_CODED_SLICE_BLA_W_RADL
&& nalu.m_nalUnitType != NAL_UNIT_CODED_SLICE_BLA_N_LP
&& nalu.m_nalUnitType != NAL_UNIT_CODED_SLICE_IDR_W_RADL
&& nalu.m_nalUnitType != NAL_UNIT_CODED_SLICE_IDR_N_LP
&& nalu.m_nalUnitType != NAL_UNIT_CODED_SLICE_CRA
&& nalu.m_nalUnitType != NAL_UNIT_VPS
&& nalu.m_nalUnitType != NAL_UNIT_SPS
&& nalu.m_nalUnitType != NAL_UNIT_EOS
&& nalu.m_nalUnitType != NAL_UNIT_EOB );
}
else
{
assert( nalu.m_nalUnitType != NAL_UNIT_CODED_SLICE_TLA_R
&& nalu.m_nalUnitType != NAL_UNIT_CODED_SLICE_TSA_N
&& nalu.m_nalUnitType != NAL_UNIT_CODED_SLICE_STSA_R
&& nalu.m_nalUnitType != NAL_UNIT_CODED_SLICE_STSA_N );
}
}
与读取NALU相关的类
比特流统计类
/*
** 比特流统计信息
*/
struct AnnexBStats
{
UInt m_numLeadingZero8BitsBytes; // leading_zero_8bits的个数
UInt m_numZeroByteBytes; // zero_byte的个数
UInt m_numStartCodePrefixBytes; // start_code_prefix_one_3bytes的个数
UInt m_numBytesInNALUnit; // (所有的)NALU中字节的数量
UInt m_numTrailingZero8BitsBytes; // trailing_zero_8bits的个数
AnnexBStats& operator+=(const AnnexBStats& rhs)
{
this->m_numLeadingZero8BitsBytes += rhs.m_numLeadingZero8BitsBytes;
this->m_numZeroByteBytes += rhs.m_numZeroByteBytes;
this->m_numStartCodePrefixBytes += rhs.m_numStartCodePrefixBytes;
this->m_numBytesInNALUnit += rhs.m_numBytesInNALUnit;
this->m_numTrailingZero8BitsBytes += rhs.m_numTrailingZero8BitsBytes;
return *this;
}
};
NALU头部
/*
** NALU的头部
*/
struct NALUnit
{
NalUnitType m_nalUnitType; ///< nal_unit_type NALU的类型
UInt m_temporalId; ///< temporal_id NALU所属的时域层
UInt m_reservedZero6Bits; ///< reserved_zero_6bits
/** construct an NALunit structure with given header values. */
NALUnit(
NalUnitType nalUnitType,
Int temporalId = 0,
Int reservedZero6Bits = 0)
:m_nalUnitType (nalUnitType)
,m_temporalId (temporalId)
,m_reservedZero6Bits(reservedZero6Bits)
{}
/** default constructor - no initialization; must be perfomed by user */
NALUnit() {}
/** returns true if the NALunit is a slice NALunit */
Bool isSlice()
{
return m_nalUnitType == NAL_UNIT_CODED_SLICE_TRAIL_R
|| m_nalUnitType == NAL_UNIT_CODED_SLICE_TRAIL_N
|| m_nalUnitType == NAL_UNIT_CODED_SLICE_TLA_R
|| m_nalUnitType == NAL_UNIT_CODED_SLICE_TSA_N
|| m_nalUnitType == NAL_UNIT_CODED_SLICE_STSA_R
|| m_nalUnitType == NAL_UNIT_CODED_SLICE_STSA_N
|| m_nalUnitType == NAL_UNIT_CODED_SLICE_BLA_W_LP
|| m_nalUnitType == NAL_UNIT_CODED_SLICE_BLA_W_RADL
|| m_nalUnitType == NAL_UNIT_CODED_SLICE_BLA_N_LP
|| m_nalUnitType == NAL_UNIT_CODED_SLICE_IDR_W_RADL
|| m_nalUnitType == NAL_UNIT_CODED_SLICE_IDR_N_LP
|| m_nalUnitType == NAL_UNIT_CODED_SLICE_CRA
|| m_nalUnitType == NAL_UNIT_CODED_SLICE_RADL_N
|| m_nalUnitType == NAL_UNIT_CODED_SLICE_RADL_R
|| m_nalUnitType == NAL_UNIT_CODED_SLICE_RASL_N
|| m_nalUnitType == NAL_UNIT_CODED_SLICE_RASL_R;
}
Bool isSei()
{
return m_nalUnitType == NAL_UNIT_PREFIX_SEI
|| m_nalUnitType == NAL_UNIT_SUFFIX_SEI;
}
Bool isVcl()
{
return ( (UInt)m_nalUnitType < 32 );
}
};
/*
** 对NALU的头部进一步包装,表示输入的NALU头部
*/
struct InputNALUnit : public NALUnit
{
InputNALUnit() : m_Bitstream(0) {};
~InputNALUnit() { delete m_Bitstream; }
TComInputBitstream* m_Bitstream;
};
文件操作的封装类
/*
** 文件操作的封装类
*/
class InputByteStream
{
public:
InputByteStream(std::istream& istream)
: m_NumFutureBytes(0)
, m_FutureBytes(0)
, m_Input(istream)
{
istream.exceptions(std::istream::eofbit);
}
void reset()
{
m_NumFutureBytes = 0;
m_FutureBytes = 0;
}
// 判断下n个字节是否会遇到文件末尾
// 同时将字节读取出来
Bool eofBeforeNBytes(UInt n)
{
assert(n <= 4);
if (m_NumFutureBytes >= n)
return false;
n -= m_NumFutureBytes;
try
{
for (UInt i = 0; i < n; i++)
{
m_FutureBytes = (m_FutureBytes << 8) | m_Input.get();
m_NumFutureBytes++;
}
}
catch (...)
{
return true;
}
return false;
}
// 抽取n个字节,原来的字节不会被删除
uint32_t peekBytes(UInt n)
{
eofBeforeNBytes(n);
return m_FutureBytes >> 8*(m_NumFutureBytes - n);
}
// 读取1个字节,原来的字节被删除
uint8_t readByte()
{
if (!m_NumFutureBytes)
{
uint8_t byte = m_Input.get();
return byte;
}
m_NumFutureBytes--;
uint8_t wanted_byte = m_FutureBytes >> 8*m_NumFutureBytes;
m_FutureBytes &= ~(0xff << 8*m_NumFutureBytes);
return wanted_byte;
}
// 读取n个字节,原来的字节被删除
uint32_t readBytes(UInt n)
{
uint32_t val = 0;
for (UInt i = 0; i < n; i++)
val = (val << 8) | readByte();
return val;
}
private:
UInt m_NumFutureBytes; /* number of valid bytes in m_FutureBytes */
uint32_t m_FutureBytes; /* bytes that have been peeked */
std::istream& m_Input; /* Input stream to read from */
};
上一篇: H265/HEVC:VPS参数集
下一篇: python字符串