移動端硬解關鍵流程梳理

介紹移動端Android/iOS硬解用法的文章有很多,本文將以筆者在實際開發工作中的經驗爲基礎,抽出幾個比較關鍵的部分來跟大家分享,旨在解決實際工作中可能遇到的花屏、(半邊)綠屏、播放不完整等問題。
本文將以目前廣泛應用的H.264編碼的視頻爲例來說明,主要包含:H.264碼流數據結構說明、解碼器的初始化、seek、前後臺切換、無縫分辨率切換、播放結束時的處理以及iOS如何避免下半部分綠屏的問題。

一、H.264碼流數據結構說明
1. 理解碼流數據結構的重要性
我們講支持硬解,提高硬解兼容性,實際上就是對碼流數據的結構進行處理以符合平臺硬解要求,因此對碼流數據結構的理解是必不可少的。
2. SPS/PPS與IDR幀
SPS(Sequence Parameter Set)序列參數集、PPS(Picture Parameter Set)圖像參數集,包含了圖像編碼的各種參數信息,是作爲解碼器初始化所必須的參數信息。
IDR(Instantaneous Decoding Refresh)幀,也就是即時解碼刷新幀,直觀意思就是解碼器在接收到IDR幀後會刷新參考幀緩存。IDR幀前後的視頻幀不會有任何參考關係,解碼器可以從任何一個IDR幀開始解碼。
3. H.264的NAL單元
NALU結構圖示:

clipboard.png

H.264標準中,視頻流是由NAL(Network Abstraction Layer)單元組成的(簡稱NALU),每個NALU中可能是IDR圖像、SPS、PPS、non-IDR圖像等。
上圖中示意的NALU單元是以startcode方式分割的,關於NALU的分割方式將在後面說明。 另外,NALU內容中添加了防競爭字節,也就是說在一個NALU中,我們不可能再找到匹配的startcode.
H.264流的NALU組成圖示

clipboard.png

從上圖可以看到,一個視頻幀中可能可能包含多個NALU, 此時可以稱該視頻幀爲多slice視頻幀(一個NALU中包含該視頻幀的一個slice)。
NAL Header的結構說明

clipboard.png

其中nal_unit_type是我們關心的字段,該字段標識了當前NALU的類型,我們可以通過將NALU中第一個字節&0x1F的方式來得到NALU類型。NALU類型的具體定義如下圖所示:
NALU類型定義

clipboard.png

其中5代表上面提到的IDR幀數據,7、8分別代表SPS/PPS數據。

  1. AVCC與Annex-B

H.264碼流分爲AVCC與Annex-B兩種組織格式。
• AVCC格式 也叫AVC1格式,MPEG-4格式,字節對齊,因此也叫Byte-Stream Format。用於mp4/flv/mkv等封裝中。
• Annex-B格式 也叫MPEG-2 transport stream format格式(ts格式), ElementaryStream格式。用於TS流中(以及使用TS作爲切片的hls格式中)。
這兩種格式的區別有兩點:
(1)NALU的分割方式不同;
(2)SPS/PPS的數據結構不同。
• AVCC格式使用NALU長度(固定字節,字節數由extradata中的信息給定)進行分割,在封裝文件或者直播流的頭部包含extradata信息(非NALU),extradata中包含NALU長度的字節數以及SPS/PPS信息。
• Annex-B格式使用start code進行分割,start code爲0x000001或0x00000001,SPS/PPS作爲一般NALU單元以start code作爲分隔符的方式放在文件或者直播流的頭部。
AVCC格式的extradata格式定義在“ISO_IEC_14496-15"文檔中,Annex-B格式的SPS/PPS定義可以在"ISO_IEC_14496-10"文檔中找到。
MediaCodec與VideoToolBox使用的數據格式
Android的硬解碼接口MediaCodec只能接收Annex-B格式的H.264數據,而iOS平臺的VideoToolBox則相反,只支持AVCC格式。
這就導致:
• 在Android平臺硬解播放flv/mp4/mkv等封裝的視頻時,需要將AVCC格式的extradata以及NALU數據轉爲Annex-B格式;
• 在iOS平臺播放ts或ts切片的hls視頻時,需要將Annex-B格式的SPS/PPS NALU轉爲AVCC格式的extradata,以及將其他以size方式分割的NALU轉爲start code方式。

