Qt 基于FFmpeg的视频转换器 - 播放、暂停以及拖动进度条跳转
- 引言
- 一、设计思路
- 二、核心源码以及相关参考链接
引言
- 本文基于
FFmpeg
,使用Qt制作了一个极简的视频播放器. 相比之前的版本,加入了播放
、暂停
、拖动滑动条跳转
功能,如上所示 (左图
): - 使用
AVSEEK_FLAG_ANY
可以精准跳转到某一帧,但会出现花屏 (左图
). - 使用EV录屏,再使用本软件将其转为gif (
左图
),再GifCam
截取本软件转gif的过程 (右图
),GifCam
无法截取鼠标.
可参考之前的博客:
Qt 基于FFmpeg的视频播放器 - QtFFmpegPlayer
Qt 基于FFmpeg的视频转换器 - 转GIF动图
一、设计思路
-
- 界面设计,鼠标移动到相应位置才会显示相关控件 (按钮、进度条),
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
:
- 初始化就记录下相对坐标,后续可以依据相对坐标判断.
- 按钮和进度条固定到最下方显示
-
- 开始和暂停功能,使用一个内部变量判断是否暂停
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如何优雅实现暂停(挂起)功能
-
- 拖动进度条跳转
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…
二、核心源码以及相关参考链接
-
- 全部源码
已在
gitee
开源:QtFFmpegPlayerDemo
-
- 相关参考链接
【Qt+FFmpeg】解码播放本地视频(二)——实现播放、暂停、重播、倍速功能
【FFmpeg+Qt】视频进度条控制——点击跳转和拖动跳转
FFmpeg源码分析:av_seek_frame()与avformat_seek_file()
avformat_seek_file函数介绍
FFmpeg中的时间基(time_base), AV_TIME_BASE
ffmpeg协议之接口篇之快进快退(av_seek_frame)