FFmpeg從入門到精通——進階篇,SEI那些事兒

FFmpeg從入門到精通——進階篇,SEI那些事兒

來自專欄 金山雲-支持站

前言

在直播應用的開發過程中,如果把主播端消息事件傳遞到觀眾端,一般會以Instant Messaging(即時通訊)的方式傳遞過去,但因為消息分發通道和直播通道是分開的,因此消息與直播音視頻數據的同步性就會出現很多問題。那麼有沒有在音視頻內部傳遞消息的方法呢?答案是SEI。

金山雲目前推出的直播問答解決方案中,就用到了SEI,阿曾作為一名視頻雲架構資深開發工程師,對於與視頻相關的技術有著深刻的實踐經驗,今天給大家分享一下關於SEI的技術細節。

流媒體是採用流式傳輸方式在網路上播放的媒體格式,視頻網站內容、短視頻、在線直播這些視頻形態,均屬於流媒體的不同分支。流媒體大致包含三個層級:碼流、封裝和協議。從音視頻編碼器輸出的碼流,經過某種封裝格式後,經過特定的協議傳輸、保存,構成了流媒體世界的基礎功能。

SEI即補充增強信息(Supplemental Enhancement Information),屬於碼流範疇,它提供了向視頻碼流中加入額外信息的方法,是H.264/H.265這些視頻壓縮標準的特性之一。

SEI的基本特徵如下:

1. 並非解碼過程的必須選項

2. 可能對解碼過程(容錯、糾錯)有幫助

3. 集成在視頻碼流中

也就是說,視頻編碼器在輸出視頻碼流的時候,可以不提供SEI信息。雖然在視頻的傳輸過程、解封裝、解碼這些環節,都可能因為某種原因丟棄SEI內容,但在視頻內容的生成端和傳輸過程中,是可以插入SEI信息的。這些插入的信息,和其他視頻內容一同經過傳輸鏈路到達消費端。舉例來說,當前火爆的直播問答模式,就是通過SEI傳遞較多和答題業務相關的信息,通過SEI承載的信息,極大地優化了題目顯示和觀眾音視頻觀看的同步性。

那麼在SEI中可以添加哪些信息呢?以下是一些用戶場景可任意擴展的例子:

1. 傳遞編碼器參數

2. 傳遞視頻版權信息

3. 傳遞攝像頭參數

4. 傳遞內容生成過程中的剪輯事件(引發場景切換)

對於SEI如何應用,我們先以H.264/AVC這一視頻編碼標準為例。在這一標準中,整個系統框架分為兩層:視頻編碼層面(Video Coding Layer,簡稱VCL)和網路抽象層面(Network Abstraction Layer,簡稱NAL)。VCL負責表示有效視頻數據的內容,NAL負責格式化數據並提供頭信息,以保證數據適合各種信道和存儲介質上的傳輸。NAL unit是NAL的基本語法結構,它包含一個位元組的頭信息(NAL header)和一系列來自VCL的原始數據位元組流(RBSP)。

H.264/AVC中的情況

NAL unit type儲存在NAL header中,在H.264/AVC標準中,可用的NAL unit type一共有17種,作用是告訴解碼器,承載的數據是視頻關鍵幀,還是視頻解碼器的配置參數信息。其中值為6時表徵SEI內容。比較常見的類型如下表所示:

H.264/AVC中完整的NAL unit類型定義請參考《ISO/IEC 14496-10:2014》,這是MPEG專家組為AVC編解碼器制定的標準,H.264/AVC中NAL unit類型完整定義都在該標準的7-1表中,標準一共預留了32種類型,在NAL header裡面,用5 bits表徵NAL unit type。

H.264/AVC中的NAL unit type

如上圖所示,在8 bits的NAL header中:

1. 第0位是禁止位0,值為1時表示語法出錯

2. 第1~2位是參考級別(NRI,NAL ref idc)

3. 第3~7位是NAL unit type

需要注意的是,當NRI取值為"00"(二進位)時,表徵NAL unit不參與重建參考圖像,這時的NAL unit是可以丟棄的。大於"00"(二進位)時,NAL unit不能被丟棄。

H.265/HEVC中的情況

