• 如何在Android平台GB28181接入终端实现语音广播和语音对讲


    技术背景

    在之前的blog,我们以Android平台国标接入终端为例,分别介绍了一些常规的功能,比如REGISTER、CATALOG、INVITE、Keepalive、SUBSCRIBE、NOTIFY等常规操作,今天主要介绍下语音广播和语音对讲这部分。

    GB28181平台广播和对讲这块,重要性不言而喻,没有广播的接入终端,数据只是单向流入,加入后,指挥中心和终端之间的联系更紧密,实时双向沟通更方便,适用的行业范围也更广泛。

    相关SPEC解读

    关于语音广播和对讲,感兴趣的开发者可直接参阅GBT 28181-2016.pdf相关技术规范里面的9.12章节,以下是部分精选介绍:

    命令交互流程

    命令描述流程

    a) 1:SIP服务器向语音流接收者发送语音广播通知消息,消息中通过 To头域标明作为目的地址 的语音流接收者ID,消息采用 Message方法携带。

    举例说明:

    1. MESSAGE sip:34020000001380000001@192.168.2.212:12070 SIP/2.0
    2. Via: SIP/2.0/UDP 192.168.2.154:15060;rport;branch=z9hG4bK311226558
    3. From: <sip:34020000002000000001@3402000000>;tag=280226558
    4. To: <sip:34020000001380000001@192.168.2.212:12070>
    5. Call-ID: 172226558
    6. CSeq: 207 MESSAGE
    7. Content-Type: Application/MANSCDP+xml
    8. Max-Forwards: 70
    9. Content-Length: 206
    10. <?xml version="1.0" encoding="GB2312"?>
    11. <Notify>
    12. <CmdType>Broadcast</CmdType>
    13. <SN>461226558</SN>
    14. <SourceID>34020000002000000001</SourceID>
    15. <TargetID>34020000001380000001</TargetID>
    16. </Notify>

    b) 2:语音流接收者收到语音广播通知消息后,向SIP服务器发送200OK 响应。

    1. SIP/2.0 200 OK
    2. CSeq: 207 MESSAGE
    3. Call-ID: 172226558
    4. From: ;tag=280226558
    5. To:
    6. Via: SIP/2.0/UDP 192.168.2.154:15060;rport=15060;branch=z9hG4bK311226558;received=192.168.2.154
    7. Content-Length: 0

    c) 3:语音流接收者向SIP服务器发送语音广播应答消息,消息中通过 To头域标明作为目的地 址的SIP服务器ID,消息采用 Message方法携带。

    1. MESSAGE sip:34020000002000000001@3402000000 SIP/2.0
    2. Call-ID: 0fc1f2c83c28898a29e146d7ef581908@192.168.2.212
    3. CSeq: 337044229 MESSAGE
    4. From: ;tag=93882333
    5. To:
    6. Via: SIP/2.0/UDP 192.168.2.212:12070;rport;branch=z9hG4bK-363733-79fd88c45667975e5ebaf18f84b91a8e
    7. Max-Forwards: 70
    8. User-Agent: NT GB28181 User Agent V1.2(daniusdk.com)
    9. Content-Type: Application/MANSCDP+xml
    10. Content-Length: 180
    11. "1.0" encoding="GB2312"?>
    12. <Response>
    13. <CmdType>BroadcastCmdType>
    14. <SN>461226558SN>
    15. <DeviceID>34020000001380000001DeviceID>
    16. <Result>OKResult>
    17. Response>

    d) 4:SIP服务器收到语音广播应答消息后,向语音流接收者发送200OK 响应。

    1. SIP/2.0 200 OK
    2. Via: SIP/2.0/UDP 192.168.2.212:12070;rport=12070;received=192.168.2.212;branch=z9hG4bK-363733-79fd88c45667975e5ebaf18f84b91a8e
    3. From: ;tag=93882333
    4. To: ;tag=355226593
    5. CSeq: 337044229 MESSAGE
    6. Call-ID: 0fc1f2c83c28898a29e146d7ef581908@192.168.2.212
    7. Content-Length: 0

    e) 5:语音流接收者向SIP服务器发送Invite消息,消息中通过 To头域标明作为目的地址的语音 流发送者ID,消息头域中携带Subject字段,表明请求的语音流发送者ID、发送方媒体流序列 号、语音流接收者ID、接收方媒体流序列号等参数,SDP消息体中s字段为“Play”代表实时点 播,m 字段中媒体参数标识为“audio”表示请求语音媒体流。

    1. INVITE sip:34020000002000000001@3402000000 SIP/2.0
    2. Call-ID: 2b4f0f0512aa1a49ffc645618d0e8bae@192.168.2.212
    3. CSeq: 44264 INVITE
    4. From: ;tag=32ecf22a
    5. To:
    6. Via: SIP/2.0/UDP 192.168.2.212:12070;rport;branch=z9hG4bK-363733-15283c8a0ea0a1e9dbf295ce2359dbe7
    7. Max-Forwards: 70
    8. Contact:
    9. Subject: 34020000002000000001:0200006727,34020000001380000001:0
    10. User-Agent: NT GB28181 User Agent V1.2(daniusdk.com)
    11. Content-Type: APPLICATION/SDP
    12. Content-Length: 221
    13. v=0
    14. o=34020000002000000001 0 0 IN IP4 192.168.2.212
    15. s=Play
    16. c=IN IP4 192.168.2.212
    17. t=0 0
    18. m=audio 25002 TCP/RTP/AVP 8
    19. a=setup:active
    20. a=connection:new
    21. a=recvonly
    22. a=rtpmap:8 PCMA/8000
    23. y=0200006727
    24. f=v/a/1/8/1

    f) 6:SIP服务器收到Invite请求后,通过三方呼叫控制建立媒体服务器和语音流发送者之间的媒体连接。向媒体服务器发送Invite消息,此消息不携带SDP消息体。

    g) 7:媒体服务器收到SIP服务器的Invite请求后,回复200OK 响应,携带SDP消息体,消息体 中描述了媒体服务器接收媒体流的IP、端口、媒体格式等内容。

    h) 8:SIP服务器收到媒体服务器返回的200OK 响应后,向语音流发送者发送Invite请求,消息 中通过 To头域标明作为目的地址的语音流发送者ID,消息头域中携带 Subject字段,表明请 求的语音流发送者ID、发送方媒体流序列号、语音流接收者ID、接收方媒体流序列号等参数, 请求中携带消息7中媒体服务器回复的200OK 响应消息体,s字段为“Play”代表实时点播, m 字段中媒体参数标识为“audio”表示请求语音媒体流,增加y字段描述SSRC值,f字段描述 媒体参数。

    1. SIP/2.0 200 OK
    2. Via: SIP/2.0/UDP 192.168.2.212:12070;rport=12070;received=192.168.2.212;branch=z9hG4bK-363733-15283c8a0ea0a1e9dbf295ce2359dbe7
    3. From: ;tag=32ecf22a
    4. To: ;tag=954226632
    5. CSeq: 44264 INVITE
    6. Call-ID: 2b4f0f0512aa1a49ffc645618d0e8bae@192.168.2.212
    7. Contact:
    8. Content-Length: 222
    9. Content-Type: APPLICATION/SDP
    10. v=0
    11. o=34020000002000000001 0 0 IN IP4 192.168.2.154
    12. s=Play
    13. c=IN IP4 192.168.2.154
    14. t=0 0
    15. m=audio 30005 TCP/RTP/AVP 8
    16. a=sendonly
    17. a=rtpmap:8 PCMA/8000
    18. a=setup:passive
    19. a=connection:new
    20. y=0200006727
    21. f=v/a/1/8/1

    i) 9:语音流发送者收到SIP服务器的Invite请求后,回复200OK 响应,携带SDP消息体,消息 体中描述了媒体流发送者发送媒体流的IP、端口、媒体格式、SSRC 字段等内容,s字段为 “Play”代表实时点播,m 字段中媒体参数标识为“audio”表示请求语音媒体流。

    j) 10:SIP服务器收到语音流发送者返回的200OK 响应后,向媒体服务器发送 ACK 请求,请求 中携带消息9中语音流发送者回复的200OK 响应消息体,完成与媒体服务器的Invite会话 建立过程。

    k) 11:SIP服务器收到语音流发送者返回的200OK 响应后,向语音流发送者发送 ACK 请求,请 求中不携带消息体,完成与语音流发送者的Invite会话建立过程。

    l) 12:完成三方呼叫控制后,SIP服务器通过 B2BUA 代理方式建立语音流接收者和媒体服务器 之间的媒体连接。在消息5中增加SSRC值,转发给媒体服务器。

    m)13:媒体服务器收到Invite请求,回复200OK 响应,携带SDP消息体,消息体中描述了媒体服 务器发送媒体流的IP、端口、媒体格式、SSRC值等内容,s字段为“Play”代表实时点播,m 字段 中媒体参数标识为“audio”表示请求语音媒体流。

    n) 14:SIP服务器将消息13转发给语音流接收者。

    o) 15:语音流接收者收到200OK 响应后,回复 ACK 消息,完成与SIP服务器的Invite会话建立 过程。

    p) 16:SIP服务器将消息15转发给媒体服务器,完成与媒体服务器的Invite会话建立过程。

    q) 17:SIP服务器向语音流接收者发送 BYE消息,断开消息5、14、15建立的Invite会话。

    r) 18:语音流接收者收到 BYE消息后回复200OK 响应,会话断开。

    s) 19:SIP服务器向媒体服务器发送 BYE 消息,断开消息 12、13、16 建立的同媒体服务器的 Invite会话。

    t) 20:媒体服务器收到 BYE消息后回复200OK 响应,会话断开。

    u) 21:SIP服务器向媒体服务器发送 BYE消息,断开消息6、7、10建立的同媒体服务器的Invite 会话。

    v) 22:媒体服务器收到 BYE消息后回复200OK 响应,会话断开。

    w)23:SIP服务器向语音流发送者发送 BYE 消息,断开消息8、9、11建立的同语音流发送者的 Invite会话。

    x) 24:语音流发送者收到 BYE消息后回复200OK 响应,会话断开。

    注:语音广播通知消息除上述流程中通过SIP服务器发出外,也可由语音流发送者发出,消息中通过 To头域标明 作为目的地址的语音流接收者ID,经SIP服务器中转后发往语音流接收者;语音流接收者处理后发送应答消 息,消息中通过 To头域标明作为目的地址的语音流发送者ID,经SIP服务器中转后回复给语音流发送者。后续呼叫流程与上述流程相同。

    语音对讲

    语音对讲功能实现中心用户与前端用户之间的一对一语音对讲功能。 语音对讲功能由下述两个独立的流程组合实现:

    a) 通过9.2的实时视音频点播功能,中心用户获得前端设备的实时视音频媒体流;

    b) 通过9.12的语音广播功能,中心用户向前端对讲设备发送实时音频媒体流,语音流的封装格 式见 C.2.4音频流的 RTP封装定义。

    C.2.4 音频流的 RTP封装

    语音比特流宜采用标准的 RTP协议进行打包,这里只摘录G.711A律的:

    在一个 RTP包中,音频载荷数据应为整数个音频编码帧,且时间长度在20ms~180ms之间。

    音频载荷数据的 RTP封装参数如下:

    a) G.711的主要参数 G.711A律语音编码 RTP包的负载类型(PayloadType)的参数规定如下(见IETFRFC3551— 2003中的表4): 1)负载类型(PT):8; 2) 编码名称(encodingname):PCMA; 3) 时钟频率(clockrate):8kHz; 4) 通道数:1; 5) SDP描述中“m”字段的“media”项:audio。

    技术实现

    语音广播接收这块,由于有之前的RTMP和RTSP播放器积累,直接在player端做相应扩展即可,当收到广播后,GB28181语音广播按钮使能。

    相关接口设计如下:

    1. /*++++++++++++++++++RTP Receiver++++++++++++++++++++++*/
    2. //GitHub: https://github.com/daniulive/SmarterStreaming
    3. //WebSite: https://daniusdk.com
    4. /*
    5. * 创建RTP Receiver
    6. *
    7. * @param reserve:保留参数传0
    8. *
    9. * @return RTP Receiver 句柄,0表示失败
    10. */
    11. public native long CreateRTPReceiver(int reserve);
    12. /**
    13. *设置 RTP Receiver传输协议
    14. *
    15. * @param rtp_receiver_handle, CreateRTPReceiver
    16. * @param transport_protocol, 0:UDP, 1:TCP, 默认是UDP
    17. *
    18. * @return {0} if successful
    19. */
    20. public native int SetRTPReceiverTransportProtocol(long rtp_receiver_handle, int transport_protocol);
    21. /**
    22. *设置 RTP Receiver IP地址类型
    23. *
    24. * @param rtp_receiver_handle, CreateRTPReceiver
    25. * @param ip_address_type, 0:IPV4, 1:IPV6, 默认是IPV4
    26. *
    27. * @return {0} if successful
    28. */
    29. public native int SetRTPReceiverIPAddressType(long rtp_receiver_handle, int ip_address_type);
    30. /**
    31. *设置 RTP Receiver RTP Socket本地端口
    32. *
    33. * @param rtp_receiver_handle, CreateRTPReceiver
    34. * @param port, 必须是偶数,设置0的话SDK会自动分配, 默认值是0
    35. *
    36. * @return {0} if successful
    37. */
    38. public native int SetRTPReceiverLocalPort(long rtp_receiver_handle, int port);
    39. /**
    40. *设置 RTP Receiver SSRC
    41. *
    42. * @param rtp_receiver_handle, CreateRTPReceiver
    43. * @param ssrc, 如果设置的话,这个字符串要能转换成uint32类型, 否则设置失败
    44. *
    45. * @return {0} if successful
    46. */
    47. public native int SetRTPReceiverSSRC(long rtp_receiver_handle, String ssrc);
    48. /**
    49. *创建 RTP Receiver 会话
    50. *
    51. * @param rtp_receiver_handle, CreateRTPReceiver
    52. * @param reserve, 保留值,目前传0
    53. *
    54. * @return {0} if successful
    55. */
    56. public native int CreateRTPReceiverSession(long rtp_receiver_handle, int reserve);
    57. /**
    58. *获取 RTP Receiver RTP Socket本地端口
    59. *
    60. * @param rtp_receiver_handle, CreateRTPReceiver
    61. *
    62. * @return 失败返回0, 成功的话返回响应的端口, 请在CreateRTPReceiverSession返回成功之后调用
    63. */
    64. public native int GetRTPReceiverLocalPort(long rtp_receiver_handle);
    65. /**
    66. *设置 RTP Receiver Payload 相关信息
    67. *
    68. * @param rtp_receiver_handle, CreateRTPReceiver
    69. *
    70. * @param payload_type, 请参考 RFC 3551
    71. *
    72. * @param encoding_name, 编码名, 请参考 RFC 3551, 如果payload_type不是动态的, 可能传null就好
    73. *
    74. * @param media_type, 媒体类型, 请参考 RFC 3551, 1 是视频, 2是音频
    75. *
    76. * @param clock_rate, 请参考 RFC 3551
    77. *
    78. * @return {0} if successful
    79. */
    80. public native int SetRTPReceiverPayloadType(long rtp_receiver_handle, int payload_type, String encoding_name, int media_type, int clock_rate);
    81. /**
    82. *设置 RTP Receiver 音频采样率
    83. *
    84. * @param rtp_receiver_handle, CreateRTPReceiver
    85. * @param sampling_rate, 音频采样率
    86. *
    87. * @return {0} if successful
    88. */
    89. public native int SetRTPReceiverAudioSamplingRate(long rtp_receiver_handle, int sampling_rate);
    90. /**
    91. *设置 RTP Receiver 音频通道数
    92. *
    93. * @param rtp_receiver_handle, CreateRTPReceiver
    94. * @param channels, 音频通道数
    95. *
    96. * @return {0} if successful
    97. */
    98. public native int SetRTPReceiverAudioChannels(long rtp_receiver_handle, int channels);
    99. /**
    100. *设置 RTP Receiver 远端地址
    101. *
    102. * @param rtp_receiver_handle, CreateRTPReceiver
    103. * @param address, IP地址
    104. * @param port, 端口
    105. *
    106. * @return {0} if successful
    107. */
    108. public native int SetRTPReceiverRemoteAddress(long rtp_receiver_handle, String address, int port);
    109. /**
    110. *初始化 RTP Receiver
    111. *
    112. * @param rtp_receiver_handle, CreateRTPReceiver
    113. *
    114. * @return {0} if successful
    115. */
    116. public native int InitRTPReceiver(long rtp_receiver_handle);
    117. /**
    118. *UnInit RTP Receiver
    119. *
    120. * @param rtp_receiver_handle, CreateRTPReceiver
    121. *
    122. * @return {0} if successful
    123. */
    124. public native int UnInitRTPReceiver(long rtp_receiver_handle);
    125. /**
    126. *Destory RTP Receiver Session
    127. *
    128. * @param rtp_receiver_handle, CreateRTPReceiver
    129. *
    130. * @return {0} if successful
    131. */
    132. public native int DestoryRTPReceiverSession(long rtp_receiver_handle);
    133. /**
    134. *Destory RTP Receiver
    135. *
    136. * @param rtp_receiver_handle, CreateRTPReceiver
    137. *
    138. * @return {0} if successful
    139. */
    140. public native int DestoryRTPReceiver(long rtp_receiver_handle);
    141. /*++++++++++++++++++RTP Receiver++++++++++++++++++++++*/

    相关调用代码:

    1. class ButtonGB28181AudioBroadcastListener implements OnClickListener {
    2. public void onClick(View v) {
    3. if (gb_broadcast_source_id_ != null && gb_broadcast_target_id_ != null) {
    4. if (gb28181_agent_ != null ) {
    5. if (gb28181_agent_.byeAudioBroadcast(gb_broadcast_source_id_, gb_broadcast_target_id_) ) {
    6. gb_broadcast_source_id_ = null;
    7. gb_broadcast_target_id_ = null;
    8. btnGB28181AudioBroadcast.setText("GB28181语音广播");
    9. btnGB28181AudioBroadcast.setEnabled(false);
    10. }
    11. }
    12. }
    13. stopAudioPlayer();
    14. destoryRTPReceiver();
    15. }
    16. }
    1. @Override
    2. public void ntsOnNotifyBroadcastCommand(String fromUserName, String fromUserNameAtDomain, String sn, String sourceID, String targetID) {
    3. handler_.postDelayed(new Runnable() {
    4. @Override
    5. public void run() {
    6. Log.i(TAG, "ntsOnNotifyBroadcastCommand, fromUserName:"+ from_user_name_ + ", fromUserNameAtDomain:"+ from_user_name_at_domain_
    7. + ", SN:" + sn_ + ", sourceID:" + source_id_ + ", targetID:" + target_id_);
    8. if (gb28181_agent_ != null ) {
    9. gb28181_agent_.respondBroadcastCommand(from_user_name_, from_user_name_at_domain_,sn_,source_id_, target_id_, true);
    10. btnGB28181AudioBroadcast.setText("收到GB28181语音广播通知");
    11. }
    12. }
    13. private String from_user_name_;
    14. private String from_user_name_at_domain_;
    15. private String sn_;
    16. private String source_id_;
    17. private String target_id_;
    18. public Runnable set(String from_user_name, String from_user_name_at_domain, String sn, String source_id, String target_id) {
    19. this.from_user_name_ = from_user_name;
    20. this.from_user_name_at_domain_ = from_user_name_at_domain;
    21. this.sn_ = sn;
    22. this.source_id_ = source_id;
    23. this.target_id_ = target_id;
    24. return this;
    25. }
    26. }.set(fromUserName, fromUserNameAtDomain, sn, sourceID, targetID),0);
    27. }
    28. @Override
    29. public void ntsOnAudioBroadcast(String commandFromUserName, String commandFromUserNameAtDomain, String sourceID, String targetID) {
    30. handler_.postDelayed(new Runnable() {
    31. @Override
    32. public void run() {
    33. Log.i(TAG, "ntsOnAudioBroadcastPlay, fromFromUserName:" + command_from_user_name_
    34. + " FromUserNameAtDomain:" + command_from_user_name_at_domain_
    35. + " sourceID:" + source_id_ + ", targetID:" + target_id_);
    36. stopAudioPlayer();
    37. destoryRTPReceiver();
    38. if (gb28181_agent_ != null ) {
    39. String local_ip_addr = IPAddrUtils.getIpAddress(context_);
    40. boolean is_tcp = true; // 考虑到跨网段, 默认用TCP传输rtp包
    41. rtp_receiver_handle_ = lib_player_.CreateRTPReceiver(0);
    42. if (rtp_receiver_handle_ != 0 ) {
    43. lib_player_.SetRTPReceiverTransportProtocol(rtp_receiver_handle_, is_tcp?1:0);
    44. lib_player_.SetRTPReceiverIPAddressType(rtp_receiver_handle_, 0);
    45. if (0 == lib_player_.CreateRTPReceiverSession(rtp_receiver_handle_, 0) ) {
    46. int local_port = lib_player_.GetRTPReceiverLocalPort(rtp_receiver_handle_);
    47. boolean ret = gb28181_agent_.inviteAudioBroadcast(command_from_user_name_,command_from_user_name_at_domain_,
    48. source_id_, target_id_, "IP4", local_ip_addr, local_port, is_tcp?"TCP/RTP/AVP":"RTP/AVP");
    49. if (!ret ) {
    50. destoryRTPReceiver();
    51. btnGB28181AudioBroadcast.setText("GB28181语音广播");
    52. }
    53. else {
    54. btnGB28181AudioBroadcast.setText("GB28181语音广播呼叫中");
    55. }
    56. } else {
    57. destoryRTPReceiver();
    58. btnGB28181AudioBroadcast.setText("GB28181语音广播");
    59. }
    60. }
    61. }
    62. }
    63. private String command_from_user_name_;
    64. private String command_from_user_name_at_domain_;
    65. private String source_id_;
    66. private String target_id_;
    67. public Runnable set(String command_from_user_name, String command_from_user_name_at_domain, String source_id, String target_id) {
    68. this.command_from_user_name_ = command_from_user_name;
    69. this.command_from_user_name_at_domain_ = command_from_user_name_at_domain;
    70. this.source_id_ = source_id;
    71. this.target_id_ = target_id;
    72. return this;
    73. }
    74. }.set(commandFromUserName, commandFromUserNameAtDomain, sourceID, targetID),0);
    75. }
    76. @Override
    77. public void ntsOnInviteAudioBroadcastException(String sourceID, String targetID, String errorInfo) {
    78. handler_.postDelayed(new Runnable() {
    79. @Override
    80. public void run() {
    81. Log.i(TAG, "ntsOnInviteAudioBroadcastException, sourceID:" + source_id_ + ", targetID:" + target_id_);
    82. destoryRTPReceiver();
    83. btnGB28181AudioBroadcast.setText("GB28181语音广播");
    84. }
    85. private String source_id_;
    86. private String target_id_;
    87. public Runnable set(String source_id, String target_id) {
    88. this.source_id_ = source_id;
    89. this.target_id_ = target_id;
    90. return this;
    91. }
    92. }.set(sourceID, targetID),0);
    93. }
    94. @Override
    95. public void ntsOnInviteAudioBroadcastTimeout(String sourceID, String targetID) {
    96. handler_.postDelayed(new Runnable() {
    97. @Override
    98. public void run() {
    99. Log.i(TAG, "ntsOnInviteAudioBroadcastTimeout, sourceID:" + source_id_ + ", targetID:" + target_id_);
    100. destoryRTPReceiver();
    101. btnGB28181AudioBroadcast.setText("GB28181语音广播");
    102. }
    103. private String source_id_;
    104. private String target_id_;
    105. public Runnable set(String source_id, String target_id) {
    106. this.source_id_ = source_id;
    107. this.target_id_ = target_id;
    108. return this;
    109. }
    110. }.set(sourceID, targetID),0);
    111. }
    112. @Override
    113. public void ntsOnInviteAudioBroadcastResponse(String sourceID, String targetID, int statusCode, PlaySessionDescription sessionDescription) {
    114. handler_.postDelayed(new Runnable() {
    115. @Override
    116. public void run() {
    117. Log.i(TAG, "ntsOnInviteAudioBroadcastResponse, statusCode:" + status_code_ +" sourceID:" + source_id_ + ", targetID:" + target_id_);
    118. boolean is_need_destory_rtp = true;
    119. if (gb28181_agent_ != null ) {
    120. boolean is_need_bye = 200==status_code_;
    121. if (200 == status_code_ && session_description_ != null && rtp_receiver_handle_ != 0 ) {
    122. MediaSessionDescription audio_des = session_description_.getAudioDescription();
    123. SDPRtpMapAttribute audio_attr = null;
    124. if (audio_des != null && audio_des.getRtpMapAttributes() != null && !audio_des.getRtpMapAttributes().isEmpty() )
    125. audio_attr = audio_des.getRtpMapAttributes().get(0);
    126. if ( audio_des != null && audio_attr != null ) {
    127. lib_player_.SetRTPReceiverSSRC(rtp_receiver_handle_, audio_des.getSSRC());
    128. lib_player_.SetRTPReceiverPayloadType(rtp_receiver_handle_, audio_attr.getPayloadType(),
    129. audio_attr.getEncodingName(), 2, audio_attr.getClockRate());
    130. lib_player_.SetRTPReceiverRemoteAddress(rtp_receiver_handle_, audio_des.getAddress(), audio_des.getPort());
    131. lib_player_.InitRTPReceiver(rtp_receiver_handle_);
    132. if (startAudioPlay()) {
    133. is_need_bye = false;
    134. is_need_destory_rtp = false;
    135. gb_broadcast_source_id_ = source_id_;
    136. gb_broadcast_target_id_ = target_id_;
    137. btnGB28181AudioBroadcast.setText("终止GB28181语音广播");
    138. btnGB28181AudioBroadcast.setEnabled(true);
    139. }
    140. }
    141. } else {
    142. btnGB28181AudioBroadcast.setText("GB28181语音广播");
    143. }
    144. if (is_need_bye)
    145. gb28181_agent_.byeAudioBroadcast(source_id_, target_id_);
    146. }
    147. if (is_need_destory_rtp)
    148. destoryRTPReceiver();
    149. }
    150. private String source_id_;
    151. private String target_id_;
    152. private int status_code_;
    153. private PlaySessionDescription session_description_;
    154. public Runnable set(String source_id, String target_id, int status_code, PlaySessionDescription session_description) {
    155. this.source_id_ = source_id;
    156. this.target_id_ = target_id;
    157. this.status_code_ = status_code;
    158. this.session_description_ = session_description;
    159. return this;
    160. }
    161. }.set(sourceID, targetID, statusCode, sessionDescription),0);
    162. }
    163. @Override
    164. public void ntsOnByeAudioBroadcast(String sourceID, String targetID) {
    165. handler_.postDelayed(new Runnable() {
    166. @Override
    167. public void run() {
    168. Log.i(TAG, "ntsOnByeAudioBroadcast sourceID:" + source_id_ + " targetID:" + target_id_);
    169. gb_broadcast_source_id_ = null;
    170. gb_broadcast_target_id_ = null;
    171. btnGB28181AudioBroadcast.setText("GB28181语音广播");
    172. btnGB28181AudioBroadcast.setEnabled(false);
    173. stopAudioPlayer();
    174. destoryRTPReceiver();
    175. }
    176. private String source_id_;
    177. private String target_id_;
    178. public Runnable set(String source_id, String target_id) {
    179. this.source_id_ = source_id;
    180. this.target_id_ = target_id;
    181. return this;
    182. }
    183. }.set(sourceID, targetID),0);
    184. }
    185. @Override
    186. public void ntsOnTerminateAudioBroadcast(String sourceID, String targetID) {
    187. handler_.postDelayed(new Runnable() {
    188. @Override
    189. public void run() {
    190. Log.i(TAG, "ntsOnTerminateAudioBroadcast sourceID:" + source_id_ + " targetID:" + target_id_);
    191. gb_broadcast_source_id_ = null;
    192. gb_broadcast_target_id_ = null;
    193. btnGB28181AudioBroadcast.setText("GB28181语音广播");
    194. btnGB28181AudioBroadcast.setEnabled(false);
    195. stopAudioPlayer();
    196. destoryRTPReceiver();
    197. }
    198. private String source_id_;
    199. private String target_id_;
    200. public Runnable set(String source_id, String target_id) {
    201. this.source_id_ = source_id;
    202. this.target_id_ = target_id;
    203. return this;
    204. }
    205. }.set(sourceID, targetID),0);
    206. }

    总结

    至此、Android平台GB28181接入终端,如位置订阅、语音广播和语音对讲这块已经全面覆盖,加上之前的技术积累,看了下,已覆盖了以下部分:

    • ​[视频格式]H.264/H.265(Android H.265硬编码);
    • [音频格式]G.711 A律、AAC;
    • [音量调节]Android平台采集端支持实时音量调节;
    • [H.264硬编码]支持H.264特定机型硬编码;
    • [H.265硬编码]支持H.265特定机型硬编码;
    • [软硬编码参数配置]支持gop间隔、帧率、bit-rate设置;
    • [软编码参数配置]支持软编码profile、软编码速度、可变码率设置;
    • 支持纯视频、音视频PS打包传输;
    • 支持RTP OVER UDP和RTP OVER TCP被动模式;
    • 支持信令通道网络传输协议TCP/UDP设置;
    • 支持注册、注销,支持注册刷新及注册有效期设置;
    • 支持设备目录查询应答;
    • 支持心跳机制,支持心跳间隔、心跳检测次数设置;
    • 支持移动设备位置(MobilePosition)订阅和通知;
    • 支持国标GB/T28181—2016平台接入;
    • 支持语音广播及语音对讲;
    • [实时水印]支持动态文字水印、png水印;
    • [实时静音]支持实时静音/取消静音;
    • [实时快照]支持实时快照;
    • [降噪]支持环境音、手机干扰等引起的噪音降噪处理、自动增益、VAD检测。​

    特别是语音广播和语音对讲这块,是GB28181终端接入模块的一个核心扩展功能,在智能门禁、工业与物联网、监控等行业,用途非常广泛,技术实现这块,不要忽略的技术点还有降噪和回音消除这块,由于之前我们有技术积累,可以直接复用,对新入手的开发者来说,也提出了新的挑战,感兴趣的开发者,可以酌情参考。

  • 相关阅读:
    leetcode(力扣) 46. 全排列(回溯)
    Myblockly模块简介
    修复微信小程序不能获取头像和昵称的bug,微信小程序新版头像昵称API使用
    C++ Reference: Standard C++ Library reference: C Library: cwchar: wcsftime
    神经网络中隐藏层的作用,深度神经网络隐藏层数
    js之DOM事件机制(事件流、事件委托)
    人工智能领域从原理详细总结chatgpt的prompt方法
    HTML常用标签的使用
    VTK与OpenGL是什么,有什么关系?
    django中央财经大学体育场地信息管理系统(源码+mysql+论文)
  • 原文地址:https://blog.csdn.net/renhui1112/article/details/126458134