二、解碼器的初始化及數據輸入
初始化解碼器,除了配置輸入視頻流的的編碼格式、寬高以及輸出格式之外,還需要配置一些額外的信息。 對於H.264視頻,需要填充的就是我們前面提到的SPS/PPS信息。
1. Android平臺MediaCodec的初始化
我們需要將Annex-B格式的兩個SPS/PPS NALU單元通過setByteBuffer方法,以"csd-0"爲名稱(或SPS設爲"csd-0", PPS設爲"csd-1")設置到MediaFormat對象中,並調用configure接口配置到MediaCodec中去。
MediaCodec設置SPS/PPS信息的示例代碼
MediaCodec mediaCodec = MediaCodec.createDecoderByType("video/avc");
MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height);
// extradata中是Annex-B格式的SPS、PPS NALU數據
mediaFormat.setByteBuffer("csd-0", extradata);
// ...
mediaCodec.configure(mediaFormat, surface, 0, 0);
// ...
如上節所述,對於mp4/flv/mkv等封裝,我們得到的是AVCC格式的extradata,需要先將該extradata轉換爲Annex-B格式的兩個NALU, 然後用startcode進行分割。
Android平臺在配置解碼方式時,最好使用MediaCodec直接渲染到Surface的方式,一是可以避免不同硬件平臺繁雜的YUV格式兼容,二是在解碼渲染高分辨率的視頻時可以有非常明顯的效率提升。
2. iOS平臺VideoToolBox接口的初始化
VideoToolBox針對AVCC格式和Annex-B格式的SPS/PPS信息設置,分別提供了兩個方法:
• CMVideoFormatDescriptionCreate: 可以設置AVCC格式的extradata信
• CMVideoFormatDescriptionCreateFromH264ParameterSets: 用來設置Annex-B格式的SPS/PPS NALU信息(需要去掉startcode)
需要注意,iOS平臺不支持隔行H.264視頻的解碼,需要在創建videoToolBox前從SPS中判斷當前視頻是否隔行編碼。
3. 數據格式的轉換
如前所述,Android平臺只接受Annex-B格式以startcode分割的H.264 NALU;iOS平臺則相反,只接受AVCC格式以size分割的NALU. 在原視頻流格式不匹配時需要進行相應的轉換。
iOS還有以下的一些限制需要留意:
(1)如果源視頻流本身已經是AVCC格式,但NALU size的大小是3個字節,而非4字節時,需要轉爲4字節格式。具體的話,需要先更改extradata中標識NALU size的字段,然後每個視頻幀中的NALU size都要改成4個字節。
(2)如果一個視頻幀內有多個NALU(多slice),那必須將這些NALU打包到一個CMSampleBuffer中,一次性送給解碼器。

三、seek時的處理
編碼後的視頻幀之間存在着參考關係,我們無法直接從任意一幀開始解碼,只能從可隨機訪問幀開始,在H.264中就是IDR幀。
1. 從IDR幀開始解碼
對於點播視頻,mp4/flv/mkv的頭信息中都會保存整個視頻的IDR幀索引,seek時需要定位到原seek位置附近的IDR幀再送數據給解碼器。 如果要實現短視頻中的精確seek邏輯,可以先seek到離目標位置最近的上一個IDR幀開始解碼,但不輸出圖像,直到目標位置的視頻被解碼出來。
2. 刷新解碼器
進行seek操作時,除了要保證從IDR幀開始之外,還需要在送新的IDR幀數據前對解碼器進行刷新操作。
• Android平臺可以通過調用MediaCodec的flush()接口來實現。
• iOS平臺則需要重新創建videoToolBox.

四、前後臺切換
對Android、iOS平臺,都存在App切後臺,播放器渲染View被銷燬而導致解碼出錯的情況。
1. 切回前臺的處理
App切到後臺時,iOS的videoToolBox session會失效,切回前臺後原session也不能繼續使用,需重新創建videoToolBox實例;Android平臺在配置了Surface的情況下,如果Surface被銷燬,則在切回前臺時也需要配置新的Surface來重新創建並初始化MediaCodec.
如果我們要提高用戶體驗,實現前後臺切換時的無縫播放,而不是重新拉流,那麼可以在用戶切後臺的時候暫停播放,切回前臺時重新創建解碼器,繼續從原位置開始播放。
不過參考前面seek章節的說明,我們恢復播放的位置很可能不是IDR幀,這種情況下就會出現切回前臺後畫面會先黑一段時間,直到下一個IDR幀被解碼。黑屏的時間會跟視頻流的IDR幀間隔有關,最差情況下黑屏時間接近IDR幀間隔。 爲了儘量避免黑屏現象的出現,我們可以參考前面精確seek的處理,在解碼過程中一直緩存當前GOP(Group Of Picture)的視頻幀數據,在恢復時從當前GOP的IDR幀開始解碼但不輸出圖像,直到恢復點。
不過上述方案也無法100%解決黑屏問題,解碼恢復點前的視頻數據本身會有時間消耗,GOP越大,解碼恢復可能需要的時間也就越長,黑屏時間也就會越長。
2. Android平臺使用TextureView避免Surface被銷燬
對Android平臺,我們也可以通過使用TextureView渲染來儘量避免Surface被銷燬。
具體實現上,可以:
(1)在TextureView的onSurfaceTextureAvailable回調中保存當前創建的SurfaceTexture;
(2)App切後臺時,TextureView的onSurfaceTextureDestroyed回調中返回false,不讓系統銷燬當前的SurfaceTexture;
(3)在下一次App切回前臺,onSurfaceTextureAvailable回調中,將前面保存的SurfaceTexture通過setSurfaceTexture接口設置給TextureView,並銷燬回調參數中傳回的surfaceTexture;
(4)播放器銷燬時,需要銷燬保存的surfaceTexture.

