深入理解VLC之程式碼流程
本文以vlc-android專案為例,介紹vlc player的程式碼流程。對vlc不太熟悉的朋友,可以在我的公眾號中閱讀《深入理解VLC之縱觀全域性》,從而對vlc有一個整體上的認識。
1. 初始化流程
2. 建立input thread,解析輸入url
值得注意的是,在input_EsOutNew方法中設定了pf_control指向EsOutControl,pf_add指向EsOutAdd,這兩個指標很重要,後面還會用到
es_out_t *input_EsOutNew( input_thread_t *p_input, int i_rate ) { es_out_t *out = malloc( sizeof( *out ) ); if( !out ) return NULL; es_out_sys_t *p_sys = calloc( 1, sizeof( *p_sys ) ); if( !p_sys ) { free( out ); return NULL; } out->pf_add = EsOutAdd; out->pf_send = EsOutSend; out->pf_del = EsOutDel; out->pf_control = EsOutControl; out->pf_destroy = EsOutDelete; out->p_sys = p_sys;
3. 建立access和demux模組,讀入輸入並做parse
在這部分有幾點值得注意:
a.在第一次demux_NewAdvanced方法中嘗試建立access_demux型別的module,如果沒有符合的,再分別建立access型別module和demux型別module;選擇module的過程就是從module list中先按照型別取出所有可選module,再按照module score排序,再比對字首、字尾名判斷是否可用。access_demux型別的module很少, 像bluray這種屬於,所以一般這一步都找不到符合的,以file型別為例,就要分別建立access和demux。
在module_load方法中,會去呼叫module的pf_activate指標所指向的方法,如下
static int module_load (vlc_object_t *obj, module_t *m, vlc_activate_t init, va_list args) { int ret = VLC_SUCCESS; if (module_Map(obj, m->plugin)) return VLC_EGENERIC; if (m->pf_activate != NULL) { va_list ap; va_copy (ap, args); ret = init (m->pf_activate, ap); //vlc_activate_t的定義為typedef int (*vlc_activate_t)(void *func, va_list args); va_end (ap); } if (ret != VLC_SUCCESS) vlc_objres_clear(obj); return ret; }
以檔案型別為例,對應的module是modules/access/file.c,module定義在modules/access/fs.c,如下
vlc_module_begin ()
set_description( N_("File input") )
set_shortname( N_("File") )
set_category( CAT_INPUT )
set_subcategory( SUBCAT_INPUT_ACCESS )
add_obsolete_string( "file-cat" )
set_capability( "access", 50 )
add_shortcut( "file", "fd", "stream" )
set_callbacks( FileOpen, FileClose ) //在這裡設定pf_activate指向modules/access/file.c中的FileOpen方法
...
vlc_module_end ()
demux module也類似,以avformat module為例,會去呼叫avformat_OpenDemux方法
b.在AStreamPrebufferStream會做一次pre buffer操作,讀取1024byte長度的資料
for (;;)
{
stream_track_t *tk = &sys->tk[sys->i_tk];
mtime_t now = mdate();
int i_read;
int i_buffered = tk->i_end - tk->i_start;
if (vlc_killed() || i_buffered >= STREAM_CACHE_PREBUFFER_SIZE)
{
int64_t i_byterate;
/* Update stat */
sys->stat.i_bytes = i_buffered;
sys->stat.i_read_time = now - start;
i_byterate = (CLOCK_FREQ * sys->stat.i_bytes) /
(sys->stat.i_read_time+1);
msg_Dbg(s, "pre-buffering done %"PRId64" bytes in %"PRId64"s - "
"%"PRId64" KiB/s", sys->stat.i_bytes,
sys->stat.i_read_time / CLOCK_FREQ, i_byterate / 1024);
break;
}
i_read = STREAM_CACHE_TRACK_SIZE - i_buffered;
i_read = __MIN((int)sys->i_read_size, i_read);
i_read = vlc_stream_Read(s->p_source, &tk->p_buffer[i_buffered],
i_read);
if (i_read < 0)
continue;
else if (i_read == 0)
break; /* EOF */
if (first)
{
msg_Dbg(s, "received first data after %"PRId64" ms",
(mdate() - start) / 1000);
first = false;
}
tk->i_end += i_read;
sys->stat.i_read_count++;
}
c.在avformat/demux.c中呼叫ffmpeg的方法獲取到流資訊後,針對視訊流,音訊流,字幕流分別呼叫es_format_init方法來初始化,設定解析度,幀率,聲道數等資訊。然後呼叫es_out_Add方法新增一個es_out,有幾個流就做幾次es_out_Add操作,比如該輸入中有一個視訊流和一個音訊流,則作兩次es_out_Add操作。而es_out_Add方法就是我們在第二小節所提到過的。
4. 建立decoder,開始解碼
在這部分有幾點值得注意:
a.在UpdatePtsDelay方法中,可以設定音訊的字幕的delay值,如下
/* Take care of audio/spu delay */
const mtime_t i_audio_delay = var_GetInteger( p_input, "audio-delay" );
const mtime_t i_spu_delay = var_GetInteger( p_input, "spu-delay" );
const mtime_t i_extra_delay = __MIN( i_audio_delay, i_spu_delay );
if( i_extra_delay < 0 )
i_pts_delay -= i_extra_delay;
/* Update cr_average depending on the caching */
const int i_cr_average = var_GetInteger( p_input, "cr-average" ) * i_pts_delay / DEFAULT_PTS_DELAY;
/* */
es_out_SetDelay( input_priv(p_input)->p_es_out_display, AUDIO_ES, i_audio_delay );
es_out_SetDelay( input_priv(p_input)->p_es_out_display, SPU_ES, i_spu_delay );
es_out_SetJitter( input_priv(p_input)->p_es_out, i_pts_delay, 0, i_cr_average );
b.在es_out_vaControl中,呼叫es_out_t的pf_control指標指向的方法,這塊在第二小節中已經做過解釋
c.在input/decoder.c中,建立了decoder fifo,在vlc中,除了decoder fifo外,還有用於顯示輸出的picture fifo,將在下面進行介紹
5. 播放輸出
文章幫到你了?可以掃描如下二維碼進行打賞,打賞多少您隨意~