Android平台RTMP推送端实现外部数据对接推送和录像

244 阅读3分钟

背景

好多开发者在做Android平台RTMP推送对接的同时,除了编码前的数据外,还有些外部编码数据推送诉求,他们希望外部的编码音视频数据不止可以实现RTMP推送,还可以同时在推送端实时录制下来,本文以我们(官方)Android平台RTMP直播推送模块为例,介绍下外部数据对接流程和数据录制流程。

对接流程

开始推送

	private boolean StartPush()
	{
		if (isPushing)
			return false;

		//relayStreamUrl = "rtmp://192.168.1.77/hls/stream1";

		if (relayStreamUrl == null) {
			Log.e(TAG, "StartPush URL is null...");
			return false;
		}

		if (!OpenPushHandle())
			return false;

		if ( libPublisher.SmartPublisherSetURL(publisherHandle, relayStreamUrl) != 0 )
		{
			Log.e(TAG, "StartPush failed!");
		}

		int startRet = libPublisher.SmartPublisherStartPublisher(publisherHandle);
		if( startRet != 0)
		{
			Log.e(TAG, "Failed to call StartPublisher!");

			if(isRTSPPublisherRunning)
			{
				libPublisher.SmartPublisherClose(publisherHandle);
				publisherHandle = 0;
			}

			return false;
		}

		isPushing = true;

		return true;
	}

OpenPushHandle()实现

注意,如果对接外部编码后的音视频数据的话,调用SmartPublisherOpen()接口时,记得audio_opt和video_opt均传2。

	private boolean OpenPushHandle()
	{
		if(publisherHandle != 0)
		{
			return true;
		}

		int audio_opt = 2;
		int video_opt = 2;

		int videoWidth = 640;
		int videoHeight  = 480;

		publisherHandle = libPublisher.SmartPublisherOpen(myContext, audio_opt, video_opt,
				videoWidth, videoHeight);

		if (publisherHandle == 0 )
		{
			Log.e(TAG, "OpenPushHandle failed!");
			return false;
		}

		Log.i(TAG, "publisherHandle=" + publisherHandle);

		libPublisher.SetSmartPublisherEventCallbackV2(publisherHandle, new EventHandePublisherV2());

		return true;
	}

停止推送

	public void StopPush()
	{
		if (!isPushing)
			return;

		isPushing = false;

		libPublisher.SmartPublisherStopPublisher(publisherHandle);

		if(!isRTSPPublisherRunning && !isRTSPServiceRunning)
		{
			libPublisher.SmartPublisherClose(publisherHandle);
			publisherHandle = 0;
		}
	}

实时音视频数据投递

如果需要同时录制外部编码后的音视频数据,分别用以下接口完成数据投递:

涉及到的sps、pps或者audio的一些配置信息,上层很容易拿到,传递下去即可。

	/**
	 * 设置编码后视频数据(H.264),如需录制编码后的数据,用此接口,且设置实际宽高
	 *
	 * @param codec_id, H.264对应 1
	 *
	 * @param data 编码后的video数据
	 *
	 *@param offset data的偏移
	 *
	 * @param size data length
	 *
	 * @param is_key_frame 是否I帧, if with key frame, please set 1, otherwise, set 0.
	 *
	 * @param timestamp video timestamp
	 *
	 * @param pts Presentation Time Stamp, 显示时间戳
	 *
	 * @param width, height: 编码后视频宽高
	 *
	 * @return {0} if successful
	 */
	public native int SmartPublisherPostVideoEncodedDataV3(long handle, int codec_id,
														   ByteBuffer data, int offset, int size,
														   int is_key_frame, long timestamp, long pts,
														   byte[] sps, int sps_len,
														   byte[] pps, int pps_len,
														   int width, int height);

    /**
     * 设置音频数据(AAC/PCMA/PCMU/SPEEX)
     *
     * @param codec_id:
     *
     *  NT_MEDIA_CODEC_ID_AUDIO_BASE = 0x10000,
     *	NT_MEDIA_CODEC_ID_PCMA = NT_MEDIA_CODEC_ID_AUDIO_BASE,
     *	NT_MEDIA_CODEC_ID_PCMU,
     *	NT_MEDIA_CODEC_ID_AAC,
     *	NT_MEDIA_CODEC_ID_SPEEX,
     *	NT_MEDIA_CODEC_ID_SPEEX_NB,
     *	NT_MEDIA_CODEC_ID_SPEEX_WB,
     *	NT_MEDIA_CODEC_ID_SPEEX_UWB,
     *
     * @param data audio数据
     *
     * @param offset data的偏移
     *
     * @param size data length
     *
     * @param is_key_frame 是否I帧, if with key frame, please set 1, otherwise, set 0, audio忽略
     *
     * @param timestamp video timestamp
     *
     * @param parameter_info 用于AAC special config信息填充
     *
     * @param parameter_info_size parameter info size
     *
     * @param sample_rate 采样率,如果需要录像的话必须传正确的值
     *
     *@param channels 通道数, 如果需要录像的话必须传正确的值, 一般是1或者2
     *
     * @return {0} if successful
     */
    public native int SmartPublisherPostAudioEncodedDataV3(long handle, int codec_id,
                                                           ByteBuffer data, int offset, int size,
                                                           int is_key_frame, long timestamp,
                                                           byte[] parameter_info, int parameter_info_size,
                                                           int sample_rate, int channels);

开始录像

	private boolean StartRecorder()
	{
		if (!OpenPullHandle())
			return false;

		ConfigRecorderFuntion();

		int iRecRet = libPlayer
				.SmartPlayerStartRecorder(playerHandle);

		if (iRecRet != 0) {
			Log.e(TAG, "StartRecorder failed!");

			if ( !isPulling &&!isPlaying && !isPushing && !isRTSPPublisherRunning)
			{
				libPlayer.SmartPlayerClose(playerHandle);
				playerHandle = 0;
			}

			return false;
		}

		isRecording = true;
		return true;
	}

停止录像

	private void StopRecorder()
	{
		if ( !isRecording )
			return;

		isRecording = false;

		libPlayer.SmartPlayerStopRecorder(playerHandle);

		if ( !isPlaying && !isPulling && !isPushing && !isRTSPPublisherRunning)
		{
			libPlayer.SmartPlayerClose(playerHandle);
			playerHandle = 0;
		}
	}

总结

外部数据对接的话,需要确保传递的音视频数据编码信息正常,相关的时间戳能对得上,从而确保好的用户体验。