《ISO/IEC 23008-2:2015》是MPEG專家組為HEVC編解碼器制定的標準,H.265/HEVC中NAL unit類型完整定義都在該標準的7-1表中,可用的NAL unit type一共有40種之多,其中39和40都表徵SEI內容。因為標準一共預留64種類型,所以在NAL header裡面,用6 bits表徵NAL unit type。

H.265/HEVC中的NAL unit type

如上圖所示,在16 bits的NAL header中:

1. 第0位是禁止位0,值為1時表示語法出錯

2. 第1~6位是NAL unit type

3. 第7~12位是NUH layer id

4. 第13~15位是temporal_id

SEI 類型

在H.264/AVC視頻編碼標準中,並沒有規定SEI payload type的範圍,所以表徵payload type的位元組數是浮動的。

語法分析如下所示,當開始解析類型為SEI的NAL時,持續讀取8bit,直到非0xff為止,然後把讀取的數值累加,累加值即為SEI payload type。

讀取SEI payload size和payload type邏輯類似,仍然是讀取到0xff為止,這樣可以支持任意長度的SEI payload添加。

當獲取了SEI payload類型和大小後,就進入了實際的SEI內容讀取。

當前《ISO/IEC 14496-10:2014》Annex D.1.1提供了最大到181的payload類型處理規範,由於類型可以指定任意大小,給SEI的添加、處理創造了很大的自由空間。

其中SEI payload類型值為5時,指定的處理方法叫user_data_unregistered(),字面含義為未註冊的用戶數據,常用於存儲編碼器的編碼參數信息,是比較常見的payload類型。

讀取payload type為5時,具體的語法解析流程如下:

其中uuid_iso_iec_11578的詳細定義在《ISO/IEC 11578:1996》 Annex A中,大致規定了使用128 bits(16個位元組)來指定UUID。此處UUID可以表徵寫入SEI payload的角色ID,或者表徵其他業務用途。剩下的payloadSize -16位元組,即是業務層傳遞的具體內容了。

通過user_data_unregistered()語法解析可以看出,當使用SEI payload type為5時,注意事項如下:

1. payload size應該大於16;

2. uuid可能出現0x000000/0x000001/0x000002,需要插入0x03做防競爭處理;

構成RBSP時,都需要做RBSP拖尾處理。拖尾處理對所有SODB方式都一致。rbsp_trailing_bits()語法邏輯如下:

SEI 例子

從video.js的示例中下載oceans.mp4並提取出H.264碼流如下:

bitstream from oceans.mp4

NAL header

起始碼(暗紅底色)"0x00000001"分割出來的比特流即是NAL unit,起始碼緊跟的第一個位元組(墨綠底色)是NAL header。上圖「NAL header」一共出現了四個數值:

· "0x06",此時NRI為"00B",NAL unit type為SEI類型。

·「0x67」,此時NRI為「11B」,NAL unit type為SPS類型。

·「0x68」,此時NRI為「11B」,NAL unit type為PPS類型。

·「0x65」,此時NRI為「11B」,NAL unit type為IDR圖像。

SEI payload type

"0x06"後一個位元組為「0x05」(淡黃底色)是SEI payload type,即表徵SEI payload分析遵循user_data_unregistered()語法。

SEI payload size

「0x05」後一個位元組為「0x2F」(淡藍底色)是SEI payload size,此時整個payload是47個位元組。

SEI payload uuid

"0x2F"隨後的16個位元組即為uuid,此時uuid為

SEI payload content

由於payload size是47個位元組,除去16位元組的uuid,剩下31個位元組的content。由於content是字元串,所以有結束符"0x00",有效的30個字元內容是:

rbsp trailing bits

47個payload位元組後的"0x80"(灰底色)即是rbsp trailing bits,在user_data_unregistered()裡面都是按位元組寫入的,所以此時的NAL unit結尾寫入的位元組一定是0x80。

SEI的生成

生成SEI的方式很多,大致可以有:

1.對已有碼流做filter,插入SEI NAL

2.視頻編碼時生成SEI

3.容器層寫入時插入SEI

以下代碼示例來自於FFmpeg origin/master 分支。

bsf

BitStream Filter(碼流過濾)的縮寫為bsf,它的作用是,在不做碼流解碼的前提下,對已經編碼後的比特流做特定的修改、調整。

bsf h264_metadata的調用

使用ffmpeg工具時,可以使用比特流過濾器。基本的filter調用格式如下:

從上文提到的mp4文件中提取出h.264碼流oceans.h264,可以使用 h264_metadata比特流過濾器添加SEI。下面示例命令添加了類型為未註冊的用戶數據的SEI,其中uuid為"086f3693-b7b3-4f2c-9653-21492feee5b8",payload內容為"hello":

其中oceans.h264已經有一個SEI和28個SPS。輸出的oceans.sei.h264碼流中,共有28個SEI,其中第一個與輸入保持一致,剩下27個為新插入的SEI。

bsf h264_metadata的代碼分析

具體代碼位於:libavcodec/h264_metadata_bsf.c中。

以上代碼是h264_metadata添加SEI的判斷邏輯,當指定了sei_user_data時,滿足以下條件之一即可以處理:

·讀取的access units是第一個au;

·當前au包含sps;

滿足插入SEI邏輯後,具體處理過程中:

·如果發現第一個NAL已經是SEI,則該au不做插入SEI處理;

·如果au包含了IDR幀或者非IDR未分區的幀,則在其前面插入SEI信息。

基於以上代碼,oceans.sei.h264碼流中新插入27個新的SEI 符合處理邏輯。

具體構造SEI NAL Unit代碼如下:

代碼完整解釋了上文提到的SEI規範,其中"H264_SEI_TYPE_USER_DATA_UNREGISTERED"值為5,對應的即是未註冊的用戶信息。在解析"ffmpeg"工具輸入過程中,將"+"號前面的字元串轉換成二進位寫入uuid,"+"後內容使用字元串寫入payload。

x264

libx264支持多種SEI類型數據寫入,常用的仍然是SEI_USER_DATA_UNREGISTERED,具體的寫入函數x264_sei_version_write()位於libx264/encoder/set.c中。

libx264提供的uuid和上文舉例的uuid一致,payload中主要記錄了相關參數和版權信息。以上函數完成了SEI參數的構造,下面的函數x264_sei_write完成了具體語法的寫入:

以上寫入的代碼邏輯和標準語法說明保持一致。

解析SEI

FFmpeg在讀取和解碼NAL unit,都有相同的邏輯處理SEI。

讀取或者解碼數據時,會調用下面函數進行碼流的解碼,其中buf包含具體的二進位流,buf_size是當前碼流長度。函數內部會解析碼流並實例出具體的NAL對象:

如果NAL對象類型是SEI 時,將調用以下函數解碼:

可以看到,根據SEI語法標準,在解析了SEI payload type和length後,對未註冊用戶數據的提取,跳過了uuid的分析,只嘗試提取了x264的build信息。總體上,並未利用SEI_USER_DATA_UNREGISTERED傳遞過來的其他相關參數信息。

從解碼器邏輯看,H264SEIUnregistered結構體只有一個x264_build屬性,並未返回實質有效數據。上層業務如果需要提取SEI_USER_DATA_UNREGISTERED,仍然需要自己提取。提取邏輯,請參考下一小節(ffplay)。

ffplay

ffplay是一個簡單、常用的FFmpeg介面示例工具,常用於測試解碼、播放效果。如果在ffplay中示例跑通SEI提取功能,可以很方便的移植到其他平台。

在ffplay中通過函數av_read_frame(ic, pkt)返回後,讀取pkt->data可以快速拿到當前讀到的NAL unit。從data數據中取出NAL unit type,如果是SEI且是用戶未註冊數據類型(payload type值為5),則可以參考SEI語法繼續讀取UUID和其後傳遞的字元串。

本文主要對H.264碼流中涉及用戶未註冊數據的SEI進行了分析。總體而言,SEI只是視頻標準裡面很小的一部分,但在應用過程中,比如直播問答項目中SEI承載的信息,就極大提升了直播觀看和答題操作的整體用戶體驗。所以說,從SEI的例子中,我們就會發現,視頻標準裡面還有很多金礦等待著大家的挖掘,這就是多媒體技術的魅力,也是金山視頻雲努力的方向。


推薦閱讀:

H.265/HEVC是為4k設計還是8k啊?
視頻編碼標準HEVC的技術亮點?
VCB-S組的h265和Yousei-raws的h264哪個比較好?
能提供免費h.265雲轉碼(壓制)?
如何學習 H.264/H.265 協議?

TAG:視頻直播 | HEVCH265 |