☰
ONVIF C#接入指南:从设备发现到RTSP取流的源码解析
2026/10/8 23:50:33 网站建设 项目流程

简介:C# 语言实现的 ONVIF 协议客户端工具完整源代码,基于 Visual Studio 2015 构建。项目包含完整解决方案与工程文件,可在 VS2015 中直接打开、编译和调试。面向需要对接网络摄像机、NVR 等安防设备的嵌入式或上位机开发者,也适合通过 C# 理解 ONVIF 接口交互逻辑后迁移到其他语言的工程师。工程覆盖设备发现、设备鉴权、参数获取与设置、用户信息管理、固件升级、视频流参数配置,并通过 Live555 完成 RTSP 流的解析与显示;设备发现模块可探测网内 ONVIF 设备,鉴权部分展示了常用安全认证流程,固件升级模块演示了文件上传与状态反馈过程。压缩包约 31.69MB,共 2000 个文件,主体为 1813 个 C# 源文件,配合 XAML 界面、DLL 库及 C/C++ 头文件等构成完整可读工程,目录结构便于按模块检索和复用。资源已有 1409 人浏览学习,适合协议分析、二次开发或跨语言移植参考。

1. 这份 ONVIF C# 工具源码:VS2015 打开就能接摄像头的底稿

我接触 ONVIF C# 工具是从一台查不到型号的半球机开始的。甲方说摄像头能出画面,但平台就是拉不了流,我抱着笔记本蹲在机柜边上,最后是靠 ONVIF 的 GetStreamUri 接口把 RTSP 地址挖出来才解决的。ONVIF 是安防设备事实上的通用协议,不管海康、天地伟业还是杂牌代工,只要支持 ONVIF,就能用同一套 C# 代码完成设备发现、读取型号、取流、控制云台。这份源码是 VS2015 下的完整工程,协议层代码直接可读,不需要额外授权,改改 IP 和账号就能连设备。适合安防集成商、做视频平台的开发、以及每天要接不同品牌设备的运维。源码的价值不在界面,在于把 ONVIF 那套 WS-Discovery、WS-Security、MediaService 调用变成了你能改的 C# 代码。

2. 从 WSDL 到可编译工程:服务引用、代理类与最小调用

2.1 源码包结构:拿到手先分清四块代码

这份源码在 VS2015 里打开 .sln 就能编,工程引用了 .NET Framework 4.5/4.6 自带的 System.ServiceModel,不需要额外装 WCF 包。我拿到任何 ONVIF 源码的第一件事,是把代码按职责分成四块:界面层是 WinForm 的列表和按钮;协议层是设备服务、媒体服务、PTZ 服务的调用封装;鉴权层是 WS-Security 头的构造;工具层是 WS-Discovery 的 UDP 收发。改得最多的永远是协议层和鉴权层,界面只是把结果摆出来。

先看一眼工程里这几个关键文件:DeviceClient 相关文件对应http://www.onvif.org/ver10/device/wsdl,负责 GetDeviceInformation、GetCapabilities、GetSystemDateAndTime;MediaClient 对应http://www.onvif.org/ver10/media/wsdl,负责 GetProfiles、GetStreamUri;PtzClient 对应http://www.onvif.org/ver20/ptz/wsdl,负责 ContinuousMove、Stop。如果你拿到手的代码里这些代理类是空的或者编译报错,多半是服务引用没生成成功,往下看 2.2。

2.2 服务引用这关:WSDL 和 XSD 要本地化

ONVIF 的 WSDL 有一个特别烦人的特点:主 WSDL 文件靠import引用一堆外部 XSD,比如 common.xsd、types.xsd。VS2015 的 Add Service Reference 在拉取这些远程引用时,经常因为网络超时、证书不可信、或者 xsd 路径带版本参数而失败。失败现象很统一:生成了一堆空的 partial class,编译时找不到DevicePortType这种接口。

我一般不让 VS 直接连设备拉 WSDL,而是先把文件下载到本地。用浏览器打开http://设备IP/onvif/device_service?wsdl,把主 WSDL 保存下来,再把里面 import 的每个 XSD 逐个下载到同一个目录,最后用文本编辑器把 import 的 location 改成相对路径。这步做完,再在 VS 里添加服务引用时直接选本地文件,一次性通过。

如果嫌手工下载慢,用 svcutil 也行,但参数要到位,不然输出的一堆文件里会有大量冗余配置:

svcutil http://192.168.1.64/onvif/device_service?wsdl ^ /namespace:*,ONVIF.DeviceProxy ^ /out:DeviceProxy.cs ^ /noConfig

/noConfig是关键,ONVIF 的绑定不能指望它生成的配置文件,后面要自己控制绑定。svcutil 最大的坑是它会尝试拉取所有外部 XSD,网络一抖动就中断,所以最稳的方式还是 2.1 里说的:把 WSDL 和 XSD 全下载到本地再跑。

2.3 绕开 Service Reference:手工拼 SOAP 的最小调用

说实话,ONVIF 的 WSDL 生成代理类代码量很大,出问题排查也费劲。我更常干的是手工拼 SOAP 消息,用 HttpWebRequest 直接 POST。ONVIF 是 SOAP 1.2 协议,请求体是 XML,整段报文自己能控制,鉴权头往哪放也清楚。

static string CallOnvif(string url, string bodyXml) { string envelope = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\">" + "<s:Body>" + bodyXml + "</s:Body></s:Envelope>"; HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url); req.Method = "POST"; req.ContentType = "application/soap+xml; charset=utf-8"; req.Timeout = 5000; byte[] postBytes = Encoding.UTF8.GetBytes(envelope); using (Stream stream = req.GetRequestStream()) { stream.Write(postBytes, 0, postBytes.Length); } try { using (HttpWebResponse resp = (HttpWebResponse)req.GetResponse()) using (StreamReader reader = new StreamReader(resp.GetResponseStream(), Encoding.UTF8)) { return reader.ReadToEnd(); } } catch (WebException ex) { HttpWebResponse errorResp = (HttpWebResponse)ex.Response; if (errorResp != null && errorResp.StatusCode == HttpStatusCode.Unauthorized) { return "HTTP 401: 鉴权失败,先检查账号权限或本地时间"; } throw; } }

这里有两个细节要注意。一是SOAPAction头在 SOAP 1.2 里不是必需的,很多设备按 SOAP 1.1 兼容处理才认识它,所以我这个最小函数里没放,遇到老设备不响应时再补上不迟。二是异常处理里必须拿ex.Response看状态码,只打ex.Message根本看不出是 401 还是 404。很多人在这一步翻车,把远程服务器返回错误 (401)当成网络不通,其实问题出在鉴权。

3. 设备发现:在 3702 端口用 UDP 组播把摄像头捞出来

3.1 发现原理:Probe 消息该发到什么地址

ONVIF 的设备发现基于 WS-Discovery 协议,定义在http://schemas.xmlsoap.org/ws/2005/04/discovery。客户端向组播地址239.255.255.250:3702发一条 Probe 消息,设备收到后判断自己是不是符合,符合就单播一条 ProbeMatch 回来。这里注意:是组播不是广播,广播在某些交换机上会被限制,而组播是 ONVIF 设备默认监听的。

Probe 消息本质上是 SOAP over UDP。你可以把设备发现想象成在局域网里喊一嗓子「谁是 ONVIF 设备?报上名来」,设备回应的时候会带上自己的 XAddrs(服务地址列表)、Types(设备类型)和 Scopes(作用域)。XAddrs 是最关键的,后面所有 ONVIF 接口调用都要用它拼 URL。

有个经验:很多国产设备对带<d:Types>dn:NetworkVideoTransmitter</d:Types>的 Probe 反而爱答不理,用不带 Types 的通配 Probe 却秒回。我怀疑是某些固件对 Types 的枚举值判断和标准不完全一致,所以第一版实现我建议直接发不带 Types 的 Probe,拿到设备列表再慢慢过滤。

3.2 收发 ProbeMatch:完整 C# 代码与参数说明

下面这段是我实际在用的发现逻辑,UDP 绑定 3702 端口,同时负责发送和接收。注意ReceiveTimeout必须设,否则udp.Receive会永远阻塞,设备不存在时整个线程卡死。

