Android我還能夠相信你多少系列文章二之音視頻播放

我即將在2017.7.8號開一個直播講堂,感興趣的同窗點擊快來參加吧:https://segmentfault.com/l/15...
內容包括:java

  1. Android 知識體系分享android

  2. 從入門到提升的學習路徑git

  3. 如何進一步突破瓶頸,進一步提高github

  4. 充足的時間和你們討論,回答你們問題web

自我介紹:面試

網易 Android 專家工程師,網易雲音樂 Android 負責人,主導從零開發了網易雲音樂 Android
客戶端,目前是杭州研究院專業委員會成員,負責每一年的評級,規範起草,面試招聘等相關工做。參與並製做了網易雲課堂 Android
微專業相關課程,反響不錯。segmentfault

音頻視頻播放在如今的應用裏面很常見,傳統應用發展到必定階段多少會引入音視頻資源,特別是如今短視頻被看做下一個增加爆發點,和之相關的創業層出不窮,做爲開發者如何進行音視頻技術選型很是關鍵緩存

MediaPlayer和VideoView給咱們提供了很是方便的播放音視頻的能力,幾乎不須要要寫幾行代碼就能夠完成。咱們也可使用MediaPlayer結合SurfaceView或者TextureView來實現視頻播放,本質和VideoView是同樣的,不過有更多的靈活性。安全

正由於封裝性太強,意味着定製化變弱。MediaPlayer提供的setDataSource方法支持http,file,content等協議,但仍然沒法應對複雜的需求。因此更靈活的AudioTrack的出現,可讓咱們直接傳送解碼後的byte[]給他,帶來的問題就是本身要作解碼。解碼不是件簡單的事情,每每咱們利用MediaCodec(Android4.1增長)或者外部解碼庫(好比ffmpeg)來實現。本身來實現解碼要特別注意不要丟失了硬件加速,音頻軟解碼還好,視頻解碼軟解碼對CPU壓力會大不少。微信

在作音視頻業務的時候,常常會遇到這樣幾個問題
須要設置代理,或者邊播邊緩存,緩存加密,失敗重試,網絡優化等等

由於咱們沒法干涉MediaPlayer的網絡請求部分,因此通常會將原始的播放地址http://xxx.com/playurl轉換成本機代理地址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: -32
11-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等。

或許自解碼會引入更多的不肯定性,可是這一步早晚都要邁出去。推薦小型app或者需求不強的產品使用系統解碼,在我上面提到的一些解決思路上進行改進應該能知足絕大部分場景。而那些音視頻做爲主業務的產品則不得不面對自解碼來提升兼容性。

因而咱們又在造輪子了;)

更多文章請關注微信公衆號:anzhuozhimei

相關文章
相關標籤/搜索