Android平台GB28181设备接入侧如何实现按需打开视音频采集传输

本文涉及的产品
视觉智能开放平台,视频资源包5000点
视觉智能开放平台,分割抠图1万点
视觉智能开放平台,图像资源包5000点
简介: Android平台GB28181设备接入侧如何实现按需打开视音频采集传输
+关注继续查看

GB/T28181规范

GB/T28181是中国国家标准,全称为《安全防范视频监控联网系统信息传输、交换、控制技术要求》,该标准规定了城市安全防范监控系统中视频监控联网系统的一般要求和架构,以及信息传输、交换、控制的技术要求。它主要应用于安防领域,为各种视频监控系统提供了一致的接口规范,使得不同厂商生产的视频监控设备可以相互兼容。规范规定了公共安全视频监控联网系统(以下简称“联网系统”)的互联结构,传输、交换、控制的基本要求和安全性要求,以及控制、传输流程和协议接口等技术要求。适用于公共安全视频监控联网系统的方案设计、系统检测、验收以及与之相关的设备研发生产。其他视频监控联网系统可参照执行。目前已更新至GB/T28181-2022版。

为什么要开发Android平台GB28181接入模块

实际上,Android平台GB28181接入模块,主要目标是可实现不具备国标音视频能力的 Android终端,通过平台注册接入到现有的GB/T28181—2016服务,可用于如执法记录仪、智能安全帽、智能监控、智慧零售、智慧教育、远程办公、明厨亮灶、智慧交通、智慧工地、雪亮工程、平安乡村、生产运输、车载终端等场景。

image.gif视沃科技(大牛直播SDK)GB28181设备接入SDK.png

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

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

    功能设计

    实际上,我们在做Android平台GB28181设备接入模块之前,已经有非常成熟的视音频采集(屏幕、摄像头、外部音视频数据)、软硬编码、录像、快照、实时动态水印等技术储备,所以,GB28181设备接入,主要考虑的是信令和媒体流传输这块,考虑到设备性能和实际场景,我们信令和媒体传输设计是分离的,Android端GB28181设备接入侧注册到国标平台后,如果国标平台不需要查看前端设备数据,我们仅维持心跳(KeepAlive),需要查看的时候,我们再开摄像头、麦克风编码打包投递数据给平台侧,尽可能的减少性能消耗,这块在执法记录仪、智能安全帽等场景下,非常实用。

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

      Android端如何实现后台视音频GB28181接入

      后台采集摄像头和麦克风这块,不再赘述,基本做Andorid开发的,都能搞得定,需要注意的是,后台service推送,需要加入省电优化白名单,以免8.0及以上版本设备后台运行超过一分钟被自动停掉,6.0以上版本,需要动态获取权限:

      if (Build.VERSION.SDK_INT >=26)
      {
        if(!isIgnoringBatteryOptimizations())
        {
          gotoSettingIgnoringBatteryOptimizations();
        }
      }
      //6.0及以上版本,动态获取Audio权限
      if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
      {
        RequestAudioPermission();
      }

      image.gif

      我们要做的就是选择分辨率、软硬编码等参数后,启动GB28181即可:

      推送smartservicecamerapublisher.jpg

      因为系后台服务,启动后,任务栏可以看到:

      smartservicecamerapublisher.jpg

      收到平台侧发来的Invite后,我们会调用try_preview_camera()来启动摄像头后台预览。

      /*
       * BackgroudService.java
       * Author: daniusdk.com
       */
      @Override
      public void ntsOnInvitePlay(String deviceId, SessionDescription session_des) {
        handler_.postDelayed(new Runnable() {
          @Override
          public void run() {
            // 先振铃响应下
            gb28181_agent_.respondPlayInvite(180, device_id_);
            if (!try_preview_camera()) {
              gb28181_agent_.respondPlayInvite(488, device_id_);
              Log.i(TAG, "ntsOnInvitePlay try_preview_camera failed, response 488, device_id:" + device_id_);
              return;
            }
            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 = lib_publisher_.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();
            lib_publisher_.SetRTPSenderTransportProtocol(rtp_sender_handle, video_des.isRTPOverUDP()?0:1);
            lib_publisher_.SetRTPSenderIPAddressType(rtp_sender_handle, video_des.isIPv4()?0:1);
            lib_publisher_.SetRTPSenderLocalPort(rtp_sender_handle, 0);
            lib_publisher_.SetRTPSenderSSRC(rtp_sender_handle, video_des.getSSRC());
            lib_publisher_.SetRTPSenderSocketSendBuffer(rtp_sender_handle, 2*1024*1024); // 设置到2M
            lib_publisher_.SetRTPSenderClockRate(rtp_sender_handle, ps_rtpmap_attr.getClockRate());
            lib_publisher_.SetRTPSenderDestination(rtp_sender_handle, video_des.getAddress(), video_des.getPort());
            if ( lib_publisher_.InitRTPSender(rtp_sender_handle) != 0 ) {
              gb28181_agent_.respondPlayInvite(488, device_id_);
              lib_publisher_.DestoryRTPSender(rtp_sender_handle);
              return;
            }
            int local_port = lib_publisher_.GetRTPSenderLocalPort(rtp_sender_handle);
            if (local_port == 0) {
              gb28181_agent_.respondPlayInvite(488, device_id_);
              lib_publisher_.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) ) {
              lib_publisher_.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

      try_preview_camera()实现如下:

      private boolean try_preview_camera() {
           if (camera_ != null)
               return true;
          SurfaceHolder surface_holder = get_surface_holder();
          if (null == surface_holder) {
              Log.e(TAG, "try_preview_camera surface_holder is null");
              return false;
          }
           camera_ = open_camera(current_camera_type_);
           if (null == camera_) {
               Log.e(TAG, "try_preview_camera open_camera is null type:" + current_camera_type_);
               return false;
           }
          if (!start_camera_preview(surface_holder)) {
              release_camera();
              Log.i(TAG, "try_preview_camera start_camera_preview failed");
              return false;
          }
          return true;
       }

      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();
            lib_publisher_.SetGB28181RTPSender(publisher_handle_, 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);
            int startRet = lib_publisher_.StartGB28181MediaStream(publisher_handle_);
            if (startRet != 0) {
              if (publisher_handle_ != 0) {
                lib_publisher_.SmartPublisherClose(publisher_handle_);
                publisher_handle_ = 0;
              }
              destoryRTPSender();
              Log.e(TAG, "Failed to start GB28181 service..");
              return;
            }
            startAudioRecorder();
            startLayerPostThread();
            is_gb_stream_running_ = true;
          }
          private String device_id_;
          public Runnable set(String device_id) {
            this.device_id_ = device_id;
            return this;
          }
        }.set(deviceId),0);
      }

      image.gif

      关闭预览查看,处理bye信令:

      @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

      其中stopGB28181Stream()需要关闭摄像头(对应release_camera())和麦克风(对应stopAudioRecorder()),确保只有国标平台测查看的时候,才开启摄像头,尽可能的减少性能损耗。

      //停止GB28181 媒体流
      private void stopGB28181Stream() {
        if (!is_gb_stream_running_)
          return;
        stopLayerPostThread();
        stopAudioRecorder();
        release_camera();
        is_gb_stream_running_ = false;
        lib_publisher_.StopGB28181MediaStream(publisher_handle_);
        if (publisher_handle_ != 0) {
          lib_publisher_.SmartPublisherClose(publisher_handle_);
          publisher_handle_ = 0;
        }
      }

      image.gif

      总结

      以上是大概的流程,摄像头麦克风采集做到后台的话,可以在需要预览采集数据的时候才打开,不用的时候,直接关闭,只保留信令这块,打开视音频预览后,如果有语音广播过来,可以直接播放语音广播的数据,这样尽可能的减少设备的性能消耗,提高待机时间,特别是执法记录仪等户外设备,按需打开摄像头和麦克风,按需投递视音频数据到平台外侧,意义非常大。

      相关文章
      |
      2月前
      |
      生物认证 开发工具 Android开发
      安卓设备签到,还是用视觉智能平台人脸搜索1:N
      安卓设备签到,还是用视觉智能平台人脸搜索1:N
      143 2
      |
      2月前
      |
      XML 网络协议 开发工具
      Android平台GB28181设备接入侧如何实现SIP校时
      Android平台GB28181设备接入侧如何实现SIP校时
      |
      3月前
      |
      数据采集 前端开发 Android开发
      Android平台RTMP推送或GB28181设备接入端如何实现采集audio音量放大?
      我们在做Android平台RTMP推送和GB28181设备对接的时候,遇到这样的问题,有的设备,麦克风采集出来的audio,音量过高或过低,特别是有些设备,采集到的麦克风声音过低,导致播放端听不清前端采集的audio,这时候,就需要针对采集到的audio,做音量放大处理。
      |
      3月前
      |
      编解码 监控 API
      Android平台GB28181设备接入侧音频采集推送示例
      GB/T28181是广泛应用于视频监控行业的标准协议规范,可以在不同设备之间实现互联互通。今天我们主要探讨Android平台的Audio采集部分。
      |
      3月前
      |
      数据采集 Android开发 开发者
      Android平台GB28181设备接入模块摄像头采集方向不对怎么办?
      我们在做Android平台GB28181设备接入模块的时候,有开发者提到这样的诉求:他们的智能头盔、执法记录仪等设备,采集到的图像,是旋转了90、180甚至270°的,设备本身无法针对图像做翻转或者旋转操作,问我们这种情况下需要如何处理?
      |
      5月前
      |
      传感器 前端开发 Java
      Android流媒体开发之路一:Camera2采集摄像头原始数据并手动预览
      Android流媒体开发之路一:Camera2采集摄像头原始数据并手动预览
      308 0
      |
      10月前
      |
      API Android开发 内存技术
      android 采集PCM音频数据并播放(支持USB摄像头MIC)
      android 采集PCM音频数据并播放(支持USB摄像头MIC)
      385 0
      |
      数据采集 传感器 编解码
      【Android RTMP】安卓直播推流总结 ( 直播服务器搭建 | NV21 图像采集 | H.264 视频编码 | PCM 音频采集 | AAC 音频编码 | RTMP 包封装推流 )
      【Android RTMP】安卓直播推流总结 ( 直播服务器搭建 | NV21 图像采集 | H.264 视频编码 | PCM 音频采集 | AAC 音频编码 | RTMP 包封装推流 )
      886 0
      【Android RTMP】安卓直播推流总结 ( 直播服务器搭建 | NV21 图像采集 | H.264 视频编码 | PCM 音频采集 | AAC 音频编码 | RTMP 包封装推流 )
      |
      监控 数据可视化 数据挖掘
      淘宝 Android 帧率采集与监控详解
      淘宝 Android 帧率采集与监控详解
      567 0
      淘宝 Android 帧率采集与监控详解
      相关产品
      视觉智能开放平台
      推荐文章
      更多