H.264
本文只涉及包結構,有個大概的認識,不涉及到編碼算法。後續有機會再瞭解。
H.264簡介
H.264,又稱爲MPEG-4第10部分,高級視頻編碼(英語:MPEG-4 Part 10, Advanced Video Coding,縮寫爲MPEG-4 AVC)是一種面向塊,基於運動補償的視頻編碼標準 。到2014年,它已經成爲高精度視頻錄製、壓縮和發佈的最常用格式之一。
H.264/AVC項目的目的是爲了創建一個比以前的視頻壓縮標準,在更低的比特率的情況下依然能夠提供良好視頻質量的標準(如,一半或者更少於MPEG-2,H.263,或者MPEG-4 Part2 )。同時,還要不會太大的增加設計的複雜性。
H.264相比於更早的H.263等其他的編碼格式,有以下優勢:
- 網絡親和性,適用於各種傳輸網絡
- 高視頻壓縮比,最初提出的指標比H.263、MPEG-4壓縮率更高,約爲它們的2倍
2012年提出了H.265,H.265即HEVC(High Efficiency Video Coding),目的是爲了取代H.264,H.265能夠達到H.264兩倍的壓縮率。
H.264格式
H.264原始碼流(裸流),由一個接一個NALU組成,而它的功能分爲兩層:視頻編碼層(VCL, Video Coding Layer)和網絡提取層(NAL, Network Abstraction Layer)。
VCL數據即編碼處理的輸出,它表示被壓縮編碼後的視頻數據序列。在 VCL 數據傳輸或存儲之前,這些編碼的VCL數據,先被映射或封裝進NAL單元(NALU)中。
每個NAL單元包括:一組對應於視頻編碼數據的NAL頭信息和一個原始字節序列負荷(RBSP)。
頭信息中包含着一個可否丟棄的指示標記,標識着該NAL單元的丟棄能否引起錯誤擴散,一般如果NAL單元中的信息不用於構建參考圖像,則認爲可以將其丟棄;最後包含的是NAL單元的類型 信息,暗示着其內含有效載荷的內容。 **送到解碼器端的NAL單元必須遵守嚴格的順序,如果應用程序接收到的NAL單元處於亂序,則必須提供一種恢復其正確順序的方法。**對於RTP/UDP/IP系統,則可以直接將編碼器輸出的NAL單元作爲RTP的有效載荷。
NALU
NALU以0x00 00 01或0x00 00 00 01開頭,標準規定可以以0x00 00 00作爲一個NALU的結束標誌。
下圖中NAL頭+RBSP就是一個NALU,傳輸時每個NALU都是獨立傳輸的。
NAL Header
NAL頭長度爲1字節,內部包括三個標誌:
-
forbidden_zero
1bit,正常爲0,當網絡傳輸NALU可能存在錯誤時,爲1,解碼器可以考慮不對該NALU解碼。
-
nal_ref_idc
2bit,取值從0~3,值越大,表示當前NALU越重要。
當前NALU爲圖像參數集、序列參數集、IDR圖像或參考圖像條帶(片),或者爲參考圖像條帶數據分割時,nal_ref_idc必定不爲0。
當nal_unit_type爲6、9、10、11、11、12時,nal_ref_type都爲0。
-
nal_unit_type
5bit,表示NALU中RBSP的數據結構類型,類型見下表:
nal_unit_type NAL單元和RBSP語法結構的內容 0 未指定 1 一個非IDR圖像的編碼條帶 2 編碼條帶數據分割塊A 3 編碼條帶數據分割塊B 4 編碼條帶數據分割塊C 5 IDR圖像的編碼條帶 6 輔助增強信息(SEI) 7 序列參數集 8 圖像參數集 9 訪問單元分隔符 10 序列結尾 11 流結尾 12 填充數據 13 序列參數集擴展 14-18 保留 19 未分割的輔助編碼圖像的編碼條帶 21-23 保留 24-31 未指定
RBSP
RBSP(原始字節序列載荷,Raw Byte Sequence Payload)中包含兩部分,SODB和RBSP尾部。
SODB
SODB(數據比特串,String Of Data Bits),這個內部存儲的是真正的原始編碼數據。
RBSP尾
RBSP尾部分爲兩種:
- nal_unit_type不爲1~5時,則在尾部補一個比特位,值爲1,然後補若干個比特位,值爲0,若干個視當前SODB長度,讓字節對齊。
- nal_unit_type爲1~5時,默認情況爲第一種補全方式,但是當entropy_coding_mode_flag值爲1,即當前採用的熵編碼爲CABAC,而且more_rbsp_trailing_data()返回值爲true,RBSP有更多數據的時候,在後面再添加一個或多個0x0000。
關於SODB轉換爲RBSP定義在nal.c
中
/*!
************************************************************************
* \brief
* Converts String Of Data Bits (SODB) to Raw Byte Sequence
* Packet (RBSP)
* \param currStream
* Bitstream which contains data bits.
* \return None
* \note currStream is byte-aligned at the end of this function
*
************************************************************************
*/
void SODBtoRBSP(Bitstream *currStream)
{
currStream->byte_buf <<= 1;
currStream->byte_buf |= 1;
currStream->bits_to_go--;
currStream->byte_buf <<= currStream->bits_to_go;
currStream->streamBuffer[currStream->byte_pos++] = currStream->byte_buf;
currStream->bits_to_go = 8;
currStream->byte_buf = 0;
}
EBSP
EBSP(擴展字節序列載荷,Encapsulated Byte Sequence Payload)則是對RBSP的拓展,EBSP很容易理解,是爲了防止競爭而存在的。
爲了防止競爭,H.264規定了防止競爭機制:如果在EBSP內部出現0x00 00 00或者0x00 00 01,則在最後一個字節前加入0x03。
- 0x00 00 00 —> 0x00 00 03 00(NALU結束標誌)
- 0x00 00 01 —> 0x00 00 03 01(NALU起始標誌)
- 0x00 00 02 —> 0x00 00 03 02(保留標誌)
- 0x00 00 03 —> 0x00 00 03 03(防止視頻內本身存在的0x00 00 03被作爲防競爭標誌)
所以EBSP的本質就是,當RBSP內部存在競爭出現歧義時,對其加上0x03。
拿到一個EBSP,只需要將所有0x00 00 03修改成0x00 00即可獲得RBSP。
/*!
************************************************************************
* \brief
* This function add emulation_prevention_three_byte for all occurrences
* of the following byte sequences in the stream
* 0x000000 -> 0x00000300
* 0x000001 -> 0x00000301
* 0x000002 -> 0x00000302
* 0x000003 -> 0x00000303
*
* \param NaluBuffer
* pointer to target buffer
* \param rbsp
* pointer to source buffer
* \param rbsp_size
* Size of source
* \return
* Size target buffer after emulation prevention.
*
************************************************************************
*/
int RBSPtoEBSP(byte *NaluBuffer, unsigned char *rbsp, int rbsp_size)
{
int j = 0;
int count = 0;
int i;
for(i = 0; i < rbsp_size; i++)
{
if(count == ZEROBYTES_SHORTSTARTCODE && !(rbsp[i] & 0xFC))
{
NaluBuffer[j] = 0x03;
j++;
count = 0;
}
NaluBuffer[j] = rbsp[i];
if(rbsp[i] == 0x00)
count++;
else
count = 0;
j++;
}
return j;
}
H.264編碼流程
幀間和幀內預測(Estimation)、變換(Transform)和反變換、量化(Quantization)和反量化、環路濾波(Loop Filter)、熵編碼(Entropy Coding)。
簡單記錄下大概的流程。詳細編碼每個步驟流程後期再去學習,目前主要是希望對音視頻整體有個基礎的認識~
ffmpeg命令
轉換mp4變成h264裸流
ffmpeg -i NewRules.mp4 -vcodec copy -f h264 NewRules.264
轉換mp4前10秒視頻變成h264裸流
ffmpeg -ss 0:0:0 -t 0:0:10 -i NewRules.mp4 -vcodec copy NewRules_cut.264
解析NAL Header
代碼參照雷神的博客編寫。
代碼邏輯很簡單,按照格式將每個NALU分割找到,然後再解析第一個字節,即NAL Header。解析結果如圖:
這裏展示下部分代碼,詳細代碼訪問項目地址:
bool H264Parser::GetNextNALU()
{
int cur_pos = 0;
bool start_code_found = false;
bool info2 = false, info3 = false;
bool eof_flag = false;
char start_code[4];
// 找NALU開頭
if (3 != fread(start_code, 1, 3, h264_stream)) {
return false;
}
info2 = FindStartCode2(start_code);
if (info2) {
nalu->start_code_len = 3;
} else {
if (1 != fread(start_code + 3, 1, 1, h264_stream)) {
return false;
}
info3 = FindStartCode3(start_code);
if (info3) {
nalu->start_code_len = 4;
} else {
return false;
}
}
// 找下一個NALU的開頭
start_code_found = false;
info2 = info3 = false;
while (!start_code_found) {
if (feof(h264_stream)) {
eof_flag = true;
break;
}
nalu->buffer[cur_pos++] = fgetc(h264_stream);
info3 = FindStartCode3(nalu->buffer + cur_pos - 4);
if (!info3) {
info2 = FindStartCode2(nalu->buffer + cur_pos - 3);
}
start_code_found = (info2 || info3);
}
// 當前NALU開頭到下一個NALU開頭之間的數據就是我們要的
// 然後把FILE指針恢復到下一個NALU開頭開始的位置
if (eof_flag) {
nalu->sodb_len = cur_pos - 1;
} else {
int rwind = info2 ? -3 : -4;
if (0 != fseek(h264_stream, rwind, SEEK_CUR)) {
std::cout << "Error: fseek error\n";
return false;
}
nalu->sodb_len = cur_pos + rwind;
}
// 分析NAL Header
nalu->forbidden_bit = nalu->buffer[0] & 0x80; //1 bit
nalu->nalu_reference_idc = nalu->buffer[0] & 0x60; // 2 bit
nalu->nalu_unit_type = (nalu->buffer[0]) & 0x1f; // 5 bit
return true;
}