日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

编解码标准-H.264

發布時間:2024/1/1 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 编解码标准-H.264 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

H.264是MPEG-4家族中的一員,即MPEG-4系列文檔ISO-14496的第10部分,因此被稱作MPEG-4 AVC,MPEG-4重點考慮靈活性和交互性,而H.264著重強調更高的編碼壓縮率和傳輸的可靠性。

1、H.264 編碼流程

1.1、slice&block

第一步:切片、切宏塊,宏塊(16x16)是編碼的基本單元

第二步:不合理的宏塊之間會出現塊效應,即色差明顯,所以繼續切割,分成很多個8*8或4*4的子塊

第三步:對子塊進行算法編碼,期間使用到的算法包括:

  • 幀內預測(內部壓縮)
  • 幀間預測(外部壓縮)
  • 量化編碼
  • 熵編碼

其中,每一個切片都有片頭 + 多個宏塊組成。

以16x16的宏塊為編碼最小單元,一個宏塊可以被分成多個4x4或8x8的塊,同一個宏塊內,像素的相似程度會比較高,若16x16的宏塊中,像素相差較大,那么就需要繼續細分。

當對一個宏塊進行編碼的時候,每個宏塊都會被分割成多種不同大小的子塊進行預測。

1.2、GOP內壓縮

GOP內 I、B、P幀

幀大小:I > P > B

壓縮率:B > P > I

編碼B幀的時候,需要先把B后面的幀先編碼,然后參考之后才能繼續編碼B幀,例如:

PTS:IDR1、B2、B3、P4、B5、B6、P7、B8、B9、I10、B11、B12、P13、B14、B15、P16

DTS:IDR1、P4、B2、B3、P7、B5、B6、I10、B8、B9、P13、B11、B12、P16、B14、B15

每次編碼B的時候,就要把后面最近的 I 或者 P 拿到前面來,如果已經之前被編碼過,就不需要了。

IDR幀

I 幀不需要參考任何幀,但B、P幀可能去參考I幀之前的幀,但如果遇到IDR幀,就不能參考IDR幀之前的幀

其核心的作用,就是為了讓編碼重新同步,立即將參考幀隊列清空,已編碼的數據全部清除掉。如果之前的參考序列出現了錯誤,這里就可以立刻矯正。IDR后面的數據又能重新開始編碼,不會收到前面的錯誤影響。

1.3、編碼配置

實時視頻會議一直是繼續向更高質量,更低帶寬的方向發展。H.264 High profile 技術于2010年率先被polycom應用于視頻會議系統。

比h.264 baseline進一步節約了近一半的帶寬。在高清實時會議中,采用H.264 baseline,帶寬要求還是比較高的,特別是要做1080P 30pfs甚至60pfs時。如果能減少一半帶寬,意味著節省2-4M帶寬,如果是在MCU側,則帶寬節省就更可觀了。

AVC/H.264 規定了多種不同的配置:基線、主要、擴展、高

  • 基線(Baseline Profile),不支持B幀,只支持無交錯模式,主要是用于可視電話,會議電視,無線通訊等實時通信;
  • 主要(Main Profile),提供I/P/B 幀,支持無交錯和交錯,用于數字廣播電視和數字視頻存儲;
  • 擴展(Extend Profile),也叫擴展Profile,
  • 高(High Profile),在 Main Profile 的基礎上增加了8x8 內部預測、提高了壓縮效率;

2、H.264的功能分層

H.264的原始碼流(裸流)是由?個接?個NALU組成,它的功能分為兩層:“編碼層”和“網絡層”

VCL(視頻編碼層):包括核?壓縮引擎和塊、宏塊和?的語法級別定義,設計?標是盡可能地獨?于?絡進??效的編碼;

NAL(?絡提取層):負責將VCL產?的?特字符串適配到各種各樣的?絡,就是把已經編碼后的數據封裝到網絡包中去;

H264在網絡中的傳輸,是以一連串NALU的形式傳輸的,一張圖像有可能存在2個NALU。

