mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2026-02-20 23:30:54 +08:00
初步添加onvif客户端
This commit is contained in:
@@ -71,6 +71,9 @@
|
||||
#include "VideoStack.h"
|
||||
#endif
|
||||
|
||||
#include "Onvif/Onvif.h"
|
||||
#include "Onvif/SoapUtil.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace Json;
|
||||
using namespace toolkit;
|
||||
@@ -714,7 +717,7 @@ void getThreadsLoad(TaskExecutorGetterImp &getter, API_ARGS_MAP_ASYNC) {
|
||||
* Install api interface
|
||||
* All apis support GET and POST methods
|
||||
* POST method parameters support application/json and application/x-www-form-urlencoded methods
|
||||
|
||||
|
||||
* [AUTO-TRANSLATED:62e68c43]
|
||||
*/
|
||||
void installWebApi() {
|
||||
@@ -2337,6 +2340,53 @@ void installWebApi() {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
api_regist("/index/api/searchOnvifDevice",[](API_ARGS_MAP_ASYNC){
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("timeout_ms");
|
||||
|
||||
auto result = std::make_shared<Value>(std::move(val));
|
||||
auto complete_token = std::make_shared<onceToken>(nullptr, [result, headerOut, invoker]() {
|
||||
invoker(200, headerOut, result->toStyledString());
|
||||
});
|
||||
auto lam_search = [complete_token, result](const std::map<string, string> &device_info,
|
||||
const std::string &onvif_url) {
|
||||
Value obj;
|
||||
obj["onvif_url"] = onvif_url;
|
||||
for (auto &pr : device_info) {
|
||||
obj[pr.first] = pr.second;
|
||||
}
|
||||
(*result)["data"].append(std::move(obj));
|
||||
//继续等待扫描
|
||||
return true;
|
||||
};
|
||||
OnvifSearcher::Instance().sendSearchBroadcast(std::move(lam_search), allArgs["timeout_ms"]);
|
||||
});
|
||||
|
||||
api_regist("/index/api/getStreamUrl", [](API_ARGS_MAP_ASYNC) {
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("onvif_url");
|
||||
|
||||
SoapUtil::asyncGetStreamUri(allArgs["onvif_url"],[val, headerOut, allArgs, invoker]
|
||||
(const SoapErr &err, const SoapUtil::GetStreamUriRetryInvoker &retry_invoker,
|
||||
int retry_count, const std::string &url) mutable {
|
||||
if (err && retry_count == 0 && !allArgs["user_name"].empty() /* &&
|
||||
(err.httpCode() == 400 || err.httpCode() == 401)*/) {
|
||||
//第一次失败,且提供了用户密码,且确定是鉴权失败
|
||||
retry_invoker(allArgs["user_name"], allArgs["passwd"]);
|
||||
return;
|
||||
}
|
||||
val["code"] = err ? API::OtherFailed : API::Success;
|
||||
if (err) {
|
||||
val["http_code"] = err.httpCode();
|
||||
val["msg"] = (string) err;
|
||||
} else {
|
||||
val["url"] = url;
|
||||
}
|
||||
invoker(200, headerOut, val.toStyledString());
|
||||
});
|
||||
});
|
||||
|
||||
#if defined(ENABLE_VIDEOSTACK) && defined(ENABLE_X264) && defined(ENABLE_FFMPEG)
|
||||
VideoStackManager::Instance().loadBgImg("novideo.yuv");
|
||||
NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastStreamNoneReader, [](BroadcastStreamNoneReaderArgs) {
|
||||
|
||||
183
src/Onvif/Onvif.cpp
Normal file
183
src/Onvif/Onvif.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "Onvif.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/onceToken.h"
|
||||
#include "pugixml.hpp"
|
||||
#include "SoapUtil.h"
|
||||
#include "Common/config.h"
|
||||
#include "Common/MediaSource.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
using namespace mediakit;
|
||||
|
||||
////////////Rtp代理相关配置///////////
|
||||
namespace Onvif {
|
||||
#define ONVIF_FIELD "onvif."
|
||||
const string kPort = ONVIF_FIELD"port";
|
||||
static onceToken token([]() {
|
||||
mINI::Instance()[kPort] = 3702;
|
||||
});
|
||||
|
||||
} //namespace RtpProxy
|
||||
|
||||
bool OnvifSearcher::onDeviceCB::operator()(std::map<string, string> &device_info, const std::string &onvif_url) {
|
||||
if (expired()) {
|
||||
//超时
|
||||
cb = nullptr;
|
||||
return false;
|
||||
}
|
||||
if (!cb) {
|
||||
return false;
|
||||
}
|
||||
return cb(device_info, onvif_url);
|
||||
}
|
||||
|
||||
bool OnvifSearcher::onDeviceCB::expired() const {
|
||||
return ticker.elapsedTime() > timeout_ms;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
INSTANCE_IMP(OnvifSearcher)
|
||||
|
||||
OnvifSearcher::OnvifSearcher() {
|
||||
_poller = EventPollerPool::Instance().getPoller();
|
||||
}
|
||||
|
||||
void OnvifSearcher::sendSearchBroadcast(onDevice cb, uint64_t timeout_ms) {
|
||||
weak_ptr<OnvifSearcher> weak_self = shared_from_this();
|
||||
_poller->async([weak_self, cb, timeout_ms]() mutable {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (strong_self) {
|
||||
strong_self->sendSearchBroadcast_l(std::move(cb), timeout_ms);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void OnvifSearcher::sendSearchBroadcast_l(onDevice cb, uint64_t timeout_ms) {
|
||||
static struct sockaddr_in s_search_address;
|
||||
static onceToken s_token([]() {
|
||||
s_search_address.sin_family = AF_INET;
|
||||
s_search_address.sin_port = htons(3702);
|
||||
s_search_address.sin_addr.s_addr = inet_addr("239.255.255.250");
|
||||
bzero(&(s_search_address.sin_zero), sizeof s_search_address.sin_zero);
|
||||
});
|
||||
|
||||
GET_CONFIG(uint16_t, onvif_port, Onvif::kPort);
|
||||
if (_sock_list.empty()) {
|
||||
for (auto &network : SockUtil::getInterfaceList()) {
|
||||
auto sock = Socket::createSocket(_poller, false);
|
||||
sock->bindUdpSock(onvif_port, network["ip"]);
|
||||
SockUtil::setBroadcast(sock->rawFD());
|
||||
weak_ptr<OnvifSearcher> weak_self = shared_from_this();
|
||||
sock->setOnRead([weak_self](const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (strong_self) {
|
||||
strong_self->onDeviceResponse(buf, addr, addr_len);
|
||||
}
|
||||
});
|
||||
_sock_list.emplace_back(std::move(sock));
|
||||
}
|
||||
}
|
||||
|
||||
if (!_timer) {
|
||||
weak_ptr<OnvifSearcher> weak_self = shared_from_this();
|
||||
_timer = std::make_shared<Timer>(1, [weak_self]() {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
return false;
|
||||
}
|
||||
for (auto it = strong_self->_cb_map.begin(); it != strong_self->_cb_map.end();) {
|
||||
if (it->second.expired()) {
|
||||
it = strong_self->_cb_map.erase(it);
|
||||
continue;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
return true;
|
||||
}, _poller);
|
||||
}
|
||||
|
||||
auto uuid = SoapUtil::createUuidString();
|
||||
auto xml = SoapUtil::createDiscoveryString(uuid);
|
||||
auto &ref = _cb_map[uuid];
|
||||
ref.cb = std::move(cb);
|
||||
ref.timeout_ms = timeout_ms;
|
||||
|
||||
for (auto &sock : _sock_list) {
|
||||
sock->send(xml, (struct sockaddr *) &s_search_address, sizeof(s_search_address));
|
||||
}
|
||||
}
|
||||
|
||||
std::map<string, string> getDeviceInfo(const string &scopes) {
|
||||
std::map<string, string> keys = {{"onvif://www.onvif.org/location", "location"},
|
||||
{"onvif://www.onvif.org/name", "name"},
|
||||
{"onvif://www.onvif.org/hardware", "hardware"}};
|
||||
std::map<string, string> ret;
|
||||
auto vec = split(scopes, " ");
|
||||
for (auto &item : vec) {
|
||||
string key;
|
||||
for (auto &pr : keys) {
|
||||
if (start_with(item, pr.first)) {
|
||||
key = pr.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!key.empty()) {
|
||||
auto pos = item.rfind('/');
|
||||
ret.emplace(key, item.substr(pos + 1));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void OnvifSearcher::onDeviceResponse(const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) {
|
||||
try {
|
||||
SoapObject root;
|
||||
root.load(buf->data(), buf->size());
|
||||
auto uuid = root["Envelope/Header/RelatesTo"];
|
||||
auto device_service = root["Envelope/Body/ProbeMatches/ProbeMatch/XAddrs"];
|
||||
auto scopes = root["Envelope/Body/ProbeMatches/ProbeMatch/Scopes"];
|
||||
auto map = getDeviceInfo(scopes.as_xml().text().as_string());
|
||||
onGotDevice(uuid.as_xml().text().as_string(), map, device_service.as_xml().text().as_string());
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << ex.what();
|
||||
}
|
||||
}
|
||||
|
||||
static string getIpv4Url(const std::string &onvif_url) {
|
||||
auto urls = split(onvif_url, " ");
|
||||
if (urls.size() > 1) {
|
||||
for (auto url : urls) {
|
||||
MediaInfo info(url);
|
||||
if (isIP(info.host.data())) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
}
|
||||
return onvif_url;
|
||||
}
|
||||
|
||||
void OnvifSearcher::onGotDevice(const std::string &uuid, std::map<string, string> &device_info,
|
||||
const std::string &onvif_url) {
|
||||
auto it = _cb_map.find(uuid);
|
||||
if (it == _cb_map.end()) {
|
||||
WarnL << uuid << " " << onvif_url << " " << device_info["location"] << " " << device_info["name"] << " "
|
||||
<< device_info["hardware"];
|
||||
return;
|
||||
}
|
||||
auto flag = it->second(device_info, getIpv4Url(onvif_url));
|
||||
if (!flag) {
|
||||
_cb_map.erase(it);
|
||||
}
|
||||
}
|
||||
50
src/Onvif/Onvif.h
Normal file
50
src/Onvif/Onvif.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef ZLMEDIAKIT_ONVIF_H
|
||||
#define ZLMEDIAKIT_ONVIF_H
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "Network/Socket.h"
|
||||
#include "Network/Buffer.h"
|
||||
|
||||
class OnvifSearcher : public std::enable_shared_from_this<OnvifSearcher> {
|
||||
public:
|
||||
//返回false代表不再监听事件
|
||||
using onDevice = std::function<bool(const std::map<std::string, std::string> &device_info, const std::string &onvif_url)>;
|
||||
OnvifSearcher();
|
||||
|
||||
static OnvifSearcher &Instance();
|
||||
void sendSearchBroadcast(onDevice cb = nullptr, uint64_t timeout_ms = 10 * 1000);
|
||||
|
||||
private:
|
||||
void onDeviceResponse(const toolkit::Buffer::Ptr &buf, struct sockaddr *addr, int addr_len);
|
||||
void onGotDevice(const std::string &uuid, std::map<std::string, std::string> &device_info, const std::string &onvif_url);
|
||||
void sendSearchBroadcast_l(onDevice cb, uint64_t timeout_ms);
|
||||
|
||||
private:
|
||||
struct onDeviceCB{
|
||||
onDevice cb;
|
||||
toolkit::Ticker ticker;
|
||||
uint64_t timeout_ms;
|
||||
|
||||
bool expired() const;
|
||||
bool operator()(std::map<std::string, std::string> &device_info, const std::string &onvif_url);
|
||||
};
|
||||
|
||||
private:
|
||||
toolkit::EventPoller::Ptr _poller;
|
||||
toolkit::Timer::Ptr _timer;
|
||||
std::vector<toolkit::Socket::Ptr> _sock_list;
|
||||
std::unordered_map<std::string/*uuid*/, onDeviceCB> _cb_map;
|
||||
};
|
||||
|
||||
#endif //ZLMEDIAKIT_ONVIF_H
|
||||
491
src/Onvif/SoapUtil.cpp
Normal file
491
src/Onvif/SoapUtil.cpp
Normal file
@@ -0,0 +1,491 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <iomanip>
|
||||
#include <random>
|
||||
#include "SoapUtil.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/SHA1.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Util/base64.h"
|
||||
#include "Util/onceToken.h"
|
||||
#include "Http/HttpRequester.h"
|
||||
#include "Rtsp/Rtsp.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
using namespace mediakit;
|
||||
|
||||
static pugi::xml_node find_node(const pugi::xml_node &parent, const std::string &end_str) {
|
||||
auto ret = parent.find_child([&](const pugi::xml_node &node) {
|
||||
auto len = strlen(node.name());
|
||||
if (len < end_str.size()) {
|
||||
return false;
|
||||
}
|
||||
if (end_str == node.name()) {
|
||||
return true;
|
||||
}
|
||||
if (*(node.name() + len - end_str.size() - 1) != ':') {
|
||||
return false;
|
||||
}
|
||||
return strcasecmp(node.name() + len - end_str.size(), end_str.data()) == 0;
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
SoapObject::SoapObject() {
|
||||
_root = std::make_shared<pugi::xml_node>();
|
||||
}
|
||||
|
||||
SoapObject::SoapObject(std::shared_ptr<pugi::xml_node> node) {
|
||||
_root = std::move(node);
|
||||
}
|
||||
|
||||
void SoapObject::load(const char *data, size_t len) {
|
||||
auto doc = std::make_shared<pugi::xml_document>();
|
||||
auto result = doc->load_string(data, len);
|
||||
if (!result) {
|
||||
throw std::invalid_argument(string("解析xml失败:") + result.description());
|
||||
}
|
||||
_root = std::move(doc);
|
||||
}
|
||||
|
||||
SoapObject::operator bool() const {
|
||||
return !_root->empty();
|
||||
}
|
||||
|
||||
SoapObject SoapObject::operator[](const string &path) const{
|
||||
auto hit = *_root;
|
||||
auto node_name = split(path, "/");
|
||||
for (auto &node : node_name) {
|
||||
hit = find_node(hit, node);
|
||||
if (hit.empty()) {
|
||||
return SoapObject();
|
||||
}
|
||||
}
|
||||
auto ref = _root;
|
||||
shared_ptr<pugi::xml_node> node(new pugi::xml_node(std::move(hit)), [ref](pugi::xml_node *ptr) {
|
||||
delete ptr;
|
||||
});
|
||||
return SoapObject(std::move(node));
|
||||
}
|
||||
|
||||
SoapObject SoapObject::operator[](size_t index) const {
|
||||
for (auto &hit : *_root) {
|
||||
if (index-- == 0) {
|
||||
auto ref = _root;
|
||||
shared_ptr<pugi::xml_node> node(new pugi::xml_node(hit), [ref](pugi::xml_node *ptr) {
|
||||
delete ptr;
|
||||
});
|
||||
return SoapObject(node);
|
||||
}
|
||||
}
|
||||
return SoapObject();
|
||||
}
|
||||
|
||||
std::string SoapObject::as_string() const {
|
||||
if (!(bool) (*this)) {
|
||||
return "";
|
||||
}
|
||||
xml_string_writer writer;
|
||||
_root->print(writer);
|
||||
return writer.result;
|
||||
}
|
||||
|
||||
pugi::xml_node SoapObject::as_xml() const {
|
||||
return *_root;
|
||||
}
|
||||
|
||||
SoapObject::SoapObject(const pugi::xml_node &node, const SoapObject &ref) {
|
||||
auto root = ref._root;
|
||||
_root.reset(new pugi::xml_node(node.internal_object()), [root](pugi::xml_node *ptr) {
|
||||
delete ptr;
|
||||
});
|
||||
}
|
||||
|
||||
std::string SoapUtil::createDiscoveryString(const string &uuid_in) {
|
||||
auto uuid = uuid_in;
|
||||
if (uuid.empty()) {
|
||||
uuid = createUuidString();
|
||||
}
|
||||
static constexpr char str_fmt[] = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
"<e:Envelope xmlns:e=\"http://www.w3.org/2003/05/soap-envelope\"\n"
|
||||
" xmlns:w=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\"\n"
|
||||
" xmlns:d=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\"\n"
|
||||
" xmlns:dn=\"http://www.onvif.org/ver10/network/wsdl\">\n"
|
||||
" <e:Header>\n"
|
||||
" <w:MessageID>%s</w:MessageID>\n"
|
||||
" <w:To e:mustUnderstand=\"true\">urn:schemas-xmlsoap-org:ws:2005:04:discovery</w:To>\n"
|
||||
" <w:Action a:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</w:Action>\n"
|
||||
" </e:Header>\n"
|
||||
" <e:Body>\n"
|
||||
" <d:Probe>\n"
|
||||
" <d:Types>dn:NetworkVideoTransmitter</d:Types>\n"
|
||||
" </d:Probe>\n"
|
||||
" </e:Body>\n"
|
||||
"</e:Envelope>";
|
||||
return print_to_string(str_fmt, uuid.data());
|
||||
}
|
||||
|
||||
static std::string creatString4() {
|
||||
std::mt19937 rng(std::random_device{}());
|
||||
string ret = StrPrinter << std::hex << std::uppercase << std::setfill('0') << ((1 + rng()) & 0xFFFF);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string SoapUtil::createUuidString() {
|
||||
auto ret = std::string("uuid:") +
|
||||
creatString4() + creatString4() + '-' +
|
||||
creatString4() + '-' +
|
||||
creatString4() + '-' +
|
||||
creatString4() + '-' +
|
||||
creatString4() + creatString4() + creatString4();
|
||||
TraceL << ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static tuple<string/*passdigest*/, string/*nonce*/, string/*timestamp*/>
|
||||
makePasswordDigest(const string &user_name, const string &passwd) {
|
||||
std::mt19937 rng(std::random_device{}());
|
||||
string nonce;
|
||||
nonce.resize(16);
|
||||
for (auto &ch : nonce) {
|
||||
ch = rng() & 0xFF;
|
||||
}
|
||||
auto timestamp = getTimeStr("%Y-%m-%dT%H:%M:%S%z");
|
||||
auto passdigest = SHA1::encode_bin(nonce + timestamp + passwd);
|
||||
return std::make_tuple(encodeBase64(passdigest), encodeBase64(nonce), timestamp);
|
||||
}
|
||||
|
||||
static std::string
|
||||
createSoapSecurity(const string &user_name, const string &passdigest, const string &nonce, const string ×tamp) {
|
||||
static constexpr char str_fmt[] =
|
||||
R"(<Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
|
||||
<UsernameToken>
|
||||
<Username>%s</Username>
|
||||
<Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">%s</Password>
|
||||
<Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">%s</Nonce>
|
||||
<Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">%s</Created>
|
||||
</UsernameToken></Security>)";
|
||||
return print_to_string(str_fmt, user_name.data(), passdigest.data(), nonce.data(), timestamp.data());
|
||||
}
|
||||
|
||||
std::string SoapUtil::createSoapRequest(const string &body, const string &user_name, const string &passwd) {
|
||||
string header = R"(<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing"><s:Header>)";
|
||||
if (!user_name.empty() && !passwd.empty()) {
|
||||
auto req = makePasswordDigest(user_name, passwd);
|
||||
header += createSoapSecurity(user_name, std::get<0>(req), std::get<1>(req), std::get<2>(req));
|
||||
}
|
||||
header += R"(</s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">)";
|
||||
header += body;
|
||||
header += R"(</s:Body></s:Envelope>)";
|
||||
return header;
|
||||
}
|
||||
|
||||
SoapErr::SoapErr(std::string url,
|
||||
std::string action,
|
||||
SockException ex,
|
||||
const mediakit::Parser &parser,
|
||||
std::string err) {
|
||||
_url = std::move(url);
|
||||
_action = std::move(action);
|
||||
_net_err = std::move(ex);
|
||||
_http_code = atoi(parser.status().data());
|
||||
_http_msg = parser.statusStr();
|
||||
_other_err = std::move(err);
|
||||
}
|
||||
|
||||
SoapErr::operator std::string() const {
|
||||
_StrPrinter printer;
|
||||
printer << "request onvif service failed, url:" << _url << ", action:" << _action << ", ";
|
||||
if (_net_err) {
|
||||
return printer << "network err:" << _net_err.what() << endl;
|
||||
}
|
||||
if (_http_code != 200) {
|
||||
return printer << "http bad status:" << _http_code << " " << _http_msg << endl;
|
||||
}
|
||||
if (!_other_err.empty()) {
|
||||
return printer << _other_err << endl;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
SoapErr::operator bool() const {
|
||||
return _net_err || _http_code != 200 || !_other_err.empty();
|
||||
}
|
||||
|
||||
bool SoapErr::empty() const {
|
||||
return !*this;
|
||||
}
|
||||
|
||||
int SoapErr::httpCode() const {
|
||||
return _http_code;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& sout, const SoapErr &err) {
|
||||
sout << (string)err;
|
||||
return sout;
|
||||
}
|
||||
|
||||
void SoapUtil::sendSoapRequest(const string &url, const string &SOAPAction, const string &body, const SoapRequestCB &func,
|
||||
float timeout_sec) {
|
||||
HttpRequester::Ptr requester(new HttpRequester);
|
||||
requester->setMethod("POST");
|
||||
requester->setBody(body);
|
||||
requester->addHeader("Content-Type", "text/xml; charset=utf-8; action=" + SOAPAction);
|
||||
requester->addHeader("Accept", "text/xml; charset=utf-8");
|
||||
requester->addHeader("SOAPAction", SOAPAction);
|
||||
std::shared_ptr<Ticker> ticker(new Ticker);
|
||||
requester->startRequester(url, [url, SOAPAction, func, requester, ticker](const SockException &ex,
|
||||
const Parser &parser) mutable {
|
||||
|
||||
onceToken token(nullptr, [&]() mutable {
|
||||
requester.reset();
|
||||
});
|
||||
auto invoker = [&](const SoapObject &node, const SoapErr &err) {
|
||||
if (err) {
|
||||
WarnL << err;
|
||||
}
|
||||
if (func) {
|
||||
func(node, err);
|
||||
}
|
||||
};
|
||||
|
||||
if (ex) {
|
||||
invoker(SoapObject(), SoapErr(url, SOAPAction, ex, parser));
|
||||
return;
|
||||
}
|
||||
if (parser.status() != "200") {
|
||||
invoker(SoapObject(), SoapErr(url, SOAPAction, ex, parser));
|
||||
return;
|
||||
}
|
||||
SoapObject root;
|
||||
try {
|
||||
root.load(parser.content().data(), parser.content().size());
|
||||
} catch (std::exception &e) {
|
||||
auto err = StrPrinter << "[parse xml failed]:" << e.what() << endl;
|
||||
invoker(SoapObject(), SoapErr(url, SOAPAction, ex, parser, err));
|
||||
return;
|
||||
}
|
||||
auto body = root["Envelope/Body"];
|
||||
if (!body) {
|
||||
auto err = StrPrinter << "[invalid onvif soap response]:" << ex.what() << endl;
|
||||
invoker(SoapObject(), SoapErr(url, SOAPAction, ex, parser, err));
|
||||
return;
|
||||
}
|
||||
auto fault = body["Fault"];
|
||||
if (fault) {
|
||||
auto err = StrPrinter << "[onvif soap fault]:" << fault["Reason/Text"].as_xml().text().as_string() << endl;;
|
||||
invoker(SoapObject(), SoapErr(url, SOAPAction, ex, parser, err));
|
||||
return;
|
||||
}
|
||||
//成功
|
||||
invoker(body, SoapErr(url, SOAPAction, ex, parser));
|
||||
}, timeout_sec);
|
||||
}
|
||||
|
||||
void SoapUtil::sendGetDeviceInformation(const std::string &device_service, const std::string &user_name,
|
||||
const std::string &pwd, SoapRequestCB cb) {
|
||||
static constexpr char action_url[] = R"("http://www.onvif.org/ver10/device/wsdl/GetDeviceInformation")";
|
||||
static constexpr char str_fmt[] = R"(<GetDeviceInformation xmlns="http://www.onvif.org/ver10/device/wsdl"/>)";
|
||||
auto body = SoapUtil::createSoapRequest(str_fmt, user_name, pwd);
|
||||
SoapUtil::sendSoapRequest(device_service, action_url, body, std::move(cb));
|
||||
}
|
||||
|
||||
void SoapUtil::sendGetProfiles(bool is_media2, const string &media_url, const string &user_name, const string &pwd,
|
||||
const onGetProfilesResponse &cb) {
|
||||
auto invoker = [is_media2, cb](const SoapObject &res, const SoapErr &err) {
|
||||
if (err) {
|
||||
cb(err, vector<onGetProfilesResponseTuple>());
|
||||
return;
|
||||
}
|
||||
multimap<int, onGetProfilesResponseTuple> sorted;
|
||||
for (auto &xml_node : res["GetProfilesResponse"].as_xml()) {
|
||||
SoapObject obj(xml_node, res);
|
||||
auto profile_name = obj["Name"].as_xml().text().as_string();
|
||||
auto token = xml_node.attribute("token");
|
||||
if (token) {
|
||||
profile_name = token.value();
|
||||
}
|
||||
auto codec = obj[string(is_media2 ? "Configurations/VideoEncoder/Encoding"
|
||||
: "VideoEncoderConfiguration/Encoding")].as_xml().text().as_string();
|
||||
auto width = obj[string(is_media2 ? "Configurations/VideoEncoder/Resolution/Width"
|
||||
: "VideoEncoderConfiguration/Resolution/Width")].as_xml().text().as_int();
|
||||
auto height = obj[string(is_media2 ? "Configurations/VideoEncoder/Resolution/Height"
|
||||
: "VideoEncoderConfiguration/Resolution/Height")].as_xml().text().as_int();
|
||||
sorted.emplace(width * height, std::make_tuple(profile_name, codec, width, height));
|
||||
}
|
||||
vector<onGetProfilesResponseTuple> result;
|
||||
for (auto &pr : sorted) {
|
||||
result.insert(result.begin(), pr.second);
|
||||
}
|
||||
cb(err, result);
|
||||
};
|
||||
static constexpr char action_url[] = R"("http://www.onvif.org/ver10/media/wsdl/GetProfiles")";
|
||||
static constexpr char str_fmt[] = R"(<GetProfiles xmlns="http://www.onvif.org/ver10/media/wsdl"/>)";
|
||||
static constexpr char action_url2[] = R"("http://www.onvif.org/ver20/media/wsdl/GetProfiles")";
|
||||
static constexpr char str_fmt2[] = R"(<GetProfiles xmlns="http://www.onvif.org/ver20/media/wsdl"><Type>All</Type></GetProfiles>)";
|
||||
|
||||
auto body = SoapUtil::createSoapRequest(is_media2 ? str_fmt2 : str_fmt, user_name, pwd);
|
||||
SoapUtil::sendSoapRequest(media_url, is_media2 ? action_url2 : action_url, body, invoker);
|
||||
}
|
||||
|
||||
void SoapUtil::sendGetServices(const std::string &device_service, const std::initializer_list<std::string> &ns_filter,
|
||||
const std::string &user_name, const std::string &pwd, const onGetServicesResponse &cb) {
|
||||
static constexpr char action_url[] = R"("http://www.onvif.org/ver10/device/wsdl/GetServices")";
|
||||
static constexpr char str_fmt[] = R"(<GetServices xmlns="http://www.onvif.org/ver10/device/wsdl">
|
||||
<IncludeCapability>true</IncludeCapability>
|
||||
</GetServices>)";
|
||||
set<string, StrCaseCompare> filter = ns_filter;
|
||||
auto body = SoapUtil::createSoapRequest(str_fmt, user_name, pwd);
|
||||
SoapUtil::sendSoapRequest(device_service, action_url, body,[filter, cb](const SoapObject &node, const SoapErr &err) {
|
||||
onGetServicesResponseMap mp;
|
||||
if (err) {
|
||||
cb(err, mp);
|
||||
return;
|
||||
}
|
||||
auto res = node["GetServicesResponse"];
|
||||
for (auto &xml_node : res.as_xml()) {
|
||||
SoapObject obj(xml_node, node);
|
||||
string ns = obj["Namespace"].as_xml().text().as_string();
|
||||
string xaddr = obj["XAddr"].as_xml().text().as_string();
|
||||
if (filter.find(ns) != filter.end()) {
|
||||
mp.emplace(ns, xaddr);
|
||||
}
|
||||
}
|
||||
cb(err, mp);
|
||||
});
|
||||
}
|
||||
|
||||
static string getRtspUrlWithAuth(const std::string &user_name, const std::string &pwd, const string &url) {
|
||||
RtspUrl parser;
|
||||
parser.parse(url);
|
||||
if (user_name.empty() || pwd.empty() || !parser._user.empty() || !parser._passwd.empty()) {
|
||||
return url;
|
||||
}
|
||||
auto ret = url;
|
||||
auto pos = ret.find("://");
|
||||
if (pos == string::npos) {
|
||||
return ret;
|
||||
}
|
||||
ret.insert(pos + 3, (user_name + ":" + pwd + "@").data());
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SoapUtil::sendGetStreamUri(bool is_media2, const string &media_url, const string &profile,
|
||||
const std::string &user_name, const std::string &pwd,
|
||||
const onGetStreamUriResponse &cb) {
|
||||
if (!is_media2) {
|
||||
static constexpr char action_url[] = R"("http://www.onvif.org/ver10/media/wsdl/GetStreamUri")";
|
||||
static constexpr char str_fmt[] = R"(<GetStreamUri xmlns="http://www.onvif.org/ver10/media/wsdl">
|
||||
<StreamSetup>
|
||||
<Stream xmlns="http://www.onvif.org/ver10/schema">%s</Stream>
|
||||
<Transport xmlns="http://www.onvif.org/ver10/schema">
|
||||
<Protocol>%s</Protocol>
|
||||
</Transport>
|
||||
</StreamSetup>
|
||||
<ProfileToken>%s</ProfileToken>
|
||||
</GetStreamUri>)";
|
||||
|
||||
auto body = SoapUtil::createSoapRequest(print_to_string(str_fmt, "RTP-Unicast", "RTSP", profile.data()),
|
||||
user_name, pwd);
|
||||
SoapUtil::sendSoapRequest(media_url, action_url, body, [cb](const SoapObject &node, const SoapErr &err) {
|
||||
if (err) {
|
||||
cb(err, "");
|
||||
return;
|
||||
}
|
||||
auto res = node["GetStreamUriResponse/MediaUri/Uri"];
|
||||
cb(err, res.as_xml().text().as_string());
|
||||
});
|
||||
} else {
|
||||
static constexpr char action_url[] = R"("http://www.onvif.org/ver20/media/wsdl/GetStreamUri")";
|
||||
static constexpr char str_fmt[] = R"(<GetStreamUri xmlns="http://www.onvif.org/ver20/media/wsdl">
|
||||
<Protocol>%s</Protocol>
|
||||
<ProfileToken>%s</ProfileToken>
|
||||
</GetStreamUri>)";
|
||||
auto body = SoapUtil::createSoapRequest(print_to_string(str_fmt, "RTSP", profile.data()), user_name, pwd);
|
||||
SoapUtil::sendSoapRequest(media_url, action_url, body, [cb](const SoapObject &node, const SoapErr &err) {
|
||||
if (err) {
|
||||
cb(err, "");
|
||||
return;
|
||||
}
|
||||
auto res = node["GetStreamUriResponse/Uri"];
|
||||
cb(err, res.as_xml().text().as_string());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static void asyncGetStreamUri_l(const std::string &onvif_url, const std::string &user_name,
|
||||
const std::string &pwd, const SoapUtil::AsyncGetStreamUriCB &cb,
|
||||
std::shared_ptr<int> retry_count) {
|
||||
|
||||
static constexpr char media_ns[] = "http://www.onvif.org/ver10/media/wsdl";
|
||||
static constexpr char media2_ns[] = "http://www.onvif.org/ver20/media/wsdl";
|
||||
|
||||
auto invoker = [=](const std::string &user_name, const std::string &pwd) {
|
||||
++*retry_count;
|
||||
asyncGetStreamUri_l(onvif_url, user_name, pwd, cb, retry_count);
|
||||
};
|
||||
SoapUtil::sendGetDeviceInformation(onvif_url, user_name, pwd, [=](const SoapObject &body, const SoapErr &err) {
|
||||
if (err) {
|
||||
cb(err, invoker, *retry_count, "");
|
||||
return;
|
||||
}
|
||||
|
||||
SoapUtil::sendGetServices(onvif_url, {media_ns, media2_ns}, user_name, pwd,
|
||||
[=](const SoapErr &err, SoapUtil::onGetServicesResponseMap &mp) {
|
||||
auto media1_url = mp[media_ns];
|
||||
auto media2_url = mp[media2_ns];
|
||||
auto media_url = media2_url.empty() ? media1_url : media2_url;
|
||||
bool is_media2 = media2_url.empty() ? false : true;
|
||||
if (err) {
|
||||
cb(err, invoker, *retry_count, "");
|
||||
return;
|
||||
}
|
||||
if (media_url.empty()) {
|
||||
static constexpr char action_url[] = R"("http://www.onvif.org/ver10/device/wsdl/GetServices")";
|
||||
SoapErr err(onvif_url, action_url, SockException(), mediakit::Parser(),
|
||||
"get media service failed");
|
||||
cb(err, invoker, *retry_count, "");
|
||||
return;
|
||||
}
|
||||
SoapUtil::sendGetProfiles(is_media2, media_url, user_name, pwd,
|
||||
[=] (const SoapErr &err, const vector<SoapUtil::onGetProfilesResponseTuple> &profile) {
|
||||
if (err) {
|
||||
cb(err, invoker, *retry_count, "");
|
||||
return;
|
||||
}
|
||||
if (profile.empty()) {
|
||||
static constexpr char action_url[] = R"("http://www.onvif.org/ver10/media/wsdl/GetProfiles")";
|
||||
static constexpr char action_url2[] = R"("http://www.onvif.org/ver20/media/wsdl/GetProfiles")";
|
||||
SoapErr err(onvif_url, is_media2 ? action_url2 : action_url, SockException(), mediakit::Parser(),
|
||||
"get media profile failed");
|
||||
cb(err, invoker, *retry_count, "");
|
||||
return;
|
||||
}
|
||||
auto profile_name = get<0>(profile[0]);
|
||||
SoapUtil::sendGetStreamUri(is_media2, media_url, profile_name, user_name, pwd,
|
||||
[=](const SoapErr &err, const string &uri) {
|
||||
if (err) {
|
||||
cb(err, invoker, *retry_count, "");
|
||||
return;
|
||||
}
|
||||
cb(err, invoker, *retry_count, getRtspUrlWithAuth(user_name, pwd, uri));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void SoapUtil::asyncGetStreamUri(const std::string &onvif_url, const SoapUtil::AsyncGetStreamUriCB &cb) {
|
||||
asyncGetStreamUri_l(onvif_url, "", "", cb, std::make_shared<int>(0));
|
||||
}
|
||||
177
src/Onvif/SoapUtil.h
Normal file
177
src/Onvif/SoapUtil.h
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef ZLMEDIAKIT_SOAPUTIL_H
|
||||
#define ZLMEDIAKIT_SOAPUTIL_H
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include "Common/Parser.h"
|
||||
#include "Network/Socket.h"
|
||||
#include "pugixml.hpp"
|
||||
|
||||
struct xml_string_writer : pugi::xml_writer {
|
||||
std::string result;
|
||||
virtual void write(const void *data, size_t size) {
|
||||
result.append(static_cast<const char *>(data), size);
|
||||
}
|
||||
};
|
||||
|
||||
template<size_t sz, typename ...ARGS>
|
||||
std::string print_to_string(const char (&str_fmt)[sz], ARGS &&...args) {
|
||||
std::string ret;
|
||||
//鉴权%s长度再减去\0长度
|
||||
ret.resize(2 * sizeof(str_fmt));
|
||||
//string的真实内存大小必定比size大一个字节(用于存放\0)
|
||||
auto size = snprintf((char *) ret.data(), ret.size() + 1, str_fmt, std::forward<ARGS>(args)...);
|
||||
ret.resize(size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
class SoapObject;
|
||||
|
||||
class SoapErr {
|
||||
public:
|
||||
SoapErr(std::string url,
|
||||
std::string action,
|
||||
toolkit::SockException ex,
|
||||
const mediakit::Parser &parser,
|
||||
std::string err = "");
|
||||
|
||||
operator std::string() const;
|
||||
operator bool() const;
|
||||
bool empty() const;
|
||||
int httpCode() const;
|
||||
|
||||
private:
|
||||
std::string _url;
|
||||
std::string _action;
|
||||
toolkit::SockException _net_err;
|
||||
int _http_code = 200;
|
||||
std::string _http_msg;
|
||||
std::string _other_err;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& sout, const SoapErr &err);
|
||||
|
||||
class SoapUtil {
|
||||
public:
|
||||
static std::string createDiscoveryString(const std::string &uuid = "");
|
||||
static std::string createUuidString();
|
||||
static std::string createSoapRequest(const std::string &body, const std::string &user_name = "", const std::string &passwd = "");
|
||||
|
||||
using SoapRequestCB = std::function<void(const SoapObject &node, const SoapErr &err)>;
|
||||
static void sendSoapRequest(const std::string &url, const std::string &action, const std::string &body,
|
||||
const SoapRequestCB &func = nullptr, float timeout_sec = 10);
|
||||
|
||||
|
||||
using onGetProfilesResponseTuple = std::tuple<std::string/*profile_name*/, std::string /*codec*/, int /*width*/, int /*height*/>;
|
||||
using onGetProfilesResponse = std::function<void(const SoapErr &err,
|
||||
const std::vector<onGetProfilesResponseTuple> &profile)>;
|
||||
|
||||
/**
|
||||
* 获取profile
|
||||
* @param is_media2 是否为media2访问方式
|
||||
* @param media_url media服务访问地址
|
||||
* @param user_name 用户名
|
||||
* @param pwd 密码
|
||||
* @param cb 回调, 高分辨率的profile在前
|
||||
*/
|
||||
static void sendGetProfiles(bool is_media2, const std::string &media_url, const std::string &user_name,
|
||||
const std::string &pwd, const onGetProfilesResponse &cb);
|
||||
|
||||
/**
|
||||
* 获取设备信息
|
||||
* @param device_service device_service服务访问地址
|
||||
* @param user_name 用户名
|
||||
* @param pwd 密码
|
||||
* @param cb 回调
|
||||
*/
|
||||
static void sendGetDeviceInformation(const std::string &device_service, const std::string &user_name,
|
||||
const std::string &pwd, SoapRequestCB cb);
|
||||
|
||||
|
||||
using onGetServicesResponseMap = std::map<std::string/*ns*/, std::string/*xaddr*/, mediakit::StrCaseCompare>;
|
||||
using onGetServicesResponse = std::function<void(const SoapErr &err, onGetServicesResponseMap &val)>;
|
||||
|
||||
/**
|
||||
* 获取服务url地址
|
||||
* @param device_service device_service服务访问地址
|
||||
* @param ns_filter 刷选的服务的命名空间
|
||||
* @param user_name 用户名
|
||||
* @param pwd 密码
|
||||
* @param cb 回调
|
||||
*/
|
||||
static void sendGetServices(const std::string &device_service, const std::initializer_list<std::string> &ns_filter,
|
||||
const std::string &user_name, const std::string &pwd, const onGetServicesResponse &cb);
|
||||
|
||||
|
||||
using onGetStreamUriResponse = std::function<void(const SoapErr &err, const std::string &uri)>;
|
||||
|
||||
/**
|
||||
* 获取rtsp播放url
|
||||
* @param is_media2 是否为media2方式
|
||||
* @param media_url media或media2服务访问地址
|
||||
* @param profile sendGetProfiles接口获取的分辨率方案
|
||||
* @param user_name 用户名
|
||||
* @param pwd 密码
|
||||
* @param cb 回调
|
||||
*/
|
||||
static void sendGetStreamUri(bool is_media2, const std::string &media_url, const std::string &profile,
|
||||
const std::string &user_name, const std::string &pwd,
|
||||
const onGetStreamUriResponse &cb);
|
||||
|
||||
using GetStreamUriRetryInvoker = std::function<void(const std::string &user_name, const std::string &pwd)>;
|
||||
using AsyncGetStreamUriCB = std::function<void(const SoapErr &err, const GetStreamUriRetryInvoker &invoker,
|
||||
int retry_count, const std::string &url)>;
|
||||
|
||||
/**
|
||||
* 异步获取播放url
|
||||
* @param onvif_url 设备搜索时返回的url
|
||||
* @param cb 回调
|
||||
*/
|
||||
static void asyncGetStreamUri(const std::string &onvif_url, const AsyncGetStreamUriCB &cb);
|
||||
|
||||
private:
|
||||
SoapUtil() = delete;
|
||||
~SoapUtil() = delete;
|
||||
};
|
||||
|
||||
class SoapObject {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<SoapObject>;
|
||||
|
||||
SoapObject(const pugi::xml_node &node, const SoapObject &ref);
|
||||
SoapObject();
|
||||
operator bool () const;
|
||||
void load(const char *data, size_t len);
|
||||
SoapObject operator[](const std::string &path) const;
|
||||
|
||||
template<size_t sz>
|
||||
SoapObject operator[](const char (&path)[sz]) const{
|
||||
return (*this)[std::string(path, sz - 1)];
|
||||
}
|
||||
|
||||
SoapObject operator[](size_t index) const;
|
||||
std::string as_string() const;
|
||||
pugi::xml_node as_xml() const;
|
||||
|
||||
private:
|
||||
SoapObject(std::shared_ptr<pugi::xml_node> node);
|
||||
|
||||
private:
|
||||
std::shared_ptr<pugi::xml_node> _root;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif //ZLMEDIAKIT_SOAPUTIL_H
|
||||
77
src/Onvif/pugiconfig.hpp
Normal file
77
src/Onvif/pugiconfig.hpp
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* pugixml parser - version 1.11
|
||||
* --------------------------------------------------------
|
||||
* Copyright (C) 2006-2020, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
|
||||
* Report bugs and download new versions at https://pugixml.org/
|
||||
*
|
||||
* This library is distributed under the MIT License. See notice at the end
|
||||
* of this file.
|
||||
*
|
||||
* This work is based on the pugxml parser, which is:
|
||||
* Copyright (C) 2003, by Kristen Wegner (kristen@tima.net)
|
||||
*/
|
||||
|
||||
#ifndef HEADER_PUGICONFIG_HPP
|
||||
#define HEADER_PUGICONFIG_HPP
|
||||
|
||||
// Uncomment this to enable wchar_t mode
|
||||
// #define PUGIXML_WCHAR_MODE
|
||||
|
||||
// Uncomment this to enable compact mode
|
||||
// #define PUGIXML_COMPACT
|
||||
|
||||
// Uncomment this to disable XPath
|
||||
// #define PUGIXML_NO_XPATH
|
||||
|
||||
// Uncomment this to disable STL
|
||||
// #define PUGIXML_NO_STL
|
||||
|
||||
// Uncomment this to disable exceptions
|
||||
// #define PUGIXML_NO_EXCEPTIONS
|
||||
|
||||
// Set this to control attributes for public classes/functions, i.e.:
|
||||
// #define PUGIXML_API __declspec(dllexport) // to export all public symbols from DLL
|
||||
// #define PUGIXML_CLASS __declspec(dllimport) // to import all classes from DLL
|
||||
// #define PUGIXML_FUNCTION __fastcall // to set calling conventions to all public functions to fastcall
|
||||
// In absence of PUGIXML_CLASS/PUGIXML_FUNCTION definitions PUGIXML_API is used instead
|
||||
|
||||
// Tune these constants to adjust memory-related behavior
|
||||
// #define PUGIXML_MEMORY_PAGE_SIZE 32768
|
||||
// #define PUGIXML_MEMORY_OUTPUT_STACK 10240
|
||||
// #define PUGIXML_MEMORY_XPATH_PAGE_SIZE 4096
|
||||
|
||||
// Tune this constant to adjust max nesting for XPath queries
|
||||
// #define PUGIXML_XPATH_DEPTH_LIMIT 1024
|
||||
|
||||
// Uncomment this to switch to header-only version
|
||||
// #define PUGIXML_HEADER_ONLY
|
||||
|
||||
// Uncomment this to enable long long support
|
||||
// #define PUGIXML_HAS_LONG_LONG
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Copyright (c) 2006-2020 Arseny Kapoulkine
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
13027
src/Onvif/pugixml.cpp
Normal file
13027
src/Onvif/pugixml.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1501
src/Onvif/pugixml.hpp
Normal file
1501
src/Onvif/pugixml.hpp
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user