Wie ruft die Android-Seite den RTSP/RTMP-Stream ab, ruft die YUV/RGB-Daten zurück und fügt dann den einfachen RTSP-Dienst ein?

technischer Hintergrund

Als wir Audio- und Videomodule für die Android-Plattform entwickelten, stießen wir auf ein solches Problem: Die Hersteller wollten die RTSP-Streams von Hikvision, Dahua und anderen Kameras abrufen und ihnen dann die dekodierten YUV- oder RGB-Daten zurücksenden, und das taten sie auch Videos erstellen. Nach der Analyse oder Verarbeitung werden sie dann an das leichtgewichtige RTSP-Dienstmodul oder RTMP-Push-Modul übermittelt, um die verarbeiteten Daten und die sekundäre Weiterleitung zu implementieren. In diesem Artikel wird der RTSP-Stream abgerufen, analysiert und dann der leichtgewichtige RTSP-Dienst als eingefügt Beispiel zur Einführung. Ungefähre technische Umsetzung.

Technische Umsetzung

Ohne weitere Umschweife gibt es keine Wahrheit ohne Bilder. Das Bild unten ist während des Tests. Das Android-Terminal ruft den RTSP-Stream ab, ruft dann die YUV-Daten zurück und injiziert sie über die Push-Schnittstelle in den leichtgewichtigen RTSP-Dienst und dann in Windows Plattform ruft die leichte RTSP-URL ab, insgesamt, Verzögerung in Millisekunden:

Lassen Sie uns zunächst über das Abrufen des RTSP-Streams sprechen. Beachten Sie, dass Sie den zweiten Parameter bei SetSurface () auf Null setzen können, wenn Sie ihn nicht abspielen möchten. Wenn Sie kein Audio benötigen, setzen Sie SetMute einfach auf 1 , weil Sie YUV zurückrufen müssen. Stellen Sie dann den I420-Rückruf ein. Wenn Sie RGB benötigen, aktivieren Sie einfach den RGB-Rückruf.

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

		// 如果第二个参数设置为null,则播放纯音频
		libPlayer.SmartPlayerSetSurface(playerHandle, sSurfaceView);

		libPlayer.SmartPlayerSetRenderScaleMode(playerHandle, 1);

    // libPlayer.SmartPlayerSetExternalRender(playerHandle, new
		// RGBAExternalRender());
		 libPlayer.SmartPlayerSetExternalRender(playerHandle, new
		 I420ExternalRender());

		libPlayer.SmartPlayerSetFastStartup(playerHandle, isFastStartup ? 1 : 0);

		libPlayer.SmartPlayerSetAudioOutputType(playerHandle, 1);

		if (isMute) {
			libPlayer.SmartPlayerSetMute(playerHandle, isMute ? 1
					: 0);
		}

		if (isHardwareDecoder)
		{
			int isSupportH264HwDecoder = libPlayer
					.SetSmartPlayerVideoHWDecoder(playerHandle, 1);

			int isSupportHevcHwDecoder = libPlayer.SetSmartPlayerVideoHevcHWDecoder(playerHandle, 1);

			Log.i(TAG, "isSupportH264HwDecoder: " + isSupportH264HwDecoder + ", isSupportHevcHwDecoder: " + isSupportHevcHwDecoder);
		}

		libPlayer.SmartPlayerSetLowLatencyMode(playerHandle, isLowLatency ? 1
				: 0);

		libPlayer.SmartPlayerSetRotation(playerHandle, rotate_degrees);

		int iPlaybackRet = libPlayer
				.SmartPlayerStartPlay(playerHandle);

		if (iPlaybackRet != 0) {
			Log.e(TAG, "StartPlay failed!");

			if ( !isPulling && !isRecording && !isPushing && !isRTSPPublisherRunning)
			{
				releasePlayerHandle();
			}

			return false;
		}

		isPlaying = true;
		return true;
	}

Die entsprechende Implementierung von OpenPullHandle() lautet wie folgt:

/*
   * SmartRelayDemo.java
   * Created: daniusdk.com
   */
  private boolean OpenPullHandle()
	{
		//if (playerHandle != 0) {
		//	return true;
		//}

		if(isPulling || isPlaying || isRecording)
			return true;

		//playbackUrl = "rtsp://xxxx";
    
		if (playbackUrl == null) {
			Log.e(TAG, "playback URL is null...");
			return false;
		}

		playerHandle = libPlayer.SmartPlayerOpen(myContext);

		if (playerHandle == 0) {
			Log.e(TAG, "playerHandle is nil..");
			return false;
		}

		libPlayer.SetSmartPlayerEventCallbackV2(playerHandle,
				new EventHandlePlayerV2());

		libPlayer.SmartPlayerSetBuffer(playerHandle, playBuffer);

		// set report download speed
		libPlayer.SmartPlayerSetReportDownloadSpeed(playerHandle, 1, 5);

		//设置RTSP超时时间
		int rtsp_timeout = 12;
		libPlayer.SmartPlayerSetRTSPTimeout(playerHandle, rtsp_timeout);

		//设置RTSP TCP/UDP模式自动切换
		int is_auto_switch_tcp_udp = 1;
		libPlayer.SmartPlayerSetRTSPAutoSwitchTcpUdp(playerHandle, is_auto_switch_tcp_udp);

		libPlayer.SmartPlayerSaveImageFlag(playerHandle, 1);

		// It only used when playback RTSP stream..
		//libPlayer.SmartPlayerSetRTSPTcpMode(playerHandle, 1);

		libPlayer.SmartPlayerSetUrl(playerHandle, playbackUrl);

		return true;
	}

Der Ereignisrückrufstatus der Streaming-Seite lautet wie folgt. Die Streaming-Seite konzentriert sich hauptsächlich auf den Linkstatus und die Echtzeit-Download-Geschwindigkeit:

class EventHandlePlayerV2 implements NTSmartEventCallbackV2 {
		@Override
		public void onNTSmartEventCallbackV2(long handle, int id, long param1,
											 long param2, String param3, String param4, Object param5) {

			//Log.i(TAG, "EventHandleV2: handle=" + handle + " id:" + id);

			String player_event = "";

			switch (id) {
				case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STARTED:
					player_event = "开始..";
					break;
				case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTING:
					player_event = "连接中..";
					break;
				case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTION_FAILED:
					player_event = "连接失败..";
					break;
				case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTED:
					player_event = "连接成功..";
					break;
				case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_DISCONNECTED:
					player_event = "连接断开..";
					break;
				case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STOP:
					player_event = "停止播放..";
					break;
				case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_RESOLUTION_INFO:
					player_event = "分辨率信息: width: " + param1 + ", height: " + param2;
					break;
				case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_NO_MEDIADATA_RECEIVED:
					player_event = "收不到媒体数据,可能是url错误..";
					break;
				case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_SWITCH_URL:
					player_event = "切换播放URL..";
					break;
				case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_CAPTURE_IMAGE:
					player_event = "快照: " + param1 + " 路径:" + param3;

					if (param1 == 0) {
						player_event = player_event + ", 截取快照成功";
					} else {
						player_event = player_event + ", 截取快照失败";
					}
					break;

				case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_RECORDER_START_NEW_FILE:
					player_event = "[record]开始一个新的录像文件 : " + param3;
					break;
				case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_ONE_RECORDER_FILE_FINISHED:
					player_event = "[record]已生成一个录像文件 : " + param3;
					break;

				case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_START_BUFFERING:
					Log.i(TAG, "Start Buffering");
					break;

				case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_BUFFERING:
					Log.i(TAG, "Buffering:" + param1 + "%");
					break;

				case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_STOP_BUFFERING:
					Log.i(TAG, "Stop Buffering");
					break;

				case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_DOWNLOAD_SPEED:
					player_event = "download_speed:" + param1 + "Byte/s" + ", "
							+ (param1 * 8 / 1000) + "kbps" + ", " + (param1 / 1024)
							+ "KB/s";
					break;

				case NTSmartEventID.EVENT_DANIULIVE_ERC_PLAYER_RTSP_STATUS_CODE:
					Log.e(TAG, "RTSP error code received, please make sure username/password is correct, error code:" + param1);
					player_event = "RTSP error code:" + param1;
					break;
			}
		}
	}

