文章目录
前言
这个小demo实现了下面的功能:
1.打开文件。
2.播放。
3.暂停。
4.停止。
5.进度条显示,视频时长,当前进度时间。
6.控制面板隐藏和再现。
应该实现却没有实现的功能:
1.倍速播放
2.滑动进度条指定播放位置
3.音量调节
demo的效果:
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);
}