MPV源碼探究:源碼結構和調用層次

源碼結構和調用層次

源碼結構

從 Github 上拉取最新的源碼,目錄結構大體以下:javascript

H:\MPV          
├─.github
├─audio                    
│  ├─decode                
│  ├─filter                
│  └─out                   
├─ci                       
├─common                   
├─demux                    
├─DOCS                     
│  └─man                   
├─etc                      
├─filters                  
├─input                    
├─libmpv                   
├─misc                     
├─options                  
├─osdep                    
│  ├─android               
│  ├─ar                    
│  ├─macos                 
│  └─win32                 
│      └─include           
├─player                   
│  ├─javascript            
│  └─lua                   
├─stream                   
├─sub                      
├─ta                       
├─test                     
│  └─ref                   
├─TOOLS                    
│  ├─lua                   
│  ├─mpv-osd-symbols.sfdir 
│  └─osxbundle             
│      └─mpv.app           
│          └─Contents      
│              ├─MacOS     
│              │  └─lib    
│              └─Resources 
├─video                    
│  ├─decode                
│  ├─filter                
│  └─out                   
│      ├─cocoa             
│      ├─cocoa-cb          
│      ├─d3d11             
│      ├─gpu               
│      ├─hwdec             
│      ├─opengl            
│      ├─placebo           
│      ├─vulkan            
│      └─win32             
└─waftools                 
    ├─checks               
    ├─detections           
    ├─fragments            
    └─generators
  • <libmpv>:這個文件夾內放置了做爲 libmpv 連接庫所暴露的方法(頭文件),具體實現都在別的文件夾裏。實際上編譯到動態連接庫的時候,暴露的方法名都定義在了libmpv/mpv.def裏面。可是這個 .def 文件不是標準的導出文件
  • <audio>:顧名思義,音頻解碼相關的源碼。
  • <video>:視頻解碼、分離、渲染相關的文件,分別在 decode, filter, out 文件夾裏。
  • <player>:一個具體的播放器實現,內部調用上面幾個部分的模塊。
  • wscript:編譯腳本。新添加的文件要由此加入到編譯流程中。

內部調用層次

初始化核心上下文

若是是啓動播放器進行播放,則首先會進行一個內部狀態的初始化,主要是初始化了MPContext這個結構體。這個結構體是一個大雜燴,全部播放相關的參數、動態變化的屬性都綁定到這上面。而後內核進入 idle 狀態,等待播放視頻。java

初始化渲染驅動

打開第一個媒體文件的時候,會開始進行視頻/音頻播放鏈路(video_output_chain)初始化,其中就包括初始化解碼和渲染模塊。渲染模塊由結構體 vo_driver 定義,(mpv 內部使用結構體來定義接口),例如 vo_gpu 的定義以下:android

const struct vo_driver video_out_gpu = {
    .description = "Shader-based GPU Renderer",
    .name = "gpu",
    .caps = VO_CAP_ROTATE90,
    .preinit = preinit,
    .query_format = query_format,
    .reconfig = reconfig,
    .control = control,
    .get_image = get_image,
    .draw_frame = draw_frame,
    .flip_page = flip_page,
    .get_vsync = get_vsync,
    .wait_events = wait_events,
    .wakeup = wakeup,
    .uninit = uninit,
    .priv_size = sizeof(struct gpu_priv),
    .options = options,
};

接下來咱們都以這個 Windows 下最經常使用的 vo 驅動器——vo_gpu 爲例。在 /video/out/vo.c 中,你能夠看到全部支持的 vo_driver:git

const struct vo_driver *const video_out_drivers[] =
    {
        &video_out_libmpv,
#if HAVE_ANDROID
        &video_out_mediacodec_embed,
#endif
        &video_out_gpu,
#if HAVE_VDPAU
        &video_out_vdpau,
#endif
...省略多個driver

Mpv 會根據系統、編譯狀況、傳入參數決定使用哪一個具體的視頻輸出驅動。以後,調用該驅動的preinit方法。對於 vo_gpu 來講,它的下層還依賴於不一樣的 render_context,對應了在不一樣系統環境上的渲染接口。這也是 Mpv 跨平臺兼容的關鍵。全部 gpu 支持的渲染接口定義在 video/out/gpu/context.cgithub

static const struct ra_ctx_fns *contexts[] = {
#if HAVE_D3D11
    &ra_ctx_d3d11,
#endif

// OpenGL contexts:
#if HAVE_EGL_ANDROID
    &ra_ctx_android,
#endif
#if HAVE_RPI
    &ra_ctx_rpi,
#endif
#if HAVE_GL_COCOA
    &ra_ctx_cocoa,
#endif
#if HAVE_EGL_ANGLE_WIN32
    &ra_ctx_angle,
#endif
#if HAVE_GL_WIN32
    &ra_ctx_wgl,
#endif
...省略大量接口
};

每一個底層接口都由結構體 ra_ctx_fns 定義。這個結構體暴露了一組用於配置的具體方法:macos

const struct ra_ctx_fns ra_ctx_d3d11 = {
    .type = "d3d11",
    .name = "d3d11",
    .reconfig = d3d11_reconfig,
    .control = d3d11_control,
    .init = d3d11_init,
    .uninit = d3d11_uninit,
};

所以在 gpu 渲染驅動的 preinit 函數中一大任務就是調用具體渲染接口的 init 方法。app

視頻播放循環

視頻、音頻播放驅動初始化完畢後,就開始視頻播放。整個播放的流程(render loop)以下僞代碼:ide

for video in Videos {
    while(1) {
        render_frame(video);
        wait for next frame;
    }
}

對的,就是這麼簡單粗暴。這裏有意忽略了時間同步、音視頻同步等具體細節,實際上 Mpv 內部大量依賴於鎖和信號量進行線程間同步。函數

TL;DR

總結一下,一個初始化的流程涉及以下接口的調用:oop

  • MPContext 初始化
  • vo_driver 初始化
  • render_backend 初始化(即特定的、與系統環境相關的底層接口)

下一篇文章,咱們順着官方播放器的具體代碼,看看 Mpv 具體初始化了哪些東西,並試圖捋清楚 libmpv 又是如何進行初始化的。

相關文章
相關標籤/搜索