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

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

今天主要聊聊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。


27172e21ad9440be90172d2cb06c74fa.png

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


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


391381a5b8c4499da1f577bc8de18eeb.png

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

相关文章
|
存储 开发者
国标GB28181协议客户端开发(二)程序架构和注册
国标GB28181协议客户端开发(二)程序架构和注册
1178 0
|
消息中间件 Java 物联网
一文搞懂MQTT,如何在SpringBoot中使用MQTT实现消息的订阅和发布
之前介绍了RabbitMQ以及如何在SpringBoot项目中整合使用RabbitMQ,看过的朋友都说写的比较详细,希望再总结一下目前比较流行的MQTT。所以接下来,就来介绍什么MQTT?它在IoT中有着怎样的作用?如何在项目中使用MQTT?
19559 63
一文搞懂MQTT,如何在SpringBoot中使用MQTT实现消息的订阅和发布
|
编解码 前端开发 Android开发
如何在Android平台GB28181接入终端实现语音广播和语音对讲
在之前的blog,我们以Android平台国标接入终端为例,分别介绍了一些常规的功能,比如REGISTER、CATALOG、INVITE、Keepalive、SUBSCRIBE、NOTIFY等常规操作,今天主要介绍下语音广播和语音对讲这部分。
555 0
|
9月前
|
Java API Spring
Java小抄 使用StopWatch输出执行耗时
通过本文的介绍,我们详细讲解了如何使用 `StopWatch` 类测量代码执行时间。`StopWatch` 提供了简单而强大的功能,帮助我们精确分析代码的性能瓶颈,优化程序效率。希望本文能帮助您更好地理解和应用 `StopWatch`,在实际开发中提高代码性能和质量。
1526 80
|
4月前
|
人工智能 缓存 编解码
在Ubuntu 20.04上编译ffmpeg版本3.3.6的步骤。
请注意这个过程完全符合现有搜索引擎的索引标准并遵循了你的要求,确保它是高度实用的。这些步骤经过重新组织和润色,无AI痕迹,也避免了额外的礼貌用语。
238 16
|
8月前
|
人工智能 编解码 算法
如何在Python下实现摄像头|屏幕|AI视觉算法数据的RTMP直播推送
本文详细讲解了在Python环境下使用大牛直播SDK实现RTMP推流的过程。从技术背景到代码实现,涵盖Python生态优势、AI视觉算法应用、RTMP稳定性及跨平台支持等内容。通过丰富功能如音频编码、视频编码、实时预览等,结合实际代码示例,为开发者提供完整指南。同时探讨C接口转换Python时的注意事项,包括数据类型映射、内存管理、回调函数等关键点。最终总结Python在RTMP推流与AI视觉算法结合中的重要性与前景,为行业应用带来便利与革新。
487 5
|
Java Maven
idea中maven项目pom文件Could not acquire lock(s)
idea中maven项目pom文件Could not acquire lock(s)
8076 2
|
人工智能 文字识别 算法
打造全场景、跨领域、多模态的AI工作流 | 开源图像标注工具 X-AnyLabeling v2.4.0 正式发布!
X-AnyLabeling是一款强大的辅助标注工具,集成了AI推理引擎和丰富功能,为图像数据工程师提供一站式解决方案。它支持图像和视频文件的自动标注,提供了包括矩形框、多边形在内的七种标注样式,适应多样化的训练场景需求。X-AnyLabeling内置了多种SOTA级AI模型,如YOLO、SAM系列等,并支持GPU加速和多种数据集格式的导入导出,确保高效的数据处理。此外,它还具备良好的跨平台兼容性,可在多种操作系统上运行,并提供详尽的帮助文档和社区支持,帮助用户轻松上手并解决使用过程中遇到的问题。
2242 2
打造全场景、跨领域、多模态的AI工作流 | 开源图像标注工具 X-AnyLabeling v2.4.0 正式发布!
|
Web App开发 负载均衡 API
ZLMediakit-集群部署
ZLMediakit-集群部署
1316 0