日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android平台GB28181设备接入端如何支持跨网段语音对讲

發布時間:2023/12/20 Android 58 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android平台GB28181设备接入端如何支持跨网段语音对讲 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

技術背景

如果你是音視頻開發者亦或尋求這塊技術方案的公司,在探討這個問題之前,你可能網上看了太多關于語音廣播和語音對講相關的資料,大多文章認為語音對講和語音廣播無本質區別,實現思路也大同小異。

今天我們主要探討的是,語音對講有哪些可行的技術方案?實際使用場景下,分別有哪些限制?如何實現相對可行的語音對講方案?

提到語音對講,典型的限制如RTP UDP包無法實現跨網段的數據傳輸,基于此,一般可以考慮以下兩種解決方案:

方案1:

Android平臺GB28181設備接入端,語音這塊,走實時音視頻點播通道,編碼后的audio數據,封裝到PS包,和視頻數據一起打包。數據接收這塊,跨網段使用RTP over TCP模式。

不幸的是,好多國標平臺側,并不支持TCP,使用UDP打洞,這需要部署單獨的打洞服務器,也存在穿透不成功的情況。

方案2:

通過語音對講模式,一般來說SDP里面“s=Talk”代表語音對講,但實際場景下,又有兩種模式:

  • 模式1:“s=Talk”模式;
  • 模式2:“s=Play”模式。
  • 模式1:“s=Talk”模式,這種實現,相對來說難度稍小,只需把PCMA打包成rtp包發送或接收:

    s=Talk ............ t=0 0 m=audio 端口 RTP/AVP 8 a=rtpmap:8 PCMA/8000 a=sendrecv y=xxxx.......

    模式2:“s=Play”模式:

    s=Play ............ t=0 0 m=audio 端口號 RTP/AVP 96 a=rtpmap:96 PS/90000 a=sendrecv y=xxxxxxxx....

    “s=Play”模式下,SDP處理難度加大,按照GB/T28181規范,當看到SDP描述里面“m=audio”時,可判定國標平臺側不想要video數據,僅需要國標設備接入端發送純音頻即可,從而實現傳統意義的語音對講。

    大多開發者在實現GB28181設備接入的時候都是音視頻數據一起打包發送的,如果需要兼容這種情況,需要針對純音頻打包PS,純音頻打包PS,可以參照GB/T28181-2016規范針對音視頻或純視頻模式下的PS打包,當然,也可以直接PCMA over RTP模式。

    方案2的SDP信息有個“a=sendrecv”,具體來說,用同一個端口來同時發送和接收RTP包。按照GB28181標準,語音對講,先把audio RTP包發到媒體服務器,需要確保各個網段的GB28181設備可以訪問到媒體服務器。Android平臺GB28181設備接入端先主動發RTP包到媒體服務器,媒體服務器再用相同的端口,發到Android平臺GB28181設備接入端。

    值得一提的是,語音廣播在一些國標平臺的實現,可能走點對點模式(如宇視),并沒有通過媒體服務器來轉發RTP包,此外,如果SDP信息中“s=Play”,那么對應的200 OK響應中的SDP 也需要確保是Play模式,即“s=Play”。

    優劣勢分析

    方案1把音視頻數據,按照GB/T28181-2016規范,都打到一個PS包中,然后使用相同的端口發送,PS包大,對帶寬要求也高,如因網絡抖動很容易出現延遲或丟包,從而導致語音對講的極差體驗,而且UDP存在穿透問題。

    方案2,我們只傳純音頻,加之PCMA碼率僅有64kbps,加上RTP頭的字節數,帶寬占用非常小,美中不足的是,技術實現相對復雜。

    技術實現

    我們Android平臺GB28181設備接入模塊,已經實現了上述提到的技術方案,相關接口設計如下:

    // Github: https://github.com/daniulive/SmarterStreaming // Contract: 89030985@qq.compublic interface GBSIPAgentTalkListener {/**收到語音對講INVITE*/void ntsOnInviteTalk(String deviceId, SessionDescription sessionDescription);/**發送talk invite response 異常*/void ntsOnTalkInviteResponseException(String deviceId, int statusCode, String errorInfo);/** 收到CANCEL Talk INVITE請求*/void ntsOnCancelTalk(String deviceId);/** 收到Ack*/void ntsOnAckTalk(String deviceId);/** 收到Bye*/void ntsOnByeTalk(String deviceId);/** 不是在收到BYE Message情況下,終止Talk*/void ntsOnTerminateTalk(String deviceId);void ntsOnTalkDialogTerminated(String deviceId); }public interface GBSIPAgent {// 其他接口省略......void addTalkListener(GBSIPAgentTalkListener talkListener);/**響應Invite Talk 200 OK*/boolean respondTalkInviteOK(String deviceId, String addressType, String localAddress,MediaSessionDescription mainLocalAudioDescription, MediaSessionDescription subLocalAudioDescription);/**響應Invite Talk 其他狀態碼*/boolean respondTalkInvite(int statusCode, String deviceId);/**終止Talk會話*/void terminateTalk(String deviceId, boolean isSendBYE);/**終止所有Talk會話*/void terminateAllTalks(boolean isSendBYE); }

    相關調用示例代碼如下:

    @Overridepublic void ntsOnInviteTalk(String deviceId, SessionDescription sessionDescription) {handler_.postDelayed(new Runnable() {@Overridepublic void run() {gb28181_agent_.respondTalkInvite(180, device_id_);MediaSessionDescription audio_description = null;SDPRtpMapAttribute rtp_map_attribute = null;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()) {rtp_map_attribute = m.getRtpMapAttribute(SDPRtpMapAttribute.PCMA_ENCODING_NAME);if (rtp_map_attribute != null) {audio_description = m;break;}}}if (null == rtp_map_attribute) {for(MediaSessionDescription m : audio_des_list) {if (m != null && m.isValidAddressType() && m.isHasAddress()) {rtp_map_attribute = m.getRtpMapAttribute(SDPRtpMapAttribute.PS_ENCODING_NAME);if (rtp_map_attribute != null) {audio_description = m;break;}}}}}if (null == audio_description) {gb28181_agent_.respondTalkInvite(488, device_id_);Log.i(TAG, "ntsOnInviteTalk get audio description is null, response 488, device_id:" + device_id_);return;}if (null == rtp_map_attribute ) {gb28181_agent_.respondTalkInvite(488, device_id_);Log.i(TAG, "ntsOnInviteTalk get rtp map attribute is null, response 488, device_id:" + device_id_);return;}Log.i(TAG,"ntsOnInviteTalk, device_id:" +device_id_+", is_tcp:" + audio_description.isRTPOverTCP()+ " rtp_port:" + audio_description.getPort() + " ssrc:" + audio_description.getSSRC()+ " address_type:" + audio_description.getAddressType() + " address:" + audio_description.getAddress()+ " payload_type:" + rtp_map_attribute.getPayloadType() + " encoding_name:" + rtp_map_attribute.getEncodingName());long rtp_sender_handle = libPublisher.CreateRTPSender(0);if (0 == rtp_sender_handle) {gb28181_agent_.respondTalkInvite(488, device_id_);Log.i(TAG, "ntsOnInviteTalk CreateRTPSender failed, response 488, device_id:" + device_id_);return;}gb_talk_rtp_payload_type_ = rtp_map_attribute.getPayloadType();gb_talk_rtp_encoding_name_ = rtp_map_attribute.getEncodingName();libPublisher.SetRTPSenderTransportProtocol(rtp_sender_handle, audio_description.isRTPOverUDP()?0:1);libPublisher.SetRTPSenderIPAddressType(rtp_sender_handle, audio_description.isIPv4()?0:1);libPublisher.SetRTPSenderLocalPort(rtp_sender_handle, 0);libPublisher.SetRTPSenderSSRC(rtp_sender_handle, audio_description.getSSRC());libPublisher.SetRTPSenderSocketSendBuffer(rtp_sender_handle, 256*1024); // 音頻配置到256KBlibPublisher.SetRTPSenderClockRate(rtp_sender_handle, rtp_map_attribute.getClockRate());libPublisher.SetRTPSenderDestination(rtp_sender_handle, audio_description.getAddress(), audio_description.getPort());gb_talk_is_receive_ = audio_description.isHasAttribute("sendrecv");if (gb_talk_is_receive_) {libPublisher.EnableRTPSenderReceive(rtp_sender_handle, 1);// libPublisher.SetRTPSenderReceiveSSRC(rtp_sender_handle, audio_description.getSSRC());libPublisher.SetRTPSenderReceivePayloadType(rtp_sender_handle, gb_talk_rtp_payload_type_, gb_talk_rtp_encoding_name_, 2, rtp_map_attribute.getClockRate());// 目前發現某些平臺 PS-PCMA 是8000, 不建議設置//if (gb_talk_rtp_encoding_name_.equals("PS")) {// libPublisher.SetRTPSenderReceivePSClockFrequency(rtp_sender_handle, 8000);// }// 如果是PCMA編碼, 采樣率和通道可以先不設置// libPublisher.SetRTPSenderReceiveAudioSamplingRate(rtp_sender_handle, 8000);// libPublisher.SetRTPSenderReceiveAudioChannels(rtp_sender_handle, 1);}if (libPublisher.InitRTPSender(rtp_sender_handle) != 0 ) {gb28181_agent_.respondTalkInvite(488, device_id_);libPublisher.DestoryRTPSender(rtp_sender_handle);return;}int local_port = libPublisher.GetRTPSenderLocalPort(rtp_sender_handle);if (0==local_port) {gb28181_agent_.respondTalkInvite(488, device_id_);libPublisher.DestoryRTPSender(rtp_sender_handle);return;}Log.i(TAG,"ntsOnInviteTalk get local_port:" + local_port);String local_ip_addr = IPAddrUtils.getIpAddress(context_);MediaSessionDescription main_local_audio_des = new MediaSessionDescription(audio_description.getType());main_local_audio_des.addFormat(String.valueOf(rtp_map_attribute.getPayloadType()));main_local_audio_des.addRtpMapAttribute(rtp_map_attribute);main_local_audio_des.addAttribute(new SDPAttribute("sendonly"));if (audio_description.isRTPOverTCP()) {// tcp主動鏈接服務端main_local_audio_des.addAttribute(new SDPAttribute("setup", "active"));main_local_audio_des.addAttribute(new SDPAttribute("connection", "new"));}main_local_audio_des.setPort(local_port);main_local_audio_des.setTransportProtocol(audio_description.getTransportProtocol());main_local_audio_des.setSSRC(audio_description.getSSRC());MediaSessionDescription sub_local_audio_des = null;if (gb_talk_is_receive_) {sub_local_audio_des = new MediaSessionDescription(audio_description.getType());sub_local_audio_des.addFormat(String.valueOf(rtp_map_attribute.getPayloadType()));sub_local_audio_des.addRtpMapAttribute(rtp_map_attribute);sub_local_audio_des.addAttribute(new SDPAttribute("recvonly"));if (audio_description.isRTPOverTCP()) {// tcp主動鏈接服務端sub_local_audio_des.addAttribute(new SDPAttribute("setup", "active"));sub_local_audio_des.addAttribute(new SDPAttribute("connection", "new"));}sub_local_audio_des.setPort(local_port);sub_local_audio_des.setTransportProtocol(audio_description.getTransportProtocol());sub_local_audio_des.setSSRC(audio_description.getSSRC());}if (!gb28181_agent_.respondTalkInviteOK(device_id_, audio_description.getAddressType(), local_ip_addr, main_local_audio_des, sub_local_audio_des) ) {libPublisher.DestoryRTPSender(rtp_sender_handle);Log.e(TAG, "ntsOnInviteTalk call respondPlayInviteOK failed.");return;}gb_talk_rtp_sender_handle_ = rtp_sender_handle;}private String device_id_;private SessionDescription session_description_;public Runnable set(String device_id, SessionDescription session_des) {this.device_id_ = device_id;this.session_description_ = session_des;return this;}}.set(deviceId, sessionDescription),0);}

    總結

    實際上,GB28181平臺語音廣播和語音對講,特別是語音對講,不光要解決傳輸跨網段問題,還可能要處理回音,噪音,增益控制等,這塊,我們之前有了非常好的技術積累,處理起來輕車熟路,有需要測試的開發者,也可以私信聯系我。

    兩種技術方案雖然都可以實現語音對講,方案1相對實現起來簡單,但缺點明顯,方案2技術優勢有目共睹,更適合相對復雜的網絡環境。遺憾的是,大多公司都沒有實現,或者說市面上真正實現跨網段語音對講的尚在少數,感興趣的開發者可以酌情參考。

    總結

    以上是生活随笔為你收集整理的Android平台GB28181设备接入端如何支持跨网段语音对讲的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。