GB28181设备接入侧录像查询和录像下载技术探究之实时录像

简介: 我们在对接GB28181设备接入侧的时候,除了常规实时音视频按需上传外,还有个重要的功能,就是本地实时录像,录像后的数据,在执法记录仪等前端设备留底,然后,到工作站拷贝到专门的平台。

技术背景

我们在对接GB28181设备接入侧的时候,除了常规实时音视频按需上传外,还有个重要的功能,就是本地实时录像,录像后的数据,在执法记录仪等前端设备留底,然后,到工作站拷贝到专门的平台。


本文探讨的是,基于GB28181设备接入更进一步的处理:录像查询和录像下载,本文以我们Android平台开发的GB28181设备接入为例,做个简单的分析。

本地录像存储

eb8cd3a5d58742d996502ab267a021af.jpg

GB28181设备接入侧,非常重要的功能属性就是实时录像,我们在做实时录像的时候,设计如下:


先说录像参数设置:

/**
   * SmartPublisherJniV2.java
   * Author: daniusdk.com
   * Created on 2015/09/20.
   */
/**
   * 音频录制开关, 目的是为了更细粒度的去控制录像, 一般不需要调用这个接口, 这个接口使用场景比如同时推送音视频,但只想录制视频,可以调用这个接口关闭音频录制
   *
   * @param is_recoder: 0: do not recorder; 1: recorder; sdk默认是1
   *
   * @return {0} if successful
   */
public native int SmartPublisherSetRecorderAudio(long handle, int is_recoder);
/**
   * 视频录制开关, 目的是为了更细粒度的去控制录像, 一般不需要调用这个接口, 这个接口使用场景比如同时推送音视频,但只想录制音频,可以调用这个接口关闭视频录制
   *
   * @param is_recoder: 0: do not recorder; 1: recorder; sdk默认是1
   *
   * @return {0} if successful
   */
public native int SmartPublisherSetRecorderVideo(long handle, int is_recoder);
/**
     * Create file directory(创建录像存放目录)
     * 
     * @param path,  E.g: /sdcard/daniulive/rec
     * 
     * <pre> The interface is only used for recording the stream data to local side. </pre> 
     * 
     * @return {0} if successful
     */
public native int SmartPublisherCreateFileDirectory(String path);
/**
     * Set recorder directory(设置录像存放目录)
     * 
     * @param path: the directory of recorder file.
     * 
     * <pre> NOTE: make sure the path should be existed, or else the setting failed. </pre>
     * 
     * @return {0} if successful
     */
public native int SmartPublisherSetRecorderDirectory(long handle, String path);
/**
     * Set the size of every recorded file(设置单个录像文件大小,如超过最大文件大小,自动切换到下个文件录制)
     * 
     * @param size: (MB), (5M~500M), if not in this range, set default size with 200MB.
     * 
     * @return {0} if successful
     */
public native int SmartPublisherSetRecorderFileMaxSize(long handle, int size);


录像控制:

/**
  * Start recorder(开始录像)
  *
  * @return {0} if successful
  */
public native int SmartPublisherStartRecorder(long handle);
/**
   * Pause recorder(暂停/恢复录像)
   *
   * is_pause: 1表示暂停, 0表示恢复录像, 输入其他值将调用失败
   *
   * @return {0} if successful
   */
public native int SmartPublisherPauseRecorder(long handle, int is_pause);
/**
    * Stop recorder(停止录像)
    *
    * @return {0} if successful
    */
public native int SmartPublisherStopRecorder(long handle);


录像状态回调

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_STARTED:
        publisher_event = "开始..";
        break;
      case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTING:
        publisher_event = "连接中..";
        break;
      case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTION_FAILED:
        publisher_event = "连接失败..";
        break;
      case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTED:
        publisher_event = "连接成功..";
        break;
      case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_DISCONNECTED:
        publisher_event = "连接断开..";
        break;
      case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_STOP:
        publisher_event = "关闭..";
        break;
      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 (record_executor_ != null) {
          RecordExecutorService executor = record_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;
      case NTSmartEventID.EVENT_DANIULIVE_ERC_PUSH_RTSP_SERVER_RESPONSE_STATUS_CODE:
        publisher_event ="RTSP status code received, codeID: " + param1 + ", RTSP URL: " + param3;
        break;
      case NTSmartEventID.EVENT_DANIULIVE_ERC_PUSH_RTSP_SERVER_NOT_SUPPORT:
        publisher_event ="服务器不支持RTSP推送, 推送的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, RecordExecutorService record_executor) {
    this.handler_ = new WeakReference<>(handler);
    this.record_executor_ = new WeakReference<>(record_executor);
    return this;
  }
  private WeakReference<android.os.Handler> handler_;
  private WeakReference<RecordExecutorService> record_executor_;
}