public static List<string> DiscoverOnvifDevices(int timeoutMs = 5000) { List<string> xaddrList = new List<string>(); using (UdpClient udp = new UdpClient()) { udp.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); udp.Client.Bind(new IPEndPoint(IPAddress.Any, 3702)); udp.JoinMulticastGroup(IPAddress.Parse("239.255.255.250")); udp.MulticastLoopback = true; udp.Client.ReceiveTimeout = timeoutMs; string probe = BuildProbeXml(); byte[] buffer = Encoding.UTF8.GetBytes(probe); udp.Send(buffer, buffer.Length, new IPEndPoint(IPAddress.Parse("239.255.255.250"), 3702)); while (true) { try { IPEndPoint remote = new IPEndPoint(IPAddress.Any, 0); byte[] recv = udp.Receive(ref remote); string xmlString = Encoding.UTF8.GetString(recv); if (xmlString.Contains("ProbeMatch")) { string xaddr = ParseXAddrs(xmlString); if (!string.IsNullOrEmpty(xaddr)) xaddrList.Add(xaddr); } } catch (SocketException ex) { if (ex.SocketErrorCode == SocketError.TimedOut) break; throw; } } } return xaddrList; } static string BuildProbeXml() { string uuid = "uuid:" + Guid.NewGuid().ToString(); return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<e:Envelope xmlns:e=\"http://www.w3.org/2003/05/soap-envelope\" " + "xmlns:w=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" " + "xmlns:d=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\">" + "<e:Header>" + "<w:MessageID>" + uuid + "</w:MessageID>" + "<w:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery</w:To>" + "<w:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</w:Action>" + "</e:Header>" + "<e:Body><d:Probe /></e:Body></e:Envelope>"; }

参数说明:ReuseAddress允许重复绑定 3702 端口,避免上次程序没退干净导致 bind 失败;JoinMulticastGroup把本机加入组播组,Windows 上不加入是收不到组播包的;ReceiveTimeout决定了最多等多久,我一般给 5 秒,设备多或跨交换机时加到 8 秒。ParseXAddrs用XDocument解析,XNamespace前缀对应http://schemas.xmlsoap.org/ws/2005/04/discovery,取第一个wsd:XAddrs节点的文本值即可。如果你收到的 ProbeMatch 里 XAddrs 是空,说明设备没配好或者固件有 bug,这种设备即使发现了也连不上。

3.3 跨三层发现:Remote Discovery 的边界

组播发现只能覆盖同一个二层广播域,跨 VLAN 或跨路由是发现不到的。ONVIF 规范里有个 Remote Discovery 机制,客户端可以往指定的管理地址发单播 Probe,但这种功能大多是平台级设备才支持,普通摄像头基本没有。所以你在写工具时,要设计成「本地发现 + 手动输入 IP」两条路,手动输入 IP 时直接拼http://ip/onvif/device_service去试,很多设备就算 WS-Discovery 是坏的,这个入口也是活的。

4. 鉴权到取流:UsernameToken 摘要、RTSP 地址和 PTZ 的完整链路

4.1 PasswordDigest 计算:nonce、created 与密码的拼接顺序

ONVIF 接口默认要鉴权,鉴权方式不是简单的用户名密码,而是 WS-Security 的 UsernameToken,密码部分用 PasswordDigest 摘要。算法拆开是四步:生成随机 nonce、取当前 UTC 时间、把 nonce 字节 + created 字符串字节 + 密码字节拼一起、做 SHA1 摘要后再 Base64。这一步拼错顺序必 401,没有任何商量余地。

