Android GB28181设备接入端语音广播和语音对讲技术实现探究

简介: 上篇文章提到Android端GB28181接入端的语音广播和语音对讲的实现,从spec角度大概介绍了下流程和简单的接口设计,好多开发者私信我,希望展开说一下。其实这块难度不大,只是广播和对讲涉及到双向实现,如果之前没有相关的积累,从头实现麻烦一些而已。

上篇文章提到Android端GB28181接入端的语音广播和语音对讲的实现,从spec角度大概介绍了下流程和简单的接口设计,好多开发者私信我,希望展开说一下。其实这块难度不大,只是广播和对讲涉及到双向实现,如果之前没有相关的积累,从头实现麻烦一些而已。


语音广播的流程大家应该非常清楚了,简单来说,SIP服务器发送Broadcast语音广播命令到android接入端,接入端应答,在收到200 OK后,发送INVITE消息,Android接入端收到INVITE的200 OK响应后,回复ACK,开始读取并解析RTP包,然后对音频数据解码,输出到Android播放设备即可。


从DEMO来看,当有语音广播接入进来后,GB28181语音广播按钮会处于可用状态。

2741de7ac309420ea71a50e9c285a3a9.png

语音广播信令Listener如下:

package com.gb28181.ntsignalling;
public interface GBSIPAgentListener
{
    /*
    *收到语音广播通知
     */
    void ntsOnNotifyBroadcastCommand(String fromUserName, String fromUserNameAtDomain, String sn, String sourceID, String targetID);
    /*
    *需要准备接受语音广播的SDP内容
     */
    void ntsOnAudioBroadcast(String commandFromUserName, String commandFromUserNameAtDomain, String sourceID, String targetID);
    /*
    *音频广播, 发送Invite请求异常
     */
    void ntsOnInviteAudioBroadcastException(String sourceID, String targetID, String errorInfo);
    /*
    *音频广播, 等待Invite响应超时
     */
    void ntsOnInviteAudioBroadcastTimeout(String sourceID, String targetID);
    /*
    *音频广播, 收到Invite消息最终响应
     */
    void ntsOnInviteAudioBroadcastResponse(String sourceID, String targetID, int statusCode, PlaySessionDescription sessionDescription);
    /*
     * 音频广播, 收到BYE Message
     */
    void ntsOnByeAudioBroadcast(String sourceID, String targetID);
    /*
    * 不是在收到BYE Message情况下, 终止音频广播
     */
    void ntsOnTerminateAudioBroadcast(String sourceID, String targetID);
}

相关信令接口如下:

package com.gb28181.ntsignalling;
public interface GBSIPAgent {
    /*
     *语音广播应答
     */
    void respondBroadcastCommand(String fromUserName, String fromUserNameAtDomain, String sn, String sourceID, String targetID, boolean result);
    /*
    *语音广播接收者发送Invite消息, rtp ssrc暂时由sdk生成
    *@param addressType: ipv4:"IP4", ipv6:"IP6", 其他不支持, 填充SDP用
    *@param localAddress: 本地IP地址, 填充SDP用
    *@param localPort: 本地端口, 填充SDP用
    *@param mediaTransportProtocol: 媒体传输协议, rtp over udp:"RTP/AVP", rtp over tcp:"TCP/RTP/AVP". 其他不支持, 填充SDP用
     */
    boolean inviteAudioBroadcast(String commandFromUserName, String commandFromUserNameAtDomain, String sourceID, String targetID,
                                 String addressType, String localAddress, int localPort, String mediaTransportProtocol);
    /*
    *取消音频广播, 这个需要在invite收到临时响应之后,最终响应之前才能成功, 如果UAS已经发送过最终响应, UAS收到cancel不做处理, 具体参考RFC3261
     */
    boolean cancelAudioBroadcast(String sourceID, String targetID);
    /*
    *终止语音广播会话, 发送BYE消息
     */
    boolean byeAudioBroadcast(String sourceID, String targetID);
}