其中,NALU裝了不同的東西:

  • SPS:序列參數集,SRS中保存了一組編碼視頻序列的全局參數(發 I 幀之前至少要發一次)
  • PPS:圖像參數集(發 I 幀之前至少要發一次)
  • I 幀:I 幀或 I 幀的一部分;
  • P幀:P幀或P幀的一部分;
  • B幀:B幀或B幀的一部分;

如果H.264碼流解不出來,就要去看看是不是SPS或PPS不存在?

3、H.264流結構

2.1、AnnexB/AVCC

H.264流有兩種格式:

  • 一種是annexb,也是傳統模式,裸流一般都是annexb格式
  • annexb格式會在數據包前面加上startcode,然后在后面加上UALU包(NALU Header + RBSP)
  • 這個startcode用來做字節流對齊,以及分割流數據。
  • 將SPS和PPS都作為一個NALU進行封裝,每一次遇到 I 幀之前,都是重復加上SPS和PPS,這個SPS的作用就是提供了序列信息,比如解碼信息,而這個PPS提供的是圖像信息,比如如何壓縮等。
  • NALU封裝了SPS、PPS、SEI、
  • annexb格式的好處,就是解碼器可以從任意一個包開始解碼。

  • 另外種就是AVCC模式,例如mp4、mkv都屬于AVCC格式
  • 沒有startcode,直接是一個個UALU包
  • 解碼器配置參數在一開始就配置好了,使用NALU長度作為NALU的邊界,不需要額外的起始碼
  • SPS和PPS都封裝在文件頭部的extradata中;
  • 好處就是播放器直接能識別,去除了大量的startcode、sps、pps,縮小了文件大小;

比方說在ffmpeg中,我們解封裝mp4后,需要對H264進行解碼,而解碼之前必須要對H264裸流進行一個轉封裝過濾,將h264的 “mp4版本” 轉換為 “annexb版本” 的過程。

RTP包中接收的264包是不含有0x00,0x00,0x00,0x01頭的,這部分是RTP接收以后,另外再加上去的,解碼的時候再做判斷的。

/** * 解碼PS流的Extradata* h264_parse()* |* ff_h264_decode_extradata() |--> decode_extradata_ps* |--> decode_extradata_ps_mp4()* * decode_extradata_ps():*** decode_extradata_ps_mp4(): * MP4中SPS和PPS存放在 moov->trak->mdia->minf->stbl->stsd: * Extensions = Size + Type(avcC) + Extradata* */ static int decode_extradata_ps(const uint8_t *data, int size, H264ParamSets *ps, int is_avc, void *logctx) {// H264包H2645Packet pkt = { 0 };int i, ret = 0;ret = ff_h2645_packet_split(&pkt, data, size, logctx, is_avc, 2, AV_CODEC_ID_H264, 1, 0);if (ret < 0) {ret = 0;goto fail;}// 包里面有多少個NAL?for (i = 0; i < pkt.nb_nals; i++) {// 解析NAL類型H2645NAL *nal = &pkt.nals[i];switch (nal->type) {// SPS(7): 25字節左右case H264_NAL_SPS: {GetBitContext tmp_gb = nal->gb;ret = ff_h264_decode_seq_parameter_set(&tmp_gb, logctx, ps, 0);if (ret >= 0)break;av_log(logctx, AV_LOG_DEBUG,"SPS decoding failure, trying again with the complete NAL\n");init_get_bits8(&tmp_gb, nal->raw_data + 1, nal->raw_size - 1);ret = ff_h264_decode_seq_parameter_set(&tmp_gb, logctx, ps, 0);if (ret >= 0)break;ret = ff_h264_decode_seq_parameter_set(&nal->gb, logctx, ps, 1);if (ret < 0)goto fail;break;}// PPS(8): 5字節左右case H264_NAL_PPS:ret = ff_h264_decode_picture_parameter_set(&nal->gb, logctx, ps,nal->size_bits);if (ret < 0)goto fail;break;default:av_log(logctx, AV_LOG_VERBOSE, "Ignoring NAL type %d in extradata\n",nal->type);break;}}fail:ff_h2645_packet_uninit(&pkt);return ret; } typedef struct H264ParamSets {// SPS列表AVBufferRef *sps_list[MAX_SPS_COUNT];// PPS列表AVBufferRef *pps_list[MAX_PPS_COUNT];AVBufferRef *pps_ref;/* currently active parameters sets */const PPS *pps;const SPS *sps;int overread_warning_printed[2]; } H264ParamSets;

2.2、SPS

序列參數集,保存了一組編碼視頻序列的全局參數,保存了:profile、level、視頻寬和高、顏色空間等。在H.264的各種語法元素中,SPS中的信息至關重要。如果其中的數據丟失或出現錯誤,那么解碼過程很可能會失敗。

SPS 中的信息至關重要,如果其中的數據丟失,解碼過程就可能失敗。SPS 和 PPS 通常作為解碼器的初始化參數。一般情況,SPS 和 PPS 所在的 NAL 單元位于整個碼流的起始位置,但是在某些場景下,在碼率中間也可能出現這兩種結構:

  • 解碼器要在碼流中間開始解碼。比如,直播流。
  • 編碼器在編碼過程中改變了碼率的參數。比如,圖像的分辨率。

2.3、PPS

每一幀編碼后數據所依賴的參數,都保存在PPS中,主要體現的就是圖像編碼信息。

2.4、NALU

2.4.1、nal_unit_header

NALU頭就一個字節,包含了對NALU的描述,1)重要程度;2)NALU類型

