ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Android利用Onvif实现局域网摄像头发现与RTSP取流的完整实践

Android利用Onvif实现局域网摄像头发现与RTSP取流的完整实践 简介在Android平台上使用Onvif协议完成局域网摄像头自动发现并获取可播放的视频流地址是安防与物联网项目开发中常见的需求。资源面向具备Android基础、需要对接不同品牌Onvif兼容设备的开发者围绕设备发现、身份认证、媒体服务和视频流解析提供了完整且可直接参考的工程实现。压缩包内共142个文件整体大小约28.46MB包括Java源码、类文件、布局与配置、依赖库、动态库以及可直接安装的APK编译产物代码结构清晰可从中看到UDP多播搜索、SOAP消息交互、媒体参数获取和RTSP地址提取等关键流程的实现方式。已有2028人学习/下载既适合快速搭建Android端摄像头发现模块也适合通过示例理解Onvif协议在移动端的具体落地方法减少大量自行抓包排错和兼容性验证的时间成本。1. 厂家SDK五花八门我为什么坚持先走Onvif做安防类App的人应该都遇到过这种状况要接的摄像头来自好几个品牌海康的SDK是AAR某家的SDK只有Windows版还有个牌子连SDK都没给只留下一份文档让你自己去抓它的RTSP流。最后翻到包装盒背面三个品牌都印着同一个缩写Onvif。这就是统一接入的突破口。当时我的需求很简单在Android端发现局域网内的所有摄像头拿到每台设备的播放地址再交给播放器出画面。听起来像一个几十行代码的小功能实际拆开之后会发现里面有两条完全不同的技术链路一条是设备发现一条是取流地址获取两条链路又分别踩了不少Android特有的坑。这篇就把这两条链路完整展开给出能直接落地的代码参考和排错思路。1.1 Onvif协议到底管哪几件事Onvif全称Open Network Video Interface Forum它不是一个单一协议而是一整套面向网络视频设备的互操作规范。跟取流相关的部分可以拆成三条链路设备发现基于WS-Discovery客户端在局域网内发一条UDP组播消息所有支持Onvif的摄像头都会响应设备能力与媒体配置通过SOAP/XML接口查询设备服务地址、媒体服务地址、通道Profile等媒体流传输通过GetStreamUri拿到RTSP地址真正的音视频数据走RTSP/RTP这层不归Onvif管。厂家SDK本质上就是把这套流程封装起来再叠加自己的云服务和私有协议。如果只做局域网内的设备发现和取流Onvif是覆盖面最广的最低通用标准。海康、大华、TP-LINK、宇视这些主流品牌基本都支持一些小众代工产品只要印了Onvif标志也大概率能通过同一套逻辑拿到RTSP地址。1.2 用Onvif的代价和收益代价很明显SOAP请求繁琐不同设备对规范的理解和执行有偏差网上现成的Android端Onvif库要么年久失修要么文档稀缺。收益也很明确协议栈写一次以后新增任何品牌都走同一套逻辑不需要为某个厂家单独维护SDK版本。我的建议是只要产品线里摄像头品牌不是单一固定Onvif就值得投入。哪怕是主打厂家SDK的项目也可以把Onvif发现能力当作兼容模式兜底。下面这套流程跑通之后你在Android上就拥有了一个跟品牌无关的摄像头接入层。2. 设备发现一条UDP组播消息开启的局域网广播2.1 WS-Discovery的工作逻辑Onvif设备发现基于WS-Discovery。设备上电后会加入组播组239.255.255.250:3702客户端构造一条Probe消息发给这个组播地址设备收到后单播回复ProbeMatch里面携带设备类型、作用域Scopes、以及最重要的服务地址XAddrs。注意单播回复这个细节。设备一般是回给你发送方的IP端口所以Android端不需要强制绑定3702端口用一个普通DatagramSocket发出去就能在同一个socket上收回复。有些教程让你用MulticastSocket加joinGroup那也能工作但多个工具同时占3702会冲突不如随机端口干净。设备收到Probe后通常会等待一小段随机时间再回复有的200毫秒就回了有的要等两秒以上。所以发送之后不能立刻放弃要在合理窗口内持续接收。2.2 Probe消息模板与收发代码Probe消息是一段SOAP封装的XML模板如下?xml version1.0 encodingUTF-8? e:Envelope xmlns:ehttp://www.w3.org/2003/05/soap-envelope xmlns:whttp://schemas.xmlsoap.org/ws/2004/08/addressing xmlns:dhttp://schemas.xmlsoap.org/ws/2005/04/discovery xmlns:dnhttp://www.onvif.org/ver10/network/wsdl e:Header w:MessageIDuuid:xxxxxxxx/w:MessageID w:Tourn:schemas-xmlsoap-org:ws:2005:04:discovery/w:To w:Actionhttp://schemas.xmlsoap.org/ws/2005/04/discovery/Probe/w:Action /e:Header e:Body d:Probe d:Typesdn:NetworkVideoTransmitter/d:Types /d:Probe /e:Body /e:EnvelopeTypes里写dn:NetworkVideoTransmitter是只召回网络视频设备如果要把NVR、门禁这些也一起发现可以换成设备理解的其他类型或者干脆留空。MessageID每次请求必须重新生成否则部分设备直接忽略这条消息。我之前图省事用一个固定UUID结果某品牌的摄像头一次都不回排查了半天才定位到是MessageID重复。发送和接收我放在同一个线程、同一个socket上DatagramSocket socket new DatagramSocket(); socket.setSoTimeout(6000); byte[] payload probeXml.getBytes(StandardCharsets.UTF_8); socket.send(new DatagramPacket(payload, payload.length, InetAddress.getByName(239.255.255.250), 3702)); long deadline System.currentTimeMillis() 6000; byte[] buffer new byte[8192]; while (System.currentTimeMillis() deadline) { try { DatagramPacket resp new DatagramPacket(buffer, buffer.length); socket.receive(resp); String xml new String(resp.getData(), 0, resp.getLength(), StandardCharsets.UTF_8); handleProbeMatch(xml); } catch (SocketTimeoutException e) { break; } }解析ProbeMatch时重点抓XAddrs标签这就是后面所有SOAP调用的入口地址。解析用XmlPullParser按标签本地名匹配不要去依赖前缀XmlPullParser parser Xml.newPullParser(); parser.setInput(new StringReader(xml)); while (parser.getEventType() ! XmlPullParser.END_DOCUMENT) { if (parser.getEventType() XmlPullParser.START_TAG XAddrs.equals(parser.getName())) { String xaddrs parser.nextText(); // 格式类似 http://192.168.1.64/onvif/device_service } parser.next(); }2.3 MulticastLock这个坑必须提前说Android在WiFi下默认会过滤组播包不主动持有MulticastLock设备即使回复了ProbeMatch网卡根本不会把包交给应用。这个坑几乎每个第一次写的人都会踩。WifiManager wifiManager (WifiManager) getApplicationContext() .getSystemService(Context.WIFI_SERVICE); MulticastLock multicastLock wifiManager.createMulticastLock(onvif-discovery); multicastLock.acquire(); // 整个发现流程跑完后再release还有无论摄像头是POE供电还是独立DC供电只要手机和它在同一局域网、设备Onvif开关打开发现逻辑都一样。别在POE摄像头上额外做特殊处理。3. 拿RTSP地址的SOAP三连GetCapabilities、GetProfiles、GetStreamUri3.1 三连是干什么的能不能省发现阶段只拿到了设备服务地址也就是XAddrs里那个/onvif/device_service。这个入口只能查设备级能力不能直接拿流地址。要得到最终能播的RTSP地址必须按顺序问三次GetCapabilities问设备你的媒体服务在哪拿到Media XAddrGetProfiles问媒体服务你有哪些通道/编码配置拿到ProfileTokenGetStreamUri问某个Profile给我流地址拿到RTSP URI。能不能省如果只针对某个品牌把厂家经验地址直接写死确实能跑比如海康常见rtsp://IP:554/Streaming/Channels/101但App就退化成只支持某几款摄像头。走完SOAP三连不同品牌的路径差异会被中间层全部消化掉后面接新品牌根本不用改代码。按我个人的习惯这三步我封装成了一个同步方法getRtspAddress(deviceXAddr, profileToken)业务层只用关心最终返回的字符串。中间任何一步失败要么返回null要么抛出带步骤信息的异常方便定位是哪个环节断了。3.2 每次调用的Action和返回值三个请求都是标准SOAP POSTContent-Type用application/soapxml。关键差异我整理成了表调用SOAPActionBody关键参数返回关键字段GetCapabilitieshttp://www.onvif.org/ver10/device/wsdl/GetCapabilitiesCategory: AllCapabilities/Media/XAddrGetProfileshttp://www.onvif.org/ver10/media/wsdl/GetProfiles空Profiles节点上的token属性GetStreamUrihttp://www.onvif.org/ver10/media/wsdl/GetStreamUriProfileToken、StreamSetupMediaUri/UriGetProfiles的请求体大致是这个样子soap:Envelope xmlns:soaphttp://www.w3.org/2003/05/soap-envelope xmlns:trthttp://www.onvif.org/ver10/media/wsdl soap:Header !-- 注意这里要带认证信息见下文 -- /soap:Header soap:Body trt:GetProfiles/ /soap:Body /soap:EnvelopeGetStreamUri稍微多两个必填字段trt:GetStreamUri trt:StreamSetup tt:StreamRTP-Unicast/tt:Stream tt:Transporttt:ProtocolRTSP/tt:Protocol/tt:Transport /trt:StreamSetup trt:ProfileToken这里填上面拿到的token/trt:ProfileToken /trt:GetStreamUri第一次写容易踩的坑有两个。第一是SOAPAction大小写和命名空间版本部分设备对Action很严格写错直接返回Fault。命名空间有ver10和ver20两套老设备和兼容性差的设备只认ver10我建议第一版统一用ver10遇到不支持的再按版本升级。第二是响应的XML里不同厂家可能返回不同的namespace前缀但XmlPullParser按本地名解析能绕开这个问题。认证方面Onvif官方推荐WS-Security的UsernameToken Digest要算Nonce、Created、PasswordDigest三个值再塞进Header手写比较繁琐。我的经验是先用无认证模式测通一遍设备返回401或Soap Fault再补认证。很多摄像头默认是关鉴权的或者兼容HTTP Basic Auth这两种情况都不需要完整的WS-Security。真正强制要求Digest的设备确实存在不过占比不高。3.3 拼出最终播放地址GetStreamUri返回的Uri可能是rtsp://192.168.1.64:554/Streaming/Channels/101但多数设备不把用户名密码写进Uri。如果设备开了RTSP鉴权你得手动拼接String user admin; String pass yourpass; String rawUri rtsp://192.168.1.64:554/Streaming/Channels/101; String finalUri rawUri.replace(rtsp://, rtsp:// Uri.encode(user) : Uri.encode(pass) );这个拼接看着简单容易出错的是密码里有、:、/这些特殊字符时不做URL编码。我见过一个项目因为密码里带符号RTSP地址被拼错排查了一整天才发现是编码问题。所以每个字段都要单独Uri.encode再拼。4. Android端绕不开的几个特殊处理4.1 权限与MulticastLock的实现要点Manifest里这几个权限一个都不能少uses-permission android:nameandroid.permission.INTERNET/ uses-permission android:nameandroid.permission.ACCESS_NETWORK_STATE/ uses-permission android:nameandroid.permission.ACCESS_WIFI_STATE/ uses-permission android:nameandroid.permission.CHANGE_WIFI_MULTICAST_STATE/如果还要顺带获取WiFi信息或者做网络状态判断Android 8.0以上还需要定位权限因为系统把扫描WiFi当成地理位置相关操作。这个容易被忽略运行时权限弹窗不授予WifiManager相关API会返回空。MulticastLock的获取和释放要跟后台任务生命周期绑到一起不要在Activity的onCreate和onDestroy里直接配一对。发现流程往往是异步的Activity退出时流程可能还在跑锁提前释放后后续响应全部收不到。我是在启动发现任务时acquire任务执行完的finally里release这样即使异常退出也不会把锁一直攥着。4.2 Android 9明文HTTP限制Onvif走的是http://不是https://Android 9开始默认禁止明文流量OkHttp会直接报Cleartext HTTP traffic not permitted。开发期最快的方式是给application加开关application android:usesCleartextTraffictrue ...如果产品要求更高可以配置networkSecurityConfig对特定网段放行。但做局域网硬件工具类App全局允许明文流量的代价不大因为流量本来就不出局域网。我自己的项目开发阶段直接usesCleartextTraffictrue上线时再按安全要求收紧。线程模型上UDP收发、SOAP请求都是阻塞操作不能跑在主线程。我一般用一个单线程的ExecutorService串行执行整个发现流程遇到多设备再换固定线程数3到5的线程池避免同一时间一堆请求打到低端摄像头上。4.3 播放器选型与RTSP认证拿到合法RTSP地址之后Android原生MediaPlayer已经不太能指望了实际项目基本两个选择。一个是Media3 ExoPlayer需要单独引入RTSP模块比如media3-exoplayer-rtsp然后直接MediaItem.fromUri(rtspUri)就能播。RTSP的Basic和Digest认证它都支持前提是你把用户名密码拼进URL。另一个是LibVLCRTSP兼容性覆盖面广再奇葩的摄像头流基本都能播代价是APK体积变大不少API风格也偏老。个人排序是先试ExoPlayer遇到播不了的奇葩流再上LibVLC。实际踩过的一个坑是某些摄像头RTSP强制走TCP而不是UDPExoPlayer对强制TCP传输的支持不如VLC这种情况下就要查看设备的RTSP传输配置或者直接换LibVLC拉流。5. 实测多品牌设备后的兼容性笔记5.1 Probe发出去了却收不到响应先别怀疑代码手机发了Probe但迟迟没有ProbeMatch我建议按这个顺序排查手机和摄像头是不是同一网段跨网段组播基本不通MulticastLock拿没拿到release是不是被上面的生命周期问题提前释放了用PC上的Onvif Device Manager在同一网络里点一下发现如果PC也发现不了说明设备侧Onvif没开或处于异常状态不是App的问题最后再上抓包工具看UDP包到底有没有到达设备。设备发现类问题超过一半是手机没开组播锁和手机和摄像头不在同一局域网两个原因。另有一小部分是厂家把摄像头出厂默认Onvif关闭需要去设备Web管理页打开。5.2 响应XML的命名空间差异不同厂家对Onvif版本的支持参差不齐有的返回ver10/media/wsdl有的返回ver20还有的连前缀都不一样。所以解析响应时必须用本地名解析不要在前缀上做文章。发送请求时固定用ver10起步设备报Fault再说。ProbeMatch里XAddrs可能不止一个有的设备会同时给LANIP和WLANIP两条地址手机往往只能访问其中一条。我处理的办法是全部拆分出来逐个用1到2秒超时去请求GetCapabilities第一个成功的地址作为本次会话主用地址。这个逻辑看着笨实际上是兼容性最好的做法。5.3 多设备环境下的并发与去重环境里有几十台摄像头时Probe一发会收到一堆ProbeMatch而且由于组播特性同一设备的响应可能出现两三次。处理时必须做好去重我用一个Set记录已处理过的XAddrs。后续的SOAP三连不要并发轰低端摄像头扛不住。我是用固定线程数3到5的线程池串行处理设备列表每台设备之间留200毫秒间隔实测比全并发稳定得多。每个设备的发现会话最好设置总超时。发现阶段设6秒SOAP三连每步3秒超时的设备先标记为不支持Onvif不再重试避免越积越多拖垮整个列表刷新。这个超时值不是拍脑袋定的是拿十来种设备实测出来的折中值太短会漏掉慢速设备太长会让用户感觉列表一直转圈。5.4 排查问题时的抓包手段PC上Wireshark抓包是终极大杀器。把手机、摄像头、PC接到同一个交换机在PC上抓3702端口的UDP包能直接看到Probe是否到达、设备是否回了ProbeMatch、SOAP请求的HTTP状态码是200还是401。这类问题靠猜效率太低抓包看响应是最省时间的。常见现象和排查方向我整理成了表现象可能原因排查手段Probe无任何响应未持有MulticastLock / 跨网段 / 设备Onvif未开启锁生命周期检查PC端Onvif Device Manager对比验证ProbeMatch有地址GetCapabilities超时XAddrs里给的是设备不可达地址遍历XAddrs所有地址逐个试GetProfiles返回空列表设备不支持标准媒体Profile换GetVideoSources兜底GetStreamUri返回401设备开启认证请求没带拼接用户名密码或实现WS-Security Digest拿到地址ExoPlayer却播不了设备RTSP要求TCP传输 / 厂家私有路径改播放器传输模式或用LibVLC验证如果条件允许先在PC的Onvif Device Manager里把摄像头完整流程走一遍确认设备本身没问题再回到Android上定位。很多时候差距只在认证方式ODM会自动带上WS-Security而你的App还没实现看到ODM正常、App失败优先怀疑认证环节。最后分享一个经验拿到RTSP地址只是第一步真正上线前一定要做设备自动重连、断流恢复、地址过期重取这几个兜底逻辑。Onvif的地址理论上是会话级的设备重启后旧地址可能失效到时候重新走一遍三连流程比让用户重启App体验好得多。这套链路跑顺之后你会发现局域网摄像头接入这件事跟品牌的关系真的不大。本文还有配套的精品资源点击获取
返回列表