ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于Java的GB28181视频监控平台接入实战:从SIP信令到RTP媒体流

基于Java的GB28181视频监控平台接入实战:从SIP信令到RTP媒体流 简介一份基于Java实现的GB28181协议平台源码面向视频监控与安防领域的开发者。该平台遵循国标信令规范覆盖设备注册、状态恢复、目录查询、实时视频流传输等核心场景可用于解决国标设备接入与服务端通信的常见问题。压缩包内共49个文件其中43个为Java源文件承担主要业务逻辑2个XML文件与多个配置文件负责参数设定另有文档和版本管理辅助文件整体仅64KB结构紧凑适合按源码顺序学习。目前平台已实现注册、恢复、目录查询以及实时视频流支持TCP被动与UDP两种模式等功能修改配置后即可编译运行方便快速搭建测试环境。当前已有4240人学习下载对于希望理解国标协议实现、信令流程和流媒体传输机制的Java开发者而言这是一份可直接参考的开源工程兼具学习与二次开发价值。 做视频监控平台接入的人应该都体会过“厂商SDK各写各的”的痛海康一个SDK、大华一个SDK、其他小厂再来一个接口风格千奇百怪光是适配就能磨掉半个团队。GB28181就是冲着这个痛点来的——它定了一套标准的联网协议注册、预览、录像、语音对讲全给你规范好设备只要支持国标平台就能用统一的方式去接入。而JGB28181这个项目就是用Java把这套国标平台从零搭起来的一个完整示例信令、媒体、设备管理都在里边。这篇文章我从协议理解讲到代码落地的关键环节再把我实际调试设备时踩过的坑一并列出来。不管你是准备在公司内部自研一套视频接入平台还是想快速搞定上级平台的级联对接这篇都值得当一份实操手册来参考。1. 整体架构与设计思路为什么视频平台要拆成“信令面”和“媒体面”1.1 GB28181到底在管什么GB28181全称叫做《公共安全视频监控联网系统信息传输、交换、控制技术要求》名字听着很长本质一句话它规定了视频监控设备之间、平台之间怎么“打招呼”和“传视频”。具体拆开看国标定义了三大块内容第一块是信令控制设备注册、心跳、目录查询、云台控制、录像回放这些都是走信令的第二块是媒体传输摄像头拍出来的H.264/H.265视频流怎么封装、怎么通过网络传到平台第三块是系统互联也就是上下级平台之间如何级联、如何把资源目录共享出去。在JGB28181里这三个部分被分别处理SIP层负责信令RTP/PS流处理负责媒体设备目录与状态管理负责互联。这样拆分的好处在后边排查问题时非常明显——信令出问题了抓SIP包画面出问题了抓RTP包互不干扰。1.2 Java生态怎么接SIP和RTP很多人听到GB28181第一反应是“这玩意儿不是C/C的天下吗”。早期确实如此好多厂家提供的SDK都是C动态库。但Java生态做国标平台不是不行反而有几个天然优势。第一SIP协议本身就是文本协议解析报文用Java的字符串处理能力绰绰有余而且成熟的开源SIP栈比如MJSIP基于JAIN SIP、RestComm等可以直接复用。第二媒体流接收依赖高性能网络IONetty对UDP高吞吐场景支持非常好处理一路摄像头的RTP流完全不是问题。第三视频联网平台不是只有协议解析还有设备管理、权限控制、Web展示、录像检索这些业务逻辑用Java做起来效率远高于C。实际项目中我通常把SIP信令服务和RTP媒体服务放在同一个进程里方便内部通信同时用消息队列或本地回调把业务层和协议层解耦避免协议细节污染上层业务。1.3 为什么不直接选RTSP/ONVIF聊GB28181绕不开一个对比问题为什么不用RTSP或者ONVIFRTSP适合单点拉流比如一个播放器去拉一个摄像头但面对几千路摄像头的平台接入RTSP在设备发现、目录管理、级联共享这些场景几乎没有规范支持。ONVIF偏向设备管理控制媒体传输部分却相对弱。GB28181的强项是平台级联网设备只需要知道平台地址注册上来之后平台可以查询目录、按需拉流、甚至把资源共享给上级平台这些都是为规模化运营设计的。所以如果你的目标是“统一接入、统一管理、向上级共享”GB28181几乎是唯一选择。2. 核心流程从设备注册到实时预览的完整链路2.1 REGISTER注册鉴权与周期心跳设备接入平台的第一步是注册走的是SIP的REGISTER方法。你可以把SIP理解成“视频界的电话系统”设备就是打电话的人平台就是交换机。设备向平台发送一个REGISTER请求平台收到后返回401要求鉴权设备用密码计算摘要信息再次发送REGISTER平台校验通过后返回200 OK。这个过程和普通电话的认证逻辑类似只不过认证的相关参数在SIP头域的Authorization字段里。以下是一个典型REGISTER报文的简化结构REGISTER sip:34020000002000000001192.168.1.10:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.1.20:5060;rport;branchz9hG4bK12345 From: sip:34020000001320000001192.168.1.20;tagabc To: sip:34020000002000000001192.168.1.10 Call-ID: shell192.168.1.20 CSeq: 1 REGISTER Expires: 3600 Content-Length: 0注册成功不等于万事大吉。国标要求设备周期发送心跳默认60秒一次通过MESSAGE方法携带Keepalive类型的XML消息。平台如果在指定时间一般3个心跳周期内收不到心跳就会把设备标记为离线。我要特别提醒心跳超时时间的配置别太短有些设备网络抖动一下你直接把它判定下线会引起大量误报我一般把离线判定时间调到心跳周期乘以4。2.2 INVITE拉流与SDP媒体协商注册搞定之后用户点开预览平台向设备发起INVITE请求这就是SIP里的“拨号”过程。INVITE请求的消息体里携带SDP信息描述平台希望接收什么样的媒体流。一个典型的视频SDP片段看起来这样v0 o34020000002000000001 0 0 IN IP4 192.168.1.10 sPlay cIN IP4 192.168.1.10 t0 0 mvideo 10000 RTP/AVP 96 98 arecvonly artpmap:96 PS/90000 artpmap:98 H264/90000设备收到INVITE后如果认可这个媒体参数就回复200 OK并在SDP里写上自己的媒体地址和发送方向。之后平台回一个ACK设备就开始往平台指定的IP和端口推RTP流了。整个过程对应到电话就是拨号INVITE对方接听200 OK确认接通ACK开始说话RTP流。这里有个坑部分厂商的设备在发完200 OK后并不会等ACK而是立刻开始推流平台必须在发送200 OK响应之前就把收流UDP端口准备好否则前几个RTP包就丢了画面可能起不来或者出现短暂花屏。2.3 设备管理信令目录查询、云台控制与语音对讲除了注册和拉流国标还定义了很多常用操作。目录查询平台向设备发送MESSAGE携带Catalog查询指令设备返回以XML形式组织的设备列表里面包含摄像头通道ID、名称、经纬度、状态等信息。云台控制平台下发Control指令控制摄像机上下左右转动、变倍变焦、预置位设置等。语音对讲平台向设备发起音频方向的INVITE协商一个双向RTP音频通道把平台麦克风采集的音频发给设备同时把设备端音频收回平台。这些操作看起来是单个指令但需要注意请求超时和事务匹配。SIP是事务型协议一个请求必须有对应的响应平台实现时要建立Call-ID与操作类型的映射关系防止多个指令交叉错乱。我自己在实现时就是用一个ConcurrentHashMap缓存“Call-ID - 实际业务操作”收到响应后按Call-ID取回上下文再驱动业务状态机推进。3. 实操落地把一个GB28181平台跑起来3.1 环境准备与工程骨架JGB28181这类项目选型时我的建议是直接以Spring Boot为底座原因很简单设备管理、用户权限、REST API这些都要用Spring Boot能省掉大量重复劳动。基础环境需要JDK 8或17、Maven 3.6、MySQL、Redis。JDK17的虚拟线程在高并发信令场景有优势但如果你的团队对旧版本更熟JDK8也完全够用别为了追新而折腾。工程内部我一般拆成三个模块sip-server负责SIP协议栈的启动和请求分发stream-handler负责RTP流的接收、PS解封装和转码推流web-manager负责设备管理、实时预览页面、录像检索等业务。模块之间通过Spring事件机制或者内部HTTP接口通信尽量避免直接依赖对方的具体实现类。核心依赖大概这样dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdio.netty/groupId artifactIdnetty-all/artifactId /dependency dependency groupIdjavax.sip/groupId artifactIdjain-sip-api/artifactId version1.2.1.4/version /dependency dependency groupIdcom.google.code.gson/groupId artifactIdgson/artifactId /dependency3.2 SIP服务端代码的关键组成实现SIP服务端并不需要把整个JAIN SIP源码看一遍重点只需要实现监听器接口针对几个关键方法做业务分发。我会在SipListener的processRequest里做一层拦截public class Gb28181SipListener implements SipListener { Override public void processRequest(RequestEvent requestEvent) { String method requestEvent.getRequest().getMethod(); switch (method) { case Request.REGISTER - deviceRegisterService.handle(requestEvent); case Request.MESSAGE - messageService.handle(requestEvent); case Request.INVITE - inviteService.handle(requestEvent); case Request.BYE - byeService.handle(requestEvent); case Request.ACK - ackService.handle(requestEvent); default - log.warn(unsupported method: {}, method); } } Override public void processResponse(ResponseEvent responseEvent) { // 处理平台作为客户端发起的请求的响应例如向上一级平台拉流时 responseEvent.getResponse().getStatusCode(); } }这里有一个非常容易被忽略的细节JAIN SIP的监听器默认是单线程回调如果业务处理太慢会阻塞整个协议栈。我的做法是在processRequest里先把RequestEvent交给一个独立线程池处理监听器本身只负责快速返回。线程池大小按预期设备数量配置我一般设为核心线程8、最大线程32、队列容量2000避免设备批量上线时把CPU打满。3.3 媒体流接收、转封装与分发设备推上来的RTP流裸内容其实是PS封装Program Stream的H.264/H.265数据播放器不能直接识别。所以媒体面要做的事情是用Netty监听UDP端口接收RTP包把PS包从RTP payload里剥离出来解PS封装拿到H.264裸流然后转封装成FLV、HLS或RTMP输出给Web端播放。public class RtpServerInitializer extends ChannelInitializerDatagramChannel { Override protected void initChannel(DatagramChannel ch) { ChannelPipeline pipeline ch.pipeline(); pipeline.addLast(new RtpDecoder()); // RTP头解析 pipeline.addLast(new PsDepacketizer()); // PS包解封装 pipeline.addLast(new H264ToFlvHandler()); // H264转FLV tag } }实际部署时很多团队会把转封装这层剥离出来交给ZLMediaKit这类C流媒体服务独立处理Java只负责SIP信令和回调通知。Java进程收到设备RTP流后转发给ZLMediaKit的RTMP端口ZLMediaKit统一输出HLS/FLV/WebRTC性能和稳定性都会好很多。不过纯Java方案也不是不行单机支持一百路以内并发预览完全不吃力主要瓶颈在转封装CPU消耗建议把转码线程池独立出来别跟业务线程互相抢占。3.4 国标设备ID编码规则做GB28181平台无论如何绕不开20位国标编码。很多人第一次接触就被这一串数字搞晕我把它拆成表格就非常清晰位段长度含义示例中心编码8位行政区划/行业中心编码34020000行业编码2位接入的行业域编码20/21/22等类型编码1位设备类型1是中心、2是行业、4是摄像机等4序号7位设备域内序号0000001校验位2位前18位算出的CRC校验码01平台自身的ID是20位编码摄像头的编码也遵循同一套规则只是类型编码不同。校验算法核心是CRC-16如果你用Java写可以直接用现成的CRC16工具类但一定要注意多项式参数和国标要求的保持一致否则设备会直接拒绝注册。我踩过这个坑平台编码校验位算错排查了一个多小时才发现是CRC参数问题。4. 常见问题与排查技巧实录4.1 设备注册不上先查这五个点设备注册不上是最常见的问题也是最能拉开排查效率差距的场景。我自己的排查顺序是第一抓包看SIP端口有没有收到REGISTER如果设备根本发不出来检查设备配置里的平台IP和端口是否正确第二收到REGISTER后平台有没有返回401如果返回的是400/500多半是SIP消息格式不符合国标规范第三设备带鉴权信息的第二次REGISTER有没有到平台如果没到检查设备和平台之间的网络是否有SIP ALG干扰第四平台返回200 OK后设备有没有确认如果设备日志显示注册成功但平台列表里还是离线大概率是设备与平台对心跳字段的解析不一致第五检查设备ID和平台ID是否在同一个中心编码下很多老设备对跨域注册会直接拒绝。我强烈建议在任何排查开始之前先学会用Wireshark的sip过滤条件。一条一条看信令交互比看任何平台日志都直观。4.2 拉流黑屏或花屏的根因画面能出但黑屏问题基本出在媒体协商或RTP传输层面。先确认SDP里平台填的是recvonly还是sendonly方向填错设备会直接把流发出去但平台没接收。然后看RTP包是否到达了平台收流端口常用的排查命令是在平台服务器上执行netstat -unlp看UDP端口是否有数据增长。如果端口有数据但画面依然黑屏基本可以锁定在PS解封装层最常见的坑是RTP payload里PS包跨包分段解封装时没有做正确的拼接。花屏则多与丢包和数据错序有关。局域网内高带宽之下不该有大量丢包如果RTP序列号不连续检查中间网络设备有没有对UDP做限速策略。还有一个隐藏问题多路摄像头并发拉流时平台监听端口如果只有一个UDP Socket高并发容易丢包。我的解法是采用端口池给每路会话分配独立的RTP收流端口从根上隔离相互影响。4.3 语音对讲没有声音语音对讲这类双向音频场景问题比视频多一个方向维度。第一确认设备SDP里m行确实是audio类型且rtpmap声明的编码是PCMA或者PCMU很多设备默认没有开启音频编码协商需要手动在设备端配置。第二确认平台发出去的音频方向和设备期望一致有些设备是sendonly有些是sendrecv如果你只按recvonly去对接平台的音频根本推不进去。第三G.711编码数据裸流播放时需要注意采样率和声道匹配我遇到过一次反反复复没声音最后发现是设备回传的音频采样率是16k而平台按8k解码音调和节奏全乱掉了。语音对讲还有一个经常被忽略的问题回声。平台端如果没有做回声消除说话的人会听到自己的声音延迟返回体验非常差。实现级别可以在采集端过滤参考信号或者直接用带AEC的音频处理库做一轮处理再编码发送。4.4 并发、级联和长时间运行的稳定性项目上线前一定要做一轮长时间稳定性验证。SIP的UDP消息没有连接保活长时间运行后可能出现内存里堆积大量超时事务对象导致内存缓慢增长。我的处理方式是启动一个定时任务把超过60秒没有匹配到响应的事务全部清理掉相当于给信令层做了一次垃圾回收。级联场景会引入新的复杂度下级平台向本级注册本级再向上级注册。在这个链条上每个平台的设备编码必须唯一否则上级平台会因为编码冲突把设备抛弃。而且信令超时时间在级联下要逐级放大下级平台到本级是1秒超时本级到上级可能就需要2到3秒否则中间网络稍有抖动拉流任务就会反复重试把带宽打满。另外UDP动态端口在长时间运行后可能被占用完平台一定要配置收流端口池并加入释放机制。收到BYE或者设备离线后立即释放对应端口和内存资源否则运行一周后平台大概率出现“新设备拉不动流”的诡异现象。提示如果平台要对外开放公网接入务必把SIP信令端口和RTP媒体端口分开映射千万别只映射5060端口。大量“设备注册成功但看不到视频”的问题都是NAT下媒体端口没有映射导致的。我在实际做国标平台的过程中最大的感受是协议本身并不难难的是各种设备之间“看似标准、实则各搞一套”的兼容性适配。JGB28181这类Java实现的价值恰恰在于把复杂的协议栈封装得相对可控遇到问题可以快速看源码、改逻辑而不是对着厂商文档干瞪眼。最后分享一个调试技巧手头没有真机的时候用SIPp模拟设备注册和INVITE拉流能让信令联调效率提升好几倍。等真机到位之后再把个别兼容性差异逐个打磨整个平台就会越用越稳。本文还有配套的精品资源点击获取
返回列表