GB/T28181技术背景
在此之前,我们先对协议规范做个简单了解:GB28181协议是一种用于视频监控系统互联互通的国际标准,它定义了视频监控系统中的设备间如何进行通信、交换数据和协调控制。以下是GB28181协议的一些主要内容:
1.设备互联互通
GB28181协议的核心是实现不同厂商、不同品牌、不同型号的设备之间的互联互通。通过该协议,可以实现视频监控系统的统一管理和调度,以及设备间的信息共享和联动控制。
1.实时流传输
GB28181协议支持实时流传输,包括视频流、音频流、报警信息等。在传输过程中,协议提供了可靠的传输机制,确保数据能够及时、准确地传输到目的地。
1.设备管理
视频监控系统中,设备的管理和维护是非常重要的。GB28181协议定义了设备的注册、认证、授权、配置等管理操作,以及设备状态监测、故障诊断等功能,为系统的稳定运行提供了保障。
1.数据交互
GB28181协议支持设备之间的数据交互,包括视频、音频、报警信息等数据的共享和转发。同时,还支持设备的控制指令和命令的传递,实现了系统中的双向通信。
1.安全保障
GB28181协议提供了多种安全保障机制,包括用户认证、授权管理、数据加密、访问控制等,确保系统的安全性和可靠性。
1.标准性和可扩展性
GB28181协议遵循开放式架构的原则,具有良好的标准性和可扩展性。同时,协议还提供了与其他标准(如ONVIF、PSI等)的兼容性,方便了系统的集成和融合。
总之,GB28181协议是一种适用于视频监控系统的互联互通协议,它具有设备互联互通、实时流传输、设备管理、数据交互和安全保障等特点。通过该协议的应用,可以实现视频监控系统的统一管理和调度,提高系统的效率和使用体验。
Android平台如何实现GB28181设备对接?
Android平台GB28181接入模块设计的目的,可实现不具备国标音视频能力的 Android终端,通过平台注册接入到现有的GB/T28181—2016服务,可用于如智能监控、智慧零售、智慧教育、远程办公、生产运输、智慧交通、车载或执法记录仪等场景。
Android终端除支持常规的音视频数据接入外,还可以支持移动设备位置(MobilePosition)订阅和通知、语音广播和语音对讲、云台控制回调和预置位查询,支持对接数据类型如下:
- 编码前数据(目前支持的有YV12/NV21/NV12/I420/RGB24/RGBA32/RGB565等数据类型);
- 编码后数据(如无人机等264/HEVC数据,或者本地解析的MP4音视频数据);
- 拉取RTSP或RTMP流并接入至GB28181平台(比如其他IPC的RTSP流,可通过Android平台GB28181接入到国标平台)。
功能支持
- [视频格式]H.264/H.265(Android H.265硬编码);
- [音频格式]G.711 A律、AAC;
- [音量调节]Android平台采集端支持实时音量调节;
- [H.264硬编码]支持H.264特定机型硬编码;
- [H.265硬编码]支持H.265特定机型硬编码;
- [软硬编码参数配置]支持gop间隔、帧率、bit-rate设置;
- [软编码参数配置]支持软编码profile、软编码速度、可变码率设置;
- 支持纯视频、音视频PS打包传输;
- 支持RTP OVER UDP和RTP OVER TCP被动模式;
- 支持信令通道网络传输协议TCP/UDP设置;
- 支持注册、注销,支持注册刷新及注册有效期设置;
- 支持设备目录查询应答;
- 支持心跳机制,支持心跳间隔、心跳检测次数设置;
- 支持移动设备位置(MobilePosition)订阅和通知;
- 支持语音广播;
- 支持语音对讲;
- 支持云台控制和预置位查询;
- [实时水印]支持动态文字水印、png水印;
- [镜像]Android平台支持前置摄像头实时镜像功能;
- [实时静音]支持实时静音/取消静音;
- [实时快照]支持实时快照;
- [降噪]支持环境音、手机干扰等引起的噪音降噪处理、自动增益、VAD检测;
- [外部编码前视频数据对接]支持YUV数据对接;
- [外部编码前音频数据对接]支持PCM对接;
- [外部编码后视频数据对接]支持外部H.264数据对接;
- [外部编码后音频数据对接]外部AAC数据对接;
- [扩展录像功能]支持和录像SDK组合使用,录像相关功能。
接口设计
以Android平台Camera2对接为例,信令部分需要实现如下标红接口:
public class MainActivity extends Activity implements ViewTreeObserver.OnGlobalLayoutListener, Camera2Listener, GBSIPAgentListener, GBSIPAgentPlayListener, GBSIPAgentAudioBroadcastListener, GBSIPAgentDeviceControlListener, GBSIPAgentQueryCommandListener, GBSIPAgentTalkListener{ }
媒体数据处理接口,可参照SmartPublisherJniV2.java,如需语音广播或语音对讲,可参照SmartPlayerJniV2.java。
信令处理
GBSIPAgentListener主要系GB28181注册、心跳、DevicePosition等,如注册成功、注册超时、注册网络传输层错误、心跳异常、设备位置请求处理:
public interface GBSIPAgentListener { /*注册成功 * @param dateString: 服务器日期,用来校准设备端时间,用户自行决定是否校准设备时间 */ void ntsRegisterOK(String dateString); /* *注册超时 */ void ntsRegisterTimeout(); /* *注册网络传输层异常 */ void ntsRegisterTransportError(String errorInfo); /* *心跳达到异常次数 */ void ntsOnHeartBeatException(int exceptionCount, String lastExceptionInfo); /* * 设备位置请求, 这个主要用在移动设备位置订阅上 * @param interval 请求间隔, 单位是毫秒 */ void ntsOnDevicePositionRequest(String deviceId, int interval); }
GBSIPAgentPlayListener主要系GB28181的Invite、Ack、Bye等处理:
public interface GBSIPAgentPlayListener { /* *收到s=Play的实时视音频点播 */ void ntsOnInvitePlay(String deviceId, SessionDescription sessionDescription); /* *发送play invite response 异常 */ void ntsOnPlayInviteResponseException(String deviceId, int statusCode, String errorInfo); /* * 收到CANCEL play INVITE请求 */ void ntsOnCancelPlay(String deviceId); /* * 收到Ack */ void ntsOnAckPlay(String deviceId); /* * 收到Bye */ void ntsOnByePlay(String deviceId); /* * 不是在收到BYE Message情况下, 终止Play */ void ntsOnTerminatePlay(String deviceId); /* * Play会话对应的对话终止, 一般不会出发这个回调,目前只有在响应了200K, 但在64*T1时间后还没收到ACK,才可能会出发 收到这个, 请做相关清理处理 */ void ntsOnPlayDialogTerminated(String deviceId); }
GBSIPAgentAudioBroadcastListener主要系GB28181语音广播处理相关,如有语音广播相关需求,可参照demo实例实现:
public interface GBSIPAgentAudioBroadcastListener { /* *收到语音广播通知 */ void ntsOnNotifyBroadcastCommand(String fromUserName, String fromUserNameAtDomain, String sn, String sourceID, String targetID); /* *需要准备接受语音广播的SDP内容 */ void ntsOnAudioBroadcast(String commandFromUserName, String commandFromUserNameAtDomain, String sourceID, String targetID); /* *音频广播, 发送Invite请求异常 */ void ntsOnInviteAudioBroadcastException(String sourceID, String targetID, String errorInfo); /* *音频广播, 等待Invite响应超时 */ void ntsOnInviteAudioBroadcastTimeout(String sourceID, String targetID); /* *音频广播, 收到Invite消息最终响应 */ void ntsOnInviteAudioBroadcastResponse(String sourceID, String targetID, int statusCode, SessionDescription sessionDescription); /* * 音频广播, 收到BYE Message */ void ntsOnByeAudioBroadcast(String sourceID, String targetID); /* * 不是在收到BYE Message情况下, 终止音频广播 */ void ntsOnTerminateAudioBroadcast(String sourceID, String targetID); }
GBSIPAgentDeviceControlListener主要系GB28181设备控制相关,比如远程启动、云台控制:
public interface GBSIPAgentDeviceControlListener { /* * 收到远程启动控制命令 */ void ntsOnDeviceControlTeleBootCommand(String deviceId, String teleBootValue); /* * 云台控制 */ void ntsOnDeviceControlPTZCmd(String deviceId, String typeValue); }
GBSIPAgentQueryCommandListener主要系GB28181查询命令,如预置位查询:
public interface GBSIPAgentQueryCommandListener { /* * 设备预置位查询 */ void ntsOnDevicePresetQueryCommand(String fromUserName, String fromUserNameAtDomain, String sn, String deviceId); }
GBSIPAgentTalkListener主要系GB28181语音对讲相关处理:
public interface GBSIPAgentTalkListener { /* *收到s=Talk 语音对讲 */ void ntsOnInviteTalk(String deviceId, SessionDescription sessionDescription); /* *发送talk invite response 异常 */ void ntsOnTalkInviteResponseException(String deviceId, int statusCode, String errorInfo); /* * 收到CANCEL Talk INVITE请求 */ void ntsOnCancelTalk(String deviceId); /* * 收到Ack */ void ntsOnAckTalk(String deviceId); /* * 收到Bye */ void ntsOnByeTalk(String deviceId); /* * 不是在收到BYE Message情况下, 终止Talk */ void ntsOnTerminateTalk(String deviceId); /* * Talk会话对应的对话终止, 一般不会出发这个回调,目前只有在响应了200K, 但在64*T1时间后还没收到ACK,才可能会出发 收到这个, 请做相关清理处理 */ void ntsOnTalkDialogTerminated(String deviceId); }
媒体数据处理
RTP数据发送
RTP Sender(SmartPublisherJniV2.java)相关接口设计:
/* * SmartPublisherJniV2.java * Author: https://daniusdk.com */ /* * 创建RTP Sender实例 * * @param reserve:保留参数传0 * * @return RTP Sender 句柄,0表示失败 */ public native long CreateRTPSender(int reserve); /** *设置 RTP Sender传输协议 * * @param rtp_sender_handle, CreateRTPSender返回值 * @param transport_protocol, 0:UDP, 1:TCP, 默认是UDP * * @return {0} if successful */ public native int SetRTPSenderTransportProtocol(long rtp_sender_handle, int transport_protocol); /** *设置 RTP Sender IP地址类型 * * @param rtp_sender_handle, CreateRTPSender返回值 * @param ip_address_type, 0:IPV4, 1:IPV6, 默认是IPV4, 当前仅支持IPV4 * * @return {0} if successful */ public native int SetRTPSenderIPAddressType(long rtp_sender_handle, int ip_address_type); /** *设置 RTP Sender RTP Socket本地端口 * * @param rtp_sender_handle, CreateRTPSender返回值 * @param port, 必须是偶数,设置0的话SDK会自动分配, 默认值是0 * * @return {0} if successful */ public native int SetRTPSenderLocalPort(long rtp_sender_handle, int port); /** *设置 RTP Sender SSRC * * @param rtp_sender_handle, CreateRTPSender返回值 * @param ssrc, 如果设置的话,这个字符串要能转换成uint32类型, 否则设置失败 * * @return {0} if successful */ public native int SetRTPSenderSSRC(long rtp_sender_handle, String ssrc); /** *设置 RTP Sender RTP socket 发送Buffer大小 * * @param rtp_sender_handle, CreateRTPSender返回值 * @param buffer_size, 必须大于0, 默认是512*1024, 当前仅对UDP socket有效, 根据视频码率考虑设置合适的值 * * @return {0} if successful */ public native int SetRTPSenderSocketSendBuffer(long rtp_sender_handle, int buffer_size); /** *设置 RTP Sender RTP时间戳时钟频率 * * @param rtp_sender_handle, CreateRTPSender返回值 * @param clock_rate, 必须大于0, 对于GB28181 PS规定是90kHz, 也就是90000 * * @return {0} if successful */ public native int SetRTPSenderClockRate(long rtp_sender_handle, int clock_rate); /** *设置 RTP Sender 目的IP地址, 注意当前用在GB2818推送上,只设置一个地址,将来扩展如果用在其他地方,可能要设置多个目的地址,到时候接口可能会调整 * * @param rtp_sender_handle, CreateRTPSender返回值 * @param address, IP地址 * @param port, 端口 * * @return {0} if successful */ public native int SetRTPSenderDestination(long rtp_sender_handle, String address, int port); /** * 设置是否开启 RTP Receiver * @param rtp_sender_handle, CreateRTPSender返回值 * @param is_enable, 0表示不收RTP包, 1表示收RTP包, SDK默认值为0. * @return */ public native int EnableRTPSenderReceive(long rtp_sender_handle, int is_enable); /** *设置RTP Receiver SSRC * * @param rtp_sender_handle, CreateRTPSender返回值 * @param ssrc, 如果设置的话,这个字符串要能转换成uint32类型, 否则设置失败 * * @return {0} if successful */ public native int SetRTPSenderReceiveSSRC(long rtp_sender_handle, String ssrc); /** *设置RTP Receiver Payload 相关信息 * * @param rtp_sender_handle, CreateRTPSender返回值 * * @param payload_type, 请参考 RFC 3551 * * @param encoding_name, 编码名, 请参考 RFC 3551, 如果payload_type不是动态的, 可能传null就好 * * @param media_type, 媒体类型, 请参考 RFC 3551, 1 是视频, 2是音频 * * @param clock_rate, 请参考 RFC 3551 * * @return {0} if successful */ public native int SetRTPSenderReceivePayloadType(long rtp_sender_handle, int payload_type, String encoding_name, int media_type, int clock_rate); /** *设置RTP Receiver PS的pts和dts clock frequency * * @param rtp_sender_handle, CreateRTPSender返回值 * * @param ps_clock_frequency, 默认是90000, 一些特殊场景需要设置 * * @return {0} if successful */ public native int SetRTPSenderReceivePSClockFrequency(long rtp_sender_handle, int ps_clock_frequency); /** *设置 RTP Receiver 音频采样率 * * @param rtp_sender_handle, CreateRTPSender返回值 * @param sampling_rate, 音频采样率 * * @return {0} if successful */ public native int SetRTPSenderReceiveAudioSamplingRate(long rtp_sender_handle, int sampling_rate); /** *设置 RTP Receiver 音频通道数 * * @param rtp_sender_handle, CreateRTPSender返回值 * @param channels, 音频通道数 * * @return {0} if successful */ public native int SetRTPSenderReceiveAudioChannels(long rtp_sender_handle, int channels); /** *初始化RTP Sender, 初始化之前先调用上面的接口配置相关参数 * * @param rtp_sender_handle, CreateRTPSender返回值 * * @return {0} if successful */ public native int InitRTPSender(long rtp_sender_handle); /** *获取RTP Sender RTP Socket本地端口 * * @param rtp_sender_handle, CreateRTPSender返回值 * * @return 失败返回0, 成功的话返回响应的端口, 请在InitRTPSender返回成功之后调用 */ public native int GetRTPSenderLocalPort(long rtp_sender_handle); /** * UnInit RTP Sender * * @param rtp_sender_handle, CreateRTPSender返回值 * * @return {0} if successful */ public native int UnInitRTPSender(long rtp_sender_handle); /** * 释放RTP Sender, 释放之后rtp_sender_handle就无效了,请不要再使用 * * @param rtp_sender_handle, CreateRTPSender返回值 * * @return {0} if successful */ public native int DestoryRTPSender(long rtp_sender_handle);
RTP数据接收
对应RTP Receiver(SmartPlayerJniV2.java)相关接口设计,如无语音广播或语音对讲相关技术需求,这部分可忽略:
/* * SmartPlayerJniV2.java * Author: https://daniusdk.com */ /* * 创建RTP Receiver * * @param reserve:保留参数传0 * * @return RTP Receiver 句柄,0表示失败 */ public native long CreateRTPReceiver(int reserve); /** *设置 RTP Receiver传输协议 * * @param rtp_receiver_handle, CreateRTPReceiver * @param transport_protocol, 0:UDP, 1:TCP, 默认是UDP * * @return {0} if successful */ public native int SetRTPReceiverTransportProtocol(long rtp_receiver_handle, int transport_protocol); /** *设置 RTP Receiver IP地址类型 * * @param rtp_receiver_handle, CreateRTPReceiver * @param ip_address_type, 0:IPV4, 1:IPV6, 默认是IPV4 * * @return {0} if successful */ public native int SetRTPReceiverIPAddressType(long rtp_receiver_handle, int ip_address_type); /** *设置 RTP Receiver RTP Socket本地端口 * * @param rtp_receiver_handle, CreateRTPReceiver * @param port, 必须是偶数,设置0的话SDK会自动分配, 默认值是0 * * @return {0} if successful */ public native int SetRTPReceiverLocalPort(long rtp_receiver_handle, int port); /** *设置 RTP Receiver SSRC * * @param rtp_receiver_handle, CreateRTPReceiver * @param ssrc, 如果设置的话,这个字符串要能转换成uint32类型, 否则设置失败 * * @return {0} if successful */ public native int SetRTPReceiverSSRC(long rtp_receiver_handle, String ssrc); /** *创建 RTP Receiver 会话 * * @param rtp_receiver_handle, CreateRTPReceiver * @param reserve, 保留值,目前传0 * * @return {0} if successful */ public native int CreateRTPReceiverSession(long rtp_receiver_handle, int reserve); /** *获取 RTP Receiver RTP Socket本地端口 * * @param rtp_receiver_handle, CreateRTPReceiver * * @return 失败返回0, 成功的话返回响应的端口, 请在CreateRTPReceiverSession返回成功之后调用 */ public native int GetRTPReceiverLocalPort(long rtp_receiver_handle); /** *设置 RTP Receiver Payload 相关信息 * * @param rtp_receiver_handle, CreateRTPReceiver * * @param payload_type, 请参考 RFC 3551 * * @param encoding_name, 编码名, 请参考 RFC 3551, 如果payload_type不是动态的, 可能传null就好 * * @param media_type, 媒体类型, 请参考 RFC 3551, 1 是视频, 2是音频 * * @param clock_rate, 请参考 RFC 3551 * * @return {0} if successful */ public native int SetRTPReceiverPayloadType(long rtp_receiver_handle, int payload_type, String encoding_name, int media_type, int clock_rate); /** *设置 RTP Receiver 音频采样率 * * @param rtp_receiver_handle, CreateRTPReceiver * @param sampling_rate, 音频采样率 * * @return {0} if successful */ public native int SetRTPReceiverAudioSamplingRate(long rtp_receiver_handle, int sampling_rate); /** *设置 RTP Receiver 音频通道数 * * @param rtp_receiver_handle, CreateRTPReceiver * @param channels, 音频通道数 * * @return {0} if successful */ public native int SetRTPReceiverAudioChannels(long rtp_receiver_handle, int channels); /** *设置 RTP Receiver 远端地址 * * @param rtp_receiver_handle, CreateRTPReceiver * @param address, IP地址 * @param port, 端口 * * @return {0} if successful */ public native int SetRTPReceiverRemoteAddress(long rtp_receiver_handle, String address, int port); /** *初始化 RTP Receiver * * @param rtp_receiver_handle, CreateRTPReceiver * * @return {0} if successful */ public native int InitRTPReceiver(long rtp_receiver_handle); /** *UnInit RTP Receiver * * @param rtp_receiver_handle, CreateRTPReceiver * * @return {0} if successful */ public native int UnInitRTPReceiver(long rtp_receiver_handle); /** *Destory RTP Receiver Session * * @param rtp_receiver_handle, CreateRTPReceiver * * @return {0} if successful */ public native int DestoryRTPReceiverSession(long rtp_receiver_handle); /** *Destory RTP Receiver * * @param rtp_receiver_handle, CreateRTPReceiver * * @return {0} if successful */ public native int DestoryRTPReceiver(long rtp_receiver_handle);
PostAudioPacket(SmartPlayerJniV2.java),投递音频包给外部Live source,目前仅于语音对讲使用:
/* * SmartPlayerJniV2.java * Author: https://daniusdk.com */ /** * 投递音频包给外部Live source, 注意ByteBuffer对象必须是DirectBuffer * * @param handle: return value from SmartPlayerOpen() * * @return {0} if successful */ public native int PostAudioPacket(long handle, int codec_id, java.nio.ByteBuffer packet, int offset, int size, long pts, boolean is_pts_discontinuity, java.nio.ByteBuffer extra_data, int extra_data_offset, int extra_data_size, int sample_rate, int channels);
GB28181接口调用
对应GB28181相关接口调用相关设计如下:
/* * SmartPublisherJniV2.java * Author: https://daniusdk.com */ /** * 设置GB28181 RTP Sender * * @param rtp_sender_handle, CreateRTPSender返回值 * @param rtp_payload_type, 对于GB28181 PS, 协议定义是96, 具体以SDP为准, RFC 3551有定义 * @param encoding_name, 编码名, 请参考 RFC 3551, 当前仅支持: "PS", 其他值返回失败 * @return {0} if successful */ public native int SetGB28181RTPSender(long handle, long rtp_sender_handle, int rtp_payload_type, String encoding_name); /** * 设置GB28181 RTP 收到的音频包回调 * @param handle * @param audio_packet_callback * @return */ public native int SetGB28181ReceiveAudioPacketCallback(long handle, NTAudioPacketCallback audio_packet_callback); /** * 启动 GB28181 媒体流 * * @return {0} if successful */ public native int StartGB28181MediaStream(long handle); /** * 停止 GB28181 媒体流 * * @return {0} if successful */ public native int StopGB28181MediaStream(long handle);
接口调示例代码
private boolean initGB28181Agent() { if ( gb28181_agent_ != null ) return true; getLocation(context_); String local_ip_addr = IPAddrUtils.getIpAddress(context_); Log.i(TAG, "initGB28181Agent local ip addr: " + local_ip_addr); if ( local_ip_addr == null || local_ip_addr.isEmpty() ) { Log.e(TAG, "initGB28181Agent local ip is empty"); return false; } gb28181_agent_ = GBSIPAgentFactory.getInstance().create(); if ( gb28181_agent_ == null ) { Log.e(TAG, "initGB28181Agent create agent failed"); return false; } gb28181_agent_.addListener(this); gb28181_agent_.addPlayListener(this); gb28181_agent_.addAudioBroadcastListener(this); gb28181_agent_.addDeviceControlListener(this); gb28181_agent_.addQueryCommandListener(this); // 必填信息 gb28181_agent_.setLocalAddress(local_ip_addr); gb28181_agent_.setServerParameter(gb28181_sip_server_addr_, gb28181_sip_server_port_, gb28181_sip_server_id_, gb28181_sip_domain_); gb28181_agent_.setUserInfo(gb28181_sip_username_, gb28181_sip_password_); //gb28181_agent_.setUserInfo(gb28181_sip_username_, gb28181_sip_username_, gb28181_sip_password_); // 可选参数 gb28181_agent_.setUserAgent(gb28181_sip_user_agent_filed_); gb28181_agent_.setTransportProtocol(gb28181_sip_trans_protocol_==0?"UDP":"TCP"); // GB28181配置 gb28181_agent_.config(gb28181_reg_expired_, gb28181_heartbeat_interval_, gb28181_heartbeat_count_); com.gb.ntsignalling.Device gb_device = new com.gb.ntsignalling.Device("34020000001380000001", "安卓测试设备", Build.MANUFACTURER, Build.MODEL, "宇宙","火星1","火星", true); if (mLongitude != null && mLatitude != null) { com.gb.ntsignalling.DevicePosition device_pos = new com.gb.ntsignalling.DevicePosition(); device_pos.setTime(mLocationTime); device_pos.setLongitude(mLongitude); device_pos.setLatitude(mLatitude); gb_device.setPosition(device_pos); gb_device.setSupportMobilePosition(true); // 设置支持移动位置上报 } gb28181_agent_.addDevice(gb_device); /* com.gb28181.ntsignalling.Device gb_device1 = new com.gb28181.ntsignalling.Device("34020000001380000002", "安卓测试设备2", Build.MANUFACTURER, Build.MODEL, "宇宙","火星1","火星", true); if (mLongitude != null && mLatitude != null) { com.gb28181.ntsignalling.DevicePosition device_pos = new com.gb28181.ntsignalling.DevicePosition(); device_pos.setTime(mLocationTime); device_pos.setLongitude(mLongitude); device_pos.setLatitude(mLatitude); gb_device1.setPosition(device_pos); gb_device1.setSupportMobilePosition(true); } gb28181_agent_.addDevice(gb_device1); */ if (!gb28181_agent_.createSipStack()) { gb28181_agent_ = null; Log.e(TAG, "initGB28181Agent gb28181_agent_.createSipStack failed."); return false; } boolean is_bind_local_port_ok = false; // 最多尝试5000个端口 int try_end_port = gb28181_sip_local_port_base_ + 5000; try_end_port = try_end_port > 65536 ?65536: try_end_port; for (int i = gb28181_sip_local_port_base_; i < try_end_port; ++i) { if (gb28181_agent_.bindLocalPort(i)) { is_bind_local_port_ok = true; break; } } if (!is_bind_local_port_ok) { gb28181_agent_.releaseSipStack(); gb28181_agent_ = null; Log.e(TAG, "initGB28181Agent gb28181_agent_.bindLocalPort failed."); return false; } if (!gb28181_agent_.initialize()) { gb28181_agent_.unBindLocalPort(); gb28181_agent_.releaseSipStack(); gb28181_agent_ = null; Log.e(TAG, "initGB28181Agent gb28181_agent_.initialize failed."); return false; } return true; }
invite处理逻辑
public void ntsOnInvitePlay(String deviceId, SessionDescription session_des) { handler_.postDelayed(new Runnable() { public void run() { // 先振铃响应下 gb28181_agent_.respondPlayInvite(180, device_id_); MediaSessionDescription video_des = null; SDPRtpMapAttribute ps_rtpmap_attr = null; // 28181 视频使用PS打包 Vector<MediaSessionDescription> video_des_list = session_des_.getVideoPSDescriptions(); if (video_des_list != null && !video_des_list.isEmpty()) { for(MediaSessionDescription m : video_des_list) { if (m != null && m.isValidAddressType() && m.isHasAddress() ) { video_des = m; ps_rtpmap_attr = video_des.getPSRtpMapAttribute(); break; } } } if (null == video_des) { gb28181_agent_.respondPlayInvite(488, device_id_); Log.i(TAG, "ntsOnInvitePlay get video description is null, response 488, device_id:" + device_id_); return; } if (null == ps_rtpmap_attr) { gb28181_agent_.respondPlayInvite(488, device_id_); Log.i(TAG, "ntsOnInvitePlay get ps rtp map attribute is null, response 488, device_id:" + device_id_); return; } Log.i(TAG,"ntsOnInvitePlay, device_id:" +device_id_+", is_tcp:" + video_des.isRTPOverTCP() + " rtp_port:" + video_des.getPort() + " ssrc:" + video_des.getSSRC() + " address_type:" + video_des.getAddressType() + " address:" + video_des.getAddress()); long rtp_sender_handle = libPublisher.CreateRTPSender(0); if ( rtp_sender_handle == 0 ) { gb28181_agent_.respondPlayInvite(488, device_id_); Log.i(TAG, "ntsOnInvitePlay CreateRTPSender failed, response 488, device_id:" + device_id_); return; } gb28181_rtp_payload_type_ = ps_rtpmap_attr.getPayloadType(); gb28181_rtp_encoding_name_ = ps_rtpmap_attr.getEncodingName(); libPublisher.SetRTPSenderTransportProtocol(rtp_sender_handle, video_des.isRTPOverUDP()?0:1); libPublisher.SetRTPSenderIPAddressType(rtp_sender_handle, video_des.isIPv4()?0:1); libPublisher.SetRTPSenderLocalPort(rtp_sender_handle, 0); libPublisher.SetRTPSenderSSRC(rtp_sender_handle, video_des.getSSRC()); libPublisher.SetRTPSenderSocketSendBuffer(rtp_sender_handle, 2*1024*1024); // 设置到2M libPublisher.SetRTPSenderClockRate(rtp_sender_handle, ps_rtpmap_attr.getClockRate()); libPublisher.SetRTPSenderDestination(rtp_sender_handle, video_des.getAddress(), video_des.getPort()); if ( libPublisher.InitRTPSender(rtp_sender_handle) != 0 ) { gb28181_agent_.respondPlayInvite(488, device_id_); libPublisher.DestoryRTPSender(rtp_sender_handle); return; } int local_port = libPublisher.GetRTPSenderLocalPort(rtp_sender_handle); if (local_port == 0) { gb28181_agent_.respondPlayInvite(488, device_id_); libPublisher.DestoryRTPSender(rtp_sender_handle); return; } Log.i(TAG,"get local_port:" + local_port); String local_ip_addr = IPAddrUtils.getIpAddress(context_); MediaSessionDescription local_video_des = new MediaSessionDescription(video_des.getType()); local_video_des.addFormat(String.valueOf(ps_rtpmap_attr.getPayloadType())); local_video_des.addRtpMapAttribute(ps_rtpmap_attr); local_video_des.setAddressType(video_des.getAddressType()); local_video_des.setAddress(local_ip_addr); local_video_des.setPort(local_port); local_video_des.setTransportProtocol(video_des.getTransportProtocol()); local_video_des.setSSRC(video_des.getSSRC()); if (!gb28181_agent_.respondPlayInviteOK(device_id_,local_video_des) ) { libPublisher.DestoryRTPSender(rtp_sender_handle); Log.e(TAG, "ntsOnInvitePlay call respondPlayInviteOK failed."); return; } gb28181_rtp_sender_handle_ = rtp_sender_handle; } private String device_id_; private SessionDescription session_des_; public Runnable set(String device_id, SessionDescription session_des) { this.device_id_ = device_id; this.session_des_ = session_des; return this; } }.set(deviceId, session_des),0); }
ack处理逻辑
public void ntsOnAckPlay(String deviceId) { handler_.postDelayed(new Runnable() { public void run() { Log.i(TAG,"ntsOnACKPlay, device_id:" +device_id_); if (!isRTSPPublisherRunning && !isPushingRtmp && !isRecording) { InitAndSetConfig(); } libPublisher.SetGB28181RTPSender(publisherHandle, gb28181_rtp_sender_handle_, gb28181_rtp_payload_type_, gb28181_rtp_encoding_name_); //libPublisher.SetGBTCPConnectTimeout(publisherHandle, 10*60*1000); //libPublisher.SetGBInitialTCPReconnectInterval(publisherHandle, 1000); //libPublisher.SetGBInitialTCPMaxReconnectAttempts(publisherHandle, 3); int startRet = libPublisher.StartGB28181MediaStream(publisherHandle); if (startRet != 0) { if (!isRTSPPublisherRunning && !isPushingRtmp && !isRecording) { if (publisherHandle != 0) { long handle = publisherHandle; publisherHandle = 0; libPublisher.SmartPublisherClose(handle); } } destoryRTPSender(); Log.e(TAG, "Failed to start GB28181 service.."); return; } if (!isRTSPPublisherRunning && !isPushingRtmp && !isRecording) { CheckInitAudioRecorder(); } startLayerPostThread(); isGB28181StreamRunning = true; } private String device_id_; public Runnable set(String device_id) { this.device_id_ = device_id; return this; } }.set(deviceId),0); }
总结
Android平台GB28181设备接入模块设计,需要考虑的点非常多,除了常规的信令和媒体交互设计外,对接调试也非常耗时耗力,好多做国标平台的厂商,出于对协议的了解程度或实现等各方面的因素,好多时候并没有完全按照规范来,需要酌情兼容,此外,特别比如执法记录仪等场景下,需要考虑好的视频编码效率、网络重连等,做个demo容易,做个产品真的非常考验开发者。