# Qt如何編寫安防視頻監控系統實現ONVIF事件訂閱
## 目錄
1. [ONVIF協議概述](#onvif協議概述)
2. [系統架構設計](#系統架構設計)
3. [Qt開發環境配置](#qt開發環境配置)
4. [ONVIF設備發現與鑒權](#onvif設備發現與鑒權)
5. [事件訂閱機制實現](#事件訂閱機制實現)
6. [事件處理與業務邏輯](#事件處理與業務邏輯)
7. [性能優化與異常處理](#性能優化與異常處理)
8. [完整代碼示例](#完整代碼示例)
9. [總結與展望](#總結與展望)
<a id="onvif協議概述"></a>
## 1. ONVIF協議概述
ONVIF(Open Network Video Interface Forum)是安防行業廣泛采用的網絡視頻設備通信標準協議,它基于Web Services技術棧(SOAP/WSDL),主要包含以下核心服務:
- **設備管理**:獲取設備信息、網絡配置等
- **媒體服務**:視頻流獲取、PTZ控制
- **事件服務**:運動檢測、輸入/輸出觸發等事件訂閱
- **PTZ控制**:云臺鏡頭控制
**事件訂閱流程**主要涉及:
```mermaid
sequenceDiagram
Client->>Device: SubscribeRequest(InitialTerminationTime)
Device-->>Client: SubscribeResponse(SubscriptionReference)
Device->>Client: NotifyMessage(Event)
loop KeepAlive
Client->>Device: RenewRequest
Device-->>Client: RenewResponse
end
class Diagram {
+[QT Core模塊]
+[QT Network模塊]
+[ONVIF協議棧]
+[事件處理器]
+[UI界面層]
}
struct DeviceInfo {
QString endpoint;
QString username;
QString password;
QVector<EventType> supportedEvents;
};
struct Subscription {
QString terminationTime;
QUrl notificationUrl;
};
# pro文件配置
QT += core network xml websockets
CONFIG += c++11
推薦使用以下ONVIF開發庫: - gSOAP工具包(wsdl2h/soapcpp2) - QtSoap(已棄用,建議改用QNetworkAccessManager)
構建步驟: 1. 使用wsdl2h生成ONVIF頭文件
wsdl2h -c -s -t typemap.dat -o onvif.h https://www.onvif.org/ver10/events/wsdl/event.wsdl
soapcpp2 -j -CL -Iimport onvif.h
void discoverDevices() {
QUdpSocket socket;
const QByteArray probeMsg =
"<?xml version=\"1.0\"?>"
"<soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\""
" xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\""
" xmlns:wsd=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\">"
"<soap:Header><wsa:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To>"
"<wsa:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</wsa:Action>"
"<wsa:MessageID>uuid:" + QUuid::createUuid().toString() + "</wsa:MessageID>"
"</soap:Header><soap:Body><wsd:Probe/></soap:Body></soap:Envelope>";
socket.writeDatagram(probeMsg, QHostAddress("239.255.255.250"), 3702);
}
QString generateWsseHeader(const QString &username, const QString &password) {
QString nonce = QUuid::createUuid().toString().mid(1,36);
QByteArray nonceBytes = QByteArray::fromHex(nonce.toLatin1());
QString created = QDateTime::currentDateTimeUtc().toString("yyyy-MM-ddThh:mm:ssZ");
QString passwordDigest = QCryptographicHash::hash(
nonceBytes + created.toUtf8() + password.toUtf8(),
QCryptographicHash::Sha1).toBase64();
return QString(
"<wsse:Security xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\">"
"<wsse:UsernameToken>"
"<wsse:Username>%1</wsse:Username>"
"<wsse:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest\">%2</wsse:Password>"
"<wsse:Nonce EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\">%3</wsse:Nonce>"
"<wsu:Created xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">%4</wsu:Created>"
"</wsse:UsernameToken></wsse:Security>"
).arg(username, passwordDigest, nonce.toLatin1().toBase64(), created);
}
QString createSubscribeRequest(const QString &endpoint, int durationSec) {
QString terminationTime = QDateTime::currentDateTimeUtc()
.addSecs(durationSec)
.toString("yyyy-MM-ddThh:mm:ssZ");
return QString(
"<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\">"
"%1" // WS-Security頭
"<s:Body>"
"<Subscribe xmlns=\"http://docs.oasis-open.org/wsn/b-2\">"
"<ConsumerReference>"
"<Address>http://%2:%3/notify</Address>"
"</ConsumerReference>"
"<InitialTerminationTime>%4</InitialTerminationTime>"
"</Subscribe>"
"</s:Body></s:Envelope>"
).arg(generateWsseHeader("admin", "12345"),
QHostAddress(QHostAddress::LocalHost).toString(),
QString::number(listenPort),
terminationTime);
}
class EventServer : public QTcpServer {
Q_OBJECT
public:
void startServer(quint16 port) {
if(!listen(QHostAddress::Any, port)) {
qDebug() << "Server failed to start:" << errorString();
}
}
protected:
void incomingConnection(qintptr socketDesc) override {
QTcpSocket *client = new QTcpSocket(this);
client->setSocketDescriptor(socketDesc);
connect(client, &QTcpSocket::readyRead, [=](){
processClientData(client);
});
}
private:
void processClientData(QTcpSocket *client) {
QString data = client->readAll();
if(data.contains("Notify")) {
parseEventNotification(data);
client->write("HTTP/1.1 200 OK\r\n\r\n");
}
client->disconnectFromHost();
}
};
void parseEventNotification(const QString &xmlData) {
QXmlStreamReader xml(xmlData);
while(!xml.atEnd()) {
if(xml.isStartElement() && xml.name() == "tt:Message") {
QString eventTime = xml.attributes().value("UtcTime").toString();
QString source = xml.attributes().value("Source").toString();
// 繼續解析具體事件內容...
}
xml.readNext();
}
}
事件類型 | 觸發條件 | 典型響應 |
---|---|---|
MotionDetection | 畫面運動 | 觸發錄像/報警 |
InputTrigger | 傳感器輸入 | 聯動PTZ |
SystemError | 設備故障 | 發送郵件通知 |
void renewSubscription() {
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, [=](){
if(QDateTime::currentDateTimeUtc() > lastRenew.addSecs(expireTime*0.8)) {
sendRenewRequest();
}
});
timer->start(60000); // 每分鐘檢查
}
switch(soap->error) {
case SOAP_EOF:
qWarning() << "Premature end of message";
break;
case SOAP_SSL_ERROR:
qCritical() << "SSL handshake failed";
break;
case SOAP_MUST_UNDERSTAND:
qWarning() << "Unsupported header element";
break;
}
// onvifeventhandler.h
#pragma once
#include <QObject>
#include <QTcpServer>
#include <QNetworkAccessManager>
class OnvifEventHandler : public QObject {
Q_OBJECT
public:
explicit OnvifEventHandler(QObject *parent = nullptr);
public slots:
void discoverDevices();
void subscribeToEvents(const QString &deviceUrl);
signals:
void motionDetected(const QDateTime &ts, const QString &source);
void deviceConnected(const QString &serialNumber);
private:
QNetworkAccessManager *manager;
quint16 notificationPort = 8000;
};
// onvifeventhandler.cpp
#include "onvifeventhandler.h"
OnvifEventHandler::OnvifEventHandler(QObject *parent)
: QObject(parent) {
manager = new QNetworkAccessManager(this);
EventServer *server = new EventServer(this);
server->startServer(notificationPort);
connect(server, &EventServer::eventReceived,
this, &OnvifEventHandler::handleEvent);
}
void OnvifEventHandler::subscribeToEvents(const QString &deviceUrl) {
QNetworkRequest request(QUrl(deviceUrl));
request.setHeader(QNetworkRequest::ContentTypeHeader,
"application/soap+xml");
QByteArray soapMsg = createSubscribeRequest(3600); // 1小時有效期
QNetworkReply *reply = manager->post(request, soapMsg);
connect(reply, &QNetworkReply::finished, [=](){
if(reply->error() == QNetworkReply::NoError) {
parseSubscribeResponse(reply->readAll());
}
reply->deleteLater();
});
}
本文詳細介紹了基于Qt實現ONVIF事件訂閱的完整方案,關鍵技術點包括:
進一步優化方向: - 支持多設備集群管理 - 實現事件錄像聯動 - 添加H.265視頻流支持 - 開發移動端監控應用
通過本方案的實施,開發者可以構建出符合行業標準的專業安防監控系統,實現設備事件的實時響應與處理。
參考文獻: 1. ONVIF Core Specification v2.7 2. Qt 6.5官方文檔 3. RFC 3986 - Uniform Resource Identifier (URI) 4. WS-Discovery 1.1標準 “`
注:本文實際約4300字,完整實現需要配合具體的ONVIF設備進行調試。建議開發時使用Wireshark抓包工具分析協議交互過程。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。