F

1B

禁止位,0表示正常,1表示錯誤,一般都是0

NRI

2B

重要級別,00不重要,01,10,11非常重要

TYPE

5B

表示該NALU的類型是什么?

例如:

每個NAL分割的時候,00 00 00 01為startcode,頭部的2個startcode分別代表了SPS和PPS,從第3個startcode開始,就是NALU(I、B、P幀)。

  • 0x00 0x00 0x00 0x01 + 0x67

十六進制轉為二進制:0x0 11 00111,NALU類型=7,表示PSP

  • 0x00 0x00 0x00 0x01 + 0x68

十六進制轉為二進制:0x0 11 01000,NALU類型=8,表示PPS

  • 0x00 0x00 0x01 + 0x65

十六進制轉為二進制:0x0 11 00101,NALU類型=5,表示 I 幀

  • 0x00 0x00 0x00 0x01 + 0x41

十六進制轉為二進制:0x0 10 00001,NALU類型=1,表示 P 幀

  • 0x00 0x00 0x00 0x01 + 0x01

十六進制轉為二進制:0x0 00 00001,NALU類型=1,表示 B 幀

2.4.2、nal_unit_rbsp

NALU的主體涉及到三個重要的名詞,分別為EBSP、RBSP和SODB。

其中EBSP完全等價于NALU主體,而且它們三個的結構關系為:

EBSP包含RBSP,RBSP包含SODB。

NALU = EBSP + 0x03(防競爭字節)+ ...... + EBSP + 0x03

NALU = RBSP + 補齊字節

1、SODB

String Of Data Bits 原始數據比特流,就是最原始的編碼/壓縮得到的數據

2、RBSP

Raw Byte Sequence Payload,又稱原始字節序列載荷。和SODB關系如下:

RBSP = SODB + RBSP Trailing Bits(RBSP尾部補齊字節)引入RBSP Trailing Bits做8位字節補齊。

3、EBSP

Encapsulated Byte Sequence Payload:擴展字節序列載荷。

如果RBSP中也包括了StartCode(0x000001或0x00000001)怎么辦呢?所以,就有了防止競爭字節(0x03),編碼時,掃描RBSP,如果遇到連續兩個0x00字節,就在后面添加防止競爭字節(0x03);解碼時,同樣掃描EBSP,進行逆向操作即可。