Der nächste Schritt besteht darin, den RTSP-Dienst zu starten:

//启动/停止RTSP服务
	class ButtonRtspServiceListener implements OnClickListener {
		public void onClick(View v) {
			if (isRTSPServiceRunning) {
				stopRtspService();

				btnRtspService.setText("启动RTSP服务");
				btnRtspPublisher.setEnabled(false);

				isRTSPServiceRunning = false;
				return;
			}

			if(!OpenPushHandle())
			{
				return;
			}

			Log.i(TAG, "onClick start rtsp service..");

			rtsp_handle_ = libPublisher.OpenRtspServer(0);

			if (rtsp_handle_ == 0) {
				Log.e(TAG, "创建rtsp server实例失败! 请检查SDK有效性");
			} else {
				int port = 8554;
				if (libPublisher.SetRtspServerPort(rtsp_handle_, port) != 0) {
					libPublisher.CloseRtspServer(rtsp_handle_);
					rtsp_handle_ = 0;
					Log.e(TAG, "创建rtsp server端口失败! 请检查端口是否重复或者端口不在范围内!");
				}

				//String user_name = "admin";
				//String password = "12345";
				//libPublisher.SetRtspServerUserNamePassword(rtsp_handle_, user_name, password);

				if (libPublisher.StartRtspServer(rtsp_handle_, 0) == 0) {
					Log.i(TAG, "启动rtsp server 成功!");
				} else {
					libPublisher.CloseRtspServer(rtsp_handle_);
					rtsp_handle_ = 0;
					Log.e(TAG, "启动rtsp server失败! 请检查设置的端口是否被占用!");
				}

				btnRtspService.setText("停止RTSP服务");
				btnRtspPublisher.setEnabled(true);

				isRTSPServiceRunning = true;
			}
		}
	}

Wenn Sie den Dienst stoppen müssen, lautet die entsprechende Implementierung wie folgt:

//停止RTSP服务
	private void stopRtspService() {
		if(!isRTSPServiceRunning)
			return;

		if (libPublisher != null && rtsp_handle_ != 0) {
			libPublisher.StopRtspServer(rtsp_handle_);
			libPublisher.CloseRtspServer(rtsp_handle_);
			rtsp_handle_ = 0;
		}

		if(!isPushing)
		{
			releasePublisherHandle();
		}

		isRTSPServiceRunning = false;
	}

Veröffentlichen und stoppen Sie die Veröffentlichung von RTSP-Streams:

private boolean StartRtspStream()
	{
		if (isRTSPPublisherRunning)
			return false;

		String rtsp_stream_name = "stream1";
		libPublisher.SetRtspStreamName(publisherHandle, rtsp_stream_name);
		libPublisher.ClearRtspStreamServer(publisherHandle);

		libPublisher.AddRtspStreamServer(publisherHandle, rtsp_handle_, 0);

		if (libPublisher.StartRtspStream(publisherHandle, 0) != 0)
		{
			Log.e(TAG, "调用发布rtsp流接口失败!");

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

			return false;
		}

		isRTSPPublisherRunning = true;
		return true;
	}

	//停止发布RTSP流
	private void stopRtspPublisher()
	{
		if(!isRTSPPublisherRunning)
			return;

		isRTSPPublisherRunning = false;

		if (null == libPublisher || 0 == publisherHandle)
			return;

		libPublisher.StopRtspStream(publisherHandle);

		if (!isPushing && !isRTSPServiceRunning)
		{
			releasePublisherHandle();
		}
	}

Da die YUV- oder RGB-Daten nach der Verarbeitung neu codiert werden müssen, muss zu diesem Zeitpunkt das Push-End festgelegt werden, um die Codierungsparameter festzulegen:

