开发环境:MinGw730+QT5.14.2+FFMPEG20220422
一、开发环境搭建
(1)FFMPEG源码编译见
windows 使用qt mingw730_64 编译ffmpeg_炽旗7的博客-CSDN博客
(2)添加库
在qt工程目录底下新建ffmpeg目录,将FFMPEG源码编译后生成的inlude、lib文件夹及内容拷贝到ffmpeg底下
在新建的QT工程的pro文件添加lib目录及lib目录。
INCLUDEPATH += "ffmpeg/include"
LIBS += -L$$PWD/ffmpeg/lib -lavutil -lavformat -lavcodec -lavdevice -lavfilter
LIBS += -lswresample -lswscale
将FFMPEG源码编译后生成的bin目录底下dll文件拷贝到qt生成的exe目录底下。
二、实现播放功能
(1)创建CFfmpegDecodeTh继承QThread,实现基本解码功能
(2) 创建CFfmpegWidge继承QWidget,实现播控控制及显示功能
---------------------------CFfmpegDecodeTh---------------------------------------------
CFfmpegDecodeTh::CFfmpegDecodeTh(const QString &strFile,
QObject *parent)
: QThread(parent)
, m_strVideoFile(strFile)
, m_bPlaying(true)
{
}
CFfmpegDecodeTh::~CFfmpegDecodeTh()
{
CQ_DEBUG() << "~CFfmpegDecodeTh:" << currentThreadId();
}
/******************************************************************************
** 线程停止函数
** 触发m_hTerminateEvent信号
******************************************************************************/
void CFfmpegDecodeTh::stop()
{
// m_waitTerminateCond.wakeAll();
m_mutexTerminate.tryLock(1);
m_mutexTerminate.unlock();
m_waitPauseCond.wakeAll();
CQ_DEBUG() << "FfmpegDecodeTh stop";
}
/******************************************************************************
** 暂停
** 设置m_bPlaying false
******************************************************************************/
void CFfmpegDecodeTh::pause()
{
m_bPlaying = false;
}
/******************************************************************************
** 线程停止函数
** 设置m_bPlaying true
** 触发暂停条件变量
******************************************************************************/
void CFfmpegDecodeTh::resume()
{
m_bPlaying = true;
m_waitPauseCond.wakeAll();
}
/******************************************************************************
** ffmpeg处理函数
******************************************************************************/
void CFfmpegDecodeTh::run()
{
m_nVideoStartTime = 0;
CQ_DEBUG() << "FfmpegDecodeTh run:" << currentThreadId();
if (false == initFfmpegForFile())
{
CQ_DEBUG() << "initFfmpegForFile failed";
return;
}
// 分配帧显示内存
AVFrame *pAvFrame = av_frame_alloc();
AVFrame *pAvFrameRgb = av_frame_alloc();
int nBufSize =av_image_get_buffer_size(AV_PIX_FMT_RGB32, m_pVideoCodecCtx->width, m_pVideoCodecCtx->height, 1);
uchar *pOutBuffer = reinterpret_cast<uchar *>(av_malloc(static_cast<size_t>(nBufSize)));
av_image_fill_arrays(pAvFrameRgb->data, pAvFrameRgb->linesize, pOutBuffer,
AV_PIX_FMT_RGB32, m_pVideoCodecCtx->width, m_pVideoCodecCtx->height, 1);
AVPacket *pPacket = reinterpret_cast<AVPacket *>(av_malloc(sizeof(AVPacket)));
CQ_DEBUG() << "sws_getContext:" << m_pVideoCodecCtx->width << " " << m_pVideoCodecCtx->pix_fmt;
// 图片格式转为RGB32
SwsContext *pSwsCtx = sws_getContext(m_pVideoCodecCtx->width, m_pVideoCodecCtx->height, m_pVideoCodecCtx->pix_fmt,
m_pVideoCodecCtx->width, m_pVideoCodecCtx->height, AV_PIX_FMT_RGB32,
SWS_BICUBIC, Q_NULLPTR, Q_NULLPTR, Q_NULLPTR);
m_bPlaying = true;
m_mutexTerminate.lock();
m_nVideoStartTime = 0;
m_pauseTime = 0;
int64_t nDecodeTimeCount = 0;
int64_t nDecodeBegin = av_gettime();
while (true)
{
if (m_bPlaying == false) // 暂停状态则等待条件变量
{
int64_t pauseStart = av_gettime();
m_mutexPause.lock();
m_waitPauseCond.wait(&m_mutexPause);
m_mutexPause.unlock();
int64_t pauseInterval = av_gettime() - pauseStart;
m_pauseTime += pauseInterval;
}
// int64_t frameBegin = av_gettime();
// 停止互斥量能触发则退出
if (m_mutexTerminate.tryLock(1) == true)
{
m_mutexTerminate.unlock();
break;
}
if (av_read_frame(m_pAvFormatCtx, pPacket) != 0)
{
break;
}
if (WaitForSingleObject(m_hTerminateEvent, 1) == WAIT_OBJECT_0)
{
CQ_DEBUG() << "Terminate";
break;
}
if (pPacket->stream_index != m_nVideoIndex)
{
av_packet_unref(pPacket);
continue;
}
// CQ_DEBUG() << "avcodec_send_packet";
int nRet = avcodec_send_packet(m_pVideoCodecCtx, pPacket);
if (nRet != 0)
{
char szTmp[64] = {0};
av_strerror(nRet, szTmp, 63);
CQ_DEBUG() << pPacket->stream_index << "avcodec_send_packet failed: = " << szTmp;
av_packet_unref(pPacket);
continue;
}
// CQ_DEBUG() << "avcodec_receive_frame";
while (avcodec_receive_frame(m_pVideoCodecCtx, pAvFrame) == 0)
{
// nDecodeTimeCount += (av_gettime() - frameBegin);
sws_scale(pSwsCtx, pAvFrame->data, pAvFrame->linesize, 0, m_pVideoCodecCtx->height,
pAvFrameRgb->data, pAvFrameRgb->linesize);
CQ_DEBUG() << "frame decode pts:" << pAvFrame->pts << " " << pPacket->duration
<< " " << m_pAvFormatCtx->streams[m_nVideoIndex]->time_base.num << ":"
<< m_pAvFormatCtx->streams[m_nVideoIndex]->time_base.den
<< " " << "start time:" << m_pAvFormatCtx->streams[m_nVideoIndex]->start_time
<< " " << av_gettime() - nDecodeBegin;
// g_pMainWindow->addMsg(QString("%1").arg(pAvFrame->pts));
QImage img(pAvFrameRgb->data[0], m_pVideoCodecCtx->width, m_pVideoCodecCtx->height, QImage::Format_RGB32);
CFfmpegWidget *pWidget = dynamic_cast<CFfmpegWidget *>(parent());
if (pWidget != Q_NULLPTR)
{
pWidget->pushImage(img);
}
if (m_nVideoStartTime == 0)
{
m_nVideoStartTime = av_gettime();
}
else
{
delayTime(pAvFrame, m_nVideoIndex);
}
}
av_packet_unref(pPacket);
}
sws_freeContext(pSwsCtx);
av_frame_free(&pAvFrame);
av_frame_free(&pAvFrameRgb);
av_free(pPacket);
av_free(pOutBuffer);
unInitFfmpeg();
CQ_DEBUG() << "FfmpegDecodeTh Run Exist";
}
/******************************************************************************
** 初始化ffmpeg上下文
******************************************************************************/
bool CFfmpegDecodeTh::initFfmpegForFile()
{
do
{
avdevice_register_all();
av_dict_set(&m_pAvOptions, "threads", "auto", 0);
AVFormatContext *&pFormatCtx = m_pAvFormatCtx;
if (avformat_open_input(&pFormatCtx, m_strVideoFile.toStdString().c_str(), nullptr, &m_pAvOptions) != 0)
{
break;
}
if (avformat_find_stream_info(pFormatCtx, nullptr) < 0)
{
break;
}
// 获取视频帧
int nVideoIndex = -1;
for (unsigned int i = 0; i < pFormatCtx->nb_streams; ++i)
{
if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
nVideoIndex = i;
break;
}
}
if (nVideoIndex == -1)
{
break;
}
m_nVideoIndex = nVideoIndex;
// 获取解码参数
AVCodecParameters *pCodecParams = pFormatCtx->streams[nVideoIndex]->codecpar;
const AVCodec *pAvCodec = avcodec_find_decoder(pCodecParams->codec_id);
if (pAvCodec == nullptr)
{
break;
}
// 创建视频解码上下文
m_pVideoCodecCtx = avcodec_alloc_context3(pAvCodec);
CQ_DEBUG() << pAvCodec->long_name << pAvCodec->type;
if (avcodec_parameters_to_context(m_pVideoCodecCtx, pCodecParams) < 0)
{
break;
}
// m_pVideoCodecCtx->lowres = pAvCodec->max_lowres;
// m_pVideoCodecCtx->flags2 |= AV_CODEC_FLAG2_FAST;
if (avcodec_open2(m_pVideoCodecCtx, pAvCodec, nullptr) < 0)
{
break;
}
if (m_pVideoCodecCtx->width == 0 || m_pVideoCodecCtx->height == 0)
{
CQ_DEBUG() << "VideoCodecCtx width or height is 0";
break;
}
// 打印视频格式信息
// av_dump_format(pFormatCtx, 0, m_strVideoFile.toStdString().c_str(), 0);
return true;
} while (0);
return false;
}
/******************************************************************************
** 释放ffmpeg资源
******************************************************************************/
void CFfmpegDecodeTh::unInitFfmpeg()
{
if (m_pVideoCodecCtx != Q_NULLPTR)
{
avcodec_close(m_pVideoCodecCtx);
avcodec_free_context(&m_pVideoCodecCtx);
m_pVideoCodecCtx = Q_NULLPTR;
}
if (m_pAvFormatCtx != Q_NULLPTR)
{
avformat_close_input(&m_pAvFormatCtx);
m_pAvFormatCtx = Q_NULLPTR;
}
if (m_pAvOptions != Q_NULLPTR)
{
av_dict_free(&m_pAvOptions);
m_pAvOptions = Q_NULLPTR;
}
}
/******************************************************************************
** 延迟
** pFrame:当前帧数据
** nStreamIndex:视频索引
******************************************************************************/
void CFfmpegDecodeTh::delayTime(const AVFrame *pFrame, int nStreamIndex)
{
qint64 nOffsetTime = getDelayTime(pFrame, nStreamIndex);
if (nOffsetTime > 0)
{
av_usleep(nOffsetTime);
return;
}
g_pMainWindow->addMsg(QString("Pts:%1 delay:%2").arg(pFrame->pts).arg(nOffsetTime));
// CQ_DEBUG() << "delayTime" << nOffsetTime;
}
/******************************************************************************
** 获取当前帧视频需要延迟的时间
** packet:当前帧数据
** nStreamIndex:视频索引
******************************************************************************/
qint64 CFfmpegDecodeTh::getDelayTime(const AVFrame *pFrame, int nStreamIndex)
{
AVRational timeBase = m_pAvFormatCtx->streams[nStreamIndex]->time_base;
// CQ_DEBUG() << "timeBase:" << timeBase.num << ":" << timeBase.den;
AVRational timeBaseQ = {1, AV_TIME_BASE};
int64_t nPtsTime = av_rescale_q(pFrame->pts, timeBase, timeBaseQ);
// CQ_DEBUG() << "PtsTime:" << nPtsTime << pFrame->pts << " " << pFrame->best_effort_timestamp;
int64_t nNowTime = av_gettime() - m_nVideoStartTime - m_pauseTime;
int64_t nOffsetTime = nPtsTime - nNowTime;
if (0)
{
CQ_DEBUG() << "packet pts:" << pFrame->pts
<<" PtsTime:" << nPtsTime
<< " nowTime:" << nNowTime << " offsetTime" << nOffsetTime
<< " pauseTime:" << m_pauseTime;
}
return nOffsetTime;
}
---------------------------CFfmpegWidget---------------------------------------------
CFfmpegWidget::CFfmpegWidget(QWidget *parent)
: QWidget(parent)
{
CQ_DEBUG() << "CFfmpegWidget:" << QThread::currentThreadId();
}
CFfmpegWidget::~CFfmpegWidget()
{
if (m_pDecodeTh != Q_NULLPTR)
{
m_pDecodeTh->stop();
m_pDecodeTh->quit();
m_pDecodeTh->wait();
delete m_pDecodeTh;
m_pDecodeTh = Q_NULLPTR;
}
}
/******************************************************************************
** 增加待显示的图片
******************************************************************************/
void CFfmpegWidget::pushImage(const QImage &img)
{
m_listImage.push_back(img);
update();
}
/******************************************************************************
** 关闭解码线程
******************************************************************************/
void CFfmpegWidget::closeTh()
{
CQ_DEBUG() << "FfmpegWidget closeTh";
}
/******************************************************************************
** 播放视频文件
******************************************************************************/
void CFfmpegWidget::play(const QString &strFile)
{
if (m_pDecodeTh != Q_NULLPTR)
{
m_pDecodeTh->stop();
m_pDecodeTh->quit();
m_pDecodeTh->wait();
delete m_pDecodeTh;
m_pDecodeTh = Q_NULLPTR;
}
m_pDecodeTh = new CFfmpegDecodeTh(strFile, this);
if (m_pDecodeTh == Q_NULLPTR)
{
return;
}
m_pDecodeTh->start();
}
/******************************************************************************
** 暂停播放
******************************************************************************/
void CFfmpegWidget::pause()
{
if (m_pDecodeTh == Q_NULLPTR)
{
return;
}
m_pDecodeTh->pause();
}
/******************************************************************************
** 恢复播放
******************************************************************************/
void CFfmpegWidget::resume()
{
if (m_pDecodeTh == Q_NULLPTR)
{
return;
}
m_pDecodeTh->resume();
}
/******************************************************************************
** 停止播放视频文件
******************************************************************************/
void CFfmpegWidget::stop()
{
if (m_pDecodeTh == Q_NULLPTR)
{
return;
}
m_pDecodeTh->stop();
}
/******************************************************************************
** 重绘
******************************************************************************/
void CFfmpegWidget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
if (m_listImage.isEmpty())
{
return;
}
QImage img = m_listImage.first();
m_listImage.removeFirst();
QPainter painter(this);
painter.drawImage(rect(), img);
}