Android與IOS端h264硬解關(guān)鍵流程梳理

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還有以下的一些限制需要留意:

  1. 如果源視頻流本身已經(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é)。
  2. 如果一個(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)上呕寝,可以:

  1. 在TextureView的onSurfaceTextureAvailable回調(diào)中保存當(dāng)前創(chuàng)建的SurfaceTexture;
  2. App切后臺時(shí)勋眯,TextureView的onSurfaceTextureDestroyed回調(diào)中返回false,不讓系統(tǒng)銷毀當(dāng)前的SurfaceTexture;
  3. 在下一次App切回前臺下梢,onSurfaceTextureAvailable回調(diào)中客蹋,將前面保存的SurfaceTexture通過setSurfaceTexture接口設(shè)置給TextureView,并銷毀回調(diào)參數(shù)中傳回的surfaceTexture;
  4. 播放器銷毀時(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í)踐中送挑,我們可能會遇到如下圖的這種情況(上面一部分有畫面绑莺,下面部分是綠屏):

v2-62cbcb1506428c62b21d9cfd5f421992_hd.png

這種現(xiàn)象實(shí)際上就是多slice視頻的組織格式不符合VideoToolBox的要求引起的。

以上圖的視頻為例惕耕,該視頻流的每一幀是由3個(gè)slice構(gòu)成的纺裁,對于VideoToolBox可以正常解碼的組織格式應(yīng)該如下圖所示:

1.png

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

可以看出,該視頻混用了AVCC與Annex-B格式的分隔符司澎,導(dǎo)致iOS VideoToolBox只能解碼第一個(gè)slice單元对扶,從而出現(xiàn)下半部分綠屏的情況。

  • 對于這類問題視頻的處理: 如果是源視頻流可控惭缰,可以調(diào)整源視頻流的打包方式浪南,按第一種圖示的方式打包。
  • 對于不可控的場景,播放器也可以做下兼容:因?yàn)橐粋€(gè)NALU中的內(nèi)容一定是不包含startcode的,所以如果在一個(gè)NALU中找到了startcode妻献,就可以將其處理成第一種圖示中的格式稳析。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖怨愤,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蛹批,居然都是意外死亡撰洗,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門腐芍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來差导,“玉大人,你說我怎么就攤上這事猪勇∩韬郑” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長助析。 經(jīng)常有香客問我犀被,道長,這世上最難降的妖魔是什么外冀? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任寡键,我火速辦了婚禮,結(jié)果婚禮上锥惋,老公的妹妹穿的比我還像新娘昌腰。我一直安慰自己开伏,他們只是感情好膀跌,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著固灵,像睡著了一般捅伤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上巫玻,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天丛忆,我揣著相機(jī)與錄音,去河邊找鬼仍秤。 笑死熄诡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的诗力。 我是一名探鬼主播凰浮,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼苇本!你這毒婦竟也來了袜茧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤瓣窄,失蹤者是張志新(化名)和其女友劉穎笛厦,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體俺夕,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡裳凸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了劝贸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片登舞。...
    茶點(diǎn)故事閱讀 38,100評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖悬荣,靈堂內(nèi)的尸體忽然破棺而出菠秒,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布践叠,位于F島的核電站言缤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏禁灼。R本人自食惡果不足惜管挟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望弄捕。 院中可真熱鬧僻孝,春花似錦、人聲如沸守谓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽斋荞。三九已至荞雏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間平酿,已是汗流浹背凤优。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蜈彼,地道東北人筑辨。 一個(gè)月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像幸逆,于是被迫代替她去往敵國和親棍辕。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評論 2 345