轉載:VLC架構及流程分析

來源:互聯網
上載者:User

標籤:

這個是一篇轉載的文章,但是沒有找到出處,所以如果作者看到了這篇文章,請聯絡我,我將按照您的意願或者刪除,或者附上您的原文連結!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 基礎概念

對於一個視頻的播放,播放器的執行步驟大致如下:

  1. 讀取未經處理資料
  2. 解複用
  3. 解碼
  4. 顯示

VLC在包含以上概念的基礎上,又抽象出幾個其他概念,先列出VLC中抽象出來的重要概念:

  1. playlist: playlist表示播放清單,VLC在啟動後,即建立一個playlist thread,使用者輸入後,動態建立input。
  2. input: input表示輸入,當使用者通過介面輸入一個檔案或者流地址時,input thread 被動態建立,該線程的生命週期直到本次播放結束。
  3. access: access表示訪問,是VLC抽象的一個層,該層向下直接使用檔案或網路IO介面,向上為stream層服務,提供IO介面。
  4. stream: stream表示流,是VLC抽象的一個層,該層向下直接使用access層提供的IO介面,向上為demux層服務,提供IO介面。
  5. demux: demux表示解複用,是視頻技術中的概念,該層向下直接使用stream層提供的IO介面,資料出來後送es_out。
  6. es_out: es_out表示輸出,是VLC抽象的一個層,該層擷取demux後的資料,送decode解碼。
  7. decode: decode表示解碼,是視頻技術中的概念,擷取es_out出來的資料(通過一個fifo互動),解碼後送output。
  8. output: output表示輸出,擷取從decode出來的資料,送readerer。
  9. 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)
  1. 參數訊號處理相關,不詳分析。
  2. 調用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其他成員。
  3. 調用libvlc_set_exit_handler設定VLC退出時的回呼函數。
  4. 調用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模組。
  5. 調用libvlc_playlist_play,如果播放清單不為空白,並且被設定為自動播放,則播放播放清單內容。
  6. 訊號處理相關,不詳分析。
(2) 建立一個輸入
  1. 初始化成功後,程式運行在playlist的線程Thread(src/playlist/thread.c)中,迴圈接收介面輸入的請求。
  2. 當輸入一個新的檔案或者流地址,在PlaylistVAControl獲得訊號,並發送該訊號。
  3. 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方法,開始初始化。

  1. 調用input_EsOutTimeshiftNew建立一個50M的Timeshift(暫停緩衝),包括建立並初始化p_es_out(es_out),與後續步驟9相關。
  2. 設定input的狀態為OPENING_S。
  3. 調用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。
  4. 調用demux_Control擷取輸入的長度。
  5. 調用StartTitle顯示標題。
  6. 調用LoadSubtitles載入字幕。
  7. 調用LoadSlaves,含義不詳。
  8. 調用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。
  9. 續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。
  10. 續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。
  11. 續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。
  12. 根據需要,設定線程優先順序。
  13. 設定meta相關。
  14. 初始化完成,設定該input的狀態為PLAYING_S。
(4) 播放輸入

MainLoop(src/input/input.c)

  1. 調用MainLoopDemux訪問demuxer去demux資料。
  2. 進一步調用在載入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釋放這一幀資料。
  3. 調用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 字幕處理相關,未詳細分析。
  4. 續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方法。
  5. 續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)。
  6. pf_decode_video返回後,解碼後的資料儲存在p_pic中,進一步調用DecoderPlayVideo方法,在該方法中調用vout_PutPicture將解碼後的資料壓入picture fifo中。
  7. 當picture fifo中有資料後,vout線程調用ThreadDisplayPicture中的ThreadDisplayPreparePicture方法。
    7.1 調用picture_fifo_Pop從picture fifo中擷取解碼後的資料。
    7.2 如果延遲太大,並且設定延遲丟幀,則丟掉該幀資料。
  8. 調用ThreadDisplayRenderPicture顯示映像。
0x06 總結

對VLC的流程分析,主要通過跟蹤資料流向的方式展開。對於最後顯示部分的分析還不足,另外很多細節尚未深入。

參考:

    1. http://blog.csdn.net/tx3344/article/details/8517062
    2. http://my.oschina.net/xiaot99/blog/197555  

轉載:VLC架構及流程分析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.