📹 第三章 FFmpeg二次开发采集并预览本地摄像头
📄 一句话概括

SDL 不是播放器,是「窗口 + 显卡 + 事件」的跨平台封装。 它只认像素,不认协议、不认时间戳——所以它和 FFmpeg 分工刚好不重叠。

📝 本节和前面几节的关系

前两节把帧拿到手了,接下来缺一个「把帧放上屏幕」的东西。 选 SDL 而不是 Qt/MFC,是因为它最小:两个函数就能显示一帧,没有控件、没有布局、 没有事件循环的复杂度。 Qt 那一套在 3.4 讲,那一节的重点是信号槽,不是渲染。

一、它到底封装了什么

SDL 底下对接的是各平台自己的东西,上面给你一套统一接口。

平台 窗口 渲染
Windows Win32 窗口 Direct3D/OpenGL
Linux X11/Wayland OpenGL/软件
macOS Cocoa Metal/OpenGL

这张表就是「跨平台」的全部含义:你的代码不碰任何一个平台 API,SDL 在编译时选一套。 代价是它只能做「大家都有的功能」——SDLRenderer 没有抗锯齿、没有变色、没有混合模式, 这些得自己上 OpenGL。

二、视频驱动和渲染驱动是两回事

D28 数出来:视频驱动 2 个,渲染驱动 6 个。 这两个数不是一回事。

概念 管什么 沙箱实测
视频驱动 怎么开窗口、怎么收事件 windows/dummy
渲染驱动 怎么把像素送进显卡 6 个,5 硬件 1 软件

「视频驱动只有 2 个」不是 SDL 弱,是因为开了 dummy 之后,平台相关的只剩 windows, 另一个是万能兜底。 而渲染驱动有 6 个,说明它们和窗口是解耦的——同一套绘制代码能在 D3D 和 OpenGL 上跑。

这里有个必须知道的坑:dummy 下能列出 direct3d11,但真正建渲染器时会退到 software。 「列表里有」不等于「能用」。

三、为什么选 SDL 做教学载体

因为它把「播放器」这件事削到只剩骨架。

框架 显示一帧要几行 适合
SDL2 三行 看清原理
Qt 五行(还要 moc) 做界面
MFC 十几行(还有设备上下文) 维护老项目

第一行那三行就是 D32 干的事:建纹理、UpdateTexture、RenderCopy。 没有控件、没有布局、没有资源文件。 这正是学习时想要的:屏幕上出现东西之前,你要理解的东西越少越好。

四、版本号那点事

D28 报出运行时 2.32.10、编译时 2.30.10——不一致。

SDL 有两条版本线在这里交叉:

| 概念 | 从哪来 | 不一致的后果 | |---|---| | 运行时版本 | SDL_GetVersion,实际的 DLL | 函数表可能错位 | | 编译时版本 | 头文件里的宏 | 调不存在的函数 |

两者差一个小版本一般没事(SDL 承诺主版本内兼容),差一个大版本一定出事: SDL1 和 SDL2 的函数名前缀都不一样。 pysdl2 的做法是启动时甩一条警告,看到别忽略。

这条经验放之四海:任何「头文件和动态库不是同一份」的场景,症状都是「随机崩溃」, 而不是「编译失败」。

五、动手 Demo:初始化 SDL,数一遍驱动

开一个 dummy 窗口,把版本、视频驱动、渲染驱动全列出来。 这一段是环境体检:后面所有 SDL 的 Demo 都建立在这上面。

import sdl2, ctypes
sdl2.SDL_Init(sdl2.SDL_INIT_VIDEO | sdl2.SDL_INIT_TIMER)

v = sdl2.SDL_version()
sdl2.SDL_GetVersion(ctypes.byref(v))
print(v.major, v.minor, v.patch)          # 运行时
print(sdl2.SDL_MAJOR_VERSION, sdl2.SDL_MINOR_VERSION)  # 编译时

