1. 程式人生 > >EasyPlayer-RTSP-Android安卓播放器播放RTSP延遲優化策略,極低延時!

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

獲取更多資訊

郵件:[email protected]

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

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

EasyNVR無外掛直播方案:www.EasyNVR.com

Copyright © EasyDarwin Team 2012-2018

EasyDarwin