为适配GB28181的录像查询和处理,我们会把录像的文件,文件名做一定的处理,比如加上开始、结束时间还有duration和file size。


录像调用逻辑如下:

void ConfigRecorderParam() {
  if (libPublisher != null && publisherHandle != 0) {
    if (recDir != null && !recDir.isEmpty()) {
      int ret = libPublisher.SmartPublisherCreateFileDirectory(recDir);
      if (0 == ret) {
        if (0 != libPublisher.SmartPublisherSetRecorderDirectory(publisherHandle, recDir)) {
          Log.e(TAG, "Set record dir failed , path:" + recDir);
          return;
        }
        // 更细粒度控制录像的, 一般情况无需调用
        //libPublisher.SmartPublisherSetRecorderAudio(publisherHandle, 0);
        // libPublisher.SmartPublisherSetRecorderVideo(publisherHandle, 0);
        if (0 != libPublisher.SmartPublisherSetRecorderFileMaxSize(publisherHandle, 200)) {
          Log.e(TAG, "SmartPublisherSetRecorderFileMaxSize failed.");
          return;
        }
      } else {
        Log.e(TAG, "Create record dir failed, path:" + recDir);
      }
    }
  }
}
class ButtonStartRecorderListener implements View.OnClickListener {
  public void onClick(View v) {
    if (isRecording) {
      stopRecorder();
      if (!isPushingRtmp && !isRTSPPublisherRunning && !isGB28181StreamRunning) {
        ConfigControlEnable(true);
      }
      btnStartRecorder.setText("实时录像");
      btnPauseRecorder.setText("暂停录像");
      btnPauseRecorder.setEnabled(false);
      isPauseRecording = true;
      return;
    }
    Log.i(TAG, "onClick start recorder..");
    if (libPublisher == null)
      return;
    if (!isPushingRtmp && !isRTSPPublisherRunning&& !isGB28181StreamRunning) {
      InitAndSetConfig();
    }
    ConfigRecorderParam();
    int startRet = libPublisher.SmartPublisherStartRecorder(publisherHandle);
    if (startRet != 0) {
      if (!isPushingRtmp && !isRTSPPublisherRunning && !isGB28181StreamRunning) {
        if (publisherHandle != 0) {
          long handle = publisherHandle;
          publisherHandle = 0;
          libPublisher.SmartPublisherClose(handle);
        }
      }
      Log.e(TAG, "Failed to start recorder.");
      return;
    }
    if (!isPushingRtmp && !isRTSPPublisherRunning && !isGB28181StreamRunning) {
      CheckInitAudioRecorder();
      ConfigControlEnable(false);
    }
    startLayerPostThread();
    btnStartRecorder.setText("停止录像");
    isRecording = true;
    btnPauseRecorder.setEnabled(true);
    isPauseRecording = true;
  }
}
class ButtonPauseRecorderListener implements View.OnClickListener {
  public void onClick(View v) {
    if (isRecording) {
      if(isPauseRecording)
      {
        int ret = libPublisher.SmartPublisherPauseRecorder(publisherHandle, 1);
        if (ret == 0)
        {
          isPauseRecording = false;
          btnPauseRecorder.setText("恢复录像");
        }
        else if(ret == 3)
        {
          Log.e(TAG, "Pause recorder failed, please re-try again..");
        }
        else
        {
          Log.e(TAG, "Pause recorder failed..");
        }
      }
      else
      {
        int ret = libPublisher.SmartPublisherPauseRecorder(publisherHandle, 0);
        if (ret == 0)
        {
          isPauseRecording = true;
          btnPauseRecorder.setText("暂停录像");
        }
        else if(ret == 3)
        {
          Log.e(TAG, "Resume recorder failed, please re-try again..");
        }
        else
        {
          Log.e(TAG, "Resume recorder failed..");
        }
      }
    }
  }
}

总结

如果需要实现GB28181平台的录像查询和录像下载,实时录像的处理必不可少。下一章节,我们将根据GB28181规范探讨录像查询和录像下载。