RTP音频包接收和解码输出接口,由于我们已经有非常成熟的RTMP和RTSP Player,我们是要在此基础上,扩展一些接口即可:

/*
 * SmartPlayerJniV2.java
 * SmartPlayerJniV2
 *
 * Github: https://github.com/daniulive/SmarterStreaming
 * 
 */
package com.daniulive.smartplayer;
public class SmartPlayerJniV2 {
/**
   * Initialize Player(启动播放实例)
   *
   * @param ctx: get by this.getApplicationContext()
   *
   * <pre>This function must be called firstly.</pre>
   *
   * @return player handle if successful, if return 0, which means init failed. 
   */
  public native long SmartPlayerOpen(Object ctx);
  /**
   * Set External Audio Output(设置回调PCM数据)
   *
   * @param handle: return value from SmartPlayerOpen()
   *
   * @param external_audio_output:  External Audio Output
   *
   * @return {0} if successful
   */
  public native int SmartPlayerSetExternalAudioOutput(long handle, Object external_audio_output);
  /**
   * Set Audio Data Callback(设置回调编码后音频数据)
   *
   * @param handle: return value from SmartPlayerOpen()
   *
   * @param audio_data_callback: Audio Data Callback.
   *
   * @return {0} if successful
   */
  public native int SmartPlayerSetAudioDataCallback(long handle, Object audio_data_callback);
  /**
   * Set buffer(设置缓冲时间,单位:毫秒)
   *
   * @param handle: return value from SmartPlayerOpen()
   *
   * @param buffer:
   *
   * <pre> NOTE: Unit is millisecond, range is 0-5000 ms </pre>
   *
   * @return {0} if successful
   */
  public native int SmartPlayerSetBuffer(long handle, int buffer);
  /**
   * Set mute or not(设置实时静音)
   *
   * @param handle: return value from SmartPlayerOpen()
   *
   * @param is_mute: if with 1:mute, if with 0: does not mute
   *
   * @return {0} if successful
   */
  public native int SmartPlayerSetMute(long handle, int is_mute);
  /**
   * 设置播放音量
   *
   * @param handle: return value from SmartPlayerOpen()
   *
   * @param volume: 范围是[0, 100], 0是静音,100是最大音量, 默认是100
   *
   * @return {0} if successful
   */
  public native int SmartPlayerSetAudioVolume(long handle, int volume);
  /**
   * 清除所有 rtp receivers
   *
   * @param handle: return value from SmartPlayerOpen()
   *
   * @return {0} if successful
   */
  public native int SmartPlayerClearRtpReceivers(long handle);
  /**
   * 增加 rtp receiver
   *
   * @param handle: return value from SmartPlayerOpen()
   *
   * @param rtp_receiver_handle: return value from CreateRTPReceiver()
   *
   * @return {0} if successful
   */
  public native int SmartPlayerAddRtpReceiver(long handle, long rtp_receiver_handle);
  /**
   * 设置需要播放或录像的RTMP/RTSP url
   *
   * @param handle: return value from SmartPlayerOpen()
   *
   * @param uri: rtsp/rtmp playback/recorder uri
   *
   * @return {0} if successful
   */
  public native int SmartPlayerSetUrl(long handle, String uri);
  /**
   * Start playback stream(开始播放)
   *
   * @param handle: return value from SmartPlayerOpen()
   *
   * @return {0} if successful
   */
  public native int SmartPlayerStartPlay(long handle);
  /**
   * Stop playback stream(停止播放)
   *
   * @param handle: return value from SmartPlayerOpen()
   *
   * @return {0} if successful
   */
  public native int SmartPlayerStopPlay(long handle);
  /**
   * Start pull stream(开始拉流,用于数据转发,只拉流不播放)
   *
   * @param handle: return value from SmartPlayerOpen()
   *
   * @return {0} if successful
   */
  public native int SmartPlayerStartPullStream(long handle);
  /**
   * Stop pull stream(停止拉流)
   *
   * @param handle: return value from SmartPlayerOpen()
   *
   * @return {0} if successful
   */
  public native int SmartPlayerStopPullStream(long handle);
  /**
   * 关闭播放实例,结束时必须调用close接口释放资源
   *
   * @param handle: return value from SmartPlayerOpen()
   *
   * <pre> NOTE: it could not use player handle after call this function. </pre> 
   *
   * @return {0} if successful
   */
  public native int SmartPlayerClose(long handle);
  /*++++++++++++++++++RTP Receiver++++++++++++++++++++++*/
  /*
   * 创建RTP Receiver
   *
   * @param reserve:保留参数传0
   *
   * @return RTP Receiver 句柄,0表示失败
   */
  public native long CreateRTPReceiver(int reserve);
  /**
   *设置 RTP Receiver传输协议
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   * @param transport_protocol, 0:UDP, 1:TCP, 默认是UDP
   *
   * @return {0} if successful
   */
  public native int SetRTPReceiverTransportProtocol(long rtp_receiver_handle, int transport_protocol);
  /**
   *设置 RTP Receiver IP地址类型
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   * @param ip_address_type, 0:IPV4, 1:IPV6, 默认是IPV4
   *
   * @return {0} if successful
   */
  public native int SetRTPReceiverIPAddressType(long rtp_receiver_handle, int ip_address_type);
  /**
   *设置 RTP Receiver RTP Socket本地端口
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   * @param port, 必须是偶数,设置0的话SDK会自动分配, 默认值是0
   *
   * @return {0} if successful
   */
  public native int SetRTPReceiverLocalPort(long rtp_receiver_handle, int port);
  /**
   *设置 RTP Receiver SSRC
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   * @param ssrc, 如果设置的话,这个字符串要能转换成uint32类型, 否则设置失败
   *
   * @return {0} if successful
   */
  public native int SetRTPReceiverSSRC(long rtp_receiver_handle, String ssrc);
  /**
   *创建 RTP Receiver 会话
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   * @param reserve, 保留值,目前传0
   *
   * @return {0} if successful
   */
  public native int CreateRTPReceiverSession(long rtp_receiver_handle, int reserve);
  /**
   *获取 RTP Receiver RTP Socket本地端口
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   *
   * @return 失败返回0, 成功的话返回响应的端口, 请在CreateRTPReceiverSession返回成功之后调用
   */
  public native int GetRTPReceiverLocalPort(long rtp_receiver_handle);
  /**
   *设置 RTP Receiver Payload 相关信息
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   *
   * @param payload_type, 请参考 RFC 3551
   *
   * @param encoding_name, 编码名, 请参考 RFC 3551, 如果payload_type不是动态的, 可能传null就好
   *
   * @param media_type, 媒体类型, 请参考 RFC 3551, 1 是视频, 2是音频
   *
   * @param clock_rate, 请参考 RFC 3551
   *
   * @return {0} if successful
   */
  public native int SetRTPReceiverPayloadType(long rtp_receiver_handle, int payload_type, String encoding_name, int media_type, int clock_rate);
  /**
   *设置 RTP Receiver 音频采样率
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   * @param sampling_rate, 音频采样率
   *
   * @return {0} if successful
   */
  public native int SetRTPReceiverAudioSamplingRate(long rtp_receiver_handle, int sampling_rate);
  /**
   *设置 RTP Receiver 音频通道数
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   * @param channels, 音频通道数
   *
   * @return {0} if successful
   */
  public native int SetRTPReceiverAudioChannels(long rtp_receiver_handle, int channels);
  /**
   *设置 RTP Receiver 远端地址
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   * @param address, IP地址
   * @param port, 端口
   *
   * @return {0} if successful
   */
  public native int SetRTPReceiverRemoteAddress(long rtp_receiver_handle, String address, int port);
  /**
   *初始化 RTP Receiver
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   *
   * @return {0} if successful
   */
  public native int InitRTPReceiver(long rtp_receiver_handle);
  /**
   *UnInit RTP Receiver
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   *
   * @return {0} if successful
   */
  public native int UnInitRTPReceiver(long rtp_receiver_handle);
  /**
   *Destory RTP Receiver Session
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   *
   * @return {0} if successful
   */
  public native int DestoryRTPReceiverSession(long rtp_receiver_handle);
  /**
   *Destory RTP Receiver
   *
   * @param rtp_receiver_handle, CreateRTPReceiver
   *
   * @return {0} if successful
   */
  public native int DestoryRTPReceiver(long rtp_receiver_handle);
  /*++++++++++++++++++RTP Receiver++++++++++++++++++++++*/
}

