Android平台GB28181设备接入端如何降低资源占用和性能消耗

背景

我们在做GB28181设备接入模块的时候,考虑到好多设备性能一般,我们一般的设计思路是,先注册设备到平台侧,平台侧发calalog过来,获取设备信息,然后,设备侧和国标平台侧维持心跳,如果有位置订阅信息,按照订阅时间间隔,实时上报设备位置信息。

如果本地没有录像诉求,或者,国标平台侧不发起invite请求,Android平台GB28181设备接入端,不做视频编码,甚至可以连摄像头都不打开,等有实时录像或国标平台侧视频预览播放请求的时候,再打开摄像头,毕竟摄像头单纯的打开,设备都有性能损耗,甚至一些中低端记录仪,还没编码就开始发热。

技术实现

本文以大牛直播SDK的Android平台GB28181设备接入侧为例,先启动GB28181,启动后,直接注册到国标平台侧,整体设计架构图如下:

class ButtonGB28181AgentListener implements View.OnClickListener {
  public void onClick(View v) {
    stopAudioPlayer();
    destoryRTPReceiver();

    gb_broadcast_source_id_ = null;
    gb_broadcast_target_id_ = null;
    btnGB28181AudioBroadcast.setText("GB28181语音广播");
    btnGB28181AudioBroadcast.setEnabled(false);

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

其中,initGB28181Agent()做的工作如下:

private boolean initGB28181Agent() {
  if ( gb28181_agent_ != null )
    return  true;

  getLocation(context_);

  String local_ip_addr = IPAddrUtils.getIpAddress(context_);
  Log.i(TAG, "[daniusdk]initGB28181Agent local ip addr: " + local_ip_addr);

  if ( local_ip_addr == null || local_ip_addr.isEmpty() ) {
    Log.e(TAG, "[daniusdk]initGB28181Agent local ip is empty");
    return  false;
  }

  gb28181_agent_ = GBSIPAgentFactory.getInstance().create();
  if ( gb28181_agent_ == null ) {
    Log.e(TAG, "[daniusdk]initGB28181Agent create agent failed");
    return false;
  }

  gb28181_agent_.addListener(this);
  gb28181_agent_.addPlayListener(this);
  gb28181_agent_.addAudioBroadcastListener(this);
  gb28181_agent_.addDeviceControlListener(this);
  gb28181_agent_.addQueryCommandListener(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);

  if (!gb28181_agent_.createSipStack()) {
    gb28181_agent_ = null;
    Log.e(TAG, "[daniusdk]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, "[daniusdk]initGB28181Agent gb28181_agent_.bindLocalPort failed.");
    return  false;
  }

  if (!gb28181_agent_.initialize()) {
    gb28181_agent_.unBindLocalPort();
    gb28181_agent_.releaseSipStack();
    gb28181_agent_ = null;
    Log.e(TAG, "[daniusdk]initGB28181Agent gb28181_agent_.initialize failed.");
    return  false;
  }

  return true;
}

注册成功后,会把国标平台侧返回200 OK时带的时间返回上来,便于Android平台GB28181设备侧做校时,如有注册异常,也会返回:

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

周期性的心跳,如有异常,我们也回调到上层:

@Override
public void ntsOnHeartBeatException(int exceptionCount,  String lastExceptionInfo) {
  Log.e(TAG, "ntsOnHeartBeatException heart beat timeout count reached, count:" + exceptionCount+
        ", exception info:" + (lastExceptionInfo!=null?lastExceptionInfo:""));

  // 停止信令, 然后重启
  handler_.postDelayed(new Runnable() {
    @Override
    public void run() {
      Log.i(TAG, "gb28281_heart_beart_timeout");

      stopAudioPlayer();
      destoryRTPReceiver();

      if (gb_broadcast_source_id_ != null && gb_broadcast_target_id_ != null && gb28181_agent_ != null)
        gb28181_agent_.byeAudioBroadcast(gb_broadcast_source_id_, gb_broadcast_target_id_);

      gb_broadcast_source_id_ = null;
      gb_broadcast_target_id_ = null;
      btnGB28181AudioBroadcast.setText("GB28181语音广播");
      btnGB28181AudioBroadcast.setEnabled(false);

      stopGB28181Stream();
      destoryRTPSender();

      if (gb28181_agent_ != null) {
        gb28181_agent_.terminateAllPlays(true);

        Log.i(TAG, "gb28281_heart_beart_timeout sip stop");
        gb28181_agent_.stop();

        String local_ip_addr = IPAddrUtils.getIpAddress(context_);
        if (local_ip_addr != null && !local_ip_addr.isEmpty() ) {
          Log.i(TAG, "gb28281_heart_beart_timeout get local ip addr: " + local_ip_addr);
          gb28181_agent_.setLocalAddress(local_ip_addr);
        }

        Log.i(TAG, "gb28281_heart_beart_timeout sip start");
        gb28181_agent_.start();
      }
    }

  },0);
}

如果国标平台侧订阅了实时位置信息,我们的处理如下:

@Override
public void ntsOnDevicePositionRequest(String deviceId, int interval) {
  handler_.postDelayed(new Runnable() {
    @Override
    public void run() {
      getLocation(context_);

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

        if (gb28181_agent_ != null ) {
          gb28181_agent_.updateDevicePosition(device_id_, device_pos);
        }
      }
    }

    private String device_id_;
    private int interval_;

    public Runnable set(String device_id, int interval) {
      this.device_id_ = device_id;
      this.interval_ = interval;
      return this;
    }

  }.set(deviceId, interval),0);
}

如果平台侧发起预览请求,我们的处理如下:

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

      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后,才开始真正发送数据:

@Override
public void ntsOnAckPlay(String deviceId) {
  handler_.postDelayed(new Runnable() {
    @Override
    public void run() {
      Log.i(TAG,"ntsOnACKPlay, device_id:" +device_id_);

      if (!isRTSPPublisherRunning && !isPushingRtmp && !isRecording) {
        InitAndSetConfig();
      }

      libPublisher.SetGB28181RTPSender(publisherHandle, 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 = libPublisher.StartGB28181MediaStream(publisherHandle);
      if (startRet != 0) {

        if (!isRTSPPublisherRunning && !isPushingRtmp  && !isRecording) {
          if (publisherHandle != 0) {
            long handle = publisherHandle;
            publisherHandle = 0;
            libPublisher.SmartPublisherClose(handle);
          }
        }

        destoryRTPSender();

        Log.e(TAG, "Failed to start GB28181 service..");
        return;
      }

      if (!isRTSPPublisherRunning && !isPushingRtmp && !isRecording) {
        CheckInitAudioRecorder();
      }

      startLayerPostThread();
      isGB28181StreamRunning = true;
    }

    private String device_id_;

    public Runnable set(String device_id) {
      this.device_id_ = device_id;
      return this;
    }

  }.set(deviceId),0);
}

总结

除此之外,还有语音广播和语音对讲,这里不再赘述,GB28181规范普及之前,要想从外网远程访问局域网内的监控设备非常麻烦,一般要么RTSP转RTMP推到RTMP服务器,此外还要单独构建信令。GB28181规范,让远程、跨网访问监控设备更方便,把GB28181平台部署到外网后,前端设备只要注册到国标服务器,就可以被远程访问、管理和调取视频。但由于设备侧性能并不是非常好,如果要有好的稳定性和性能要求,需尽可能的减少性能消耗,按需打开摄像头、按需编码等。

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何在Spring MVC中使用@ControllerAdvice创建全局异常处理器

文章目录 前言一、认识注解&#xff1a;RestControllerAdvice和ExceptionHandler二、使用步骤1、封装统一返回结果类2、自定义异常类封装3、定义全局异常处理类4、测试 总结 前言 全局异常处理器是一种 &#x1f31f;✨机制&#xff0c;用于处理应用程序中发生的异常&#xff…

HCIA---OSI/RM--开放式系统互联参考模型

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 一.OSI--开放式系统互联参考模型简介 OSI开放式系统互联参考模型是一种用于计算机网络通信…

C# Blazor 学习笔记(11):路由跳转和信息传值

文章目录 前言路由跳转测试用例路由传参/路由约束想法更新&#xff1a;2023年8月4日 前言 Blazor对路由跳转进行了封装。 ASP.NET Core Blazor 路由和导航 NavigationManager 类 本文的主要内容就是全局的跳转 路由跳转 路由跳转就要用到NavigationManager 类。 其实最常用…

解密HTTP代理爬虫中的IP代理选择与管理策略

在当今数据驱动的世界中&#xff0c;HTTP代理爬虫作为一项重要的数据采集工具&#xff0c;其成功与否往往取决于IP代理的选择与管理策略。作为一家专业的HTTP代理产品供应商&#xff0c;我们深知IP代理在数据采集中的重要性。在本文中&#xff0c;我们将分享一些关于HTTP代理爬…

RabbitMQ-API

这里写目录标题 Hello word 模式添加依赖生产者消费者获取信道工具类 Work Queues模式消费者代码 C1开启多线程运行启动 消费者代码 C2生产者代码 消息应答自动应答消息应答的方法Multiple 的解释消息自动重新入队消息手动应答代码消费者API 队列持久化消息持久化不公平分发消息…

一文带你深入了解JMM(Java内存模型)

JMM&#xff08;Java内存模型&#xff09;详解 为什么要有内存模型&#xff1f; 要想回答这个问题&#xff0c;我们需要先弄懂传统计算机硬件内存架构。 硬件内存架构 &#xff08;1&#xff09;CPU 去过机房的同学都知道&#xff0c;一般在大型服务器上会配置多个CPU&…

edge://settings/defaultbrowser default ie

Microsoft Edge 中的 Internet Explorer 模式 有些网站专为与 Internet Explorer 一起使用&#xff0c;它们具有 Microsoft Edge 等新式浏览器不支持的功能。 如果你需要查看其中的某个网站&#xff0c;可使用 Microsoft Edge 中的 Internet Explorer 模式。 大多数网站在新…

数据库数据恢复-Oracle数据库文件出现坏块的数据恢复案例

Oracle数据库故障&初检&分析&#xff1a; 打开Oracle数据库时报错&#xff0c;报错信息&#xff1a;“system01.dbf需要更多的恢复来保持一致性&#xff0c;数据库无法打开”。用户急需恢复zxfg用户下的数据。 出现上述报错的可能原因包括&#xff1a;控制文件损坏、数…

微信小程序如何引入Iconfont

在小程序中引入 Iconfont 可以通过以下步骤进行操作&#xff1a; 打开 Iconfont 网站&#xff08;https://www.iconfont.cn/&#xff09;并登录账号&#xff0c;创建一个项目并添加所需的图标到项目中。 在项目中选中需要使用的图标&#xff0c;点击右上角的 “下载代码” 按钮…

TBB库中实现协程(coroutine)的源码说明

源码请见: https://github.com/oneapi-src/oneTBB/blob/master/src/tbb/co_context.h 在windows系统&#xff0c;TBB(也就是intel 的 oneTBB库)&#xff0c;通过windwos fiber(纤程)来实现协程(coroutine)。 创建一个协程,代码很简洁: inline void create_coroutine(corouti…

接口测试—知识速查(Postman)

文章目录 接口测试1. 概念2. 原理3. 测试流程4. HTTP协议4.1 URL的介绍4.2 HTTP请求4.2.1 请求行4.2.2 请求头4.2.3 请求体4.2.4 完整的HTTP请求示例 4.3 HTTP响应4.3.1 状态行4.3.2 响应头4.3.3 响应体4.3.4 完整的HTTP请求示例 5. RESTful接口规范6. 测试用例的设计思路6.1 单…

Spring boot 集成 Skywalking 配置 || Skywalking 打不开【已解决】

一、Skywalking官网 Apache SkyWalking 1.下载Skywalking APM &#xff08;如果下载最新的&#xff0c;双击打开闪退&#xff0c;选老点的版本&#xff09; 2. 下载 Skywalking Agents 如果下载太慢&#xff0c;建议复制下载链接&#xff0c;然后用下载器下载&#xff0c;比…

BeanFactory和ApplicationContext容器

1.BeanFactory容器 在Spring容器中&#xff0c;BeanFactory是IOC容器中的最顶级的接口&#xff0c;是最基础的版本&#xff0c;里面定义了管理bean的基本方法&#xff0c;如&#xff1a;获取bean、判断是否存在等等方法。 BeanFactory下面有很多的实现类&#xff0c;各有职责&…

js:使用LetterAvatar通过canvas实现浏览器中生成字母头像

实现效果 LetterAvatar的原理就是利用了浏览器对象canvas 在线体验&#xff1a;https://mouday.github.io/tools/pages/letter-avatar/index.html LetterAvatar.js 完整代码 /** LetterAvatar* * Artur Heinze* Create Letter avatar based on Initials* based on https:/…

保留网络:大型语言模型的Transformer继任者

原文信息 原文题目&#xff1a;《Retentive Network: A Successor to Transformer for Large Language Models》 原文引用&#xff1a;Sun Y, Dong L, Huang S, et al. Retentive Network: A Successor to Transformer for Large Language Models[J]. arXiv preprint arXiv:2…

装饰器模式(C++)

定义 动态(组合)地给一个对象增加一些额外的职责。就增加功能而言&#xff0c;Decorator模式比生成子类(继承)更为灵活(消除重复代码&减少子类个数)。 一《设计模式》 GoF 装饰器模式&#xff08;Decorator Pattern&#xff09;允许向一个现有的对象添加新的功能&#xf…

MongoDB 6.0.8 安装配置

一、前言 MongoDB是一个基于分布式文件存储的数据库。由C语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。在高负载的情况下&#xff0c;添加更多的节点&#xff0c;可以保证服务器性能。 MongoDB 将数据存储为一个文档&#xff0c;数据结构由键值(key>value…

【Linux:线程池】

文章目录 1 线程池概念2 第一个版本的线程池3 第二个版本的线程池4 第三个版本的线程池5 STL中的容器以及智能指针的线程安全问题6 其他常见的各种锁7 读者写者问题(了解) 1 线程池概念 一种线程使用模式。线程过多会带来调度开销&#xff0c;进而影响缓存局部性和整体性能。而…

【绪论0】

#pic_center R 1 R_1 R1​ R 2 R^2 R2 目录 知识框架No.0 引言No.1 操作系统的概念功能和定义一、操作系统的概念和定义1、电脑的演变 二、操作系统的功能和目标 No.2 操作系统的特征一、并发二、共享三、虚拟四、异步 No.3 操作系统的发展与分类一、手工操作阶段二、批处理阶段…

elementui Cascader 级联选择使用心得

相信大家对于elementui并不陌生&#xff0c;作为适配Vue的优秀UI框架之一&#xff0c;一直被所有的开发者痛并快乐着。今天要记录的就是里边的主角之一Cascader。 首先先介绍一下Cascader ---> 当一个数据集合有清晰的层级结构时&#xff0c;可通过级联选择器逐级查看并选择…