移動開發(fā)平臺視頻播放器探索
背景
APP開發(fā)中需要使用視頻播放器播放視頻雪隧,我們采用的是開源的第三方播放器IJK砰苍。雖然IJK功能強(qiáng)大吏祸,但是還是遇到不少問題揭糕。這里以iOS平臺為例子記錄下平時開發(fā)中學(xué)習(xí)到的知識和碰到的問題,以及如何解決屎即。
視頻播放基本流程
IJK是基于ffmpeg二次開發(fā)庙睡,其編解碼依賴于ffmpeg和videoToolBox,視頻顯示采用的是SDL(基于openGL實(shí)現(xiàn))技俐。整個視頻到畫面的流程我簡單用一張圖概括下乘陪。
流程看上去簡單,但是其中還涉及了多線程處理雕擂、圖形學(xué)知識啡邑、音視頻倍速播放等等知識,涉及的技術(shù)點(diǎn)很多井赌,想學(xué)透還是比較困難的谣拣。下面我將挑幾個基礎(chǔ)的簡單介紹募寨。
知識點(diǎn):視頻解碼I幀族展、B幀森缠、P幀以及視頻播放順序
視頻其實(shí)是一張張圖片按照時間點(diǎn)連續(xù)顯示到畫面上而形成的。如果視頻的清晰度很高仪缸,每張圖片都很大贵涵,那么在網(wǎng)絡(luò)上傳輸所付出代價就很大。所以視頻都不是原圖完完整整的保存的恰画,而是需要進(jìn)行"壓縮"宾茂,這個過程就是編碼,而按照什么規(guī)則"壓縮"就是編碼格式拴还,比如h265/h264跨晴。
在H.264壓縮標(biāo)準(zhǔn)中,編碼后,圖片會變成I幀片林,B幀端盆,P幀。
I幀
I幀其實(shí)就是完整的圖片费封,其沒有壓縮焕妙,可以直接用來顯示
P幀
P幀是基于前面的I幀進(jìn)行編碼的,所以要得到P幀原始的圖片需要前面I幀或者P幀的數(shù)據(jù)弓摘。
B幀
B幀是基于前面的幀和后面的幀一起編碼的焚鹊,要叨叨B真原始圖片需要前后2個幀。
視頻播放順序和視頻解碼順序
由于B幀的存在韧献,所以含有B幀視頻的解碼順序和播放順序是不一致的末患,比如
所以有B幀的視頻播放順序不等于解碼順序。
實(shí)際遇到問題1
前段時間遇到一個視頻播放卡頓的問題锤窑,畫面播放不連續(xù)璧针。一開始以為是碼率不夠?qū)е碌奶鴰髞斫?jīng)過調(diào)試發(fā)現(xiàn)果复,音畫同步并沒有跳過幀陈莽,而是在排序隊(duì)列出口有大量的跳幀。這個問題比較奇怪虽抄,于是我對該過程進(jìn)行了模擬走搁。
由于B幀的存在,解碼的順序不等于播放順序迈窟,所以在解碼出幀后需要將現(xiàn)有幀根據(jù)播放順序排序击碗,IJK的設(shè)計是將幀放入排序隊(duì)列,并且保證隊(duì)列里有2幀以上才允許出隊(duì)列锄贼。那么上圖播放流程如下圖:
目前按正常思路來看是正常的,2隊(duì)列深度不應(yīng)該出現(xiàn)跳幀情況索绪,但是事實(shí)調(diào)試確實(shí)出現(xiàn)了,這就比較奇怪了贫悄。后續(xù)查資料發(fā)現(xiàn) B幀是依賴P幀和I幀解碼瑞驱,那會不會B幀不是按照隊(duì)列先進(jìn)先出,而是先進(jìn)后出呢窄坦?圖調(diào)整如下唤反,不一樣的地方已經(jīng)標(biāo)紅:
是否規(guī)定B幀一定要先進(jìn)先出這個沒法考證,硬解碼返回的順序完全依賴VideoToolBox的代碼實(shí)現(xiàn)鸭津,并且IJK有多年未維護(hù)彤侍,很可能是該問題導(dǎo)致。修改方案是將排序隊(duì)列擴(kuò)大逆趋,避免丟幀的情況出現(xiàn)盏阶。
知識點(diǎn):RGB和YUV
RGB和YUV都是圖像對顏色的編碼方式,簡單理解就是2中不同的方式來描述顏色闻书,視頻幀里面就是存儲這些顏色信息名斟。一個“屏幕”有許多像素點(diǎn),幀數(shù)據(jù)就是告知“屏幕”中每一個像素點(diǎn)要顯示什么顏色惠窄,然后所有像素點(diǎn)都顯示自己的顏色后就形成了圖片蒸眠。
RGB就比較簡單了,就是三原色杆融,每個像素點(diǎn)都有紅楞卡、綠、藍(lán)個“燈泡”,像素點(diǎn)根據(jù)幀傳進(jìn)來的3個值來調(diào)整3個燈泡的亮度脾歇,光顏色疊加就能顯示不同的顏色蒋腮。
YUV是被歐洲電視系統(tǒng)所采用的一種顏色編碼方法,之前都是黑白電視藕各,RGB對黑白電視兼容性不好池摧,YUV可以。Y”表示明亮度(Luminance或Luma)激况,也就是灰階值作彤;而“U”和“V” 表示的則是色度(Chrominance或Chroma),作用是描述影像色彩及飽和度乌逐,用于指定像素的顏色竭讳。那現(xiàn)在顯示屏是怎么用YUV顯示的呢?其實(shí)很簡單浙踢,就是將YUV根據(jù)算法轉(zhuǎn)換成RGB绢慢,然后采用RGB的方式進(jìn)行顯示。
IJK播放器從幀轉(zhuǎn)換成圖像的過程簡化如下:
圖像顯示這塊講述比較簡單洛波,具體可以參考其他資料胰舆,這里不再贅述骚露。
實(shí)際問題2
近期在播放視頻的時候發(fā)現(xiàn)視頻播放畫面顯示異常,顯示如下:
起初以為是解碼問題缚窿,因?yàn)橹苯雍谄良摇5亲屑?xì)看,還有一些紅色的影像在動滨攻。那么就先定位是什么問題導(dǎo)致够话。先通過電腦端測試視頻,發(fā)現(xiàn)可以播放光绕,說明視頻本身沒有問題。通過調(diào)試畜份,發(fā)現(xiàn)可以正常解碼出幀诞帐,并且音畫同步和排序隊(duì)列出幀扣都沒有丟幀的情況。那么就可以初步定位問題出在幀->渲染這層爆雹。下面繼續(xù)判斷是解出的幀有問題還是渲染有問題停蕉。
videoToolBox解出的數(shù)據(jù)是CVImageBufferRef(可以參考IJKVideoToolBoxSync.m中VTDecoderCallback回調(diào)),我們可以通過代碼,將CVImageBufferRef轉(zhuǎn)換成UIimage再在頁面上顯示钙态,看看幀是否能正常顯示慧起。結(jié)果可以顯示,那么問題就出在渲染層了册倒。
IJK的渲染是靠SDL蚓挤,其核心還是openGL,又由于該視頻是硬解碼驻子,所以直接定位到其render文件renderer_yuv420sp_vtb.m灿意。下斷點(diǎn)后發(fā)現(xiàn),在openGL生成YUV Y層貼圖時報錯:
嗯崇呵,Y層貼圖報錯缤剧,是剩UV層,Y層是表示亮度域慷,出錯后顯示黑色合情合理荒辕。但是該怎么修改呢,前面已經(jīng)證明幀數(shù)據(jù)沒有問題犹褒,那為什么Y層貼圖會報錯呢抵窒?并且這個錯誤也沒有具體回調(diào),線索就此斷了化漆,只知道是這里出問題估脆,但是無法修改,嘗試調(diào)整SDL幾個參數(shù)無果座云,陷入僵局疙赠。
后來點(diǎn)擊該方法付材,發(fā)現(xiàn)OPENGLES_DEPRECATED(ios(3.0, 12.0) 這么一句話,openGL在iOS12就被廢棄了圃阳?通過查資料厌衔,原來openGL在iOS12之后就不建議使用,蘋果采用替代方案Metal全面替代openGL捍岳,并且性能據(jù)說比之前高10倍富寿。開發(fā)文檔:開發(fā)文檔 IJK渲染層只是將幀轉(zhuǎn)化成圖像,只要幀已經(jīng)正確解出來并且格式固定锣夹,那么是采用OPENGL渲染還是采用Metal渲染對整個視頻播放流程無影響页徐。因此我們決定嘗試采用Metal全面替換openGL。
經(jīng)過幾天嘗試Metal银萍,發(fā)現(xiàn)Metal的寫法與OpenGL十分相似变勇,而且采用OC編寫面向?qū)ο螅梢灾苯佑肁RC管理內(nèi)存贴唇,省去了釋放內(nèi)存的煩惱搀绣。果然還是蘋果自家的東西好用。戳气。簡單的思路是在IJK播放器蓋一個MTKView瓶您,當(dāng)使用Metal渲染時將MTKView解除隱藏芯肤,將解析出來的CVPixelBufferRef一層層傳出來,遞給MTKView顯示击蹲。
優(yōu)勢:
1、CVPixelBufferRef-> MTKView都是蘋果的類类咧,兼容性好区宇,不會像openGL出現(xiàn)異常報錯
2、MTKView的性能更高
3卧晓、面向?qū)ο缶幊虄?nèi)存釋放有保障
劣勢:
1螟炫、有一定門檻昼钻,有openGL開發(fā)經(jīng)驗(yàn)更容易理解狈究。
調(diào)整后的視頻如下: