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*/@OverridepublicvoidntsOnInvitePlay(StringdeviceId, SessionDescriptionsession_des) {
      handler_.postDelayed(newRunnable() {
      @Overridepublicvoidrun() {
      // 先振铃响应下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;
            }
      MediaSessionDescriptionvideo_des=null;
      SDPRtpMapAttributeps_rtpmap_attr=null;
      // 28181 视频使用PS打包Vector<MediaSessionDescription>video_des_list=session_des_.getVideoPSDescriptions();
      if (video_des_list!=null&&!video_des_list.isEmpty()) {
      for(MediaSessionDescriptionm : 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());
      longrtp_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); // 设置到2Mlib_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;
            }
      intlocal_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);
      Stringlocal_ip_addr=IPAddrUtils.getIpAddress(context_);
      MediaSessionDescriptionlocal_video_des=newMediaSessionDescription(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;
          }
      privateStringdevice_id_;
      privateSessionDescriptionsession_des_;
      publicRunnableset(Stringdevice_id, SessionDescriptionsession_des) {
      this.device_id_=device_id;
      this.session_des_=session_des;
      returnthis;
          }
        }.set(deviceId, session_des),0);
      }

      image.gif

      try_preview_camera()实现如下:

      privatebooleantry_preview_camera() {
      if (camera_!=null)
      returntrue;
      SurfaceHoldersurface_holder=get_surface_holder();
      if (null==surface_holder) {
      Log.e(TAG, "try_preview_camera surface_holder is null");
      returnfalse;
          }
      camera_=open_camera(current_camera_type_);
      if (null==camera_) {
      Log.e(TAG, "try_preview_camera open_camera is null type:"+current_camera_type_);
      returnfalse;
           }
      if (!start_camera_preview(surface_holder)) {
      release_camera();
      Log.i(TAG, "try_preview_camera start_camera_preview failed");
      returnfalse;
          }
      returntrue;
       }

      image.gif

      收到ack后,直接发送数据到国标平台侧即可

      @OverridepublicvoidntsOnAckPlay(StringdeviceId) {
      handler_.postDelayed(newRunnable() {
      @Overridepublicvoidrun() {
      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);intstartRet=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;
          }
      privateStringdevice_id_;
      publicRunnableset(Stringdevice_id) {
      this.device_id_=device_id;
      returnthis;
          }
        }.set(deviceId),0);
      }

      image.gif

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

      @OverridepublicvoidntsOnByePlay(StringdeviceId) {
      handler_.postDelayed(newRunnable() {
      @Overridepublicvoidrun() {
      Log.i(TAG, "ntsOnByePlay, stop GB28181 media stream, deviceId="+device_id_);
      stopGB28181Stream();
      destoryRTPSender();
          }
      privateStringdevice_id_;
      publicRunnableset(Stringdevice_id) {
      this.device_id_=device_id;
      returnthis;
          }
        }.set(deviceId),0);
      }

      image.gif

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

      //停止GB28181 媒体流privatevoidstopGB28181Stream() {
      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月前
      |
      Java Android开发 Swift
      安卓与iOS开发对比:平台选择对项目成功的影响
      【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
      118 1
      |
      3月前
      |
      IDE Android开发 iOS开发
      探索Android与iOS开发的差异:平台选择对项目成功的影响
      【9月更文挑战第27天】在移动应用开发的世界中,Android和iOS是两个主要的操作系统平台。每个系统都有其独特的开发环境、工具和用户群体。本文将深入探讨这两个平台的关键差异点,并分析这些差异如何影响应用的性能、用户体验和最终的市场表现。通过对比分析,我们将揭示选择正确的开发平台对于确保项目成功的重要作用。
      |
      2月前
      |
      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开发知识可参考相关书籍。
      102 0
      FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
      |
      3月前
      |
      开发工具 Android开发 iOS开发
      安卓与iOS开发环境对比:选择适合你的平台
      【9月更文挑战第26天】在移动应用开发的广阔天地中,安卓和iOS是两大巨头。它们各自拥有独特的优势和挑战,影响着开发者的选择和决策。本文将深入探讨这两个平台的开发环境,帮助你理解它们的核心差异,并指导你根据个人或项目需求做出明智的选择。无论你是初学者还是资深开发者,了解这些平台的异同都至关重要。让我们一起探索,找到最适合你的那片开发天地。
      |
      14天前
      |
      开发框架 前端开发 Android开发
      安卓与iOS开发中的跨平台策略
      在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
      |
      19天前
      |
      搜索推荐 Android开发 开发者
      探索安卓开发中的自定义视图:打造个性化UI组件
      【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
      |
      1天前
      |
      搜索推荐 前端开发 API
      探索安卓开发中的自定义视图:打造个性化用户界面
      在安卓应用开发的广阔天地中,自定义视图是一块神奇的画布,让开发者能够突破标准控件的限制,绘制出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战技巧,逐步揭示如何在安卓平台上创建和运用自定义视图来提升用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你的应用在众多同质化产品中脱颖而出。
      28 19
      |
      5天前
      |
      Java 调度 Android开发
      安卓与iOS开发中的线程管理差异解析
      在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
      |
      21天前
      |
      Android开发 Swift iOS开发
      探索安卓与iOS开发的差异和挑战
      【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。
      |
      23天前
      |
      XML 存储 Java
      探索安卓开发之旅:从新手到专家
      【10月更文挑战第35天】在数字化时代,安卓应用的开发成为了一个热门话题。本文旨在通过浅显易懂的语言,带领初学者了解安卓开发的基础知识,同时为有一定经验的开发者提供进阶技巧。我们将一起探讨如何从零开始构建第一个安卓应用,并逐步深入到性能优化和高级功能的实现。无论你是编程新手还是希望提升技能的开发者,这篇文章都将为你提供有价值的指导和灵感。

      热门文章

      最新文章