技术方案
将Android平台的GB28181客户端记录仪与应急指挥调度系统对接,可以实现实时视频监控和数据传输,以协助应急指挥中心对工地的监控和调度。具体的对接方法如下:
1. 配置GB28181设备接入端记录仪:在Android设备上安装和配置GB28181客户端记录仪,确保其连接到智慧工地国标服务平台,实现现场设备和GB28181平台侧的通信连接。
2. 集成与调度系统接口:通过与应急指挥调度系统的接口,将GB28181客户端记录仪和调度系统进行集成。根据调度系统的接口规范,进行相关配置和设置,确保客户端记录仪能够与调度系统进行通信。
3. 实时视频监控:通过GB28181客户端记录仪,将智慧工地的实时监控视频传输到调度系统中。在调度系统的界面中,显示摄像头的实时监控画面,并提供对视频流的控制,如实时预览、语音广播、实时位置更新查看等。
4. 录像存储与回放:通过GB28181客户端记录仪,实现设备按需录像功能。调度系统可以通过接口调用,实现对历史视音频文件的下载和回放。例如,在应急调度中心需要回放某个时间段内的监控记录时,可以通过调度系统的界面进行选择和播放。
5. 报警与事件处理:GB28181客户端记录仪可以与调度系统进行报警和事件的通信。当监控设备发生报警时,客户端记录仪可以向调度系统发送报警信息,以便调度员及时响应和处理。
通过与应急指挥调度系统的对接,Android平台上的GB28181记录仪可以提供实时视频监控和数据传输的功能,帮助应急指挥中心对工地进行监控和调度。这样可以提高应急响应的效率和准确性,确保工地安全和管理的有效性。
技术实现
本文以大牛直播SDK的Android平台GB28181设备接入模块为例,想说配置SIP服务器:设定GB28181设备需要连接的SIP服务器地址、端口、用户凭证等信息。
GBSIPAgent gb28181_agent_ = null;
private int gb28181_sip_local_port_base_ = 5060;
private String gb28181_sip_server_id_ = "34020000002000000001";
private String gb28181_sip_domain_ = "3402000000";
private String gb28181_sip_server_addr_ = "192.168.0.108";
private int gb28181_sip_server_port_ = 15060;
private String gb28181_sip_user_agent_filed_ = null; // "NT GB UserAgent V1.7";
private String gb28181_sip_username_ = "34020000011310000039";
private String gb28181_sip_password_ = "12345678";
private int gb28181_reg_expired_ = 3600; // 注册有效期时间最小3600秒
private int gb28181_heartbeat_interval_ = 20; // 心跳间隔GB28181默认是60, 目前调整到20秒
private int gb28181_heartbeat_count_ = 3; // 心跳间隔3次失败,表示和服务器断开了
private int gb28181_sip_trans_protocol_ = 0; // 0表示信令用UDP传输, 1表示信令用TCP传输
1. 注册设备:通过SIP协议实现设备的注册,将设备注册到SIP服务器上。
@Override
public void ntsRegisterOK(String dateString) {
Log.i(TAG, "ntsRegisterOK Date: " + (dateString!= null? dateString : ""));
}
@Override
public void ntsRegisterTimeout() {
Log.e(TAG, "ntsRegisterTimeout");
}
@Override
public void ntsRegisterTransportError(String errorInfo) {
Log.e(TAG, "ntsRegisterTransportError error:" + (errorInfo != null?errorInfo :""));
}
2. 响应呼叫:当有呼叫请求时,通过SIP协议接收呼叫请求,并进行相应的处理(如接听、拒绝等)。
@Override
public void ntsOnInvitePlay(String deviceId, SessionDescription session_des) {
handler_.postDelayed(new Runnable() {
@Override
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();
...
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);
}
@Override
public void ntsOnCancelPlay(String deviceId) {
// 这里取消Play会话
handler_.postDelayed(new Runnable() {
@Override
public void run() {
Log.i(TAG, "ntsOnCancelPlay, deviceId=" + device_id_);
destoryRTPSender();
}
private String device_id_;
public Runnable set(String device_id) {
this.device_id_ = device_id;
return this;
}
}.set(deviceId),0);
}
3. 视频流传输:通过SIP协议实现GB28181设备之间的视频流传输,使用相关的音视频编解码技术将视频数据进行传输。
@Override
public void ntsOnAckPlay(String deviceId) {
handler_.postDelayed(new Runnable() {
@Override
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);
}
4. 语音广播或语音对讲:通过SIP协议实现设备之间的语音对讲功能,使得设备之间可以进行双向的语音通话。
@Override
public void ntsOnAudioBroadcast(String commandFromUserName, String commandFromUserNameAtDomain, String sourceID, String targetID) {
handler_.postDelayed(new Runnable() {
@Override
public void run() {
Log.i(TAG, "ntsOnAudioBroadcastPlay, fromFromUserName:" + command_from_user_name_
+ " FromUserNameAtDomain:" + command_from_user_name_at_domain_
+ " sourceID:" + source_id_ + ", targetID:" + target_id_);
stopAudioPlayer();
destoryRTPReceiver();
if (gb28181_agent_ != null ) {
String local_ip_addr = IPAddrUtils.getIpAddress(context_);
boolean is_tcp = true; // 考虑到跨网段, 默认用TCP传输rtp包
rtp_receiver_handle_ = lib_player_.CreateRTPReceiver(0);
if (rtp_receiver_handle_ != 0 ) {
lib_player_.SetRTPReceiverTransportProtocol(rtp_receiver_handle_, is_tcp?1:0);
lib_player_.SetRTPReceiverIPAddressType(rtp_receiver_handle_, 0);
if (0 == lib_player_.CreateRTPReceiverSession(rtp_receiver_handle_, 0) ) {
int local_port = lib_player_.GetRTPReceiverLocalPort(rtp_receiver_handle_);
boolean ret = gb28181_agent_.inviteAudioBroadcast(command_from_user_name_,command_from_user_name_at_domain_,
source_id_, target_id_, "IP4", local_ip_addr, local_port, is_tcp?"TCP/RTP/AVP":"RTP/AVP");
if (!ret ) {
destoryRTPReceiver();
btnGB28181AudioBroadcast.setText("GB28181语音广播");
}
else {
btnGB28181AudioBroadcast.setText("GB28181语音广播呼叫中");
}
} else {
destoryRTPReceiver();
btnGB28181AudioBroadcast.setText("GB28181语音广播");
}
}
}
}
private String command_from_user_name_;
private String command_from_user_name_at_domain_;
private String source_id_;
private String target_id_;
public Runnable set(String command_from_user_name, String command_from_user_name_at_domain, String source_id, String target_id) {
this.command_from_user_name_ = command_from_user_name;
this.command_from_user_name_at_domain_ = command_from_user_name_at_domain;
this.source_id_ = source_id;
this.target_id_ = target_id;
return this;
}
}.set(commandFromUserName, commandFromUserNameAtDomain, sourceID, targetID),0);
}
5. 视音频录制与历史视音频下载回放:实现对视频流的录制和下载回放功能,可以将实时视频数据进行录制保存,并可以进行下载、回放操作。
信令接口设计:
/**
* Author: daniusdk.com
*/
package com.gb.ntsignalling;
public interface GBSIPAgent {
void addDownloadListener(GBSIPAgentDownloadListener downloadListener);
void removeDownloadListener(GBSIPAgentDownloadListener removeListener);
/*
*响应Invite Download 200 OK
*/
boolean respondDownloadInviteOK(long id, String deviceId, String startTime, String stopTime, MediaSessionDescription localMediaDescription);
/*
*响应Invite Download 其他状态码
*/
boolean respondDownloadInvite(int statusCode, long id, String deviceId, String startTime, String stopTime);
/*
* 媒体流发送者在文件下载结束后发Message消息通知SIP服务器回文件已发送完成
* notifyType 必须是"121“
*/
boolean notifyDownloadMediaStatus(long id, String deviceId, String startTime, String stopTime, String notifyType);
/*
*终止Download会话
*/
void terminateDownload(long id, String deviceId, String startTime, String stopTime, boolean isSendBYE);
/*
*终止所有Download会话
*/
void terminateAllDownloads(boolean isSendBYE);
}
历史视音频下载listener设计:
/**
* Author: daniusdk.com
*/
package com.gb.ntsignalling;
public interface GBSIPAgentDownloadListener {
/*
*收到s=Download的文件下载Invite
*/
void ntsOnInviteDownload(long id, String deviceId, SessionDescription sessionDescription);
/*
*发送Download invite response 异常
*/
void ntsOnDownloadInviteResponseException(long id, String deviceId, String startTime, String stopTime, int statusCode, String errorInfo);
/*
* 收到CANCEL Download INVITE请求
*/
void ntsOnCancelDownload(long id, String deviceId, String startTime, String stopTime);
/*
* 收到Ack
*/
void ntsOnAckDownload(long id, String deviceId, String startTime, String stopTime);
/*
* 更改下载速度
*/
void ntsOnDownloadMANSRTSPScaleCommand(long id, String deviceId, String startTime, String stopTime, double scale);
/*
* 收到Bye
*/
void ntsOnByeDownload(long id, String deviceId, String startTime, String stopTime);
/*
* 不是在收到BYE Message情况下, 终止Download
*/
void ntsOnTerminateDownload(long id, String deviceId, String startTime, String stopTime);
/*
* Download会话对应的对话终止, 一般不会触发这个回调,目前只有在响应了200K, 但在64*T1时间后还没收到ACK,才可能会出发
收到这个, 请做相关清理处理
*/
void ntsOnDownloadDialogTerminated(long id, String deviceId, String startTime, String stopTime);
}
感兴趣的开发者可以酌情参考。