GB/T 28181联网系统通信协议结构和技术实现

简介: 在本文开头,我们先一起回顾下GB/T28181联网系统通信协议结构:联网系统在进行视音频传输及控制时应建立两个传输通道:会话通道和媒体流通道。• 会话通道用于在设备之间建立会话并传输系统控制命令;• 媒体流通道用于传输视音频数据,经过压缩编码的视音频流采用流媒体协议 RTP/RTCP传输。

技术回顾

在本文开头,我们先一起回顾下GB/T28181联网系统通信协议结构:


联网系统在进行视音频传输及控制时应建立两个传输通道:会话通道媒体流通道


  • 会话通道用于在设备之间建立会话并传输系统控制命令;
  • 媒体流通道用于传输视音频数据,经过压缩编码的视音频流采用流媒体协议 RTP/RTCP传输。


具体如下图

78adba5bb1844e608bdf2665d5466fea.png

我们先来看看会话初始协议


  • 安全注册、实时视音频点播、历史视音频的回放等应用的会话控制采用IETF RFC 3261规定的 Register、Invite等请求和响应方法实现;
  • 历史视音频回放控制采用SIP扩展协议IETF RFC 2976规定的INFO方法实现;
  • 前端设备控制、信息查询、报警事件通知和分发等应用的会话控制采用 SIP扩展协议IETF RFC 3428规定的Message方法实现;
  • SIP消息应支持基于UDP和 TCP的传输;
  • 互联的系统平台及设备不应向对方的SIP端口发送应用无关消息,避免应用无关消息占用系统平台及设备的SIP消息处理资源。


接下来是会话描述协议


联网系统有关设备之间会话建立过程的会话协商和媒体协商应采用IETF RFC 4566协议描述,主要内容包括会话描述、媒体信息描述、时间信息描述。


会话协商和媒体协商信息应采用SIP消息的消息体携带传输。


制描述协议


联网系统有关前端设备控制、报警信息、设备目录信息等控制命令应采用监控报警联网系统控制描述协议(MANSCDP)描述。


联网系统控制命令应采用SIP消息Message的消息体携带传输。


媒体回放控制协议


历史视音频的回放控制命令应采用监控报警联网系统实时流协议(MANSRTSP),实现设备在端到端之间对视音频流的正常播放、快速、暂停、停止、随机拖动播放等远程控制。


历史媒体的回放控制命令采用SIP消息Info的消息体携带传输。


由于我们主要侧重于GB/T 28181音视频实时数据接入,这块未做实现,有相关需求的开发者,参考对应的spec章节即可。


媒体传输和媒体编解码协议


  • 媒体流在联网系统IP网络上传输时应支持 RTP传输,媒体流发送源端应支持控制媒体流发送峰值功能;
  • RTP的负载应采用如下两种格式之一:基于 PS封装的视音频数据或视音频基本流数据;
  • 媒体流的传输应采用IETF RFC 3550规定的 RTP协议,提供实时数据传输中的时间戳信息及各数据流的同步;应采用IETFRFC3550规定的RTCP协议,为按序传输数据包提供可靠保证,提供流量控制和拥塞控制。

技术实现

下面以Android平台GB/T 28181设备接入实现为例,大概介绍下相关的参数配置和设计细节:


先说参数配置,除了常规的链接sip server基础配置外,我们根据规范要求,添加了注册有效期、心跳间隔、心跳间隔失败次数、信令传输协议等配置:

/*** GB28181 相关参数,可以修改相关参数后测试 ***/
    GBSIPAgent     gb28181_agent_             = null;
    private int    gb28181_sip_local_port_    = 12070;
    private String gb28181_sip_server_id_     = "34020000002000000001";
    private String gb28181_sip_domain_        = "3402000000";
    private String gb28181_sip_server_addr_   = "192.168.0.105";
    private int    gb28181_sip_server_port_   = 15060;
    private String gb28181_sip_user_agent_filed_  = "NT GB28181 User Agent V1.2";
    private String gb28181_sip_username_   = "31011500991320000069";
    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传输
    private long gb28181_rtp_sender_handle_ = 0;
    private int  gb28181_rtp_payload_type_  = 96;
    private long player_handle_ = 0;
    private long rtp_receiver_handle_ = 0;
    private AtomicLong last_received_audio_data_time_ = new AtomicLong(0);
    /*** GB28181 相关参数,可以修改相关参数后测试 ***/

为了测试方便,我们在界面加了个启动/停止GB28181的按钮:

/*
 * Github: https://github.com/daniulive/SmarterStreaming
 */
class ButtonGB28181AgentListener implements OnClickListener {
  public void onClick(View v) {
    stopAudioPlayer();
    destoryRTPReceiver();
    gb_broadcast_source_id_ = null;
    gb_broadcast_target_id_ = null;
    btnGB28181AudioBroadcast.setText("GB28181语音广播");
    btnGB28181AudioBroadcast.setEnabled(false);
    stopGB28181Stream();
    destoryRTPSender();
    if (null == gb28181_agent_ ) {
      if( !initGB28181Agent() )
        return;
    }
    if (gb28181_agent_.isRunning()) {
      gb28181_agent_.terminateAllPlays(true);// 目前测试下来,发送BYE之后,有些服务器会立即发送INVITE,是否发送BYE根据实际情况看
      gb28181_agent_.stop();
      btnGB28181Agent.setText("启动GB28181");
    }
    else {
      if ( gb28181_agent_.start() ) {
        btnGB28181Agent.setText("停止GB28181");
      }
    }
  }
}

