上面介绍了如何使用opengl绘制视频和Qt的界面设计,也比较简单,现在我们看下如何控制视频播放及进度的控制,内容主要分为以下几个部分

1、创建解码线程控制播放速度

2、通过Qt打开外部视频

3、视频总时间显示和播放的当前时间显示

4、进度条显示播放进度、拖动进度条控制播放位置

5、控制视频播放和暂停

6、视频显示和窗口大小变化同步

7、重载Qt滑动条类鼠标点击移动滑动条并跳转到相应视频位置

一、创建解码线程控制播放速度

上一篇中我们说了播放视频时不是很顺,有些卡顿。因为我们将解码过程以及转RGB过程都放在QT的槽中即paintEVent中,这是一个重绘的过程,通常来说对于这个过程实现的都是一些比较简单的内容,所以对于读取视频,解码过程我们重新创建一个线程进行实现。这里我们创建一个XVideoThread类(继承自QThread),用于读取,解码以及控制读取的速度。考虑到实际中解码后的视频帧,在重绘时不一定需要那么帧(一个视频中原fps为250帧,在我重绘显示时只需要25帧的情况,也需要知道fps),这里我们只是控制它的读取速度。所以首先我们需要原视频的fps,在XFFMpeg.h中申明变量fps,在XFFMpeg.cpp文件的Open函数中打开解码器过程中判断是否为视频内加入fps = r2d(ic->streams[i]->avg_frame_rate);//获得视频得fps,其中的r2d函数是避免计算时分母为0时的特判情况,代码如下。

static double r2d(AVRational r)
{return r.den == 0 ? 0 : (double)r.num / (double)r.den;
}
if (enc->codec_type == AVMEDIA_TYPE_VIDEO)//判断是否为视频{videoStream = i;fps = r2d(ic->streams[i]->avg_frame_rate);//获得视频得fpsAVCodec *codec = avcodec_find_decoder(enc->codec_id);//查找解码器

原视频的fps我们已经获得了,现在需要实现读取视频、解码以及控制读取的速度了,在XVideoThread.h中

#pragma once
#include <QThread>
class XVideoThread:public QThread
{
public:static XVideoThread *Get()//创建单例模式{static XVideoThread vt;return &vt;}void run();//线程的运行XVideoThread();virtual ~XVideoThread();
};

在XVideoThread.cpp中

