歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux技術 >> 修改VLC讀緩沖機制

修改VLC讀緩沖機制

日期:2017/3/3 14:05:23   编辑:Linux技術

0x00 前置信息

為進一步降低延遲,采用極端方法修改VLC讀緩沖機制。

0x01 VLC讀緩沖機制

對於一個rtmp流的讀取,發起端在Demux module中,具體在該模塊的Demux方法中調用ffmepg的接口av_read_frame讀取每一幀數據。但是這個read的接口實在不清晰,經過了多個抽象層的封裝,最後真正指向了rtmp_read接口。還是通過一個圖來看會比較清晰:

上圖描述了read指針的指向,由read的指向可以看出VLC中抽象層次的關系。
Demux layer 開始調用av_read_frame接口,在ffmpeg中經過層層調用,s->read_packet實際指向了Demux layer中IORead接口,然後再進一步指向Stream layer;Stream layer的AReadStream指向了Access layer的Read接口,最終Read接口調用了ffmpeg的接口avio_read,至進一步指向了rtmp_read接口。
在清楚了調用關系之後,現在詳細分析上圖中2個緩沖區是如何協調讀緩沖的。
首先明確一個問題,rtmp_read接口向下調用librtmp的RTMP_Read接口時,一次調用向上層返回一個rtmp packet,正常情況下一個rtmp packet是不會大於16k的。那麼先從avio_read接口入手,avio_read接口是針對AVIOContext結構體使用的,它內部有一個緩沖區buffer,默認大小為16k,avio_read接口在被調用時,如果緩沖區中的數據大小小於請求的大小,則調用fill_buffer接口填滿緩沖區。fill_buffer相當於以16k的大小向下請求數據,這裡進一步看retry_transfer_wrapper中的代碼片段:
[code]static inline int retry_transfer_wrapper(URLContext *h, uint8_t *buf,
                                         int size, int size_min,
                                         int (*transfer_func)(URLContext *h,
                                                              uint8_t *buf,
                                                              int size))
{
  ...
  while (len < size_min) {
        ret = transfer_func(h, buf + len, size - len);
        if (ret == AVERROR(EINTR))
            continue;
        if (h->flags & AVIO_FLAG_NONBLOCK)
            return ret;
  ...
  }
  ...
}

如果沒有設置AVIO_FLAG_NONBLOCK標志,那麼會一直讀到16k的數據才返回,這時候buffer中數據的狀態像如下圖所示:

Packet 4可能不是一個完整的Packet。
現在回到最初的Demux layer,Demux這層的Cache大小默認是16k,相當於從IORead接口以16k的大小向Stream layer層請求數據,最終Stream layer層向IORead接口返回16k的數據。Stream layer層有一個4M的緩沖區,緩存從Access layer讀到的數據,可借助下圖理解這三個緩沖的關系:

如圖所示,初始狀態時,三個緩沖區都為空。然後VLC在創建Stream layer後會執行一次AStreamPrebufferStream,預讀1024個字節數據。該預讀操作會使得pb->buffer被充滿,然後移動current ptr(pb->buf_ptr),向Stream layer返回1024個字節,這時候的狀態是tk->buffer中有1024個字節數據。接著,VLC在加載模塊的時候,需要通過預讀一定大小的數據來判斷具體加載哪個模塊,即要執行Stream layer的peek操作,一系列的peek操作結束後,緩沖區的狀態如上圖第三部分所示,pb->buffer的緩沖區數據未變,current
ptr(pb->buf_ptr)指針移動,tk->buffer數據大小變大,大約在10000個字節左右,p_sys->io_buffer依舊為空。在Demux中開始第一次read之後,pb->buffer中的數據被全部讀到tk->buffer中,然後再全部被讀到p_sys->io_buffer中。因為Stream layer層並不是每次以16k的大小向下請求數據,所以三層緩沖區的數據並不是完全對齊的,比如此時tk->buffer中有15k的數據,尚不夠16k,然後Stream layer再一次以6k大小向下請求數據,會使得pb->buffer緩沖被再一次填滿,而tk->buffer中的數據大小為21k。
現在來分析該讀緩沖機制產生延遲的原因,在Stream layer被創建的時候,pb->buffer中已經存在了3個完整的數據包,而這個數據包直到Demux layer去read的時候才被上層獲取,這時Pkt 1已經等待了一段時間,產生了延遲。另外av_read_frame接口只需要獲得1幀數據即可處理,而該讀緩沖機制會首先填滿緩沖區再提供數據,這就導致先到的幀沒有被及時的處理,造成了無謂的等待。

0x02 優化方法

為了降低延遲,現修改VLC的讀緩沖機制,我的方法比較極端,直接去掉了Stream layer這層的緩沖,初始化階段不預讀,也不在Stream layer做peek操作,直到Demux layer第一次讀,向下請求數據時,讀到一個packet就返回給上層,不做任何緩存。
對應的狀態圖如下:

這樣做的目的就是:使得下層讀到packet迅速被上層獲取並處理。

0x03 代碼修改

[code]src/input/stream_filter.c
(1)
/*
Change by sparktend.
Note the next line. for not find "stream_filter" module, for no peeking.
*/
    //s->p_module = module_need( s, "stream_filter", psz_stream_filter, true );
---------------------------------------------------------------------------------------------------------------------------------------------------
modules/access/avio.c
(1)
/*
Change by sparktend.
Add 'AVIO_FLAG_NONBLOCK' flag. for no buffering.
*/
    ret = avio_open2(&sys->context, url, AVIO_FLAG_READ | AVIO_FLAG_NONBLOCK, &cb, &options);

(2)
    //int r = avio_read(access->p_sys->context, data, size);
    int r = 0;
     AVIOContext* s = access->p_sys->context;

     if( s->read_packet )
     {
          r = s->read_packet(s->opaque, data, size);
     }
---------------------------------------------------------------------------------------------------------------------------------------------------
src/input/demux.c

(1)
/*
Edit by sparktend.
I note the 'while()'.Because I use acc/h264, no ID3 and APE. 
*/
        /*
        while (SkipID3Tag( p_demux ))
          ;
        SkipAPETag( p_demux );
          */
(2)
/*
Edit by sparktend.
change module name from "demux" to "demux_rtmp".
I want just to find a module, not every module that name "demux".
*/
        p_demux->p_module =
            module_need( p_demux, "demux_rtmp", psz_module,
                         !strcmp( psz_module, p_demux->psz_demux ) );

---------------------------------------------------------------------------------------------------------------------------------------------------
modules/demux/avformat/avformat.c
/*
Edit by sparktend.
change "demux" to "demux_rtmp".
because I only use this module to demux,set the individual name.
connect to  "input/demux.c" module_need().
*/
    set_capability( "demux_rtmp", 2 )

---------------------------------------------------------------------------------------------------------------------------------------------------
src/input/stream.c

(1)
/*
Change by sparktend.
Note the next info for no Prebuffer.
*/
      /*       
     AStreamPrebufferStream( s );

        if( p_sys->stream.tk[p_sys->stream.i_tk].i_end <= 0 )
        {
            msg_Err( s, "cannot pre fill buffer" );
            goto error;
        }
*/ 

(2)
static int AStreamReadNoSeekStream( stream_t *s, void *p_read, unsigned int i_read )
{
...
/*
Change by sparktend.
Note the next info, for no buffering.
*/  
/*
    if( tk->i_start >= tk->i_end )
        return 0;
*/
...
/*
Change by sparktend.
Do AReadStream, for no buffering. do return before while
*/
    return AReadStream( s, p_read, i_read );

    while( i_data < i_read )
}
 ---------------------------------------------------------------------------------------------------------------------------------------------------
modules/demux/avformat/demux.c
(1)
     /*
     Edit by sparktend.
     I note the stream*, because I avoid the peek.
     */
     if( strcmp( p_demux->psz_access, "rtmp" )  )
     {
          pd.filename = psz_url;
         if( ( pd.buf_size = stream_Peek( p_demux->s, (const uint8_t**)&pd.buf, 2048 + 213 ) ) <= 0 )
         {
             free( psz_url );
             msg_Warn( p_demux, "cannot peek" );
             return VLC_EGENERIC;
         }
     }

//stream_Control( p_demux->s, STREAM_CAN_SEEK, &b_can_seek );

(2)
/*
Edit by sparktend.
I add 'flv' for format.
*/
/*
    //char *psz_format = var_InheritString( p_this, "avformat-format" );
    char *psz_format = "flv";
    if( psz_format )
    {
        if( (fmt = av_find_input_format(psz_format)) )
            msg_Dbg( p_demux, "forcing format: %s", fmt->name );
         //free( psz_format );
    }
*/
注釋之後的內容一直到msg_Dbg( p_demux, "detected format: %s", fmt->name );

(4)如果ffmpeg version 低於 2.3.3
需要設置
  p_sys->ic->pb->max_packet_size = 32768;
具體見aviobuf.c 中fill_buffer的實現異同。
 ---------------------------------------------------------------------------------------------------------------------------------------------------
ffmpeglibavformatutils.c

int avformat_open_input()
{
/*
Edit by sparktend.
*/
/*
    if (s->pb)
        ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);
*/
}

0x04 總結

經過如上修改,繞過了VLC的緩沖機制,缺陷就是只能針對專門的協議,相當於添加了很多硬編碼的代碼,當然還是那句話,看項目具體需求了,如果對延遲有苛刻要求,那麼就可以這麼做。
zz:https://jiya.io/archives/vlc_optimize_2.html
Copyright © Linux教程網 All Rights Reserved