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

2023-10-27

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

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

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

 

语音广播信令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,验证技术可行性的话不难,但是如果是产品级,确保功能完备性能优异长时间运行稳定的话,从头开发,难度还是挺大的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • android 4.4中的流媒体渲染过程

    第一次写blog 只是为了记下学习的过程 android中东西很多 架构和流程都很复杂 经常发现以前学习过的很多东西 即使当时看明白没多久就忘记了 只能重新拾起再看 于是想起blog这个东东 写下来总不会忘记 也和别人一起共享 以下基于an
  • Android平台GB28181历史视音频文件检索规范探讨及技术实现

    技术背景 我们在做Android平台GB28181设备接入侧模块的时候 特别是执法记录仪或类似场景 系统除了对常规的录像有要求 还需要能和GB28181平台侧交互 比如实现设备侧视音频文件检索 下载或回放 本文假定记录仪或相关设备已经完成录
  • Android平台GB28181设备接入技术探讨

    GB T28181技术背景 在此之前 我们先对协议规范做个简单了解 GB28181协议是一种用于视频监控系统互联互通的国际标准 它定义了视频监控系统中的设备间如何进行通信 交换数据和协调控制 以下是GB28181协议的一些主要内容 设备互联
  • 基于RTMP实现Linux

    背景 Windows操作系统自问世以来 以其简单易用的图形化界面操作受到大众追捧 为计算机的普及 科技的发展做出了不可磨灭的功绩 也慢慢的成为人们最依赖的操作系统 在中国 90 以上的办公环境都是Windows 学校和各种培训班的培训内容也
  • 网络流媒体(七)———RTSP

    RTSP协议介绍 RTSP协议的一些分析 一 一些字符串函数的使用 RTSP协议的一些分析 二 printf类似函数 sscanf以及log保存到内存中 printf输入重定位 1 简介 DSP产生的媒体流需要通过网络传送到客户端 如图1
  • wireshark提取视频数据之RTP包中提取H264和H265

    wireshark提取视频数据之RTP包中提取H264和H265 文章目录 wireshark提取视频数据之RTP包中提取H264和H265 1 背景 2 提取前工作 3 H264视频从RTP包中提取步骤 4 H265视频从RTP包中提取步
  • fmp4打包H265视频流

    1 fmp4打包H265视频流 文章目录 1 fmp4打包H265视频流 1 1 码流存储和传输格式介绍 1 1 1 Annex B封装格式 1 1 2 AVCC封装格式 1 1 2 HVCC封装格式 1 2 fmp4封装H265 1 2
  • NV21、NV12、YV12、RGB565、YUV等颜色编码格式区别和接口设计探讨

    NV21 NV12 YV12 RGB565 YUV扫盲 NV21 NV12 YV12 RGB565 YUV分别是不同的颜色编码格式 这些颜色编码格式各有特点 适用于不同的应用场景 选择合适的颜色编码格式取决于具体的需求和环境 NV21 NV
  • FFmpeg入门详解之92:Live555学习之(一)-------Live555的基本介绍

    Live555学习之 一 Live555的基本介绍 前一阵子 因为项目需要 研究了一下Live555开源框架 研究的不是很深入 基本上把Live555当做API用了一下 但是毕竟也是本人看的第一个开源框架 在此记录总结一下 Live555是
  • librtmp ssl 1.0.0 到 ssl 1.1.1

    openssl 版本更新了 导致 librtmp 库不能使用 于是查查资料 Compiler errors dereferencing pointer to incomplete type DH aka struct dh st 根据上面的
  • GB28181平台如何接入无人机实现智能巡检?

    大家都知道 无人机巡检系统 有效解决了传统巡查工作空间和时间局限问题 降低人力工作成本 有效替代人工巡检工作模式 智能巡检系统通过人工智能技术和机械智能技术完美结合 在工业等场景下 应用非常广泛 本文旨在讲如何实现无人机 如大疆无人机 数据
  • FFmpeg学习(11)——视频转码之-crf参数详解

    什么是固定码率因子crf Constant Rate Factor 固定码率因子 CRF 是 x264 和 x265 编码器的默认质量 和码率控制 设置 取值范围是 0 到 51 这其中越低的值 结果质量越好 同时输出文件越大 越高的值意味
  • 使用nginx做为http-flv服务如何解决跨域问题

    什么是跨域 跨域是指浏览器的同源策略限制 这个策略会阻止一个域的javascript脚本和另外一个域的内容进行交互 如果一个请求url的协议 域名 端口三者之间任意一个与当前页面的url不同即为跨域 如下图所示即为跨域时的报错 使用ngin
  • 前端实现预览功能,播放rtsp视频流(node.js+ffmpeg+flv.js)

    实现思路 获取摄像头rtsp流 通过node js ffmpeg转码 通过哔哩哔哩flv js播放 1 获取摄像机RTSP流 之前文章有说明不多阐述 2 配置流媒体服务器 1 下载安装node js 运行node js 网上教程很多自行下载
  • Android平台一对一音视频通话方案对比:WebRTC VS RTMP VS RTSP

    一对一音视频通话使用场景 一对一音视频通话都需要稳定 清晰和流畅 以确保良好的用户体验 常用的使用场景如下 社交应用 社交应用是一种常见的使用场景 用户可以通过音视频通话进行面对面的交流 在线教育 老师和学生可以通过音视频通话功能进行实时互
  • Android平台GB28181设备接入模块相关博客概览

    Android平台GB28181设备接入模块 可实现不具备国标音视频能力的 Android终端 通过平台注册接入到现有的GB T28181 2016服务 可用于如智能监控 智慧零售 智慧教育 远程办公 生产运输 智慧交通 车载或执法记录仪等
  • 视频编码格式发展史

    1 编码标准之战 想预测未来 就回顾历史 先来看看H 264这些编码的从标准化到现在普及的过程 人们一直在想尽办法提高视频编码的效率 让它在尽可能小的体积内提供最好的画面质量 从而满足人们对于视频传输 存储的需求 长期以来 视频编码标准主要
  • 基于SRS的视频直播服务器搭建

    srs提供的一个demo实例 包括实时流的rtmp播放 hls播放 视频会议 ffmpeg视频变换 jwplayer播放 OSMF播放 vlc播放等等功能 下面是在Centos 6 x环境下的编译搭建流程 1 下载或更新源码或者使用git更
  • 元宇宙时代超高清视音频技术白皮书关于流媒体协议和媒体传输解读

    流媒体协议 元宇宙业务场景对流媒体传输的实时性和互动性提出了更高的要求 这就需要在传统的 RTMP SRT HLS 等基础上增加实时互动的支持 实时互动 指在远程条件下沟通 协作 可随时随地接入 实时地传递虚实融合的多维信息 身临其境的交互
  • [技术分享]Android平台实时音视频录像模块设计之道

    实现背景 录像有什么难的 无非就是数据过来 编码保存mp4而已 这可能是好多开发者在做录像模块的时候的思考输出 是的 确实不难 但是做好 或者和其他模块有非常好的逻辑配合 确实不容易 好多开发者希望聊聊录像模块 实际上录像这块 需求层面的东

