Android平台如何实现RTSP转GB28181

本文涉及的产品
视觉智能开放平台,图像资源包5000点
视觉智能开放平台,分割抠图1万点
视觉智能开放平台,视频资源包5000点
简介: 实际上,在做Android平台GB28181设备接入模块的时候,我们已经有了非常好的技术积累,比如RTMP推送、轻量级RTSP服务、一对一互动模块、业内几乎最好的RTMP|RTSP低延迟播放器。

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

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

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

image.gif

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模块即可。

image.gif


废话不多说,上代码,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();
    }

image.gif

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

image.gif

注册后,会有以下回调:

@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 :""));
    }

image.gif

如果国标平台侧有实时查看请求,先发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);
  }

image.gif

收到平台侧的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);
    }

image.gif

国标平台侧停止查看:

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

image.gif

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

image.gif

对应的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;
  }

image.gif

这里设置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);
        }
    }

image.gif

视频处理如下:

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

image.gif

除此之外,如果需要本地预览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_);
    }

image.gif

如果需要把拉取到的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;
        }
    }

image.gif

如果需要实时快照,可以调用快照接口,实现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);
            }
        });

image.gif

总结

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

相关文章
|
27天前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
97 1
|
2月前
|
IDE Android开发 iOS开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
【9月更文挑战第27天】在移动应用开发的世界中,Android和iOS是两个主要的操作系统平台。每个系统都有其独特的开发环境、工具和用户群体。本文将深入探讨这两个平台的关键差异点,并分析这些差异如何影响应用的性能、用户体验和最终的市场表现。通过对比分析,我们将揭示选择正确的开发平台对于确保项目成功的重要作用。
|
17天前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
64 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
2月前
|
开发工具 Android开发 iOS开发
安卓与iOS开发环境对比:选择适合你的平台
【9月更文挑战第26天】在移动应用开发的广阔天地中,安卓和iOS是两大巨头。它们各自拥有独特的优势和挑战,影响着开发者的选择和决策。本文将深入探讨这两个平台的开发环境,帮助你理解它们的核心差异,并指导你根据个人或项目需求做出明智的选择。无论你是初学者还是资深开发者,了解这些平台的异同都至关重要。让我们一起探索,找到最适合你的那片开发天地。
|
2月前
|
Android开发 开发者
Android平台无纸化同屏如何实现实时录像功能
Android平台无纸化同屏,如果需要本地录像的话,实现难度不大,只要复用之前开发的录像模块的就可以,对我们来说,同屏采集这块,只是数据源不同而已,如果是自采集的其他数据,我们一样可以编码录像。
|
2月前
|
安全 API 开发工具
Android平台RTMP推送|轻量级RTSP服务如何实现麦克风|扬声器声音采集切换
Android平台扬声器播放声音的采集,在无纸化同屏等场景下,意义很大,早期低版本的Android设备,是没法直接采集扬声器audio的(从Android 10开始支持),所以,如果需要采集扬声器audio,需要先做系统版本判断,添加相应的权限。
|
2月前
|
编解码 开发工具 Android开发
Android平台实现屏幕录制(屏幕投影)|音频播放采集|麦克风采集并推送RTMP或轻量级RTSP服务
Android平台屏幕采集、音频播放声音采集、麦克风采集编码打包推送到RTMP和轻量级RTSP服务的相关技术实现,做成高稳定低延迟的同屏系统,还需要有配套好的RTMP、RTSP直播播放器
|
3天前
|
编解码 Java Android开发
通义灵码:在安卓开发中提升工作效率的真实应用案例
本文介绍了通义灵码在安卓开发中的应用。作为一名97年的聋人开发者,我在2024年Google Gemma竞赛中获得了冠军,拿下了很多项目竞赛奖励,通义灵码成为我的得力助手。文章详细展示了如何安装通义灵码插件,并通过多个实例说明其在适配国际语言、多种分辨率、业务逻辑开发和编程语言转换等方面的应用,显著提高了开发效率和准确性。
|
2天前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
13 5
|
2天前
|
缓存 数据库 Android开发
安卓开发中的性能优化技巧
【10月更文挑战第29天】在移动应用的海洋中,性能是船只能否破浪前行的关键。本文将深入探讨安卓开发中的性能优化策略,从代码层面到系统层面,揭示如何让应用运行得更快、更流畅。我们将以实际案例和最佳实践为灯塔,引领开发者避开性能瓶颈的暗礁。
9 3

热门文章

最新文章

  • 1
    2024重生之回溯数据结构与算法系列学习之串(12)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
    15
  • 2
    2024重生之回溯数据结构与算法系列学习(11)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
    9
  • 3
    2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    11
  • 4
    2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    13
  • 5
    2024重生之回溯数据结构与算法系列学习(8)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    14
  • 6
    2024重生之回溯数据结构与算法系列学习(7)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    11
  • 7
    2024重生之回溯数据结构与算法系列学习之王道第2.3章节之线性表精题汇总二(5)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    8
  • 8
    23
    7
  • 9
    2024重生之回溯数据结构与算法系列学习之单双链表精题(4)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    16
  • 10
    2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    10