FFmpeg视频播放器开发(四):音视频同步

        之前的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);
}

   代码还做了其它细微的改动,就不多说了,可以下载源码自行阅读学习。本篇博客【源码链接】.   

 

    拖动播放的效果如下:

  

FFmpeg视频播放器开发(四):音视频同步_第1张图片

          视频显示,音频播放,现在基本都完成了,后面几篇会介绍其它功能实现。

          第五篇链接:https://blog.csdn.net/yao_hou/article/details/103790602

        

你可能感兴趣的:(#,C++,ffmpeg视频播放器开发,FFmpeg,视频播放器,拖动播放)