Android平台GB28181设备接入端语音广播技术探究和填坑指南

news2024/10/7 8:26:41

技术背景

GB/T28181-2016官方规范和交互流程,我们不再赘述。

SIP服务器发起广播流程示意图如下:

 需要注意的是:语音广播通知、语音广播应答命令

消息头 Content-type字段为 Content-type:Application/MANSCDP+xml。

语音广播通知、语音广播应答命令采用 MANSCDP协议格式定义。

消息示例如下:

a) 语音广播通知

MESSAGE sip:34020000001310000056@192.168.100.9:6720 SIP/2.0\
Via: SIP/2.0/UDP 192.168.100.10:5060;rport=5060;branch=z9hG4bK1073741856;received=192.168.100.10\
From: <sip:34020000002000000001@192.168.100.10:5060>;tag=912513446\
To: <sip:34020000001310000056@192.168.100.9:6720>\
Call-ID: 536870958\
CSeq: 1 MESSAGE\
Contact: <sip:34020000002000000001@192.168.100.10:5060>\
Content-Type: Application/MANSCDP+xml\
Max-Forwards: 70\
User-Agent: Hikvision\
Content-Length: 172\
\
<?xml version="1.0"?>\
<Notify>\
<CmdType>Broadcast</CmdType>\
<SN>11</SN>\
<SourceID>34020000002000000001</SourceID>\
<TargetID>34020000001310000056</TargetID>\
</Notify>\

b) 语音广播应答

MESSAGE sip:34020000002000000001@192.168.100.10:5060 SIP/2.0\
Call-ID: 6d6bc2a5d380a0f8d6787cf614fb0bd8@192.168.100.9\
CSeq: 18300138 MESSAGE\
From: <sip:34020000001310000056@3402000000>;tag=4a5b3953\
To: <sip:34020000002000000001@192.168.100.10:5060>\
Via: SIP/2.0/UDP 192.168.100.9:6720;rport;branch=z9hG4bK-373435-549b6376963815eb98e2a2f011473b41\
Max-Forwards: 70\
User-Agent: NT GB UserAgent V1.91-20230420[daniusdk.com]\
Content-Type: Application/MANSCDP+xml\
Content-Length: 173\
\
<?xml version="1.0" encoding="GB2312"?>\
<Response>\
<CmdType>Broadcast</CmdType>\
<SN>11</SN>\
<DeviceID>34020000001310000056</DeviceID>\
<Result>OK</Result>\
</Response>\

c) 平台侧回复200 OK:

SIP/2.0 200 OK\
Via: SIP/2.0/UDP 192.168.100.9:6720;rport;branch=z9hG4bK-373435-549b6376963815eb98e2a2f011473b41\
From: <sip:34020000001310000056@3402000000>;tag=4a5b3953\
To: <sip:34020000002000000001@192.168.100.10:5060>\
Call-ID: 6d6bc2a5d380a0f8d6787cf614fb0bd8@192.168.100.9\
CSeq: 18300138 MESSAGE\
User-Agent: Hikvision\
Content-Length: 0\

d) 设备接入侧发起invite请求:

INVITE sip:34020000002000000001@3402000000 SIP/2.0\
...
User-Agent: NT GB UserAgent V1.91-20230420[daniusdk.com]\
Content-Type: APPLICATION/SDP\
Content-Length: 245\
\
v=0\
o=34020000001310000056 3898650599696 3898650599696 IN IP4 192.168.100.9\
s=Play\
c=IN IP4 192.168.100.9\
t=0 0\
m=audio 25000 TCP/RTP/AVP 8\
a=setup:active\
a=connection:new\
a=recvonly\
a=rtpmap:8 PCMA/8000\
y=0200009722\
f=v/a/1/8/1\

e) 国标平台侧回复200 OK:

SIP/2.0 200 OK\
...
Content-Type: application/sdp\
User-Agent: Hikvision\
Content-Length: 205\
\
v=0\
o=34020000002000000001 0 0 IN IP4 192.168.100.10\
s=Play\
c=IN IP4 192.168.100.10\
t=0 0\
m=audio 16002 TCP/RTP/AVP 8\
a=rtpmap:8 PCMA/8000\
a=sendonly\
a=setup:passive\
y=0200009727\
f=v/a/1/8/1\

f) 设备接入侧发Ack:

ACK sip:34020000002000000001@192.168.100.10:5060 SIP/2.0\
...
Max-Forwards: 70\
Contact: <sip:34020000001310000056@192.168.100.9:6720>\
User-Agent: NT GB UserAgent V1.91-20230420[daniusdk.com]\
Content-Length: 0\

技术实现

以大牛直播SDK的Android平台GB28181设备接入侧为例:

收到语音广播:

@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, "daniusdk, 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处理:

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

Broadcast Response处理:

    @Override
    public void ntsOnInviteAudioBroadcastResponse(String sourceID, String targetID, int statusCode, SessionDescription 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 = null;
                        List<SDPRtpMapAttribute> audio_attrs = new LinkedList<>();

                        Vector<MediaSessionDescription> audio_des_list = session_description_.getAudioDescriptions();
                        if (audio_des_list != null && !audio_des_list.isEmpty() ) {
                            for (MediaSessionDescription m : audio_des_list) {
                                if (m != null && m.isValidAddressType() && m.isHasAddress() && m.isHasRtpMapAttribute()) {
                                    audio_attrs.clear();
                                    Vector<SDPRtpMapAttribute> rtp_maps = m.getRtpMapAttributes();
                                    for (SDPRtpMapAttribute a : rtp_maps) {
                                        int type = a.getPayloadType();
                                        String name = a.getEncodingName();
                                        if (0 == type || 8 == type)
                                            audio_attrs.add(a);
                                        else if (name != null && !name.isEmpty()) {
                                            if (name.equals("PS") || name.equals("PCMA") || name.equals("PCMU"))
                                                audio_attrs.add(a);
                                        }
                                    }

                                    if (!audio_attrs.isEmpty()) {
                                        audio_des = m;
                                        break;
                                    }
                                }
                            }
                        }

                        if (audio_des != null && !audio_attrs.isEmpty() ) {

                            // 有些场景下 SDP.SSRC 和 RTP.SSRC 不相等, 对于这种情况,不要设置SSRC给SDK, 屏蔽掉下面这行设置SSRC的代码
                            lib_player_.SetRTPReceiverSSRC(rtp_receiver_handle_, audio_des.getSSRC());
                            ....
                            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 SessionDescription session_description_;

            public Runnable set(String source_id, String target_id, int status_code, SessionDescription 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);
    }

需要注意的是,以上述GB28181平台厂商为例,尽管SDP协商的是PCMA,实际上,平台侧下发的是PS的audio数据,如果不设置PS下去,会有以下日志:

2023-07-18 14:30:05.193 9248-9574/com.smartpublisher.camera2demo I/NTLogAndroid: NTRTP readSource: received rtp packet, is_udp:0, payload_type=96, len=232, t:0, sn=0, ssrc=200009727, src_address:0.0.0.0:0\
2023-07-18 14:30:05.193 9248-9574/com.smartpublisher.camera2demo E/NTLogAndroid: NTRTP readSource could not find configuration for payload type:96\
2023-07-18 14:30:05.193 9248-9574/com.smartpublisher.camera2demo E/NTLogAndroid: NTRTP readSource could not find configuration for payload type:96\
2023-07-18 14:30:05.193 9248-9574/com.smartpublisher.camera2demo E/NTLogAndroid: NTRTP readSource could not find configuration for payload type:96\
2023-07-18 14:30:05.193 9248-9574/com.smartpublisher.camera2demo E/NTLogAndroid: NTRTP readSource could not find configuration for payload type:96\
2023-07-18 14:30:05.193 9248-9574/com.smartpublisher.camera2demo E/NTLogAndroid: NTRTP readSource could not find configuration for payload type:96\
2023-07-18 14:30:05.193 9248-9574/com.smartpublisher.camera2demo E/NTLogAndroid: NTRTP readSource could not find configuration for payload type:96\
2023-07-18 14:30:05.250 9248-9574/com.smartpublisher.camera2demo E/NTLogAndroid: NTRTP readSource could not find configuration for payload type:96\
2023-07-18 14:30:05.277 9248-9574/com.smartpublisher.camera2demo E/NTLogAndroid: NTRTP readSource could not find configuration for payload type:96\
2023-07-18 14:30:05.277 9248-9574/com.smartpublisher.camera2demo E/NTLogAndroid: NTRTP readSource could not find configuration for payload type:96\

所以通用的做法是判断SDP里面有没有PS,如果没有,设置RTP Receiver Payload Type下去:

总结

Android平台GB28181设备接入侧为什么没有公司愿意做?真的是坑太多,GB28181厂商太多,好多厂商包括大厂商并没有严格按照规范来,简单来说,50%的精力写代码,50%的精力查问题和各种兼容处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/767723.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue写车牌号 自定义键盘

vue写车牌号自定义键盘 <template><div><div class"content-wrapper"><div class"content-top-wrapper"><van-radio-group v-model"radioCarType"><van-radio name"1">蓝牌<imgslot"icon…

COC 深圳城市开发者社区【职言职语】第1季活动来袭。。。

文章目录 活动介绍活动议程活动奖品参与方式活动时间活动费用活动地点 活动介绍 &#x1f389;&#x1f465; 欢迎加入职言职语活动&#xff01;与我们一起探索职场的智慧和灵感&#xff01; &#x1f454;&#x1f4bc; 在这个快节奏的职业世界中&#xff0c;职场话题 是我们…

【实战总结】SpringMVC架构升级SpringCloudAlibaba

升级目标 SpringMVCDubboZookeeper分布式架构改为Spring Cloud Alibaba微服务 技术框架:Spring Boot 2.7.2、Spring Cloud 2021.0.3 & Alibaba 2021.0.1.0 容器:Tomcat 9.0.65 JDK:1.8 配置中心:Nacos 2.0.4 消息队列:RocetMQ 4.9.3 配置中心:Apollo 11.0 缓存: Redis 4.0…

大华智慧园区综合管理平台存在任意文件上传漏洞

大华智慧园区综合管理平台存在任意文件上传漏洞 免责声明&#xff1a;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作…

Ceph

Ceph简介 Ceph使用C语言开发&#xff0c;是一个开放、自我修复和自我管理的开源分布式存储系统。具有高扩展性、高性能、高可靠性的优点。Ceph目前已得到众多云计算厂商的支持并被广泛应用。RedHat及OpenStack&#xff0c;Kubernetes都可与Ceph整合以支持虚拟机镜像的后端存储…

CMake:设置语言标准(一)

CMake:设置语言标准&#xff08;一&#xff09; 导言C标准历史C11版本特性介绍类型推导之auto和decltypeC返回值类型后置对模板实例化中连续尖括号>>的改进使用using定义别名&#xff08;替代typedef&#xff09;支持函数模板的默认参数在函数模板和类模板中使用可变参数…

接下来讲一讲Vue的数据代理

首先讲一下原生js的数据代理 原生的 Object.defineProperty() let aa wewewlet person {name: "王李斌",age: 12} Object.defineProperty(person, "address", {// value: 14&#xff0c; 给字段设置值//enumerable:true, 设置动态设置的字段为可以遍历/…

Docker 镜像解密:分层存储与构建原理多角度解析

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

超声波水表的优势

超声波水表是一种新型的水表&#xff0c;它采用超声波测流技术&#xff0c;能够实现对水流的非接触式测量&#xff0c;具有许多优势。下面将从多个方面来介绍超声波水表的优势。 一、高精度 由于超声波测流技术采用的是非接触式测量&#xff0c;因此不会受到水流的摩擦、涡流等…

【C++】C++11 (3): lambda表达式和包装器

一、lambda表达式 C98中的一个例子 在C98中&#xff0c;如果想要对一个数据集合中的元素进行排序&#xff0c;可以使用std::sort方法。 #include <algorithm> #include <functional> int main() {int a[] { 4,1,8,5,3,7,0,9,2,6 };// 默认按照小于比较&#xff…

S32 Design Studio for ARM(S32DS)下载和安装

1. S32 Design Studio for ARM 介绍 S32 Design Studio for ARM&#xff08;下面简称S32DS&#xff09;&#xff0c;是 NXP 官方在 2014 年官方推出的&#xff0c;专门面向 S32K、KEA、MAC57D54H等系列微控制器的集成开发环境。 S32DS是由Eclipse和一些插件集成而来的开发平台…

自来水收费系统适合应用于哪些场景?

自来水收费系统是一种用于自来水公司或供水管理部门的软件系统&#xff0c;旨在帮助自动化自来水的收费和管理过程。该系统可以帮助自来水公司更好地管理水资源&#xff0c;提高供水质量和效率&#xff0c;同时也可以为用户提供更加便捷和安全的用水服务。下面将从多个方面来介…

centos7通过epel源安装redis

目录 一、下载epel源 二、下载并且启动 一、下载epel源 wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo 二、下载并且启动 yum install redis systemctl enable --now redis

c代码和c++代码互相调用

note 不管是c代码调用c代码&#xff0c;还是c代码调用c代码&#xff0c;本质上都是g兼容gcc的功能 c代码调用c代码 lib.h #ifndef _LIB_H_void libfun(void);#endif lib.c #include <stdio.h> #include "lib.h"void libfun(void) {printf("this is i…

Intel nuc 清除cmos密码

设备为intel nuc7&#xff0c;其他版本可以参考&#xff0c;步骤如下 1、找到主板上的bios_sec的三针跳线 2、将跳线开关取下 3、按住电源键3秒&#xff0c;电源指示灯变为橙色后松手 4、耐心等待显示器显示bios recovery倒计时画面 5、按esc取消bios recovery 6、显示器显…

Oss文件上传-简化版

这里使用的是element的上传组件&#xff0c;相关上传钩子看官网 一、获取到oss后台相关的签名数据 建议通过接口获取 二、上传 //获取oss相关参数// getOsstokenasync beforeUploadMasterImg(file) {console.log("点击上传");await this.getOssSingnature(file);},…

这7款UI设计工具值得一试!

在设计工作中&#xff0c;UI设计更注重于人和产品互动式时的交互效果&#xff0c;也就是页面内容丰富程度。无论是想打造哪种风格的UI设计界面&#xff0c;这个过程都离不开好用的UI设计工具&#xff0c;本文总结了7款年度好用的UI设计工具&#xff0c;能帮助设计师更轻松地创作…

压裂工艺原理简述

一、压裂程序 1. 压裂概念 在石油领域&#xff0c;压裂是指采油或采气过程中&#xff0c;利用水力作用&#xff0c;使油气层形成裂缝的一种方法&#xff0c;又称水力压裂。压裂是人为地使地层产生裂缝&#xff0c;改善油在地下的流动环境&#xff0c;使油井产量增加&#xff…

【kubernetes系列】kubernetes之计算资源管理

资源类型 在 Kubernetes 中&#xff0c;Node 提供资源&#xff0c;而 Pod 使用资源。其中资源分为计算&#xff08;CPU、Memory、GPU&#xff09;、存储&#xff08;Disk、SSD&#xff09;、网络&#xff08;Network Bandwidth、IP、Ports&#xff09;。这些资源提供了应用运行…

DIN - 序列模型之深度兴趣网络(阿里)

&#x1f525; DIN来自于 阿里 盖坤团队 在 KDD-2018 发的论文《Deep Interest Network for Click-Through Rate Prediction》。该模型在当时已经应用于阿里的电商广告推荐业务&#xff0c;效果不错。 文章目录 1、介绍&#xff1a;2、单值特征 & 多值特征&#xff1a;3、动…