Bootstrap

ffpmeg笔记:(2)学习一个开源视频播放器小demo:qt+sdl+ffmpeg,计算时间戳

前言

这个小demo实现了下面的功能:

1.打开文件。
2.播放。
3.暂停。
4.停止。
5.进度条显示,视频时长,当前进度时间。
6.控制面板隐藏和再现。

应该实现却没有实现的功能:

1.倍速播放
2.滑动进度条指定播放位置
3.音量调节

demo的效果:

https://www.bilibili.com/video/BV1Vv4y1j7sw/?spm_id_from=333.999.0.0&vd_source=f724775496aa565e12020e8529f8b274

1.源码和编译方法

原工程:https://gitee.com/tgtsml/QtPlayer
改动后工程:https://gitee.com/huangweide001/hwd-qt-demo/tree/master/QtPlayer-004

1.1编译方法:

我使用的版本是 Qt 5.14.1
1.使用qtCreator打开《QtSdlPlayer.pro》;
2.选择Qt的工具集 MinGW_64_bit;
3.构建项目,在工程目录下自动生成bin目录,用于存放exe文件。把下面的文件拷贝到本bin目录:

3.1 《3rdlib\ffmpeg-4.4-full_build-shared\bin*》
3.2 《3rdlib\SDL2\bin*》

4.在Qt中点击运行。

2.源码简单介绍

2.1 播放线程类 PlayThread

类PlayThread主要完成音视频的播放流程,相关的原理性介绍参考雷神的文章:最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0)
在播放流程的基础上,增加了几个发送信号的函数:

class PlayThread : public QThread
{
    Q_OBJECT
    void run() override;

signals:
    void signal_updateDisplayImage(QImage img);	//	发送画面更新信号
    void signal_updateTotalTime(QTime time);	//	发送视频的长度
    void signal_updatePlayedTime(QTime time);	//	发送播放进度时间
    void signal_PlayedDone(void);				//	发送播放完毕信号
......
}    

这些信号对应主界面类MainWindow的槽函数。

2.1.1 计算当前播放进度时间

在这里插入图片描述
AVRational vedio_time_base= pFormatCtx->streams[videoindex]->time_base;
current_play_time = pFrame->pts * av_q2d(vedio_time_base);
在更新视频帧的位置加入计算当前进度时间的代码,单位为秒;因为一秒有约30个视频帧,不必每次更新视频帧时一起更新时间;所以做一下判断,只有秒数发生变化,才通知主界面更新当前进度时间。

                    ticks = pFrame->pts * av_q2d(vedio_time_base);		//计算当前时间戳
                    //当进度秒数有变化时,发送信号通知界面
                    if(old_sec != ticks){
                        old_sec = ticks;
                        qDebug()<<ticks;
                    	time.setHMS(ticks/3600, (ticks%3600)/60, ticks%60);	//	将当前时间戳赋值给QTime对象
                        emit signal_updatePlayedTime(time);		//	发送信号
                    }

2.2 主界面类 MainWindow

类 MainWindow显示界面包括视频区域和控制区域,控制区域当鼠标移动时显示,鼠标停止动作3秒后隐藏。和PlayThread的信号对应的槽函数:

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

    void slot_updateCurrentImage(QImage img);
    void slot_updateTotalTime(QTime time);
    void slot_updatePlayedTime(QTime time);
    void slot_PlayedDone(void);
......
}    

2.2.1 在Qt widget中显示视频

本demo显示视频的方式是在视频解码时,生成一个QImage的对象,再把QImage通过信号和槽函数的方式传输。
在类PlayThread中生成QImage,并发送信号:

	sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx_Video->height, pFrameYUV->data, pFrameYUV->linesize);

	QImage tmpImg((uchar *)out_buffer_video, pCodecCtx_Video->width, pCodecCtx_Video->height,QImage::Format_RGB32);
	QImage image = tmpImg.copy(); //把图像复制一份 传递给界面显示
	emit signal_updateDisplayImage(image);  //发送界面刷新信号

在类MainWindow中的槽函数接收QImage:

void MainWindow::slot_updateCurrentImage(QImage img)
{
    m_currentImage = img;
    update();
}
void MainWindow::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.setBrush(Qt::black);
    painter.drawRect(0, 0, this->width(), this->height());
    if (m_currentImage.size().width() <= 0){
        return;
    }

    QImage img = m_currentImage.scaled(this->size(), Qt::KeepAspectRatio);
    int x = this->width() - img.width();
    int y = this->height() - img.height();
    painter.drawImage(x/2.0, y/2.0, img);
}

2.2.2 控制区域的自动隐藏和再现

在类MainWindow的构造函数中,开启一个定时器,每次启动后,单次触发。

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
......
    m_timerToHideControlBar = new QTimer(this);
    m_timerToHideControlBar->setSingleShot(true);	//	单次触发
    //	定时时间到,触发进度条隐藏的动作
    connect(m_timerToHideControlBar, &QTimer::timeout, [=](){ui->widget_playControlWgt->setVisible(false);});
}

鼠标按下或者移动时,显示进度条,并启动3秒的定时:

void MainWindow::restartControlBarHideTimer()
{
    if(! ui->widget_playControlWgt->isVisible() || m_timerToHideControlBar->isActive()){
      m_timerToHideControlBar->stop();
      ui->widget_playControlWgt->setVisible(true);
    }
    m_timerToHideControlBar->start(3000);
}
void MainWindow::mousePressEvent(QMouseEvent *event)
{
    restartControlBarHideTimer();
    QMainWindow::mousePressEvent(event);
}
void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
    restartControlBarHideTimer();
    QMainWindow::mouseMoveEvent(event);
}
;