標籤:nba 產品 播放器 iap 最佳化 called 加速 car 加密
https://zhuanlan.zhihu.com/p/27029577?utm_source=qq&utm_medium=social
Android我還可以相信你多少系列文章二之音視頻播放
音頻視頻播放在現在的應用裡面很常見,傳統應用發展到一定階段多少會引入音視頻資源,特別是現在短視頻被看作下一個增長爆發點,和之相關的創業層出不窮,作為開發人員如何進行音視頻技術選型非常關鍵
MediaPlayer和VideoView給我們提供了非常方便的播放音視頻的能力,幾乎不需要要寫幾行代碼就可以完成。
我們也可以使用MediaPlayer結合SurfaceView或者TextureView來實現視頻播放,本質和VideoView是一樣的,不過有更多的靈活性。
正因為封裝性太強,意味著定製化變弱。MediaPlayer提供的setDataSource方法支援http,file,content等協議,但仍然無法應對複雜的需求。所以更靈活的AudioTrack的出現,可以讓我們直接傳送解碼後的byte[]給他,帶來的問題就是自己要做解碼。解碼不是件簡單的事情,往往我們利用MediaCodec(Android4.1增加)或者外部解碼庫(比如ffmpeg)來實現。自己來實現解碼要特別注意不要丟失了硬體加速,音頻軟解碼還好,視頻解碼軟解碼對CPU壓力會大很多。
在做音視頻業務的時候,經常會遇到這樣幾個問題需要設定代理,或者邊播邊緩衝,緩衝加密,失敗重試,網路最佳化等等
因為我們無法幹涉MediaPlayer的網路請求部分,所以一般會將原始的播放地址http://xxx.com/playurl轉換成本機Proxy 位址http://127.0.0.1:port?url=htt...,這樣MediaPlayer就會來請求本機port連接埠上面起的一個代理服務,在這個代理端可以做很多最佳化邏輯,比如給真正發往服務端的請求加上代理;將請求到的資料寫入磁碟緩衝,這個代理端可以根據磁碟緩衝來按需請求服務端(使用http的Range參數);還有一些失敗重試等網路最佳化手段。這個代理層還有個特別的意義甚至可以接管webview裡面的audio和video標籤請求。
這種實現方式在實際運行中偶爾會出現本機代理無法啟動的情況,原因是Socket無法bind到指定連接埠,往往我們會在bind的時候指定讓系統來分配一個可用連接埠,所以這種失敗情況很有可能是root手機或者一些安全管理軟體禁用了許可權。
特別再說下邊播邊緩衝的實現,快取檔案允許空洞,每個快取檔案配備另外一個內容索引檔案,MediaPlayer本身會根據解碼情況發出多個帶Range的請求,根據內容索引檔案來確定當前請求從檔案哪個位置讀,接下去多少位元組從檔案讀,多少位元組從網路讀,網路讀的部分同時寫迴文件以保證下次請求可以複用,這樣就實現了一個邊播邊緩衝的邏輯,甚至我們還可以給本機快取檔案進行加密。同時這個快取檔案的載入百分比可以用來做UI介面上面的緩衝進度,監控下載速度進行網路請求最佳化。
2.MediaPlayer的Looper。新手往往可能不關心MediaPlayer的實現,開啟它的構造器前面幾行代碼我們就會看到他預設使用的是當前線程的Looper,如果當前線程不是個Looper線程則使用MainLooper。這一點比較重要,因為我們知道即使MediaPlayer運行在Service裡面,實際上還在跑在主線程,這樣的結果導致後續所有的MediaPlayer回調操作都跑在主線程,這可能是隱藏的一個定時炸彈。
更優雅的設計我們建議將MediaPlayer的回調和主動操作(stop,reset等操作)都放入work線程,操作的序列化是種最簡單的設計,也是最有效設計。大概的代碼形式是這樣的:
MediaPlayer在PlayHandlerThread裡面初始化,就保證了他裡面使用的Looper也是這個PlayHandlerThread的,這樣回調就都會在這個線程觸發,同時我們也在這個線程裡面做setDataSource等主動操作。
3.視頻播放本質上也是用MediaPlayer實現的,所以讀取資料上面沒有特別差異。現在比較熱的小視頻需要顯示在列表頁面支援滾動播放一個視頻,點擊在新頁面繼續觀看,一般採用MediaPlayer+TextureView來實現,MediaPlayer可以採用全域定義唯一一個,只是不同時刻把內容綁定顯示在不同的TextureView上而已。
4.MediaPlayer最大的問題還是在於其相容性。從我們的經驗來看可能會有這些問題:
——音頻格式支援不全(ape,wma等原生系統不支援),
——未緩衝完不開始播放,
——播放過程中突然沒有聲音,
——播放存在跳幀,
——mediaserver died;
——視頻播放只有聲音沒有畫面,
——視頻格式相容性差無法播放等。這些問題在系統基礎上基本無法解決。
最頭疼的問題是MediaPlayer返回的errorcode很多都是廠家擴充出來的,文檔上面提供的幾個值基本也是表意不清到底什麼問題。這給排查問題帶來很大麻煩。最最頭疼的是MediaPlayer的EventHandler裡面處理異常直接導致程式崩潰,比如像這樣:
11-04 13:43:08.966: E/AndroidRuntime(26482): java.lang.RuntimeException: failure code: -3211-04 13:43:08.966: E/AndroidRuntime(26482): at android.media.MediaPlayer.invoke(MediaPlayer.java:664)11-04 13:43:08.966: E/AndroidRuntime(26482): at android.media.MediaPlayer.getInbandTrackInfo(MediaPlayer.java:1692)11-04 13:43:08.966: E/AndroidRuntime(26482): at android.media.MediaPlayer.scanInternalSubtitleTracks(MediaPlayer.java:1851)11-04 13:43:08.966: E/AndroidRuntime(26482): at android.media.MediaPlayer.access$600(MediaPlayer.java:529)11-04 13:43:08.966: E/AndroidRuntime(26482): at android.media.MediaPlayer$EventHandler.handleMessage(MediaPlayer.java:2198)11-04 13:43:08.966: E/AndroidRuntime(26482): at android.os.Handler.dispatchMessage(Handler.java:102)11-04 13:43:08.966: E/AndroidRuntime(26482): at android.os.Looper.loop(Looper.java:137)11-04 13:43:08.966: E/AndroidRuntime(26482): at android.app.ActivityThread.main(ActivityThread.java:4998)11-04 13:43:08.966: E/AndroidRuntime(26482): at java.lang.reflect.Method.invokeNative(Native Method)11-04 13:43:08.966: E/AndroidRuntime(26482): at java.lang.reflect.Method.invoke(Method.java:515)11-04 13:43:08.966: E/AndroidRuntime(26482): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:777)11-04 13:43:08.966: E/AndroidRuntime(26482): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:593)11-04 13:43:08.966: E/AndroidRuntime(26482): at dalvik.system.NativeStart.main(Native Method)
除了反射替換MediaPlayer裡面的EventHandler來抓住異常,其他沒啥特別好的辦法。
遇到這麼多問題開發人員只能另投他路。市面上採用自解碼的方案也很多,比較主流的是使用MediaCodec和ffmpeg,ffmpeg更是因為MediaCodec版本限制原因,加上本來就聞名遐邇,被很多開發人員青睞。主流的音視頻播放器大部分都是在這個上面進行改造的。
ExoPlayer:https://github.com/google/Exo... ,作為google在MediaCodec的封裝也是不錯的推薦,相比自己要去抽取ffmpeg代碼進行android適配編譯來得容易得多
ffmepg:當然也有一些現成的實現:https://github.com/search?o=d... ,最出名的當是ijkplayer,嗶哩嗶哩出品,跨平台還有彈幕。做視頻彈幕真是開箱即用。ffmpeg功能強大,唯一的缺點就是軟解碼,這也是他相容性好的原因,我們知道硬解碼依賴各個廠家硬體實現相容性自然就下降了。
在使用自解碼的時候,我們建議將自己的MediaPlayer封裝成Android高版本上面添加的介面一樣:
/** * Sets the data source (MediaDataSource) to use. * * @param dataSource the MediaDataSource for the media you want to play * @throws IllegalStateException if it is called in an invalid state * @throws IllegalArgumentException if dataSource is not a valid MediaDataSource */public void setDataSource(MediaDataSource dataSource) throws IllegalArgumentException, IllegalStateException { _setDataSource(dataSource);}
這樣做的好處是所有實現都對MediaPlayer透明,我們只需要定義好MediaDataSource介面,後面只需要專註於實現就可以了,比如HttpDataSource,FileDataSource,MemoryDataSource等。
或許自解碼會引入更多的不確定性,但是這一步遲早都要邁出去。
(1)推薦小型app或者需求不強的產品使用系統解碼,在我上面提到的一些解決思路上進行改進應該能滿足絕大部分情境。
(2)而那些音視頻作為主業務的產品則不得不面對自解碼來提高相容性。
於是我們又在造輪子了;)
Android視頻播放方案選擇——深刻分析android平台的視頻播放優缺點