DongGu
播放器开发--项目概述与整体框架

播放器开发--项目概述与整体框架

这是什么

MyPlayer 是一个基于 Qt6 + FFmpeg + OpenGL 的桌面视频播放器。支持常见的视频格式(mp4、avi、mkv、mov、flv 等),具备播放列表、进度拖动、音量调节、键盘快捷键等基本功能。

核心播放能力包括:

  • 音视频解码与渲染
  • 基于音频时钟的 A/V 同步
  • 播放帧丢弃与尾部保护
  • 响应式 Seek(长按方向键不卡顿)
  • 多格式 YUV 渲染(YUV420/422/444/NV12)

写这个项目的主要目的是学习音视频播放器的底层原理,所以没有使用 Qt Multimedia 的高层播放接口,而是直接从 FFmpeg 解码到 OpenGL 渲染,自己搭建整个播放管线。


技术栈

层面 技术 用途
UI 框架 Qt 6.10.2 + QML 窗口界面、播放列表、控制栏
解封装/解码 FFmpeg(libavformat / libavcodec / libswscale / libswresample) 文件解析、音视频解码、像素/采样格式转换
视频渲染 OpenGL 3.3 Core YUV→RGB 转换、GPU 渲染
音频输出 Qt Multimedia(QAudioSink) 将解码后的 PCM 数据推送到声卡
语言 C++20 整个后端逻辑

整体架构

播放器的核心是一个 五级流水线,各级之间通过有界阻塞队列连接:

1
2
3
[文件] → Demuxer → PacketQueue → Decoder → FrameQueue → 渲染/输出
↑ ↑
包队列(原始压缩数据) 帧队列(解码后的数据)

具体展开后是两条并行的管道——视频和音频各自独立解码,最终在时钟的协调下同步输出:

1
2
3
4
5
                        ┌─→ VideoPacketQueue ─→ VideoDecoder ─→ VideoFrameQueue ─→ VideoSurface (OpenGL)
File → Demuxer ─→ 分流 ─│ │
└─→ AudioPacketQueue ─→ AudioDecoder ─→ AudioFrameQueue ─→ AudioOutput (QAudioSink)

Clock(音频驱动时钟)

数据流的分步说明:

  1. Demuxer(解封装线程) — 打开文件,读取 AVPacket,根据 stream index 分流到视频或音频的 PacketQueue
  2. VideoDecoder(视频解码线程) — 从 VideoPacketQueue 取包,avcodec_send_packet / avcodec_receive_frame 解码,帧格式统一转换为 YUV420p 后放入 VideoFrameQueue
  3. AudioDecoder(音频解码线程) — 同上,音频帧经过重采样后放入 AudioFrameQueue
  4. VideoSurface(Qt 渲染线程) — 从 VideoFrameQueue 取帧,对比 Clock 时间做同步(丢弃/等待),通过 OpenGL shader 将 YUV 数据转换为 RGB 纹理渲染到屏幕
  5. AudioOutput(音频输出线程) — 从 AudioFrameQueue 取帧,写入 QAudioSink 播放,同时用已播放的音频样本数更新 Clock

线程模型

整个播放器涉及 4 个常驻工作线程 + 1 个 Qt 渲染线程:

线程 所在模块 职责
Demux 线程 PlayerController::demuxLoop() 循环读取文件包,分发到两个 PacketQueue
Video 解码线程 VideoDecoder::decodeLoop() 解码视频帧,归一化格式,推入 VideoFrameQueue
Audio 解码线程 AudioDecoder::decodeLoop() 解码音频帧,重采样,推入 AudioFrameQueue
Audio 输出线程 AudioOutput 内部 从 AudioFrameQueue 取帧写入 QAudioSink,更新 Clock
Qt 渲染线程 VideoSurface::createRenderer() OpenGL 渲染,与 AudioOutput 共享 Clock 做帧同步

线程安全机制:

  • 队列BaseQueue 内部使用 std::mutex + std::condition_variable 保护,生产者和消费者互不干扰
  • 时钟Clock 内部使用 std::mutex 保护,AudioOutput 写入、VideoSurface 读取
  • 生命周期:通过 _abort 原子标志位 + condition_variable::notify_all() 实现各线程的快速唤醒和安全退出

核心模块一览

Demuxer(解封装器)

