C++ 同时从多个流中捕获数据,最佳方法以及如何减少CPU使用

C++ 同时从多个流中捕获数据,最佳方法以及如何减少CPU使用,c++,qt,opencv,ffmpeg,C++,Qt,Opencv,Ffmpeg,我目前正在编写一个应用程序,该应用程序将捕获大量RTSP流(在我的例子中是its 12),并将其显示在QT小部件上。当我超过6-7个数据流时,就会出现问题,CPU使用率会急剧上升,并且会出现明显的口吃 我之所以认为它不是QT draw函数,是因为我做了一些检查,以测量从相机和我的样本图像绘制传入图像所需的时间,它总是远远少于33毫秒(即使有12个小部件正在更新) 我也只是在没有画图的情况下运行opencv捕获方法,得到的CPU消耗量与画图时差不多(最多损失10%的CPU,GPU使用率为零) 重要

我目前正在编写一个应用程序,该应用程序将捕获大量RTSP流(在我的例子中是its 12),并将其显示在QT小部件上。当我超过6-7个数据流时,就会出现问题,CPU使用率会急剧上升,并且会出现明显的口吃

我之所以认为它不是QT draw函数,是因为我做了一些检查,以测量从相机和我的样本图像绘制传入图像所需的时间,它总是远远少于33毫秒(即使有12个小部件正在更新)

我也只是在没有画图的情况下运行opencv捕获方法,得到的CPU消耗量与画图时差不多(最多损失10%的CPU,GPU使用率为零)

重要提示:我使用的是RTSP流,它是h264流

如果与我的规格有关:

英特尔Core i7-6700@3.40GHZ(8个CPU) 内存:16gb GPU:Intel HD Graphics 530

(另外,我在带有专用图形卡的计算机上运行了我的代码,它确实消除了一些口吃,但CPU使用率仍然相当高)

我目前正在使用OPENCV 4.1.0,并启用和构建了GSTREAMER,我还有OPENCV-WORLD版本,在性能上没有差异

我创建了一个名为Camera的特殊类,该类保存其帧大小约束和各种控制函数以及流函数。stream函数是在一个单独的线程上运行的,每当stream()函数使用当前帧完成时,它就会通过我创建的onNewFrame事件发送ready Mat,该事件将转换为QPixmap并更新小部件的lastImage变量。通过这种方式,我可以以更线程安全的方式更新图像

我曾试图操纵那些VideoCapture.set()值,但没有真正起到作用

这是我的流函数(忽略bool返回,它没有做任何事情,这是几分钟前我尝试使用std::async时留下的):

这是我给齐玛格的垫子:

QImage GLWidget::toQImageFromPMat(cv::Mat* mat) {



    return QImage(mat->data, mat->cols, mat->rows, QImage::Format_RGB888).rgbSwapped();

注意:不转换不会导致CPU提升(至少不是一个显著的提升)

最小可验证示例

这个项目很大。我将粘贴GLWidget.cpp和GLWidget.h以及Camera.h和Camera.cpp。您可以将GLWidget放入任何内容,只要您生成的内容超过6个即可。摄像头依赖于摄像头,但也可以在视频捕获中粘贴url

我还提供了CamUtils,以防万一

摄像机h:


#pragma once
#include <iostream>
#include <vector>
#include <fstream>
#include <map>
#include <string>
#include <sstream>
#include <algorithm>
#include "FrameListener.h"
#include <opencv2\opencv.hpp>
#include <thread>
#include "CamUtils.h"
#include <ctime>
#include "dPacket.h"

using namespace std;
using namespace cv;

class Camera
{

    /*
        CLEANED UP!
        Camera now is only responsible for streaming and echoing captured frames.
        Frames are now wrapped into dPacket struct.
    */


private:
    string id;
    vector<FrameListener*> clients;
    VideoCapture ipCam;
    string streamUrl;
    Size size;
    bool tryResetConnection = false;

    //TODO: Remove these as they are not going to be used going on:
    bool isPlaying = true;
    bool capture = true;

    //SECRET FEATURES:
    bool detect = false;


public:
    Camera(string url, int width = 480, int height = 240, bool detect_=false);
    bool stream();
    void setReconnectable(bool newReconStatus);
    void addListener(FrameListener* client);
    vector<bool> getState();    // Returns current state: vector[0] stream state; vector[1] stream state; TODO: Remove this as this is no longer should control behaviour
    void killStream();
    bool getReconnectable();
};


#布拉格语一次
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括“FrameListener.h”
#包括
#包括
#包括“CamUtils.h”
#包括
#包括“dPacket.h”
使用名称空间std;
使用名称空间cv;
类摄像机
{
/*
打扫干净!
照相机现在只负责流式传输和回显捕获的帧。
帧现在被包装到dPacket结构中。
*/
私人:
字符串id;
向量客户端;
视频捕获ipCam;
字符串流URL;
大小;
bool-tryResetConnection=false;
//TODO:删除这些,因为它们将不被使用。正在进行:
bool isplay=true;
布尔捕获=真;
//秘密特征:
布尔检测=假;
公众:
摄像机(字符串url,整数宽度=480,整数高度=240,布尔检测=false);
布尔流();
void setReconnectable(bool-newresuratus);
void addListener(FrameListener*客户端);
vector getState();//返回当前状态:向量[0]流状态;向量[1]流状态;TODO:删除此项,因为这不再是应控制的行为
void killStream();
bool getReconnectable();
};
Camera.cpp

#include "Camera.h"


Camera::Camera(string url, int width, int height, bool detect_) // Default 240p
{
    streamUrl = url; // Prepare url
    size = Size(width, height);
    detect = detect_;

}

void Camera::addListener(FrameListener* client) {
    clients.push_back(client);
}


/*
                TEST CAMERAS(Paste into cameras.dViewer):
                {"id":"96a73796-c129-46fc-9c01-40acd8ed7122","ip":"176.57.73.231","password":"null","username":"null"},
                {"id":"96a73796-c129-46fc-9c01-40acd8ed7122","ip":"176.57.73.231","password":"null","username":"null"},
                {"id":"96a73796-c129-46fc-9c01-40acd8ed7144","ip":"172.20.101.13","password":"admin","username":"root"}
                {"id":"96a73796-c129-46fc-9c01-40acd8ed7144","ip":"172.20.101.13","password":"admin","username":"root"}

*/



bool Camera::stream() {
    /* This function is meant to run on a separate thread and fill up the buffer independantly of
    main stream thread */
    //cv::setNumThreads(100);
    /* Rules for these slightly changed! */
    Mat pre;  // Grab initial undoctored frame
    //pre = Mat::zeros(size, CV_8UC1);
    Mat frame; // Final modified frame
    frame = Mat::zeros(size, CV_8UC1);
    if (!pre.isContinuous()) pre = pre.clone();

    ipCam.open(streamUrl, CAP_FFMPEG);

    while (ipCam.isOpened() && capture) {
        // If camera is opened wel need to capture and process the frame
        try {
            auto start = std::chrono::system_clock::now();

            ipCam >> pre;

            if (pre.empty()) {
                /* Check for blank frame, return error if there is a blank frame*/
                cerr << id << ": ERROR! blank frame grabbed\n";
                for (FrameListener* i : clients) {
                    i->onNotification(1); // Notify clients about this shit
                }
                break;
            }

            else {
                // Only continue if frame not empty

                if (pre.cols != size.width && pre.rows != size.height) {
                    resize(pre, frame, size);
                    pre.release();
                }
                else {
                    frame = pre;
                }

                auto end = std::chrono::system_clock::now();
                std::time_t ts = std::chrono::system_clock::to_time_t(end);
                dPacket* pack = new dPacket{ id,&frame};
                for (auto i : clients) {
                    i->onPNewFrame(pack);
                }
                frame.release();
                delete pack;
            }
        }

        catch (int e) {
            cout << endl << "-----Exception during capture process! CODE " << e << endl;
        }
        // End camera manipulations
    }

    cout << "Camera timed out, or connection is closed..." << endl;
    if (tryResetConnection) {
        cout << "Reconnection flag is set, retrying after 3 seconds..." << endl;
        for (FrameListener* i : clients) {
            i->onNotification(-1); // Notify clients about this shit
        }
        this_thread::sleep_for(chrono::milliseconds(3000));
        stream();
    }

    return true;
}


void Camera::killStream(){
    tryResetConnection = false;
    capture = false;
    ipCam.release();
}

void Camera::setReconnectable(bool reconFlag) {
    tryResetConnection = reconFlag;
}

bool Camera::getReconnectable() {
    return tryResetConnection;
}

vector<bool> Camera::getState() {
    vector<bool> states;
    states.push_back(isPlaying);
    states.push_back(ipCam.isOpened());
    return states;
}



#包括“Camera.h”
Camera::Camera(字符串url、整数宽度、整数高度、布尔检测)//默认值240p
{
streamUrl=url;//准备url
尺寸=尺寸(宽度、高度);
检测=检测;
}
无效摄影机::addListener(FrameListener*客户端){
客户。推回(客户);
}
/*
测试摄像头(粘贴到CAMERAS.dViewer中):
{“id”:“96a73796-c129-46fc-9c01-40acd8ed7122”,“ip”:“176.57.73.231”,“密码”:“null”,“用户名”:“null”},
{“id”:“96a73796-c129-46fc-9c01-40acd8ed7122”,“ip”:“176.57.73.231”,“密码”:“null”,“用户名”:“null”},
{“id”:“96a73796-c129-46fc-9c01-40acd8ed7144”,“ip”:“172.20.101.13”,“密码”:“admin”,“用户名”:“root”}
{“id”:“96a73796-c129-46fc-9c01-40acd8ed7144”,“ip”:“172.20.101.13”,“密码”:“admin”,“用户名”:“root”}
*/
bool-Camera::stream(){
/*此函数用于在单独的线程上运行,并独立地填充缓冲区
主流线程*/
//cv::setNumThreads(100);
/*这些规则略有改变*/
Mat pre;//获取初始未分解帧
//pre=材料:零(尺寸,CV_8UC1);
Mat frame;//最终修改的帧
框架=材料::零(尺寸,CV_8UC1);
如果(!pre.isContinuous())pre=pre.clone();
ipCam.open(streamUrl,CAP_FFMPEG);
while(ipCam.isOpened()&&capture){
//如果相机打开,我们需要捕捉并处理帧
试一试{
自动启动=标准::时钟::系统时钟::现在();
ipCam>>预处理;
if(pre.empty()){
/*检查空白帧,如果有空白帧,则返回错误*/
cerr-onPNewFrame(包装);
}
frame.release();
删除包装;
}
}
捕获(INTE){
cout drawText(QPoint(画师->视口().width()-fm.width(str),50),str);
如果(调试){
QFont font=画师->字体();
字体设置点大小(25);
画师->设置笔(Qt::红色);
字符串camMess=“CAMID:”+CAMID;
QString-mess(cammass.c_-str());
字符串camIp=“IP:”+camUtils->getIp(camId);
QString ipMess(camIp.c_str());
QString bufferSize(“缓冲区大小:+QString::number(Buffer.size()));
QString lastFrameText(“最后一帧绘制时间:”+QString::number)

#pragma once
#include <iostream>
#include <vector>
#include <fstream>
#include <map>
#include <string>
#include <sstream>
#include <algorithm>
#include "FrameListener.h"
#include <opencv2\opencv.hpp>
#include <thread>
#include "CamUtils.h"
#include <ctime>
#include "dPacket.h"

using namespace std;
using namespace cv;

class Camera
{

    /*
        CLEANED UP!
        Camera now is only responsible for streaming and echoing captured frames.
        Frames are now wrapped into dPacket struct.
    */


private:
    string id;
    vector<FrameListener*> clients;
    VideoCapture ipCam;
    string streamUrl;
    Size size;
    bool tryResetConnection = false;

    //TODO: Remove these as they are not going to be used going on:
    bool isPlaying = true;
    bool capture = true;

    //SECRET FEATURES:
    bool detect = false;


public:
    Camera(string url, int width = 480, int height = 240, bool detect_=false);
    bool stream();
    void setReconnectable(bool newReconStatus);
    void addListener(FrameListener* client);
    vector<bool> getState();    // Returns current state: vector[0] stream state; vector[1] stream state; TODO: Remove this as this is no longer should control behaviour
    void killStream();
    bool getReconnectable();
};

#include "Camera.h"


Camera::Camera(string url, int width, int height, bool detect_) // Default 240p
{
    streamUrl = url; // Prepare url
    size = Size(width, height);
    detect = detect_;

}

void Camera::addListener(FrameListener* client) {
    clients.push_back(client);
}


/*
                TEST CAMERAS(Paste into cameras.dViewer):
                {"id":"96a73796-c129-46fc-9c01-40acd8ed7122","ip":"176.57.73.231","password":"null","username":"null"},
                {"id":"96a73796-c129-46fc-9c01-40acd8ed7122","ip":"176.57.73.231","password":"null","username":"null"},
                {"id":"96a73796-c129-46fc-9c01-40acd8ed7144","ip":"172.20.101.13","password":"admin","username":"root"}
                {"id":"96a73796-c129-46fc-9c01-40acd8ed7144","ip":"172.20.101.13","password":"admin","username":"root"}

*/



bool Camera::stream() {
    /* This function is meant to run on a separate thread and fill up the buffer independantly of
    main stream thread */
    //cv::setNumThreads(100);
    /* Rules for these slightly changed! */
    Mat pre;  // Grab initial undoctored frame
    //pre = Mat::zeros(size, CV_8UC1);
    Mat frame; // Final modified frame
    frame = Mat::zeros(size, CV_8UC1);
    if (!pre.isContinuous()) pre = pre.clone();

    ipCam.open(streamUrl, CAP_FFMPEG);

    while (ipCam.isOpened() && capture) {
        // If camera is opened wel need to capture and process the frame
        try {
            auto start = std::chrono::system_clock::now();

            ipCam >> pre;

            if (pre.empty()) {
                /* Check for blank frame, return error if there is a blank frame*/
                cerr << id << ": ERROR! blank frame grabbed\n";
                for (FrameListener* i : clients) {
                    i->onNotification(1); // Notify clients about this shit
                }
                break;
            }

            else {
                // Only continue if frame not empty

                if (pre.cols != size.width && pre.rows != size.height) {
                    resize(pre, frame, size);
                    pre.release();
                }
                else {
                    frame = pre;
                }

                auto end = std::chrono::system_clock::now();
                std::time_t ts = std::chrono::system_clock::to_time_t(end);
                dPacket* pack = new dPacket{ id,&frame};
                for (auto i : clients) {
                    i->onPNewFrame(pack);
                }
                frame.release();
                delete pack;
            }
        }

        catch (int e) {
            cout << endl << "-----Exception during capture process! CODE " << e << endl;
        }
        // End camera manipulations
    }

    cout << "Camera timed out, or connection is closed..." << endl;
    if (tryResetConnection) {
        cout << "Reconnection flag is set, retrying after 3 seconds..." << endl;
        for (FrameListener* i : clients) {
            i->onNotification(-1); // Notify clients about this shit
        }
        this_thread::sleep_for(chrono::milliseconds(3000));
        stream();
    }

    return true;
}


void Camera::killStream(){
    tryResetConnection = false;
    capture = false;
    ipCam.release();
}

void Camera::setReconnectable(bool reconFlag) {
    tryResetConnection = reconFlag;
}

bool Camera::getReconnectable() {
    return tryResetConnection;
}

vector<bool> Camera::getState() {
    vector<bool> states;
    states.push_back(isPlaying);
    states.push_back(ipCam.isOpened());
    return states;
}




#ifndef GLWIDGET_H
#define GLWIDGET_H

#include <QOpenGLWidget>
#include <QMouseEvent>
#include "FrameListener.h"
#include "Camera.h"
#include "FrameListener.h"
#include <opencv2\opencv.hpp>
#include "Camera.h"
#include "CamUtils.h"
#include <qstyleoption.h>
#include "dPacket.h"
#include <chrono>
#include <ctime>
#include <qmenu.h>
#include "FullScreenVideo.h"
#include <QMovie>
#include "helper.h"
#include <iostream>
#include <QPainter>
#include <QTimer>

class Helper;

class GLWidget : public QOpenGLWidget, public FrameListener
{
    Q_OBJECT

public:
    GLWidget(std::string camId, CamUtils *cUtils, int width, int height, bool denyFullScreen_ = false, bool detectFlag_=false, QWidget* parent = nullptr);
    void killStream();
    ~GLWidget();

public slots:
    void animate();
    void setBufferEnabled(bool setState);
    void setCameraRetryConnection(bool setState);
    void GLUpdate();            // Call to update the widget
    void onRightClickMenu(const QPoint& point);

protected:
    void paintEvent(QPaintEvent* event) override;
    void onPNewFrame(dPacket* frame);
    void onNotification(int alert_code);


private:
    // Objects and resourses
    Helper* helper;
    Camera* cam;
    CamUtils* camUtils;
    QTimer* timer; // Keep track of update
    QPixmap lastImage;
    QMovie* connMov;
    QMovie* test;

    QPixmap logo;

    // Control fields
    int width;
    int height;
    int camUtilsAddr;
    int elapsed;
    std::thread* camThread;
    std::string camId;
    bool denyFullScreen = false;
    bool playing = true;
    bool streaming = true;
    bool debug = false;
    bool connecting = true;
    int lastFlag = 0;


    // Debug fields
    std::chrono::high_resolution_clock::time_point lastFrameAt;
    std::chrono::high_resolution_clock::time_point now;
    std::chrono::duration<double> painTime; // time took to draw last frame

    //Buffer stuff
    std::queue<QPixmap> buffer;
    bool bufferEnabled = false;
    bool initialBuffer = false;
    bool buffering = true;
    bool frameProcessing = false;



    //Functions
    QImage toQImageFromPMat(cv::Mat* inFrame);
    void mousePressEvent(QMouseEvent* event) override;
    void drawImageGLLatest(QPainter* painter, QPaintEvent* event, int elapsed);
    void drawOnPaused(QPainter* painter, QPaintEvent* event, int elapsed);
    void drawOnStatus(int statusFlag, QPainter* painter, QPaintEvent* event, int elapsed);
};

#endif

#include "glwidget.h"
#include <future>


FullScreenVideo* fullScreen;

GLWidget::GLWidget(std::string camId_, CamUtils* cUtils, int width_, int height_,  bool denyFullScreen_, bool detectFlag_, QWidget* parent)
    : QOpenGLWidget(parent), helper(helper)
{
    cout << "Player for CAMERA " << camId_ << endl;

    /* Underlying properties */
    camUtils = cUtils;
    cout << "GLWidget Incoming CamUtils addr " << camUtils << endl;
    cout << "GLWidget Set CamUtils addr " << camUtils << endl;
    camId = camId_;
    elapsed = 0;
    width = width_ + 5;
    height = height_ + 5;
    helper = new Helper();
    setFixedSize(width, height);
    denyFullScreen = denyFullScreen_;

    /* Camera capture thread */
    cam = new Camera(camUtils->getCameraStreamURL(camId), width_, height_, detectFlag_);
    cam->addListener(this);

    /* Sync states */
    vector<bool> initState = cam->getState();
    playing = initState[0];
    streaming = initState[1];
    cout << "Initial states: " << playing << " " << streaming << endl;
    camThread = new std::thread(&Camera::stream, cam);
    cout << "================================================" << endl;

    // Right click set up
    setContextMenuPolicy(Qt::CustomContextMenu);


    /* Loading gif */
    connMov = new QMovie("establishingConnection.gif");
    connMov->start();
    QString url = R"(RLC-logo.png)";
    logo = QPixmap(url);
    QTimer* timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(GLUpdate()));
    timer->start(1000/30);
    playing = true;

}

/* SYSTEM */
void GLWidget::animate()
{
    elapsed = (elapsed + qobject_cast<QTimer*>(sender())->interval()) % 1000;
    std::cout << elapsed << "\n";
}


void GLWidget::GLUpdate() {
    /* Process descisions before update call */
    if (bufferEnabled) {
        /* Process buffer before update */
        now = chrono::high_resolution_clock::now();
        std::chrono::duration<double, std::milli> timeSinceLastUpdate = now - lastFrameAt;
        if (timeSinceLastUpdate.count() > 25) {
            if (buffer.size() > 1 && playing) {
                lastImage.swap(buffer.front());
                buffer.pop();
                lastFrameAt = chrono::high_resolution_clock::now();
            }
        }
        //update(); // Update
    }
    else {
        /* No buffer */
    }
    repaint();
}


/* EVENTS */
void GLWidget::onRightClickMenu(const QPoint& point) {
    cout << "Right click request got" << endl;

    QPoint globPos = this->mapToGlobal(point);
    QMenu myMenu;

    if (!denyFullScreen) {
        myMenu.addAction("Open Full Screen");
    }
    myMenu.addAction("Toggle Debug Info");


    QAction* selected = myMenu.exec(globPos);

    if (selected) {
        string optiontxt = selected->text().toStdString();

        if (optiontxt == "Open Full Screen") {
            cout << "Chose to open full screen of " << camId << endl;
            fullScreen = new FullScreenVideo(bufferEnabled, this);
            fullScreen->setUpView(camUtils, camId);
            fullScreen->show();
            playing = false;
        }

        if (optiontxt == "Toggle Debug Info") {
            cout << "Chose to toggle debug of " << camId << endl;
            debug = !debug;
        }
    }
    else {
        cout << "Chose nothing!" << endl;
    }


}



void GLWidget::onPNewFrame(dPacket* inPack) {
    lastFlag = 0;

    if (bufferEnabled) {
        buffer.push(QPixmap::fromImage(toQImageFromPMat(inPack->frame)));
    }
    else {
        if (playing) {
            /* Only process if this widget is playing */
            frameProcessing = true;
            lastImage.convertFromImage(toQImageFromPMat(inPack->frame));
            frameProcessing = false;
        }
    }

    if (lastFlag != -1 && !lastImage.isNull()) {
        connecting = false;
    }
    else {
        connecting = true;
    }
}


void GLWidget::onNotification(int alert) {
    lastFlag = alert;   
}


/* Paint events*/


void GLWidget::paintEvent(QPaintEvent* event)
{
    QPainter painter(this);

        if (lastFlag != 0 || connecting) {
            drawOnStatus(lastFlag, &painter, event, elapsed);
        }
        else {

            /* Actual frame drawing */
            if (playing) {
                if (!frameProcessing) {
                    drawImageGLLatest(&painter, event, elapsed);
                }
            }
            else {
                drawOnPaused(&painter, event, elapsed);
            }
        }
    painter.end();

}


/* DRAWING STUFF */

void GLWidget::drawOnStatus(int statusFlag, QPainter* bgPaint, QPaintEvent* event, int elapsed) {

    QString str;
    QFont font("times", 15);
    bgPaint->eraseRect(QRect(0, 0, width, height));
    if (!lastImage.isNull()) {
        bgPaint->drawPixmap(QRect(0, 0, width, height), lastImage);
    }
    /* Test background painting */
    if (connecting) {
        string k = "Connecting to " + camUtils->getIp(camId);
        str.append(k.c_str());
    }
    else {
        switch (statusFlag) {
        case 1:
            str = "Blank frame received...";
            break;

        case -1:
            if (cam->getReconnectable()) {
                str = "Connection lost, will try to reconnect.";
                bgPaint->setOpacity(0.3);
            }
            else {
                str = "Connection lost...";
                bgPaint->setOpacity(0.3);
            }

            break;
        }
    }

    bgPaint->drawPixmap(QRect(0, 0, width, height), QPixmap::fromImage(connMov->currentImage()));
    bgPaint->setPen(Qt::red);
    bgPaint->setFont(font);
    QFontMetrics fm(font);
    const QRect kek(0, 0, fm.width(str), fm.height());
    QRect bound;
    bgPaint->setOpacity(1);
    bgPaint->drawText(bgPaint->viewport().width()/2 - kek.width()/2, bgPaint->viewport().height()/2 - kek.height(), str);

    bgPaint->drawPixmap(bgPaint->viewport().width() / 2 - logo.width()/2, height - logo.width() - 15, logo);

}



void GLWidget::drawOnPaused(QPainter* painter, QPaintEvent* event, int elapsed) {
    painter->eraseRect(0, 0, width, height);
    QFont font = painter->font();
    font.setPointSize(18);
    painter->setPen(Qt::red);
    QFontMetrics fm(font);
    QString str("Paused");
    painter->drawPixmap(QRect(0, 0, width, height),lastImage);
    painter->drawText(QPoint(painter->viewport().width() - fm.width(str), 50), str);

    if (debug) {
        QFont font = painter->font();
        font.setPointSize(25);
        painter->setPen(Qt::red);
        string camMess = "CAMID: " + camId;
        QString mess(camMess.c_str());
        string camIp = "IP: " + camUtils->getIp(camId);
        QString ipMess(camIp.c_str());
        QString bufferSize("Buffer size: " + QString::number(buffer.size()));
        QString lastFrameText("Last frame draw time: " + QString::number(painTime.count()) + "s");
        painter->drawText(QPoint(10, 50), mess);
        painter->drawText(QPoint(10, 60), ipMess);
        QString bufferState;
        if (bufferEnabled) {
            bufferState = QString("Experimental BUFFER is enabled!");
            QString currentBufferSize("Current buffer load: " + QString::number(buffer.size()));
            painter->drawText(QPoint(10, 80), currentBufferSize);
        }
        else {
            bufferState = QString("Experimental BUFFER is disabled!");
        }
        painter->drawText(QPoint(10, 70), bufferState);
        painter->drawText(QPoint(10, height - 25), lastFrameText);
    }
}


void GLWidget::drawImageGLLatest(QPainter* painter, QPaintEvent* event, int elapsed) {
    auto start = chrono::high_resolution_clock::now();
    painter->drawPixmap(QRect(0, 0, width, height), lastImage);
    if (debug) {
        QFont font = painter->font();
        font.setPointSize(25);
        painter->setPen(Qt::red);
        string camMess = "CAMID: " + camId;
        QString mess(camMess.c_str());
        string camIp = "IP: " + camUtils->getIp(camId);
        QString ipMess(camIp.c_str());
        QString bufferSize("Buffer size: " + QString::number(buffer.size()));
        QString lastFrameText("Last frame draw time: " + QString::number(painTime.count()) + "s");
        painter->drawText(QPoint(10, 50), mess);
        painter->drawText(QPoint(10, 60), ipMess);
        QString bufferState;
        if(bufferEnabled){
            bufferState = QString("Experimental BUFFER is enabled!");
            QString currentBufferSize("Current buffer load: " + QString::number(buffer.size()));
            painter->drawText(QPoint(10,80), currentBufferSize);
        }
        else {
            bufferState = QString("Experimental BUFFER is disabled!");
            QString currentBufferSize("Current buffer load: " + QString::number(buffer.size()));
            painter->drawText(QPoint(10, 80), currentBufferSize);
        }
        painter->drawText(QPoint(10, 70), bufferState);
        painter->drawText(QPoint(10, height - 25), lastFrameText);

    }
    auto end = chrono::high_resolution_clock::now();
    painTime = end - start;
}



/* END DRAWING STUFF */



/* UI EVENTS */

void GLWidget::mousePressEvent(QMouseEvent* e) {

    if (e->button() == Qt::LeftButton) {
        if (fullScreen == nullptr || !fullScreen->isVisible()) { // Do not unpause if window is opened
            playing = !playing;
        }
    }

    if (e->button() == Qt::RightButton) {
        onRightClickMenu(e->pos());
    }
}



/* Utilities */
QImage GLWidget::toQImageFromPMat(cv::Mat* mat) {



    return QImage(mat->data, mat->cols, mat->rows, QImage::Format_RGB888).rgbSwapped();



}

/* State control */

void GLWidget::killStream() {
    cam->killStream();
    camThread->join();
}

void GLWidget::setBufferEnabled(bool newBufferState) {
    cout << "Player: " << camId << ", buffer state updated: " << newBufferState << endl;
    bufferEnabled = newBufferState;
    buffer.empty();
}

void GLWidget::setCameraRetryConnection(bool newState) {
    cam->setReconnectable(newState);
}

/* Destruction */
GLWidget::~GLWidget() {
    cam->killStream();
    camThread->join();
}
#pragma once
#include <iostream>
#include <vector>
#include <fstream>
#include <map>
#include <string>
#include <sstream>
#include <algorithm>
#include <nlohmann/json.hpp>

using namespace std;
using json = nlohmann::json;

class CamUtils
{
private:

    string camDb = "cameras.dViewer";
    map<string, vector<string>> cameraList; // Legacy
    json cameras;
    ofstream dbFile;
    bool dbExists(); // Always hard coded

    /* Old IMPLEMENTATION */
    void writeLineToDb_(const string& content, bool append = false);
    void loadCameras_();

    /* JSON based */
    void loadCameras();

public:
    CamUtils();
    string generateRandomString(size_t length);
    string getCameraStreamURL(string cameraId) const;
    string saveCamera(string ip, string username, string pass); // Return generated id
    vector<string> listAllCameraIds();
    string getIp(string cameraId);
};


#include "CamUtils.h"
#pragma comment(lib, "rpcrt4.lib")  // UuidCreate - Minimum supported OS Win 2000
#include <windows.h>
#include <iostream>

CamUtils::CamUtils()
{
    if (!dbExists()) {
        ofstream dbFile;
        dbFile.open(camDb);
        cameras["cameras"] = json::array();
        dbFile << cameras << std::endl;
        dbFile.close();

    }
    else {
        loadCameras();
    }
}




vector<string> CamUtils::listAllCameraIds() {
    vector<string> ids;
    cout << "IN LIST " << endl;
    for (auto& cam : cameras["cameras"]) {
        ids.push_back(cam["id"].get<string>());
        //cout << cam["id"].get<string>() << std::endl;
    }
    return ids;
}

string CamUtils::getIp(string id) {
    vector<string> camDetails = cameraList[id];
    string ip = "NO IP WILL DISPLAYED UNTIL I FIGURE OUT A BUG";
    for (auto& cam : cameras["cameras"]) {
        if (id == cam["id"]) {
            ip = cam["ip"].get<string>();
        }
    }

    return ip;
}

string CamUtils::getCameraStreamURL(string id) const {
    string url = "err"; // err is the default, it will be overwritten in case id is found, dont forget to check for it

    for (auto& cam : cameras["cameras"]) {
        if (id == cam["id"]) {
            if (cam["username"].get<string>() == "null") {
                url = "rtsp://" + cam["ip"].get<string>() + ":554/axis-media/media.amp?tcp";
            }
            else {
                url = "rtsp://" + cam["username"].get<string>() + ":" + cam["password"].get<string>() + "@" + cam["ip"].get<string>() + ":554/axis-media/media.amp?streamprofile=720_30";
            }
        }
    }

    return url;  // Dont forget to check for err when using this shit
}


string CamUtils::saveCamera(string ip, string username, string password) {
    UUID uid;
    UuidCreate(&uid);
    char* str;
    UuidToStringA(&uid, (RPC_CSTR*)&str);
    string id = str;
    cout << "GEN: " << id << endl;
    json cam = json({}); //Create emtpy object
    cam["id"] = id;
    cam["ip"] = ip;
    cam["username"] = username;
    cam["password"] = password;
    cameras["cameras"].push_back(cam);
    std::ofstream out(camDb);
    out << cameras << std::endl;
    cout << cameras["cameras"] << endl;

    cout << "Saved camera as " << id << endl;
    return id;
}


bool CamUtils::dbExists() {
    ifstream dbFile(camDb);
    return (bool)dbFile;
}





void CamUtils::loadCameras() {
    cout << "Load call" << endl;
    ifstream dbFile(camDb);
    string line;
    string wholeFile;

    while (std::getline(dbFile, line)) {
        cout << line << endl;
        wholeFile += line;
    }
    try {
        cameras = json::parse(wholeFile);
        //cout << cameras["cameras"] << endl;

    }
    catch (exception e) {
        cout << e.what() << endl;
    }
    dbFile.close();
}










/*
    LEGACY CODE, TO BE REMOVED!

*/



void CamUtils::loadCameras_() {
    /* 
        LEGACY CODE:
        This used to be the way to load cameras, but I moved on to JSON based configuration so this is no longer needed and will be removed soon
    */

    ifstream dbFile(camDb);
    string line;
    while (std::getline(dbFile, line)) {
        /*
            This function load camera data to the map:
            The order MUST be the following: 0:ID, 1:IP, 2:USERNAME, 3:PASSWORD.
            Always delimited with | no spaces between!
        */
        if (!line.empty()) {
            stringstream ss(line);
            string item;
            vector<string> splitString;

            while (std::getline(ss, item, '|')) {
                splitString.push_back(item);
            }
            if (splitString.size() > 0) {
                /* Dont even parse if the program didnt split right*/
                //cout << "Split string: " << splitString.size() << "\n";
                for (int i = 0; i < (splitString.size()); i++) cameraList[splitString[0]].push_back(splitString[i]);
            }
        }
    }
}



void CamUtils::writeLineToDb_(const string & content, bool append) {
    ofstream dbFile;
    cout << "Creating?";
    if (append) {
        dbFile.open(camDb, ios_base::app);
    }
    else {
        dbFile.open(camDb);
    }

    dbFile << content.c_str() << "\r\n";
    dbFile.flush();
}

/* JSON Reworx */




string CamUtils::generateRandomString(size_t length)
{
    const char* charmap = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    const size_t charmapLength = strlen(charmap);
    auto generator = [&]() { return charmap[rand() % charmapLength]; };
    string result;
    result.reserve(length);
    generate_n(back_inserter(result), length, generator);
    return result;
}
bool Camera::stream() {
    /* This function is meant to run on a separate thread and fill up the buffer independantly of
    main stream thread */

    ipCam.open(streamUrl, CAP_FFMPEG);

    while (ipCam.isOpened() && capture) {

        UMat frame;  // Grab initial undoctored frame
        // If camera is opened wel need to capture and process the frame 

        try {
            ipCam >> frame;

            if (!frame.empty()) {
                UMat temp;
                cvtColor(frame, temp, COLOR_BGR2RGB);
                frame = temp;
                if (frame.cols != size.width && frame.rows != size.height) {
                    resize(frame, temp, size);
                    frame = temp;
                }

                dPacket* pack = new dPacket;
                pack->camId = id;
                Mat t;
                t = frame.getMat(ACCESS_FAST);
                pack->frame = &t;
                if (t.empty()) cout << "WTF" << endl;
                for (auto i : clients) {
                    i->onPNewFrame(pack);
                }

                // Clean up:
                t.release();
                frame.release();
                temp.release();
                delete pack;

            }
            else {
                /* Blank frame caught */
                cerr << id << ": ERROR! blank frame grabbed\n";
                for (FrameListener* i : clients) {
                    i->onNotification(1); // Notify clients about this shit
                }
                break;

            }


        }

        catch (int e) {
            cout << endl << "-----Exception during capture process! CODE " << e << endl;
        }
        // End camera manipulations
    }

    cout << "Camera timed out, or connection is closed..." << endl;
    if (tryResetConnection) {
        cout << "Reconnection flag is set, retrying after 3 seconds..." << endl;
        for (FrameListener* i : clients) {
            i->onNotification(-1); // Notify clients about this shit
        }
        this_thread::sleep_for(chrono::milliseconds(3000));
        stream();
    }

    return true;
}