private boolean OpenPushHandle() {

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

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

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

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

		int fps = 20;
		int gop = fps * 1;

		int videoEncodeType = 1;	//1: h.264硬编码 2: H.265硬编码

		if(videoEncodeType == 1)  {
			int h264HWKbps = setHardwareEncoderKbps(true, videoWidth, videoHeight);
			h264HWKbps = h264HWKbps*fps/25;

			Log.i(TAG, "h264HWKbps: " + h264HWKbps);

			int isSupportH264HWEncoder = libPublisher
					.SetSmartPublisherVideoHWEncoder(publisherHandle, h264HWKbps);

			if (isSupportH264HWEncoder == 0) {
				libPublisher.SetNativeMediaNDK(publisherHandle, 0);
				libPublisher.SetVideoHWEncoderBitrateMode(publisherHandle, 1); // 0:CQ, 1:VBR, 2:CBR
				libPublisher.SetVideoHWEncoderQuality(publisherHandle, 39);
				libPublisher.SetAVCHWEncoderProfile(publisherHandle, 0x08); // 0x01: Baseline, 0x02: Main, 0x08: High

				// libPublisher.SetAVCHWEncoderLevel(publisherHandle, 0x200); // Level 3.1
				// libPublisher.SetAVCHWEncoderLevel(publisherHandle, 0x400); // Level 3.2
				// libPublisher.SetAVCHWEncoderLevel(publisherHandle, 0x800); // Level 4
				libPublisher.SetAVCHWEncoderLevel(publisherHandle, 0x1000); // Level 4.1 多数情况下,这个够用了
				//libPublisher.SetAVCHWEncoderLevel(publisherHandle, 0x2000); // Level 4.2

				// libPublisher.SetVideoHWEncoderMaxBitrate(publisherHandle, ((long)h264HWKbps)*1300);

				Log.i(TAG, "Great, it supports h.264 hardware encoder!");
			}
		}
		else if (videoEncodeType == 2) {
			int hevcHWKbps = setHardwareEncoderKbps(false, videoWidth, videoHeight);
			hevcHWKbps = hevcHWKbps*fps/25;

			Log.i(TAG, "hevcHWKbps: " + hevcHWKbps);

			int isSupportHevcHWEncoder = libPublisher
					.SetSmartPublisherVideoHevcHWEncoder(publisherHandle, hevcHWKbps);

			if (isSupportHevcHWEncoder == 0) {
				libPublisher.SetNativeMediaNDK(publisherHandle, 0);
				libPublisher.SetVideoHWEncoderBitrateMode(publisherHandle, 0); // 0:CQ, 1:VBR, 2:CBR
				libPublisher.SetVideoHWEncoderQuality(publisherHandle, 39);

				// libPublisher.SetVideoHWEncoderMaxBitrate(publisherHandle, ((long)hevcHWKbps)*1200);

				Log.i(TAG, "Great, it supports hevc hardware encoder!");
			}
		}

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

		libPublisher.SmartPublisherSetGopInterval(publisherHandle, gop);

		libPublisher.SmartPublisherSetFPS(publisherHandle, fps);

		return true;
	}

Die Implementierung von I420ExternalRender ist wie folgt: Hier können Sie die YUV-Daten des RTSP-Streams abrufen und nach der Verarbeitung PostLayerImageI420ByteBuffer() des Push-Endes aufrufen, um es an den leichtgewichtigen RTSP-Dienst zu liefern, oder das RTMP-Push-End, um es zu codieren und zu verschlüsseln schicke es ab.

class I420ExternalRender implements NTExternalRender {
		// public static final int NT_FRAME_FORMAT_RGBA = 1;
		// public static final int NT_FRAME_FORMAT_ABGR = 2;
		// public static final int NT_FRAME_FORMAT_I420 = 3;

		private int width_ = 0;
		private int height_ = 0;

		private int y_row_bytes_ = 0;
		private int u_row_bytes_ = 0;
		private int v_row_bytes_ = 0;

		private ByteBuffer y_buffer_ = null;
		private ByteBuffer u_buffer_ = null;
		private ByteBuffer v_buffer_ = null;