上层调用DEMO实例代码:

public class AndroidGB28181Demo implements GBSIPAgentListener {
    private String gb_source_id_ = null;
    private String gb_target_id_ = null;
    private long player_handle_ = 0;
    private long rtp_receiver_handle_ = 0;
    private AtomicLong last_receive_audio_data_time_ = new AtomicLong(0);
    @Override
    public void ntsOnNotifyBroadcastCommand(String fromUserName, String fromUserNameAtDomain, String sn, String sourceID, String targetID) {
        handler_.postDelayed(new Runnable() {
            @Override
            public void run() {
                if (gb28181_agent_ != null ) {
                    gb28181_agent_.respondBroadcastCommand(from_user_name_, from_user_name_at_domain_,sn_,source_id_, target_id_, true);
                }
            }
            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);
    }
    @Override
    public void ntsOnAudioBroadcast(String commandFromUserName, String commandFromUserNameAtDomain, String sourceID, String targetID) {
        handler_.postDelayed(new Runnable() {
            @Override
            public void run() {
                stopAudioPlayer();
                destoryRTPReceiver();
                if (gb28181_agent_ != null ) {
                    String local_ip_addr = IPAddrUtils.getIpAddress(context_);
                    boolean is_tcp = true; // 默认用TCP
                    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();
                            }
                        } else {
                            destoryRTPReceiver();
                        }
                    }
                }
            }
            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);
    }
    @Override
    public void ntsOnInviteAudioBroadcastException(String sourceID, String targetID, String errorInfo) {
        handler_.postDelayed(new Runnable() {
            @Override
            public void run() {
                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);
    }
    @Override
    public void ntsOnInviteAudioBroadcastTimeout(String sourceID, String targetID) {
        handler_.postDelayed(new Runnable() {
            @Override
            public void run() {
                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);
    }
    class PlayerExternalPCMOutput implements NTExternalAudioOutput {
        private int buffer_size_ = 0;
        private ByteBuffer pcm_buffer_ = null;
        @Override
        public ByteBuffer getPcmByteBuffer(int size)  {
            if(size < 1)
                return null;
            if(buffer_size_ != size) {
                buffer_size_ = size;
                pcm_buffer_ = ByteBuffer.allocateDirect(buffer_size_);
            }
            return pcm_buffer_;
        }
        public void onGetPcmFrame(int ret, int sampleRate, int channel, int sampleSize, int is_low_latency) {
            if (null == pcm_buffer_)
                return;
            pcm_buffer_.rewind();
            if (ret == 0 && isGB28181StreamRunning && publisherHandle != 0 )
                // 传给发送端做音频相关处理
                libPublisher.SmartPublisherOnFarEndPCMData(publisherHandle, pcm_buffer_, sampleRate, channel, sampleSize, is_low_latency);
        }
    }
    class PlayerAudioDataOutput implements NTAudioDataCallback {
        private int buffer_size_ = 0;
        private int param_info_size_ = 0;
        private ByteBuffer buffer_ = null;
        private ByteBuffer parameter_info_ = null;
        @Override
        public ByteBuffer getAudioByteBuffer(int size) {
            if( size < 1 ) return null;
            if (size <= buffer_size_ && buffer_ != null )
                return buffer_;
            buffer_size_ = align(size + 256, 16);
            buffer_ = ByteBuffer.allocateDirect(buffer_size_);
            return buffer_;
        }
        @Override
        public ByteBuffer getAudioParameterInfo(int size) {
            if(size < 1) return null;
            if ( size <= param_info_size_ &&  parameter_info_ != null )
                return  parameter_info_;
            param_info_size_ = align(size + 32, 16);
            parameter_info_ = ByteBuffer.allocateDirect(param_info_size_);
            return parameter_info_;
        }
        public void onAudioDataCallback(int ret, int audio_codec_id, int sample_size, int is_key_frame, long timestamp, int sample_rate, int channel, int parameter_info_size, long reserve)  {
            last_receive_audio_data_time_.set(SystemClock.elapsedRealtime());
        }
    }
    class AudioPlayerDataTimer implements Runnable {
        public static final int THRESHOLD_MS = 60*1000; 
        public static final int INTERVAL_MS = 10*1000; 
        public AudioPlayerDataTimer(long handle) {
            handle_ = handle;
        }
        @Override
        public void run() {
            if (0 == handle_)
                return;
            if (handle_ != player_handle_)
                return;
            long last_update_time = last_receive_audio_data_time_.get();
            long cur_time = SystemClock.elapsedRealtime();
            if ( (last_update_time + this.THRESHOLD_MS) >  cur_time) {
                // 继续定时器
                handler_.postDelayed(new AudioPlayerDataTimer(this.handle_), this.INTERVAL_MS);
            }
            else {
                if (gb_source_id_!= null && gb_target_id_ != null) {
                    if (gb28181_agent_ != null)
                        gb28181_agent_.byeAudioBroadcast(gb_source_id_, gb_target_id_);
                }
                gb_source_id_= null;
                gb_target_id_ = null;
                stopAudioPlayer();
                destoryRTPReceiver();
            }
        }
        private long handle_;
    }
    private boolean startAudioPlay() {
        if (player_handle_ != 0 )
            return false;
        player_handle_ = lib_player_.SmartPlayerOpen(context_);
        if (player_handle_ == 0)
            return false;
        // lib_player_.SetSmartPlayerEventCallbackV2(player_handle_,new EventHandePlayerV2());
        lib_player_.SmartPlayerSetBuffer(player_handle_, 0);
        lib_player_.SmartPlayerSetReportDownloadSpeed(player_handle_, 1, 10);
        lib_player_.SmartPlayerClearRtpReceivers(player_handle_);
        lib_player_.SmartPlayerAddRtpReceiver(player_handle_, rtp_receiver_handle_);
        lib_player_.SmartPlayerSetSurface(player_handle_, null);
        // lib_player_.SmartPlayerSetRenderScaleMode(player_handle_, 1);
        lib_player_.SmartPlayerSetAudioOutputType(player_handle_, 1);
        lib_player_.SmartPlayerSetMute(player_handle_, 0);
        lib_player_.SmartPlayerSetAudioVolume(player_handle_, 100);
        lib_player_.SmartPlayerSetExternalAudioOutput(player_handle_, new PlayerExternalPCMOutput());
        lib_player_.SmartPlayerSetUrl(player_handle_, "rtp://xxxxxxxxxxxxxxxxxxx");
        if (0 != lib_player_.SmartPlayerStartPlay(player_handle_)) {
            lib_player_.SmartPlayerClose(player_handle_);
            player_handle_ = 0;
            Log.e(TAG,  "start audio paly failed");
            return false;
        }
        lib_player_.SmartPlayerSetAudioDataCallback(player_handle_, new PlayerAudioDataOutput());
        if (0 ==lib_player_.SmartPlayerStartPullStream(player_handle_) ) {
            // 启动定时器,长时间收不到音频数据,则停止播放,发送BYE
            last_receive_audio_data_time_.set(SystemClock.elapsedRealtime());
            handler_.postDelayed(new AudioPlayerDataTimer(player_handle_), AudioPlayerDataTimer.INTERVAL_MS);
        }
        return true;
    }
    private void stopAudioPlayer() {
        if (player_handle_ != 0 ) {
            lib_player_.SmartPlayerStopPullStream(player_handle_);
            lib_player_.SmartPlayerStopPlay(player_handle_);
            lib_player_.SmartPlayerClose(player_handle_);
            player_handle_ = 0;
        }
    }
    private void destoryRTPReceiver() {
        if (rtp_receiver_handle_ != 0) {
            lib_player_.UnInitRTPReceiver(rtp_receiver_handle_);
            lib_player_.DestoryRTPReceiverSession(rtp_receiver_handle_);
            lib_player_.DestoryRTPReceiver(rtp_receiver_handle_);
            rtp_receiver_handle_ = 0;
        }
    }
    @Override
    public void ntsOnInviteAudioBroadcastResponse(String sourceID, String targetID, int statusCode, PlaySessionDescription sessionDescription) {
        handler_.postDelayed(new Runnable() {
            @Override
            public void run() {
                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());
                            int clock_rate = audio_attr.getClockRate();
                            lib_player_.SetRTPReceiverPayloadType(rtp_receiver_handle_, audio_attr.getPayloadType(),  audio_attr.getEncodingName(), 2, clock_rate);
                            // 如果是PCMA, 会默认填采样率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_source_id_ = source_id_;
                                gb_target_id_ = target_id_;
                            }
                        }
                    } 
                    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);
    }
    @Override
    public void ntsOnByeAudioBroadcast(String sourceID, String targetID) {
        handler_.postDelayed(new Runnable() {
            @Override
            public void run() {
                gb_source_id_ = null;
                gb_target_id_ = null;
                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);
    }
    @Override
    public void ntsOnTerminateAudioBroadcast(String sourceID, String targetID) {
        handler_.postDelayed(new Runnable() {
            @Override
            public void run() {
                gb_source_id_ = null;
                gb_target_id_ = null;
                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);
    }
}

以上是大概的流程,感兴趣的开发者,可Q我89030985,通过自测和现场的反馈,由于我们有回音消除机制,整体的体验还是非常不错的。


有开发者私信我们,如果从头开发Android平台的GB28181接入端,需要多久?我想说的是,如果是按照SPEC实现个DEMO,验证技术可行性的话不难,但是如果是产品级,确保功能完备性能优异长时间运行稳定的话,从头开发,难度还是挺大的。

相关文章
|
3天前
|
存储 API 开发工具
kotlin安卓开发,如何获取设备的唯一id, 有哪些开源库
在Kotlin的Android开发中,获取设备唯一ID的方法包括不稳定的ANDROID_ID、需要权限的IMEI、使用UUID与SharedPreference结合,以及考虑隐私的Firebase Installations ID和Advertising ID。由于隐私问题和Google Play政策,IMEI和ANDROID_ID不推荐作为长期唯一标识。推荐使用UUID(首次安装时生成并存储),或在涉及广告时使用Advertising ID(需用户同意),而Firebase Installations ID则提供了一种合规的设备标识选项。在选择方法时,必须遵守隐私指南和政策。
|
3天前
|
安全 Android开发 iOS开发
Android vs iOS:移动操作系统的技术比较与未来发展
本文深入探讨了Android和iOS这两大主流移动操作系统的技术特点和差异,从架构设计、安全性、开发环境、用户体验等多个方面进行详细分析。通过对比两者在市场份额、生态系统建设以及未来发展方向上的表现,本文将为读者提供一个全面的视角,以便更好地理解这两种操作系统的当前地位和未来潜力。
|
17天前
|
机器学习/深度学习 传感器 vr&ar
探索安卓与iOS平台下的虚拟现实技术发展
随着移动设备的普及和技术的不断进步,安卓和iOS平台上的虚拟现实(VR)技术发展迅速。本文将探讨安卓与iOS平台下虚拟现实技术的最新进展,包括技术特点、应用场景以及未来发展趋势。
27 0
|
18天前
|
机器学习/深度学习 Linux 5G
揭秘安卓系统背后的技术奇迹
【5月更文挑战第31天】本文将深入探讨安卓系统背后的技术原理,从其发展历程、核心架构到最新的技术创新,全面解析安卓系统的技术魅力。通过对比分析,我们将揭示安卓系统在移动设备领域的独特优势和未来发展趋势。
|
18天前
|
物联网 区块链 Android开发
构建高效Android应用:Kotlin与Jetpack的实践之路未来技术的融合潮流:区块链、物联网与虚拟现实的交汇点
【5月更文挑战第30天】 在移动开发领域,效率和性能始终是开发者追求的核心。随着技术的不断进步,Kotlin语言以其简洁性和现代化特性成为Android开发的新宠。与此同时,Jetpack组件为应用开发提供了一套经过实践检验的库、工具和指南,旨在简化复杂任务并帮助提高应用质量。本文将深入探索如何通过Kotlin结合Jetpack组件来构建一个既高效又稳定的Android应用,并分享在此过程中的最佳实践和常见陷阱。
|
19天前
|
安全 物联网 测试技术
构建未来:Android与IoT设备的无缝交互深入探索软件自动化测试的未来趋势
【5月更文挑战第30天】在物联网(IoT)技术快速发展的当下,Android系统因其开放性和广泛的用户基础成为了连接智能设备的首选平台。本文将探讨如何通过现代Android开发技术实现智能手机与IoT设备的高效、稳定连接,并分析其中的挑战和解决方案。我们将深入挖掘Android系统的底层通信机制,提出创新的交互模式,并通过实例演示如何在Android应用中集成IoT控制功能,旨在为开发者提供一套可行的指导方案,促进IoT生态系统的进一步发展。
|
19天前
|
移动开发 数据库 Android开发
构建高效Android应用:探究Kotlin协程的优势与实践
【5月更文挑战第29天】 随着移动开发技术的不断进步,开发者寻求更高效、更简洁的方式来编写代码。在Android平台上,Kotlin语言凭借其现代化的特性和对协程的原生支持,成为提高开发效率的关键。本文将深入分析Kotlin协程的核心优势,并通过实例展示如何在Android应用开发中有效地利用协程来处理异步任务,优化性能,以及提升用户体验。通过对比传统线程和回调机制,我们将揭示协程如何简化异步编程模型,并减少内存消耗,使应用更加健壮和可维护。
|
19天前
|
移动开发 Java Android开发
构建高效Android应用:探究Kotlin与Java的性能差异
【5月更文挑战第29天】 在移动开发领域,性能优化一直是开发者追求的关键目标。随着Kotlin在Android开发中的普及,了解其与传统Java语言在性能方面的差异成为一项重要议题。本文通过深入分析和对比两种语言的运行效率、启动时间以及内存消耗,为开发者在选择编程语言时提供数据支持和实践指南,从而帮助他们构建更加高效的Android应用。
|
20天前
|
安全 网络安全 Android开发
安卓应用开发:打造流畅的用户体验网络安全与信息安全:防御前线的技术与意识
【5月更文挑战第29天】在移动应用市场中,用户对流畅体验的要求日益提高。针对安卓平台,开发者需深入理解系统原理、内存管理及性能优化策略。本文旨在探讨安卓应用开发中的关键技术和工具,包括布局优化、内存泄漏防治以及使用Kotlin语言的优势,以期为读者提供实用的性能提升指导和实践建议。
|
20天前
|
物联网 区块链 vr&ar
构建高效Android应用:Kotlin协程的实践指南未来交织:新兴技术趋势与跨领域应用探索
【5月更文挑战第28天】随着移动应用开发的不断进步,开发者寻求更高效、更简洁的方式来处理异步任务和提升用户体验。在Android平台上,Kotlin协程作为一种轻量级的线程管理方案,提供了强大的工具来简化并发和异步编程。本文将深入探讨Kotlin协程的核心概念,并通过实例演示如何在Android应用中利用协程优化性能和响应性。通过本文,你将学会如何运用协程来编写更加流畅和高效的代码,同时减少内存消耗和提高应用的稳定性。 【5月更文挑战第28天】 随着科技的迅猛发展,一系列创新技术如区块链、物联网(IoT)、虚拟现实(VR)等正在逐渐从概念验证走向实际应用。这些技术的融合与交叉不仅预示着信息时