項目實戰:Qt+OpenCV視頻播放器(支持播放器操作,如暫停、恢復、停止、時間、進度條拽託等)

若該文爲原創文章,未經允許不得轉載
原博主博客地址: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視頻播放器控件(不依賴系統編解碼)

項目實戰: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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章