1.播放视频 -----videoPthread
使用线程播放视频
void PlayThread::run()
{
while(cap.read(frame)) //----------循环 不断将cap读到的Mat数据存储到frame变量
{
QImage qImg=MatToQimage(frame);// -----------Mat数据到QImage图片的转换函数,opencv使用的图片类型是Mat,但是Mat不能直接显示在label上,所以需要一次转换
emit sendImg(qImg); //---------------一边播一边将图片传递给界面
//msleep(20);
}
}
此处插播 MatToQimage()函数的实现方式 参数:Mat类型数据,就是cap读到帧画面后存储的mat数据,返回值:转换后的QImage。
转换原理:我也不太了解,印象里是Mat图片数据是BGR通道分布,QImage是RGB通道分布,所以转换就是将B,G两个通道的数据交换(不知道是不是这样)
QImage playThread::changeToQImg(Mat img)
{
QImage qImg;
if (img.channels() == 3) {
//cvtColor(img, img, CV_BGR2RGB);
qImg = QImage((const unsigned char*)(img.data), img.cols, img.rows,
img.cols * img.channels(), QImage::Format_RGB888);
}
else if (img.channels() == 1) {
qImg = QImage((const unsigned char*)(img.data), img.cols, img.rows,
img.cols * img.channels(), QImage::Format_Indexed8);
}
else {
qImg = QImage((const unsigned char*)(img.data), img.cols, img.rows,
img.cols * img.channels(), QImage::Format_RGB888);
}
return qImg;
}
2、线程发出的信号----------sendImg(QImage img)-------播放线程解出一帧就发送一帧
界面上的槽-------------showImg(QImage showimg)-----------线程专递来图片,显示到界面的label上
显示图片,窗口重绘函数,只更新label------paintEvent(QPaint xxxx)--------------使用update()调用重绘函数
void VideoPlayWin::paintEvent(QPaintEvent *e) //----------------重绘事件
{
ui->label->setPixmap(QPixmap::fromImage(this->showimg)); //------------label上设置图片 setPixmap 但是设置上去的图片是Qimage,
QPixmap,Qimage这两个我也不知道有什么区别
ui->label->setScaledContents(true); //设置图片自适应label大小
}
void VideoPlayWin::showImage(QImage showimg)//--------------界面上 接收图片的槽函数
{
this->showimg=showimg; //---------------界面类的私有变量 this-<showimg, 等于传递来的showimg,然后调用update,使用重绘事件刷新界面,
等于每次传来一帧图片,就刷新一次label
this->update();
}
3、到此视频就可以在界面上播放,但是速度很快,所以需要在线程发送信号后msleep(25)
4、继续完善,界面需要能控制视频的暂停和播放
bool stopFalg;//------线程私有变量,bool类型判断是否被暂停
void setStopFlag(bool value);//---------相应的,提供一个set 暂停位的函数,在线程之外的地方可以调用set函数修改flag的值,来控制run里解画面帧,
如果flag的值是true,就不要继续解码。
void PlayThread::run()
{
while(cap.read(frame))
{
while(stopFlag)//---------------stopFlag==true 标志位置1,说明点击了暂停。因为上面while(read)一直在不断解帧,所以这里要使用一个死循环,点击暂停就进到这个死循环,等于 卡主上面read解码的流程,如果只是用if(stopFlag),当检测到暂停标志位之后,没有做死循环操作,确实是不会触发下面的emit信号,界面上的画面 处于停止状态,但其实cap(read)还在偷偷的继续解码,等到我们点击继续播放,会发现视频已经播到后面去了。
{
qDebug()<<"stop"<<endl;
msleep(10);
}
QImage qImg=MatToQimage(frame);
emit sendImg(qImg);
//msleep(20);
}
}
界面点击按钮,修改stopFlag值,因为界面上按钮太多不够好,所以只设置一个按钮,那么如何确定哪一次点击是播放视频,哪一次点击是暂停线程。
所以,我们可以根据按钮上的文字进行判断,是要暂停还是播放,使用text()函数获取button上的文字。更改stopFlag后,记得也要把button上的文字修改掉,自然使用setText()
void VideoPlayWin::on_pushButton_clicked() //----------点击暂停/播放按钮
{
if(ui->pushButton->text()=="播放")
{
this->videothread->start();
this->videothread->setIsSTop(false);
ui->pushButton->setText("暂停");
}
else if(ui->pushButton->text()=="暂停")
{
this->videothread->setIsSTop(true);
ui->pushButton->setText("播放");
}
//if number==biggest,btn can't clicked 这边测试过后发现,如果视频已经播完,这时候继续点击播放键已经无法打开视频了。
这种情况的话,就只好在播完视频之后,将视频直接关闭,按钮也回到播放状态。视频播完也就是线程的while(read)死循环结束,所以在线程里 发送一个结束信号,界面再响应该信号,这里先不处理,还要结合进度条的控制,后面再处理。
}
5、修改视频倍速
此处变速框使用的是 下拉框 设计 先了解一下 QComBox的使用方式:
5.1.currentIndex(); 获取当bai前ducomBox的索引,是int类型的值。//QComBox变速框第0个索引是0.5倍速,第1个索引值1倍速,第2个索引2倍速,第3个索引4倍速
5.2.currentText(); 获取当前comBox的文本dao,是QString类型。
5.3.currentData(int role = Qt::UserRole)获取当前comBox绑定的数据,是QVariant类型。
所以,就不像暂停一样,在点击按钮的时候才改变线程里的值,我们在界面打开线程的时候,就可以将下拉框的索引值当做参数传递到线程中,然后在线程里写一个修改变速的函数
,switch(flag),直接打开线程,就在初始化的时候得到视频要显示的速度,然后在run函数里做msleep
这里需要计算,视频的帧率,帧数,求得视频的时长
rate = cap.get(5); //获取帧率
fraNum=cap.get(7); //获取帧数
duration=fraNum / rate; //帧数/每x秒一帧=总共多少秒
onePlayTime = 1000/rate; //msleep使用的是ms,所以再用1000/x秒,就能大概知道,视频帧和视频帧之间的延迟需要设置多长
那么默认播放速度就是按onePlayTime,如果设置了倍速=0。5倍,那么播放速度变慢,延迟时间就要长一点。
speedTime=2*onePlayer;
代码:
PlayThread::PlayThread(QString pwd, int speedIndex)
{
cap.open(pwd.toLatin1().data());
rate = cap.get(5); //获取帧率
framNum=cap.get(7); //获取帧数
duration=framNum / rate; //帧数/每x秒一帧=总共多少秒
onePlayTime = 1000/rate;
qDebug()<<"onePlayTime="<<onePlayTime<<endl;
changeSpeed(speedIndex);
//this->stopFlag=false;
}
计算切换倍速后播放速度的函数://switch语句每个分支一定要有break;否则就等于没有分支
void PlayThread::changeSpeed(int flag)
{
switch(flag)
{
case 0:
speedTime=2*onePlayTime;
break;
case 1:
speedTime=onePlayTime;
break;
case 2:
speedTime=onePlayTime/2;
break;
case 3:
speedTime=onePlayTime/4;
break;
default:
speedTime=onePlayTime;
}
}
界面上,点击下拉框的槽函数:右键点击comBox转到槽
void VideoPlayWin::on_comboBox_activated(int index)
{
this->videothread->changeSpeed(index);
}
界面构造函数里调用线程的函数:ui->comboBox->currentIndex()就是下拉框的当前索引值
videothread=new PlayThread(this->videopath,ui->comboBox->currentIndex());
6、实现进度条跟着视频移动
6.1自定义控件进度条 自定义一个控制条可以实现点击进度条跳转视频吧
基本上就是根据点击的进度条坐标,求出占整条坐标的白百分比,百分比乘以进度条时长,就可以求得数字
.h
#ifndef MYSLIDER_H
#define MYSLIDER_H
#include <QObject>
#include <QSlider>
#include <QWidget>
class MySlider : public QSlider
{
Q_OBJECT
public:
MySlider(QWidget *parent);
MySlider(int min,int max,int single);
protected:
void mousePressEvent(QMouseEvent *ev);
signals:
void sendFrame(int,int);
void sendOld(int);
void sendSec(int);
public slots:
};
#endif // MYSLIDER_H
.cpp
#include "myslider.h"
#include <QDebug>
#include <QMouseEvent>
MySlider::MySlider(QWidget *parent): QSlider(parent)
{
this->setMinimum(0);//最小值0
this->setMaximum(0);//最大值100
this->setSingleStep(1);//单步步长1
this->setPageStep(0);//设置鼠标点击步长为0,触发点击事件
}
MySlider::MySlider(int min, int max, int single)
{
this->setOrientation(Qt::Horizontal); // 水平方向
this->setMinimum(min);//最小值0
this->setMaximum(max);//最大值100
this->setSingleStep(single);//单步步长1
this->setPageStep(0);//设置鼠标点击步长为0,触发点击事件
}
void MySlider::mousePressEvent(QMouseEvent *ev)
{
// emit sendOld(this->value());
//获取当前点击位置,得到的这个鼠标坐标是相对于当前QSlider的坐标
int currentX = ev->pos().x();
//获取当前点击的位置占整个Slider的百分比
double per = currentX *1.0 /this->width();
//利用算得的百分比得到具体数字
int value = per*(this->maximum() - this->minimum()) + this->minimum();
// emit sendFrame(this->value(),value);
emit sendSec(value);
qDebug()<<"当前帧是:"<<value;
//设定滑动条位置
this->setValue(value);
//滑动条移动事件等事件也用到了mousePressEvent,加这句话是为了不对其产生影响,是的Slider能正常相应其他鼠标事件
QSlider::mousePressEvent(ev);
}
6.2 播放线程添加一个私有变量 nowtime,表示了当前播放到的进度,就是在run函数的while(read)里进行++。每解出一帧照片,nowtime++
emit sendNowTime(nowtime); 给界面传信号
进度条根据输入的值移动 ui->horizontalSlider->setValue(nowtime);
播放线程中有视频总帧数,将其设置为进度条的总长度,
6.3界面中编写拖动进度条的代码 但是带1个参,在服务里点击进度条,然后将数据传给视频播放线程
还有很多,下次再写