		@Override
		public int getNTFrameFormat() {
			Log.i(TAG, "I420ExternalRender::getNTFrameFormat return "
					+ NT_FRAME_FORMAT_I420);
			return NT_FRAME_FORMAT_I420;
		}

		@Override
		public void onNTFrameSizeChanged(int width, int height) {
			width_ = width;
			height_ = height;

			y_row_bytes_ = (width_ + 15) & (~15);
			u_row_bytes_ = ((width_ + 1) / 2 + 15) & (~15);
			v_row_bytes_ = ((width_ + 1) / 2 + 15) & (~15);

			y_buffer_ = ByteBuffer.allocateDirect(y_row_bytes_ * height_);
			u_buffer_ = ByteBuffer.allocateDirect(u_row_bytes_
					* ((height_ + 1) / 2));
			v_buffer_ = ByteBuffer.allocateDirect(v_row_bytes_
					* ((height_ + 1) / 2));

			Log.i(TAG, "I420ExternalRender::onNTFrameSizeChanged width_="
					+ width_ + " height_=" + height_ + " y_row_bytes_="
					+ y_row_bytes_ + " u_row_bytes_=" + u_row_bytes_
					+ " v_row_bytes_=" + v_row_bytes_);
		}

		@Override
		public ByteBuffer getNTPlaneByteBuffer(int index) {
			if (index == 0) {
				return y_buffer_;
			} else if (index == 1) {
				return u_buffer_;
			} else if (index == 2) {
				return v_buffer_;
			} else {
				Log.e(TAG, "I420ExternalRender::getNTPlaneByteBuffer index error:" + index);
				return null;
			}
		}

		@Override
		public int getNTPlanePerRowBytes(int index) {
			if (index == 0) {
				return y_row_bytes_;
			} else if (index == 1) {
				return u_row_bytes_;
			} else if (index == 2) {
				return v_row_bytes_;
			} else {
				Log.e(TAG, "I420ExternalRender::getNTPlanePerRowBytes index error:" + index);
				return 0;
			}
		}

    	public void onNTRenderFrame(int width, int height, long timestamp)
    	{
    		if ( y_buffer_ == null )
    			return;
    		
    		if ( u_buffer_ == null )
    			return;
    		
    		if ( v_buffer_ == null )
    			return;
    		      
    		y_buffer_.rewind();
    		u_buffer_.rewind();
    		v_buffer_.rewind();
    		
    		 if( isPushing || isRTSPPublisherRunning )
         {
            libPublisher.PostLayerImageI420ByteBuffer(publisherHandle, 0, 0, 0,
                y_buffer_, 0, y_row_bytes_,
                u_buffer_, 0, u_row_bytes_,
                v_buffer_, 0, v_row_bytes_,
                width_, height_, 0, 0,
                960, 540, 0,0);
         }
    	}
    }

Wenn der Lightweight-Dienst normal startet, wird die URL von rtsp zurückgerufen:

class EventHandlePublisherV2 implements NTSmartEventCallbackV2 {
   @Override
   public void onNTSmartEventCallbackV2(long handle, int id, long param1, long param2, String param3, String param4, Object param5) {

      Log.i(TAG, "EventHandlePublisherV2: handle=" + handle + " id:" + id);

      String publisher_event = "";

      switch (id) {
         ....
         case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_RTSP_URL:
            publisher_event = "RTSP服务URL: " + param3;
            break;
      }
   }
}

Technische Zusammenfassung

Das Obige ist ein grober Prozess. Nachdem der Stream von RTSP zur Datenverarbeitung gezogen wurde, wird er dann an den Lightweight-RTSP-Dienst gesendet, und dann ruft der Player den Stream vom Lightweight-RTSP-Server ab. Wenn die Verarbeitung für den YUV- oder RGB-Algorithmus verzögert ist ist nicht groß. Die Gesamtlatenz kann leicht das Millisekunden-Niveau erreichen und erfüllt die technischen Anforderungen der meisten Szenarien.

Acho que você gosta

Origin blog.csdn.net/renhui1112/article/details/133135936
Recomendado
Clasificación