其中,initGB2818Agent()主要是基础参数设置,对应的实现如下:

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_.setLocalAddressInfo(local_ip_addr, gb28181_sip_local_port_);
  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_.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.gb28181.ntsignalling.Device gb_device = new com.gb28181.ntsignalling.Device("34020000001380000001", "安卓测试设备", 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_device.setPosition(device_pos);
    gb_device.setSupportMobilePosition(true); // 设置支持移动位置上报
  }
  gb28181_agent_.addDevice(gb_device);
  if (!gb28181_agent_.initialize()) {
    gb28181_agent_ = null;
    Log.e(TAG, "initGB28181Agent gb28181_agent_.initialize failed.");
    return  false;
  }
  return true;
}

参数设定后,开始发送Regiter到平台端,Android设备端,针对Register的处理如下:

@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 :""));
}

Catalog不再赘述,我们看看Inite处理:

@Override
public void ntsOnInvitePlay(String deviceId, PlaySessionDescription session_des) {
  handler_.postDelayed(new Runnable() {
    @Override
    public void run() {
      MediaSessionDescription video_des = session_des_.getVideoDescription();
      SDPRtpMapAttribute ps_rtpmap_attr = video_des.getPSRtpMapAttribute();
      Log.i(TAG,"ntsInviteReceived, 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());
      // 可以先给信令服务器发送临时振铃响应
      //sip_stack_android.respondPlayInvite(180, device_id_);
      long rtp_sender_handle = libPublisher.CreateRTPSender(0);
      if ( rtp_sender_handle == 0 ) {
        gb28181_agent_.respondPlayInvite(488, device_id_);
        Log.i(TAG, "ntsInviteReceived CreateRTPSender failed, response 488, device_id:" + device_id_);
        return;
      }
      gb28181_rtp_payload_type_ = ps_rtpmap_attr.getPayloadType();
      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_);
      gb28181_agent_.respondPlayInviteOK(device_id_,local_ip_addr, local_port);
      gb28181_rtp_sender_handle_ = rtp_sender_handle;
    }
    private String device_id_;
    private PlaySessionDescription session_des_;
    public Runnable set(String device_id, PlaySessionDescription session_des) {
      this.device_id_ = device_id;
      this.session_des_ = session_des;
      return this;
    }
  }.set(deviceId, session_des),0);
}

Ack后,开始发送打包后的ps数据:

@Override
public void ntsOnAckPlay(String deviceId) {
  handler_.postDelayed(new Runnable() {
    @Override
    public void run() {
      Log.i(TAG,"ntsOnACKPlay, device_id:" +device_id_);
      if (!isRecording && !isRTSPPublisherRunning && !isPushingRtmp) {
        InitAndSetConfig();
      }
      libPublisher.SetGB28181RTPSender(publisherHandle, gb28181_rtp_sender_handle_, gb28181_rtp_payload_type_);
      int startRet = libPublisher.StartGB28181MediaStream(publisherHandle);
      if (startRet != 0) {
        if (!isRecording && !isRTSPPublisherRunning && !isPushingRtmp ) {
          if (publisherHandle != 0) {
            libPublisher.SmartPublisherClose(publisherHandle);
            publisherHandle = 0;
          }
        }
        destoryRTPSender();
        Log.e(TAG, "Failed to start GB28181 service..");
        return;
      }
      if (!isRecording && !isRTSPPublisherRunning && !isPushingRtmp) {
        if (pushType == 0 || pushType == 1) {
          CheckInitAudioRecorder();    //enable pure video publisher..
        }
      }
      startLayerPostThread();
      isGB28181StreamRunning = true;
    }
    private String device_id_;
    public Runnable set(String device_id) {
      this.device_id_ = device_id;
      return this;
    }
  }.set(deviceId),0);
}

再看看位置订阅处理:

@Override
    public void ntsOnDevicePositionRequest(String deviceId, int interval) {
        handler_.postDelayed(new Runnable() {
            @Override
            public void run() {
                getLocation(context_);
                Log.v(TAG, "ntsOnDevicePositionRequest, deviceId:" + this.device_id_ + ", Longitude:" + mLongitude
                        + ", Latitude:" + mLatitude + ", Time:" + mLocationTime);
                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);
                    if (gb28181_agent_ != null ) {
                        gb28181_agent_.updateDevicePosition(device_id_, device_pos);
                    }
                }
            }
            private String device_id_;
            private int interval_;
            public Runnable set(String device_id, int interval) {
                this.device_id_ = device_id;
                this.interval_ = interval;
                return this;
            }
        }.set(deviceId, interval),0);
    }

语音广播和语音对讲处理:

