【OpenCV+Qt】实现简易视频播放器——支持进度条拖动

news2024/10/7 11:29:57

OpenCV实现视频播放器,其思路大致就是在线程中使用OpenCV中的VideoCapture循环读取本地视频的每一帧Mat,然后发送到界面转换成QImage进行显示,而进度条拖动则用到了VideoCapture中的set函数,进度条则是使用Qslider;并且通过自定义新的进度条类实现点击跳转功能;

效果:

1.进行播放,线程循环读取视频帧并计数当前帧数,把Mat帧和当前帧数通过信号发送到窗口

窗口中创建线程,然后点击播放时在调用start启动线程;

void videothread::run()
{
    while(stop==false)//线程运行和停止  卡住线程 暂停时不退出线程
    {
        while(isrun==true)//视频运行和暂停
        {
            if(cap.read(frame))//捕获视频帧
            {
                this->currentFramecount++;
                cvtColor(frame, frame, CV_BGR2RGB);//opencvBGR格式转成Image用到的RGB
                emit sendFrame(currentFramecount,frame);//发送帧数据

            }
            msleep(40);//延时

        }
    }

    cap.release();//释放打开的视频
}

2.获取当前帧数设置进度条当前值

 3.通过VideoCapture中的get方法获取视频总帧数并保存

    if(cap.open(filename));//创建视频对象
    {
        this->allFramecount=cap.get(CAP_PROP_FRAME_COUNT);//获取视频文件中的总帧数
        this->fps=int(round(cap.get(CAP_PROP_FPS)));//获取视频帧率
    }

 4.设置进度条取值范围,即0-总帧数

    ui->horizontalSlider->setRange(0,pthread->getVideoAllFramecount());//设置进度条取值范围
    ui->horizontalSlider->setSingleStep(1);//设置单步长为1
    ui->label_4->setText(QString::number(pthread->getVideoAllFramecount()));//设置总帧数

接下来就是进度条拖动,在知道如何做之前,我们要先了解Qslider的几个信号,我们拖动时就需要用到点击、滑动和释放信号

Qslider常用信号

1.移动滑动条时发出的信号

void sliderMove(int value);

2.点击滑动条时所发出的信号

void sliderPressed();

 3.释放时所发出的信号

void sliderReleased();

5.首先是拖动进度条,拖动的时候先把播放的视频暂停

就是停止循环读帧

void Widget::on_horizontalSlider_sliderMoved(int position)//进度条拖动
{
    pthread->pauseThread();//播放暂停
}
void videothread::pauseThread()//这两个函数用于确保是在运行情况下才能切换状态
{
    if(this->isRunning()&&this->isrun==true)//当前线程运行且视频运行
    {
        this->isrun=false;
    }
}

6.释放进度条

获取拖动后的进度条的值,然后通过set中的CV_CAP_PROP_POS_FRAMES进行设置

void Widget::on_horizontalSlider_sliderReleased()//释放进度条
{
    //ui->horizontalSlider->value();获取当前进度条值
    pthread->setCurrentFrame(ui->horizontalSlider->value());
    pthread->resumeThread();//播放继续 设置为true

}
void videothread::setCurrentFrame(int value)
{
    this->currentFramecount=value;//当前帧数
    cap.set(CV_CAP_PROP_POS_FRAMES,currentFramecount);//进度条跳转对应帧
}

7.点击进度条,重写一个进度条类

因为系统自带的进度条点击时只能移动一小段,不能实现点哪就移动到哪,所以我们要自己重写一个进度条类,然后在ui中提升为重写的进度条

 

完整源码:

 自定义进度条

#ifndef NEWQSLIDER_H
#define NEWQSLIDER_H

#include <QWidget>
#include <QSlider>
#include <QMouseEvent>
class newqslider : public QSlider
{
    Q_OBJECT
public:
    explicit newqslider(QWidget *parent = 0);
    void mousePressEvent(QMouseEvent *ev);
signals:
    void costomSliderClicked();//自定义的鼠标单击信号,用于捕获并处理
public slots:
};

#endif // NEWQSLIDER_H
#include "newqslider.h"

newqslider::newqslider(QWidget *parent) : QSlider(parent)
{    
}
/*****************************************************************
* 函数名称:mousePressEvent(QMouseEvent *ev)
* 功能描述:重写鼠标点击事件,实现进度条点击哪跳到哪
* 参数说明: 无
* 返回值:   无
******************************************************************/
void newqslider::mousePressEvent(QMouseEvent *ev)
{
    //先调用父类的鼠标点击处理事件,这样可以不影响拖动的情况
    QSlider::mousePressEvent(ev);
    //获取鼠标的位置,这里并不能直接从ev中取值(因为如果是拖动的话,鼠标开始点击的位置没有意义了)
    double pos = ev->pos().x() / (double)width();
    setValue(pos * (maximum() - minimum()) + minimum());
    //发送自定义的鼠标单击信号
    emit costomSliderClicked();
}

 线程类

#ifndef VIDEOTHREAD_H
#define VIDEOTHREAD_H

#include <QObject>
#include <QThread>
#include <opencv2/opencv.hpp>
#include <iostream>
#include <QDebug>
#include <QDateTime>
using namespace  std;
using namespace  cv;
class videothread : public QThread
{
    Q_OBJECT
public:
    videothread(char*filename);
    void run();
    int getVideoAllFramecount();//获取视频总帧数
    void setCurrentFrame(int value);//设置当前进度条

    bool getStop() const;
    void setStop(bool value);//设置视频结束标识
    bool getIsrun() const;
    void setIsrun(bool value);

    void pauseThread();//暂停
    void resumeThread();//继续
    void stopThread();//停止

signals:
    void sendFrame(int currentFrame,Mat frame);//当前帧和 帧数
private:
    VideoCapture cap;//视频对象
    Mat frame;
    int currentFramecount;//视频当前帧数
    int allFramecount;//总帧数
    int fps;//视频帧率
    int videoWriterFrame;//录制视频帧
    bool stop;//线程结束标识位
    bool isrun;//视频暂停标识位

};

#endif // VIDEOTHREAD_H

 

#include "videothread.h"

videothread::videothread(char*filename)
{
    this->stop = false;
    this->isrun =false;
    this->currentFramecount=0;
    this->videoWriterFrame=0;
    if(cap.open(filename));//创建视频对象
    {
        this->allFramecount=cap.get(CAP_PROP_FRAME_COUNT);//获取视频文件中的总帧数
        this->fps=int(round(cap.get(CAP_PROP_FPS)));//获取视频帧率
    }
}

void videothread::run()
{
    while(stop==false)//线程运行和停止  卡住线程 暂停时不退出线程
    {
        while(isrun==true)//视频运行和暂停
        {
            if(cap.read(frame))//捕获视频帧
            {
                this->currentFramecount++;
                cvtColor(frame, frame, CV_BGR2RGB);//opencvBGR格式转成Image用到的RGB
                emit sendFrame(currentFramecount,frame);//发送帧数据

            }
            msleep(40);//延时

        }
    }

    cap.release();//释放打开的视频
}

int videothread::getVideoAllFramecount()
{
    return allFramecount;
}


void videothread::setStop(bool value)
{
    stop = value;
}

void videothread::setCurrentFrame(int value)
{
    this->currentFramecount=value;//当前帧数
    cap.set(CV_CAP_PROP_POS_FRAMES,currentFramecount);//进度条跳转对应帧
}

bool videothread::getIsrun() const
{
    return isrun;
}

void videothread::setIsrun(bool value)
{
    isrun = value;
}

void videothread::pauseThread()//这两个函数用于确保是在运行情况下才能切换状态
{
    if(this->isRunning()&&this->isrun==true)//当前线程运行且视频运行
    {
        this->isrun=false;
    }
}

void videothread::resumeThread()
{
    if(this->isRunning()&&this->isrun==false)//当前线程运行且视频暂停
    {
        this->isrun=true;
    }
}

void videothread::stopThread()
{
    if(this->isRunning())//当前线程运行
    {
       this->stop=true;//结束线程
       //msleep(10);

        this->terminate();
    }
}


bool videothread::getStop() const
{
    return stop;
}

 窗口类

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QSlider>
#include "videothread.h"
namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();
    void paintEvent(QPaintEvent *e);

public slots:
    void receiveFrame(int currentFrame,Mat frame);
private slots:
    void on_pushButton_clicked();

    void sliderClickedSlot();//点击进度条信号槽

    void on_horizontalSlider_sliderReleased();

    void on_horizontalSlider_sliderMoved(int position);

    void on_pushButton_pause_clicked();

    void on_pushButton_pause_2_clicked();

    void on_pushButton_pause_3_clicked();
private:
    Ui::Widget *ui;
    videothread *pthread;

    int receiveCurrentFramecount;
    QImage img1;
    bool isend;
};

#endif // WIDGET_H

 

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    this->receiveCurrentFramecount=0;
    this->isend=false;
    ui->setupUi(this);

    pthread=new videothread("carMove.mp4");
    //第五个参数采用Qt::BlockingQueuedConnection   信号发出后发送线程阻塞,直到槽函数执行完毕,继续发送下一条信号
    connect(pthread,SIGNAL(sendFrame(int,Mat)),this,SLOT(receiveFrame(int,Mat)),Qt::BlockingQueuedConnection);//接收每一帧Mat
    //connect(pthread,SIGNAL(endPlayVideo()),this,SLOT(setPlayEndSlot()),Qt::BlockingQueuedConnection);
    connect(ui->horizontalSlider,SIGNAL(costomSliderClicked()),this,SLOT(sliderClickedSlot()));//点击进度条信号槽
    ui->horizontalSlider->setRange(0,pthread->getVideoAllFramecount());//设置进度条取值范围
    ui->horizontalSlider->setSingleStep(1);//设置单步长为1
    ui->label_4->setText(QString::number(pthread->getVideoAllFramecount()));//设置总帧数
}

Widget::~Widget()
{
    delete ui;
}

void Widget::receiveFrame(int currentFrame, Mat frame)
{
    this->receiveCurrentFramecount=currentFrame;
    this->img1 = QImage(frame.data,frame.cols,frame.rows,QImage::Format_RGB888);
    this->img1 = this->img1.scaled(ui->label->width(),ui->label->height());

    ui->horizontalSlider->setValue(this->receiveCurrentFramecount);//设置当前进度条取值
    ui->label_2->setText(QString::number(this->receiveCurrentFramecount));//设置当前帧数
    update();
}

void Widget::setPlayEndSlot()
{
    pthread->setStop(true);//播放完毕 设置为true
}

void Widget::on_pushButton_clicked()
{
    pthread->start();
    pthread->setIsrun(true);//视频开始

}
void Widget::on_pushButton_pause_clicked()//暂停
{
    if(pthread->getIsrun()==false&&pthread->isRunning())//暂停状态且线程运行
    {
        pthread->resumeThread();//修改为播放状态
        ui->pushButton_pause->setText("暂停播放");
    }
    else if(pthread->getIsrun()==true&&pthread->isRunning())//播放状态
    {
        pthread->pauseThread();;//视频暂停
        ui->pushButton_pause->setText("继续播放");
    }
}
void Widget::paintEvent(QPaintEvent *e)
{
    ui->label->setPixmap(QPixmap::fromImage(this->img1));
}

void Widget::sliderClickedSlot()//点击进度条 信号槽
{
    //自定义鼠标点击信号,可以实现点哪 跳转到哪
    qDebug()<<"点击了进度条";
    pthread->pauseThread();//播放暂停
    pthread->setCurrentFrame(ui->horizontalSlider->value());
    pthread->resumeThread();//播放继续 设置为true
}

void Widget::on_horizontalSlider_sliderReleased()//释放进度条
{
    //ui->horizontalSlider->value();获取当前进度条值
    pthread->setCurrentFrame(ui->horizontalSlider->value());
    pthread->resumeThread();//播放继续 设置为true
    //此处一定要先设置进度条再开启线程,否则线程开启时再设置会冲突
}

void Widget::on_horizontalSlider_sliderMoved(int position)//进度条拖动
{
    pthread->pauseThread();//播放暂停
}

void Widget::on_pushButton_pause_2_clicked()//结束播放
{
    pthread->stopThread();//结束线程
    qDebug()<<"结束播放,线程关闭";
    if(!pthread->isRunning())//线程不在运行
    {
        if(this->isend!=true)//此时线程结束 已释放,就不再释放
        {
            //断开连接
            disconnect(pthread,SIGNAL(sendFrame(int,Mat)),this,SLOT(receiveFrame(int,Mat)));//接收每一帧Mat
            delete pthread;
            qDebug()<<"释放线程000000000000";
            //this->pthread = NULL;
            this->isend=true;//表明此时线程结束 已释放
            //ui->pushButton_pause_2->setEnabled(false);
        }
    }
}

