若該文爲原創文章,未經允許不得轉載
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客導航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/103235954
紅胖子(紅模仿)的博文大全:開發技術集合(包含Qt實用技術、樹莓派、三維、OpenCV、OpenGL、ffmpeg、OSG、單片機、軟硬結合等等)持續更新中…(點擊傳送門)
Qt開發專欄:項目實戰(點擊傳送門)
OpenCV開發專欄(點擊傳送門)
需求
使用OpenCV庫的視頻播放器(支持播放器操作,如暫停、恢復、停止、時間、進度條拽託等)。
原理
使用OpenCV打開視頻文件,獲取總幀數,根據當前幀數,刷新當前時間戳與預期的時間間隔,調用槽函數動態刷新播放內容。
注意
當前只測試了avi文件。
相關博客
《OpenCV開發筆記(四):OpenCV圖片和視頻數據的讀取與存儲》
《項目實戰:Qt+OpenCV操作攝像頭拍照、調節參數和視頻錄製》
《項目實戰:Qt+OpenCV視頻播放器(支持播放器操作,如暫停、恢復、停止、時間、進度條拽託等)》
《Qt實用技巧:使用QMediaPlayer和Windows自帶組件播放swf、rmvb、mpg、mp4等視頻文件》
《Qt實用技巧:使用QMediaPlayer播放mp4文件》
Demo:OpenCVPlayer v1.0.0
運行效果
下載地址
CSDN:https://download.csdn.net/download/qq21497936/11996319
核心類代碼
OpenCVPlayerManager.h
#ifndef OPENCVPLAYERMANAGER_H
#define OPENCVPLAYERMANAGER_H
/************************************************************\
* 控件名稱: OpenCVPlayerManager,OpenCV管理類
* 控件描述:
* 1.OpenCV打開視頻文件
* 2.播放器操作:播放、暫停、停止
* 3.播放時顯示:當前時間、總時間
* 4.設置當前播放的時間點
*
* 作者:紅模仿 聯繫方式:QQ21497936
* 博客地址:https://blog.csdn.net/qq21497936
* 日期 版本 描述
* 2019年11月25日 v1.0.0 opencv打開文件
\************************************************************/
#include <QObject>
#include <QImage>
#include <QTimer>
#include <QElapsedTimer>
// opencv
#include "opencv/highgui.h"
#include "opencv/cxcore.h"
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/opencv.hpp"
class OpenCVPlayerManager : public QObject
{
Q_OBJECT
public:
enum PLAY_STATE {
PLAY_STATE_PLAYING,
PLAY_STATE_PAUSE,
PLAY_STATE_STOP
};
public:
explicit OpenCVPlayerManager(QObject *parent = 0);
~OpenCVPlayerManager();
public:
QString getWindowTitle() const;
public:
void setWindowTitle(const QString &windowTitle);
signals:
void signal_captureOneFrame(cv::Mat mat); // 接收圖像後拋出信號
void signal_playStateChanged(OpenCVPlayerManager::PLAY_STATE playState);
// 播放器狀態
void signal_durationChanged(qint64 duration); // 視頻總長度
void signal_positionChanged(qint64 position); // 當前位置
public:
bool startPlay(QString filePath, int width = 480, int height = 320);
void pause(); // 暫停
void resume(); // 恢復播放
void stopPlay(); // 停止播放
void setPosition(qint64 position); // 切換到播放位置
public slots:
bool slot_start(); // 開啓線程
bool stop(); // 關閉線程
protected slots:
void slot_captrueFrame(); // 消息循環獲取圖像
void slot_stopPlay(); // 停止播放
void slot_setPosition(qint64 position);
public:
static QImage cvMat2QImage(const cv::Mat &mat);
private:
cv::VideoCapture *_pVideoCapture; // 播放文件實例
QString _filePath; // 播放文件路徑
bool _running; // 線程是否運行
int _totalFrames; // 總幀數
int _fps; // 每秒幀數
int _currentFrame; // 當前幀數
int _width; // 高度
int _height; // 寬度
QElapsedTimer _elapsedTimer; // 計時器
qint64 _pauseMs; // 暫停的毫秒
qint64 _duration; // 視頻總長度(毫秒)
PLAY_STATE _playState; // 播放器狀態
int _position; // 改變到的播放位置
bool _setPostion; // 播放位置修改標誌
private:
QString _windowTitle;
};
#endif // OPENCVPLAYERMANAGER_H
OpenCVPlayerManager.cpp
#include "OpenCVPlayerManager.h"
#include <QDebug>
OpenCVPlayerManager::OpenCVPlayerManager(QObject *parent)
: QObject(parent),
_pVideoCapture(0),
_totalFrames(0),
_currentFrame(0),
_playState(PLAY_STATE_STOP),
_pauseMs(0),
_duration(0)
{
QString version = "v1.0.0";
_windowTitle = QString("OpenCVPlayer %1(紅模仿-紅胖子 QQ21497936 博客地址: blog.csdn.net/qq21497936").arg(version);
}
OpenCVPlayerManager::~OpenCVPlayerManager()
{
}
bool OpenCVPlayerManager::startPlay(QString filePath, int width, int height)
{
// 狀態判斷
switch (_playState)
{
case PLAY_STATE_PAUSE:
resume();
case PLAY_STATE_PLAYING:
return true;
break;
case PLAY_STATE_STOP:
break;
default:
break;
}
if(!_pVideoCapture->open(filePath.toStdString()))
{
qDebug() << __FILE__ << __LINE__ << "Failed to start video :" << filePath;
return false;
}
_width = width;
_height = height;
_pVideoCapture->set(CV_CAP_PROP_FRAME_WIDTH, _width);
_pVideoCapture->set(CV_CAP_PROP_FRAME_HEIGHT, _height);
_width = _pVideoCapture->get(CV_CAP_PROP_FRAME_WIDTH);
_height = _pVideoCapture->get(CV_CAP_PROP_FRAME_HEIGHT);
_fps = _pVideoCapture->get(CV_CAP_PROP_FPS);
_totalFrames = _pVideoCapture->get(CV_CAP_PROP_FRAME_COUNT);
qDebug() << __FILE__ << __LINE__ << "width =" << width;
qDebug() << __FILE__ << __LINE__ << "height =" << height;
qDebug() << __FILE__ << __LINE__ << "_width =" << _width;
qDebug() << __FILE__ << __LINE__ << "_height =" << _height;
qDebug() << __FILE__ << __LINE__ << "_fps =" << _fps;
qDebug() << __FILE__ << __LINE__ << "_totalFrames =" << _totalFrames;
_currentFrame = 0;
_pauseMs = 0;
QTimer::singleShot(0, this, SLOT(slot_captrueFrame()));
_elapsedTimer.start();
_playState = PLAY_STATE_PLAYING;
emit signal_playStateChanged(_playState);
_duration = (_totalFrames - 1) * (1000.0f / _fps);
emit signal_durationChanged(_duration);
return true;
}
void OpenCVPlayerManager::pause()
{
// 狀態判斷
switch (_playState)
{
case PLAY_STATE_PAUSE:
case PLAY_STATE_STOP:
return;
break;
case PLAY_STATE_PLAYING:
break;
default:
break;
}
_playState = PLAY_STATE_PAUSE;
emit signal_playStateChanged(_playState);
}
void OpenCVPlayerManager::resume()
{
// 狀態判斷
switch (_playState)
{
case PLAY_STATE_PLAYING:
case PLAY_STATE_STOP:
return;
break;
case PLAY_STATE_PAUSE:
break;
default:
break;
}
QTimer::singleShot(0, this, SLOT(slot_captrueFrame()));
_elapsedTimer.start();
_playState = PLAY_STATE_PLAYING;
emit signal_playStateChanged(_playState);
}
void OpenCVPlayerManager::stopPlay()
{
// 狀態判斷
switch (_playState)
{
case PLAY_STATE_STOP:
return;
break;
case PLAY_STATE_PAUSE:
case PLAY_STATE_PLAYING:
break;
default:
break;
}
QTimer::singleShot(0, this, SLOT(slot_stopPlay()));
}
void OpenCVPlayerManager::setPosition(qint64 position)
{
QMetaObject::invokeMethod(this, "slot_setPosition", Q_ARG(qint64, position));
}
void OpenCVPlayerManager::slot_setPosition(qint64 position)
{
// 當前幀數
_currentFrame = position / (1000.0f / _fps);
_pauseMs = _currentFrame * (1000.0f / _fps);
_pVideoCapture->set(CV_CAP_PROP_POS_FRAMES, _currentFrame);
_elapsedTimer.start();
qDebug() << __FILE__ << __LINE__
<< _currentFrame << ":" << _totalFrames << ":" << _pauseMs;
}
bool OpenCVPlayerManager::slot_start()
{
_pVideoCapture = new cv::VideoCapture;
_running = true;
}
bool OpenCVPlayerManager::stop()
{
if(_running)
{
_running = false;
delete _pVideoCapture;
_pVideoCapture = 0;
}
}
void OpenCVPlayerManager::slot_captrueFrame()
{
if(!_running)
{
return;
}
if(_pVideoCapture->isOpened())
{
// 沒有下一幀,表示播放結束
if(_currentFrame >= _totalFrames)
{
_playState = PLAY_STATE_STOP;
emit signal_playStateChanged(_playState);
cv::Mat mat(_height, _width, CV_8UC3, cv::Scalar(0, 0, 0));
emit signal_captureOneFrame(mat);
return;
}
cv::Mat mat;
*_pVideoCapture >> mat;
emit signal_captureOneFrame(mat);
if(_currentFrame % _fps == 0)
{
emit signal_positionChanged(_currentFrame * (1000.0f / _fps));
}
_currentFrame++;
int nowTime;
switch (_playState)
{
case PLAY_STATE_STOP:
case PLAY_STATE_PAUSE:
_pauseMs = _currentFrame * (1000.0 / _fps);
break;
case PLAY_STATE_PLAYING:
nowTime = _elapsedTimer.elapsed();
if((_currentFrame * (1000.0 / _fps) - (nowTime + _pauseMs) < 0))
{
QTimer::singleShot(0, this, SLOT(slot_captrueFrame()));
}else{
QTimer::singleShot(_currentFrame * (1000.0 / _fps)
- (nowTime + _pauseMs), this, SLOT(slot_captrueFrame()));
}
break;
default:
break;
}
}
}
void OpenCVPlayerManager::slot_stopPlay()
{
if(_pVideoCapture->isOpened())
{
_playState = PLAY_STATE_STOP;
_pVideoCapture->release();
cv::Mat mat(_width, _height, CV_8UC3, cv::Scalar(0, 0, 0));
emit signal_captureOneFrame(mat);
_currentFrame = 0;
emit signal_positionChanged(0);
_duration = 0;
emit signal_durationChanged(_duration);
}
}
QImage OpenCVPlayerManager::cvMat2QImage(const cv::Mat &mat)
{
// 8-bits unsigned, NO. OF CHANNELS = 1
if(mat.type() == CV_8UC1)
{
QImage image(mat.cols, mat.rows, QImage::Format_Indexed8);
// Set the color table (used to translate colour indexes to qRgb values)
image.setColorCount(256);
for(int i = 0; i < 256; i++)
{
image.setColor(i, qRgb(i, i, i));
}
// Copy input Mat
uchar *pSrc = mat.data;
for(int row = 0; row < mat.rows; row ++)
{
uchar *pDest = image.scanLine(row);
memcpy(pDest, pSrc, mat.cols);
pSrc += mat.step;
}
return image;
}
// 8-bits unsigned, NO. OF CHANNELS = 3
else if(mat.type() == CV_8UC3)
{
// Copy input Mat
const uchar *pSrc = (const uchar*)mat.data;
// Create QImage with same dimensions as input Mat
QImage image(pSrc, mat.cols, mat.rows, mat.step, QImage::Format_RGB888);
QImage image2 = image.rgbSwapped();
return image2;
}
else if(mat.type() == CV_8UC4)
{
qDebug() << "CV_8UC4";
// Copy input Mat
const uchar *pSrc = (const uchar*)mat.data;
// Create QImage with same dimensions as input Mat
QImage image(pSrc, mat.cols, mat.rows, mat.step, QImage::Format_ARGB32);
// return image.copy();
return image;
}
else
{
qDebug() << "ERROR: Mat could not be converted to QImage.";
QImage image;
return image;
}
}
QString OpenCVPlayerManager::getWindowTitle() const
{
return _windowTitle;
}
void OpenCVPlayerManager::setWindowTitle(const QString &windowTitle)
{
_windowTitle = windowTitle;
}
入坑
入坑一:調整播放位置時,宕機
原因:
直接主線程操作設置當前幀的位置,同時子線程也卻在讀取,OpenCV內部沒有錯異步處理。
解決方法:
先調用設置位置,然後槽調用,將設置位置放置到子線程當中去。
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客導航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/103235954