GB28181语音广播这块,我们依据GB/T28181-2016针对流程和实例代码,做过详细的描述,本次主要是探讨下,广播数据过来后,如何处理。
鉴于我们之前有非常成熟的RTMP|RTSP低延迟播放模块,语音广播数据过来后,调用startAudioPlay(),ntsOnInviteAudioBroadcastResponse()处理如下:
@Override public void ntsOnInviteAudioBroadcastResponse(String sourceID, String targetID, int statusCode, PlaySessionDescription sessionDescription) { handler_.postDelayed(new Runnable() { @Override public void run() { Log.i(TAG, "ntsOnInviteAudioBroadcastResponse, statusCode:" + status_code_ +" sourceID:" + source_id_ + ", targetID:" + target_id_); boolean is_need_destory_rtp = true; if (gb28181_agent_ != null ) { boolean is_need_bye = 200==status_code_; if (200 == status_code_ && session_description_ != null && rtp_receiver_handle_ != 0 ) { MediaSessionDescription audio_des = 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()); lib_player_.SetRTPReceiverPayloadType(rtp_receiver_handle_, audio_attr.getPayloadType(), audio_attr.getEncodingName(), 2, audio_attr.getClockRate()); // 如果是PCMA, SDK会默认填 采样率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_broadcast_source_id_ = source_id_; gb_broadcast_target_id_ = target_id_; btnGB28181AudioBroadcast.setText("终止GB28181语音广播"); btnGB28181AudioBroadcast.setEnabled(true); } } } else { btnGB28181AudioBroadcast.setText("GB28181语音广播"); } if (is_need_bye) gb28181_agent_.byeAudioBroadcast(source_id_, target_id_); } if (is_need_destory_rtp) destoryRTPReceiver(); } private String source_id_; private String target_id_; private int status_code_; private 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); }
startAudioPlay()初始化实例后,为了保证低延迟,拉流端设置0 buffer,处于调试方便,设置download speed回调2-5秒一次(可以看到是不是有音频数据过来),由于只需要播放音频,不需要视频,所以不要设置surface下去,然后设置拉流数据回调,需要注意的是,拉到的audio数据,不要转aac输出:
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_.SmartPlayerSetFastStartup(player_handle_, 0); // set report download speed(默认2秒一次回调 用户可自行调整report间隔) lib_player_.SmartPlayerSetReportDownloadSpeed(player_handle_, 1, 20); 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://ntinternal/rtpreceiver/implemention0"); 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()); lib_player_.SmartPlayerSetPullStreamAudioTranscodeAAC(player_handle_, 0); if (0 ==lib_player_.SmartPlayerStartPullStream(player_handle_) ) { // 启动定时器,长时间收不到音频数据,则停止播放,发送BYE last_received_audio_data_time_.set(SystemClock.elapsedRealtime()); handler_.postDelayed(new AudioPlayerPCMTimer(player_handle_), AudioPlayerPCMTimer.INTERVAL_MS); } return true; }
调用StartPlay后,拿到的audio数据,塞到publisher端,做回音消除处理:
class PlayerExternalPCMOutput implements NTExternalAudioOutput { private int buffer_size_ = 0; private ByteBuffer pcm_buffer_ = null; @Override public ByteBuffer getPcmByteBuffer(int size) { //Log.i("getPcmByteBuffer", "size: " + 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) { /*Log.i("onGetPcmFrame", "ret: " + ret + ", sampleRate: " + sampleRate + ", channel: " + channel + ", sampleSize: " + sampleSize + ",is_low_latency:" + is_low_latency + " buffer_size:" + buffer_size);*/ 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); } } private static int align(int d, int a) { return (d + (a - 1)) & ~(a - 1); } 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) { //Log.i("getAudioByteBuffer", "size: " + 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_); // Log.i("getAudioByteBuffer", "size: " + size + " buffer_size:" + audio_buffer_size); return buffer_; } @Override public ByteBuffer getAudioParameterInfo(int size) { //Log.i("getAudioParameterInfo", "size: " + 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_); //Log.i("getAudioParameterInfo", "size: " + size + " buffer_size:" + 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) { /*Log.i("onAudioDataCallback", "ret: " + ret + ", audio_codec_id: " + audio_codec_id + ", sample_size: " + sample_size + ", timestamp: " + timestamp + ",sample_rate:" + sample_rate); */ last_received_audio_data_time_.set(SystemClock.elapsedRealtime()); } }
如果长时间收不到数据,主动断掉音频广播:
class AudioPlayerPCMTimer implements Runnable { public static final int THRESHOLD_MS = 60*1000; // 暂时设置到1分钟 public static final int INTERVAL_MS = 10*1000; // 十秒一次, 太频繁影响主线程 public AudioPlayerPCMTimer(long handle) { handle_ = handle; } @Override public void run() { if (0 == handle_) return; if (handle_ != player_handle_) { Log.i(TAG, "AudioPlayerPCMTimer handle changed, will stop this timer, handle:" + handle_ + " new handle:" + player_handle_); return; } long last_update_time = last_received_audio_data_time_.get(); long cur_time = SystemClock.elapsedRealtime(); // Log.i(TAG, "AudioPlayerPCMTimer last_update_time:" + last_update_time + " cur_time:" + cur_time); if ( (last_update_time + this.THRESHOLD_MS) > cur_time) { // 继续定时器 handler_.postDelayed(new AudioPlayerPCMTimer(this.handle_), this.INTERVAL_MS); // Log.i(TAG, "AudioPlayerPCMTimer running."); } else { Log.i(TAG, "AudioPlayerPCMTimer,trigger threshold, bye audio, stop player."); if (gb_broadcast_source_id_ != null && gb_broadcast_target_id_ != null) { if (gb28181_agent_ != null) gb28181_agent_.byeAudioBroadcast(gb_broadcast_source_id_, gb_broadcast_target_id_); } gb_broadcast_source_id_ = null; gb_broadcast_target_id_ = null; stopAudioPlayer(); destoryRTPReceiver(); btnGB28181AudioBroadcast.setText("GB28181语音广播"); btnGB28181AudioBroadcast.setEnabled(false); } } private long handle_; }
停止广播数据播放:
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; } }
销毁RTPReceiver:
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; } }
以上是针对GB28181平台端音频广播播放的一点说明,感兴趣的开发者,可以酌情参考,也可以和我探讨Android平台GB28181接入模块的测试。