C#接入ONVIF操控摄像头
本帖最后由 liuyuntianxia12 于 2020-11-4 11:55 编辑ONVIF(开放式网络视频接口论坛)是一个全球性的开放式行业论坛,其目标是促进开发和使用基于物理IP的安全产品接口的全球开放标准。ONVIF创建了一个视频监控和其他物理安全领域的IP产品如何进行相互通信的标准。所以说ONVIF接口是市面上所有摄像头的公用接口,而不是厂商提供的独特接口,这就为实现各种厂家、型号、新旧的摄像头的操控(虽然效果差强人意)
网上绝对有相关教程,但是可能就差在某个点,导致学习使用困难,我就是这么过来的,所以直接就把我整理号的接入代码拿出来秀一秀{:1_918:}
直接给东西:
相关工具类就不多说了,就是些会用的的公用方法和接收对象实体类,主要的内容还是在ONVIFHelper.cs,在说这个类之前,我先介绍一个工具:ONVIF Device Test Tool(下载地址),安装后打开的界面如下
使用方法,参考:ONVIF Device Test Tool测试工具使用方法,我们开发的话主要用到的是Request内容 如下图所示:
任何接入都是先获取指定IP摄像头的相关信息,使用:GetSystemDateAndTime
/// <summary>
/// 同步服务器与客户端时间(无需授权)
/// </summary>
public string ONVIF_GetSystemDateAndTime()
{
String reqMessageStr = "<?xml version=\"1.0\" encoding=\"utf - 8\"?> "+
"<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:tds=\"http://www.onvif.org/ver10/device/wsdl\" xmlns:tt=\"http://www.onvif.org/ver10/schema\">"+
" <s:Body> " +
" <tds:GetSystemDateAndTime/> " +
" </s:Body > " +
"</s:Envelope> ";
byte[] reqMessageBinary = EncodingHelper.String2Byte(reqMessageStr);
string Url = deviceURL;
_http.Connect(Url);
_http.setONVIFHeader("\"http://www.onvif.org/ver10/device/wsdl/GetSystemDateAndTime\"");
_http.Write(reqMessageBinary);
try
{
string paramStr = _http.Read();
//序列化返回内容
XmlDocument xdoc = new XmlDocument();
xdoc.LoadXml(paramStr.ToString());
XmlNamespaceManager xnamespace = XMLControl.getAllNamespaces(xdoc);
onvifdev.schemaPrefix = xnamespace.LookupPrefix(ONVIFNameSpace.schema);
XmlNodeList xnode_list = xdoc.GetElementsByTagName(onvifdev.schemaPrefix + ":UTCDateTime");
XmlNode time_node = xnode_list.FirstChild;
XmlNode date_node = xnode_list.LastChild;
string _hour = time_node.InnerText;
string _min = time_node.InnerText;
string _sec = time_node.InnerText;
string _year = date_node.InnerText;
string _month = date_node.InnerText;
string _day = date_node.InnerText;
System.DateTime currentTime = new System.DateTime(Int32.Parse(_year), Int32.Parse(_month), Int32.Parse(_day)
, Int32.Parse(_hour), Int32.Parse(_min), Int32.Parse(_sec));
//更新设置相关时间
baseDateTime = currentTime;
onvifdev.timeoffset = baseDateTime;
startDate = System.DateTime.Now;
}
catch (Exception ex)
{
return ex.ToString();
}
return "";
}
然后获取设备支持的接口列表:GetCapabilities
/// <summary>
/// 初始化摄像头服务配置(无需授权)
/// </summary>
/// <returns></returns>
public string ONVIF_GetCapabilities()
{
String reqMessageStr = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:tds=\"http://www.onvif.org/ver10/device/wsdl\" xmlns:tt=\"http://www.onvif.org/ver10/schema\">" +
" <s:Body>" +
" <tds:GetCapabilities> "+
" <tds:Category>All</tds:Category>"+
" </tds:GetCapabilities> " +
" </s:Body>" +
"</s:Envelope> ";
byte[] reqMessageBinary = EncodingHelper.String2Byte(reqMessageStr.ToString());
string paramStr = string.Empty;
string Url = deviceURL;
try
{
_http.Connect(Url);
_http.setONVIFHeader(ONVIFHeader.GetCapabilities);
_http.Write(reqMessageBinary);
paramStr = _http.Read();
//序列化返回内容
XmlDocument xdoc = new XmlDocument();
xdoc.LoadXml(paramStr.ToString());
XmlNamespaceManager xnamespace = XMLControl.getAllNamespaces(xdoc);
onvifdev.schemaPrefix = xnamespace.LookupPrefix(ONVIFNameSpace.schema);
onvifdev.devicePrefix = xnamespace.LookupPrefix(ONVIFNameSpace.device_service);
//保存服务器配置信息
onvifdev = XMLControl.findAllXAddr(xdoc, onvifdev);
}
catch (XmlException xe)
{
return xe.ToString();
}
catch (WebException we)
{
return we.ToString();
}
return "";
}
获取设备配置信息:GetProfiles
/// <summary>
/// 获取设备配置信息
/// </summary>
/// <returns>返回Token</returns>
public string ONVIF_GetProfiles(bool IsOAuth = false)
{
string reqMessageStr = "";
if (IsOAuth)
{
//生成客户端随机码
setNonce();
//基准时间
baseDateTime = DigestPassword.getCurrentTime(startDate, baseDateTime);
//UtcTime时间
_wsdUsernameToken.Create = DigestPassword.getCreatedTimeString(baseDateTime);
//Digest摘要
_wsdUsernameToken.Password = DigestPassword.getPasswordDigest(_wsdUsernameToken.Nonce,
_wsdUsernameToken.Create,
_wsdUsernameToken.plainPassword);
/* set packet */
reqMessageStr = "<?xml version=\"1.0\" encoding=\"utf - 8\"?>" +
"<soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:trt=\"http://www.onvif.org/ver10/media/wsdl\" xmlns:tt=\"http://www.onvif.org/ver10/schema\">" +
" <s:Header xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\">" +
" <wsse:Security xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">" +
" <wsse:UsernameToken>" +
" <wsse:Username>" + _wsdUsernameToken.Username + "</wsse:Username>" +
" <wsse:Password Type = \"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest\">" + _wsdUsernameToken.Password + "</wsse:Password>" +
" <wsse:Nonce>" + _wsdUsernameToken.Nonce + "</wsse:Nonce>" +
" <wsu:Created>" + _wsdUsernameToken.Create + "</wsu:Created>" +
" </wsse:UsernameToken>" +
" </wsse:Security>" +
" </s:Header>" +
" <soap:Body>" +
" <trt:GetProfiles />" +
" </soap:Body>" +
"</soap:Envelope> ";
}
else
{
reqMessageStr = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<soap:Envelope xmlns:soap = \"http://www.w3.org/2003/05/soap-envelope\" xmlns:trt = \"http://www.onvif.org/ver10/media/wsdl\" xmlns:tt = \"http://www.onvif.org/ver10/schema\" > " +
"<soap:Body> " +
"<trt:GetProfiles /> " +
"</soap:Body> " +
"</soap:Envelope> ";
}
byte[] reqMessageBinary = EncodingHelper.String2Byte(reqMessageStr);
string paramStr = string.Empty;
string Url = onvifdev.mediaxaddr;
try
{
_http.Connect(Url);
_http.setONVIFHeader(ONVIFHeader.GetProfiles);
_http.Write(reqMessageBinary);
paramStr = _http.Read();
//序列化返回内容
XmlDocument xdoc = new XmlDocument();
xdoc.LoadXml(paramStr.ToString());
//解析出Token
XmlElement root = xdoc.DocumentElement;
string nameSpace = root.NamespaceURI;
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xdoc.NameTable);
//设置命名空间前缀
nsmgr.AddNamespace("SOAP-ENV", nameSpace);
nsmgr.AddNamespace("trt", root.Attributes["xmlns:trt"].InnerText);
//默认值为第一个
string value = xdoc.SelectSingleNode("/SOAP-ENV:Envelope/SOAP-ENV:Body/trt:GetProfilesResponse", nsmgr).ChildNodes.Attributes["token"].InnerText.Trim();
return value;
}
catch (XmlException xe)
{
return "";
}
catch (WebException we)
{
return "";
}
}
指定通道接口获取视频流地址:GetStreamUri
/// <summary>
/// 获取视频流地址
/// </summary>
/// <param name="token">Token</param>
/// <returns>视频流地址</returns>
public string ONVIF_GetStreamUri(bool IsOAuth=false)
{
string token = ONVIF_GetProfiles(IsOAuth);
string reqMessageStr = "";
if (IsOAuth)
{
//生成客户端随机码
setNonce();
//基准时间
baseDateTime = DigestPassword.getCurrentTime(startDate, baseDateTime);
//UtcTime时间
_wsdUsernameToken.Create = DigestPassword.getCreatedTimeString(baseDateTime);
//Digest摘要
_wsdUsernameToken.Password = DigestPassword.getPasswordDigest(_wsdUsernameToken.Nonce,
_wsdUsernameToken.Create,
_wsdUsernameToken.plainPassword);
//设置发送消息体
reqMessageStr = "<soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:trt=\"http://www.onvif.org/ver10/media/wsdl\" xmlns:tt=\"http://www.onvif.org/ver10/schema\" >" +
" <s:Header xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\">" +
" <wsse:Security xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">" +
" <wsse:UsernameToken>" +
" <wsse:Username>" + _wsdUsernameToken.Username + "</wsse:Username>" +
" <wsse:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest\">" + _wsdUsernameToken.Password + "</wsse:Password>" +
" <wsse:Nonce>" + _wsdUsernameToken.Nonce + "</wsse:Nonce>" +
" <wsu:Created>" + _wsdUsernameToken.Create + "</wsu:Created>" +
" </wsse:UsernameToken>" +
" </wsse:Security>" +
" </s:Header>" +
" <soap:Body>" +
" <GetStreamUri xmlns=\"http://www.onvif.org/ver10/media/wsdl\">" +
" <StreamSetup>" +
" <Stream xmlns=\"http://www.onvif.org/ver10/schema\">RTP-Unicast</Stream>" +
" <Transport xmlns=\"http://www.onvif.org/ver10/schema\">" +
" <Protocol>UDP</Protocol>" +
" </Transport>" +
" </StreamSetup>" +
" <ProfileToken>" + token + "</ProfileToken>" +
" </GetStreamUri>" +
" </soap:Body>" +
"</soap:Envelope>";
}
else
{
reqMessageStr = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:trt=\"http://www.onvif.org/ver10/media/wsdl\" xmlns:tt=\"http://www.onvif.org/ver10/schema\">" +
"<soap:Body>" +
"<GetStreamUri xmlns = \"http://www.onvif.org/ver10/media/wsdl\">" +
"<StreamSetup>" +
"<Stream xmlns = \"http://www.onvif.org/ver10/schema\">RTP-Unicast</Stream>" +
"<Transport xmlns = \"http://www.onvif.org/ver10/schema\">" +
"<Protocol>UDP</Protocol>" +
"</Transport>" +
"</StreamSetup>" +
"<ProfileToken>mainStream</ProfileToken>" +
"</GetStreamUri>" +
"</soap:Body>" +
"</soap:Envelope>";
}
byte[] reqMessageBinary = EncodingHelper.String2Byte(reqMessageStr);
string paramStr = string.Empty;
string Url = onvifdev.mediaxaddr;
try
{
_http.Connect(Url);
_http.setONVIFHeader("\"http://www.onvif.org/ver10/media/wsdl/GetStreamUri\"");
_http.Write(reqMessageBinary);
paramStr = _http.Read();
//序列化返回内容
XmlDocument xdoc = new XmlDocument();
xdoc.LoadXml(paramStr.ToString());
//解析出Token
XmlElement root = xdoc.DocumentElement;
string nameSpace = root.NamespaceURI;
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xdoc.NameTable);
//设置命名空间前缀
nsmgr.AddNamespace("SOAP-ENV", nameSpace);
nsmgr.AddNamespace("trt", root.Attributes["xmlns:trt"].InnerText);
//默认值为第一个
string value = xdoc.SelectSingleNode("/SOAP-ENV:Envelope/SOAP-ENV:Body/trt:GetStreamUriResponse/trt:MediaUri", nsmgr).ChildNodes.InnerText.Trim();
return value;
}
catch (XmlException xe)
{
return "";
}
catch (WebException we)
{
return "";
}
finally
{
}
}
代码部分就是这样,很简单的逻辑,拼接请求参数获取信息
开发过程中可以用工具先测试相关接口,配置好摄像头的账号密码权限,同时要摄像头启用支持ONVIF,部分接口不用传递账号密码(传递有可能还报错),部分就需要传递账号密码,具体看代码
觉得ONVIF很有意思的一个东西,分享给大家
sweet_love 发表于 2020-11-18 16:25
我有个突发奇想。IP地址段扫描摄像头设备端口。然后写循环,用ONVIF去尝试链接......然后你 ...
是可以的{:1_925:},不能干坏事儿呀
我之前搞测试的时候能直接打开我们公司的监控摄像头(主要是没有设置密码或者是默认密码)
但是后面新出的摄像头都支持ONVIF,但是需要手动去开启,所以。。。 liuyuntianxia12 发表于 2020-11-18 15:49
基本所有摄像头都支持,我就是因为老摄像头没有开发SDK,才关注使用ONVIF的
{:301_1008:}我有个突发奇想。IP地址段扫描摄像头设备端口。然后写循环,用ONVIF去尝试链接......然后你懂。 太优秀了 很不错啊,感谢分享···
很不错啊,感谢分享··· 谢谢LZ分享 如果这个协议,相关的摄像头厂商都支持了,那就好多了。相信应用会很多。 sweet_love 发表于 2020-11-18 14:42
如果这个协议,相关的摄像头厂商都支持了,那就好多了。相信应用会很多。
基本所有摄像头都支持,我就是因为老摄像头没有开发SDK,才关注使用ONVIF的 太好了,可以学习下,感谢分享