前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >GB28181中SSRC的使用和语音广播流程浅析

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

原创
作者头像
音视频牛哥
发布2022-10-03 13:08:51
1.1K0
发布2022-10-03 13:08:51
举报

今天主要聊聊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语音按钮。

/*
 * 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链路接收数据。

@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,处理相关回调:

@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()回调处理如下:

@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()回调处理如下:

@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()回调处理如下:

@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和语音广播的一点经验,感兴趣的开发者可酌情参考。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云点播
面向音视频、图片等媒体,提供制作上传、存储、转码、媒体处理、媒体 AI、加速分发播放、版权保护等一体化的高品质媒体服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档