for i in range(sdl2.SDL_GetNumVideoDrivers()):
    print(sdl2.SDL_GetVideoDriver(i))

for i in range(sdl2.SDL_GetNumRenderDrivers()):
    info = sdl2.SDL_RendererInfo()
    sdl2.SDL_GetRenderDriverInfo(i, ctypes.byref(info))
    print(info.name, info.flags)

实跑输出:

--------------------------------------------------------------------
SDL2 是什么——先把它初始化,再数数它带几个驱动
--------------------------------------------------------------------
**SDL 不是播放器,是「跨平台的窗口和显卡接口」。** 它只负责把一帧像素推到屏幕上,
解码和同步一概不管。

  SDL 版本:**2.32.10**
  编译时版本:2.30.10

  **上面这两个数就不一样**:运行时 2.32.10,编译时 2.30.10。这台机器上就是不一致的,
  pysdl2 启动时会甩一条警告出来——**看到这条警告别忽略**,函数表错位导致的崩溃很难查。

  视频驱动共 **2 个**:
    windows/dummy

  当前用的是:**dummy**(沙箱里强制设了 dummy)

  渲染驱动共 **6 个**:
  渲染驱动       硬件加速     纯软件
  --------------------------------------
  direct3d       是           —
  direct3d11     是           —
  direct3d12     是           —
  opengl         是           —
  opengles2      是           —
  software       —            是

  **这张表说明一件重要的事**:`dummy` 视频驱动下能列出硬件渲染驱动,
  但真正建窗口时会退到 software。
  **所以「驱动列表里有 direct3d11」不等于「能用 direct3d11」**——要看建立之后实际拿到的是哪个。

2.32.10 对 2.30.10,这台机器上就是不一致的。我用的是 pysdl2 0.9.17 配 pysdl2-dll 2.32.10, 两条版本线本来就没法完全对齐,这也是那行警告的来源。 实际跑下来没出问题,因为用到的都是稳定 API。但这是运气,不是保证——上线前还是对齐一下。

渲染驱动那张表里,direct3d 到 opengles2 全是硬件加速,只有 software 是纯软件。 但 dummy 窗口下实际会退到 software——所以「这台机器能硬件加速」这句结论, 在沙箱里是验不出来的。 我只能报「列表里有」,不能报「真的用上了」。 这一点必须说清。

30 秒实验

  1. 把环境变量 SDL_VIDEODRIVER 改成 windows,看会怎样——沙箱里没有真实桌面会话, 大概率建窗口失败。
  2. 用 SDL_GetNumRenderDrivers 的索引逐个建渲染器,看哪个真能建成。
  3. 查一下 SDL_GetRevision(),它是源码版本号,和 SDL_GetVersion 不是一回事。

常见误区

  1. 以为视频驱动越多越好。 沙箱里只有 windows 和 dummy,但一样能跑——驱动数量反映的是平台, 不是能力。
  2. 以为列表里有 direct3d11 就能用。 dummy 下会退到 software, 「能列出」和「能建起来」是两件事。
  3. 以为 SDL 版本不一致一定崩。 主版本内不一致通常没事,但看到警告别忽略——崩起来是随机的。
自测三问(点开对答案) 1. 视频驱动和渲染驱动分别管什么? —— 视频驱动管开窗口和收事件(windows/dummy), 渲染驱动管把像素送进显卡(direct3d11/opengl/software)。 2. 为什么沙箱里能列出 direct3d11 却用不上? —— 驱动列表来自编译进来的支持,建立渲染器时才真正初始化。 dummy 窗口下没有真实的显示设备,会退到 software。 3. 运行时版本和编译时版本不一致,为什么会崩? —— 头文件声明的函数布局和 DLL 里实际的不一样,按旧布局调用就会错位——症状是随机崩溃, 不是编译失败。

下一节开始配环境:先把 Windows 上的 VS 走一遍,再说 Qt 和 Linux。

相关笔记