@Override
public void ntsOnNotifyBroadcastCommand(String fromUserName, String fromUserNameAtDomain, String sn, String sourceID, String targetID) {
  handler_.postDelayed(new Runnable() {
    @Override
    public void run() {
      Log.i(TAG, "ntsOnNotifyBroadcastCommand, fromUserName:"+ from_user_name_ + ", fromUserNameAtDomain:"+ from_user_name_at_domain_
            + ", SN:" + sn_ + ", sourceID:" + source_id_ + ", targetID:" + target_id_);
      if (gb28181_agent_ != null ) {
        gb28181_agent_.respondBroadcastCommand(from_user_name_, from_user_name_at_domain_,sn_,source_id_, target_id_, true);
        btnGB28181AudioBroadcast.setText("收到GB28181语音广播通知");
      }
    }
    private String from_user_name_;
    private String from_user_name_at_domain_;
    private String sn_;
    private String source_id_;
    private String target_id_;
    public Runnable set(String from_user_name, String from_user_name_at_domain, String sn, String source_id, String target_id) {
      this.from_user_name_ = from_user_name;
      this.from_user_name_at_domain_ = from_user_name_at_domain;
      this.sn_ = sn;
      this.source_id_ = source_id;
      this.target_id_ = target_id;
      return this;
    }
  }.set(fromUserName, fromUserNameAtDomain, sn, sourceID, targetID),0);
}
@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);
}

Bye处理如下:

@Override
public void ntsOnByePlay(String deviceId) {
  handler_.postDelayed(new Runnable() {
    @Override
    public void run() {
      Log.i(TAG, "ntsOnByePlay, stop GB28181 media stream, deviceId=" + device_id_);
      stopGB28181Stream();
      destoryRTPSender();
    }
    private String device_id_;
    public Runnable set(String device_id) {
      this.device_id_ = device_id;
      return this;
    }
  }.set(deviceId),0);
}

考虑到篇幅有限,上面仅展示基础的处理,如各种异常情况处理等,可单独沟通我,获取相关demo。


总的来说,GB/T 28181接入,资料相对全面,但是好多都是基于demo的验证,经不住推敲,如果要产品化,开发者还需要很长的路要走。

相关文章
|
1月前
|
安全 定位技术 数据安全/隐私保护
|
6月前
|
运维 监控 网络架构
|
6月前
|
存储 监控 Windows
ABB 分布式控制系统 DCS紧凑型产品套件 紧凑型HMI
ABB 分布式控制系统 DCS紧凑型产品套件 紧凑型HMI
ABB 分布式控制系统 DCS紧凑型产品套件 紧凑型HMI
|
数据安全/隐私保护 网络架构 UED
光纤布线对企业基础设施网络的五大影响
光纤布线对企业基础设施网络的五大影响
118 0
光纤布线对企业基础设施网络的五大影响
|
5G 网络架构
5G 系统网络架构 | 带你读《5G 无线系统设计与国际标准》之四
为了适应各种部署场景,5G 支持了两种部署方式:一种为分布式部署,这种方式与 LTE系统类似,网络由基站组成,基站支持全协议栈的功能;另一种为集中式部署,基站进一步分为集中单元(CU,Centralized Unit)和分布单元(DU,Distributed Unit)两个节点,CU 和 DU 分别支持不同的协议栈和功能,
5G 系统网络架构  | 带你读《5G 无线系统设计与国际标准》之四
|
存储 监控 Ubuntu
汽车以太网交换机的设计
汽车以太网交换机的设计
汽车以太网交换机的设计
|
安全 5G 网络性能优化
无线接口 | 带你读《5G 无线系统设计与国际标准》之五
本节对物理层、数据链路层和网络层基本功能相关内容进行一些讨论。
无线接口  | 带你读《5G 无线系统设计与国际标准》之五
|
存储 监控 物联网
能源路由器 :分布式智能电网应用的混合通信体系结构
能源路由器 :分布式智能电网应用的混合通信体系结构
281 0
能源路由器 :分布式智能电网应用的混合通信体系结构
|
传感器 存储 物联网
全网仅此一篇:工业级压力传感器设计及实现(华大半导体HC32L136)
全网仅此一篇:工业级压力传感器设计及实现(华大半导体HC32L136)
全网仅此一篇:工业级压力传感器设计及实现(华大半导体HC32L136)
|
存储 数据采集 资源调度
第五代蜂窝通信的系统要求 | 带你读《5G非正交多址技术》之二
与前四代不同的是,5G 的应用十分多样化。峰值速率和平均小区频谱效率 不再是唯一的要求。除此之外,体验速率、连接数、低时延、高可靠、高能效都将成为系统设计的重要考量因素。应用场景也不只是广域覆盖,还包括密集 热点、机器间通信、车联网、大型露天集会、地铁等。这也决定了 5G 中的技 术是多元的,不会像前几代那样,每一代都有唯一一个标志性技术。就多址技 术而言,5G 的一大特点是采用非正交资源。
第五代蜂窝通信的系统要求  | 带你读《5G非正交多址技术》之二