對H.264碼流結構的理解


2011年8月18日 09時31分13秒

SODB到RBSP的轉換:

對SODB的最后填充rbsp_trailing_bits就得到RBSP,而這個rbsp_trailing_bits是第一個比特為1,接下來是0,直到字節對齊。比如SODB的最后幾個比特是1001,這時rbsp_trailing_bits即為:1000

SODB 到RBSP到轉換代碼如下:

void SODBtoRBSP(Bitstream*currStream)

{

currStream->byte_buf <<= 1;  //左移1bit

currStream->byte_buf |= 1;   //在尾部填一個"1"占1bit

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;

}

前4句就是要得到rbsp_trailing_bits,bits_to_go用來記錄要添0的個數。bits_to_go這個量在進行u_v等函數調用的函數writeUVLC2buffer中是對其不斷進行改變的。

 

2011817

191402

NALU第一字節就是包括3個語法結構: forbidden_zero_bit(1),nal_ref_idc(2),nal_unit_type(5),加起來正好一個字節, 如下圖所示:

下圖展示的是H.264中的碼流結構:

 

JM8.6代碼中對H.264的描述:

在編碼函數中,先將NALU填充完,然后再寫nalu的函數WriteAnnexbNALU中,先寫3個字節的起始碼0x000001

即:

putc (0, f);

putc (0, f);

putc (1, f);

BitsWritten += 24;

然后再將NALU寫入到文件中,即:

if (n->len != fwrite (n->buf, 1, n->len, f))

 

在JM8.6的main函數中,

1. 先調用start_sequence();函數來開始序列:start_sequence()函數中進行了寫序列參數集和圖像參數集的操作。

(1) GenerateSeq_parameter_set_NALU產生序列參數集,WriteNALU函數寫NALU,即:

nalu = GenerateSeq_parameter_set_NALU ();

len += WriteNALU (nalu);

(2) GeneratePic_parameter_set_NALU產生圖像參數集,WriteNALU函數寫NALU,即:

nalu = GeneratePic_parameter_set_NALU ();

len += WriteNALU (nalu);

在函數GenerateSeq_parameter_set_NALU和函數GeneratePic_parameter_set_NALU中都是先將參數集數據寫入到數據結構bitstream->streamBuffer中,然后利用SODBtoRBSP函數對原始數據進行處理得到RBSP,然后再函數RBSPtoNALU函數中將RBSP轉為EBSP 最后調用WriteNALU (nalu)函數將所寫好的nalu寫入到文件中去。

2. 在for循環中調用encode_one_frame函數編碼每一幀,並且包括將編碼完每一幀得到熵編碼得到的碼流 寫入到一個NALU中去,所以說一個NALU就是一幀圖像編碼過程中的編碼結果是保存在currStream->streamBuffer數據結構中的,然后在函數writeUnit中將currStream->streamBuffer中的數據復制到了nalu結構中。其實是在writeUnit函數中先生成了一個nalu數據結構,然后填充nalu的相關數據,最后調用WriteNALU函數將nalu寫入到文件中。

幀圖像:encode_one_frame()

->(1) frame_picture()->code_a_picture()->(while循環)encode_one_slice->encode_one_MB

(2) writeout_picture()->(for循環)writeUnit()->WriteNALU()[准確來說是寫一個slice]

場圖像:encode_one_frame()

->(1)field_picture (top_pic, bottom_pic);->code_a_picture(分別對頂場和底場編碼)

(2)writeout_picture (top_pic);

writeout_picture (bottom_pic);分別寫頂場和底場

 

通過分析可以知道編碼時是將一個slice作為一個小組(區域單位)進行編碼的,一幀圖像可以包括多個slice。在JM8.6中數據結構的層次是:

Picture{

Slice

{DataPartition

{Bitstream

}

}

}

從這個我們可以看到一幅圖像可以包括多個slice,一個slice可以對應1個Bitstream,而在函數writeUnit中是以slice為單位進行寫的,所以我們可以得到一個NALU中包含一個slice,一般情況下一個slice對應一幅圖像,所以,此時一個NALU也就對應一個slice

 

3. 最后釋放空間

對於RBSPtoEBSP函數的理解:

這個函數是將RBSP轉為EBSP,需要進行填充防止競爭的0x03,需要對以下幾種進行變化:

0x000000----->0x00000300

0x000001----->0x00000301

0x000002----->0x00000302

0x000003------>0x00000303

在具體的代碼中, 是利用下面的代碼進行實現的:

if(count == ZEROBYTES_SHORTSTARTCODE && !(NAL_Payload_buffer[i] & 0xFC))

{

streamBuffer[j] = 0x03;

j++;

count = 0;

}

注意:在上面的if語句中,count用於統計0x00字節的個數,當count==2時,我們需要檢測接下來的一個字節是否為(00,01,02或03),此處用的方法比較巧妙:0xFC=11111100b,也就是說屏蔽了最后兩位比特,這樣要求NAL_Payload_buffer[i] 必須為0。如果滿足這個說明就是我們要找的那4個。

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM