Android平台如何实现RTSP转GB28181

为什么要做GB28181设备接入侧?

实际上,在做Android平台GB28181设备接入模块的时候,我们已经有了非常好的技术积累,比如RTMP推送、轻量级RTSP服务、一对一互动模块、业内几乎最好的RTMP|RTSP低延迟播放器。

Android平台GB28181接入SDK(SmartGBD),主要实现不具备国标音视频能力的 Android终端,通过平台注册接入到现有的GB/T28181—2016(包括后续的GB/T28181—2022)服务,可用于如执法记录仪、智能安全帽、智能监控、智慧零售、智慧教育、远程办公、明厨亮灶、智慧交通、智慧工地、雪亮工程、平安乡村、生产运输、车载终端等场景,可能是业内为数不多功能齐全性能优异的商业级水准GB28181接入SDK。

Android终端除支持常规的音视频数据接入外,还可以支持移动设备位置(MobilePosition)订阅和通知、图像抓拍、语音广播和语音对讲、历史视音频下载和回放,支持对接数据类型如下:

  1. 编码前数据(目前支持的有YV12/NV21/NV12/I420/RGB24/RGBA32/RGB565等数据类型),其中,Android平台前后摄像头数据,或者屏幕数据,或者Unity拿到的数据,均属编码前数据;
  2. 编码后数据(如无人机等264/HEVC数据,或者本地解析的MP4音视频数据);
  3. 拉取RTSP或RTMP流并接入至GB28181平台(比如其他IPC的RTSP流,可通过Android平台GB28181接入到国标平台)。

目前,我们支持到的功能如下:

  •  [视频格式]H.264/H.265(Android H.265硬编码);
  •  [音频格式]G.711 A律、AAC;
  •  [音量调节]Android平台采集端支持实时音量调节;
  •  [H.264硬编码]支持H.264特定机型硬编码;
  •  [H.265硬编码]支持H.265特定机型硬编码;
  •  [软硬编码参数配置]支持gop间隔、帧率、bit-rate设置;
  •  [软编码参数配置]支持软编码profile、软编码速度、可变码率设置;
  •  支持横屏、竖屏推流;
  •  Android平台支持后台service推送屏幕(推送屏幕需要5.0+版本);
  • 支持纯视频、音视频PS打包传输;
  • 支持RTP OVER UDP和RTP OVER TCP被动模式(TCP媒体流传输客户端);
  • 支持信令通道网络传输协议TCP/UDP设置;
  • 支持注册、注销,支持注册刷新及注册有效期设置;
  • 支持设备目录查询应答;
  • 支持心跳机制,支持心跳间隔、心跳检测次数设置;
  • 支持移动设备位置(MobilePosition)订阅和通知;
  •  适用国家标准:GB/T 28181—2016;
  • 支持语音广播;
  • 支持语音对讲;
  • 支持图像抓拍;
  • 支持历史视音频文件检索;
  • 支持历史视音频文件下载;
  • 支持历史视音频文件回放;
  • 支持云台控制和预置位查询;
  •  [实时水印]支持动态文字水印、png水印;
  •  [镜像]Android平台支持前置摄像头实时镜像功能;
  •  [实时静音]支持实时静音/取消静音;
  •  [实时快照]支持实时快照;
  •  [降噪]支持环境音、手机干扰等引起的噪音降噪处理、自动增益、VAD检测;
  •  [外部编码前视频数据对接]支持YUV数据对接;
  •  [外部编码前音频数据对接]支持PCM对接;
  •  [外部编码后视频数据对接]支持外部H.264数据对接;
  •  [外部编码后音频数据对接]外部AAC数据对接;
  •  [扩展录像功能]支持和录像SDK组合使用,录像相关功能。

本篇blog,我们主要讲的是如何把RTSP的流,转GB28181投递到国标平台。

技术实现

由于我们已经有非常成熟的RTSP直播播放模块和RTSP转RTMP推送模块,实际上,RTSP转GB28181这块,和转RTMP原理类似,把拉流过来的RTSP音视频数据,回调上来,然后通过推送接口,把数据投递到GB28181模块即可。

废话不多说,上代码,APP启动起来后,启动GB28181即可完成和国标平台侧的注册在线:

	class ButtonGB28181AgentListener implements OnClickListener {
		public void onClick(View v) {
			stopGB28181Stream();
			destoryRTPSender();

			if (null == gb28181_agent_ ) {
				if( !initGB28181Agent() )
					return;
			}

			if (gb28181_agent_.isRunning()) {
				gb28181_agent_.terminateAllPlays(true);// 目前测试下来,发送BYE之后,有些服务器会立即发送INVITE,是否发送BYE根据实际情况看
				gb28181_agent_.stop();
				btnGB28181Agent.setText("启动GB28181");
			}
			else {
				if ( gb28181_agent_.start() ) {
					btnGB28181Agent.setText("停止GB28181");
				}
			}
		}
	}

	//停止GB28181 媒体流
	private void stopGB28181Stream() {
		stream_publisher_.StopGB28181MediaStream();
		stream_publisher_.try_release();
	}

对应InitGB28181Agent()实现:

    /*
     * SmartRtsp2GB28181.java
     * Author: daniusdk.com
     */
    private boolean initGB28181Agent() {
		if ( gb28181_agent_ != null )
			return  true;

		getLocation(context_);

		String local_ip_addr = IPAddrUtils.getIpAddress(context_);
		Log.i(TAG, "initGB28181Agent local ip addr: " + local_ip_addr);

		if ( local_ip_addr == null || local_ip_addr.isEmpty() ) {
			Log.e(TAG, "initGB28181Agent local ip is empty");
			return  false;
		}

		gb28181_agent_ = GBSIPAgentFactory.getInstance().create();
		if ( gb28181_agent_ == null ) {
			Log.e(TAG, "initGB28181Agent create agent failed");
			return false;
		}

		gb28181_agent_.addListener(this);
		gb28181_agent_.addPlayListener(this);
		gb28181_agent_.addDeviceControlListener(this);

		// 必填信息
		gb28181_agent_.setLocalAddress(local_ip_addr);
		gb28181_agent_.setServerParameter(gb28181_sip_server_addr_, gb28181_sip_server_port_, gb28181_sip_server_id_, gb28181_sip_domain_);
		gb28181_agent_.setUserInfo(gb28181_sip_username_, gb28181_sip_password_);
		//gb28181_agent_.setUserInfo(gb28181_sip_username_, gb28181_sip_username_, gb28181_sip_password_);

		// 可选参数
		gb28181_agent_.setUserAgent(gb28181_sip_user_agent_filed_);
		gb28181_agent_.setTransportProtocol(gb28181_sip_trans_protocol_==0?"UDP":"TCP");

		// GB28181配置
		gb28181_agent_.config(gb28181_reg_expired_, gb28181_heartbeat_interval_, gb28181_heartbeat_count_);

		com.gb.ntsignalling.Device gb_device = new com.gb.ntsignalling.Device("34020000001380000001", "安卓测试设备", Build.MANUFACTURER, Build.MODEL,
				"宇宙","火星1","火星", true);

		if (mLongitude != null && mLatitude != null) {
			com.gb.ntsignalling.DevicePosition device_pos = new com.gb.ntsignalling.DevicePosition();

			device_pos.setTime(mLocationTime);
			device_pos.setLongitude(mLongitude);
			device_pos.setLatitude(mLatitude);
			gb_device.setPosition(device_pos);

			gb_device.setSupportMobilePosition(true); // 设置支持移动位置上报
		}

		gb28181_agent_.addDevice(gb_device);

/*
        com.gb28181.ntsignalling.Device gb_device1 = new com.gb28181.ntsignalling.Device("34020000001380000002", "安卓测试设备2", Build.MANUFACTURER, Build.MODEL,
                "宇宙","火星1","火星", true);

        if (mLongitude != null && mLatitude != null) {
            com.gb28181.ntsignalling.DevicePosition device_pos = new com.gb28181.ntsignalling.DevicePosition();

            device_pos.setTime(mLocationTime);
            device_pos.setLongitude(mLongitude);
            device_pos.setLatitude(mLatitude);
            gb_device1.setPosition(device_pos);

            gb_device1.setSupportMobilePosition(true);
        }

        gb28181_agent_.addDevice(gb_device1);


 */

		if (!gb28181_agent_.createSipStack()) {
			gb28181_agent_ = null;
			Log.e(TAG, "initGB28181Agent gb28181_agent_.createSipStack failed.");
			return  false;
		}

		boolean is_bind_local_port_ok = false;

		// 最多尝试5000个端口
		int try_end_port = gb28181_sip_local_port_base_ + 5000;
		try_end_port = try_end_port > 65536 ?65536: try_end_port;

		for (int i = gb28181_sip_local_port_base_; i < try_end_port; ++i) {
			if (gb28181_agent_.bindLocalPort(i)) {
				is_bind_local_port_ok = true;
				break;
			}
		}

		if (!is_bind_local_port_ok) {
			gb28181_agent_.releaseSipStack();
			gb28181_agent_ = null;
			Log.e(TAG, "initGB28181Agent gb28181_agent_.bindLocalPort failed.");
			return  false;
		}

		if (!gb28181_agent_.initialize()) {
			gb28181_agent_.unBindLocalPort();
			gb28181_agent_.releaseSipStack();
			gb28181_agent_ = null;
			Log.e(TAG, "initGB28181Agent gb28181_agent_.initialize failed.");
			return  false;
		}

		return true;
	}

 注册后,会有以下回调:

	@Override
	public void ntsRegisterOK(String dateString) {
		Log.i(TAG, "ntsRegisterOK Date: " + (dateString!= null? dateString : ""));
	}

	@Override
	public void ntsRegisterTimeout() {
		Log.e(TAG, "ntsRegisterTimeout");
	}

	@Override
	public void ntsRegisterTransportError(String errorInfo) {
		Log.e(TAG, "ntsRegisterTransportError error:" + (errorInfo != null?errorInfo :""));
	}

如果国标平台侧有实时查看请求,先发invite过来:

	@Override
	public void ntsOnInvitePlay(String deviceId, SessionDescription session_des) {
		handler_.postDelayed(new Runnable() {
			@Override
			public void run() {
				// 先振铃响应下
				gb28181_agent_.respondPlayInvite(180, device_id_);

				MediaSessionDescription video_des = null;
				SDPRtpMapAttribute ps_rtpmap_attr = null;

				// 28181 视频使用PS打包
				Vector<MediaSessionDescription> video_des_list = session_des_.getVideoPSDescriptions();
				if (video_des_list != null && !video_des_list.isEmpty()) {
					for(MediaSessionDescription m : video_des_list) {
						if (m != null && m.isValidAddressType() && m.isHasAddress() ) {
							video_des = m;
							ps_rtpmap_attr = video_des.getPSRtpMapAttribute();
							break;
						}
					}
				}

				if (null == video_des) {
					gb28181_agent_.respondPlayInvite(488, device_id_);
					Log.i(TAG, "ntsOnInvitePlay get video description is null, response 488, device_id:" + device_id_);
					return;
				}

				if (null == ps_rtpmap_attr) {
					gb28181_agent_.respondPlayInvite(488, device_id_);
					Log.i(TAG, "ntsOnInvitePlay get ps rtp map attribute is null, response 488, device_id:" + device_id_);
					return;
				}

				Log.i(TAG,"ntsOnInvitePlay, device_id:" +device_id_+", is_tcp:" + video_des.isRTPOverTCP()
						+ " rtp_port:" + video_des.getPort() + " ssrc:" + video_des.getSSRC()
						+ " address_type:" + video_des.getAddressType() + " address:" + video_des.getAddress());

				long rtp_sender_handle = libPublisher.CreateRTPSender(0);
				if ( rtp_sender_handle == 0 ) {
					gb28181_agent_.respondPlayInvite(488, device_id_);
					Log.i(TAG, "ntsOnInvitePlay CreateRTPSender failed, response 488, device_id:" + device_id_);
					return;
				}

				gb28181_rtp_payload_type_  = ps_rtpmap_attr.getPayloadType();
				gb28181_rtp_encoding_name_ =  ps_rtpmap_attr.getEncodingName();

				libPublisher.SetRTPSenderTransportProtocol(rtp_sender_handle, video_des.isRTPOverUDP()?0:1);
				libPublisher.SetRTPSenderIPAddressType(rtp_sender_handle, video_des.isIPv4()?0:1);
				libPublisher.SetRTPSenderLocalPort(rtp_sender_handle, 0);
				libPublisher.SetRTPSenderSSRC(rtp_sender_handle, video_des.getSSRC());
				libPublisher.SetRTPSenderSocketSendBuffer(rtp_sender_handle, 2*1024*1024); // 设置到2M
				libPublisher.SetRTPSenderClockRate(rtp_sender_handle, ps_rtpmap_attr.getClockRate());
				libPublisher.SetRTPSenderDestination(rtp_sender_handle, video_des.getAddress(), video_des.getPort());

				if ( libPublisher.InitRTPSender(rtp_sender_handle) != 0 ) {
					gb28181_agent_.respondPlayInvite(488, device_id_);
					libPublisher.DestoryRTPSender(rtp_sender_handle);
					return;
				}

				int local_port = libPublisher.GetRTPSenderLocalPort(rtp_sender_handle);
				if (local_port == 0) {
					gb28181_agent_.respondPlayInvite(488, device_id_);
					libPublisher.DestoryRTPSender(rtp_sender_handle);
					return;
				}

				Log.i(TAG,"get local_port:" + local_port);

				String local_ip_addr = IPAddrUtils.getIpAddress(context_);

				MediaSessionDescription local_video_des = new MediaSessionDescription(video_des.getType());

				local_video_des.addFormat(String.valueOf(ps_rtpmap_attr.getPayloadType()));
				local_video_des.addRtpMapAttribute(ps_rtpmap_attr);

				local_video_des.setAddressType(video_des.getAddressType());
				local_video_des.setAddress(local_ip_addr);
				local_video_des.setPort(local_port);

				local_video_des.setTransportProtocol(video_des.getTransportProtocol());
				local_video_des.setSSRC(video_des.getSSRC());

				if (!gb28181_agent_.respondPlayInviteOK(device_id_,local_video_des) ) {
					libPublisher.DestoryRTPSender(rtp_sender_handle);
					Log.e(TAG, "ntsOnInvitePlay call respondPlayInviteOK failed.");
					return;
				}

				gb28181_rtp_sender_handle_ = rtp_sender_handle;
			}

			private String device_id_;
			private SessionDescription session_des_;

			public Runnable set(String device_id, SessionDescription session_des) {
				this.device_id_ = device_id;
				this.session_des_ = session_des;
				return this;
			}
		}.set(deviceId, session_des),0);
	}

收到平台侧的Ack后,开始投递数据到国标平台侧:

	@Override
	public void ntsOnAckPlay(String deviceId) {
		handler_.postDelayed(new Runnable() {
			@Override
			public void run() {
				Log.i(TAG,"ntsOnACKPlay, device_id:" +device_id_);

				InitAndSetConfig();

				stream_publisher_.SetGB28181RTPSender(gb28181_rtp_sender_handle_, gb28181_rtp_payload_type_, gb28181_rtp_encoding_name_);

				//libPublisher.SetGBTCPConnectTimeout(publisherHandle, 10*60*1000);
				//libPublisher.SetGBInitialTCPReconnectInterval(publisherHandle, 1000);
				//libPublisher.SetGBInitialTCPMaxReconnectAttempts(publisherHandle, 3);

				boolean start_ret  = stream_publisher_.StartGB28181MediaStream();
				if (!start_ret) {
					stream_publisher_.try_release();
					destoryRTPSender();
					Log.e(TAG, "Failed to start GB28181 service..");
					return;
				}
			}

			private String device_id_;

			public Runnable set(String device_id) {
				this.device_id_ = device_id;
				return this;
			}

		}.set(deviceId),0);
	}

国标平台侧停止查看:

	@Override
	public void ntsOnByePlay(String deviceId) {
		handler_.postDelayed(new Runnable() {
			@Override
			public void run() {
				Log.i(TAG, "ntsOnByePlay, stop GB28181 media stream, deviceId=" + device_id_);

				stopGB28181Stream();
				destoryRTPSender();
			}

			private String device_id_;

			public Runnable set(String device_id) {
				this.device_id_ = device_id;
				return this;
			}

		}.set(deviceId),0);
	}

GB28181这块介绍过,再说数据源的问题,由于本次是拉取RTSP流转推GB28181平台,拉取RTSP流的时候,设置音视频数据回调。

    /*
     * SmartRtsp2GB28181.java
     * Author: daniusdk.com
     */
    private boolean StartPull()
	{
		if ( isPulling )
			return false;

		if(!isPlaying)
		{
			if (!OpenPullHandle())
				return false;
		}

		libPlayer.SmartPlayerSetAudioDataCallback(player_handle_, new PlayerAudioDataCallback(stream_publisher_));
		libPlayer.SmartPlayerSetVideoDataCallback(player_handle_, new PlayerVideoDataCallback(stream_publisher_));

		int is_pull_trans_code  = 1;
		libPlayer.SmartPlayerSetPullStreamAudioTranscodeAAC(player_handle_, is_pull_trans_code);

		int startRet = libPlayer.SmartPlayerStartPullStream(player_handle_);

		if (startRet != 0) {
			Log.e(TAG, "Failed to start pull stream!");

			if(!isPlaying)
			{
				releasePlayerHandle();
			}

			return false;
		}

		isPulling = true;
		return true;
	}

对应的OpenPullHandle()实现如下:

	private boolean OpenPullHandle()
	{
		//playbackUrl可自定义
		//playbackUrl = "rtsp://admin:daniulive12345@192.168.0.120:554/h264/ch1/main/av_stream";

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

		player_handle_ = libPlayer.SmartPlayerOpen(context_);

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

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

		libPlayer.SmartPlayerSetBuffer(player_handle_, playBuffer);

		// set report download speed
		libPlayer.SmartPlayerSetReportDownloadSpeed(player_handle_, 1, 3);

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

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

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

		libPlayer.SmartPlayerSetUrl(player_handle_, playbackUrl);

		return true;
	}

这里设置RTSP拉流参数,比如缓冲时间,下载速度实时回调间隔,RTSP超时时间、RTSP-TCP/UDP模式切换等。

音频处理如下:

	class PlayerAudioDataCallback implements NTAudioDataCallback
	{
		private WeakReference<LibPublisherWrapper> publisher_;
		private int audio_buffer_size = 0;
		private int param_info_size = 0;

		private ByteBuffer audio_buffer_ = null;
		private ByteBuffer parameter_info_ = null;

		public PlayerAudioDataCallback(LibPublisherWrapper publisher) {
			if (publisher != null)
				publisher_ = new WeakReference<>(publisher);
		}

		@Override
		public ByteBuffer getAudioByteBuffer(int size)
		{
			//Log.i("getAudioByteBuffer", "size: " + size);

			if( size < 1 )
			{
				return null;
			}

			if ( size <= audio_buffer_size && audio_buffer_ != null )
			{
				return audio_buffer_;
			}

			audio_buffer_size = size + 512;
			audio_buffer_size = (audio_buffer_size+0xf) & (~0xf);

			audio_buffer_ = ByteBuffer.allocateDirect(audio_buffer_size);

			// Log.i("getAudioByteBuffer", "size: " + size + " buffer_size:" + audio_buffer_size);

			return audio_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 = size + 32;
			param_info_size = (param_info_size+0xf) & (~0xf);

			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);

			if ( audio_buffer_ == null)
				return;

			LibPublisherWrapper publisher = publisher_.get();
			if (null == publisher)
				return;

			if (!publisher.is_publishing())
				return;

			audio_buffer_.rewind();

			publisher.PostAudioEncodedData(audio_codec_id, audio_buffer_, sample_size, is_key_frame, timestamp, parameter_info_, parameter_info_size);
		}
	}

视频处理如下:

	class PlayerVideoDataCallback implements NTVideoDataCallback
	{
		private WeakReference<LibPublisherWrapper> publisher_;
		private int video_buffer_size = 0;

		private ByteBuffer video_buffer_ = null;

		public PlayerVideoDataCallback(LibPublisherWrapper publisher) {
			if (publisher != null)
				publisher_ = new WeakReference<>(publisher);
		}

		@Override
		public ByteBuffer getVideoByteBuffer(int size)
		{
			//Log.i("getVideoByteBuffer", "size: " + size);

			if( size < 1 )
			{
				return null;
			}

			if ( size <= video_buffer_size &&  video_buffer_ != null )
			{
				return  video_buffer_;
			}

			video_buffer_size = size + 1024;
			video_buffer_size = (video_buffer_size+0xf) & (~0xf);

			video_buffer_ = ByteBuffer.allocateDirect(video_buffer_size);

			// Log.i("getVideoByteBuffer", "size: " + size + " buffer_size:" + video_buffer_size);

			return video_buffer_;
		}

		public void onVideoDataCallback(int ret, int video_codec_id, int sample_size, int is_key_frame, long timestamp, int width, int height, long presentation_timestamp)
		{
			//Log.i("onVideoDataCallback", "ret: " + ret + ", video_codec_id: " + video_codec_id + ", sample_size: " + sample_size + ", is_key_frame: "+ is_key_frame +  ", timestamp: " + timestamp +
			//		",presentation_timestamp:" + presentation_timestamp);

			if ( video_buffer_ == null)
				return;

			LibPublisherWrapper publisher = publisher_.get();
			if (null == publisher)
				return;

			if (!publisher.is_publishing())
				return;

			video_buffer_.rewind();

			publisher.PostVideoEncodedData(video_codec_id, video_buffer_, sample_size, is_key_frame, timestamp, presentation_timestamp);
		}
	}

除此之外,如果需要本地预览RTSP流数据,可以调用播放操作:

	private boolean StartPlay()
	{
		if(isPlaying)
			return false;

		if(!isPulling)
		{
			if (!OpenPullHandle())
				return false;
		}

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

		libPlayer.SmartPlayerSetRenderScaleMode(player_handle_, 1);

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

		libPlayer.SmartPlayerSetAudioOutputType(player_handle_, 1);

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

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

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

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

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

		libPlayer.SmartPlayerSetRotation(player_handle_, rotate_degrees);

		int iPlaybackRet = libPlayer.SmartPlayerStartPlay(player_handle_);

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

			releasePlayerHandle();
			return false;
		}

		isPlaying = true;

		return true;
	}

	private void StopPlay()
	{
		if ( !isPlaying )
			return;

		isPlaying = false;

		if (null == libPlayer || 0 == player_handle_)
			return;

		libPlayer.SmartPlayerStopPlay(player_handle_);
	}

如果需要把拉取到的RTSP流,本地录制留存,那么可以调用录像逻辑:

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

		ConfigRecorderFunction();

		int iRecRet = libPlayer
				.SmartPlayerStartRecorder(player_handle_);

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

			if ( !isPulling &&!isPlaying && !stream_publisher_.is_rtmp_publishing() && !stream_publisher_.is_rtsp_publishing() && !stream_publisher_.is_gb_stream_publishing())
			{
				libPlayer.SmartPlayerClose(player_handle_);
				player_handle_ = 0;
			}

			return false;
		}

		isRecording = true;
		return true;
	}

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

		isRecording = false;

		libPlayer.SmartPlayerStopRecorder(player_handle_);

		if ( !isPlaying && !isPulling && !stream_publisher_.is_rtmp_publishing() && !stream_publisher_.is_rtsp_publishing() && !stream_publisher_.is_gb_stream_publishing())
		{
			libPlayer.SmartPlayerClose(player_handle_);
			player_handle_ = 0;
		}
	}

如果需要实时快照,可以调用快照接口,实现snapshot,快照可以保存jpg或png格式:

		btnCaptureImage.setOnClickListener(new Button.OnClickListener() {
			@SuppressLint("SimpleDateFormat")
			public void onClick(View v) {
				if (0 == player_handle_)
					return;

				if (null == capture_image_date_format_)
					capture_image_date_format_ = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS");

				String timestamp = capture_image_date_format_.format(new Date());
				String imageFileName = timestamp;

				String image_path = imageSavePath + "/" + imageFileName;

				int quality;
				boolean is_jpeg = true;
				if (is_jpeg) {
					image_path += ".jpeg";
					quality = 100;
				}
				else {
					image_path += ".png";
					quality = 100;
				}

				int capture_ret = libPlayer.CaptureImage(player_handle_,is_jpeg?0:1, quality, image_path, "test cix");
				Log.i(TAG, "capture image ret:" + capture_ret + ", file:" + image_path);
			}
		});

总结

RTSP转GB28181到国标平台侧,涉及到两个模块,RTSP拉流和GB28181设备接入,如果需要本地录像留存数据,还需要有功能齐全的录像模块。实现起来,如果没有成熟的技术储备,短期内确实很难做出来真正可用的产品。以上是大概的流程,感兴趣的开发者,可以跟我探讨。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/368358.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

clickhouse在MES中的应用-跟踪扫描

开发的MES&#xff0c;往往都要做生产执行跟踪扫描&#xff0c;这样会产生大量的扫描数据&#xff0c;用关系型数据库&#xff0c;很容易造成查询冲突的问题。 生产跟踪扫描就发生的密度是非常高的&#xff0c;每个零部件的加工过程&#xff0c;都要被记录下来&#xff0c;特别…

在Linux中对Nginx进行安全加固

准备工作 在IP为x.x.x.x的服务器上安装nginx&#xff0c;确保Linux系统为nginx环境。 检查nginx是否配置nginx账号锁定策略 配置nginx账号锁定策略&#xff0c;降低被攻击概率。 第一步&#xff0c;查看nginx的锁定状态。 命令&#xff1a;passwd -S nginx 若结果出现“P…

vivado 制定执行策略

制定执行策略 策略是一组到工具的开关&#xff0c;这些开关在预先配置的一组选项中定义用于合成应用程序或在实现期间运行的各种实用程序和程序。每个主要版本都有特定于版本的策略选项。 视频&#xff1a;有关更多信息&#xff0c;请参阅以下内容&#xff1a;Vivado Design …

在Flutter中调用Android的代码

参考 【Flutter 混合开发】嵌入原生View-Android 默认使用Android studio 和 Kotlin 基本配置 创建flutter项目 在终端执行 flutter create batterylevel添加 Android 平台的实现 打开项目下的android/app/src/main/kotlin 下的 MainActivity.kt 文件。 我这里编辑器有…

MicroPython ESP32开发:快速参考

ESP32是使用非常广泛的一款微处理器&#xff0c;集成了WiFi和蓝牙模块&#xff0c;根据性能和应用场景的不同有很多不同的版本&#xff0c;本文是ESP32开发板在MicroPython环境下运行的快速参考&#xff0c;对于首次使用这个开发板在MicroPython下进行开发的应该会有一定的帮助…

会计的记账凭证

目录 一. 记账凭证的填制与审核1.1 收付款凭证1.2 转账凭证1.3 单式记账凭证 二. 记账凭证的编号 \quad 一. 记账凭证的填制与审核 \quad \quad 1.1 收付款凭证 \quad 注意︰ 凡是涉及货币资金之间收付款的业务如将库存现金存入银行或从银行提取现金等类经济业务。在实际工作中…

探索设计模式的魅力:为什么你应该了解装饰器模式-代码优化与重构的秘诀

设计模式专栏&#xff1a;http://t.csdnimg.cn/nolNS 开篇 在一个常常需要在不破坏封装的前提下扩展对象功能的编程世界&#xff0c;有一个模式悄无声息地成为了高级编程技术的隐形冠军。我们日复一日地享受着它带来的便利&#xff0c;却往往对其背后的复杂性视而不见。它是怎样…

幻兽帕鲁服务器多少钱?价格PK阿里云腾讯云华为云

2024年幻兽帕鲁服务器价格表更新&#xff0c;阿里云、腾讯云和华为云Palworld服务器报价大全&#xff0c;4核16G幻兽帕鲁专用服务器阿里云26元、腾讯云32元、华为云26元&#xff0c;阿腾云atengyun.com分享幻兽帕鲁服务器优惠价格表&#xff0c;多配置报价&#xff1a; 幻兽帕鲁…

【C++】C++入门— 类与对象初步介绍

C入门 1 认识面向对象2 类的引入3 类的定义类的定义方式 4 类的访问限定符及封装访问限定符封装 Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&#xff01;下一篇文章见&#xff01;&#xff01;&#xff01; 1 认识面向对象 C语言是面向过程的&#xff0c;关注…

位运算之妙用:识别独特数字(寻找单身狗)

目录 找单身狗1 图解&#xff1a; 代码如下&#xff1a; 找单身狗2 图解&#xff1a; 代码如下&#xff1a; 寻找单身狗1 从数组中 的1 2 3 4 5 1 2 3 4 中找出没有另一个相同的数与其匹配的数 这个问题的原理是利用异或运算的性质。异或运算&#xff08;XOR&#xff09…

java学习03 判断和循环

一 流程控制语句 1.流程控制语句分类 顺序结构 判断和选择结构(if, switch) 循环结构(for, while, do…while) 2. 顺序结构 顺序结构是程序中最简单最基本的流程控制&#xff0c;没有特定的语法结构&#xff0c;按照代码的先后顺序&#xff0c;依次执行&#xff0c;程序中…

Blender使用Rigify和Game Rig Tool基础

做动画需要的几个简要步骤&#xff1a; 1.建模 2.绑定骨骼 3.绘制权重 4.动画 有一个免费的插件可以处理好给引擎用&#xff1a;Game Rig Tool 3.6和4.0版本的 百度网盘 提取码&#xff1a;vju8 1.Rigify是干嘛用的&#xff1f; 》 绑定骨骼 2.Game Rig Tool干嘛用的&#xf…

Revit中使用依赖注入

依赖注入的技术已经很成熟&#xff0c;本文主要是说明一下Revit中的适用版本与介绍相关的开源项目。 版本问题 版本 目前的依赖注入包无法支持Revit 2020 以下的版本&#xff0c;原因是因为包中的依赖项与Revit本身的依赖项不一致导致的&#xff0c;所以说如果使用Revit DI…

2V2无人机红蓝对抗仿真

两架红方和蓝方无人机分别从不同位置起飞&#xff0c;蓝方无人机跟踪及击毁红方无人机 2020a可正常运行 2V2无人机红蓝对抗仿真资源-CSDN文库

探索Gin框架:Golang使用Gin完成文件上传

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站https://www.captainbed.cn/kitie。 前言 在之前的文章中&#xff0c;我们讲解了Gin框架的快速入门使用&#xff0c;今天我们来聊聊如何使用…

单细胞scATAC-seq测序基础知识笔记

单细胞scATAC-seq测序基础知识笔记 单细胞ATAC测序前言scATAC-seq数据怎么得出的&#xff1f; 该笔记来源于 Costa Lab - Bioinformatics Course 另一篇关于scRNA-seq的请移步 单细胞ATAC测序前言 因为我的最终目的是scATAC-seq的数据&#xff0c;所以这部分只是分享下我刚学…

c++类继承

一、继承的规则 &#xff08;1&#xff09;基类成员在派生类中的访问权限不得高于继承方式中指定的权限。例如&#xff0c;当继承方式为protected时&#xff0c;那么基类成员在派生类中的访问权限最高也为protected&#xff0c;高于protected会降级为protected&#xff0c;但低…

阿里云部署的幻兽帕鲁服务器,为什么没有一键更新游戏服务端、导入存档、可视化的游戏配置

如果有的朋友发现自己用阿里云部署的幻兽帕鲁服务器&#xff0c;找不到一键更新游戏服务端、一键导入存档、以及可视化的游戏配置。其实答案很简单&#xff0c;因为你的“阿里云计算巢”版本需要更新。你只需要更新到最新版&#xff0c;就可以拥有这些便捷的功能了。 具体的操…

【备战蓝桥杯】——循环结构终篇

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-yl4Tqejg4LkjZLAM {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

一文辨析清楚LORA、Prompt Tuning、P-Tuning、Adapter 、Prefix等大模型微调方法

本文探讨了大模型微调的核心概念和方法&#xff0c;详细介绍了如LoRA、Adapter Tuning、Prefix Tuning等多种微调策略。每种方法的原理、优势及适用场景都有详尽阐述&#xff0c;大家可以根据不同的应用需求和计算资源&#xff0c;选择到最合适自己的微调途径。 希望本文能对想…