EasyPlayer-RTSP-Android安卓播放器播放RTSP延遲優化策略,極低延時!


EasyPlayer-RTSP-Android安卓RTSP播放器低延遲播放延時優化策略

EasyPlayer-RTSP-Android播放器是一款專門針對RTSP協議進行過優化的流媒體播放器,其中我們引以為傲的兩個技術優勢就是起播速度快和播放延遲低。最近我們遇到一些需求,其對延遲要求非常苛刻,於是我們再把代碼撿起來,針對之前的播放策略進行再優化,果然又發現一些可以更改和調優的地方,於是又對性能進行了一次壓榨,再一次降低了延遲:

提高解碼線程的優先級

一個不容忽視且容易被人忽略的事實,就是安卓層在一些低優先級的線程上面,線程休眠時間要比sleep時間要長,比如下面一段代碼,在一個線程優先級為BACKGROUND的線程里,我們sleep 100毫秒,然后打印實際上線程暫停的時間。

new Thread(new Runnable() {
            @Override
            public void run() {
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                long millis = System.currentTimeMillis();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.d(TAG,"thread sleep :" + (System.currentTimeMillis() - millis));
            }
        }).start();

然后打印輸出如下內容:

thread sleep :102

可見優先級對線程的睡眠時間影響很大,我們這里需要嚴格控制休眠時間,所以我們需要將線程優先級設置高一些,設置成Audio級別:

Process.setThreadPriority(Process.THREAD_PRIORITY_AUDIO);

對於音頻播放,使用AudioTrack的非阻塞模式寫入

EasyPlayer在渲染視頻軌和音頻軌的時候,是分別在不同的線程進行的,但是由於有音視頻同步策略,如果某一個線程速度慢了,那另外一個線程就也會放慢下來等待它,而音頻渲染就是這樣一個容易"慢下來"的線程!

音頻數據是由AudioTrack來進行渲染的,我們將PCM數據由AudioTrack的write接口寫入,就可以播放出聲音,但是這個write函數是阻塞的,假設某段時間由於網絡抖動,沒有音頻數據過來,過會又突然來了一大塊數據,把這些數據都write到AudioTrack,會阻塞一段時間,這樣就會導致不可避免的延遲!

Android 6.0 AudioTrack提供了一個非阻塞的寫入方式,我們在6.0以上的安卓系統,使用非阻塞方式寫入,這樣大塊數據也能很快寫入音頻設備,就不會因此而導致延遲了。

優化追幀策略

視頻是有一個個視頻幀組成的幀序列。每個視頻幀代表了一個時間點的采樣,我們收到視頻幀同時會得到其所在的時間信息,即視頻時間戳。通過時間戳可計算出視頻幀之間的時間間隔。播放時,我們需要根據這個時間間隔T來Sleep,這樣播放時才能保證流暢性。可通過下面的代碼來計算出T:

// 睡眠時間=當前時間戳-上一幀的時間戳-解碼時間
long sleepTime = frameInfo.stamp - previousStampUs - decodeSpend * 1000;
if (sleepTime > 100000) {	// 睡眠時間超過100毫秒了,可能時間戳異常。設置為100毫秒。
       Log.w(TAG, "sleep time.too long:" + sleepTime);
       sleepTime = 100000;
}

上面說了,由於網絡抖動,可能一段時間內都沒有收到媒體數據,過一會又突然來了一大塊數據。這時候已經有延遲產生了!那怎么辦呢?我們可以讓播放器稍微快速點播放,通過控制視頻線程的Sleep時間T便可實現,當緩沖區內緩存幀數比較大時,可以以一定比例降低T,這樣播放器便可更快地消耗掉緩存幀數,將已經存在的延遲逐步追上。

如下面的代碼所示,我們對當前的的Sleep時間進行修正:

if (sleepTime > 0) {
   // 計算當前視頻隊列的緩沖時間。
     long cache = mNewestStample - frameInfo.stamp;
     // 根據緩沖時間計算一個新的睡眠時間。
     sleepTime = fixSleepTime(sleepTime, cache, 50000);
     if (sleepTime > 0) {
         Thread.sleep(sleepTime / 1000);
     }
    }

fixSleepTime函數用來修正睡眠時間。思路就是根據當前隊列的緩沖和一個固定的延遲時間,調整睡眠時間。其代碼如下,第一個參數表示修正前的睡眠時間T,第二個參數表示當前緩沖時長Cache,第三個參數表示當前設置的緩沖時長Delay,單位都是微秒:

    private static final long fixSleepTime(long sleepTimeUs, long totalTimestampDifferUs, long delayUs) {
      if (totalTimestampDifferUs < 0l) {		// 修正參數異常
          Log.w(TAG, String.format("totalTimestampDifferUs is:%d, this should not be happen.", totalTimestampDifferUs));
          totalTimestampDifferUs = 0;
      }
      double dValue = ((double) (delayUs - totalTimestampDifferUs)) / 1000000d;
      double radio = Math.exp(dValue);
      double r = sleepTimeUs * radio + 0.5f;
      Log.i(TAG, String.format("%d,%d,%d->%d", sleepTimeUs, totalTimestampDifferUs, delayUs, (int) r));
      return (long) r;
  }

這個函數的思路是根據自然指數在x小於0時y小於1,大於0但無限趨近於0,使用這個值乘以睡眠時間,得出新的睡眠時間。
x為允許的緩存時間Delay減去緩沖區的時間Cache。

  • 當Cache大於Delay時,x小於0,y小於1,這時睡眠時間會變小,播放器加速播放。
  • 當Cache等於Delay時,x為0,y等於1,這時睡眠時間不變。
  • 當Cache小於Delay時,x大於0,y大於1.這樣修正的睡眠時間會變大。這時播放器會降低播放速度。

總結

通過這個機制,播放器會在播放的過程中通過調節睡眠時間,將當前的緩存時間逐步趨向用戶設置的緩沖值。我們可更改這個緩沖值Delay,Delay越大,緩沖越大,播放越流暢;Delay越小,緩沖越小,延遲就越低。

關於EasyPlayer流媒體播放器

An elegant, simple, fast android RTSP/RTMP/HLS/HTTP Player.EasyPlayer support RTSP(RTP over TCP/UDP)version & Pro version,cover all kinds of streaming media!EasyPlayer是一款精煉、高效、穩定的流媒體播放器,分為RTSP版、RTMP版和Pro版三個版本,支持各種各樣的流媒體音視頻協議和文件的播放,在安防、互聯網、教育、錄播、IPTV等多個領域大放異彩,廣泛應用!

EasyPlayer:https://github.com/EasyDSS/EasyPlayer

點擊鏈接加入群【EasyPlayer】:544917793

獲取更多信息

郵件:support@easydarwin.org

EasyDarwin開源流媒體服務器:www.EasyDarwin.org

EasyDSS商用流媒體解決方案:www.EasyDSS.com

EasyNVR無插件直播方案:www.EasyNVR.com

Copyright © EasyDarwin Team 2012-2018

EasyDarwin


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM