Android平台GB28181设备接入侧如何同时对外输出RTSP流?

2023年 7月 31日 93.2k 0

​技术背景

GB28181的应用场景非常广泛,如公共安全、交通管理、企业安全、教育、医疗等众多领域,细分场景可用于如执法记录仪、智能安全帽、智能监控、智慧零售、智慧教育、远程办公、明厨亮灶、智慧交通、智慧工地、雪亮工程、平安乡村、生产运输、车载终端等:

  • 公共安全:通过GB28181协议,用户可以实时监控特定区域的视频画面,从而提高公共安全水平。
  • 交通管理:GB28181可用于交通监控系统,帮助交通部门实时监控道路交通情况,提高交通管理效率。
  • 企业安全:GB28181可以用于构建企业视频监控系统,保护企业资产,提高安全工作效率。
  • 教育:通过GB28181协议,用户可以进行远程视频会议和教学,为学生提供更为灵活的学习方式。
  • 医疗:GB28181可以用于医疗领域的视频监控,提高医疗安全和管理效率。
  • 技术实现

    本文以Android平台GB28181设备接入模块为例,谈谈具体实现,还有如何对外输出RTSP流。

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

    • 编码前数据(目前支持的有YV12/NV21/NV12/I420/RGB24/RGBA32/RGB565等数据类型);
    • 编码后数据(如无人机等264/HEVC数据,或者本地解析的MP4音视频数据);
    • 拉取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、软编码速度、可变码率设置;
    • 支持纯视频、音视频PS打包传输;
    • 支持RTP OVER UDP和RTP OVER TCP被动模式(TCP媒体流传输客户端);
    • 支持信令通道网络传输协议TCP/UDP设置;
    • 支持注册、注销,支持注册刷新及注册有效期设置;
    • 支持设备目录查询应答;
    • 支持心跳机制,支持心跳间隔、心跳检测次数设置;
    • 支持移动设备位置(MobilePosition)订阅和通知;
    • 支持语音广播;
    • 支持语音对讲;
    • 支持云台控制和预置位查询;
    • [实时水印]支持动态文字水印、png水印;
    • [镜像]Android平台支持前置摄像头实时镜像功能;
    • [实时静音]支持实时静音/取消静音;
    • [实时快照]支持实时快照;
    • [降噪]支持环境音、手机干扰等引起的噪音降噪处理、自动增益、VAD检测;
    • [外部编码前视频数据对接]支持YUV数据对接;
    • [外部编码前音频数据对接]支持PCM对接;
    • [外部编码后视频数据对接]支持外部H.264数据对接;
    • [外部编码后音频数据对接]外部AAC数据对接;
    • [扩展录像功能]支持和录像模块组合使用,录像相关功能。​

    Android平台GB28181设备接入模块,除了上述的功能点外,我们遇到的诉求有,如何同时对外输出RTSP,供如内网平台预览播放?

    这里就提到了轻量级RTSP服务,音视频数据源过来后,编码分别注入GB28181模块和轻量级RTSP服务模块,如果需要做到对外输出RTSP流,只需要启动RTSP服务,然后发布RTSP流即可,具体的操作如下:

    启动、停止RTSP服务:

    //启动/停止RTSP服务
    class ButtonRtspServiceListener implements View.OnClickListener {
      public void onClick(View v) {
        if (isRTSPServiceRunning) {
          stopRtspService();
    
          btnRtspService.setText("启动RTSP服务");
          btnRtspPublisher.setEnabled(false);
    
          isRTSPServiceRunning = false;
          return;
        }
    
        Log.i(TAG, "onClick start rtsp service..");
    
        rtsp_handle_ = libPublisher.OpenRtspServer(0);
    
        if (rtsp_handle_ == 0) {
          Log.e(TAG, "创建rtsp server实例失败! 请联系 https://daniusdk.com 检查SDK有效性");
        } else {
          int port = 8554;
          if (libPublisher.SetRtspServerPort(rtsp_handle_, port) != 0) {
            libPublisher.CloseRtspServer(rtsp_handle_);
            rtsp_handle_ = 0;
            Log.e(TAG, "创建rtsp server端口失败! 请检查端口是否重复或者端口不在范围内!");
          }
    
          if (libPublisher.StartRtspServer(rtsp_handle_, 0) == 0) {
            Log.i(TAG, "启动rtsp server 成功!");
          } else {
            libPublisher.CloseRtspServer(rtsp_handle_);
            rtsp_handle_ = 0;
            Log.e(TAG, "启动rtsp server失败! 请检查设置的端口是否被占用!");
          }
    
          btnRtspService.setText("停止RTSP服务");
          btnRtspPublisher.setEnabled(true);
    
          isRTSPServiceRunning = true;
        }
      }
    }
    

    发布、停止RTSP流:

    //发布/停止RTSP流
    class ButtonRtspPublisherListener implements View.OnClickListener {
      public void onClick(View v) {
        if (isRTSPPublisherRunning) {
          stopRtspPublisher();
    
          btnRtspPublisher.setText("发布RTSP流");
          btnGetRtspSessionNumbers.setEnabled(false);
          btnRtspService.setEnabled(true);
          return;
        }
    
        Log.i(TAG, "onClick start rtsp publisher..");
    
        if (!isPushingRtmp && !isGB28181StreamRunning && !isRecording) {
          InitAndSetConfig();
        }
    
        if (publisherHandle == 0) {
          Log.e(TAG, "Start rtsp publisher, publisherHandle is null..");
          return;
        }
    
        String rtsp_stream_name = "stream1";
        libPublisher.SetRtspStreamName(publisherHandle, rtsp_stream_name);
        libPublisher.ClearRtspStreamServer(publisherHandle);
    
        libPublisher.AddRtspStreamServer(publisherHandle, rtsp_handle_, 0);
    
        if (libPublisher.StartRtspStream(publisherHandle, 0) != 0) {
          Log.e(TAG, "调用发布rtsp流接口失败!");
          return;
        }
    
        if (!isPushingRtmp && !isGB28181StreamRunning && !isRecording) {
          CheckInitAudioRecorder();    //enable pure video publisher..
        }
    
        startLayerPostThread();
    
        btnRtspPublisher.setText("停止RTSP流");
        btnGetRtspSessionNumbers.setEnabled(true);
        btnRtspService.setEnabled(false);
        isRTSPPublisherRunning = true;
      }
    }
    

    获取RTSP链接数:

    //获取RTSP会话数
    class ButtonGetRtspSessionNumbersListener implements View.OnClickListener {
      public void onClick(View v) {
        if (libPublisher != null && rtsp_handle_ != 0) {
          int session_numbers = libPublisher.GetRtspServerClientSessionNumbers(rtsp_handle_);
    
          Log.i(TAG, "GetRtspSessionNumbers: " + session_numbers);
    
          PopRtspSessionNumberDialog(session_numbers);
        }
      }
    }
    

    获取回调上来的RTSP URL,对应的事件ID为EVENT_DANIULIVE_ERC_PUBLISHER_RTSP_URL:

    private static class EventHandlerPublisherV2 implements NTSmartEventCallbackV2 {
      @Override
      public void onNTSmartEventCallbackV2(long handle, int id, long param1, long param2, String param3, String param4, Object param5) {
    
        Log.i(TAG, "EventHandeV2: handle=" + handle + " id:" + id);
    
        String publisher_event = "";
    
        switch (id) {
         .....
          case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_RECORDER_START_NEW_FILE:
            publisher_event = "开始一个新的录像文件 : " + param3;
            break;
          case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_ONE_RECORDER_FILE_FINISHED:
            if (recorder_io_executor_ != null) {
              ExecutorService executor = recorder_io_executor_.get();
              if (executor != null)
                executor.execute(new RecordFileFinishedHandler().set(handle, param3, param1));
            }
            publisher_event = "已生成一个录像文件 : " + param3;
            break;
    
          case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_SEND_DELAY:
            publisher_event = "发送时延: " + param1 + " 帧数:" + param2;
            break;
    
          case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CAPTURE_IMAGE:
            publisher_event = "快照: " + param1 + " 路径:" + param3;
    
            if (param1 == 0) {
              publisher_event = publisher_event + "截取快照成功..";
            } else {
              publisher_event = publisher_event + "截取快照失败..";
            }
            break;
          case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_RTSP_URL:
            publisher_event = "RTSP服务URL: " + param3;
            break;
        }
    
        String str = "当前回调状态:" + publisher_event;
    
        Log.i(TAG, str);
    
        if (handler_ != null) {
          android.os.Handler handler = handler_.get();
          if (handler != null) {
            Message message = new Message();
            message.what = PUBLISHER_EVENT_MSG;
            message.obj = publisher_event;
            handler.sendMessage(message);
          }
        }
      }
    
      public NTSmartEventCallbackV2 set(android.os.Handler handler, ExecutorService recorder_io_executor) {
        this.handler_ = new WeakReference(handler);
        this.recorder_io_executor_ = new WeakReference(recorder_io_executor);
        return this;
      }
    
      private WeakReference handler_;
      private WeakReference recorder_io_executor_;
    }
    

    总结

    GB28181设备接入模块同时输出RTSP流的话,需要注意的是,在一个实例里面完成,确保只编码一路音视频数据,然后分别打包注入两个模块,尽可能的降低设备性能消耗。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论