news 2026/10/26 10:29:55

ONVIF Java SDK网络摄像头接入实战:发现、鉴权、取流与PTZ

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ONVIF Java SDK网络摄像头接入实战:发现、鉴权、取流与PTZ

简介:这份 ONVIF JAVA SDK 面向需要在 Java 项目中接入网络摄像头的开发者,适合已具备一定 Java 与网络编程基础、希望快速打通设备发现、媒体与 PTZ 等 ONVIF 能力的中级读者。它提供较完整的协议实现代码,Demo 侧重核心流程演示,便于理解 UDP 发现、HTTP 交互与 XML 报文组织方式,也为后续扩展复杂功能留下改造空间。压缩包共 1157 个文件,整体约 2.32MB,以 1147 个 Java 源文件为主体,辅以 5 个 jar 依赖、Gradle 构建脚本及少量批处理与配置属性文件,目录和依赖关系相对清晰,可直接导入 IDE 研究或二次开发。目前已有 657 人学习下载。读者可从中获得 ONVIF 设备接入的可运行示例、协议字段与对象模型的参考写法、依赖配置和工程结构模板,并借助源码排查报文拼接、服务调用等常见问题,减少从零搭建的时间。

1. 网络摄像头接入的岔路口:一份 ONVIF JAVA SDK 能省掉什么

现场六台网络摄像头,四个品牌,要求在三天内全部接进看板,还要能调云台。第一反应往往是找各家私有 SDK,装完四套 jar,依赖冲突就能排一个下午。换条路走:这些设备大多支持 ONVIF,用一套 SOAP 协议就能通吃,问题从“对接四个厂商”收缩成“对接一份规范”。ONVIF JAVA SDK 这类资源做的就是把规范落地:设备发现、WS-Security 鉴权、媒体配置、PTZ 控制、事件订阅,这些散落在 WSDL 里的动作被封装成 Java 调用。前提是设备确实开了 ONVIF 开关。下面按“包里有什么、怎么连、怎么取流、坑在哪”拆开看。

2. 拆开 SDK 再看协议:三个命名空间和一次 GetDeviceInformation 调用

拿到压缩包先别急着写 main 方法,把包结构和它对应的 WSDL 对上号,后面排错能省一半时间。ONVIF 不是单一协议,它是一组按服务切分的 SOAP 接口,每个服务一套 WSDL、一套命名空间,混用前缀是新手最容易翻车的地方。

2.1 包结构与服务的对应关系

SDK 的目录一般按服务切,看到包名基本就知道它管什么事:

模块(包名关键字)对应 WSDL 命名空间典型操作用在哪一步
devicehttp://www.onvif.org/ver10/device/wsdlGetDeviceInformation、GetSystemDateAndTime、GetCapabilities建立连接的第一步
mediahttp://www.onvif.org/ver10/media/wsdlGetProfiles、GetStreamUri、GetSnapshotUri取流和抓图
ptzhttp://www.onvif.org/ver20/ptz/wsdlContinuousMove、SetPreset、GotoPreset云台控制
eventshttp://www.onvif.org/ver10/events/wsdlCreatePullPointSubscription、PullMessages移动侦测等事件
schemahttp://www.onvif.org/ver10/schema公共数据类型所有请求共用的类型定义

依赖侧基本绕不开 CXF 那套组合:cxf-rt-frontend-jaxws、cxf-rt-transports-http、cxf-rt-ws-security,再加 JAXB 和commons-codec。如果压缩包里已经带了lib/目录,优先用包内的版本,ONVIF 的 WSDL 生成代码对 CXF 版本比较敏感,混用容易在ClassNotFoundException上耗时间。

2.2 一次 GetDeviceInformation 请求的完整解剖

ONVIF 的每个动作都是一次 HTTP POST,body 是 SOAP 信封。以GetDeviceInformation为例,去掉鉴权头之后长这样:

<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope" xmlns:tds="http://www.onvif.org/ver10/device/wsdl"> <soapenv:Body> <tds:GetDeviceInformation/> </soapenv:Body> </soapenv:Envelope>

