
简介在Android端通过Onvif协议完成局域网摄像头自动发现与播放地址获取是不少安防与物联开发者关注的问题。这份资源正是一套围绕该专题整理的工程文件与源码包面向有一定Java或Android基础、希望快速接入Onvif功能的开发者覆盖设备发现、设备认证、媒体Profile解析、RTSP地址提取和视频流播放等关键环节。压缩包共142个文件大小28.46MB目录中既包含Android工程常见的java、class、xml、jar文件也含有大量so动态库及apk可安装包可直接参考工程结构或用于逆向分析多个class文件如CameraFinder、RtspClient、HttpSoap等与tvonvif库配合展示了从UDP多播搜索到SOAP交互再到播放地址获取的完整链路。资源已有2028人学习下载适用于需要了解Onvif协议实现细节、构建监控或智能家居应用的开发者。通过这套文件能够快速掌握局域网摄像头发现流程、RTSP地址解析方法以及MediaPlayer/ExoPlayer的接入方式减少从零摸索协议的成本。 做Android端的摄像头接入最头疼的往往不是写代码而是“怎么知道摄像头在哪儿”。厂家的私有SDK要申请、要审核而且海康的SDK和大华的SDK互不通用一个App想兼容多家摄像头基本等于把每个品牌的接入协议都啃一遍。但如果目标只是“在局域网里自动发现摄像头拿到播放地址然后把流拉出来”用Onvif协议是一条成本低得多的路。这篇内容就围绕这个主题展开Android如何通过Onvif协议的WS-Discovery机制在局域网内自动发现摄像头设备再从设备上拿到可用的RTSP播放地址。适合要做安防App、巡检工具、局域网监控大屏或者家里装了各种品牌摄像头想统一管理的开发者参考。我会把发现、鉴权、取流这条完整链路拆开讲同时把你最可能踩的坑提前排掉。1. 开始动手前先区分“Onvif负责哪一段”1.1 Onvif能做的和不能做的先说结论Onvif协议不传视频流。它是一种基于SOAP/XML的Web Service协议本质是一套“设备管理接口”。你要发现摄像头、获取设备信息、拿到RTSP地址这些都是Onvif的活儿。但真正开始播放视频的时候走的是RTSP协议。打个比方Onvif是服务台的电话你通过它问清楚“这个摄像头在第几号窗口取餐”RTSP就是取餐窗口你拿着Onvif交给你的凭证去窗口把视频流端回来。两个环节缺一不可很多人把Onvif理解成一个拉流协议一上来就找“Onvif的播放SDK”方向就偏了。这个认知非常关键。整个项目的架构就是两条线第一条线是控制面走HTTPSOAP信令第二条线是媒体面走RTSP/RTP。Android端要做的事情就是先把控制面跑通拿到媒体面的地址再交给播放器。1.2 为什么不用各家私有SDK做多品牌接入方案无非三种私有SDK、直接猜RTSP地址、Onvif标准协议。三者的区别我用真实项目里碰到的场景来对比。接入方案优点典型坑厂商私有SDK对接快功能全通道管理和云台控制都封装好了SDK体积大线程模型复杂换一个品牌就要重新集成一套直接猜RTSP地址最简单海康大华地址格式网上到处都是不同固件版本、不同通道格式不固定密码改了之后地址直接失效Onvif标准协议不挑品牌不预知IP设备开启Onvif就能用标准化接口拿到地址每个厂商对标准的实现有细节差异需要做兼容处理如果你的场景是“未知品牌、未知IP、未知通道”的统一接入Onvif是唯一能做成通用方案的路。私人接一套两套摄像头的直接猜地址反而最快这个我心里有数。但凡是产品型项目建议还是从Onvif切入。1.3 动手前必须确认的三个前提在写任何代码之前先检查三件事缺一个后面的工作都白做。设备侧摄像头必须开启Onvif。海康大部分型号默认开启大华部分型号默认关闭杂牌摄像头通常开着。这个可以在摄像头的Web管理后台里找到“ONVIF”或者“网络服务”开关。网络侧手机和摄像头必须在同一个局域网注意是同一个子网不是“都能连上同一个WiFi”就可以了。如果路由器开了AP隔离两台设备即使在同一WiFi下也互不相通这个问题后面专门讲。账号侧必须知道摄像头的管理员账号密码。很多人卡在这一步因为Onvif拉流用的是摄像头自己的Web登录账号不是App的账号也不是云平台账号。这三件事确认完再把Android工程的代码写起来成功率会高非常多。2. 核心原理WS-Discovery是怎么把设备“喊”出来的2.1 UDP组播不用知道IP也能找到设备Onvif设备发现走的协议叫WS-DiscoveryWeb Services Dynamic Discovery本质是一个UDP组播机制。设备上电后会一直监听局域网里的239.255.255.250:3702这个组播地址。客户端往这个地址发一条Probe消息所有开着Onvif的设备都会回一条ProbeMatch响应把自己的设备服务地址告诉客户端。组播的方式跟单播有本质区别它不需要知道摄像头的IP。你往组播地址发一条“谁在听”的消息局域网里所有Onvif设备都会应答。这和“在小区里喊一嗓子谁家有摄像头就回句话”是一个道理。所以无论摄像头的IP是DHCP分配的还是手工配的静态地址都不影响发现流程。这里要留意的是组播只负责“找到设备”不负责“控制设备”。ProbeMatch里返回的是一个HTTP地址后续跟摄像头打交道的SOAP请求全走这个地址跟组播就没有关系了。2.2 Probe请求和响应长什么样Probe消息本身是一段SOAP XML通过UDP发到组播地址。我贴一段实际用过的请求报文核心结构?xml version1.0 encodingUTF-8? e:Envelope xmlns:ehttp://www.w3.org/2003/05/soap-envelope e:Body d:Probe xmlns:dhttp://docs.oasis-open.org/ws-dd/ns/discovery/2009/01 d:Types xmlns:dp0http://www.onvif.org/ver10/network/wsdldp0:NetworkVideoTransmitter/d:Types /d:Probe /e:Body /e:EnvelopeType字段告诉设备你要找什么类型的服务。NetworkVideoTransmitter表示网络视频发射器也就是摄像头。有些设备不识别这个Type那就把Types字段去掉返回的就是所有支持WS-Discovery的设备再自己过滤。设备回包里的核心字段是XAddrs它给出了设备服务地址格式类似http://192.168.1.64/onvif/device_servicehttp://192.168.1.64:8080/onvif/device_service拿到这个地址后续的GetCapabilities、GetProfiles、GetStreamUri全部通过这个入口走HTTP SOAP请求完成。所以XAddrs是整个链路的地基解析到这里出错后面全崩。2.3 为什么组播发了却没收到响应这是所有人都会遇到的第一个拦路虎。代码发出去了Logcat里一点动静都没有。我按排查顺序整理一下原因你自己对照着查Android默认收不到组播包需要在代码里拿到WifiManager.MulticastLock并acquire这点我在下一节详细讲。手机和摄像头不在同一个子网检查WiFi是不是访客网络或者路由器有没有开AP隔离。部分型号的摄像头只回复同网段内的组播请求并且只响应一次。如果第一次Probe没收到重试时要间隔久一点。公司网络环境经常有“无线隔离”策略PC能发现设备手机死活不行就是这个原因。我印象最深的一次就是在公司测试机上排查了一下午最后发现是无线接入点开了“用户隔离”把设备之间的组播封掉了。所以拿到问题先看网络环境再看代码顺序别搞反。3. Android工程环境权限、网络策略、多播锁3.1 清单文件和网络权限Android工程的网络配置是第一个藏坑点。先把Manifest里的权限写全缺一个后面就是各种“收不到”和“连不上”uses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.ACCESS_WIFI_STATE / uses-permission android:nameandroid.permission.CHANGE_WIFI_MULTICAST_STATE /ADDCHANGE_WIFI_MULTICAST_STATE这个权限很容易被漏掉。它不是普通网络权限而是专门控制组播收发的开关没有它MulticastLock就拿不到。如果你用的是Android高版本还要注意运行时权限的问题。虽然INTERNET属于正常权限不需要动态申请但部分国产ROM对网络权限管控严格建议在设置里确认应用有网络访问权。定位权限这里不需要因为我们是靠组播发现不靠地理位置。3.2 Android 9开始默认禁止明文HTTP直接使用http明文请求Onvif的SOAP接口几乎都是http开头不走HTTPS。如果你的targetSdkVersion28Android会默认禁止应用使用明文HTTP流量表现就是“代码没问题但所有SOAP请求全部失败日志报ERR_CLEARTEXT_NOT_PERMITTED”。解决办法有两种。最简单粗暴的是在application节点里加上application android:usesCleartextTraffictrue严格一点的做法是通过networkSecurityConfig指定哪些域名允许明文比如只允许局域网IP段。考虑到项目里摄像头IP不固定通常开发阶段直接开usesCleartextTraffic是最省事的上线前再收紧策略。3.3 MulticastLock必须显式持有Android的Wifi模块默认会过滤掉组播包这是出于省电考虑。你从代码里发送组播没问题问题在于收不到回复。必须拿到WifiManager的MulticastLock并显式调用acquire设备才会在组播地址上继续监听。WifiManager wifi (WifiManager) context.getApplicationContext() .getSystemService(Context.WIFI_SERVICE); WifiManager.MulticastLock lock wifi.createMulticastLock(onvif_discovery); lock.acquire();这里的坑在于acquire之后很多人忘记释放。MulticastLock持有期间WiFi芯片会一直处于高功耗模式影响待机续航和网络性能。正确写法是在发现流程结束后finally块里调用release。如果你用的是线程池或协程要特别注意锁的持有时机不要整个App生命周期都持锁。另外一定要在子线程里跑发现流程和SOAP请求。UDP套接字操作和XML解析都非常耗时放UI线程跑轻则卡顿重则直接ANR。我的做法是发现逻辑封装成一个线程池任务通过回调通知主线程更新UI。4. 核心实现从发现设备到拿到RTSP地址4.1 发送WS-Discovery请求先上最核心的发现代码。这段逻辑就是拼接XML报文用DatagramSocket发到组播地址再阻塞等待设备回包。我用的都是Java标准库不依赖第三方框架方便你理解完整链路private ListString discoverOnvifDevices(int timeout) throws IOException { ListString deviceUrls new ArrayList(); MulticastSocket socket new MulticastSocket(); socket.setReuseAddress(true); socket.setSoTimeout(timeout); String probeXml ?xml version\1.0\ encoding\UTF-8\? e:Envelope xmlns:e\http://www.w3.org/2003/05/soap-envelope\ e:Body d:Probe xmlns:d\http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01\/ /e:Body /e:Envelope; InetAddress group InetAddress.getByName(239.255.255.250); DatagramPacket packet new DatagramPacket( probeXml.getBytes(StandardCharsets.UTF_8), probeXml.getBytes(StandardCharsets.UTF_8).length, group, 3702); socket.send(packet); long endTime System.currentTimeMillis() timeout; while (System.currentTimeMillis() endTime) { byte[] buf new byte[4096]; DatagramPacket response new DatagramPacket(buf, buf.length); try { socket.receive(response); String xml new String(response.getData(), 0, response.getLength(), StandardCharsets.UTF_8); String xaddr parseXAddrs(xml); if (xaddr ! null !deviceUrls.contains(xaddr)) { deviceUrls.add(xaddr); } } catch (SocketTimeoutException e) { // 超时跳出不要在这里吞掉异常 break; } } socket.close(); return deviceUrls; }有几点需要注意。SOAP包发送后设备不一定会立即回复有些老摄像头要等几百毫秒才应答所以接收循环的超时时间建议设2~3秒。另外socket.setReuseAddress(true)在切换WiFi场景下有用能避免地址被占用导致的下一次发现失败。4.2 解析ProbeMatchParseXAddrs最省事的办法是用正则直接抓http链接。虽然粗暴但实测可靠前提是报文格式标准。我从响应里取到的URL一般是这样的http://192.168.1.64/onvif/device_servicehttp://192.168.1.64:8080/onvif/device_serviceprivate String parseXAddrs(String xml) { Pattern pattern Pattern.compile(http://[^]); Matcher matcher pattern.matcher(xml); if (matcher.find()) { return matcher.group(); } return null; }如果你的项目对报文解析要求高也可以用XmlPullParser严格按命名空间解析但开发阶段先用正则跑通链路最重要。XAddrs拿到后不要直接全信部分设备会返回多个地址一般取第一个能通的。4.3 鉴权与GetCapabilities拿到设备服务地址后下一个动作是GetCapabilities目的是拿到媒体服务MediaXAddr。大部分摄像头这一步需要鉴权用户名密码是摄像头Web后台的登录账号。Onvif标准推荐WS-Security UsernameToken鉴权现代框架里一般封装好了。但你用HttpURLConnection直接拼SOAP也可以流程是构造一个带Authorization头的POST请求把SOAP报文发过去解析响应里的MediaXAddr。如果这一步返回401优先检查用户名密码是否对其次检查摄像头后台是不是开启了单独的Onvif账号。部分设备允许设置一个“只给Onvif用”的独立账号与Web登录账号分离很多人在这一步被卡住。4.4 GetProfiles GetStreamUri真正的落点取RTSP地址的完整流程分三步第一步对设备服务地址发GetCapabilities从响应里拿到MediaXAddr通常是http://ip:port/onvif/Media这样的地址。第二步对MediaXAddr发GetProfiles拿到摄像头支持的Profile列表。每个Profile相当于一条视频流配置比如主码流、子码流。从这里提取ProfileToken它是后面取流的关键参数。第三步用ProfileToken发GetStreamUri响应里的Uri字段就是你要的最终播放地址。常见返回格式有两种rtsp://192.168.1.64:554/Streaming/Channels/101rtsp://192.168.1.64:554/profile1GetStreamUri的SOAP请求片段大致是这样的soap:Envelope xmlns:soaphttp://www.w3.org/2003/05/soap-envelope soap:Body trt:GetStreamUri xmlns:trthttp://www.onvif.org/ver10/media/wsdl trt:ProfileTokenprofile_token_here/trt:ProfileToken /trt:GetStreamUri /soap:Body /soap:Envelope注意ProfileToken必须严格用GetProfiles返回的原始值大小写都不能改否则设备会回InvalidArgVal错误。到这一步播放地址就真正拿到了可以丢给播放器开始渲染。4.5 地址改写不要盲信任设备返回的Host这个坑非常典型而且不踩一次很难发现。某些摄像头在GetStreamUri返回的RTSP地址里Host部分不是IP地址而是设备主机名比如rtsp://IPC-1234567890:554/Streaming/Channels/101App端根本没有这个主机名的DNS解析播放器连上去必然失败。解决办法是在拿到URL后做一次“地址改写”把Host字段强制替换成你之前发现的设备IP。Uri uri Uri.parse(streamUri); Uri.Builder builder uri.buildUpon(); builder.authority(deviceIp); // 替换为发现阶段的IP String finalUrl builder.build().toString();这一步建议写在所有厂商分支之前因为无论设备端怎么折腾和我们在同一个局域网通信的IP一定是有效的。4.6 播放器选型与实测表现Android自带的MediaPlayer不支持RTSP这条路可以直接排除。我在多个项目里实测过的方案有三个ExoPlayer从2.13之后加入了RTSP支持但它的生态重心在DASH和HLSRTSP兼容性一般部分海康设备拿流地址后会卡顿甚至花屏适合做验证不适合做生产。ijkplayer是最常规的选择流传输模式设置为rtsp_transporttcp后非常稳支持硬解缺点是项目已经很久没大更新了。如果你能接受它的依赖体积它仍是商用项目里最稳的方案。VLC for Android功能最全但体积大集成复杂度高一般用于工具型App而不是面向市场的产品。我的生产建议核心播放用ijkplayer把rtsp_transport设为tcp选用子码流做预览实测局域网延迟在300ms左右足够日常监控预览需求。5. 兼容性、超时与排查技巧5.1 常见问题速查表把我在实施过程中遇到的典型问题整理成了一个表。你按表排查效率会高很多。问题现象可能原因解决办法组播发了收不到回包未获取MulticastLock / AP隔离获取锁或者关闭无线的“用户隔离”设备发现了但GetCapabilities返回401用户名密码错误或Onvif账号未开启在设备Web后台重置密码、开启Onvif账号GetStreamUri返回InvalidArgValProfileToken传错或设备版本太老重新GetProfiles原样复制token拿到地址但播放黑屏码流格式不兼容或默认UDP传输被防火墙拦改用rtsp_transporttcp或者换子码流局域网正常切到热点就失败手机和设备不在同一子网确认手机与摄像头必须同网段设备A能发现设备B不行厂商B默认关闭了主动发现到设备后台找“ONVIF发现”或“组播”开关5.2 不同品牌的兼容性差异Onvif虽然是标准协议但不同厂商的“方言”确实不少。海康大部分型号默认开启Onvif设备服务端口常见80或8000RTSP路径是/Streaming/Channels/101101表示主码流102表示子码流Manager账号和Onvif账号通常是同一个。大华部分型号需要手动开启OnvifRTSP路径形如/cam/realmonitor?channel1subtype0在Onvif响应里返回的地址格式跟海康完全不同。这个不影响我们前面写的通用逻辑但会影响你直接拿地址给播放器的兼容性。TP-LINK、中维世纪、雄迈这些品牌的标准做得相对规范但细节参数出入仍然存在。我的建议是开发前先用Windows端的ONVIF Device Tool扫一遍设备它能直接展示设备发现结果、媒体服务地址、以及最终拿到的RTSP地址。工具里能跑通再在Android端复现能省下非常多排查时间。5.3 信令请求要加超时发现流程要能重试SOAP请求走HTTP协议必须显式设置连接超时和读超时否则某个设备响应慢可能卡住整个发现流程。我习惯的配置是连接超时3秒、读超时5秒。发现流程做成“重试3次间隔1秒”的节奏因为有些老摄像头对组播的响应速度偏慢一次Probe得不到回应不代表设备不存在。另外有一个很容易被忽视的坑频繁调用GetStreamUri时部分设备会因为之前的RTSP会话没有正确释放而返回错误。如果你做的是需要刷新拉流的操作务必在切换流或界面销毁时主动断开播放器会话并在下一次取流前加一点间隔。还有个细节跟线程有关。发现设备和取地址的过程会涉及多轮HTTP请求每轮都要解析XML这个组合操作在低端机上耗时很明显。建议把整个流程放在一个带超时控制的线程池里而不是拆成多个活动分别执行这样逻辑更紧凑也方便做整体重试。做完这个项目给我最大的感受是Onvif协议的坑不在协议本身而在设备厂商的“方言”太多。同一个标准协议十家设备能给你十种细节实现但你只要把发现、鉴权、取流这条主链路跑通剩下的兼容性问题就只是一个个补丁而已。最后再说一个排查技巧电脑上用ONVIF Device Tool这个免费工具先扫一遍网络它能直接告诉你是“设备没开启Onvif”“账号密码不对”还是“Android权限配置有问题”。如果工具里都拿不到地址就别改代码了去设备后台开开关、改密码如果工具里一切正常再回头检查Android的MulticastLock、明文HTTP和子网配置。这套排查顺序能帮你绕过我当初走过的弯路。本文还有配套的精品资源点击获取