相关文章
|
编解码 监控 API
Android平台GB28181设备接入侧音频采集推送示例
GB/T28181是广泛应用于视频监控行业的标准协议规范,可以在不同设备之间实现互联互通。今天我们主要探讨Android平台的Audio采集部分。
135 1
|
数据采集 前端开发 Android开发
Android平台RTMP推送或GB28181设备接入端如何实现采集audio音量放大?
我们在做Android平台RTMP推送和GB28181设备对接的时候,遇到这样的问题,有的设备,麦克风采集出来的audio,音量过高或过低,特别是有些设备,采集到的麦克风声音过低,导致播放端听不清前端采集的audio,这时候,就需要针对采集到的audio,做音量放大处理。
|
存储 Android开发 开发者
Android平台GB28181设备接入端实现实时快照
Android平台GB28181设计开发的时候,有个功能必不可少的:实时快照,特别是用于执法记录仪等场景下,用于图像留底或分析等考量。
|
Android开发 开发者 UED
Android平台RTMP推送端实现外部数据对接推送和录像
好多开发者在做Android平台RTMP推送对接的同时,除了编码前的数据外,还有些外部编码数据推送诉求,他们希望外部的编码音视频数据不止可以实现RTMP推送,还可以同时在推送端实时录制下来,本文以我们(官方)Android平台RTMP直播推送模块为例,介绍下外部数据对接流程和数据录制流程。
115 0
|
Android开发 开发者
Android平台GB28181设备接入端如何实现本地录像?
实现Android平台GB28181设备接入的时候,有个功能点不可避免,那就是本地录像,实际上,在实现GB28181设备接入模块之前,我们前些年做RTMP推送和轻量级RTSP服务的时候,早已经实现了本地录像功能。
109 0
|
4月前
|
开发工具 Android开发 开发者
Android平台如何不推RTMP|不发布RTSP流|不实时录像|不回传GB28181数据时实时快照?
本文介绍了一种在Android平台上实现实时截图快照的方法,尤其适用于无需依赖系统接口的情况,如在RTMP推送、RTSP服务或GB28181设备接入等场景下进行截图。通过底层模块(libSmartPublisher.so)实现了截图功能,封装了`SnapShotImpl.java`类来管理截图流程。此外,提供了关键代码片段展示初始化SDK实例、执行截图、以及在Activity销毁时释放资源的过程。此方案还考虑到了快照数据的灵活处理需求,符合GB/T28181-2022的技术规范。对于寻求更灵活快照机制的开发者来说,这是一个值得参考的设计思路。
|
4月前
|
编解码 网络协议 前端开发
如何实现Android平台GB28181设备接入模块按需打开摄像头并回传数据
后台采集摄像头,如果想再进一步扩展,可以把android平台gb28181的camera2 demo,都移植过来,实现功能更强大的国标设备侧,这里主要是展示,收到国标平台侧的回传请求后,才打开摄像头,才开始编码打包,最大限度的减少资源的占用
|
4月前
|
编解码 开发工具 Android开发
Android平台GB28181设备接入侧如何实现GB28181-2022实时快照
GB/T28181-2022标准中明确了快照的具体要求,包括图像抓拍配置命令的发送与接收流程。源设备需向目标设备发送包含传输路径和会话ID等信息的命令,目标设备完成图像传输后,通过IETF RFC 3428中的MESSAGE方法发送图像抓拍传输完成的通知。图像格式推荐使用JPEG,且分辨率应与主码流相同。技术实现上,如使用大牛直播SDK在Android平台上,可通过创建`SnapShotImpl`类并调用`capture()`方法实现快照功能,之后将JPEG格式的快照文件上传至国标平台。
|
编解码 监控 网络协议
Android平台GB28181设备接入侧如何实现按需打开视音频采集传输
Android平台GB28181设备接入侧如何实现按需打开视音频采集传输
164 2
|
编解码 Android开发 数据安全/隐私保护
Android平台GB28181设备接入端对接编码前后音视频源类型浅析
今天主要对Android平台GB28181设备接入模块支持的接入数据类型,做个简单的汇总: 1. 编码前数据(目前支持的有YV12/NV21/NV12/I420/RGB24/RGBA32/RGB565等数据类型),其中,Android平台前后摄像头数据,或者屏幕数据,或者Unity拿到的数据,均属编码前数据; 2. 编码后数据(如无人机等264/HEVC数据,或者本地解析的MP4音视频数据); 3. 拉取RTSP或RTMP流并接入至GB28181平台(比如其他IPC的RTSP流,可通过Android平台GB28181接入到国标平台)。