Android国标接入端如何播放GB28181平台端语音广播数据

2022-10-07 23:12:30 浏览数 (3)

GB28181语音广播这块,我们依据GB/T28181-2016针对流程和实例代码,做过详细的描述,本次主要是探讨下,广播数据过来后,如何处理。

鉴于我们之前有非常成熟的RTMP|RTSP低延迟播放模块,语音广播数据过来后,调用startAudioPlay(),ntsOnInviteAudioBroadcastResponse()处理如下:

代码语言:javascript复制
@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输出:

代码语言:javascript复制
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端,做回音消除处理:

代码语言:javascript复制
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());
	}
}

如果长时间收不到数据,主动断掉音频广播:

代码语言:javascript复制
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_;
}

停止广播数据播放:

代码语言:javascript复制
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:

代码语言:javascript复制
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接入模块的测试。

0 人点赞