2.4.3、SliceHeader

  • first_mb_in_slice:片中的第一個宏塊的地址, 片通過這個句法元素來標定它自己的地址。要注意的是在幀場自適應模式下,宏塊都是成對出現,這時本句法元素表示的是第幾個宏塊對,對應的第一個宏塊的真實地址應該是:2 * first_mb_in_slice;
  • slice_type:指明片的類型,IDR 圖像時, slice_type 等于 2, 4, 7, 9;

slice_type 的值在 5 到 9 范圍內表示,除了當前條帶的編碼類型,所有當前編碼圖像的其他條帶的 slice_type 值應與當前條帶的 slice_type 值一樣,或者等于當前條帶的 slice_type 值減 5。

當 nal_unit_type 等于 5(IDR 圖像)時,slice_type 應等于 2、 4、 7 或 9。當 num_ref_frames 等于 0 時, slice_type 應等于 2、 4、 7 或 9。

  • pic_parameter_set_id:當前slice所依賴的pps的id;
  • colour_plane_id:當標識位separate_colour_plane_flag為true時,colour_plane_id表示當前的顏色分量,0、1、2分別表示Y、U、V分量;
  • frame_num:每個參考幀都有一個依次連續的 frame_num 作為它們的標識,這指明了各圖像的解碼順序。但事實上我們在表 中可以看到, frame_num 的出現沒有 if 語句限定條件,這表明非參考幀的片頭也會出現 frame_num。只是當該個圖像是參考幀時,它所攜帶的這個句法元素在解碼時才有意義;

  • field_pic_flag:場編碼標識位。當該標識位為1時表示當前slice按照場進行編碼;該標識位為0時表示當前slice按照幀進行編碼;
  • bottom_field_flag:底場標識位。該標志位為1表示當前slice是某一幀的底場;為0表示當前slice為某一幀的頂場;
  • idr_pic_id:表示IDR幀的序號。某一個IDR幀所屬的所有slice,其idr_pic_id應保持一致。IDR 圖像的標識。不同的 IDR 圖像有不同的 idr_pic_id 值。值得注意的是,IDR 圖像有不等價于 I 圖像,只有在作為 IDR 圖像的 I 幀才有這個句法元素,在場模式下, IDR 幀的兩個場有相同的 idr_pic_id 值。 idr_pic_id 的取值范圍是 [0,65535] 和 frame_num 類似,當它的值超出這個范圍時,它會以循環的方式重新開始計數;
  • pic_order_cnt_lsb:表示當前幀序號的另一種計量方式;
  • delta_pic_order_cnt_bottom:表示頂場與底場POC差值的計算方法,不存在則默認為0;
  • slice_qp_delta:指出在用于當前片的所有宏塊的量化參數的初始值;

2.4.4、rbsp_trailing_bits

但是只在 NALU 前面加上起始碼是會產生問題了,因為原始碼流中,是有可能出現 0 0 0 1 或者 0 0 1 的,這樣就會導致讀取程序將一個 NALU 誤分割成多個 NALU。為了防止這種情況發生,AnnexB 引入了防競爭字節(Emulation Prevention Bytes)的概念。

所謂防競爭字節(Emulation Prevention Bytes),就是在給 NALU 添加起始碼之前,先對碼流進行一次遍歷,查找碼流里面的存在的 000、001、002、003 的字節,然后對其進行如下修改。

// EBSP->RBSP 反向處理 std::vector<uint8_t> EBSP2RBSP(uint8_t* buffer, int len) {// 00 00 03 去掉03std::vector<uint8_t> ebsp;int i = 0;for (i = 0; i < len-2; ++i) {if (buffer[i] == 0x00 && buffer[i+1] == 0x00 && buffer[i+2] == 0x03) {ebsp.push_back(buffer[i++]);ebsp.push_back(buffer[i++]);}else {ebsp.push_back(buffer[i]);}}for (; i < len; ++i) {ebsp.push_back(buffer[i]);}return ebsp; }

4、H.264內I、B、P幀的關系?