随机推荐

  • JavaWeb通过前端向Mysql数据库中插入数据问题

    作为入门小白 记录下因为基础不扎实而踩得坑 在写注册界面时 需要利用web界面输入数据 idea操作向数据库插入数据 首先确定了数据库正常 tomcat正常运行 sql语句正常 在idea中测试业务层也能正常向数据库中插入数据 但是在web
  • 构造器注入导致的循环依赖问题及解决方案

    构造器注入导致的循环依赖问题及解决方案 目录 概述 实现思路分析 循环依赖 问题 解决方案 方式二 相关工具如下 分析 小结 参考资料和推荐阅读 LD is tigger forever CG are not brothers foreve
  • [疯狂Java]NIO.2:walkFileTree、FileVisitor(遍历文件/目录)

    1 遍历文件和目录 FileVisitor 1 在旧版本中遍历文件系统只能通过递归的方法来实现 但是这种方法不仅消耗资源大而且效率低 2 NIO 2的Files工具类提供了一个静态工具方法walkFileTree来高效并优雅地遍历文件系统
  • 七十五.二分查找的递归实现 —— JAVA

    编写递归代码是最重要的有以下三点 递归总有一个最简单的情况 方法的第一条语句总是一个包含 return的条件语句 递归调用总是尝试解决一个规模更小的子问题 这样递归才能收敛到最简单的情况 递归调用的父问题和尝试解决的子问题之间不应该有交集
  • 伪代码书写规范

    伪代码 Pseudocode 是一种算法描述语言 使用伪代码的目的是为了使被描述的算法可以容易地以任何一种编程语言 Pascal C Java etc 实现 因此 伪代码必须结构清晰 代码简单 可读性好 并且类似自然语言 介于自然语言与编程
  • matlab lpc求共振峰频率,用Python中的LPC估计共振峰

    我对信号处理还不太熟悉 关于这一点 numpy scipy和matlab 我试着用Python中的LPC来估计元音共振峰 方法是修改下面的matlab代码 这是我目前的代码 usr bin env python import sys imp
  • vs+opencv环境配置出现程序无法启动及提示无法打开opencv_world400d.lib问题的解决方法

    vs opencv 1配置完成但是频频出错 1问题描述 无法启动程序 系统找不到指定的文件 我的原图忘记保存 故找了一张类似问题的图片做代替描述问题 在出现上述问题之后 点击确定后 程序报错 错误如下图 2解决方法 我已按照网上教程配置环境
  • springboot多数据源配置并解决多数据源下出现Cannot determine embedded database driver class for database type NONE的问题

    被多数据源折腾晕了 为了让自己记住写下这篇博客 第一步 配置 application properties server port 8081 server session timeout 1000000 server context pat
  • Web前端——用CSS的常用样式制作一个炫酷的按钮

    文章目录 笔记 CSS的常用样式 炫酷按钮效果实现 笔记 CSS的常用样式 边框以及弧度样式 border width 边框的线条宽度 border style 边框的样式 例如 solid实现 dotted 点线 dashed 虚线 bo
  • @Async 异步调用

    package com example demo controller import com example demo service AsyncService import org springframework http Respons
  • c:\Windows\System32\drivers\etc\hosts

    c Windows System32 drivers etc hosts 是域名解析文件 可以直接用记事本打开 将IP地址重定向 格式为 ip地址 空格 域名 可以将一个域名重新定向到一个IP Hosts文件配置的映射是静态的 如果网络上的
  • NVIDIA GPU驱动和CUDA工具包 Linux CentOS 7 在线安装指南

    挑选指定系统和对应的GPU型号下载驱动和CUDA工具包 Linux CentOS安装NVIDIA GPU驱动程序和NVIDIA CUDA工具包 centos安装显卡驱动和cuda Entropy Go的博客 CSDN博客 相比之下 本文是在
  • cron表达式插件 qnn-react-cron

    eslint disable react no unstable nested components import React from react import Cron from qnn react cron import Button
  • 渗透测试流程——渗透测试的9个步骤(转)

    渗透测试的流程 1 明确目标 2 分析风险 获得授权 3 信息收集 4 漏洞探测 手动 自动 5 漏洞验证 6 信息分析 7 利用漏洞 获取数据 8 信息整理 9 形成报告 1 明确目标 1 确定范围 测试的范围 如 IP 域名 内外网 整
  • 绕过图片格式限制上传木马获取WebShell

    思路 图片上传功能中 前端页面上传的是 png格式的图片文件 但是抓包Request中修改图片后缀为 php 可以绕过对上传文件格式的限制 文件的上传路径可以在上传后的页面或查看上传成功后的response中有显示 记录下来后用菜刀连接即可
  • ApplicationContext.log 将servlet[XXX]标记为不可用 问题已解决!!!

    期间我清楚Maven重新构建项目 手动在 lib 文件中添加 servlet api 等相关jar包无果 最终我尝试着把Tomcat从 10 0 6 版本降低到 9 0 46 版本 更改环境变量重新运行 竟然好了 也许是 Tomcat 版本
  • mac下通过终端安装python3问题记录及解决

    安装python时 首先想到的是升级电脑里的python版本 可是上网搜索后发现升级可能会使用一些基于python的应用运行有问题 所以就尝试安装两个版本 一 查看当前的python版本 打开终端 输入命令python V 二 安装Xcod
  • C语言——可执行程序过程

    我们在编写代码的时候 不知道大家是否和一开始的我一样 在运行代码的时候就直接CTRL F5运行了呢 一开始 我只知道会生成一个 exe的可执行文件 中间的原理我一点也不知道 今天就由我带领大家对生成可执行的文件有更深的一层理解 程序的翻译环
  • 安装Ubuntu系统详细教程

    一 前言 本篇文章详解介绍一下如何安装Ubuntu系统 笔者在安装的过程中踩过很多坑 重装了很多次 现在把安装过程中遇到的问题也列出来 供大家参考 二 准备工作 这个环节很重要 工欲善其事 必先利其器 2 1 固态硬盘 如果是做系统源码开发
  • Android GB28181设备接入端语音广播和语音对讲技术实现探究

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