用純Qt實現GB28181協議/實時視頻/云臺控制/預置位/錄像回放和下載/事件訂閱/語音對講

一、前言

在技術的長河中探索,有些目標一旦確立,便如同璀璨星辰,指引著我們不斷前行。早在2014年,我心中就種下了用純Qt實現GB28181協議的種子,如今回首,一晃十年已逝,好在整體框架和邏輯終于打通,個中滋味,只有自己知曉。

最初接觸GB28181協議時,我就發現它遠比熟悉的onvif協議復雜。onvif協議在局域網內表現出色,配置簡單、使用方便,很多安防設備在局域網環境下借助onvif協議能輕松實現設備間的互聯互通。然而,當涉及外網訪問時,onvif就顯得力不從心,幾乎找不到有效的解決辦法。在如今監控設備遍布大街小巷,各部門機構都急需外網遠程取流的大環境下,GB28181協議應運而生。它作為一套視頻監控規范,旨在解決外網訪問監控視頻的諸多痛點。但GB28181協議也面臨著網絡通信的固有難題,比如服務端在未收到客戶端消息時,無法知曉客戶端的具體通信地址,這就導致雙方無法直接通信,必須由客戶端主動向服務器發送消息來建立聯系。

在實現GB28181協議的過程中,選擇何種方式解析SIP協議是關鍵。市面上關于SIP協議的第三方庫五花八門,功能看似完備。但經過深思熟慮,我最終決定采用Qt底層的udp通信協議來進行解析。這么做主要有兩方面的考量。一方面,從底層入手能讓我更深入地理解協議的每一個細節。在解析過程中,我可以根據實際需求打造友好的使用接口,避免了使用第三方庫時可能遇到的各種問題,比如繁瑣的編譯過程,不同版本庫之間的兼容性難題等。另一方面,考慮到項目后期的拓展性,從底層“手擼”代碼是最好的選擇。只有牢牢掌握底層實現,才能靈活應對各種新需求和新場景,將兼容性和易用性放在首位。畢竟,只有真正解決了客戶在使用過程中的痛點,產品才有市場價值。

二、效果圖在這里插入圖片描述

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

三、相關地址

  1. 國內站點:https://gitee.com/feiyangqingyun
  2. 國際站點:https://github.com/feiyangqingyun
  3. 個人作品:https://blog.csdn.net/feiyangqingyun/article/details/97565652
  4. 文件地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取碼:01jf 文件名:bin_video_gb28181。

四、功能特點

  1. 支持設備注冊、注銷、心跳、校時、注冊認證、注銷認證等。
  2. 設備上線后可以手動獲取設備狀態、設備信息、配置信息、預置位信息等。
  3. 設備上線后自動獲取設備通道信息,包括中文通道名稱。識別到通道上線離線變化,會重新獲取該設備的所有通道信息。
  4. 支持視頻點播,可以分別點播主碼流和子碼流,內置rtp解包線程,解包后發給視頻播放組件解碼播放。
  5. 每個設備每個通道支持點播多個視頻,通過ssrc區分,支持共用端口和不同端口收流。
  6. 支持對某個設備下面所有通道、某個通道、某個通道對應的某個流分別關閉。
  7. 支持錄像文件查詢和回放,回放控制支持暫停播放、繼續播放、倍速播放、切換播放進度。
  8. 支持錄像文件下載,支持倍速比如8倍速下載,可同時多線程批量下載。
  9. 回放和下載同時支持IPC和NVR,比如攝像頭自帶的SD存儲卡錄像文件回放,NVR上的硬盤錄像文件回放。
  10. 支持云臺控制,向上、向下、向左、向右、左上、右上、左下、右下方位移動,鏡頭放大縮小,光圈放大縮小,鏡頭聚焦放焦。
  11. 支持預置位信息的查詢、調用、添加、修改、刪除等操作。
  12. 自動目錄訂閱功能,通道上線下線都有對應的信號通知。
  13. 支持警情訂閱,各種警情事件比如運動目標檢測報警、入侵檢測報警、徘徊檢測報警等自動上報。
  14. 國標服務同時支持udp和tcp方式,可選只監聽一種或者兩種都監聽,tcp方式自動處理粘包問題。
  15. 收流端口自動糾錯,自動跳過被占用的端口,不會出現端口占用導致收流失敗的情況。
  16. 支持幾千路國標消息交互并發,實時視頻流支持64路同時顯示,可以拓展更多路數。
  17. 支持阿里云等云服務器,可以分別設置內網監聽地址和外網訪問地址,一般云服務器上是監聽地址用內網,對外訪問用外網地址。
  18. 支持視頻分發,也就是推流,視頻通道打開后可以自動推流到流媒體服務器,其他需要的地方拉流即可,支持rtsp、rtmp、hls、webrtc等方式拉流。
  19. SIP解析和交互采用純Qt底層代碼實現,udp/tcp通信交互,祖傳原創代碼解析,不依賴任何第三方。
  20. 代碼量少,gb28181交互部分共幾千行代碼,注釋詳細,接口友好,使用極其簡單,提供非常詳細的使用示例。
  21. 支持海康、大華、宇視、華為、天地偉業等所有國標設備。
  22. 支持所有Qt版本和編譯器以及操作系統,包括但不限于win、linux、mac、android、嵌入式linux、樹莓派香橙派、國產os等。

