MPV源码探究:源码结构和调用层次

从 Github 上拉取最新的源码,目录结构大致如下:

 
  1. H:\MPV
  2. ├─.github
  3. ├─audio
  4. │ ├─decode
  5. │ ├─filter
  6. │ └─out
  7. ├─ci
  8. ├─common
  9. ├─demux
  10. ├─DOCS
  11. │ └─man
  12. ├─etc
  13. ├─filters
  14. ├─input
  15. ├─libmpv
  16. ├─misc
  17. ├─options
  18. ├─osdep
  19. │ ├─android
  20. │ ├─ar
  21. │ ├─macos
  22. │ └─win32
  23. │ └─include
  24. ├─player
  25. │ ├─javascript
  26. │ └─lua
  27. ├─stream
  28. ├─sub
  29. ├─ta
  30. ├─test
  31. │ └─ref
  32. ├─TOOLS
  33. │ ├─lua
  34. │ ├─mpv-osd-symbols.sfdir
  35. │ └─osxbundle
  36. │ └─mpv.app
  37. │ └─Contents
  38. │ ├─MacOS
  39. │ │ └─lib
  40. │ └─Resources
  41. ├─video
  42. │ ├─decode
  43. │ ├─filter
  44. │ └─out
  45. │ ├─cocoa
  46. │ ├─cocoa-cb
  47. │ ├─d3d11
  48. │ ├─gpu
  49. │ ├─hwdec
  50. │ ├─opengl
  51. │ ├─placebo
  52. │ ├─vulkan
  53. │ └─win32
  54. └─waftools
  55. ├─checks
  56. ├─detections
  57. ├─fragments
  58. └─generators
  • <libmpv>:这个文件夹内放置了作为 libmpv 链接库所暴露的方法(头文件),具体实现都在别的文件夹里。实际上编译到动态链接库的时候,暴露的方法名都定义在了libmpv/mpv.def里面。但是这个 .def 文件不是标准的导出文件
  • <audio>:顾名思义,音频解码相关的源码。
  • <video>:视频解码、分离、渲染相关的文件,分别在 decode, filter, out 文件夹里。
  • <player>:一个具体的播放器实现,内部调用上面几个部分的模块。
  • wscript:编译脚本。新添加的文件要由此加入到编译流程中。

内部调用层次

初始化核心上下文

如果是启动播放器进行播放,则首先会进行一个内部状态的初始化,主要是初始化了MPContext这个结构体。这个结构体是一个大杂烩,所有播放相关的参数、动态变化的属性都绑定到这上面。然后内核进入 idle 状态,等待播放视频。

初始化渲染驱动

打开第一个媒体文件的时候,会开始进行视频/音频播放链路(video_output_chain)初始化,其中就包括初始化解码和渲染模块。渲染模块由结构体 vo_driver 定义,(mpv 内部使用结构体来定义接口),例如 vo_gpu 的定义如下:

 
  1. const struct vo_driver video_out_gpu = {
  2. .description = "Shader-based GPU Renderer",
  3. .name = "gpu",
  4. .caps = VO_CAP_ROTATE90,
  5. .preinit = preinit,
  6. .query_format = query_format,
  7. .reconfig = reconfig,
  8. .control = control,
  9. .get_image = get_image,
  10. .draw_frame = draw_frame,
  11. .flip_page = flip_page,
  12. .get_vsync = get_vsync,
  13. .wait_events = wait_events,
  14. .wakeup = wakeup,
  15. .uninit = uninit,
  16. .priv_size = sizeof(struct gpu_priv),
  17. .options = options,
  18. };

接下来我们都以这个 Windows 下最常用的 vo 驱动器——vo_gpu 为例。在 /video/out/vo.c 中,你可以看到所有支持的 vo_driver:

 
  1. const struct vo_driver *const video_out_drivers[] =
  2. {
  3. &video_out_libmpv,
  4. #if HAVE_ANDROID
  5. &video_out_mediacodec_embed,
  6. #endif
  7. &video_out_gpu,
  8. #if HAVE_VDPAU
  9. &video_out_vdpau,
  10. #endif
  11. ...省略多个driver

Mpv 会根据系统、编译情况、传入参数决定使用哪个具体的视频输出驱动。之后,调用该驱动的preinit方法。对于 vo_gpu 来说,它的下层还依赖于不同的 render_context,对应了在不同系统环境上的渲染接口。这也是 Mpv 跨平台兼容的关键。所有 gpu 支持的渲染接口定义在 video/out/gpu/context.c

 
  1. static const struct ra_ctx_fns *contexts[] = {
  2. #if HAVE_D3D11
  3. &ra_ctx_d3d11,
  4. #endif
  5. // OpenGL contexts:
  6. #if HAVE_EGL_ANDROID
  7. &ra_ctx_android,
  8. #endif
  9. #if HAVE_RPI
  10. &ra_ctx_rpi,
  11. #endif
  12. #if HAVE_GL_COCOA
  13. &ra_ctx_cocoa,
  14. #endif
  15. #if HAVE_EGL_ANGLE_WIN32
  16. &ra_ctx_angle,
  17. #endif
  18. #if HAVE_GL_WIN32
  19. &ra_ctx_wgl,
  20. #endif
  21. ...省略大量接口
  22. };

每个底层接口都由结构体 ra_ctx_fns 定义。这个结构体暴露了一组用于配置的具体方法:

 
  1. const struct ra_ctx_fns ra_ctx_d3d11 = {
  2. .type = "d3d11",
  3. .name = "d3d11",
  4. .reconfig = d3d11_reconfig,
  5. .control = d3d11_control,
  6. .init = d3d11_init,
  7. .uninit = d3d11_uninit,
  8. };

因此在 gpu 渲染驱动的 preinit 函数中一大任务就是调用具体渲染接口的 init 方法。

视频播放循环

视频、音频播放驱动初始化完毕后,就开始视频播放。整个播放的流程(render loop)如下伪代码:

 
  1. for video in Videos {
  2. while(1) {
  3. render_frame(video);
  4. wait for next frame;
  5. }
  6. }

对的,就是这么简单粗暴。这里有意忽略了时间同步、音视频同步等具体细节,实际上 Mpv 内部大量依赖于锁和信号量进行线程间同步。

TL;DR

总结一下,一个初始化的流程涉及如下接口的调用:

  • MPContext 初始化
  • vo_driver 初始化
  • render_backend 初始化(即特定的、与系统环境相关的底层接口)

转自:MPV源码探究:源码结构和调用层次

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值