void Widget::on_pushButton_pause_3_clicked()//重新播放
{
    pthread->stopThread();//结束线程
    qDebug()<<"重新播放";
    if(!pthread->isRunning())//线程不在运行
    {
        if(this->isend!=true)//此时线程结束 已释放,就不再释放
        {
            //断开连接
            disconnect(pthread,SIGNAL(sendFrame(int,Mat)),this,SLOT(receiveFrame(int,Mat)));//接收每一帧Mat
            delete pthread;
            qDebug()<<"重播111111111111111";

        }

        //创建新线程
        pthread=new videothread("carMove.mp4");
        pthread->start();
        pthread->setIsrun(true);//视频开始
        qDebug()<<"重播新创建线程333";
        connect(pthread,SIGNAL(sendFrame(int,Mat)),this,SLOT(receiveFrame(int,Mat)),Qt::BlockingQueuedConnection);//接收每一帧Mat

        this->isend=false;//表明此时线程还未结束
        //ui->pushButton_pause_2->setEnabled(true);
    }

}

 由于只是读取帧,所以该播放器只能播放画面,声音暂时还没做;

感谢观看!!!!

以上就是全部内容,如果对您有帮助,欢迎点赞评论,或者发现有哪里写错的,欢迎指正!

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/101876.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

记录安装 fenics 的问题

因为 fenics 官方更新后可能版本会出现有时效的问题, 所以也记录一下时间. Windows 系统下安装最大的问题 2022-12-19 记录. Windows 系统下本来是想通过 Anaconda 安装 fenics 的, 创建好虚拟环境后, 利用 conda install -c conda-forge fenics 进行安装, 但是直接提示 Pack…

操作系统,计算机网络,数据库刷题笔记14

操作系统&#xff0c;计算机网络&#xff0c;数据库刷题笔记14 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xf…

【OpenSourceC#】ET框架

1. 前言 ET算是我刚接触客户端时最早知道的框架&#xff0c;ET我最初眼馋的还是他的双端功能。包揽前后端的功能&#xff0c;这个很有吸引力。但是那时候对我来说这框架太复杂了&#xff0c;没法看。 这两天又来看看&#xff0c;曾经很多不懂的地方现在都能看懂了&#xff0c…

数据库拆分5--使用sharding-jdbc来实现水平拆分

有三张表 user log order表&#xff0c;先将user log 和order垂直分库&#xff0c;然后将user表水平拆分 配置文件 spring.shardingsphere.enabledtruespring.shardingsphere.datasource.nameswim-user,wim-orderspring.shardingsphere.datasource.wim-user.typecom.alibaba.…

【JavaEE】多线程之Thread类

一、Thread类常见方法与字段 1、构造方法 构造方法说明Thread()不带参数的构造方法Thread(String name)可以在构造时传入线程的名字Thread(Runnable run)传入Runnable&#xff0c;是创建线程的方法之一Thread(Runnable run,String name)传入线程工作并给线程起名 2、常见属性…

JaveWeb框架(一):Web入门,Http的请求和响应,https介绍,Web实战自定义服务器

Servlet入门 MVC实战项目 仓储管理系统JavaWeb入门介绍Http协议Http请求数据格式Http响应数据格式Web实战Demo&#xff1a;自定义服务器对比Https协议总结Redis章节复习已经过去&#xff0c;新的章节JavaWeb开始了&#xff0c;这个章节中将会回顾JavaWeb实战项目 仓储管理 代码…

机器人开发--电机中的电流环、速度环、位置环

机器人开发--电机中的电流环、速度环、位置环电流环、速度环、位置环1 三环原理1.1 电流环1.2 速度环1.3 位置环2 各环与PID控制2.1 电流环重点在 PID&#xff08;比例、积分和微分&#xff09;2.2 速度环重点在 PI&#xff08;比例和积分&#xff09;2.3 位置环重点在 P&#…

基于JAVA的企业部门报销管理信息系统的设计与实现

开发工具(eclipse/idea/vscode等)&#xff1a; 数据库(sqlite/mysql/sqlserver等)&#xff1a; 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a; 系统部分主要分为以下几个模块&#xff1a;公告类型&#xff0c;公告信息&#xff0c;部门信息&#xff0c;员工信 息&a…

手写Spring9(实现FactoryBean、对象作用域)

