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

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

技术背景

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

c8637929498c4bbd9fc2e557cbceee97.png


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

240f2f15804d4294934a486135027c52.png

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


消息头 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设备接入侧为例:

0f424d5bbff644bb97f0b32ff3650530.jpg

收到语音广播:

@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下去:

60e5d9c4ed3443b4a6e0a483cf9760f0.jpg

总结

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

相关文章
|
6天前
|
安全 Android开发 数据安全/隐私保护
探索安卓与iOS的安全性差异:技术深度分析与实践建议
本文旨在深入探讨并比较Android和iOS两大移动操作系统在安全性方面的不同之处。通过详细的技术分析,揭示两者在架构设计、权限管理、应用生态及更新机制等方面的安全特性。同时,针对这些差异提出针对性的实践建议,旨在为开发者和用户提供增强移动设备安全性的参考。
|
9天前
|
监控 Android开发 iOS开发
深入探索安卓与iOS的系统架构差异:理解两大移动平台的技术根基在移动技术日新月异的今天,安卓和iOS作为市场上最为流行的两个操作系统,各自拥有独特的技术特性和庞大的用户基础。本文将深入探讨这两个平台的系统架构差异,揭示它们如何支撑起各自的生态系统,并影响着全球数亿用户的使用体验。
本文通过对比分析安卓和iOS的系统架构,揭示了这两个平台在设计理念、安全性、用户体验和技术生态上的根本区别。不同于常规的技术综述,本文以深入浅出的方式,带领读者理解这些差异是如何影响应用开发、用户选择和市场趋势的。通过梳理历史脉络和未来展望,本文旨在为开发者、用户以及行业分析师提供有价值的见解,帮助大家更好地把握移动技术发展的脉络。
|
8天前
|
安全 Java Android开发
掌握安卓与iOS应用开发中的关键技术
本文深入探讨了安卓和iOS平台上应用开发的关键性技术,包括平台特性、开发工具选择、性能优化技巧及跨平台开发的可行性分析。通过对比两种平台的开发环境与实践案例,旨在为开发者提供全面的视角以理解和把握移动应用开发的核心技术。无论是安卓的Java与Kotlin之争,还是iOS的Swift语言革命,本文都将一一解析其优势与应用场景,帮助开发者在技术选型上有更明智的决策。此外,文章还将触及到当前流行的跨平台框架如React Native和Flutter,评估它们在项目实施中的实用性和限制,为有意进行多平台同步开发的团队提供参考。通过对这些关键技术的梳理,本文期望能够启发开发者深化对移动平台开发的理解,并
|
11天前
|
人工智能 vr&ar Android开发
探索安卓与iOS的无限可能:移动操作系统的技术革新与未来展望
本文旨在探讨安卓和iOS这两大主流移动操作系统在技术上的创新与突破,以及它们如何塑造我们的数字生活。通过深入分析两者的最新进展、面临的挑战以及未来的发展趋势,文章揭示了移动操作系统在推动科技进步和满足用户需求方面的关键作用。我们将从技术角度出发,解读安卓的开放性与iOS的封闭性如何影响应用生态和用户体验,并探讨这些差异背后的设计理念和商业考量。同时,我们还将关注两大系统在安全性、隐私保护、人工智能集成等方面的最新动态,以及它们如何应对日益增长的网络安全威胁和用户对隐私保护的需求。此外,文章还将展望未来移动操作系统的发展,分析5G、物联网等新兴技术如何为安卓和iOS带来新的机遇和挑战,以及这两大
33 6
|
5天前
|
IDE Android开发 iOS开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
【9月更文挑战第27天】在移动应用开发的世界中,Android和iOS是两个主要的操作系统平台。每个系统都有其独特的开发环境、工具和用户群体。本文将深入探讨这两个平台的关键差异点,并分析这些差异如何影响应用的性能、用户体验和最终的市场表现。通过对比分析,我们将揭示选择正确的开发平台对于确保项目成功的重要作用。
|
18天前
|
Android开发 开发者 Kotlin
探索安卓开发中的新特性
【9月更文挑战第14天】本文将引导你深入理解安卓开发领域的一些最新特性,并为你提供实用的代码示例。无论你是初学者还是经验丰富的开发者,这篇文章都会给你带来新的启示和灵感。让我们一起探索吧!
|
2天前
|
开发框架 移动开发 Android开发
安卓与iOS开发中的跨平台解决方案:Flutter入门
【9月更文挑战第30天】在移动应用开发的广阔舞台上,安卓和iOS两大操作系统各自占据半壁江山。开发者们常常面临着选择:是专注于单一平台深耕细作,还是寻找一种能够横跨两大系统的开发方案?Flutter,作为一种新兴的跨平台UI工具包,正以其现代、响应式的特点赢得开发者的青睐。本文将带你一探究竟,从Flutter的基础概念到实战应用,深入浅出地介绍这一技术的魅力所在。
18 7
|
5天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台解决方案
【9月更文挑战第27天】在移动应用开发的广阔天地中,安卓和iOS两大操作系统如同双子星座般耀眼。开发者们在这两大平台上追逐着创新的梦想,却也面临着选择的难题。如何在保持高效的同时,实现跨平台的开发?本文将带你探索跨平台开发的魅力所在,揭示其背后的技术原理,并通过实际案例展示其应用场景。无论你是安卓的忠实拥趸,还是iOS的狂热粉丝,这篇文章都将为你打开一扇通往跨平台开发新世界的大门。
|
2天前
|
缓存 Java Linux
探索安卓开发:从新手到专家的旅程
【9月更文挑战第30天】在这篇文章中,我们将一起踏上一段激动人心的旅程,探索安卓开发的广阔世界。无论你是刚入门的新手,还是希望提升技能的开发者,本文都将为你提供宝贵的知识和指导。我们将深入探讨安卓开发的基础知识、关键概念、实用工具和最佳实践,帮助你在安卓开发领域取得更大的成功。让我们一起开启这段精彩的旅程吧!
|
3天前
|
监控 安全 Java
Kotlin 在公司上网监控中的安卓开发应用
在数字化办公环境中,公司对员工上网行为的监控日益重要。Kotlin 作为一种基于 JVM 的编程语言,具备简洁、安全、高效的特性,已成为安卓开发的首选语言之一。通过网络请求拦截,Kotlin 可实现网址监控、访问时间记录等功能,满足公司上网监控需求。其简洁性有助于快速构建强大的监控应用,并便于后续维护与扩展。因此,Kotlin 在安卓上网监控应用开发中展现出广阔前景。
7 1
下一篇
无影云桌面