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即可完成和国标平台侧的注册在线:

scss 复制代码
	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()实现:

ini 复制代码
    /*
     * 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;
	}

注册后,会有以下回调:

typescript 复制代码
	@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过来:

ini 复制代码
	@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后,开始投递数据到国标平台侧:

typescript 复制代码
	@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);
	}

国标平台侧停止查看:

typescript 复制代码
	@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流的时候,设置音视频数据回调。

ini 复制代码
    /*
     * 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()实现如下:

ini 复制代码
	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模式切换等。

音频处理如下:

ini 复制代码
	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);
		}
	}

视频处理如下:

java 复制代码
	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流数据,可以调用播放操作:

ini 复制代码
	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流,本地录制留存,那么可以调用录像逻辑:

ini 复制代码
	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格式:

ini 复制代码
		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设备接入,如果需要本地录像留存数据,还需要有功能齐全的录像模块。实现起来,如果没有成熟的技术储备,短期内确实很难做出来真正可用的产品。以上是大概的流程,感兴趣的开发者,可以跟我探讨。

相关推荐
dvlinker2 天前
【音视频开发】使用支持硬件加速的D3D11绘图遇到的绘图失败与绘图崩溃问题的记录与总结
音视频开发·c/c++·视频播放·d3d11·d3d11绘图模式
aqi005 天前
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
ffmpeg·音视频·直播·流媒体
音视频牛哥8 天前
Android平台GB28181实时回传流程和技术实现
音视频开发·视频编码·直播
音视频牛哥9 天前
RTMP、RTSP直播播放器的低延迟设计探讨
音视频开发·视频编码·直播
音视频牛哥13 天前
电脑共享同屏的几种方法分享
音视频开发·视频编码·直播
aqi0016 天前
FFmpeg开发笔记(五十四)使用EasyPusher实现移动端的RTSP直播
android·ffmpeg·音视频·直播·流媒体
aqi0017 天前
FFmpeg开发笔记(五十三)移动端的国产直播录制工具EasyPusher
android·ffmpeg·音视频·直播·流媒体
加油吧x青年17 天前
Web端开启直播技术方案分享
前端·webrtc·直播
aqi001 个月前
FFmpeg开发笔记(五十二)移动端的国产视频播放器GSYVideoPlayer
android·ffmpeg·音视频·直播·流媒体