接上篇:
補充:
資料收集備用。
1、在弱網時使用
2、ijkplayer播放卡頓
3、如何支持https鏈接播放
4、如何降低ijkplayer延遲效應
5、ijkplayer中音視頻同步,是如何做的?
一、在弱網時如何優化
好的網絡下視音頻能夠得到及時的發送,不會造成視音頻數據在本地的堆積,直播效果流暢,延時較小。而在弱網網絡環境下,視音頻數據發送不出去,則需要我們對視音頻數據進行處理。差網絡環境下對視音頻數據一般有四種處理方式:緩存區設計、網絡檢測、丟幀處理、降碼率處理。
1、緩沖區設計
視音頻數據傳入緩沖區,發送者從緩沖區獲取數據進行發送,這樣就形成了一個異步的生產者消費者模式。生產者只需要將采集、編碼后的視音頻數據推送到緩沖區,而消費者則負責從這個緩沖區里面取出數據發送。
視音頻緩沖區
上圖中只顯示了視頻幀,顯然里面也有相應的音頻幀。要構建異步的生產者消費者模式,java中使用LinkedBlockingQueue,可以對之后進行丟幀、插入、取出等處理。
2、網絡檢測
差網絡處理過程中一個重要的過程是網絡檢測,當網絡變差的時候能夠快速地檢測出來,然后進行相應的處理,這樣對網絡反應就比較靈敏,效果就會好很多。通過實時計算每秒輸入緩沖區的數據和發送出去數據,如果發送出去的數據小於輸入緩沖區的數據,那么說明網絡帶寬不行,這時候緩沖區的數據會持續增多,這時候就要啟動相應的機制。
3、丟幀處理
當檢測到網絡變差的時候,丟幀是一個很好的應對機制。視頻經過編碼后有關鍵幀和非關鍵幀,關鍵幀也就是一副完整的圖片,而非關鍵幀描述圖像的相對變化。
丟幀策略多鍾多樣,可以自行定義,一個需要注意的地方是:如果要丟棄P幀(非關鍵幀),那么需要丟棄兩個關鍵幀之間的所有非關鍵幀,不然的話會出現馬賽克。對於丟幀策略的設計因需求而異,可以自行進行設計。如下我一個丟幀實例:
當CPU在處理視頻幀的時候處理得太慢,默認的音視頻同步方案是視頻同步到音頻, 導致了音頻播放過快,視頻跟不上。
在ff_ffplay_options.h中,找到如下代碼:

可以通過修改 framedrop 的值來解決不同步的問題,framedrop 是在視頻幀處理不過來的時候丟棄一些幀達到同步的效果。具體設置,在上層Java層中IjkVideoView中

