当前位置: 首页 > news >正文

硬解码出现画面回退分析

项目场景:

在开发实时监控视频预览库时,我们采用 FFmpeg + DXVA2 硬解码,并设计了一个渲染队列,存放解码出来待渲染的数据(队列大小可配,默认 6),以下是该队列的入队、出队接口:

// 入队接口
int InputData(AVFrame *data) {m_mutex.lock();if (m_dxvaDrawDataQue.size() >= m_nMaxSize) {  // 队列已满if (m_bThrowPacket) {  // 是否丢包av_frame_free(&data);} else {m_mutex.unlock();while (!m_bExit) {  // 不丢包则循环等待队列有空位,每次休眠10ms后重试入队if (m_dxvaDrawDataQue.size() < m_nMaxSize) {m_mutex.lock();m_dxvaDrawDataQue.push_back(data);break;}Sleep(10);}}} else {  // 队列未满则直接入队m_dxvaDrawDataQue.push_back(data);}m_mutex.unlock();return 0;
}
// 取数据接口
AVFrame* CDxvaDrawDataQueue::OutputData() {AVFrame *data = nullptr;if (!m_dxvaDrawDataQue.empty()) {m_mutex.lock();data = m_dxvaDrawDataQue.front();m_dxvaDrawDataQue.pop_front();m_mutex.unlock();}return data;
}

解码线程不断解码并将解码出来的数据入队,渲染线程不断从队列中取数据进行播放:

// 解码线程
int CDecoder::DecodePacket(AVPacket* inPacket, AVFrame* outFrame, int& gotFrame) {// ......while (true) {len = avcodec_decode_video2(m_video_codec_ctx, outFrame, &gotFrame, inPacket);if (len < 0) {// HW_WRITE_LOG(log_error, "avcodec_decode_video2 failed %d %s",len,buff);return -1;}if (gotFrame) {// 将解码出来的数据放到待渲染队列AVFrame *frame = av_frame_alloc();av_frame_move_ref(frame, outFrame);m_dxvaDrawQueue->InputData(frame);}}// ......
}
// 渲染线程
unsigned int CDecoder::DxvaDraw() {// ......while (!m_bExitDxvaDraw) {nowTime = timeGetTime();if (nowTime - lastDisplayTime >= nextDisplayTime) {delayTime = 50;  // 假设帧率是20,则一帧需要显示的时间是50msAVFrame* data = m_dxvaDrawQueue->OutputData();if (data != nullptr) {m_ffdxva2->dxva2_retrieve_data_call(m_video_codec_ctx, data);av_frame_free(&data);ret = 0;} else {ret = -1;}drawEndTime = timeGetTime();drawTime = drawEndTime - nowTime;if (drawTime >= delayTime) {  // 绘画的时间大于帧率的间隔时间,直接显示下一帧nextDisplayTime = 0;continue;} else {if (ret == 0) {nextDisplayTime = delayTime - drawTime - 1;} else {  // 缓冲区空时nextDisplayTime = 0;}}lastDisplayTime = drawEndTime;}Sleep(2);}return 0;

可以看到,渲染线程会根据视频的帧率来控制渲染的速度,不会过快或过慢。


问题描述

渲染队列可以设置为两种模式:

  • 当渲染队列设置为丢帧模式(m_bThrowPacket设置为true)时,队列满时,新解码帧直接丢弃。
  • 当渲染队列设置为阻塞模式(m_bThrowPacket设置为false)时,队列满时,解码线程等待(10ms 重试),直到队列有空位。

测试过程中发现:

  • 丢帧模式或者队列设置较大时(比如设置为6),解码线程速度非常快,但播放时会出现画面回退,即已经播放到某个时间点,突然又显示了更早的帧。
  • 阻塞模式且队列设置较小时(比如设置为3),解码线程因等待被迫减速,整体速率接近播放速率。播放流畅,没有回退。

原因分析:

1、硬解码(DXVA2)的帧顺序问题

硬件解码器内部有 DPB(Decoded Picture Buffer,解码参考帧缓存),在解码 P 帧时会保留并输出参考帧。调用 avcodec_receive_frame() 得到的帧,往往是解码顺序(decode order),而不是显示顺序(presentation order)。如果应用层直接按解码顺序入队,就可能出现播放线程取到的帧时间戳(PTS)比上一帧更早,即画面回退。

2、队列过大或丢帧模式下

解码线程疯狂产出帧(快于播放线程)。播放线程在消费时,遇到硬件解码器「迟到」吐出的早期帧,出现时间戳倒退。缺乏节奏约束,乱序现象被放大。

3、队列较小 + 阻塞模式下

解码线程被迫等待,解码速率约等于播放速率。硬解码器不会积累太多乱序帧,播放线程几乎是「解完就消费」。解码与播放自然保持同步,没有回退。

4、为什么软解码没有问题?

FFmpeg 的软解码器在输出时,已经完成了 PTS 重排序:avcodec_receive_frame() 保证输出的是显示顺序帧。因此,即使解码很快,队列中始终是按时间顺序排列的帧,不会出现回退。

http://www.dtcms.com/a/391220.html

相关文章:

  • P1068 [NOIP 2009 普及组] 分数线划定-普及-
  • 用python语言如何排大小
  • pycharm连接GitHub,怎么配置 SSH 密钥并改用 SSH 连接
  • ​​[硬件电路-265]:电源系统要考虑的因素包括:不同的输出电压、隔离防干扰、防反、防浪涌、电压可调、电源开关、电池、可充电、低纹波、低噪声、防波动等
  • 【开题答辩全过程】以 基于Python的电影推荐系统为例,包含答辩的问题和答案
  • 格拉姆角场(Gramian Angular Field, GAF)详解
  • 前端开发工具Vue有哪些?常用Vue前端开发工具推荐、Vue开发工具对比与最佳实践分享
  • 基于vue的幼儿园健康管理系统0fz0y(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • 第69课 分类任务: 基于BERT训练情感分类与区别二分类本质思考
  • Mysql杂志(二十)——MyISAM索引结构与B树B+树
  • Java 大视界 -- 基于 Java 的大数据实时流处理在金融高频交易数据分析中的应用
  • BonkFun 推出 USD1:Meme 币玩法的新入口
  • flutter在包含ListVIew的滚动列表页面中监听手势
  • Redis 三种集群模式详解
  • 打开hot100
  • Ant-Design Table中使用 AStatisticCountdown倒计时,鼠标在表格上移动时倒计时被重置
  • Linux crontab 定时任务工具使用
  • 阿里云RDS mysql8数据本地恢复,与本地主从同步(容器中)
  • 记录一次mysql启动失败问题解决
  • LeetCode算法练习:35.搜索插入位置
  • (1) 为什么推荐tauri框架
  • 嵌入式面试高频(八)!!!C++语言(嵌入式八股文,嵌入式面经)
  • Spring AI开发指导-工具调用
  • Linux 基本命令超详细解释第二期 | touch | cat | more | cp | mv | rm | which | find
  • [x-cmd] 安装指南
  • Altium Designer(AD24)原理图Move移动功能详细介绍图文教程
  • 部署java程序,服务器报403 Forbidden 问题的终极解决方案
  • 【LeetCode】链表经典问题解析:环形、回文与相交
  • 电磁超材料及其领域应用优势
  • STM32与Modbus RTU协议实战开发指南-fc3ab6a453