zoukankan      html  css  js  c++  java
  • Qt音视频开发48-通用通道管理

    一、前言

    把通用的视频控件搞定以后,后期增加新的内核方便多了,不需要在好多个文件复制粘贴之类的,接下来就是需要一个统一的类来管理视频监控系统中的16个通道或者32个通道,甚至64个通道也有可能,当然,通用通道管理也兼容各种监控内核,以前通道管理类,是每个内核写一个,也是很繁琐,大量的重复性代码,所以将通用视频监控控件整理好以后,顺其自然的要改造这个通用通道管理的类了。

    通用通道管理的需求来源自实际的开发过程需要,比如断线重连机制,尽管每个视频监控控件自带了断线重连功能,很容易会出现极端的情况,比如网络断了以后,设备重新上线,会全部瞬间重新上线(如果设置的超时时间一致的话),这就给CPU造成很大压力,瞬间暴增,所以需要一个类专门管理所有的摄像机设备,由他来负责排队断线重连,加载打开设备,统一的截图机制,统一的视频存储机制。

    通道管理基本功能:

    1. 设置地址集合(可以是配置文件读取也可以是数据库读取)、名称集合、控件集合。
    2. 所有通道或者指定通道的打开和关闭。
    3. 指定通道的抓拍截图。
    4. 设置视频通道数、超时时间。
    5. 设置打开视频的间隔、重连视频的间隔。
    6. 指定视频存储间隔和存储文件夹。

    二、功能特点

    1. 支持多画面切换,全屏切换等,包括1+4+6+8+9+13+16+25+36+64画面切换。
    2. 支持alt+enter全屏,esc退出全屏。
    3. 自定义信息框+错误框+询问框+右下角提示框(包含多种格式)。
    4. 17套皮肤样式随意更换,所有样式全部统一,包括菜单等。
    5. 云台仪表盘鼠标移上去高亮,八个方位精准识别。
    6. 底部画面工具栏(画面分割切换+截图声音等设置)移上去高亮。
    7. 可在配置文件更改左上角logo+中文软件名称+英文软件名称。
    8. 封装了百度地图,视图切换,运动轨迹,设备点位,鼠标按下获取经纬度等。
    9. 支持图片地图,设备按钮可以在图片地图上自由拖动自动保存位置信息。
    10. 在百度地图和图片地图上,双击视频可以预览摄像头实时视频。
    11. 堆栈窗体,每个窗体都是个单独的qwidget,方便编写自己的代码。
    12. 顶部鼠标右键菜单,可动态控制时间CPU+左上角面板+左下角面板+右上角面板+右下角面板的显示和隐藏,支持恢复默认布局。
    13. 工具栏可以放置多个小图标和关闭图标。
    14. 左侧右侧可拖动拉伸,并自动记忆宽高位置,重启后恢复。
    15. 双击摄像机节点自动播放视频,双击节点自动依次添加视频,会自动跳到下一个,双击父节点自动添加该节点下的所有视频。
    16. 摄像机节点拖曳到对应窗体播放视频,同时支持拖曳本地文件直接播放。
    17. 视频画面窗体支持拖曳交换,瞬间响应。
    18. 双击节点+拖曳节点+拖曳窗体交换位置,均自动更新url.txt。
    19. 支持从url.txt中加载通道视频播放,自动记忆最后通道对应的视频,软件启动后自动打开播放。
    20. 右下角音量条控件,失去焦点自动隐藏,音量条带静音图标。
    21. 集成百度在线地图和离线地图,可以添加设备对应位置,自动生成地图,支持缩放和添加覆盖物等。
    22. 视频拖动到通道窗体外自动删除视频。
    23. 鼠标右键可删除当前+所有视频,截图当前+所有视频。
    24. 录像机管理、摄像机管理,可添加删除修改导入导出打印信息,立即应用新的设备信息生成树状列表,不需重启。
    25. 在pro文件中可以自由开启是否加载地图。
    26. 视频播放可选2种内核自由切换,vlc+ffmpeg,均可在pro中设置。
    27. 可设置1+4+9+16画面轮询,可设置轮询间隔以及轮询码流类型等,直接在主界面底部工具栏右侧单击启动轮询按钮即可,再次单击停止轮询。
    28. 默认超过10秒钟未操作自动隐藏鼠标指针。
    29. 支持onvif搜素设备,支持任意onvif摄像机,包括但不限于海康大华宇视天地伟业华为等。
    30. 支持onvif云台控制,可上下左右移动云台摄像机,包括复位和焦距调整等。
    31. 同时支持sqlite、mysql、postsql等数据库。
    32. 可保存视频,可选定时存储或者单文件存储,可选存储间隔时间。
    33. 可设置视频流通信方式tcp+udp,可设置视频解码是速度优先、质量优先、均衡等。
    34. 可设置硬解码类型,支持qsv、dxva2、d3d11va等。
    35. 默认采用opengl绘制视频,超低的CPU资源占用,支持yuyv和nv12两种格式绘制,很牛逼。
    36. 高度可定制化,用户可以很方便的在此基础上衍生自己的功能,支持linux和mac系统。

    三、效果图

    四、相关站点

    1. 国内站点:https://gitee.com/feiyangqingyun/QWidgetDemo
    2. 国际站点:https://github.com/feiyangqingyun/QWidgetDemo
    3. 个人主页:https://blog.csdn.net/feiyangqingyun
    4. 知乎主页:https://www.zhihu.com/people/feiyangqingyun/
    5. 体验地址:https://blog.csdn.net/feiyangqingyun/article/details/97565652

    五、核心代码

    #include "commonvideomanage.h"
    
    #ifdef videovlc
    #include "vlchelper.h"
    #elif videoffmpeg
    #include "ffmpeghelper.h"
    #elif haikang
    #include "haikanghelper.h"
    #endif
    
    #define TIMEMS          qPrintable(QTime::currentTime().toString("HH:mm:ss zzz"))
    #define TIME            qPrintable(QTime::currentTime().toString("HH:mm:ss"))
    #define QDATE           qPrintable(QDate::currentDate().toString("yyyy-MM-dd"))
    #define QTIME           qPrintable(QTime::currentTime().toString("HH-mm-ss"))
    #define DATETIME        qPrintable(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"))
    #define STRDATETIME     qPrintable(QDateTime::currentDateTime().toString("yyyy-MM-dd-HH-mm-ss"))
    #define STRDATETIMEMS   qPrintable(QDateTime::currentDateTime().toString("yyyy-MM-dd-HH-mm-ss-zzz"))
    
    QScopedPointer<CommonVideoManage> CommonVideoManage::self;
    CommonVideoManage *CommonVideoManage::Instance()
    {
        if (self.isNull()) {
            static QMutex mutex;
            QMutexLocker locker(&mutex);
            if (self.isNull()) {
                self.reset(new CommonVideoManage);
            }
        }
    
        return self.data();
    }
    
    CommonVideoManage::CommonVideoManage(QObject *parent) : QObject(parent)
    {
        timeout = 10;
        openInterval = 1000;
        checkInterval = 5;
        videoCount = 16;
    
        saveVideo = false;
        saveVideoInterval = 0;
        savePath = qApp->applicationDirPath();
    
        //打开视频定时器
        timerOpen = new QTimer(this);
        connect(timerOpen, SIGNAL(timeout()), this, SLOT(openVideo()));
        timerOpen->setInterval(openInterval);
    
        //重连视频定时器
        timerCheck = new QTimer(this);
        connect(timerCheck, SIGNAL(timeout()), this, SLOT(checkVideo()));
        timerCheck->setInterval(checkInterval * 1000);
    
        //新建目录
        newDir("snap");
    }
    
    CommonVideoManage::~CommonVideoManage()
    {
        this->stop();
    }
    
    QString CommonVideoManage::getVersion2()
    {
    #if (defined videovlc) || (defined videoffmpeg) || (defined haikang)
        return getVersion();
    #else
        return "1.0";
    #endif
    }
    
    void CommonVideoManage::newDir(const QString &dirName)
    {
        //如果路径中包含斜杠字符则说明是绝对路径
        //linux系统路径字符带有 /  windows系统 路径字符带有 :/
        QString strDir = dirName;
        if (!strDir.startsWith("/") && !strDir.contains(":/")) {
            strDir = QString("%1/%2").arg(qApp->applicationDirPath()).arg(strDir);
        }
    
        QDir dir(strDir);
        if (!dir.exists()) {
            dir.mkpath(strDir);
        }
    }
    
    void CommonVideoManage::openVideo()
    {
        if (index < videoCount) {
            //取出一个进行打开,跳过为空的立即下一个
            QString url = videoUrls.at(index);
            if (!url.isEmpty()) {
                this->open(index);
                index++;
            } else {
                index++;
                this->openVideo();
            }
        } else {
            //全部取完则关闭定时器
            timerOpen->stop();
        }
    }
    
    void CommonVideoManage::checkVideo()
    {
        //如果打开定时器还在工作则不用继续
        if (timerOpen->isActive()) {
            return;
        }
    
        QDateTime now = QDateTime::currentDateTime();
        for (int i = 0; i < videoCount; i++) {
            //只有url不为空的才需要处理重连
            if (videoUrls.at(i).isEmpty()) {
                continue;
            }
    
            //如果10秒内已经处理过重连则跳过当前这个,防止多个掉线一直处理第一个掉线的
            if (lastTimes.at(i).secsTo(now) < 10) {
                continue;
            }
    
            //计算超时时间
            QDateTime lastTime = videoWidgets.at(i)->getLastTime();
            int sec = lastTime.secsTo(now);
            if (sec >= timeout) {
                //重连该设备
                videoWidgets.at(i)->restart();
                //每次重连记住最后重连时间
                lastTimes[i] = now;
                //break;
            }
        }
    }
    
    void CommonVideoManage::snapImage(const QImage &image)
    {
        CommonVideoWidget *w = (CommonVideoWidget *)sender();
        QString fileName = w->property("fileName").toString();
        if (!image.isNull()) {
            image.save(fileName, "jpg");
        }
    }
    
    void CommonVideoManage::setTimeout(int timeout)
    {
        if (timeout >= 5 && timeout < 60) {
            this->timeout = timeout;
        }
    }
    
    void CommonVideoManage::setOpenInterval(int openInterval)
    {
        if (openInterval >= 0 && openInterval <= 2000) {
            this->openInterval = openInterval;
            timerOpen->setInterval(openInterval);
        }
    }
    
    void CommonVideoManage::setCheckInterval(int checkInterval)
    {
        if (checkInterval >= 5 && checkInterval <= 60) {
            this->checkInterval = checkInterval;
            timerCheck->setInterval(checkInterval * 1000);
        }
    }
    
    void CommonVideoManage::setVideoCount(int videoCount)
    {
        this->videoCount = videoCount;
    }
    
    void CommonVideoManage::setSaveVideo(bool saveVideo)
    {
        this->saveVideo = saveVideo;
    }
    
    void CommonVideoManage::setSaveVideoInterval(int saveVideoInterval)
    {
        this->saveVideoInterval = saveVideoInterval;
    }
    
    void CommonVideoManage::setSavePath(const QString &savePath)
    {
        this->savePath = savePath;
    }
    
    void CommonVideoManage::setUrls(const QList<QString> &videoUrls)
    {
        this->videoUrls = videoUrls;
    }
    
    void CommonVideoManage::setNames(const QList<QString> &videoNames)
    {
        this->videoNames = videoNames;
    }
    
    void CommonVideoManage::setWidgets(QList<CommonVideoWidget *> videoWidgets)
    {
        this->videoWidgets = videoWidgets;
    }
    
    void CommonVideoManage::start()
    {
        if (videoWidgets.count() != videoCount) {
            return;
        }
    
        lastTimes.clear();
        for (int i = 0; i < videoCount; i++) {
            lastTimes.append(QDateTime::currentDateTime());
            QString url = videoUrls.at(i);
            if (!url.isEmpty()) {
                CommonVideoWidget *w = videoWidgets.at(i);
    #ifdef videoffmpeg
                disconnect(w, SIGNAL(snapImage(QImage)), this, SLOT(snapImage(QImage)));
                connect(w, SIGNAL(snapImage(QImage)), this, SLOT(snapImage(QImage)));
    #endif
    
                //设置文件url地址
                w->setUrl(url);
    
                //如果是USB摄像头则单独设置宽高
                if (w->getIsUsbCamera()) {
                    w->setVideoWidth(640);
                    w->setVideoHeight(480);
                }
    
                //设置OSD信息,可见+字体大小+文字+颜色+格式+位置
                if (i < videoNames.count()) {
                    w->setOSD1Visible(true);
                    w->setOSD1FontSize(18);
                    w->setOSD1Text(videoNames.at(i));
                    w->setOSD1Color(Qt::yellow);
                    w->setOSD1Format(CommonVideoWidget::OSDFormat_Text);
                    w->setOSD1Position(CommonVideoWidget::OSDPosition_Right_Top);
    
                    //还可以设置第二路OSD
    #if 0
                    w->setOSD2Visible(true);
                    w->setOSD2FontSize(18);
                    w->setOSD2Color(Qt::yellow);
                    w->setOSD2Format(CommonVideoWidget::OSDFormat_DateTime);
                    w->setOSD2Position(CommonVideoWidget::OSDPosition_Left_Bottom);
    #endif
                }
    
                //设置是否存储文件
                w->setSaveFile(saveVideo);
                w->setSavePath(savePath);
                w->setSaveInterval(saveVideoInterval);
                if (saveVideo && saveVideoInterval == 0) {
                    QString path = QString("%1/%2").arg(savePath).arg(QDATE);
                    newDir(path);
                    QString fileName = QString("%1/Ch%2_%3.mp4").arg(path).arg(i + 1).arg(STRDATETIME);
                    w->setFileName(fileName);
                }
    
                //打开间隔 = 0 毫秒则立即打开
                if (openInterval == 0) {
                    this->open(i);
                }
            }
        }
    
        //启动定时器挨个排队打开
        if (openInterval > 0) {
            index = 0;
            timerOpen->start();
        }
    
        //启动定时器排队处理重连
        QTimer::singleShot(5000, timerCheck, SLOT(start()));
    }
    
    void CommonVideoManage::stop()
    {
        if (videoWidgets.count() != videoCount) {
            return;
        }
    
        if (timerOpen->isActive()) {
            timerOpen->stop();
        }
    
        if (timerCheck->isActive()) {
            timerCheck->stop();
        }
    
        for (int i = 0; i < videoCount; i++) {
            this->close(i);
        }
    }
    
    void CommonVideoManage::open(int index)
    {
        if (!videoUrls.at(index).isEmpty()) {
            videoWidgets.at(index)->open();
        }
    }
    
    void CommonVideoManage::close(int index)
    {
        if (!videoUrls.at(index).isEmpty()) {
            videoWidgets.at(index)->close();
        }
    }
    
    void CommonVideoManage::snap(int index, const QString &fileName)
    {
        if (videoUrls.at(index).isEmpty()) {
            return;
        }
    
    #ifdef videoffmpeg
        CommonVideoWidget *w = videoWidgets.at(index);
        w->setProperty("fileName", fileName);
        QImage img = w->getImage();
        if (!img.isNull()) {
            img.save(fileName, "jpg");
        }
    #else
        videoWidgets.at(index)->snap(fileName);
    #endif
    }
    
  • 相关阅读:
    Candy leetcode java
    Trapping Rain Water leetcode java
    Best Time to Buy and Sell Stock III leetcode java
    Best Time to Buy and Sell Stock II leetcode java
    Best Time to Buy and Sell Stock leetcode java
    Maximum Subarray leetcode java
    Word Break II leetcode java
    Word Break leetcode java
    Anagrams leetcode java
    Clone Graph leetcode java(DFS and BFS 基础)
  • 原文地址:https://www.cnblogs.com/feiyangqingyun/p/14010278.html
Copyright © 2011-2022 走看看