五、無縫分辨率切換的處理
考慮到用戶網絡的差異性,以及不同時間段的擁堵狀況不同,爲了兼顧拉流清晰度與流暢度,我們可以通過實時檢測用戶的網絡情況,並動態切換視頻的分辨率、碼率來提高播放體驗。
rtmp直播,http/flv直播,hls直播以及hls點播可以支持動態分辨率切換。
分辨率切換時需要拿到新的SPS/PPS並重啓解碼器
• 對於rtmp, http/flv直播,以及mp4分片的hls視頻,分辨率切換時我們能夠拿到新的AVCC格式的extradata(使用ffmpeg解封裝時這個信息是在AVPacket的sidedata中), 此時需要用新的extradata數據重新創建解碼器,所需的分辨率信息可以從extradata中解析出來。
• 而對於ts切片的hls直播點播視頻,SPS/PPS信息是以Annex-B格式保存在正常的NALU中,而且每個IDR幀前都會有SPS/PPS的NALU。對此,我們需要監控每個收到的視頻包,獲取其NALU類型,如果是SPS/PPS, 則從中解析出分辨率等信息,如果有變化,則用新的SPS/PPS重新創建解碼器。

六、播放完成時避免遺漏最後幾幀
前面我們提到過,編碼後的視頻幀之間存在着參考關係,而且存在雙向參考幀(B幀)的視頻流其解碼輸出順序和輸入的順序是不同的,同時解碼器在異步模式下也不會立即返回解碼後的視頻幀,這就導致我們在輸入最後一幀數據給解碼器後,可能還會有一些視頻幀沒有輸出。
爲了避免遺漏最後幾幀的情況,我們需要做一些處理:
• Android平臺需要給MediaCodec送入一個帶有BUFFER_FLAG_END_OF_STREAM標記的buffer數據(可以是空buffer),然後等待MediaCodec輸出帶有該標記的內容,再銷燬解碼器,結束播放。
• iOS平臺需要在送完最後一幀數據後,調用VTDecompressionSessionWaitForAsynchronousFrames接口,該接口會等待所有未輸出的視頻幀輸出結束後再返回。

七、VideoToolBox兼容不標準的多slice視頻
在iOS平臺的硬解的實踐中,我們可能會遇到如下圖的這種情況(上面一部分有畫面,下面部分是綠屏):

clipboard.png

這種現象實際上就是多slice視頻的組織格式不符合VideoToolBox的要求引起的。
以上圖的視頻爲例,該視頻流的每一幀是由3個slice構成的,對於VideoToolBox可以正常解碼的組織格式應該如下圖所示:

clipboard.png

而該視頻的幀組織方式則如下圖所示:

clipboard.png

可以看出,該視頻混用了AVCC與Annex-B格式的分隔符,導致iOS VideoToolBox只能解碼第一個slice單元,從而出現下半部分綠屏的情況。
• 對於這類問題視頻的處理: 如果是源視頻流可控,可以調整源視頻流的打包方式,按第一種圖示的方式打包。
• 對於不可控的場景,播放器也可以做下兼容:因爲一個NALU中的內容一定是不包含startcode的,所以如果在一個NALU中找到了startcode,就可以將其處理成第一種圖示中的格式。

想要閱讀更多技術乾貨、行業洞察,歡迎關注網易雲信博客
瞭解網易雲信,來自網易核心架構的通信與視頻雲服務。

__
網易雲信(NeteaseYunXin)是集網易18年IM以及音視頻技術打造的PaaS服務產品,來自網易核心技術架構的通信與視頻雲服務,穩定易用且功能全面,致力於提供全球領先的技術能力和場景化解決方案。開發者通過集成客戶端SDK和雲端OPEN API,即可快速實現包含IM、音視頻通話、直播、點播、互動白板、短信等功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章