QT 使用FFmpeg API实现简易播放器

开发环境: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目录底下。

二、实现播放功能

QT 使用FFmpeg API实现简易播放器_第1张图片

(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(av_malloc(static_cast(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(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(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);
}

你可能感兴趣的:(音视频,C/C++,c++,视频编解码,qt)