初步添加onvif客户端

This commit is contained in:
xia-chu
2025-11-17 12:58:45 +08:00
parent 70a2140f27
commit adb844032d
8 changed files with 15557 additions and 1 deletions

View File

@@ -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
View 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
View 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
View 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 &timestamp) {
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
View 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
View 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

File diff suppressed because it is too large Load Diff

1501
src/Onvif/pugixml.hpp Normal file

File diff suppressed because it is too large Load Diff