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

 注册后,会有以下回调:

	@Overridepublic void ntsRegisterOK(String dateString) {Log.i(TAG, "ntsRegisterOK Date: " + (dateString!= null? dateString : ""));}@Overridepublic void ntsRegisterTimeout() {Log.e(TAG, "ntsRegisterTimeout");}@Overridepublic void ntsRegisterTransportError(String errorInfo) {Log.e(TAG, "ntsRegisterTransportError error:" + (errorInfo != null?errorInfo :""));}

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

	@Overridepublic void ntsOnInvitePlay(String deviceId, SessionDescription session_des) {handler_.postDelayed(new Runnable() {@Overridepublic 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); // 设置到2MlibPublisher.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后,开始投递数据到国标平台侧:

	@Overridepublic void ntsOnAckPlay(String deviceId) {handler_.postDelayed(new Runnable() {@Overridepublic 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);}

国标平台侧停止查看:

	@Overridepublic void ntsOnByePlay(String deviceId) {handler_.postDelayed(new Runnable() {@Overridepublic 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 speedlibPlayer.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);}@Overridepublic 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_;}@Overridepublic 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);}@Overridepublic 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设备接入,如果需要本地录像留存数据,还需要有功能齐全的录像模块。实现起来,如果没有成熟的技术储备,短期内确实很难做出来真正可用的产品。以上是大概的流程,感兴趣的开发者,可以跟我探讨。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

爱、自由与创造——教育改革的三大基石

爱、自由与创造——教育改革的三大基石 Love, Freedom, and Creativity: The Three Pillars of Educational Reform 在当今社会快速发展的背景下&#xff0c;创造性思维的重要性日益凸显。然而&#xff0c;我们必须认识到&#xff0c;创造性并非凭空产生&#xff0c;而是深深植…

MySQL | 一定会走索引却没有走的原因,日志报conversion相关错误

TL;DR&#xff1a;函数作用于表字段时&#xff0c;索引会失效。 具体情况 Indexes如下&#xff1a; SQL如下&#xff1a; explain select *from accounting_status_flow_tab_00000000WHERE ( client_no 6848134000 and loan_no 0119324345936016261000 )order by modif…

【python接口自动化】- PyMySQL数据连接

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

前端工程化基础(三):Webpack基础

Webpack和打包过程 学习webpack主要是为了了解目前前端开发的整体流程&#xff0c;实际开发中&#xff0c;我们并不需要去手动配置&#xff0c;因为框架的脚手架都已经帮助我们完成了配置 内置模块path 该模块在Webpack中会经常使用 从路径中获取信息 const path require(&qu…

[Python] 什么是逻辑回归模型?使用scikit-learn中的LogisticRegression来解决乳腺癌数据集上的二分类问题

什么是线性回归和逻辑回归&#xff1f; 线性回归是一种用于解决回归问题的统计模型。它通过建立自变量&#xff08;或特征&#xff09;与因变量之间的线性关系来预测连续数值的输出。线性回归的目标是找到一条直线&#xff08;或超平面&#xff09;&#xff0c;使得预测值与观…

Eureka实操--下篇

高可用连接方式 1、双机部署&#xff1a;Eureka的server端相互注册&#xff0c;自动相互同步应用信息。Eureka的client端注册到任意一个上面即可&#xff0c;但为了保险起见&#xff0c;可以同时注册到两个上面&#xff0c;防止client注册到server1后&#xff0c;server1挂掉&…

7.NFS服务器

目录 1. 简介 1.1. NFS背景介绍 1.2. 生产应用场景 2. NFS工作原理 2.1. 示例图 2.2. 流程 3. NFS的使用 3.1. 安装 3.2. 配置文件 3.3. 主配置文件分析 3.4. 实验1 3.5. NFS账户映射 3.5.1. 实验2&#xff1a; 3.5.2. 实验3 4. autofs自动挂载服务 4.1. 产生原…

C语言——标准输出函数(printf、putchar和puts)

目录 1. 标准输入输函数出头文件2. printf2.1 函数申明2.2 基本用法2.3 占位符2.4 输出格式2.4.1 限定宽度2.4.2 总是显示正负号2.4.3 限定小数位数2.4.4 输出部分字符串 3. putchar3.1 函数申明3.2 基本用法 4. puts4.1 函数申明4.2 基本用法 1. 标准输入输函数出头文件 #inc…

undo log 和 redo log的区别

undo log 和 redo log的区别 缓冲池&#xff08;Buffer Pool&#xff09;是MySQL用于存储数据页的内存区域&#xff0c;它用于减少对磁盘的读写操作&#xff0c;提高数据库的访问速度。在MySQL中&#xff0c;数据被分为多个固定大小的数据页&#xff08;通常为16KB&#xff09…

【代码随想录20】669.修剪二叉搜索树 108.将有序数组转换为二叉搜索树 538.把二叉搜索树转换为累加树

目录 669.修剪二叉搜索树题目描述参考代码 108.将有序数组转换为二叉搜索树题目介绍参考代码 538.把二叉搜索树转换为累加树题目描述参考代码 669.修剪二叉搜索树 题目描述 给你二叉搜索树的根节点 root &#xff0c;同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树…

前端添加富文本/Web 富文本编辑器wangeditor

官网wangEditor 需要引入两个文件 <link href"https://unpkg.com/wangeditor/editorlatest/dist/css/style.css" rel"stylesheet"> <script src"https://unpkg.com/wangeditor/editorlatest/dist/index.js"></script> 前端…

8.DNS域名解析服务器

目录 1. 概述 1.1. 产生原因 1.2. 作用&#xff1a; 1.3. 连接方式 1.4. 因特网的域名结构 1.4.1. 拓扑&#xff1a; 1.4.2. 分类 1.4.3. 域名服务器类型划分 2. DNS域名解析过程 2.1. 分类&#xff1a; 2.2. 解析图&#xff1a; 2.2.1. 图&#xff1a; 2.2.2. 过…

面试中问到的算法题。————目录树生成

前言 我在面试中遇到了算法题&#xff0c;也是我第一次面试&#xff0c;也不知道是太紧张了还是太久没刷算法题了&#xff0c;感觉压有点懵的状态&#xff0c;所以当时面试的时候没有做出来或者说只做了一半没有做完。 面试完成后&#xff0c;我又重新审视了一下题目&#xff…

【Linux】日志的实现——日志等级的分类、日志的实现和输出、日志在程序中的应用(以管道通信为例)

文章目录 日志实现1.日志的介绍2.日志的制作&#xff08;向屏幕直接打印&#xff09;2.1获取时间2.2输出内容2.3打印方式2.3.1向单个文件打印2.3.2向分类文件打印 3.日志的应用3.1以管道通信为例 日志实现 1.日志的介绍 Linux日志是以时间线-事件的方式记录操作系统和应用的信…

【软件工程】建模工具之开发各阶段绘图——UML2.0常用图实践技巧(功能用例图、静态类图、动态序列图状态图活动图)

更多示例图片可以参考&#xff1a;&#xff08;除了常见的流程图&#xff0c;其他都有&#xff09; 概念&#xff1a;类图 静态&#xff1a;用例图 动态&#xff1a;顺序图&状态图&活动图 1、【面向对象】UML类图、用例图、顺序图、活动图、状态图、通信图、构件图、部…

Redis核心技术与实战【学习笔记】 - 12.Redis删除数据后,为什么内存占用率还是很高?

前言 在使用 Redis 是&#xff0c;经常会遇到一个问题&#xff1a;明明做了数据删除&#xff0c;数据量不大&#xff0c;但是 使用 top 命令查看时&#xff0c;发现 Redis 还是占用了很多内存。 这是因为&#xff0c;当删除数据后&#xff0c;Redis 释放的内存空间会由内存分…

《数字化运维路线图》第三部分-数字化运维转型平台 震撼发布!

数字化转型已不再是企业追求效益最大化的手段&#xff0c;而是成为经济发展变革、提升国家数字竞争的核心动力。在此背景下&#xff0c;博睿数据继续发力&#xff0c;隆重推出「数字化运维转型平台」&#xff0c;汇聚了我们对数字化转型的深刻洞见与实践经验&#xff0c;以期为…

༺༽༾ཊ—Unity之-01-工厂方法模式—ཏ༿༼༻

首先创建一个项目&#xff0c; 在这个初始界面我们需要做一些准备工作&#xff0c; 建基础通用文件夹&#xff0c; 创建一个Plane 重置后 缩放100倍 加一个颜色&#xff0c; 任务&#xff1a;使用工厂方法模式 创建 飞船模型&#xff0c; 首先资源商店下载飞船模型&#xff0c…

《区块链简易速速上手小册》第6章:区块链在金融服务领域的应用(2024 最新版)

文章目录 6.1 金融服务中的区块链6.1.1 金融服务中区块链的基础6.1.2 主要案例&#xff1a;跨境支付6.1.3 拓展案例 1&#xff1a;去中心化金融&#xff08;DeFi&#xff09;6.1.4 拓展案例 2&#xff1a;代币化资产 6.2 区块链在支付系统中的作用6.2.1 支付系统中区块链的基础…