封装 AVFormatContext 的操作:打开文件、查找流、读取包、Seek。支持 I/O 中断回调(interruptCallback),在 stop/seek 时不会因为网络或磁盘 I/O 卡住。

PacketQueue / FrameQueue(有界阻塞队列)

基于 BaseQueue<T> 模板实现的线程安全队列。

  • PacketQueue:最大容量 100,存储压缩数据包(AVPacket
  • FrameQueue:最大容量 5,存储解码后的帧(AVFrame),额外支持尾部保护(protectTailFrom / markDraining

队列满时生产者阻塞等待,队列空时消费者阻塞等待,形成自然的背压机制,防止内存无限增长。

VideoDecoder / AudioDecoder(解码器)

各自运行一个解码线程,封装 FFmpeg 的 avcodec_send_packet / avcodec_receive_frame 流程。

  • 视频端通过 SwsContext 将各种像素格式统一转为 YUV420p
  • 音频端通过 SwrContext 将各种采样格式重采样为播放设备支持的格式

AudioOutput(音频输出)

封装 QAudioSink,用一个内部线程将解码后的 PCM 数据写入音频设备。同时负责 驱动时钟——每次写入后根据已播放的样本数更新 Clock 的当前时间。

Clock(时钟)

基于 std::chrono::steady_clock 的单调时钟。支持暂停/恢复,线程安全。音频输出线程是时钟的唯一写入者,视频渲染线程读取时钟来判断帧应该何时显示。

VideoSurface(视频渲染面)

继承 QQuickFramebufferObject,在 Qt 的渲染线程中执行 OpenGL 渲染。核心同步逻辑在 synchronize() 中:

  • 帧 PTS 落后时钟超过 120ms → 丢弃(追赶进度)
  • 帧 PTS 超前时钟超过 8ms → 等待(不提前显示)
  • 队列末尾 0.7s 区间内 → 保护不丢弃(保证播放到结尾时不跳帧)

GenericYuvRender(OpenGL 渲染器)

通过 C++ 模板 + YUVTraits 编译期派发,支持 YUV420p / YUV422p / YUV444p / NV12 四种像素格式。每种格式有独立的 GLSL shader,将 YUV 平面纹理上传后在 GPU 上完成 YUV→RGB 转换。


目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
VideoPlayer/
├── CMakeLists.txt # CMake 构建配置
└── src/
├── main.cpp # 入口:注册类型,加载 QML
├── backend/ # C++ 后端
│ ├── PlayerController.h/.cpp # 总控:播放/暂停/Seek/生命周期管理
│ ├── Demuxer.h/.cpp # 解封装
│ ├── VideoDecoder.h/.cpp # 视频解码
│ ├── AudioDecoder.h/.cpp # 音频解码
│ ├── AudioOutput.h/.cpp # 音频输出 + 时钟驱动
│ ├── Clock.h/.cpp # 时钟
│ ├── BaseQueue.h # 有界阻塞队列模板
│ ├── PacketQueue.h # 包队列(typedef)
│ ├── FrameQueue.h # 帧队列(typedef + 尾部保护)
│ ├── VideoSurface.h/.cpp # QQuickFramebufferObject 视频面
│ ├── BaseYUVRender.h/.cpp # OpenGL 渲染基类
│ ├── GenericYuvRender.h # 模板化 YUV 渲染器
│ ├── YUVTraits.h # YUV 格式特征(编译期常量)
│ └── FFmpegPtrs.h # FFmpeg 对象的 RAII 包装
└── ui/
└── main.qml # QML 界面

下一步

后续文章会逐一深入每个模块的实现细节:

  1. 有界阻塞队列BaseQueue 的实现、条件变量背压机制
  2. 音视频解码 — FFmpeg 解码流程与格式归一化
  3. A/V 同步与时钟 — 音频驱动时钟的原理、帧丢弃与尾部保护
  4. 音频重采样SwrContext 用法、采样率/声道/格式转换
  5. OpenGL YUV 渲染 — 模板化多格式渲染、YUV→RGB GPU shader
  6. 播放控制与 Seek — pause/resume/seek、Demux 线程、队列清空、I/O 中断

这次的插图来自画师RuneXiao
图片地址:https://www.pixiv.net/artworks/137010221

本项目源码请看:https://github.com/DongGuZhengHuaJi/VideoPlayer

本文作者:DongGu
本文链接:https://donggu.xyz/2026/05/08/音视频学习/项目概述与整体框架/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可