#include "XVideoThread.h"
#include "XFFmpeg.h"bool isexit = false;//线程未退出
XVideoThread::XVideoThread()
{
}XVideoThread::~XVideoThread()
{
}void XVideoThread::run()
{while (!isexit)//线程未退出{AVPacket pkt = XFFmpeg::Get()->Read();if (pkt.size <= 0)//未打开视频{msleep(10);continue;}if (pkt.stream_index != XFFmpeg::Get()->videoStream){av_packet_unref(&pkt);//不为视频时释放pktcontinue;}XFFmpeg::Get()->Decode(&pkt);//解码视频帧av_packet_unref(&pkt);if (XFFmpeg::Get()->fps > 0)//控制解码的进度msleep(1000/XFFmpeg::Get()->fps);}}

首先设置线程未退出,读取AVPacket包,若未打开视频,此时pkt.size必然小于0,线程睡眠一段时间继续。否则我们开始解码视频帧,同时利用线程的睡眠时间控制解码进度进而来控制播放的速度。

最后我们在VideoWidget.cpp中开启线程。

VideoWidget::VideoWidget(QWidget *parent) :QOpenGLWidget(parent)
{XFFmpeg::Get()->Open("1080.mp4");//打开视频startTimer(20);//设置定时器XVideoThread::Get()->start();//开启读取视频、解码、控制播放速度线程
}

二、通过Qt打开外部视频

上面我们的视频文件的打开Open函数都是确定了某个视频,这里我们通过Qt的控件按钮自定义打开视频文件,进入Qt的设计界面选中openButton打开文件这个按钮,然后Qt上的任务栏中找到编辑信号/槽,点击,之后按住openButton控件拖动时出现红线,将红线拖动到agineXplay这个界面处(因为我的项目名称就叫做agineXplay,大家的可能都不一样),这里要注意agineXplay的界面和我们的openGL Widget界面不一样的,openButton和playButton都在openGL Widget中,通过点击他们在agineXplay中响应的,(当然此时的openGl Widget我已经将他更改为VideoWidget类,上面也说到了,对于VS、Qt如何添加信号槽的也可以百度了解下)

然后出现如上的界面,点击编辑即可得到右侧的槽和信号的窗口,我们加入槽open()函数,之后点击clicked()信号和open()函数,此时我们的信号和槽就连接上了。现在我们在aginexplay.h中申明槽函数open()。

#ifndef AGINEXPLAY_H
#define AGINEXPLAY_H#include <QtWidgets/QWidget>
#include "ui_aginexplay.h"class agineXplay : public QWidget
{Q_OBJECTpublic:agineXplay(QWidget *parent = 0);~agineXplay();
public slots:void open();//槽函数用来响应打开文件的按钮private:Ui::agineXplayClass ui;
};#endif // AGINEXPLAY_H

相应的在aginexplay.cpp中的定义。

#include "aginexplay.h"
#include <QFileDialog>
#include <QMessageBox>
#include "XFFmpeg.h"
agineXplay::agineXplay(QWidget *parent): QWidget(parent)
{ui.setupUi(this);
}agineXplay::~agineXplay()
{}void agineXplay::open()
{QString name = QFileDialog::getOpenFileName(this,QString::fromLocal8Bit("选择视频文件"));//打开视频文件if (name.isEmpty())return;this->setWindowTitle(name);//设置窗口的标题if(!XFFmpeg::Get()->Open(name.toLocal8Bit()))//未打开视频成功{QMessageBox::information(this,"err","file open failed!");//弹出错误窗口}}

三、视频总时间显示和播放的当前时间显示

现在我们开始对视频当前的进度进行设置,如何显示总时间以及当前的时间呢?总时间比较好获得,在解封装时已经得到,对于当前时间,我们可以利用decode时它的pts来表示,然后一比较就能获得当前的播放位置。现在我们对Qt界面进行设置。在界面中加入两个label,第一个label中内容设置为000:00 /  ,第二个label中内容设置为000:00,分别代表当前播放时间和视频总时间,对象名分别为playtime和totaltime,如下图。

因为需要获得解封装视频后的总时间,所以这里我们需要对XFFMpeg.cpp中Open()函数的返回值进行修改,这里改为返回int代表视频总时间totalMs。

在aginexplay.cpp中的open()函数更改如下内容。

void agineXplay::open()
{QString name = QFileDialog::getOpenFileName(this,QString::fromLocal8Bit("选择视频文件"));//打开视频文件if (name.isEmpty())return;this->setWindowTitle(name);//设置窗口的标题int totalMs = XFFmpeg::Get()->Open(name.toLocal8Bit());//获取视频总时间if(totalMs<= 0)//未打开成功{QMessageBox::information(this,"err","file open failed!");//弹出错误窗口return;}char buf[1024] = {0};//用来存放总时间int min = (totalMs)/60;int sec = (totalMs) % 60;sprintf(buf, "%03d:%02d",min,sec);//存入buf中ui.totaltime->setText(buf);//显示在界面中}

从而获取视频的总时间并显示在界面中。现在我们来获取播放视频得当前时间,在XFFmpeg.cpp的Decode()函数中我们获得当前播放的的pts。

mutex.unlock();pts = yuv->pts*r2d(ic->streams[pkt->stream_index]->time_base)*1000;//设置当前播放的ptsreturn yuv;

然后设置定时器,按照一秒25帧,即定时器的时间设置为40ms,在aginexplay.h中申明定时器函数timerEvent(),在aginexplay.cpp定义如下。

void agineXplay::timerEvent(QTimerEvent *event)
{int min = (XFFmpeg::Get()->pts ) / 60;//视频播放当前的分钟  int sec = (XFFmpeg::Get()->pts ) % 60;//视频播放当前的秒char buf[1024] = {0};sprintf(buf,"%03d:%02d / ",min,sec);//存入buf中ui.playtime->setText(buf);//显示在界面中
}

然后在aginexplay.cpp的构造函数中启动定时器,达到一秒25帧的播放速率,至此结束。

startTimer(40);

四、进度条显示播放进度、拖动进度条控制播放位置

通常我们使用的播放器不仅有以上功能,还可以显示播放进度以及我们拖动进度条时控制它的播放位置。首先我们进入Qt的设计界面,加入一个水平滑动条,对象名改为playslider,在vs中的aginexplay.cpp的timerEvent()函数中增添如下代码。

void agineXplay::timerEvent(QTimerEvent *event)
{int min = (XFFmpeg::Get()->pts ) / 60;//视频播放当前的分钟  int sec = (XFFmpeg::Get()->pts ) % 60;//视频播放当前的秒char buf[1024] = {0};sprintf(buf,"%03d:%02d /  ",min,sec);//存入buf中ui.playtime->setText(buf);//显示在界面中if (XFFmpeg::Get()->totalMs > 0)//判断视频得总时间{float rate = (float)XFFmpeg::Get()->pts / (float)XFFmpeg::Get()->totalMs;//当前播放的时间与视频总时间的比值ui.playslider->setValue(rate * 1000);//设置当前进度条位置}}

rate*1000是因为我在Qt设计界面中将进度条的取值设置在0~999,所以需要这样转化。现在设置进度条的拖动来显示播放,在XFFMPeg.h中申明函数Seek(),此函数主要是当我们通过鼠标拖动进度条时能更新到当前视频,函数的定义如下。

bool XFFmpeg::Seek(float pos)
{mutex.lock();if (!ic)//未打开视频{mutex.unlock();return false;}int64_t stamp = 0;stamp = pos * ic->streams[videoStream]->duration;//当前它实际的位置int re = av_seek_frame(ic, videoStream, stamp,AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME);//将视频移至到当前点击滑动条位置avcodec_flush_buffers(ic->streams[videoStream]->codec);//刷新缓冲,清理掉mutex.unlock();if (re > 0)return true;return false;
}

对于 av_seek_frame(ic, videoStream, stamp,  AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME)函数;

这个函数前三个函数比较好理解,对于第四个参数,我们先要知道,视频帧分为I、B、P,这个前面提到过,当我们拖动进度条时,不可能恰好拖到它的有效帧上(比如有效的是80 、120,而我们刚好拖动到了100,此时这里的第四个参数AVSEEK_FLAG_BACKWARD意义就是从它的前一帧80开始重新解析,避免视频帧的遗漏,当然从120解析也是可以的,改变第四个参数就可以了),这里的AVSEEK_FLAG_FRAME代表的是有效帧,

对于函数avcodec_flush_buffers(ic->streams[videoStream]->codec)函数;

在我们点击滑动条更新视频位置后,由于此时缓冲区中还有先前未滑动时解码到的视频帧,这样的帧对于我们已经滑动后的位置已没有意义了,应该从缓冲区中清理掉。

现在我们需要确定按住滑动条直至松开后滑动条的位置,首先我们在Qt设计界面中对于滑动条设计两个相应槽,分别相应滑动条的按下和松开时的操作,信号函数为sliderPressed()和sliderReleased(),这里的槽函数我也是定义了sliderPressed()和sliderReleased(),然后进入aginexplay.h中申明


public slots:void open();//槽函数用来响应打开文件的按钮void sliderPressed();//按下进度条时      void sliderReleased();//松开进度条时

在aginexplay.cpp中定义如下,当松开滑动条时,获得当前滑动条位置和总滑动条长度比例,放入Seek()函数中进行滑动处理.

void agineXplay::sliderPressed()
{isPressSlider = true;
}void agineXplay::sliderReleased()
{isPressSlider = false;float pos = 0;//松开时此时滑动条的位置与滑动条的总长度pos = (float)ui.playslider->value() / (float)(ui.playslider->maximum() + 1);XFFmpeg::Get()->Seek(pos);
}

isPressSlider 是在aginexplay.cpp中定义的静态变量,用来控制是否按下了进度条

static bool isPressSlider;//是否按下进度条

同时在计时器timerEvent中修改部分内容,即只有我们松开滑动条或者未对滑动条操作时才进入  ui.playslider->setValue(rate * 1000);//设置当前进度条位置

if (XFFmpeg::Get()->totalMs > 0)//判断视频得总时间{float rate = (float)XFFmpeg::Get()->pts / (float)XFFmpeg::Get()->totalMs;//当前播放的时间与视频总时间的比值if (!isPressSlider) //当松开时继续刷新进度条位置ui.playslider->setValue(rate * 1000);//设置当前进度条位置}

五、控制视频得播放暂停

打开Qt设计器,点击信号槽,再点击播放按钮将红线拖动至窗口界面,增加一个play()槽,然后在VS中的aginexplay.h中申明play()槽,在aginexplay.cpp中定义如下:

void agineXplay::play()
{isPlay = !isPlay;//播放取反if (isPlay)//如果播放了{ui.playButton->setStyleSheet(PLAY);//显示播放按钮状态}else{ui.playButton->setStyleSheet(PAUSE);//显示暂停播放按钮状态}}

对于PLAY和PAUSE对应的是图标的暂停和播放,aginexplay.cpp定义如下

static bool isPlay = false;//是否播放
#define  PAUSE "QPushButton\
{border-image: url\
(:/agineXplay/Resources/stop.jpg);}"//css语法,暂停按钮
#define  PLAY "QPushButton\
{border-image: url\
(:/agineXplay/Resources/play.jpg);}"//播放按钮

目前只是实现了它播放暂停的界面,现在实现它点击暂停时画面的暂停和重新播放画面恢复,我们在XFFMpeg.h中申明

bool isPlay = false;//播放暂停

然后我们在线程XVideoThread.cpp中读取每帧数据前判断是否暂停了,若暂停了睡眠一段时间跳出,这样就不进行下面的解码、重绘过程,画面定格在这里,在run()中添加以下部分代码。

void XVideoThread::run()
{while (!isexit)//线程未退出{if (!XFFmpeg::Get()->isPlay)//如果为暂停状态,不处理{msleep(10);continue;}AVPacket pkt = XFFmpeg::Get()->Read();

在aginexplay.cpp中的play()中将暂停播放状态传递给XFFMpeg中的isPlay,如下

void agineXplay::play()
{isPlay = !isPlay;//播放取反XFFmpeg::Get()->isPlay = isPlay;//将播放状态传递于XFFMpeg中的isPlayif (isPlay)//如果播放了

现在有一个问题,当我们暂停后拉动滑动条时,此时滑动条过不去,虽然继续播放后视频处于我们滑动到的位置,但现在我们无法知道滑动到哪里。这个就与我们的Seek函数有关的,因为我们已经暂停了,不再解码,但要想获得此时滑动条到达的时间我们可以利用它的滑动位置乘以它的时间基数,从而得到它当前的pts,在Seek()中加入这样一句pts。

int64_t stamp = 0;stamp = pos * ic->streams[videoStream]->duration;//当前它实际的位置pts = stamp * r2d(ic->streams[videoStream]->time_base);//获得滑动条滑动后的时间戳

六、视频显示和窗口大小变化同步

之前的窗口都是固定大小的,当我们全屏时,视频窗口不会随之改变,现在需要将它修改为符合的窗口。在aginexplay.h中申明事件void resizeEvent(QResizeEvent *event);//改变窗口大小,在aginexplay.cpp中

void agineXplay::resizeEvent(QResizeEvent *event)
{ui.openGLWidget->resize(size());//设置视频窗口和界面的相同大小ui.playButton->move(this->width() / 2 + 50, this->height() - 80);//放大缩小后播放按钮位置ui.openButton->move(this->width() / 2 - 50, this->height() - 80);//........打开文件按钮位置,以下几个同样意义ui.playslider->move(25,this->height()-120);ui.playslider->resize(this->width()-50,ui.playslider->height());ui.playtime->move(25, ui.playButton->y());ui.totaltime->move(130,ui.playButton->y());}

需要注意的是ui.openGLWidget->resize(size());//设置视频窗口和界面的相同大小,对于这个函数由于是重新确定视频窗口和界面大小的一致,在VideoWidget.cpp中的paintEvent函数中,在改变窗口大小时我们需要重新分配Image内存空间,所以我们需要加入这样一段内容即可。

void VideoWidget::paintEvent(QPaintEvent *e)
{//绘制static QImage *image = NULL;static int w = 0;static int h = 0;if (w != width() || h != height())//当缩小窗口或者方法窗口时,删除image,重新绘制{if (image){delete image->bits();//删除内容delete image;image = NULL;}}if (image == NULL){uchar *buf = new uchar[width()*height() * 4];//存放解码后的视频空间image = new QImage(buf, width(), height(), QImage::Format_ARGB32);}

七、重载Qt滑动条类鼠标点击移动滑动条并跳转到相应视频位置

对于Qt的滑动条,我们拖动时是没有问题的,但是当鼠标在滑动条某个位置按下它不能指定到该位置,我们现在实现它。增加一个类XSlider(在Qt设计器中按照上一篇中方法将QSlider类提升为XSlider),在XSlider.h中申明

#pragma once
#include "qobject.h"
#include <QSlider>
class XSlider :public QSlider
{Q_OBJECTpublic:XSlider(QWidget *parent);~XSlider();void mousePressEvent(QMouseEvent *ev);//鼠标按下事件
};

在XSlider.cpp中定义

#include "XSlider.h"
#include <QMouseEvent>XSlider::XSlider(QWidget *p /*= NULL*/) :QSlider(p)
{}XSlider::~XSlider()
{
}void XSlider::mousePressEvent(QMouseEvent *ev)
{double pos = (double)ev->pos().x() / (double)width();//当前鼠标位置比率setValue(pos*this->maximum());//设置位置QSlider::mousePressEvent(ev);
}

此时获得鼠标点击滑动条任意位置时的播放界面,至此视频播放过程结束,内容虽有些多,但实现的过程还是比较清晰的,在下一篇中我们对FFMPEG音频处理原理以及实现。

下一篇链接:https://blog.csdn.net/hfuu1504011020/article/details/82722731

基于Qt、FFMpeg的音视频播放器设计四(视频播放进度控制)相关推荐

  1. 基于Qt的网络音乐播放器(四)酷狗API接口获取歌曲的搜索列表和歌曲的播放

    2020博客之星年度总评选进行中:请为74号的狗子投上宝贵的一票! 我的投票地址:点击为我投票 文章目录 1.效果图 2.准备好前面获取的酷狗api接口 3.网络歌曲搜索实现 4.网络歌曲播放的实现 ...

  2. 基于QT的网络音乐播放器(四)

    关于歌词的显示,其实我的主要思想就是解析歌词部分的字符串.歌词显示分为两部分,一部分是播放器右侧的歌词显示以及下面的桌面歌词的显示.其中桌面歌词让我很难受,想了很久,后面看到一个大佬的一篇文章后才有了 ...

  3. 【QT/C++】基于QT开发的一款A-SOUL元素的视频播放器(附源码)

    [QT/C++]基于QT开发的一款关于A-SOUL的视频播放器(附源码) 前言 一.软件使用说明 1.运行软件的界面如下 2.操作软件的步骤 二.软件设计说明 1.UI界面的设计 2.主代码中的部分函 ...

  4. 基于Django框架的视频播放器设计

    基于Django框架的视频播放器设计 前言 一.简介 二.详细实现步骤 1.路由配置 2.后台代码设计(对云盘接口的访问) 3.后台代码设计(流式视频传输) 4.前端功能设计(视频播放列表) 5.前端 ...

  5. 基于Qt ffmpeg opengl开发跨平台安卓实时投屏软件

    [开源]基于Qt ffmpeg opengl开发跨平台安卓实时投屏软件 码云地址 https://gitee.com/Barryda/QtScrcpy github地址 https://github. ...

  6. 基于STM32的电子琴音乐播放器设计

    基于STM32的电子琴/音乐播放器设计 文章目录 基于STM32的电子琴/音乐播放器设计 @[toc] 引言 第一章 总体设计 1.1 系统功能 1.2 主要技术性能指标 第二章 系统设计 2.1 系 ...

  7. 【基于Qt的在线音乐播放器】

    基于Qt的在线音乐播放器 项目功能: 本在线音乐播放器的功能在于创建一个音乐播放器页面,可以实现搜索功能通过HTTP协议获取网络中数据并解析出来,播放搜索到的歌曲并展示相关信息.效果如图: 相关类及功 ...

  8. 【毕业设计】基于单片机的MP3音乐播放器设计与实现 - stm32 物联网 c51

    文章目录 1 简介 2 绪论 2.1 课题背景与目的 3 系统设计 3.1 系统架构 3.2 软件部分设计 3.3 实现效果 3.4 部分相关代码 4 最后 1 简介 Hi,大家好,这里是丹成学长,今 ...

  9. 基于ESP32的蓝牙翻页器设计(论文附调试成功代码!!)

    基于ESP32的蓝牙翻页器设计 目录 基于ESP32的蓝牙翻页器设计 1 摘要 1 1.绪论 2 1.1认识蓝牙 2 1.2研究思路 3 2.软硬件设计 3 2.1中央处理模块------ESP32 ...

最新文章

  1. 欧拉回路【洛谷习题】无序字母对
  2. wsdl 与 soap协议详解
  3. linux系统远程工具,分享|Remmina:一个 Linux 下功能丰富的远程桌面共享工具
  4. 从桌面向手机移植Silverlight应用
  5. OpenFOAM安装教程(史上最全:OpenFOAM、ParaView、OpenFOAM多版本共存)
  6. 使用Spring访问Mongodb的方法大全——Spring Data MongoDB查询指南
  7. 工作235:splice
  8. sysbench0.5 mysql_sysbench 0.5安装步骤
  9. 排序——冒泡排序及其改进版本
  10. 异常——Python
  11. jquery匹配不区分大小写_jQuery实现contains方法不区分大小写的方法教程
  12. html实现在线展示pdf文件,在html页面中展示pdf文件,实现在线阅读
  13. 计算机共享地址怎么设置到桌面,局域网共享在哪里设置
  14. Python爬虫教程入门(附源码)
  15. HTML二级下拉菜单自动联动,html二级联动下拉菜单 [Excel函数如何制作二级联动下拉菜单]...
  16. 达内微软mta证书有用吗_mta证书值得拿吗 怎么样才能拿到mta证书
  17. 抖音算法揭秘,百万粉丝的背后逻辑
  18. 音频编码和视频编码基础
  19. Hibernate简单配置
  20. Java大作业-商品管理系统

热门文章

  1. 招募 | 香港理工大学Georg Kranz 博士诚招博士
  2. 你们还不了解YUM的使用?那就看看这篇文章把~
  3. Harmony鸿蒙开发 四、Ability的生命周期
  4. 刺猬文│以太坊交易信息解析方法
  5. 集成框架 -- 快手接入
  6. ESXi虚拟机导出为OVF模板
  7. Vue开发实例(11)之el-menu实现左侧菜单导航
  8. 超美二次元响应式引导页源码
  9. C++程序设计:税费计算
  10. WSL安装及其后续配置