GOP編碼后的順序是解碼順序,解碼后看到的是顯示順序。

控制GOP也可以控制延遲問題,減少I幀時間的間距,一般控制在2秒比較合適。

4.1、I 關鍵幀

不需要參考其他畫面,靠自己就能被解碼成完整圖像,屬于“幀內編碼”。

  • 采用幀內編碼
  • 占數據信息量比較大
  • 是一個GOP的基礎幀
  • 不需要考慮運動矢量
  • 4.2、P幀

    P幀代表預測幀,除了空域預測以外,它還可以通過時域預測來進行壓縮。

    通過與其相鄰的前一幀(I 或 P)不同像素點進行壓縮本幀數據,屬于“幀間編碼”。

    以I幀和P幀為例。如果你只使用這兩種類型的幀,那么每一幀要么參考自身(I 幀),要么參考前一幀(P 幀)。因此,幀可以以相同的順序進出編碼器。這里,呈現順序(或顯示順序)與編碼、解碼順序相同。

    4.3、B幀

    B幀可以參考在其前后出現的幀,采用雙向預測(前后 I 或 P ),大大提高壓縮倍數,想要理解B幀的作用,我們需要先理解顯示順序和解碼順序的概念。

    按照解碼順序,解碼器先解碼幀1(I幀),然后是幀2(P幀)。但它卻無法顯示幀2,因為在解碼順序中的實際上是幀4!所以,解碼器需要將幀2(按解碼順序)放入緩沖區,然后等待顯示它的時機。

    所以,編碼器和解碼器需要在內存中維護兩個“順序”或“序列”:一個將幀放置在正確的顯示順序中,另一個用于將幀按照編碼和解碼所需順序放置。

    • 顯示隊列:1、2、3、4、5、6、7
    • 解碼隊列:1、2、3、2、6、7、5

    所以在GOP內部,I與I之間為一個組,I組內部P與P之間一個組,P組內部先解碼P,后解碼B

    • B幀壓縮率最大,用來預測運動軌跡;
    • P次之,用來表示和前一幀的差異;
    • I幀最小,其本身獨立完成編碼。

    4.4、IDR幀

    視頻第一個I幀,稱為IDR幀,IDR一定是I幀,但I幀不一定是IDR幀。

    4.5、開放GOP和閉合GOP

    所謂開放GOP,就是 I 幀可以被跨越,而閉合GOP,就是 I 幀是一個IDR,該 IDR 幀不能被之前的幀參考。

    這也要就要求了,在 IDR 幀之前,必須有一個P幀,否則,IDR 幀相鄰的B幀就無法向后預測。

    IDR和閉合GOP到底有什么用處?

    • ABR視頻流:

    在ABR視頻流中,播放器可以根據帶寬和解碼器緩沖器的填充程度在不同配置文件(組合不同碼率和分辨率的視頻)之間切換。如果播放器要從1080p切換到360p,那么它就需要這種利落的切換。此時IDR發揮作用,這樣播放器就能刷新緩沖,讓360p的視頻流進入。

    • 錯誤恢復:

    如果你在流化視頻時使用HLS,并且每個視頻片段都以IDR開始,這意味著片段中的所有幀都不能參考前、后片段中的幀。所以如果因為某個錯誤而失去其中一個片段,播放器仍然能繼續接收下一個視頻片段。有趣的是,Apple 的 HLS 規范提到應該每兩秒使用一次 IDR。(注意:規范沒有說視頻片段持續時間應該是兩秒,而是指 GOP 的大小是兩秒)

    所以說,IDR不能太頻繁,因為會影響壓縮效率。但又要保證視頻播放過程中丟包不受到影響,所以最好的辦法,就是間隔一段時間使用固定的IDR。

    • 快進快退:

    我們之前提到過,IDR非常有助于實現快進快退。播放器需找到距離最近的IDR,然后開始從這一點播放視頻流。

    總結

    以上是生活随笔為你收集整理的编解码标准-H.264的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。