技术背景
好多开发者在用我们Android平台GB28181设备接入模块的时候,更希望跟我们探讨一些协议规范方面,以便在现场对接时,可以知其然知其所以然。比如,有开发者提到,GB28181的状态消息报送这块到底要怎么实现?希望我们能做个基础的普及。本文结合大牛直播SDK的模块,抛砖引玉,做个基础的讲解。
先看GB/T281810-2016规范针对状态消息报文基本要求:
当源设备(包括网关、SIP设备、SIP客户端或联网系统)发现工作异常时,应立即向本 SIP监控域的SIP服务器发送状态信息;无异常时,应定时向本SIP监控域的SIP服务器发送状态信息。
SIP设备 29 GB/T28181—2016宜在状态信息中携带故障子设备描述信,状态信息报送采用IETFRFC 3428中定义的方法 Message实现。
通过周期性的状态信息报送,实现注册服务器与源设备之间的状态检测即心跳机制。 心跳发送方、接收方需统一配置“心跳间隔”参数,按照“心跳间隔”定时发送心跳消息,默认心跳间隔60s。
心跳发送方、接收方需统一配置“心跳超时次数”参数,心跳消息连续超时达到“心跳超时次数” 则认为对方下线,默认心跳超时次数3次。
为此,我们在Android平台GB28181设备接入模块(SmartGBD)设置的参数如下:
private int gb28181_heartbeat_interval_ = 20; // 心跳间隔GB28181默认是60, 目前调整到20秒 private int gb28181_heartbeat_count_ = 3; // 心跳间隔3次失败,表示和服务器断开了
GB28181设备接入侧初始化initGB28181Agent()的时候,把心跳参数投递下去:
// GB28181配置 gb28181_agent_.config(gb28181_reg_expired_, gb28181_heartbeat_interval_, gb28181_heartbeat_count_);
心跳接收方在心跳发送方上线状态下检测到心跳消息连续超时达到商定次数则认为心跳发送方离 线;心跳发送方在心跳接收方上线状态下检测到心跳消息响应消息连续超时达到商定次数则认为心跳接收方离线。
状态信 息 报 送 命 令 应 包 括 命 令 类 型 (CmdType)、设 备/系 统 编 码 (DeviceID)、是 否 正 常 工 作 (Status)等,采用 Message方法的消息体携带。 Message消息的成功和错误应答均无消息体。
周期性的心跳示例
MESSAGE sip:34020000002000000001@3402000000 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.120:5070;rport;branch=z9hG4bK1200121904 From: <sip:34020000002000000003@3402000000>;tag=858896904 To: <sip:34020000002000000001@3402000000> Call-ID: 1700090587 CSeq: 20 MESSAGE Content-Type: Application/MANSCDP+xml Max-Forwards: 70 User-Agent: IP Camera Content-Length: 177 <?xml version="1.0" encoding="GB2312"?> <Notify> <CmdType>Keepalive</CmdType> <SN>33</SN> <DeviceID>34020000002000000003</DeviceID> <Status>OK</Status> <Info> </Info> </Notify>
心跳异常处理
基础的心跳,大多数开发者弄的明白,心跳异常处理,需要格外注意,以下是我们针对心跳异常,做的逻辑实现,感兴趣的开发者可以参考:
/* * CameraPublishActivity.java * 心跳异常处理 * Author: https://daniusdk.com * WeChat: xinsheng120 */ public void ntsOnHeartBeatException(int exceptionCount, String lastExceptionInfo) { Log.e(TAG, "ntsOnHeartBeatException heart beat timeout count reached, count:" + exceptionCount+ ", exception info:" + (lastExceptionInfo!=null?lastExceptionInfo:"")); // 停止信令, 然后重启 handler_.postDelayed(new Runnable() { public void run() { Log.i(TAG, "gb28281_heart_beart_timeout"); record_executor_.cancel_tasks(); stopRecordDownloads(true); stopPlaybacks(true); stopAudioPlayer(); destoryRTPReceiver(); gb_broadcast_source_id_ = null; gb_broadcast_target_id_ = null; btnGB28181AudioBroadcast.setText("GB28181语音广播"); btnGB28181AudioBroadcast.setEnabled(false); stopGB28181Stream(); destoryRTPSender(); if (gb28181_agent_ != null) { gb28181_agent_.terminateAllAudioBroadcasts(true); gb28181_agent_.terminateAllPlays(true); Log.i(TAG, "gb28281_heart_beart_timeout sip stop"); gb28181_agent_.stop(); String local_ip_addr = IPAddrUtils.getIpAddress(context_); if (local_ip_addr != null && !local_ip_addr.isEmpty() ) { Log.i(TAG, "gb28281_heart_beart_timeout get local ip addr: " + local_ip_addr); gb28181_agent_.setLocalAddress(local_ip_addr); } record_executor_.cancel_tasks(); initPlaybacks(null); initRecordDownloads(null); Log.i(TAG, "gb28281_heart_beart_timeout sip start"); gb28181_agent_.start(); } } },0); }
总结
以上是GB28181状态信息报送流程,相对其他逻辑实现,比如视频回传、语音广播、历史视音频下载回放等,这块难度不达,需要注意的是心跳异常处理这块,感兴趣的开发者,可以单独跟我沟通。