默認ijkplayer中是1,你可以自行,修改這個值。
4、降碼率
在Android中,如果使用了硬編進行編碼,在差網絡環境下,我們可以實時改變硬編的碼率,從而使直播更為流暢。當檢測到網絡環境較差的時候,在丟幀的同時,我們也可以降低視音頻的碼率。在Android sdk版本大於等於19的時候,可以通過傳遞參數給MediaCodec,從而改變硬編編碼器出來數據的碼率。
Bundle bitrate = new Bundle();bitrate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bps * 1024); mMediaCodec.setParameters(bitrate);
二、ijkplayer播放卡頓如何優化
在做音頻播放的時候,使用的是開源的ijkplayer播放器,ijkplayer解碼使用的是ffmpeg,聲音輸出使用的是audiotrack,在某機型上面播放遇到鎖屏、返回后台、點擊home鍵的時候會出現聲音卡頓的現象,會輸出下面的log
W/AudioTrack: releaseBuffer() track 0xcce8b600 disabled due to previous underrun, restarting
我實現播放調用步驟是在AsyncTask中,查閱資料發現是因為在線程中播放造成的問題,經過查看asynctask構造方法發現,asynctask會把線程的優先級設置為THREAD_PRIORITY_BACKGROUND后台線程,於是我將線程的優先級設置為THREAD_PRIORITY_URGENT_AUDIO,解決了播放卡頓的問題,我猜測播放線程優先級降低,系統分配時間片會減少,會導致底層ijk讀數據輸出數據時得不到及時的回應,audiotrack頻繁的releasebuffer,restarting聲道,造成卡頓。
三、如何支持https鏈接播放?
如果你的項目要進行加密播放HLS協議的視頻,要想支持https,須要在普通編譯的基礎上,進行一些配置。
接下來我們來編譯openssl
1.init openssl
$ cd .. 進入到ijkplayer的目下
$ ./init-android-openssl.sh 去遠程倉庫拉取openssl的遠程代碼,如果是iOS的,這里是init-ios-openssl.h
2.compile openssl
$ cd android/contrib $ ./compile-openssl.sh clean $ ./compile-openssl.sh all
經過以上步驟已經編譯好openssl了,然后我們執行一下方法
$./compile-ffmpeg.sh clean
編譯ffmpeg軟解碼庫,這個過程會生成各種架構的ffmpeg 這個過程比較耗時
$./compile-ffmpeg.sh all
四、如何降低ijkplayer延遲效應
通過修改源文件,因為ijkplayer實際上是基於ffplay.c實現的:
五、ijkplayer中音視頻同步,是如何做的?
對於播放器來說,音視頻同步是一個關鍵點,同時也是一個難點,同步效果的好壞,直接決定着播放器的質量。通常音視頻同步的解決方案就是選擇一個參考時鍾(主 clock),播放時讀取音視頻幀上的時間戳,同時參考當前時鍾參考時鍾(主 clock)上的時間來安排播放。如下圖所示:
如果音視頻幀的播放時間大於當前參考時鍾上的時間,則不急於播放該幀,直到參考時鍾達到該幀的時間戳;
如果音視頻幀的時間戳小於當前參考時鍾上的時間,則需要“盡快”播放該幀或丟棄,以便播放進度追上參考時鍾。
參考時鍾的選擇也有多種方式:
選取視頻時間戳作為參考時鍾源
選取音頻時間戳作為參考時鍾源
選取外部時間作為參考時鍾源
考慮人對視頻、和音頻的敏感度,在存在音頻的情況下,優先選擇音頻作為主時鍾源。
ijkplayer在默認情況下也是使用音頻作為參考時鍾源,我們可以找到ff_ffplay.c文件中,處理同步的過程主要在視頻渲染video_refresh_thread的線程中
從上述實現可以看出,該方法中主要循環做兩件事情:
- 休眠等待,remaining_time的計算在video_refresh中
- 調用video_refresh方法,刷新視頻幀
可見同步的重點是在video_refresh中,下面看該方法一些關鍵部分:

lastvp是上一幀,vp是當前幀,last_duration則是根據當前幀和上一幀的pts(Presentation Time Stamp。PTS主要用於度量解碼后的視頻幀什么時候被顯示出來),計算出來上一幀的顯示時間,經過compute_target_delay方法,計算出顯示當前幀需要等待的時間。
在compute_target_delay函數中,如果發現當前主Clock源不是video,則計算當前視頻時鍾與主時鍾的差值:
如果當前視頻幀落后於主時鍾源,則需要減小下一幀畫面的等待時間;
如果視頻幀超前,並且該幀的顯示時間大於顯示更新門檻,則顯示下一幀的時間為超前的時間差加上上一幀的顯示時間
如果視頻幀超前,並且上一幀的顯示時間小於顯示更新門檻,則采取加倍延時的策略。
回到video_refresh函數中,有如下邏輯:
frame_timer實際上就是上一幀的播放時間,而frame_timer + delay實際上就是當前這一幀的播放時間,如果系統時間還沒有到當前這一幀的播放時間,直接跳轉至display,而此時is->force_refresh變量為0,不顯示當前幀,進入video_refresh_thread中下一次循環,並睡眠等待。
如果當前這一幀的播放時間已經過了,並且其和當前系統時間的差值超過了AV_SYNC_THRESHOLD_MAX,則將當前這一幀的播放時間改為系統時間,並在后續判斷是否需要丟幀,其目的是為后面幀的播放時間重新調整frame_timer,如果緩沖區中有更多的數據,並且當前的時間已經大於當前幀的持續顯示時間,則丟棄當前幀,嘗試顯示下一幀

否則進入正常顯示當前幀的流程,調用video_display2開始渲染。
補充:
ijkplayer - 拓展:覺得有用的同學點個關注,或者留言評論區,看到郵件提示消息盡快回復。
