Qt 基于FFmpeg的视频播放器 - 播放、暂停以及拖动滑动条跳转

发布于:2024-06-28 ⋅ 阅读:(12) ⋅ 点赞:(0)

Qt 基于FFmpeg的视频转换器 - 播放、暂停以及拖动进度条跳转

引言

效果展示 另存为gif

  • 本文基于FFmpeg,使用Qt制作了一个极简的视频播放器. 相比之前的版本,加入了播放暂停拖动滑动条跳转功能,如上所示 (左图):
  • 使用AVSEEK_FLAG_ANY可以精准跳转到某一帧,但会出现花屏 (左图).
  • 使用EV录屏,再使用本软件将其转为gif (左图),再GifCam截取本软件转gif的过程 (右图),GifCam无法截取鼠标.

可参考之前的博客:
Qt 基于FFmpeg的视频播放器 - QtFFmpegPlayer
Qt 基于FFmpeg的视频转换器 - 转GIF动图

一、设计思路

    1. 界面设计,鼠标移动到相应位置才会显示相关控件 (按钮、进度条),override鼠标移动事件
void QWidget_PlayVideo::mouseMoveEvent(QMouseEvent *event)
{
    // 布局在鼠标移动过程中会变化,使得布局内控件闪烁
//    if( m_Hlayout->geometry().contains(event->pos())){
//        for(int i = 0; i < m_Hlayout->count(); i++){
//            QLayoutItem *item = m_Hlayout->itemAt(i);
//            item->widget()->show();
//        }
//    }
//    else{
//        for(int i = 0; i < m_Hlayout->count(); i++){
//            QLayoutItem *item = m_Hlayout->itemAt(i);
//            item->widget()->hide();
//        }
//    }
    // 使用按钮和滑块的geometry进行判断:鼠标是否移动到窗口底部
    if( m_btn_startorstop->geometry().contains(event->pos()) ||
        m_slider->geometry().contains(event->pos())){
        for(int i = 0; i < m_Hlayout->count(); i++){
            QLayoutItem *item = m_Hlayout->itemAt(i);
            item->widget()->show();
        }
    }
    else{
        for(int i = 0; i < m_Hlayout->count(); i++){
            QLayoutItem *item = m_Hlayout->itemAt(i);
            item->widget()->hide();
        }
    }
}

建议不要使用布局的geometry,其在鼠标移动过程会变化 (暂不清楚为什么,可能bug 或者控件隐藏之后相关布局会变化,geometry也会随之改变) - 可优化/todo

  1. 初始化就记录下相对坐标,后续可以依据相对坐标判断.
  2. 按钮和进度条固定到最下方显示
    1. 开始和暂停功能,使用一个内部变量判断是否暂停
    connect(m_btn_startorstop, &QPushButton::clicked, this, [&]{    // 按钮点击,暂停 or 继续播放
        if(m_FFmpegVideo->m_stopPlay == false){
            qDebug()<<"视频暂停";
            m_FFmpegVideo->m_stopPlay = true;    // 停止运行,跳出循环
            // todo 修改按钮,播放
        }
        else{
            qDebug()<<"视频继续播放";
            m_FFmpegVideo->m_stopPlay = false;
            m_PlayThread->start();
            m_PlayThread->quit();   // 执行完后自动关闭,否则一直在运行中... 无法重新start发送开始信号
            // todo 修改按钮,暂停
        }
    });

暂停直接退出线程即可,avformat_context内部会记录进度,再播放会从下一帧继续解码 /todo 使用原子类型
可参考:QThread如何优雅实现暂停(挂起)功能

    1. 拖动进度条跳转
    connect(m_FFmpegVideo, &FFmpegVideo::sig_SendFrameNum_play, this, [&](int frame_id){  // 滑动条随视频播放滑动
        if(b_slidermoved == false){
            m_slider->setValue(frame_id);
        }
    });

    connect(m_slider, &QSlider::sliderReleased, this, [&]{     // 滑动条手动滑动,修改视频播放位置
        qDebug()<< "sliderReleased: " << m_slider->value();
        this->m_FFmpegVideo->JumptotheFrame(m_slider->value(), m_slider->value(), m_slider->value());
        this->m_FFmpegVideo->m_frame_id = m_slider->value();
        b_slidermoved = false;
    });

    connect(m_slider, &QSlider::sliderMoved, this, [&]{
        b_slidermoved = true;
    });

使用b_slidermoved判断滑动条是否被手动拖动,是的话就先停止滑动条随视频播放滑动. /todo 目前滑动条是根据帧id进行滑动,后续可以改为按照播放时间

void FFmpegVideo::JumptotheFrame(qint64 min_frame_id, qint64 frame_id, qint64 max_frame_id)
{  
    // 将帧号转换为时间戳
    int64_t min_ts = min_frame_id * this->m_frame_timestamp;
    int64_t ts = frame_id * this->m_frame_timestamp;
    int64_t max_ts = max_frame_id * this->m_frame_timestamp;

    qDebug()<<"跳转到:" << ts/1000000.0 << "s";
    // this->av_stream_index
    // avformat_seek_file(this->avformat_context, -1, min_ts, ts, max_ts, AVSEEK_FLAG_FRAME);
    avformat_seek_file(this->avformat_context, -1, min_ts, ts, max_ts, AVSEEK_FLAG_ANY);
}

由于传递的参数是第几帧,需将帧号转为视频时间戳
使用AVSEEK_FLAG_ANY可以精准跳转到某一帧,但会出现花屏
使用AVSEEK_FLAG_FRAME不会出现花屏,但是无法精准跳转某帧,只会跳转到视频关键帧

  • /todo 还有很多待优化的bug…

二、核心源码以及相关参考链接

    1. 全部源码

已在gitee开源:QtFFmpegPlayerDemo

    1. 相关参考链接

【Qt+FFmpeg】解码播放本地视频(二)——实现播放、暂停、重播、倍速功能
【FFmpeg+Qt】视频进度条控制——点击跳转和拖动跳转
FFmpeg源码分析:av_seek_frame()与avformat_seek_file()
avformat_seek_file函数介绍
FFmpeg中的时间基(time_base), AV_TIME_BASE
ffmpeg协议之接口篇之快进快退(av_seek_frame)