原始碼結構和呼叫層次
原始碼結構
從 Github 上拉取最新的原始碼,目錄結構大致如下:
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 狀態,等待播放視頻,
初始化渲染驅動
打開第一個媒體檔案的時候,會開始進行視頻/音頻播放鏈路(video_output_chain)初始化,其中就包括初始化解碼和渲染模塊,渲染模塊由結構體 vo_driver 定義,(mpv 內部使用結構體來定義介面),例如 vo_gpu 的定義如下:
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:
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.c
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 定義,這個結構體暴露了一組用于配置的具體方法:
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 方法,
視頻播放回圈
視頻、音頻播放驅動初始化完畢后,就開始視頻播放,整個播放的流程(render loop)如下偽代碼:
for video in Videos {
while(1) {
render_frame(video);
wait for next frame;
}
}
對的,就是這么簡單粗暴,這里有意忽略了時間同步、音視頻同步等具體細節,實際上 Mpv 內部大量依賴于鎖和信號量進行執行緒間同步,
TL;DR
總結一下,一個初始化的流程涉及如下介面的呼叫:
- MPContext 初始化
- vo_driver 初始化
- render_backend 初始化(即特定的、與系統環境相關的底層介面)
下一篇文章,我們順著官方播放器的具體代碼,看看 Mpv 具體初始化了哪些東西,并試圖捋清楚 libmpv 又是如何進行初始化的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/17745.html
標籤:其他
上一篇:MPV原始碼探究:背景及準備作業
