zz:https://jiya.io/archives/vlc_learn_2.html
0x00 前置信息
VLC是一個非常龐大的工程,我從它的架構及流程入手進行分析,涉及到一些很細的概念先擱置一邊,日後詳細分析。
0x01 源碼結構(Android Java相關的暫未分析)
[code]# build-android-arm-linux-androideabi/:第三方庫。
# modules/:模塊代碼。
# modules/demux: 解復用模塊代碼。
# modules/codec: 解碼模塊相關代碼。
# modules/access: 訪問模塊相關代碼。
# 其他:未詳細分析。
# src/: VLC架構核心代碼。
# src/config/: 從命令行和配置文件加載配置,提供功能模塊的讀取和寫入配置。
# src/control/: 提供動作控制功能,如播放/暫停,音量管理,全屏,日志等。
# src/extras/: 平台特殊性相關代碼。
# src/modules/: 模塊管理。
# src/network/: 提供網絡接口。
# src/posix/: 多線程相關。
# src/osd/: 顯示屏幕上的操作。
# src/interface/ : 提供代碼中可以調用的接口中,如按鍵後硬件作出反應。
# src/playlist/: 管理播放功能,如停止,播放,下一首,隨機播放等。
# src/text/: 字符集。
# src/input/: 輸入流相關代碼。
# src/video_output/ : 初始化視頻播放器,把從解碼器獲得的數據處理後播放。
# src/audio_output/ : 初始化音頻混合器,把從解碼器獲得的數據處理後播放。
# src/stream_output/: 輸出音頻流和視頻流到網絡。
# src/test/: libvlc測試模塊。
# src/misc/: libvlc使用的其他部分功能,如線程系統,消息隊列,CPU的檢測,對象查找系統,或平台的特定代碼。
# 其他:未詳細分析。
0x02 基礎概念
對於一個視頻的播放,播放器的執行步驟大致如下:
讀取原始數據
解復用
解碼
顯示
VLC在包含以上概念的基礎上,又抽象出幾個其他概念,先列出VLC中抽象出來的重要概念:
playlist: playlist表示播放列表,VLC在啟動後,即創建一個playlist thread,用戶輸入後,動態創建input。
input: input表示輸入,當用戶通過界面輸入一個文件或者流地址時,input thread 被動態創建,該線程的生命周期直到本次播放結束。
access: access表示訪問,是VLC抽象的一個層,該層向下直接使用文件或網絡IO接口,向上為stream層服務,提供IO接口。
stream: stream表示流,是VLC抽象的一個層,該層向下直接使用access層提供的IO接口,向上為demux層服務,提供IO接口。
demux: demux表示解復用,是視頻技術中的概念,該層向下直接使用stream層提供的IO接口,數據出來後送es_out。
es_out: es_out表示輸出,是VLC抽象的一個層,該層獲取demux後的數據,送decode解碼。
decode: decode表示解碼,是視頻技術中的概念,獲取es_out出來的數據(通過一個fifo交互),解碼後送output。
output: output表示輸出,獲取從decode出來的數據,送readerer。
readerer: readerer表示顯示,獲取從output出來的數據(通過一個fifo交互),然後顯示。
下圖顯示了這些抽象的概念的關系,其中藍色表示VLC抽象的概念。
0x04 架構綜述
VLC的整體框架是設計成一套module的管理機制,將功能分類並抽象成modules。
VLC main: player的main。初始化libVLC 並加載用戶界面。
libVLCcore:libvlc的核心,抽象出了一個libvlc_instance_t 對象,提供modules的裝載/卸載機制。
modules: modules提供具體的功能,比如上面的access,demux,decode就是以一個模塊的形式存在。
External libraries:外部開源庫。
模塊的加載方式:
首先模塊先將自身注冊到VLC中,代碼片段如:
[code]vlc_module_begin()
...
vlc_module_end()
然後在需要加載模塊的時候,調用module_need接口,去找到合適的模塊。找到合適的模塊後,會執行注冊中設置的回調方法,諸如Open*名字的方法。
同樣自己可以實現模塊,只需要按照VLC模塊的標准即可。VLC中很多模塊就是通過外部的開源庫實現的。
vlc中模塊大致分類:
0x05 流程分析
首先,給出流程圖,參照該圖,再繼續下面的流程分析,綠色線表示打開VLC後的執行操作;黑色線表示用戶輸入一個視頻後的執行操作;藍色線從紅色圈開始,表示開始播放輸入流後的數據流向。
(1) main函數(vlc/bin/vlc.c)參數信號處理相關,不詳分析。
調用libvlc_new()初始化一個libvlc_instance_t實例。(libvlc_instance_t is opaque. It represents a libvlc instance)
2.1 調用libvlc_InternalCreate創建一個libvlc_int_t。(This structure is a LibVLC instance, for use by libvlc core and plugins.)
2.2 調用libvlc_InternalInit初始化libvlc_int_t實例。
2.3 初始化libvlc_instance_t其他成員。
調用libvlc_set_exit_handler設置VLC退出時的回調函數。
調用libvlc_add_intf添加模塊。
4.1 獲取playlist,如果為空,則調用playlist_Create創建一個playlist結構,並調用playlist_Activate創建新的playlist線程Thread(src/playlist/thread.c)。
4.2 調用intf_Create創建一個默認的interface。
4.2.1 調用vlc_custom_create創建一個vlc object(intf_thread_t)。
4.2.2 注冊一個添加interface的回調方法。
4.2.3 調用module_need加載一個interface模塊。
調用libvlc_playlist_play,如果播放列表不為空,並且被設置為自動播放,則播放播放列表內容。
信號處理相關,不詳分析。
(2) 創建一個輸入初始化成功後,程序運行在playlist的線程Thread(src/playlist/thread.c)中,循環接收界面輸入的請求。
當輸入一個新的文件或者流地址,在PlaylistVAControl獲得信號,並發送該信號。
Thread接收到播放請求後,在LoopRequest中調用PlayItem方法。
3.1 調用input_Create創建一個input結構,並初始化各種成員,其中包括調用input_EsOutNew創建p_es_out_display(es_out)。
3.2 調用input_Start創建一個input線程Run(src/input/input.c)。
(3) 初始化輸入調用Run(src/input/input.c)中的Init方法,開始初始化。
調用input_EsOutTimeshiftNew新建一個50M的Timeshift(暫停緩存),包括創建並初始化p_es_out(es_out),與後續步驟9相關。
設置input的狀態為OPENING_S。
調用InputSourceInit。
3.1 調用input_SplitMRL分解輸入uri。
3.2 以stream形參為NULL調用demux_New加載"access_demux"模塊。
3.3 如果沒有合適的"access_demux"模塊,則調用access_New創建一個實際的access。
3.3.1 調用vlc_custom_create創建access_t結構體。
3.3.2 調用module_need加載合適的access模塊。
3.3.3 調用access模塊的Open方法,以avio模塊為例。
3.3.3.1 調用vlc_init_avformat初始化VLC即avformat環境。
3.3.3.2 調用avio_open2打開該uri。
3.3.3.3 設置access的IO方法指針。
3.4 調用stream_AccessNew創建一個stream。
3.4.1 根據模式(stream/block)設置steam層的IO方法指針。stream層的IO方法實際指向access層對應的IO方法指針。
3.4.2 為stream層的緩沖申請並初始化內存。
3.4.3 調用AStreamPrebufferStream執行一次讀操作。
3.5 調用stream_FilterChainNew,Add stream filters(源碼描述)。
3.6 調用demux_New創建一個demux。
3.6.1 調用vlc_custom_create創建demux_t結構體。
3.6.2 調用module_need加載合適的demux模塊。
3.6.3 調用demux模塊的Open方法,以avformat/demux模塊為例。
3.6.3.1 調用stream_Peek從stream層獲取數據,用於分析輸入的文件格式。
3.6.3.2 調用av_probe_input_format分析輸入的文件格式。
3.6.3.3 設置demux_sys_t結構體部分變量的值。
3.6.3.4 調用avformat_alloc_context分配AVFormatContext結構體。
3.6.3.5 調用avio_alloc_context設置AVFormatContext結構體的AVIOContext類型成員pb,並設置read和seek方法指針。
3.6.3.6 調用avformat_open_input打開一個輸入,這裡的input與VLC中的input不是一個概念,關於avformat_open_input的分析詳見我的另一篇文章《avformat_open_input詳細分析》鏈接地址。
3.6.3.7 調用avformat_find_stream_info分析流信息,該方法通過讀取數據初始化流以及流解碼信息。
3.6.3.8 根據分析的流信息,設置fmt變量,並調用es_out_Add。
3.6.3.9 實際調用EsOutAdd(src/input/es_out.c),添加一個es_out,有幾個流就做幾次es_out_Add操作,比如該輸入中有一個視頻流和一個音頻流,則作兩次es_out_Add操作。
3.6.3.10 nb_chapters相關未詳細分析。
3.7 設置record相關。
3.8 調用demux_Control設置demux pts delay。
3.9 調用demux_Control設置fps。
調用demux_Control獲取輸入的長度。
調用StartTitle顯示標題。
調用LoadSubtitles加載字幕。
調用LoadSlaves,含義不詳。
調用InitPrograms,設置es_out和decoder相關。
8.1 調用UpdatePtsDelay計算正確的pts_delay值。
8.2 sout相關可選,暫不分析。
8.3 調用es_out_SetMode,設置es_out的mode為ES_OUT_MODE_AUTO。
8.4 以DEMUX_SET_GROUP指令調用demux_Control,DEMUX_SET_GROUP/SET_ES only a hint for demuxer (mainly DVB) to allow not reading everything。
續8.3,實際調用EsOutControlLocked進入case ES_OUT_SET_MODE分支。
9.1 設置es_out_sys_t 的b_active和i_mode。
9.2 調用EsOutSelect方法,根據指定模塊選擇一個es_out。
9.3 在EsOutSelect方法中進入ES_OUT_MODE_AUTO分支,進一步調用EsSelect方法,再進一步調用EsCreateDecoder方法創建decoder。
9.3.1 調用input_DecoderNew創建一個新的decoder。
9.3.2 如果需要緩存,調用input_DecoderStartWait發送信號,開始線程等待。
9.3.3 調用EsOutDecoderChangeDelay設置decode delay。
續9.3.1進入decoder_New方法。
10.1 調用CreateDecoder創建decoder配置結構體。
10.1.1 調用vlc_custom_create創建一個vlc object(decoder_t)。
10.1.2 新建decode fifo。
10.1.3 調用module_need加載適配的解碼模塊。
10.1.3.1 調用decode模塊的OpenDecoder方法,以codec/avcodec模塊為例。
10.1.3.2 調用GetFfmpegCodec方法 determine codec type(源碼描述)。
10.1.3.3 調用vlc_init_avcodec方法初始化解碼環境。
10.1.3.4 調用avcodec_find_decoder設置AVCodec。
10.1.3.5 調用avcodec_alloc_context3分配一個AVCodecContext。
10.1.3.6 調用Init*Dec系列初始化解碼環境。
10.1.4 初始化decoder_t結構體其他成員。
10.2 調用vlc_clone創建解碼線程DecoderThread。
續10.1.3.5,以InitVideoDec為例。
12.1 為decoder_sys_t結構分配內存。
12.2 設置相關回調方法。
12.3 設置解碼線程類型。
12.4 調用ffmpeg_InitCodec初始化extradata相關數據。
12.5 調用OpenVideoCodec方法,設置解碼的長寬及采用率,進一步調用avcodec_open2打開codec。
根據需要,設置線程優先級。
設置meta相關。
初始化完成,設置該input的狀態為PLAYING_S。
(4) 播放輸入MainLoop(src/input/input.c)
調用MainLoopDemux訪問demuxer去demux數據。
進一步調用在加載demux模塊時設置的demux方法,同樣以avformat/demux模塊為例,實際調用Demux方法(module/demux/avformat/demux.c)。
2.1 調用av_read_frame讀取一幀數據。
2.2 判斷讀取無誤時,則為block_t結構分配內存,並將這一幀從AVPacket中拷貝至block_t結構中。
2.3 如果該幀是I幀,則設置I幀標致位。
2.4 時間戳處理相關,未深入分析。
2.5 根據需要調用es_out_Control設置PCR,未深入分析。
2.6 調用es_out_Send將這一幀數據發送給es_out。
2.7 調用av_free_packet釋放這一幀數據。
調用es_out_Send後,實際調用EsOutSend(src/input/es_out.c)方法。
3.1 調用stats_Update更新相關狀態,具體未詳分析。
3.2 設置預讀相關,如果需要預讀,並且到的數據的pts小於預讀需要的時間,則設置BLOCK_FLAG_PREROLL標志位。
3.3 檢查sout mode,具體有sync 和async mode,異同未詳細分析。
3.4 如果設置record,將數據dup後送decoder。
3.5 調用input_DecoderDecode將block_t的數據送至decode fifo中。
3.5.1 判斷控制速度線程等待相關信息,具體未詳細分析。
3.5.2 如果decode fifo超過最大長度,則清空重置decode fifo。
3.5.3 調用block_FifoPut將該block_t的數據壓入decode fifo,並通知讀取線程。
3.6 格式變化判斷處理相關,未詳細分析。
3.7 字幕處理相關,未詳細分析。
續3.5進入decode read thread,即DecoderThread(src/input/decoder.c)。
4.1 調用block_FifoGet方法,從decode fifo中獲取數據。
4.2 基於某些條件,發送停止等待消息給其他線程,未詳細分析。
4.3 調用DecoderProcess方法開始decode a block。
4.4 判斷輸入流的格式,調用不同的方法,這裡以視頻流為例,調用DecoderProcessVideo方法。
4.5 packetizer相關為深入分析,在DecoderProcessVideo方法中進一步調用DecoderDecodeVideo方法。
續4.5調用pf_decode_video,這裡以avcodec模塊的decoder為例,即DecodeVideo(modules/codec/avcodec/video.c)方法,在該方法中,開始真正的解碼。
5.1 如果在Demux中獲取的流信息中包含新的extradata,並且原來的extradata數據為空,則調用ffmpeg_InitCodec初始化codec,如果b_delayed_open為true,則調用OpenVideoCodec重新打開codec。
5.2 調用av_init_packet初始化解碼數據包。
5.3 調用avcodec_decode_video2解碼數據。
5.4 調用av_free_packet釋放內存。
5.5 計算pts值,返回解碼後的數據。
5.6 如果opaque為空,則調用ffmpeg_NewPictBuf方法創建一個新的picture buffer。具體調用回調指針pf_vout_buffer_new指向的vout_new_buffer,進一步調用input_resource_RequestVout最終調用VoutCreate。
5.6.1 調用vlc_custom_create創建一個vlc object(vout_thread_t)。
5.6.2 調用spu_Create初始化sub picture unit。
5.6.3 調用vlc_clone創建一個output線程Thread(src/video_output/video_output.c)。
5.6.4 output線程循環調用vout_control_Pop,首次進入ThreadControl方法中,執行ThreadStart方向,創建picture fifo(p->decoder_fifo)。
pf_decode_video返回後,解碼後的數據保存在p_pic中,進一步調用DecoderPlayVideo方法,在該方法中調用vout_PutPicture將解碼後的數據壓入picture fifo中。
當picture fifo中有數據後,vout線程調用ThreadDisplayPicture中的ThreadDisplayPreparePicture方法。
7.1 調用picture_fifo_Pop從picture fifo中獲取解碼後的數據。
7.2 如果延遲太大,並且設置延遲丟幀,則丟掉該幀數據。
調用ThreadDisplayRenderPicture顯示圖像。
0x06 總結對VLC的流程分析,主要通過跟蹤數據流向的方式展開。對於最後顯示部分的分析還不足,另外很多細節尚未深入。
參考:
/content/796347.html
/content/2241507.html