static string BuildPasswordDigest(byte[] nonceBytes, DateTime createdUtc, string password) { string created = createdUtc.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'"); byte[] passwordBytes = Encoding.UTF8.GetBytes(password); byte[] createdBytes = Encoding.UTF8.GetBytes(created); byte[] buf = new byte[nonceBytes.Length + createdBytes.Length + passwordBytes.Length]; Buffer.BlockCopy(nonceBytes, 0, buf, 0, nonceBytes.Length); Buffer.BlockCopy(createdBytes, 0, buf, nonceBytes.Length, createdBytes.Length); Buffer.BlockCopy(passwordBytes, 0, buf, nonceBytes.Length + createdBytes.Length, passwordBytes.Length); using (SHA1 sha = SHA1.Create()) { return Convert.ToBase64String(sha.ComputeHash(buf)); } }

nonce 生成我习惯用RNGCryptoServiceProvider生成 16 字节随机数,每次请求都换新的,不能复用,复用同一个 nonce 在某些设备上会直接拒绝。created 用 UTC 时间,格式严格到秒,不带毫秒,设备端校验时如果发现系统时间差超过 30 秒,密码摘要直接无效。所以做 ONVIF 对接的第一件事不是写代码,而是确认电脑和摄像头时间一致。

组装 Security 头时,把下面这段 XML 塞进<s:Envelope>的<s:Header>节点里,注意命名空间前缀wsse和wsu不能省:

<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"> <wsse:UsernameToken> <wsse:Username>admin</wsse:Username> <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">Base64摘要</wsse:Password> <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">Base64Nonce</wsse:Nonce> <wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2025-01-15T08:30:00Z</wsu:Created> </wsse:UsernameToken> </wsse:Security>

把这段加到第 2 章的CallOnvif里,位置是 Header 而不是 Body,很多第一次写的人把它塞进 Body,设备端解析 WS-Security 头失败,返回的报错还不直观,只给你一个Sender错误码。遇到这种情况先检查 Security 头是不是在<s:Header>里。

4.2 设备信息与能力:GetDeviceInformation、GetCapabilities

鉴权通了之后,第一件事是调 GetDeviceInformation 拿设备基本信息。这个接口的作用是确认你连的设备真的是 ONVIF 设备,并且能看到厂家、型号、固件版本。SOAP Body 非常简单:

<tds:GetDeviceInformation xmlns:tds="http://www.onvif.org/ver10/device/wsdl" />

返回的 XML 里<tds:Manufacturer>、<tds:Model>、<tds:FirmwareVersion>就是你要的东西。解析用 XDocument 按命名空间取节点就行,注意 ONVIF 的命名空间前缀在返回报文里可能叫tds也可能叫别的字,别按前缀解析,按命名空间 URI 取。

GetCapabilities 比设备信息更重要,它告诉你这台设备支持哪些能力,以及每个子服务的入口地址。用下面的 Body 调用:

<tds:GetCapabilities xmlns:tds="http://www.onvif.org/ver10/device/wsdl"> <tds:Category>All</tds:Category> </tds:GetCapabilities>

返回的<tds:Capabilities>里有<tt:Media>、<tt:PTZ>、<tt:Events>等子节点,每个节点带XAddr属性,这是 Media 服务和 PTZ 服务的真实入口 URL。很多摄像头设备服务和媒体服务的地址不同,MediaService 的地址一般是http://ip/onvif/media_service,但不绝对,必须以 GetCapabilities 返回的 XAddr 为准。如果你忽略这一步直接写死 URL,连出海康以外的设备大概率要翻车。

4.3 RTSP 取流:GetProfiles 到 GetStreamUri 的标准三步

取流是视频平台对接里最核心的动作。ONVIF 规定了一条路径:GetProfiles 拿 Profile 列表,GetStreamUri 拿具体某个 Profile 的 RTSP 地址。先调 GetProfiles:

<trt:GetProfiles xmlns:trt="http://www.onvif.org/ver10/media/wsdl" />

返回的每个<trt:Profiles>节点上有token属性,值是 ProfileToken,还有一个<tt:Name>描述这个 Profile 是主码流还是子码流。你需要记录 token,后面 GetStreamUri 的入参就是它。这里有个细节:同一台设备可能有多个 Profile,比如一个 1080P 主码流、一个 D1 子码流,想要哪个流就用哪个 token,选错 Profile 拿到的 RTSP 地址分辨率不对。

再调 GetStreamUri,Body 里带 ProfileToken:

<trt:GetStreamUri xmlns:trt="http://www.onvif.org/ver10/media/wsdl"> <trt:StreamSetup> <tt:Stream xmlns:tt="http://www.onvif.org/ver10/schema">RTP-Unicast</tt:Stream> <tt:Transport xmlns:tt="http://www.onvif.org/ver10/schema"> <tt:Protocol>RTSP</tt:Protocol> </tt:Transport> </trt:StreamSetup> <trt:ProfileToken>ProfileToken_001</trt:ProfileToken> </trt:GetStreamUri>

返回的<tt:Uri>节点里就是 RTSP 地址。这个地址通常长这样:rtsp://192.168.1.64:554/stream1。注意很多设备的 RTSP 鉴权和 ONVIF 鉴权是分开的,ONVIF 调通了不代表 RTSP 能直接连,需要把用户名密码拼进地址里:rtsp://admin:密码@192.168.1.64:554/stream1。拼的时候密码里有特殊字符记得做 URL 编码,不然密码里的@或#会把地址截断。

PTZ 控制走的是另一套接口,PtzService。核心是 ContinuousMove 加 Stop,ContinuousMove 启动云台转动,参数控制方向,Stop 停止。关键参数是 Velocity 向量,x 表示水平速度,y 表示垂直速度,取值范围 -1.0 到 1.0,1.0 是最大速度,负值是反方向:

<ptz:ContinuousMove xmlns:ptz="http://www.onvif.org/ver20/ptz/wsdl"> <ptz:ProfileToken>ProfileToken_001</ptz:ProfileToken> <ptz:Velocity> <tt:PanTilt xmlns:tt="http://www.onvif.org/ver10/schema" x="-0.5" y="0" /> <tt:Zoom xmlns:tt="http://www.onvif.org/ver10/schema" x="0" /> </ptz:Velocity> <ptz:Timeout>PT5S</ptz:Timeout> </ptz:ContinuousMove>

Timeout 的PT5S是 ISO8601 时长格式,意思是这个转动指令 5 秒后自动停止。如果设成PT0S,设备会一直转到收到 Stop 为止。我建议实际项目里 Timeout 给一个有限值,防止客户端异常退出云台转不停。Stop 接口要传两个布尔参数,分别控制停止水平转动和垂直转动,一般两个都给 true。

5. ONVIF 接入避坑手册:五个最常见翻车现场与解法

5.1 设备列表空白:多网卡、防火墙与不带 Types 的 Probe

现象:程序跑起来,3702 端口也监听了,设备一台都发现不到。

原因分三种:Windows 防火墙默认拦 UDP 组播入站;多网卡机器上组播走了错误的网卡;Probe 消息带了dn:NetworkVideoTransmitter类型导致部分设备不应答。

解决:先关防火墙测一轮,确定不是系统层面问题。再检查网卡,笔记本上 WiFi 和有线同时开时,组播可能只从其中一块网卡出去。多网卡环境要遍历所有 Up 状态网卡,对每块网卡单独发一次 Probe:

foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces()) { if (ni.OperationalStatus != OperationalStatus.Up) continue; if (ni.NetworkInterfaceType == NetworkInterfaceType.Loopback) continue; IPInterfaceProperties props = ni.GetIPProperties(); foreach (UnicastIPAddressInformation addr in props.UnicastAddresses) { if (addr.Address.AddressFamily != AddressFamily.InterNetwork) continue; // 以 addr.Address 为本地地址创建 UdpClient,再发 Probe } }

最后 Probe 里把<d:Types>去掉,用通配发现。每次排查都按这个顺序来,能省掉大量抓包时间。

5.2 401 鉴权失败:本地时间与设备时间偏差

现象:Security 头拼了,密码也对,设备返回 HTTP 401。

原因:最常见的是电脑本地时间和摄像头系统时间差超过 30 秒,PasswordDigest 里带的是本地生成的时间戳,设备端校验时间窗口不通过就拒了。第二种是账号没开 ONVIF 权限,很多摄像头在 Web 管理页里 RTSP 用户和 ONVIF 用户是分开的。第三种是 nonce 复用了,连续请求用了同一个随机数。

解决:先用GetSystemDateAndTime接口读取设备时间,拿它和本地时间对比;如果服务端时间能调就校时,不能调就改为用设备时间生成 created 字段。账号问题去设备 Web 后台把 ONVIF 用户单独配置一下。nonce 每次请求必须重新生成,这是硬性要求,偷懒做成固定值必炸。

5.3 服务引用生成失败:外部 XSD 下载不全

现象:VS2015 添加服务引用时提示「无法下载 http://.../common.xsd」,或者生成出来的代理类找不到类型。

原因:ONVIF 的 WSDL import 的外部 XSD 路径是相对路径,VS 在远程拉取时偶发超时;另外部分设备启用了 HTTPS,证书是自签的,VS 直接拒了。

解决:不要用 VS 远程拉取。把 WSDL 和所有引用的 XSD 下载到本地,修改 WSDL 里的 import location 为相对路径,再从本地文件添加服务引用。如果设备只提供 HTTPS 入口,先用浏览器把https://ip/onvif/device_service?wsdl的内容另存下来,再把里面引用的 XSD 也逐个保存,证书问题通过浏览器访问时手动信任就能绕过。

5.4 RTSP 地址能取到但 VLC 拉流失败:鉴权参数过期与地址拼接

现象:GetStreamUri 正常返回了一个 RTSP 地址,但 VLC 打开报 401 或直接黑屏。

原因:ONVIF 取流的 RTSP 地址有一部分设备是动态生成的,地址里带了过期时间参数,过几分钟就失效;另一部分设备返回的地址不带用户名密码,需要手动拼;还有的情况是 ProfileToken 选错了,拿到的是不支持 RTSP 的 Profile。

解决:取流前重新调 GetStreamUri,不要缓存地址;拿到地址后先检查有没有用户名,没有就用rtsp://user:pass@ip:port/...格式补上;ProfileToken 如果拿不准,逐个尝试,直到 VLC 能出画面。补充一点,取流成功后建议顺手验证一下码率信息,有些设备主码流和子码流的编码方式在 Profile 里能看到,H.265 的流在老平台里拉不动。

5.5 HTTPS 设备连接失败:自签证书的信任问题

现象:设备开了 HTTPS,ONVIF 地址是https://ip:8443/onvif/device_service,代码一调就抛AuthenticationException或TrustFailure。

原因:摄像头用的 HTTPS 证书绝大多数是设备自签的,.NET 的 ServicePointManager 默认不信任自签证书,直接打断 TLS 握手。

解决:在建立连接前挂上证书校验回调,只对目标 IP 放过,不要全局无条件信任:

ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => { if (sslPolicyErrors == SslPolicyErrors.None) return true; // 仅在实际项目明确知道目标 IP 是摄像头时返回 true return true; };

这个写法有安全争议,但在内网设备调试场景下是常见做法。生产环境建议改成校验证书指纹,至少校验一下 IP 是否在允许清单里。

6. 用这套工具给一台陌生摄像头做 ONVIF 体检

拿到一台不确定支持不支持 ONVIF 的摄像头,别急着写代码,先把体检流程跑一遍。我用了这套源码之后总结出一个固定套路,五步下来,设备能不能接入、该用哪个 Profile、坑在哪儿,全部清楚了。

步骤操作预期结果
1设备发现能发现或手动输入 IP 能访问 device_service
2GetSystemDateAndTime返回设备时间,确认与本地偏差小于 30 秒
3GetDeviceInformation + GetCapabilities返回厂家、型号、Media XAddr
4GetProfiles + GetStreamUri返回 RTSP 地址,VLC 能播放
5ContinuousMove + Stop云台能转动并停止

每一步我在工具里都留了单独的测试按钮,体检的时候就按顺序点下来。哪个环节报错,直接跳到对应接口的日志,不用从头猜。有一次我接一批工控机上用的工业相机,发现第 4 步 GetProfiles 返回空列表,但设备信息正常,排查半天发现是那款相机需要先调SetProfile初始化才能用,协议栈和普通摄像头不一样。从那以后我每次接陌生设备,都强制走一遍这五步,不猜、不跳、不省,连厂家技术支持都省了。这套 C# 源码虽然老,但 ONVIF 协议十年没大变,VS2015 里改改照样能用。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询