標籤:
這個是一篇轉載的文章,但是沒有找到出處,所以如果作者看到了這篇文章,請聯絡我,我將按照您的意願或者刪除,或者附上您的原文連結!0x00 前置資訊
VLC是一個非常龐大的工程,我從它的架構及流程入手進行分析,涉及到一些很細的概念先擱置一邊,日後詳細分析。
0x01 源碼結構(Android Java相關的暫未分析)
# 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中,程式碼片段如:
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的流程分析,主要通過跟蹤資料流向的方式展開。對於最後顯示部分的分析還不足,另外很多細節尚未深入。
參考:
- http://blog.csdn.net/tx3344/article/details/8517062
- http://my.oschina.net/xiaot99/blog/197555
轉載:VLC架構及流程分析