之前的3篇博客,实现了视频音频解码读帧,并同步播放,这篇讲将实现视频进度条的功能,实现拖动播放。还是使用教程【3】的代码,需要前3篇教程的,请点击以下链接:
基于FFmpeg的视频播放器开发系列教程(三)
基于FFmpeg的视频播放器开发系列教程(二)
基于FFmpeg的视频播放器开发系列教程(一)
先在Qt Designer上画好界面:
基本思路:
(1) 需要实现QSlider的按下,放开等几个虚方法,以及链接信号槽,再用定时器来实现计时。
Qt定时器类不会使用的,自行百度,这里不做讲述。
(2) 每个时刻的关键帧如何确定?
在头文件FFVideoPlyer.h添加定时器函数,QSlider的槽函数:
#pragma once
#include
#include "ui_FFVideoPlyer.h"
#include
class FFVideoPlyer : public QMainWindow
{
Q_OBJECT
public:
FFVideoPlyer(QWidget *parent = Q_NULLPTR);
void timerEvent(QTimerEvent *e);
void mousePressEvent(QMouseEvent *e); //重写该函数
public slots:
void slotOpenFile();
void slotPlay();
void slotSliderPressed();
void slotSliderReleased();
private:
Ui::FFVideoPlyerClass ui;
};
在FFVideoPlyer.cpp文件的构造函数,启动定时器,并链接信号槽
FFVideoPlyer::FFVideoPlyer(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
startTimer(40);
ui.curTime->setText("00:00:00");
ui.totalTime->setText("00:00:00");
connect(ui.btn_OpenVideo, SIGNAL(clicked()), this, SLOT(slotOpenFile()));
connect(ui.btn_Play, SIGNAL(clicked()), this, SLOT(slotPlay()));
connect(ui.timeSlider, SIGNAL(sliderPressed()), this, SLOT(slotSliderPressed()));
connect(ui.timeSlider, SIGNAL(sliderReleased()), this, SLOT(slotSliderReleased()));
}
实现timerEvent方法,在UI上实现显示视频时间
void FFVideoPlyer::timerEvent(QTimerEvent *e)
{
int min = (MyFFmpeg::GetObj()->m_pts / 1000) / 60;
int sec = (MyFFmpeg::GetObj()->m_pts / 1000) % 60;
char buf[1024] = { 0 };
sprintf(buf, "%03d:%02d", min, sec);
ui.curTime->setText(buf); //当前播放时间
if (MyFFmpeg::GetObj()->m_totalMs > 0)
{
float rate = MyFFmpeg::GetObj()->m_pts / (float(MyFFmpeg::GetObj()->m_totalMs));
//只有按下了,才才显示进度条
if (!isPressSlider)
{
this->ui.timeSlider->setValue(rate * 1000); //进度条
}
}
}
本篇博客的关键,我们按下进度条拖动时,在UI上是看到进度条在动,但是如何实现视频的跳转呢,也就是如何根据响应的时间找到对应的视频的关键帧,那么需要用到FFmpeg的API,av_seek_frame ,该函数的声明如下:
/**
* Seek to the keyframe at timestamp.
* 'timestamp' in 'stream_index'.
*
* @param s media file handle
* @param stream_index If stream_index is (-1), a default
* stream is selected, and timestamp is automatically converted
* from AV_TIME_BASE units to the stream specific time_base.
* @param timestamp Timestamp in AVStream.time_base units
* or, if no stream is specified, in AV_TIME_BASE units.
* @param flags flags which select direction and seeking mode
* @return >= 0 on success
*/
int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp,
int flags);
参数说明:
s:操作上下文;
stream_index:基本流索引,表示当前的seek是针对哪个基本流,比如视频或者音频等等。
timestamp:要seek的时间点,以time_base或者AV_TIME_BASE为单位。
Flags:seek标志,可以设置为按字节,在按时间seek时取该点之前还是之后的关键帧,以及不按关键帧seek等,详细请参考FFmpeg的avformat.h说明。
返回值大于0,则调用成功。
对于FFmpeg的每个API,我们可以根据它的英文解释进行学习,英文不好的,可以有道翻译,谷歌翻译等等。
那么我们在之前的MyFFmpeg类再写一个方法,利用av_seek_frame找到每个时间戳的关键帧,实现快进快退,视频的跳转:
bool MyFFmpeg::Seek(float pos)
{
mtx.lock();
if (!m_afc)
{
mtx.unlock();
return false;
}
int64_t stamp = 0;
stamp = pos * m_afc->streams[m_videoStream]->duration;
int re = av_seek_frame(m_afc, m_videoStream, stamp, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME); //向后|关键帧
//清除之前的解码缓冲
avcodec_flush_buffers(m_afc->streams[m_videoStream]->codec);
mtx.unlock();
if (re >= 0)
{
return true;
}
return false;
}
然后再主框架FFVideoPlyer的进度条释放时,我们调用该函数,那么就实现了视频的拖动播放了:
void FFVideoPlyer::slotSliderReleased()
{
isPressSlider = false;
float pos = 0;
pos = this->ui.timeSlider->value() / (float)(ui.timeSlider->maximum() + 1); //从0开始的,不能让分母为0
MyFFmpeg::GetObj()->Seek(pos);
}
代码还做了其它细微的改动,就不多说了,可以下载源码自行阅读学习。本篇博客【源码链接】.
拖动播放的效果如下:
视频显示,音频播放,现在基本都完成了,后面几篇会介绍其它功能实现。
第五篇链接:https://blog.csdn.net/yao_hou/article/details/103790602