GB28181中SSRC的使用和语音广播流程浅析

2022-10-03 13:08:51 浏览数 (1)

今天主要聊聊GB28181中,SSRC的作用,从我们之前跟第三方厂商的对接来看,好多厂商对SSRC的处理,并不符合规范。

举个典型的操作:语音广播时带的SSRC和发送RTP包时的SSRC并不一致,然后厂商一开始给出来的结论是,不一致也不影响使用,实则按照规范来看,SSRC还是至关重要的,想想看,如果SSRC不重要的话,SDP携带的SSRC的意义在哪里?如果接入端,不对SSRC做判断,假设有多台设备向Android端GB28181设备接入设备(如执法记录仪、智能头盔等)发送语音广播RTP包,如何过滤哪个设备发过来的数据?再比如,第三方恶意冲击系统,给监听的端口乱发RTP包,如何规避?

咱们先来仔细看看GB/T28181-2016规范里面,是怎么描述SSRC的使用的:

SSRC值由媒体流发送设备所在的SIP监控域产生,作为媒体流的标识使用。点播域内设备、点播外域设备媒体流SSRC的处理方式分别说明如下:

a) 点播域内设备媒体流SSRC处理方式

点播域内设备媒体流时,SSRC值由本域监控系统产生并通过Invite请求发送给设备使用,设备在回复的200 OK消息中携带此值,设备在发送的媒体流中使用此值作为RTP的SSRC值。流程图见图 F.1。

b) 点播外域设备媒体流SSRC处理方式

点播外域设备媒体流时,SSRC由被点播域产生并在被点播域回复的200 OK SDP消息体中携带,被点播域发送的RTP码流使用该值作为SSRC值。流程图见图 F.2。

注5:错误响应补充说明

当设备收到无法满足的SDP时,向发送的Invite请求方发送488错误响应消息;当设备不能满足更多的呼叫请求时,向发送的Invite请求方发送486错误响应消息。

以下就以Android平台GB28181设备接入模块,语音广播这块为例:

当收到GB28181平台端的语音广播请求后,客户端做出响应,并在ntsOnNotifyBroadcastCommand()回调做出相应的处理,调用respondBroadcastCommand()回复平台端,并使能GB28181语音按钮。

代码语言:javascript复制
/*
 * MainActivity.java
 * GitHub: https://github.com/daniulive/SmarterStreaming
 */
@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);
}

然后,在ntsOnAudioBroadcast()回调处理语音广播,创建RTP链路接收数据。

代码语言:javascript复制
@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);
}

如有异常或timeout,处理相关回调:

代码语言:javascript复制
@Override
public void ntsOnInviteAudioBroadcastException(String sourceID, String targetID, String errorInfo) {
  handler_.postDelayed(new Runnable() {
    @Override
    public void run() {
      Log.i(TAG, "ntsOnInviteAudioBroadcastException, sourceID:"   source_id_   ", targetID:"   target_id_);

      destoryRTPReceiver();
      btnGB28181AudioBroadcast.setText("GB28181语音广播");
    }

    private String source_id_;
    private String target_id_;

    public Runnable set(String source_id, String target_id) {
      this.source_id_ = source_id;
      this.target_id_ = target_id;
      return this;
    }

  }.set(sourceID, targetID),0);
}

@Override
public void ntsOnInviteAudioBroadcastTimeout(String sourceID, String targetID) {
  handler_.postDelayed(new Runnable() {
    @Override
    public void run() {
      Log.i(TAG, "ntsOnInviteAudioBroadcastTimeout, sourceID:"   source_id_   ", targetID:"   target_id_);

      destoryRTPReceiver();
      btnGB28181AudioBroadcast.setText("GB28181语音广播");
    }

    private String source_id_;
    private String target_id_;

    public Runnable set(String source_id, String target_id) {
      this.source_id_ = source_id;
      this.target_id_ = target_id;
      return this;
    }

  }.set(sourceID, targetID),0);
}

ntsOnInviteAudioBroadcastResponse()回调处理如下:

代码语言:javascript复制
@Override
public void ntsOnInviteAudioBroadcastResponse(String sourceID, String targetID, int statusCode, PlaySessionDescription sessionDescription) {
  handler_.postDelayed(new Runnable() {
    @Override
    public void run() {
      Log.i(TAG, "ntsOnInviteAudioBroadcastResponse, statusCode:"   status_code_  " sourceID:"   source_id_   ", targetID:"   target_id_);

      boolean is_need_destory_rtp = true;

      if (gb28181_agent_ != null ) {
        boolean is_need_bye = 200==status_code_;

        if (200 == status_code_ && session_description_ != null && rtp_receiver_handle_ != 0 ) {
          MediaSessionDescription audio_des = session_description_.getAudioDescription();

          SDPRtpMapAttribute audio_attr = null;
          if (audio_des != null && audio_des.getRtpMapAttributes() != null && !audio_des.getRtpMapAttributes().isEmpty() )
            audio_attr = audio_des.getRtpMapAttributes().get(0);

          if ( audio_des != null && audio_attr != null ) {
            lib_player_.SetRTPReceiverSSRC(rtp_receiver_handle_, audio_des.getSSRC());

            lib_player_.SetRTPReceiverPayloadType(rtp_receiver_handle_, audio_attr.getPayloadType(),
                                                  audio_attr.getEncodingName(), 2, audio_attr.getClockRate());

            // 如果是PCMA, SDK会默认填 采样率8000, 通道1, 其他音频编码需要手动填入
            // lib_player_.SetRTPReceiverAudioSamplingRate(rtp_receiver_handle_, 8000);
            // lib_player_.SetRTPReceiverAudioChannels(rtp_receiver_handle_, 1);

            lib_player_.SetRTPReceiverRemoteAddress(rtp_receiver_handle_, audio_des.getAddress(), audio_des.getPort());
            lib_player_.InitRTPReceiver(rtp_receiver_handle_);

            if (startAudioPlay()) {
              is_need_bye = false;
              is_need_destory_rtp = false;

              gb_broadcast_source_id_ = source_id_;
              gb_broadcast_target_id_ = target_id_;
              btnGB28181AudioBroadcast.setText("终止GB28181语音广播");
              btnGB28181AudioBroadcast.setEnabled(true);
            }
          }

        } else {
          btnGB28181AudioBroadcast.setText("GB28181语音广播");
        }

        if (is_need_bye)
          gb28181_agent_.byeAudioBroadcast(source_id_, target_id_);
      }

      if (is_need_destory_rtp)
        destoryRTPReceiver();
    }

    private String source_id_;
    private String target_id_;
    private int status_code_;
    private PlaySessionDescription session_description_;

    public Runnable set(String source_id, String target_id, int status_code, PlaySessionDescription session_description) {
      this.source_id_ = source_id;
      this.target_id_ = target_id;
      this.status_code_ = status_code;
      this.session_description_ = session_description;
      return this;
    }

  }.set(sourceID, targetID, statusCode, sessionDescription),0);
}

ntsOnByeAudioBroadcast()回调处理如下:

代码语言:javascript复制
@Override
public void ntsOnByeAudioBroadcast(String sourceID, String targetID) {
  handler_.postDelayed(new Runnable() {
    @Override
    public void run() {
      Log.i(TAG, "ntsOnByeAudioBroadcast sourceID:"   source_id_   " targetID:"   target_id_);

      gb_broadcast_source_id_ = null;
      gb_broadcast_target_id_ = null;
      btnGB28181AudioBroadcast.setText("GB28181语音广播");
      btnGB28181AudioBroadcast.setEnabled(false);

      stopAudioPlayer();
      destoryRTPReceiver();
    }

    private String source_id_;
    private String target_id_;

    public Runnable set(String source_id, String target_id) {
      this.source_id_ = source_id;
      this.target_id_ = target_id;
      return this;
    }

  }.set(sourceID, targetID),0);
}

ntsOnTerminateAudioBroadcast()回调处理如下:

代码语言:javascript复制
@Override
public void ntsOnTerminateAudioBroadcast(String sourceID, String targetID) {
  handler_.postDelayed(new Runnable() {
    @Override
    public void run() {
      Log.i(TAG, "ntsOnTerminateAudioBroadcast sourceID:"   source_id_   " targetID:"   target_id_);

      gb_broadcast_source_id_ = null;
      gb_broadcast_target_id_ = null;
      btnGB28181AudioBroadcast.setText("GB28181语音广播");
      btnGB28181AudioBroadcast.setEnabled(false);

      stopAudioPlayer();
      destoryRTPReceiver();
    }

    private String source_id_;
    private String target_id_;

    public Runnable set(String source_id, String target_id) {
      this.source_id_ = source_id;
      this.target_id_ = target_id;
      return this;
    }

  }.set(sourceID, targetID),0);
}

以上是GB28181关于SSRC和语音广播的一点经验,感兴趣的开发者可酌情参考。

0 人点赞