文章目录前言目标设计项目结构一、实现1、Bean的作用范围定义和xml解析2、创建和修改对象时候判断单例和原型模式3、定义 FactoryBean 接口4、实现一个 FactoryBean 注册服务5、扩展 AbstractBeanFactory 创建对象逻辑二、测试1、事先准备2、定义 FactoryBean 对象3、配置文件4…

Win32多线程调用gdal库接口

作者:朱金灿 来源:clever101的专栏 为什么大多数人学不会人工智能编程?>>> 效果图和程序说明 效果图如下:   这个程序是基于MFC的GUI程序,用于给指定的文件夹批量创建金字塔。   效果图如下:   这个程序是基于Win32 API的GUI程序,用于给指定的文件创…

期末前端web大作业——动漫客栈响应式bootstarp(7页) 排版整洁,内容丰富,主题鲜明

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置&#xff0c;有div的样式格局&#xff0c;这个实例比较全面&#xff0c;有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 ⚽精彩专栏推荐&#x1…

R语言用线性回归模型预测空气质量臭氧数据

尽管线性模型是最简单的机器学习技术之一&#xff0c;但它们仍然是进行预测的强大工具。 最近我们被客户要求撰写关于线性回归模型的研究报告&#xff0c;包括一些图形和统计输出。 这尤其是由于线性模型特别容易解释这一事实。在这里&#xff0c;我将讨论使用空气质量数据集…

Python学习基础笔记五十四——多继承

多继承中&#xff0c;我们子类对象调用的一个方法&#xff0c;默认是就近原则&#xff0c;找的顺序是什么&#xff1f; 在经典类中&#xff0c;是深度优先&#xff1b; 在新式类中&#xff0c;是广度优先&#xff1b; Python2.7是经典类和新式类共存&#xff0c;新式类要继承…

领域模型设计模式

前言&#xff1a; 领域是一个组织所做的事情以及其包含的一切&#xff0c;通俗地说&#xff0c;就是组织的业务范围和做事情的方式&#xff0c;也是软件开发的目标范围。比如说淘宝的电商业务&#xff0c;C2C就是电子商务的领域&#xff0c;领域驱动设计就是从领域出发&#x…

安装VS code

五 安装VS Code Visual Studio Code&#xff0c;简称VS Code&#xff0c;是一种简化且高效的代码编辑器&#xff0c;同时支持诸如调试&#xff0c;任务执行和版本管理之类的开发操作。它的目标是提供一种快速的编码编译调试工具。优势&#xff1a; 支持多种语言的编写&#xf…

【大数据处理技术】「#1」本地数据集上传到数据仓库Hive

文章目录实验数据集下载下载实验数据集建立一个用于运行本案例的目录dbtaobao数据集的预处理删除文件第一行记录&#xff0c;即字段名称获取数据集中双11的前100000条数据导入数据仓库实验数据集下载 下载实验数据集 data_format.zip数据集用户行为日志user_log.csv&#xff…

jsp+ssm计算机毕业设计房屋租赁系统【附源码】

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JSPSSM mybatis Maven等等组成&#xff0c;B/S模式 Mave…

node 使用 pm2 日志管理及使用 pm2-logrotate 进行日志分割

目录 1. 需求背景 2. 什么是 pm2-logrotate &#xff1f; 3. 查看 pm2 自带的日志管理 4. 安装 pm2-logrotate 5. 查看配置指令 6. pm2-logrotate 具体配置说明 7. 如何设置这些值&#xff1f; 8. 停止 pm2-logrotate 服务 9. 补充&#xff1a;pm2 常用命令 1. 需求…

Java学习笔记 --- MySQL-函数

一、合计/统计函数 count Count返回行的总数 SELECT COUNT(*) 列名 FROM table_name WHERE where_definition # 演示 mysql 的统计函数的使用 -- 统计一个班级共有多少学生&#xff1f; SELECT COUNT(*) FROM student -- 统计数学成绩大于90的学生有多少个 SELECT COUNT(*) FR…

Linux——vim的使用

实验5 vim的使用 一、两种模式&#xff1a; 命令行模式和编辑模式&#xff08;前者还有底行模式&#xff0c;命令行模式输入&#xff1a;就是底行模式&#xff09; 切换方法&#xff1a;进入vim后默认在命令模式&#xff0c;可以通过输入a后者i进入编辑模式&#xff0c;或者…