五、相關代碼

#include "frmconfig.h"
#include "frmserver.h"
#include "ui_frmserver.h"
#include "qthelper.h"
#include "apphelper.h"
#include "rtpthread.h"
#include "gb28181server.h"
#include "gb28181helper.h"frmServer::frmServer(QWidget *parent) : QWidget(parent), ui(new Ui::frmServer)
{ui->setupUi(this);this->initForm();this->initConfig();
}frmServer::~frmServer()
{delete ui;
}void frmServer::closeEvent(QCloseEvent *)
{if (AppConfig::ServerStart) {on_btnStart_clicked();}qApp->quit();
}void frmServer::initForm()
{ui->tabPlayback->setEnabled(false);ui->tabDownload->setEnabled(false);ui->widgetControl->setEnabled(false);ui->widget->setFixedWidth(AppData::RightWidth);connect(ui->tabPreview, SIGNAL(selectVideo(QString, QString)), this, SLOT(selectVideo(QString, QString)));ui->treeWidget->setAnimated(false);ui->treeWidget->setIndentation(15);ui->treeWidget->setExpandsOnDoubleClick(false);//立即啟動服務server = NULL;if (AppConfig::ServerStart) {on_btnStart_clicked();}
}void frmServer::initConfig()
{ui->tabWidget->setCurrentIndex(AppConfig::TabIndexMain);connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(saveConfig()));ui->cboxFlag->addItem(AppConfig::FilterType == 0 ? "00000000000000000000" : "0.0.0.0");ui->cboxFlag->lineEdit()->setText(AppConfig::FilterFlag);connect(ui->cboxFlag->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));
}void frmServer::saveConfig()
{AppConfig::TabIndexMain = ui->tabWidget->currentIndex();AppConfig::FilterFlag = ui->cboxFlag->lineEdit()->text();AppConfig::writeConfig();
}void frmServer::receiveEvent(GB28181Event event)
{if (event.alarmMethod != 5) {return;}//設備中的某個通道離線和上線QTreeWidgetItem *itemChannel = AppHelper::findItem(ui->treeWidget, event.deviceId, event.channelId);if (itemChannel) {if (event.alarmInfo == 13) {itemChannel->setDisabled(true);ui->tabPreview->closeVideo(event.deviceId, event.channelId);} else if (event.alarmInfo == 14) {itemChannel->setDisabled(false);}}
}void frmServer::deviceChanged(const QString &deviceId, bool online)
{//關閉對應的畫面if (!online) {ui->tabPreview->closeVideo(deviceId);}QList<GB28181Device> devices = server->getDevices();GB28181Device device = GB28181Helper::getDevice(deviceId, devices);QString text = deviceId + " [" + device.deviceName + "]";QTreeWidgetItem *itemDevice = AppHelper::findItem(ui->treeWidget, deviceId, "");if (itemDevice) {itemDevice->setText(0, text);itemDevice->setDisabled(!online);return;}//不存在則添加頂層節點QTreeWidgetItem *item = new QTreeWidgetItem;item->setText(0, text);item->setData(0, Qt::UserRole, deviceId);ui->treeWidget->insertTopLevelItem(0, item);//添加到下拉框QString flag = (AppConfig::FilterType == 0 ? device.deviceId : device.deviceIp);if (ui->cboxFlag->findText(flag) < 0) {ui->cboxFlag->addItem(flag);}
}void frmServer::channelChanged(const QString &deviceId)
{//每次都清空通道再重新添加QList<GB28181Device> devices = server->getDevices();QTreeWidgetItem *itemDevice = AppHelper::findItem(ui->treeWidget, deviceId, "");if (itemDevice) {qDeleteAll(itemDevice->takeChildren());QStringList ids, names;GB28181Helper::getChannelInfo(deviceId, devices, ids, names);for (int i = 0; i < ids.count(); ++i) {QTreeWidgetItem *itemChannel = new QTreeWidgetItem(itemDevice);itemChannel->setText(0, ids.at(i) + " [" + names.at(i) + "]");itemChannel->setData(0, Qt::UserRole, ids.at(i));}}//通過單次定時器去執行/防止頻繁上線期間卡主界面static QTimer *timer = NULL;if (!timer) {timer = new QTimer(this);connect(timer, SIGNAL(timeout()), this, SLOT(finshed()));timer->setSingleShot(true);timer->setInterval(300);}timer->stop();timer->start();
}void frmServer::finshed()
{//展開所有節點ui->treeWidget->expandAll();//自動調整列寬ui->treeWidget->resizeColumnToContents(0);
}void frmServer::on_btnStart_clicked()
{if (ui->btnStart->text() == "啟動服務") {ui->tabDebug->clear();ui->tabEvent->clear();if (AppConfig::ServerIp == "0.0.0.0") {QtHelper::showMessageBoxError("請先打開系統設置, 選擇網卡地址!");return;}server = new GB28181Server;connect(server, SIGNAL(sendData(QString, int, QString, QString)), ui->tabDebug, SLOT(sendData(QString, int, QString, QString)));connect(server, SIGNAL(receiveData(QString, int, QString, QString)), ui->tabDebug, SLOT(receiveData(QString, int, QString, QString)));connect(server, SIGNAL(receiveInfo(QString, int, QString, QString)), ui->tabDebug, SLOT(receiveInfo(QString, int, QString, QString)));connect(server, SIGNAL(playStart(QString, int, QString, int, int)), ui->tabDebug, SLOT(playStart(QString, int, QString, int, int)));connect(server, SIGNAL(receiveEvent(GB28181Event)), this, SLOT(receiveEvent(GB28181Event)));connect(server, SIGNAL(receiveEvent(GB28181Event)), ui->tabEvent, SLOT(receiveEvent(GB28181Event)));connect(server, SIGNAL(receiveRecord(QList<GB28181Record>)), ui->tabPlayback, SLOT(receiveRecord(QList<GB28181Record>)));connect(server, SIGNAL(receiveRecord(QList<GB28181Record>)), ui->tabDownload, SLOT(receiveRecord(QList<GB28181Record>)));connect(server, SIGNAL(receiveStatus(GB28181Status)), ui->tabPlayback, SLOT(receiveStatus(GB28181Status)));connect(server, SIGNAL(receiveStatus(GB28181Status)), ui->tabDownload, SLOT(receiveStatus(GB28181Status)));connect(server, SIGNAL(deviceChanged(QString, bool)), this, SLOT(deviceChanged(QString, bool)));connect(server, SIGNAL(channelChanged(QString)), this, SLOT(channelChanged(QString)));connect(server, SIGNAL(presetChanged(QStringList, QStringList)), ui->widgetControl, SLOT(presetChanged(QStringList, QStringList)));GB28181ServerPara para;para.serverId = AppConfig::ServerId;para.serverRealm = AppConfig::ServerRealm;para.serverHost = AppConfig::ServerHost;para.serverIp = AppConfig::ServerIp;para.serverPort = AppConfig::ServerPort;para.serverPwd = AppConfig::ServerPwd;para.timeout = AppConfig::Timeout;server->setServerPara(para);server->start((ListenMode)AppConfig::ListenMode);ui->btnStart->setText("停止服務");ui->tabPreview->setServer(server);ui->tabPlayback->setServer(server);ui->tabDownload->setServer(server);ui->widgetControl->setServer(server);ui->tabPlayback->setEnabled(true);ui->tabDownload->setEnabled(true);ui->widgetControl->setEnabled(true);} else {ui->treeWidget->clear();ui->treeWidget->resizeColumnToContents(0);ui->btnStart->setText("啟動服務");ui->tabPreview->closeAll();ui->tabPlayback->closeAll();ui->tabDownload->closeAll();ui->widgetControl->closeAll();ui->tabPlayback->setEnabled(false);ui->tabDownload->setEnabled(false);ui->widgetControl->setEnabled(false);//停頓下處理事件qApp->processEvents();server->stop();server->deleteLater();server = NULL;RtpThread::Port = 6900;}//按鈕觸發的要保存啟動狀態if (sender() == ui->btnStart) {AppConfig::ServerStart = (ui->btnStart->text() == "停止服務");AppConfig::writeConfig();}
}void frmServer::on_btnConfig_clicked()
{static frmConfig *config = new frmConfig;config->show();config->activateWindow();
}void frmServer::selectVideo(const QString &deviceId, const QString &channelId)
{//視頻通道按下自動選中設備通道節點QTreeWidgetItem *itemChannel = AppHelper::selectItem(ui->treeWidget, deviceId, channelId);if (itemChannel) {on_treeWidget_itemClicked(itemChannel, 0);}
}void frmServer::itemClicked(QTreeWidgetItem *item, bool dbClick)
{QString deviceId, channelId;AppHelper::getItemId(item, deviceId, channelId);ui->tabPlayback->setId(deviceId, channelId);ui->tabDownload->setId(deviceId, channelId);ui->widgetControl->setId(deviceId, channelId);if (dbClick) {ui->tabPreview->openVideo(deviceId, channelId);}
}void frmServer::on_treeWidget_itemClicked(QTreeWidgetItem *item, int)
{this->itemClicked(item, false);
}void frmServer::on_treeWidget_itemDoubleClicked(QTreeWidgetItem *item, int)
{this->itemClicked(item, true);
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/77223.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/77223.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/77223.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

0x01、Redis 主從復制的實現原理是什么?

Redis 主從復制概述 Redis 的主從復制是一種機制&#xff0c;允許一個主節點&#xff08;主實例&#xff09;將數據復制到一個或多個從節點&#xff08;從實例&#xff09;。通過這一機制&#xff0c;從節點可以獲取主節點的數據并與之保持同步。 復制流程 開始同步&#xf…

整活 kotlin + springboot3 + sqlite 配置一個 SQLiteCache

要實現一個 SQLiteCache 也是很簡單的只需要創建一個 cacheManager Bean 即可 // 如果配置文件中 spring.cache.sqlite.enable false 則不啟用 Bean("cacheManager") ConditionalOnProperty(name ["spring.cache.sqlite.enable"], havingValue "t…

深入探索如何壓縮 WebAssembly

一、初始體積&#xff1a;默認 Release 構建 我們從最基礎的構建開始&#xff0c;不開啟調試符號&#xff0c;僅使用默認的 release 模式&#xff1a; $ wc -c pkg/wasm_game_of_life_bg.wasm 29410 pkg/wasm_game_of_life_bg.wasm這是我們優化的起點 —— 29,410 字節。 二…

多角度分析Vue3 nextTick() 函數

nextTick() 是 Vue 3 中的一個核心函數&#xff0c;它的作用是延遲執行某些操作&#xff0c;直到下一次 DOM 更新循環結束之后再執行。這個函數常用于在 Vue 更新 DOM 后立即獲取更新后的 DOM 狀態&#xff0c;或者在組件渲染完成后執行某些操作。 官方的解釋是&#xff0c;當…

前端面試-自動化部署

基礎概念 什么是CI/CD&#xff1f;在前端項目中如何應用&#xff1f;自動化部署相比手動部署有哪些優勢&#xff1f;常見的自動化部署工具有哪些&#xff1f;舉例說明它們的區別&#xff08;如Jenkins vs GitHub Actions&#xff09;。如何通過Git Hook實現自動化部署&#xf…

架構生命周期(高軟57)

系列文章目錄 架構生命周期 文章目錄 系列文章目錄前言一、軟件架構是什么&#xff1f;二、軟件架構的內容三、軟件設計階段四、構件總結 前言 本節講明架構設計的架構生命周期概念。 一、軟件架構是什么&#xff1f; 二、軟件架構的內容 三、軟件設計階段 四、構件 總結 就…

GPTNet如何革新創意與效率

引言 人工智能正在以前所未有的速度改變我們的工作與生活方式&#xff0c;從智能寫作到視覺創作&#xff0c;AI工具已成為不可或缺的伙伴。在眾多平臺中&#xff0c;GPTNet以其強大的功能整合和直觀體驗嶄露頭角。它不僅匯集了GPT系列、Claude、Grok、Gemini等頂級對話模型&am…

【計網】SSL/TLS核心原理

序言 在HTTP協議中&#xff0c;信息是明文傳輸的&#xff0c;因此為了通信安全就有了HTTPS(Hyper Text Transfer Protocol over Secure Socket Layer)協議。HTTPS也是一種超文本傳送協議&#xff0c;在HTTP的基礎上加入了SSL/TLS協議&#xff0c;SSL/TLS依靠證書來驗證服務端的…

Web Components 開發與集成

以下是關于 Web Components 開發與集成 的系統知識梳理,涵蓋核心概念、高級特性、集成與優化等內容: 一、Web Components 核心概念 技術作用核心 APICustom Elements定義可復用的自定義 HTML 元素customElements.define()、生命周期鉤子(connectedCallback 等)Shadow DOM封…

day26 學習筆記

文章目錄 前言一、圖像顏色轉換1.HSV顏色空間2.顏色轉換 二、灰度化1.最大值法2.平均值法3.加權均值法 三、二值化1.全局閾值法1.閾值法(THRESH_BINARY)2.反閾值法(THRESH_BINARY_INV)3.截斷閾值法(THRESH_TRUNC)4.低閾值零處理(THRESH_TOZERO)5.超閾值零處理(THRESH_TOZERO_IN…

威鋒VL822-Q7T10GHUB芯片適用于擴展塢顯示器

一、概述 VL822-Q7T是VIA Lab&#xff08;威盛電子旗下專注于USB相關技術研發的子公司&#xff09;精心打造的一款高性能USB 3.1 Gen2集線器控制器芯片。在當今數字化時代&#xff0c;USB接口作為設備連接與數據傳輸的核心通道&#xff0c;其性能與穩定性至關重要。VL822-Q7T憑…

華為OD機試真題——最小的調整次數/特異性雙端隊列(2025A卷:100分)Java/python/JavaScript/C++/C語言/GO六種最佳實現

2025 A卷 100分 題型 本文涵蓋詳細的問題分析、解題思路、代碼實現、代碼詳解、測試用例以及綜合分析&#xff1b; 并提供Java、python、JavaScript、C、C語言、GO六種語言的最佳實現方式&#xff01; 2025華為OD真題目錄全流程解析/備考攻略/經驗分享 華為OD機試真題《最小的調…

關于 Spring Boot 微服務解決方案的對比,并以 Spring Cloud Alibaba 為例,詳細說明其核心組件的使用方式、配置及代碼示例

以下是關于 Spring Boot 微服務解決方案的對比&#xff0c;并以 Spring Cloud Alibaba 為例&#xff0c;詳細說明其核心組件的使用方式、配置及代碼示例&#xff1a; 關于 Spring Cloud Alibaba 致力于提供微服務開發的一站式解決方案! https://sca.aliyun.com/?spm7145af80…

常見的爬蟲算法

1.base64加密 base64是什么 Base64編碼&#xff0c;是由64個字符組成編碼集&#xff1a;26個大寫字母AZ&#xff0c;26個小寫字母az&#xff0c;10個數字0~9&#xff0c;符號“”與符號“/”。Base64編碼的基本思路是將原始數據的三個字節拆分轉化為四個字節&#xff0c;然后…

B樹、紅黑樹、B+樹和平衡二叉樹(如AVL樹)的區別

B樹、紅黑樹、B樹和平衡二叉樹&#xff08;如AVL樹&#xff09;的區別及優缺點的總結&#xff1a; 1. 平衡二叉樹&#xff08;AVL樹&#xff09; 結構&#xff1a;二叉搜索樹&#xff0c;每個節點的左右子樹高度差不超過1。平衡方式&#xff1a;通過旋轉&#xff08;左旋/右旋…

Python Cookbook-6.5 繼承的替代方案——自動托管

任務 你需要從某個類或者類型繼承&#xff0c;但是需要對繼承做一些調整。比如&#xff0c;需要選擇性地隱藏某些基類的方法&#xff0c;而繼承并不能做到這一點。 解決方案 繼承是很方便的&#xff0c;但它并不是萬用良藥。比如&#xff0c;它無法讓你隱藏基類的方法或者屬…

長短期記憶網絡:從理論到創新應用的深度剖析

一、引言 1.1 研究背景 深度學習在人工智能領域的發展可謂突飛猛進&#xff0c;而長短期記憶網絡&#xff08;LSTM&#xff09;在其中占據著至關重要的地位。隨著數據量的不斷增長和對時序數據處理需求的增加&#xff0c;傳統的神經網絡在處理長序列數據時面臨著梯度消失和梯…

vue3.2 + element-plus 實現跟隨input輸入框的彈框,彈框里可以分組或tab形式顯示選項

效果 基礎用法&#xff08;分組選項&#xff09; 高級用法&#xff08;帶Tab欄&#xff09; <!-- 彈窗跟隨通用組件 SmartSelector.vue --> <!-- 彈窗跟隨通用組件 --> <template><div class"smart-selector-container"><el-popove…

C語言中冒泡排序和快速排序的區別

冒泡排序和快速排序都是常見的排序算法&#xff0c;但它們在原理、效率和應用場景等方面存在顯著區別。以下是兩者的詳細對比&#xff1a; 一、算法原理 1. 冒泡排序 原理&#xff1a;通過重復遍歷數組&#xff0c;比較相鄰元素的大小&#xff0c;并在必要時交換它們的位置。…

軟件信息安全性測試如何進行?有哪些注意事項?

隨著信息技術的高速發展&#xff0c;軟件已經成為我們生活和工作中不可或缺的一部分。然而&#xff0c;隨著軟件產品的廣泛普及&#xff0c;軟件信息安全性問題也日益凸顯&#xff0c;因此軟件信息安全性測試必不可少。那么軟件信息安全性測試應如何進行呢?在進行過程中又有哪…