使用FFmpeg解码视频并通过Qt播放时画面不刷新的修复咨询
修复FFmpeg+Qt播放画面不刷新问题
核心问题分析
- 单QImage重复覆盖导致同步异常:子线程仅创建一个QImage,每次解码直接覆盖数据并发送同一指针,主线程可能尚未完成绘制,数据就被下一次解码覆盖,引发画面显示问题。
- 解码速度远超UI刷新能力:解码线程全速运行,UI队列被大量帧请求塞满,无法及时完成画面刷新。
- QLabel未强制触发重绘:设置Pixmap后未主动通知控件刷新,依赖系统自动更新可能存在延迟。
- FFmpeg资源未正确释放:AVPacket、AVFrame未在每次使用后释放,导致内存泄漏和后续解码异常。
- 视频流索引硬编码错误:直接判断
packet->stream_index==0,未使用实际找到的视频流索引,可能选错流导致无画面。
具体修复方案
1. 每次解码创建独立QImage
子线程中每帧解码完成后创建新QImage存储数据,避免线程间数据竞争:
// 修改Ffplay::run()循环部分 while(av_read_frame(format,packet)==0){ if(packet->stream_index == index){ // 使用找到的视频流索引而非硬编码0 avcodec_send_packet(codec,packet); int ret; while((ret = avcodec_receive_frame(codec,frame)) >=0){ // 为当前帧创建独立QImage QImage* image = new QImage(655,378,QImage::Format_RGB32); uint8_t* dst[]={image->bits()}; int dstStride[4]; av_image_fill_linesizes(dstStride,AV_PIX_FMT_RGB32,655); sws_scale(s,frame->data,frame->linesize,0,frame->height,dst,dstStride); emit send(image); av_frame_unref(frame); // 释放当前帧资源 } } av_packet_unref(packet); // 每次处理完Packet必须释放 }
2. 添加帧间延迟匹配视频帧率
获取视频帧率,计算帧间隔延迟,避免解码过快:
// 在Ffplay::run()中找到视频流后添加 AVStream* videoStream = format->streams[index]; double fps = av_q2d(videoStream->avg_frame_rate); double frameDelay = fps > 0 ? 1000 / fps : 40; // 默认40ms约25帧/秒 // 在emit send(image)后添加延迟 QThread::msleep(static_cast<unsigned long>(frameDelay));
3. 强制QLabel触发重绘
在MainWindow的paint槽函数中,设置Pixmap后主动调用刷新:
void MainWindow::paint(QImage* image){ if(!image || image->isNull()){ delete image; return; } QPixmap m=QPixmap::fromImage(*image); ui->label->setPixmap(m); ui->label->setScaledContents(true); ui->label->update(); // 强制触发控件重绘 delete image; // 释放子线程传递的QImage }
4. 完善FFmpeg资源清理
线程结束时释放所有FFmpeg资源,避免内存泄漏:
void Ffplay::run(){ // ... 现有解码代码 ... // 循环结束后清理资源 av_packet_free(&packet); av_frame_free(&frame); sws_freeContext(s); avcodec_close(codec); avcodec_free_context(&codec); avformat_close_input(&format); avformat_free_context(format); }
5. 显式指定信号槽队列连接
确保跨线程信号槽使用队列模式,避免主线程阻塞:
// 主线程中修改连接代码 QObject::connect(&f,&Ffplay::send,&w,&MainWindow::paint, Qt::QueuedConnection);
完整修复后核心代码示例
Ffplay线程run()修改后
void Ffplay::run(){ format=avformat_alloc_context(); if(avformat_open_input(&format,"c:/Qt/project/player/movie.mp4",NULL,NULL) != 0){ return; } if(avformat_find_stream_info(format,NULL) <0){ return; } int index=-1; AVStream* videoStream = nullptr; for(unsigned int i=0;i<format->nb_streams;i++){ if(format->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_VIDEO){ index=i; videoStream = format->streams[i]; break; } } if(index == -1){ return; } c=avcodec_find_decoder(videoStream->codecpar->codec_id); codec=avcodec_alloc_context3(c); avcodec_parameters_to_context(codec,videoStream->codecpar); if(avcodec_open2(codec,c,NULL) <0){ return; } packet=av_packet_alloc(); frame=av_frame_alloc(); s=sws_getContext(codec->width,codec->height,codec->pix_fmt,655,378,AV_PIX_FMT_RGB32,SWS_FAST_BILINEAR,NULL,NULL,NULL); double fps = av_q2d(videoStream->avg_frame_rate); double frameDelay = fps >0 ? 1000 / fps : 40; while(av_read_frame(format,packet)==0){ if(packet->stream_index == index){ avcodec_send_packet(codec,packet); int ret; while((ret = avcodec_receive_frame(codec,frame)) >=0){ QImage* image=new QImage(655,378,QImage::Format_RGB32); uint8_t* dst[]={image->bits()}; int dstStride[4]; av_image_fill_linesizes(dstStride,AV_PIX_FMT_RGB32,655); sws_scale(s,frame->data,frame->linesize,0,frame->height,dst,dstStride); emit send(image); av_frame_unref(frame); QThread::msleep(static_cast<unsigned long>(frameDelay)); } } av_packet_unref(packet); } // 资源清理 av_packet_free(&packet); av_frame_free(&frame); sws_freeContext(s); avcodec_close(codec); avcodec_free_context(&codec); avformat_close_input(&format); avformat_free_context(format); }
内容的提问来源于stack exchange,提问作者user23143611
相关产品推荐
相关产品推荐

