TradingAgents-CN:用AI智能体打造你的个人金融分析助手
2026/6/22 20:28:14
视频播放中解码线程持续向队列写入帧(生产者),渲染线程从队列读取帧用于显示(消费者)。需要支持保留当前显示帧、预读下一帧(用于计算显示间隔、插帧等)、暂停时画面不消失、低延迟和高性能。普通动态队列(如链表)频繁 malloc/free 会带来性能开销和内存碎片,不适合实时音视频场景。
ffplay 的 FrameQueue 是一个固定大小的数组 + 读写指针结构:
typedefstructFrameQueue{AVFrame*queue[MAX_FRAME_QUEUE_SIZE];// 固定数组,提前分配intrindex;// 读指针(逻辑上指向“已读但未释放”的帧)intwindex;// 写指针intsize;// 当前有效帧数intmax_size;// 最大容量(通常 audio=9, video=3, sub=16)intkeep_last;// 是否保留最后一帧intrindex_shown;// 标记当前 rindex 帧是否已被“显示过”SDL_mutex*mutex;SDL_cond*cond;}FrameQueue;优点总结:
| 特性 | 普通队列(如 std::queue) | ffplay FrameQueue |
|---|---|---|
| 出队行为 | pop() 立即释放内存 | frame_queue_next() 才真正释放 |
| 读操作 | front() 后必须 pop | 可多次 peek() 查看当前/下一帧 |
| 最后一帧 | 出队即销毁 | 若 keep_last=1,即使出队也保留 |
| 用途 | 通用数据传输 | 音视频渲染专用(需保留历史帧) |
关键区别:“读” ≠ “消费”。FrameQueue 允许“查看但不移除”,这是实现流畅渲染的基础。
intrindex;// 指向“逻辑上最后一个保留的帧”(通常是 lastvp)intrindex_shown;// =0 表示 rindex 帧尚未作为“当前帧”显示过// =1 表示 rindex 帧已是“上一帧”,当前帧是 (rindex+1)%sizeintkeep_last;// =1 表示启用保留机制(video/subtitle 启用,audio 不启用)// 获取当前应显示的帧(不移动指针)staticinlineFrame*frame_queue_peek(FrameQueue*q){return&q->queue[(q->rindex+q->rindex_shown)%q->max_size];}若 rindex_shown=0 → 返回 rindex 帧(即 lastvp,也是当前帧)
若 rindex_shown=1 → 返回 (rindex+1) 帧(当前帧),而 rindex 是 lastvp
staticinlinevoidframe_queue_next(FrameQueue*q){if(q->keep_last&&!q->rindex_shown){// 第一次调用 next():仅标记 rindex_shown=1,不移动 rindexq->rindex_shown=1;return;}// 真正释放 rindex 帧,并移动指针av_frame_unref(q->queue[q->rindex]);q->rindex=(q->rindex+1)%q->max_size;q->rindex_shown=0;// 新的 rindex 尚未作为“当前帧”显示q->size--;SDL_CondSignal(q->cond);}假设解码线程写入了 3 帧:F0, F1, F2
初始状态(刚写完):
queue = [F0, F1, F2] windex = 0(循环回绕) rindex = 0 rindex_shown = 0 size = 3第一次渲染(显示 F0):
rindex = 0 (F0 保留为 lastvp) rindex_shown = 1 → 当前帧是 (0+1)=F1 size = 3(未减少!)第二次渲染(显示 F1):
queue = [__, F1, F2] (F0 已释放) rindex = 1 → 指向 F1(作为新的 lastvp) rindex_shown = 0 → 当前帧仍是 F1(下一次 peek 还是 F1)注意:F1 被“保留”了两次——第一次作为“当前帧”显示,第二次作为“lastvp”供下次同步参考。
staticintframe_queue_nb_remaining(FrameQueue*q){returnq->size-q->rindex_shown;}示例:
这对 video_refresh 判断是否该丢帧或等待至关重要。
| 场景 | 作用 |
|---|---|
| 暂停播放 | 保留 lastvp,画面不黑 |
| 计算帧率 | lastvp.pts 与 vp.pts 做差 |
| 音视频同步 | 视频时钟基于 lastvp 更新 |
| 低内存占用 | 固定 3 帧缓存,避免堆积 |
| 流畅渲染 | 支持“显示当前帧 + 预读下一帧” |