讀取一個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 */
};