AVCC與Annex-B
H264碼流分為AVCC與Annex-B兩種組織格式。
AVCC格式 也叫AVC1格式嘴秸,MPEG-4格式毁欣,字節(jié)對齊,因此也叫Byte-Stream Format岳掐。用于mp4/flv/mkv等封裝中凭疮。
Annex-B格式 也叫MPEG-2 transport stream format格式(ts格式), ElementaryStream格式。用于TS流中(以及使用TS作為切片的hls格式中)串述。
這兩種格式的區(qū)別有兩點(diǎn): 1. NALU的分割方式不同执解; 2. SPS/PPS的數(shù)據(jù)結(jié)構(gòu)不同。
- AVCC格式使用NALU長度(固定字節(jié)纲酗,字節(jié)數(shù)由extradata中的信息給定)進(jìn)行分割衰腌,在封裝文件或者直播流的頭部包含extradata信息(非NALU),extradata中包含NALU長度的字節(jié)數(shù)以及SPS/PPS信息觅赊。
- Annex-B格式使用start code進(jìn)行分割右蕊,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使用的數(shù)據(jù)格式
Android的硬解碼接口MediaCodec只能接收Annex-B格式的H264數(shù)據(jù),而iOS平臺的VideoToolBox則相反规脸,只支持AVCC格式坯约。
這就導(dǎo)致:
在Android平臺硬解播放flv/mp4/mkv等封裝的視頻時(shí)熊咽,需要將AVCC格式的extradata以及NALU數(shù)據(jù)轉(zhuǎn)為Annex-B格式莫鸭;
在iOS平臺播放ts或ts切片的hls視頻時(shí),需要將Annex-B格式的SPS/PPS NALU轉(zhuǎn)為AVCC格式的extradata横殴,以及將其他以size方式分割的NALU轉(zhuǎn)為start code方式被因。
解碼器的初始化及數(shù)據(jù)輸入
初始化解碼器卿拴,除了配置輸入視頻流的的編碼格式、寬高以及輸出格式之外梨与,還需要配置一些額外的信息堕花。對于H264視頻,需要填充的就是我們前面提到的SPS/PPS信息粥鞋。
Android平臺MediaCodec的初始化
我們需要將Annex-B格式的兩個(gè)SPS/PPS NALU單元通過setByteBuffer方法缘挽,以"csd-0"為名稱(或SPS設(shè)為"csd-0", PPS設(shè)為"csd-1")設(shè)置到MediaFormat對象中,并調(diào)用configure接口配置到MediaCodec中去呻粹。
MediaCodec設(shè)置SPS/PPS信息的示例代碼
MediaCodec mediaCodec = MediaCodec.createDecoderByType("video/avc");
MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height);
// extradata中是Annex-B格式的SPS壕曼、PPS NALU數(shù)據(jù)
mediaFormat.setByteBuffer("csd-0", extradata);
// ...
mediaCodec.configure(mediaFormat, surface, 0, 0);
// ...
如上節(jié)所述,對于mp4/flv/mkv等封裝等浊,我們得到的是AVCC格式的extradata腮郊,需要先將該extradata轉(zhuǎn)換為Annex-B格式的兩個(gè)NALU, 然后用startcode進(jìn)行分割。
Android平臺在配置解碼方式時(shí)筹燕,最好使用MediaCodec直接渲染到Surface的方式轧飞,一是可以避免不同硬件平臺繁雜的YUV格式兼容,二是在解碼渲染高分辨率的視頻時(shí)可以有非常明顯的效率提升
iOS平臺VideoToolBox接口的初始化
VideoToolBox針對AVCC格式和Annex-B格式的SPS/PPS信息設(shè)置撒踪,分別提供了兩個(gè)方法:
- CMVideoFormatDescriptionCreate: 可以設(shè)置AVCC格式的extradata信
- CMVideoFormatDescriptionCreateFromH264ParameterSets: 用來設(shè)置Annex-B格式的SPS/PPS NALU信息(需要去掉startcode)
需要注意过咬,iOS平臺不支持隔行H264視頻的解碼,需要在創(chuàng)建videoToolBox前從SPS中判斷當(dāng)前視頻是否隔行編碼制妄。
數(shù)據(jù)格式的轉(zhuǎn)換
如前所述援奢,Android平臺只接受Annex-B格式以startcode分割的H264 NALU;iOS平臺則相反忍捡,只接受AVCC格式以size分割的NALU. 在原視頻流格式不匹配時(shí)需要進(jìn)行相應(yīng)的轉(zhuǎn)換集漾。
iOS還有以下的一些限制需要留意:
- 如果源視頻流本身已經(jīng)是AVCC格式,但NALU size的大小是3個(gè)字節(jié)砸脊,而非4字節(jié)時(shí)具篇,需要轉(zhuǎn)為4字節(jié)格式。具體的話凌埂,需要先更改extradata中標(biāo)識NALU size的字段驱显,然后每個(gè)視頻幀中的NALU size都要改成4個(gè)字節(jié)。
- 如果一個(gè)視頻幀內(nèi)有多個(gè)NALU(多slice)瞳抓,那必須將這些NALU打包到一個(gè)CMSampleBuffer中埃疫,一次性送給解碼器。
seek時(shí)的處理
編碼后的視頻幀之間存在著參考關(guān)系孩哑,我們無法直接從任意一幀開始解碼栓霜,只能從可隨機(jī)訪問幀開始,在H264中就是IDR幀横蜒。
從IDR幀開始解碼
對于點(diǎn)播視頻胳蛮,mp4/flv/mkv的頭信息中都會保存整個(gè)視頻的IDR幀索引销凑,seek時(shí)需要定位到原seek位置附近的IDR幀再送數(shù)據(jù)給解碼器。 如果要實(shí)現(xiàn)短視頻中的精確seek邏輯仅炊,可以先seek到離目標(biāo)位置最近的上一個(gè)IDR幀開始解碼斗幼,但不輸出圖像,直到目標(biāo)位置的視頻被解碼出來抚垄。
刷新解碼器
進(jìn)行seek操作時(shí)蜕窿,除了要保證從IDR幀開始之外,還需要在送新的IDR幀數(shù)據(jù)前對解碼器進(jìn)行刷新操作呆馁。
- Android平臺可以通過調(diào)用MediaCodec的flush()接口來實(shí)現(xiàn)渠羞。
- iOS平臺則需要重新創(chuàng)建videoToolBox.
前后臺切換
對Android、iOS平臺智哀,都存在App切后臺次询,播放器渲染View被銷毀而導(dǎo)致解碼出錯(cuò)的情況。
切回前臺的處理
App切到后臺時(shí)瓷叫,iOS的videoToolBox session會失效屯吊,切回前臺后原session也不能繼續(xù)使用,需重新創(chuàng)建videoToolBox實(shí)例摹菠;Android平臺在配置了Surface的情況下盒卸,如果Surface被銷毀,則在切回前臺時(shí)也需要配置新的Surface來重新創(chuàng)建并初始化MediaCodec.
如果我們要提高用戶體驗(yàn)次氨,實(shí)現(xiàn)前后臺切換時(shí)的無縫播放蔽介,而不是重新拉流,那么可以在用戶切后臺的時(shí)候暫停播放煮寡,切回前臺時(shí)重新創(chuàng)建解碼器虹蓄,繼續(xù)從原位置開始播放。
不過參考前面seek章節(jié)的說明幸撕,我們恢復(fù)播放的位置很可能不是IDR幀薇组,這種情況下就會出現(xiàn)切回前臺后畫面會先黑一段時(shí)間,直到下一個(gè)IDR幀被解碼坐儿。黑屏的時(shí)間會跟視頻流的IDR幀間隔有關(guān)律胀,最差情況下黑屏?xí)r間接近IDR幀間隔。 為了盡量避免黑屏現(xiàn)象的出現(xiàn)貌矿,我們可以參考前面精確seek的處理炭菌,在解碼過程中一直緩存當(dāng)前GOP(Group Of Picture)的視頻幀數(shù)據(jù),在恢復(fù)時(shí)從當(dāng)前GOP的IDR幀開始解碼但不輸出圖像逛漫,直到恢復(fù)點(diǎn)黑低。
不過上述方案也無法100%解決黑屏問題,解碼恢復(fù)點(diǎn)前的視頻數(shù)據(jù)本身會有時(shí)間消耗尽楔,GOP越大投储,解碼恢復(fù)可能需要的時(shí)間也就越長,黑屏?xí)r間也就會越長阔馋。
Android平臺使用TextureView避免Surface被銷毀
對Android平臺玛荞,我們也可以通過使用TextureView渲染來盡量避免Surface被銷毀。
具體實(shí)現(xiàn)上呕寝,可以:
- 在TextureView的onSurfaceTextureAvailable回調(diào)中保存當(dāng)前創(chuàng)建的SurfaceTexture;
- App切后臺時(shí)勋眯,TextureView的onSurfaceTextureDestroyed回調(diào)中返回false,不讓系統(tǒng)銷毀當(dāng)前的SurfaceTexture;
- 在下一次App切回前臺下梢,onSurfaceTextureAvailable回調(diào)中客蹋,將前面保存的SurfaceTexture通過setSurfaceTexture接口設(shè)置給TextureView,并銷毀回調(diào)參數(shù)中傳回的surfaceTexture;
- 播放器銷毀時(shí)孽江,需要銷毀保存的surfaceTexture.
無縫分辨率切換的處理
考慮到用戶網(wǎng)絡(luò)的差異性讶坯,以及不同時(shí)間段的擁堵狀況不同,為了兼顧拉流清晰度與流暢度岗屏,我們可以通過實(shí)時(shí)檢測用戶的網(wǎng)絡(luò)情況辆琅,并動態(tài)切換視頻的分辨率、碼率來提高播放體驗(yàn)这刷。
rtmp直播婉烟,http/flv直播,hls直播以及hls點(diǎn)播可以支持動態(tài)分辨率切換暇屋。
分辨率切換時(shí)需要拿到新的SPS/PPS并重啟解碼器
- 對于rtmp, http/flv直播似袁,以及mp4分片的hls視頻,分辨率切換時(shí)我們能夠拿到新的AVCC格式的extradata(使用ffmpeg解封裝時(shí)這個(gè)信息是在AVPacket的sidedata中), 此時(shí)需要用新的extradata數(shù)據(jù)重新創(chuàng)建解碼器咐刨,所需的分辨率信息可以從extradata中解析出來昙衅。
- 而對于ts切片的hls直播點(diǎn)播視頻,SPS/PPS信息是以Annex-B格式保存在正常的NALU中定鸟,而且每個(gè)IDR幀前都會有SPS/PPS的NALU绒尊。對此,我們需要監(jiān)控每個(gè)收到的視頻包仔粥,獲取其NALU類型婴谱,如果是SPS/PPS, 則從中解析出分辨率等信息,如果有變化躯泰,則用新的SPS/PPS重新創(chuàng)建解碼器谭羔。
播放完成時(shí)避免遺漏最后幾幀
前面我們提到過,編碼后的視頻幀之間存在著參考關(guān)系麦向,而且存在雙向參考幀(B幀)的視頻流其解碼輸出順序和輸入的順序是不同的瘟裸,同時(shí)解碼器在異步模式下也不會立即返回解碼后的視頻幀,這就導(dǎo)致我們在輸入最后一幀數(shù)據(jù)給解碼器后诵竭,可能還會有一些視頻幀沒有輸出话告。
為了避免遺漏最后幾幀的情況兼搏,我們需要做一些處理:
- Android平臺需要給MediaCodec送入一個(gè)帶有BUFFER_FLAG_END_OF_STREAM標(biāo)記的buffer數(shù)據(jù)(可以是空buffer),然后等待MediaCodec輸出帶有該標(biāo)記的內(nèi)容沙郭,再銷毀解碼器佛呻,結(jié)束播放。
- iOS平臺需要在送完最后一幀數(shù)據(jù)后病线,調(diào)用VTDecompressionSessionWaitForAsynchronousFrames接口吓著,該接口會等待所有未輸出的視頻幀輸出結(jié)束后再返回。
VideoToolBox兼容不標(biāo)準(zhǔn)的多slice視頻
在iOS平臺的硬解的實(shí)踐中送挑,我們可能會遇到如下圖的這種情況(上面一部分有畫面绑莺,下面部分是綠屏):
這種現(xiàn)象實(shí)際上就是多slice視頻的組織格式不符合VideoToolBox的要求引起的。
以上圖的視頻為例惕耕,該視頻流的每一幀是由3個(gè)slice構(gòu)成的纺裁,對于VideoToolBox可以正常解碼的組織格式應(yīng)該如下圖所示:
而該視頻的幀組織方式則如下圖所示:
可以看出,該視頻混用了AVCC與Annex-B格式的分隔符司澎,導(dǎo)致iOS VideoToolBox只能解碼第一個(gè)slice單元对扶,從而出現(xiàn)下半部分綠屏的情況。
- 對于這類問題視頻的處理: 如果是源視頻流可控惭缰,可以調(diào)整源視頻流的打包方式浪南,按第一種圖示的方式打包。
- 對于不可控的場景,播放器也可以做下兼容:因?yàn)橐粋€(gè)NALU中的內(nèi)容一定是不包含startcode的,所以如果在一個(gè)NALU中找到了startcode妻献,就可以將其處理成第一種圖示中的格式稳析。