请求头里有两个不能少的字段。Content-Type必须是application/soap+xml; charset=utf-8,SOAP 1.2 把 action 塞在 Content-Type 的action参数里,而不是单独的SOAPAction头;老设备如果是 SOAP 1.1,就反过来用SOAPAction头。这两种写法在不同固件上都出现过,SDK 一般会按 WSDL 里的soapAction自动选。

响应体里的Manufacturer、Model、FirmwareVersion、SerialNumber是排查“到底连上了哪台设备”的第一手信息。多设备场景下建议把 SerialNumber 落库,比按 IP 认设备可靠,因为 DHCP 环境里 IP 会变。

2.3 WS-Security UsernameToken:PasswordDigest 的三段拼接

鉴权头是 ONVIF 里唯一需要自己算的东西。PasswordDigest的公式是Base64(SHA1(nonce 原始字节 + created 字符串 + 明文密码)),三个细节错一个就返回FailedAuthentication:

import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.SecureRandom; import java.time.Instant; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.util.Base64; public class WsseToken { public static String[] build(String username, String password) throws Exception { byte[] nonceBytes = new byte[16]; new SecureRandom().nextBytes(nonceBytes); // 每次请求重新生成,复用会触发设备的重放保护 String nonce = Base64.getEncoder().encodeToString(nonceBytes); String created = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'") .withZone(ZoneOffset.UTC) .format(Instant.now()); // 必须是 UTC,且和设备时间差控制在 5 分钟内 MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); sha1.update(nonceBytes); // 用解码后的原始字节,不是 Base64 字符串 sha1.update(created.getBytes(StandardCharsets.UTF_8)); sha1.update(password.getBytes(StandardCharsets.UTF_8)); String digest = Base64.getEncoder().encodeToString(sha1.digest()); return new String[]{username, digest, nonce, created}; } }

三个参数逐个说清楚。nonce是 16 字节随机数,在 SOAP 头里以 Base64 字符串形式出现,但参与哈希的是解码前的原始字节,这一步写错是最常见的“密码明明对却 401”。created必须是 UTC 时间,格式精确到秒,末尾带Z;很多国产设备返回的GetSystemDateAndTime标的时区是假的,实际是本地时间,直接拿来当基准会差出八小时。password用明文参与哈希,不要先做 Base64 或 MD5。

提示:设备端一般有 5 分钟的时间窗和 nonce 去重。同一秒内并发发多个请求时,nonce 必须每个请求独立生成,不能按设备缓存一个。

2.4 为什么建议用 SDK 而不是手拼字符串

手拼 SOAP 在单设备调试时看着挺爽,一旦设备型号超过三种,问题立刻出来:命名空间前缀各家不统一、SOAP 1.1 和 1.2 的 action 传递方式不同、ProfileToken这种带下划线和大小写敏感的字段在字符串拼接里极易写错一格。SDK 用 JAXB 把 WSDL 生成成强类型对象,字段名拼错了编译期就报错,这个收益在多设备对接里很实在。

3. 发现与鉴权:从 239.255.255.250 组播 Probe 到 GetCapabilities

设备在局域网里,但你不知道 IP,这一步靠 WS-Discovery。它的本质是往组播地址发一个 UDP 包,设备收到后单播回自己的服务地址。搞清楚这个机制,后面“发现不到设备”就有得查了。

3.1 WS-Discovery:组播 Probe 的报文与回复解析

组播地址239.255.255.250,端口3702,UDP 协议。Probe 里Types写dn:NetworkVideoTransmitter才是找摄像头,写Device有些设备也会回,但不是规范写法。

import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.util.UUID; public class DiscoveryProbe { private static final String PROBE_TEMPLATE = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<e:Envelope xmlns:e=\"http://www.w3.org/2003/05/soap-envelope\" " + " xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" " + " xmlns:wsd=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" " + " xmlns:dn=\"http://www.onvif.org/ver10/network/wsdl\">" + " <e:Header>" + " <wsa:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</wsa:Action>" + " <wsa:MessageID>uuid:%s</wsa:MessageID>" + " <wsa:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To>" + " </e:Header>" + " <e:Body><wsd:Probe><wsd:Types>dn:NetworkVideoTransmitter</wsd:Types></wsd:Probe></e:Body>" + "</e:Envelope>"; public static void main(String[] args) throws Exception { String probe = String.format(PROBE_TEMPLATE, UUID.randomUUID()); byte[] payload = probe.getBytes(StandardCharsets.UTF_8); DatagramSocket socket = new DatagramSocket(new InetSocketAddress(0)); socket.setSoTimeout(3000); // 3 秒收包窗口,多网卡机器可以调到 5 秒 InetAddress group = InetAddress.getByName("239.255.255.250"); socket.send(new DatagramPacket(payload, payload.length, group, 3702)); byte[] buf = new byte[8192]; while (true) { DatagramPacket reply = new DatagramPacket(buf, buf.length); socket.receive(reply); // 超时抛 SocketTimeoutException,说明没设备回 String xml = new String(reply.getData(), 0, reply.getLength(), StandardCharsets.UTF_8); System.out.println("from=" + reply.getAddress().getHostAddress()); System.out.println(xml); } } }

回复是ProbeMatches,里面两项最关键:wsa:EndpointReference是设备的唯一标识,XAddrs是设备服务地址,通常形如http://192.168.1.64/onvif/device_service。Scopes里带设备名和位置信息,多设备场景可以用来做分组。XAddrs可能有多条,设备有几个网卡就有几个地址,选跟本次 UDP 源地址同网段的那个。

3.2 先取时间再做鉴权:GetSystemDateAndTime

GetSystemDateAndTime是 ONVIF 里少数不需要鉴权的动作,正好用来对时。正确的顺序是:连上设备后先发这一条,拿到设备的 UTC 时间,算出和本地的偏移量,之后所有请求的created都用“本地时间 + 偏移”生成。

这一步看起来多余,但它是FailedAuthentication的头号成因。摄像头长期运行、没接 NTP,时间漂个十几分钟很常见;本地服务器在容器里跑,时区是 UTC,两边一对就是八小时。用偏移量而不是直接改系统时间,是因为你不一定有改容器时钟的权限。

SDK 层面一般把这件事封装在初始化里,形态大致是:

// 传 IP、用户名、密码。init 内部先调 GetSystemDateAndTime 做时差校正,再建 SOAP 客户端 OnvifDevice device = new OnvifDevice("192.168.1.64", "admin", "pass123"); device.init(); DeviceService ds = device.getDeviceService(); DeviceInformation info = ds.getDeviceInformation(); System.out.println(info.getManufacturer() + " / " + info.getModel() + " / " + info.getSerialNumber()); // 拿能力集,重点看 Media 和 PTZ 的 XAddr,有的设备媒体服务不在同一个端口 Capabilities caps = ds.getCapabilities(Arrays.asList("Media", "PTZ", "Events")); System.out.println(caps.getMedia().getXAddr());

参数说明:init里的时差校正是幂等的,重复调用没有副作用,所以在长时间运行的服务里可以定时(比如每小时)再校一次。getCapabilities的入参按需传,全量返回的 XML 会比较大。如果caps.getPtz()返回 null,说明这台设备没有云台服务,后面的 PTZ 代码直接跳过,不要在运行时才报错。

3.3 用 Capabilities 里的地址,而不是自己拼

一个容易忽略的点:设备服务的地址是http://ip/onvif/device_service,但媒体服务的地址不一定是http://ip/onvif/media_service,硬编码路径会在部分固件上直接 404。正确做法是从GetCapabilities返回的Media.XAddr里取,SDK 一般也按这个逻辑实现。如果 SDK 暴露了手动设置媒体地址的入口,优先走自动发现,实在不行再覆盖。

4. 取流与云台:Profile、StreamUri、ContinuousMove 和事件订阅的完整链路

到这一步设备已经能认出来了,接下来是三件事:把 RTSP 地址取到手、让云台转起来、把移动侦测事件拉出来。三件事共用同一个前提,就是ProfileToken。

4.1 GetProfiles 与 GetStreamUri:拼出可用的 RTSP 地址

Profile 是设备上的“一套音视频参数组合”,主码流一个、子码流一个,每个有自己的 token。取流地址是按 token 查的,所以第一步是把 token 列出来:

MediaService media = device.getMediaService(); for (Profile p : media.getProfiles()) { String token = p.getToken(); // 形如 Profile_1 / MainStream VideoEncoderConfiguration v = p.getVideoEncoderConfiguration(); System.out.printf("token=%s codec=%s %dx%d fps=%d%n", token, v.getEncoding().value(), // H264 / H265 / MJPEG v.getResolution().getWidth(), v.getResolution().getHeight(), v.getRateControl().getFrameRateLimit()); System.out.println(" ptz=" + (p.getPTZConfiguration() != null)); // 这个 profile 能不能控云台 }

ptz=这一行在调试阶段很有用,后面 PTZ 报ActionNotSupported时先回头看它。列出 token 之后按需求选,看板用子码流、存证用主码流,能显著降低带宽。

然后取 RTSP 地址:

StreamSetup setup = new StreamSetup(); setup.setStream(StreamType.RTP_UNICAST); // 单播,多播改 RTP_MULTICAST Transport transport = new Transport(); transport.setProtocol(TransportProtocol.RTSP); // 少数设备支持 HTTP 隧道 setup.setTransport(transport); MediaUri uri = media.getStreamUri(setup, "Profile_1"); System.out.println(uri.getUri()); // rtsp://192.168.1.64:554/Streaming/Channels/101

这里有个必须自己补的逻辑:返回的 URI 里 host 部分是设备“自己认为”的地址,设备并不知道你从哪个 IP 访问它。多网卡设备经常返回0.0.0.0或者另一个网段的地址,直接拿去拉流会超时。稳妥做法是把 URI 里的 host 和 port 替换成本次 SOAP 请求实际连的 IP 和 SDK 里配置的端口,路径部分保留。

4.2 用 ffprobe 验证一路流到底通不通

Java 端拿到地址只是第一步,实际能不能出画面还得单独验。我习惯先用命令行确认,排除 SDK 层的问题:

# 走 TCP 传输,排除 UDP 丢包干扰;只看视频轨的关键参数 ffprobe -v error -rtsp_transport tcp \ -i "rtsp://admin:pass123@192.168.1.64:554/Streaming/Channels/101" \ -show_entries stream=codec_name,width,height,avg_frame_rate \ -select_streams v:0 # 只读 5 秒,确认能持续出帧而不是卡在握手 ffprobe -v error -rtsp_transport tcp -read_intervals "%+5" \ -i "rtsp://admin:pass123@192.168.1.64:554/Streaming/Channels/101" \ -show_entries stream=codec_name,avg_frame_rate

输出里codec_name=h264、avg_frame_rate=25/1这类信息就是正常。如果报401 Unauthorized,是 RTSP 层的用户名密码问题,跟 SOAP 的 WS-Security 是两套认证;如果报Connection timed out,先回去检查 URI 的 host 有没有被替换。实际项目里还有一种情况:ffprobe 能出流,但 Java 侧用 OpenCV 拉不到,这通常是 OpenCV 的 FFmpeg 编译时没带 RTSP,跟本资源无关,换 JavaCV 或者用 VLCJ 能绕开。

4.3 快照地址:走的是普通 HTTP,不是 SOAP

GetSnapshotUri返回的是个 HTTP 地址,请求它不经过 SOAP,也不带 WS-Security 头,需要单独做 HTTP Digest 认证:

MediaUri snapshot = media.getSnapshotUri("Profile_1"); String url = snapshot.getUri(); // http://192.168.1.64/onvif-http/snapshot?Profile_1 // 用 HttpClient 带 Digest 认证去 GET,不要复用 SOAP 客户端的鉴权逻辑

这一步是高频坑,下面第 5 章展开。参数上唯一要注意的是Profile_1换成你实际要的分辨率对应的 token,快照出的图尺寸由 profile 的分辨率决定。

4.4 PTZ:ContinuousMove 的速度量纲与 stop

云台动作的关键是 profile 必须绑定了 PTZ 配置,第 4.1 节打印的ptz=true就是这个意思。

PtzService ptz = device.getPtzService(); // 连续移动:x 左右,y 上下,zoom 缩放,取值范围都是 -1.0 到 1.0,按设备最大速度归一化 ptz.continuousMove("Profile_1", 0.3f, 0.0f, 0.0f); Thread.sleep(800); // 转 0.8 秒 ptz.stop("Profile_1", true, true); // 第一个 true 停 pan/tilt,第二个停 zoom // 预置位 String presetToken = ptz.setPreset("Profile_1", "gate"); // 存当前位置为 gate ptz.gotoPreset("Profile_1", presetToken, 0.5f); // 速度 0.5,走一半速度过去

三个参数容易理解错。速度不是角度,是相对设备最大转速的比例,0.3f不代表每秒转多少度,各家设备的最大转速差别很大,所以同一套代码在不同型号上观感会差很多,这属于正常。stop必须显式调用,不调用设备会一直转下去直到撞到限位。gotoPreset的速度参数在部分固件上会被忽略,如果发现预置位移动过快,改用AbsoluteMove自己拆路径。

4.5 事件订阅:PullPoint 是拉模式,租约要续

ONVIF 的事件不是推送,是设备端维持一个队列,客户端定时去拉。三个动作连起来用:

EventService events = device.getEventService(); // 1) 建订阅,租约 60 秒。返回的是独立的 PullPoint 地址,不是设备服务地址 PullPointSubscription sub = events.createPullPointSubscription(60); // 2) 拉消息。Timeout 是这一次请求最长阻塞多久,MessageLimit 是一次最多返回几条 List<NotificationMessage> msgs = events.pullMessages(sub, 10, 10); for (NotificationMessage m : msgs) { System.out.println("topic=" + m.getTopic()); // tns1:RuleEngine/CellMotionDetector/Motion for (SimpleItem item : m.getSource()) { // 来源,通常是 VideoSourceConfigurationToken System.out.println(" src " + item.getName() + "=" + item.getValue()); } for (SimpleItem item : m.getData()) { // 数据,比如 IsMotion=true System.out.println(" data " + item.getName() + "=" + item.getValue()); } } // 3) 租约剩一半的时候续,别等到期 events.renew(sub, 60);

参数上,createPullPointSubscription的入参是租约秒数,pullMessages的第二个参数是阻塞超时秒数,第三个是一次最多几条。这两个时间别设反:pull 的超时应该明显短于租约,否则租约到期了还在等消息,拉取会失败。实际项目里我会把租约设 120 秒、pull 超时设 10 秒、每 60 秒续一次,留足余量。

订阅建立之后不保证立刻有事件,很多设备默认不推送,需要在设备自己的 Web 页面上打开移动侦测。这是下一章要说的坑之一。

5. 避坑排查:认证、发现、地址、PTZ、事件五类翻车记录

这一章是我在这个场景里踩过的、也帮别人排查过的五类问题,按出现频率排。每条按“现象 → 原因 → 解决”写,能对号入座的基本五分钟内能定位。

5.1 认证永远是 FailedAuthentication,密码确认没错

现象:用户名密码在设备 Web 页面能登进去,用 SDK 调GetDeviceInformation一直返回wsse:FailedAuthentication或者 HTTP 401。

原因:八成是时间差。设备的created校验窗口是 5 分钟,本地服务器在容器里跑、时区是 UTC,摄像头是本地时区,两边差八小时;或者摄像头长时间没重启,内部时钟漂了十几分钟。另一成是 nonce 复用,多线程共用一个 token 对象。

解决:先发一次GetSystemDateAndTime(这个动作不需要鉴权),拿到设备时间算偏移量,之后所有请求的created用“本地时间 + 偏移”生成。nonce 每个请求用SecureRandom重新生成。排查时可以先用 Postman 或 curl 手发一条不带鉴权的GetSystemDateAndTime,把返回的时间和本地时间并排看一眼,十秒能确认。

5.2 Probe 发出去了,一个 ProbeMatch 都收不到

现象:组播包发送成功(没抛异常),socket.receive一直超时。

原因:三种可能。设备和你不在同一网段,跨网段组播默认不通;开发机在容器或虚拟机里,虚拟网卡把组播吞了;设备根本没开 WS-Discovery,有些型号默认关闭。

解决:能拿到 IP 的场景直接跳过发现,用http://ip/onvif/device_service建连接,WS-Discovery 只是省去人工填 IP,不是协议必需。必须用发现且是多网卡环境时,用MulticastSocket.setNetworkInterface()指定从哪块网卡出去,别让它自己挑。

5.3 GetStreamUri 返回的地址拉不出流

现象:RTSP 地址拿到了,ffprobe 报Connection timed out,或者地址是rtsp://0.0.0.0/...。

原因:设备返回的是它自己认为的地址,多网卡设备或者 NAT 环境下这个地址是错的。设备不知道你从哪个 IP 访问它。

解决:把 URI 里的 host 和 port 替换成本次 SOAP 请求实际连的 IP 和端口,路径保留。多网卡设备如果不知道哪个网口通,用GetNetworkInterfaces列出所有网口,看哪个的Info.Enabled是 true 且有地址。

5.4 PTZ 报 ActionNotSupported 或参数非法

现象:ContinuousMove返回ter:ActionNotSupported,或者InvalidArgs。

原因:第一种是这个ProfileToken对应的 profile 没绑 PTZ 配置,第 4.1 节打印ptz=false的 profile 就是这种;第二种是设备本身没有云台,GetCapabilities里PTZ为 null;第三种是速度参数超范围,部分设备只接受-1.0到1.0之外还额外限制。

解决:先用GetProfiles过滤出PTZConfiguration != null的 profile,再用GetNodes确认有可用节点,最后用GetConfigurationOptions查这台设备实际接受的速度范围。三步都过了还报错,就是设备固件的问题,换 profile 或者升级固件。

5.5 订阅建成功了,pull 回来永远是空的

现象:CreatePullPointSubscription返回正常,PullMessages一直返回空的 NotificationMessage 列表,不报错。

原因:设备默认不推送事件,移动侦测功能压根没开;或者订阅时没带 TopicFilter,设备推的事件被过滤掉了;还有一种情况是 pull 的超时设得比租约还长,租约到期之后消息直接丢。

解决:先用GetEventProperties看设备支持哪些 Topic,确认CellMotionDetector之类的存在;再去设备 Web 页面把移动侦测打开,设置好灵敏度;订阅时带上 TopicFilter 只订需要的;pull 超时设 10 秒、租约设 120 秒,节奏上留够余量。

6. 进阶用法:多设备并发、订阅续租与一份链路自检清单

单台设备跑通之后,真正的复杂度来自数量。几十台设备同时在线,最容易出问题的地方不是协议本身,而是连接管理和生命周期。

并发上,不要每台设备每次都新建 SOAP 客户端。SDK 里的OnvifDevice实例是带连接的,按设备 IP 做单例缓存,进程启动时统一init,之后复用。线程池按设备数量配,一般CPU 核数 × 2就够,因为 ONVIF 的请求是短连接、等待 IO 为主。设备超过 50 台时建议给每台设备单独限流,避免同时拉流把交换机打满。

订阅的生命周期要单独管。CreatePullPointSubscription拿到的租约是会被回收的,过期之后所有 pull 都失败,而且失败信息不明显。我会给每个订阅挂一个定时任务,在租约过半的时候调renew,续租失败就重建订阅并重新拉一次GetEventProperties,因为设备重启后 Topic 列表可能变了。这套逻辑写成独立组件,别散在业务代码里。

下面这份清单是我每次接入新批设备必走的自检流程,按顺序跑,哪一步断了一眼能看出来:

步骤调用正常表现失败先查
1WS-Discovery Probe3 秒内收到 ProbeMatch,XAddrs 与设备 IP 同网段组播是否通、设备是否开了发现
2GetSystemDateAndTime返回 UTC 时间,与本地时间差在偏移记录范围内设备时区设置
3GetDeviceInformation返回厂商、型号、序列号鉴权头,重点看 nonce 和 created
4GetCapabilitiesMedia 和 PTZ 的 XAddr 非空设备是否支持对应服务
5GetProfiles至少一个 profile,记录 token 和 ptz 标记设备是否配置了码流
6GetStreamUri + ffprobeffprobe 出codec_name和帧率URI 的 host 是否要替换
7GetSnapshotUriHTTP GET 返回 JPEG(需要 Digest 认证)快照地址的认证方式
8ContinuousMove + stop云台转动后停下profile 的 PTZConfiguration 是否为空
9CreatePullPointSubscription + pull移动后 5 秒内拉到 IsMotion=true设备端移动侦测是否开启

这份清单我习惯做成命令行工具,输入 IP、用户名、密码,逐项打印通过或失败和原因,新设备接进来先跑一遍,比在业务代码里打日志快得多。

最后说个习惯。早些年我接一批新设备,图省事直接跳过第 2 步对时,结果十台里有三台鉴权失败,排查了一下午才发现是摄像头时间飘了。从那以后,不管多急,我每次接新设备都强制走一遍上面这份清单,前两步宁可多花三十秒也不跳。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/26 10:28:42

AI文献工具如何提升科研效率:8款工具全解析

1. 科研写作的AI革命&#xff1a;文献综述工具如何改变学术游戏规则作为一名在高校指导本科生科研多年的导师&#xff0c;我亲眼见证了学生们在文献综述环节消耗的时间占比——通常超过整个科研周期的40%。直到去年开始系统测试各类AI文献工具&#xff0c;才发现这个领域的技术…

作者头像 李华
网站建设 2026/10/26 10:28:39

增量式PID在S7-1200/1500 PLC中的SCL功能块实现

简介&#xff1a;面向西门子S7-1200与S7-1500 PLC工程师及自动化控制学习者的增量式PID程序实例&#xff0c;聚焦闭环控制中的输出调节、误差处理与参数整定等实际问题&#xff0c;可参考其编程结构用于温度、流量、压力等控制场景。压缩包共49个文件、约1.26MB&#xff0c;主体…

作者头像 李华
网站建设 2026/10/26 10:28:03

WinForms离线地图实战:GMap.NET源码解析与二次开发

简介&#xff1a;GMap.NET源码实例V1.7.5面向.NET桌面与Web开发者&#xff0c;尤其是需要在Windows Forms或WPF项目中集成地图能力的C#程序员。它基于开源地图控件GMap.NET&#xff0c;可对接Google、Bing、OpenStreetMap、ArcGIS等多种地图源&#xff0c;实现地图展示、缩放、…

作者头像 李华
网站建设 2026/10/26 10:27:37

python实现京东E卡批量绑定

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 有相关问题请第一时间点击头像看简介或…

作者头像 李华
网站建设 2026/10/26 10:27:34

graphify:让 AI 编码助手先查知识图谱、而不是翻源码的开源项目

AI 编码助手在一个几万行的仓库里干活&#xff0c;token 预算就那么点&#xff0c;全量读源码不现实。向量 RAG 能找“相似的代码片段”&#xff0c;但回答不了“这个函数被谁调用”“这张表被哪个服务写入”这类结构问题。以前的做法只能靠人工&#xff1a;用 grep 搜索、沿着…

作者头像 李华
网站建设 2026/10/26 10:24:42

20道 Redis 经典面试题【学完吊打面试官】

文章目录 1、什么是Redis?它主要用来什么的? 2、说说Redis的基本数据结构类型 2.1 Redis 的五种基本数据类型 2.2 Redis 的三种特殊数据类型 3、Redis为什么这么快? 3.1 基于内存存储实现 3.2 高效的数据结构 3.3 合理的数据编码 3.4 合理的线程模型 3.5 虚拟内存机制 4. 什…

作者头像 李华