GB/T28181-2022之图像抓拍规范解读和设计实现

简介: GB/T28181-2022之图像抓拍规范解读和设计实现

技术背景

GB/T28181-2022相对2016版,对图像抓拍有了明确的界定,图像抓拍在视频监控行业非常重要, Android平台GB28181设备接入端,无需实时上传音视频实时数据的情况下,就可以抓图上传到指定的图像存储服务器上。

图像抓拍基本要求如下:

    1. 源设备向目标设备发送图像抓拍配置命令,需要携带传输路径、会话ID等信息。
    2. 目标设备完成图像传输后,发送图像抓拍传输完成通知命令,采用IETF RFC3428中的MESSAGE方法实现。
    3. 图像文件命名规则宜采用“设备编码(20位)、图像编码(2位)、时间编码(17位)、序列码(2位)”的形式
    4. 图像格式宜使用JPEG,图像分辨率宜采用与主码流相同的分辨率。

    image.gif抓拍图像文件命名规则.png

    需要注意的是,MESSAGE消息头Content-type头域为Content-type:Application/MANSCDP+xml,采用XML封装。设备收到图像抓拍配置命令后,发送配置响应命令,响应命令中包含执行结果信息。

    图像抓拍流程如下:

    gb28181-图像抓拍流程.png

    技术实现

    大牛直播SDK的SmartGBD已经完成GB28181设备接入侧的图像抓拍。

    camera2 gb28181界面.jpg

    总体功能设计如下:

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

      图像抓拍相关信令处理如下:

      /** Author: daniusdk.com*/packagecom.gb.ntsignalling;
      publicinterfaceGBSIPAgent {
      voidsetDeviceConfigListener(GBSIPAgentDeviceConfigListenerdeviceConfigListener);
      /** 通知图像抓拍传输完成*/booleannotifyUploadSnapShotFinished(StringfromUserName, StringfromUserNameAtDomain, StringdeviceID, StringsessionID, java.util.List<String>snapShotList);
      }

      image.gif

      Device配置Listener如下:

      packagecom.gb.ntsignalling;
      publicinterfaceGBSIPAgentDeviceConfigListener {
      /** 图像抓拍配置*/voidntsOnDeviceSnapShotConfig(Stringfrom_user_name, Stringfrom_user_name_at_domain,
      Stringsn, Stringdevice_id, SnapShotConfigconfig,
      List<String>extra_info_list);
      }

      image.gif

      Snapshot配置接口如下:

      publicinterfaceSnapShotConfig {
      intsnap_num();
      intinterval();
      Stringupload_url();
      Stringsession_id();
      }

      image.gif

      图像抓拍JNI接口设计如下:

      publicclassSmartPublisherJniV2 {
      /*** 截图接口, 支持JPEG和PNG两种格式* @param compress_format: 压缩格式, 0:JPEG格式, 1:PNG格式, 其他返回错误* @param quality: 取值范围:[0, 100], 值越大图像质量越好, 仅对JPEG格式有效, 若是PNG格式,请填100* @param file_name: 图像文件名, 例如:/dirxxx/test20231113100739.jpeg, /dirxxx/test20231113100739.png* @param user_data_string: 用户自定义字符串* @return {0} if successful*/publicnativeintCaptureImage(longhandle, intcompress_format, intquality, Stringfile_name, Stringuser_data_string);
      }

      image.gif

      Device Snap Shot Listener 核心代码如下:

      /** Author: daniusdk.com*/publicclassGBDeviceSnapShotListenerImplimplementsGBSIPAgentDeviceControlListener {
      @OverridepublicvoidntsOnDeviceSnapShotConfig(Stringfrom_user_name, finalStringfrom_user_name_at_domain,
      Stringsn, Stringdevice_id, finalSnapShotConfigconfig,
      List<String>extra_info_list) {
      if (null==config)
      return;
      handler_.postDelayed(newRunnable() {
      @Overridepublicvoidrun() {
      Log.i(TAG, "ntsOnDeviceSnapShotConfig device_id:"+device_id_+" session_id:"+config.session_id()
      +", snap_num:"+config.snap_num() +", interval:"+config.interval() +", upload_url:"+config.upload_url());
      if (null==gb28181_agent_)
      return;
      if (null==snap_shot_impl_) {
      snap_shot_impl_=newSnapShotGBImpl(image_path_, context_, handler_, lib_publisher_jni, snap_shot_publisher_);
      snap_shot_impl_.start();
                      }
      snap_shot_impl_.add_config(gb28181_agent_, from_user_name_, from_user_name_at_domain_, sn_,
      device_id_, snap_shot_config_, extra_info_list_);
                  }
      privateStringfrom_user_name_;
      privateStringfrom_user_name_at_domain_;
      privateStringsn_;
      privateStringdevice_id_;
      privateSnapShotConfigsnap_shot_config_;
      privateList<String>extra_info_list_;
      publicRunnableset(Stringfrom_user_name, Stringfrom_user_name_at_domain,
      Stringsn, Stringdevice_id, SnapShotConfigconfig,
      List<String>extra_info_list) {
      this.from_user_name_=from_user_name;
      this.from_user_name_at_domain_=from_user_name_at_domain;
      this.sn_=sn;
      this.device_id_=device_id;
      this.snap_shot_config_=config;
      this.extra_info_list_=extra_info_list;
      returnthis;
                  }
              }.set(from_user_name, from_user_name_at_domain, sn, device_id, config, extra_info_list), 0);
          }
      }
      publicclassSnapShotGBImplextendsSnapShotImpl {
      privateList<SnapConfig>config_list_=newLinkedList<>();
      publicSnapShotGBImpl(Stringdir, Contextcontext, android.os.Handlerhandler,
      SmartPublisherJniV2lib_sdk, LibPublisherWrapperpublisher) {
      super(dir, context, handler, lib_sdk, publisher);
          }
      publicbooleanadd_config(GBSIPAgentagent, Stringfrom_user_name, Stringfrom_user_name_at_domain, Stringsn,
      Stringdevice_id, SnapShotConfigconfig, List<String>extra_info_list) {
      if (null==agent)
      returnfalse;
      if (is_null_or_empty(device_id))
      returnfalse;
      if (null==config)
      returnfalse;
      if (config.snap_num() <1)
      returnfalse;
      if (config.interval() <1)
      returnfalse;
      if (is_null_or_empty(config.session_id()))
      returnfalse;
      SnapConfigc=newSnapConfig(agent, from_user_name, from_user_name_at_domain, sn, device_id, config, extra_info_list);
      config_list_.add(c);
      returntrue;
          }
      publicvoidon_captured_image(longresult, Stringfile_name, longfile_date_time_ms, Stringuser_data) {
      SnapConfigconfig=find_config(user_data);
      if (null==config) {
      super.on_captured_image(result, file_name, file_date_time_ms, user_data);
      return;
              }
      SnapItemitem=config.find_capturing_item(file_name);
      if (null==item) {
      super.on_captured_image(result, file_name, file_date_time_ms, user_data);
      return;
              }
      if (result!=0) {
      item.set_status(SnapItem.ERROR_STATUS);
      item.set_error_info("capture failed");
      Log.e(TAG, "capture failed, file:"+file_name+", session_id:"+user_data);
      return;
              }
      item.set_status(SnapItem.CAPTURE_COMPLETION_STATUS);
          }
      publicvoidon_uploaded(booleanis_ok, Stringfile_name, Stringsession_id, Stringgb_name) {
      SnapConfigconfig=find_config(session_id);
      if (null==config) {
      Log.w(TAG, "on_uploaded cannot find config, session_id:"+session_id+", gb_name:"+gb_name);
      return;
              }
      SnapItemitem=config.find_uploading_item(gb_name);
      if (null==item) {
      Log.w(TAG, "on_uploaded cannot find item, session_id:"+session_id+", gb_name:"+gb_name);
      return;
              }
      if (is_ok) {
      item.set_status(SnapItem.UPLOAD_COMPLETION_STATUS);
      Log.i(TAG, "on_uploaded ok, session_id:"+session_id+", file:"+file_name);
              }else {
      item.set_status(SnapItem.ERROR_STATUS);
      item.set_error_info("upload failed");
      Log.e(TAG, "on_uploaded failed, session_id:"+session_id+", file:"+file_name);
              }
          }
      @Overridepublicvoidon_stop() {
      this.config_list_.clear();
      shutdown(200, TimeUnit.MILLISECONDS);
          }
      privatevoidprocess_upload() {
      android.os.Handlerapp_handler=os_handler();
      for(SnapConfigc : config_list_)
      c.upload_files(app_handler, this);
          }
      privatevoidprocess_finished() {
      List<String>notified_files=null;
      Iterator<SnapConfig>iterator=config_list_.iterator();
      while(iterator.hasNext()) {
      SnapConfigc=iterator.next();
      if (!c.is_can_notify_server())
      continue;
      iterator.remove();
      if (null==notified_files)
      notified_files=newLinkedList<>();
      c.notify_server(notified_files);
              }
      // 暂时删除这些文件, 根据业务需求随时调整就好if(notified_files!=null&&!notified_files.isEmpty())
      execute(newDeleteFilesTask(notified_files));
          }
      privatestaticclassSnapItem {
      privateintstatus_=INITIALIZATION_STATUS;
      privatefinalStringdevice_id_;
      privatefinalintsn_; // 序列码, 40~41privatefinalStringdir_;
      privateStringfile_name_;
          }
      privatestaticclassSnapConfig {
      privateWeakReference<GBSIPAgent>agent_;
      privatefinalStringfrom_user_name_;
      privatefinalStringfrom_user_name_at_domain_;
      privatefinalStringsn_;
      privatefinalStringdevice_id_;
      privatefinalStringsession_id_;
      privatefinalintsnap_num_;
      privatefinalStringupload_url_;
      privatefinalintinterval_sec_;
      privatefinalList<String>extra_info_list_;
      privateArrayList<SnapItem>items_;
      publicfinalStringsession_id() {
      returnthis.session_id_;
              }
      publicvoidupload_files(android.os.Handleros_handler, SnapShotGBImplsnap) {
      if (null==items_)
      return;
      for (SnapItemi : items_) {
      if (i.is_capture_completion_status()) {
      i.set_status(SnapItem.UPLOADING_STATUS);
      BaseUploadTaskupload_task=newMyUploadTask(upload_url_, i.file_name(), i.gb_snap_shot_file_id(),
      session_id(), i.gb_name(), os_handler, snap);
      if (!snap.submit(upload_task) ) {
      i.set_status(SnapItem.ERROR_STATUS);
      i.set_error_info("submit upload task failed");
                          }
                      }
                  }
              }
      publicvoidnotify_server(List<String>notified_files) {
      ArrayList<String>snap_shot_list=newArrayList(items_.size());
      for (SnapItemi : items_) {
      if (SnapItem.UPLOAD_COMPLETION_STATUS==i.status())
      snap_shot_list.add(i.gb_snap_shot_file_id());
      if (notified_files!=null)
      notified_files.add(i.file_name());
                  }
      if (null==agent_)
      return;
      GBSIPAgentagent=agent_.get();
      if (null==agent)
      return;
      agent.notifyUploadSnapShotFinished(from_user_name_, from_user_name_at_domain_, device_id_, this.session_id(), snap_shot_list);
              }
          }
      privatestaticclassDeleteFilesTaskimplementsRunnable {
      privateList<String>file_name_list_;
      publicDeleteFilesTask(List<String>file_name_list) {
      this.file_name_list_=file_name_list;
              }
      @Overridepublicvoidrun() {
      if (null==file_name_list_)
      return;
      if (file_name_list_.isEmpty()) {
      file_name_list_=null;
      return;
                  }
      for (Stringi : file_name_list_) {
      try  {
      Filef=newFile(i);
      if (!f.exists()||!f.isFile() )
      continue;
      if (f.delete())
      Log.i(TAG, "delete file:"+i);
      elseLog.w(TAG, "delete file failed, "+i);
                      }
      catch(Exceptione) {
      Log.e(TAG, "DeleteFilesTask.run Exception:", e);
                      }
                  }
      file_name_list_.clear();
      file_name_list_=null;
              }
          }
      publicstaticclassBaseUploadTaskextendsCancellableTask {
      privatefinalStringupload_url_;
      privatefinalStringfile_name_;
      privatefinalStringgb_snap_shot_file_id_;
      privatefinalStringsession_id_;
      privatefinalStringgb_name_;
      privateWeakReference<android.os.Handler>os_handler_;
      privateWeakReference<SnapShotGBImpl>snap_;
      publicBaseUploadTask(Stringupload_url, Stringfile_name, Stringgb_snap_shot_file_id,
      Stringsession_id, Stringgb_name, android.os.Handleros_handler, SnapShotGBImplsnap) {
      this.upload_url_=upload_url;
      this.file_name_=file_name;
      this.gb_snap_shot_file_id_=gb_snap_shot_file_id;
      this.session_id_=session_id;
      this.gb_name_=gb_name;
      if (os_handler!=null)
      this.os_handler_=newWeakReference<>(os_handler);
      if (snap!=null)
      this.snap_=newWeakReference<>(snap);
              }
      protectedfinalStringupload_url() {
      returnthis.upload_url_;
              }
      protectedfinalStringfile_name() {
      returnthis.file_name_;
              }
      protectedfinalStringgb_snap_shot_file_id() {
      returnthis.gb_snap_shot_file_id_;
              }
      protectedfinalStringsession_id() {
      returnthis.session_id_;
              }
      protectedfinalStringgb_name() { returnthis.gb_name_; }
      protectedfinalandroid.os.Handleros_handler() {
      if (os_handler_!=null)
      returnos_handler_.get();
      returnnull;
              }
      protectedfinalSnapShotGBImplsnap() {
      if (snap_!=null)
      returnsnap_.get();
      returnnull;
              }
      privatestaticclassResultRunnableimplementsRunnable {
      privatefinalbooleanresult_;
      privatefinalStringfile_name_;
      privatefinalStringsession_id_;
      privatefinalStringgb_name_;
      privateWeakReference<SnapShotGBImpl>snap_;
      publicResultRunnable(booleanresult, Stringfile_name, Stringsession_id,
      Stringgb_name, SnapShotGBImplsnap) {
      this.result_=result;
      this.file_name_=file_name;
      this.session_id_=session_id;
      this.gb_name_=gb_name;
      if (snap!=null)
      this.snap_=newWeakReference<>(snap);
                  }
      @Overridepublicvoidrun(){
      if (null==this.snap_)
      return;
      SnapShotGBImplsnap=this.snap_.get();
      if (null==snap)
      return;
      snap.on_uploaded(result_, file_name_, session_id_, gb_name_);
                  }
              }
      protectedvoidpost_result(booleanis_ok) {
      android.os.Handlerhandler=os_handler();
      if (null==handler)
      return;
      SnapShotGBImplgb_snap=snap();
      if (null==gb_snap)
      return;
      handler.post(newResultRunnable(is_ok, file_name_,session_id_, gb_name_, gb_snap));
              }
          }
      }

      image.gif

      总结

      以上是GB28181图像抓拍大概的流程和设计参考,权当抛砖引玉,Android终端除支持常规的音视频数据接入外,还可以支持移动设备位置(MobilePosition)订阅和通知、图像抓拍、语音广播和语音对讲、历史视音频下载和回放。感兴趣的开发者,可以单独加我微*信 xinsheng120 跟我探讨。

      相关文章
      |
      机器学习/深度学习
      深度学习数据增强方法-内含(亮度增强,对比度增强,旋转图图像,翻转图像,仿射变化扩充图像,错切变化扩充图像,HSV数据增强)七种方式进行增强-每种扩充一张实现7倍扩)+ 图像缩放代码-批量
      深度学习数据增强方法-内含(亮度增强,对比度增强,旋转图图像,翻转图像,仿射变化扩充图像,错切变化扩充图像,HSV数据增强)七种方式进行增强-每种扩充一张实现7倍扩)+ 图像缩放代码-批量
      |
      9天前
      |
      存储 数据可视化 vr&ar
      突破传统 重新定义:3D医学影像PACS系统源码(包含RIS放射信息) 实现三维重建与还原
      突破传统,重新定义PACS/RIS服务,洞察用户需求,关注应用场景,新一代PACS/RIS系统,系统顶层设计采用集中+分布式架构,满足医院影像全流程业务运行,同时各模块均可独立部署,满足医院未来影像信息化扩展新需求、感受新时代影像服务便捷性、易用性!系统基于平台化设计,与第三方服务自然接入无压力,从功能多样化到调阅速度快;覆盖(放射、超声、内镜、病理、核医学、心血管、临床科室等,是以影像采集、传输、存储、诊断、报告书写和科室管理)为核心应用的模块化PACS/RIS系统,实现了全院级影像信息的合理共享与应用。
      22 0
      突破传统 重新定义:3D医学影像PACS系统源码(包含RIS放射信息) 实现三维重建与还原
      |
      5月前
      |
      存储 数据采集 数据可视化
      【C++】PACS医学图像存储和传输系统源码带三维重建
      【C++】PACS医学图像存储和传输系统源码带三维重建
      39 0
      |
      5月前
      |
      存储 安全
      支持CT、彩色超声、内窥镜检查的医院影像PACS系统源码(三维重建技术)
      支持CT、彩色超声、内窥镜检查的医院影像PACS系统源码(三维重建技术)
      26 0
      |
      5月前
      |
      存储 算法 搜索推荐
      pacs系统源码,医学图像信息的快速采集、传输、存储、显示和后处理
      PACS系统以实现医学影像数字化存储、诊断为核心任务,从医学影像设备(如CT、CR、DR、MR、DSA、RF等)获取影像,集中存储、综合管理医学影像及病人相关信息,建立数字化工作流程。 系统可实现检查预约、病人信息登记、计算机阅片、电子报告书写、胶片打印、数据备份等一系列满足影像科室日常工作的功能,并且由于影像数字化存储,用户可利用影像处理与测量技术辅助诊断、方便快捷地查找资料或利用网络将资料传输至临床科室
      |
      8月前
      |
      XML 存储 编解码
      GB/T28181-2022图像抓拍规范解读及技术实现
      源设备向目标设备发送图像抓拍配置命令,携带传输路径、会话ID等信息。目标设备完成图像传输后,发送图像抓拍传输完成通知命令,采用IETF RFC 3428中的MESSAGE方法实现,命令流程见9.14.2。图像文件命名规则宜采用“设备编码(20位)、图像编码(2位)、时间编码(17位)、序列码(2位)”的形式,抓拍图像文件命名规则应符合表4的要求。图像格式宜使用JPEG,图像分辨率宜采用与主码流相同的分辨率。
      365 0
      |
      8月前
      |
      编解码 监控 安全
      GB/T28181-2022针对H.265编码细化及技术实现
      新版国家标准GB/T28181-2022《公共安全视频监控联网系统信息传输、交换、控制技术要求》已于2022年12月30日发布,并将于2023年7月1日正式实施。
      |
      机器学习/深度学习 人工智能 自然语言处理
      USB:首个将视觉、语言和音频分类任务进行统一的半监督分类学习基准
      USB:首个将视觉、语言和音频分类任务进行统一的半监督分类学习基准
      102 0
      USB:首个将视觉、语言和音频分类任务进行统一的半监督分类学习基准
      |
      存储 自然语言处理 数据安全/隐私保护
      医院影像图像科室工作站PACS系统源码 DICOM 三维图像后处理与重建
      开放式体系结构,完全符合DICOM3.0标准,提供HL7标准接口,可实现与提供相应标准接口的HIS系统以及其他医学信息系统间的数据通信。 ·全面PACS/RIS,实现对不同设备、不同图像信息的处理。 ·多种临床工具包,可对图像进行多种增强处理、测量、标注,充分发挥电子胶片的特点。 ·支持WORKLIST功能,自动化工作流程。 ·有效解决大容量图像存储问题,支持多种存储方式和多种备份方式。 ·报告单有多种模式及自定义样式。 ·集成三维影像后处理功能。
      207 0
      |
      存储 编解码 搜索推荐
      PACS系统源码(医学图像存储与传输)支持三维重建与还原
      影像阅片 影像阅片是PACS最核心的部分,主要用来给医生提供调阅影像和影像处理,基础功能一般厂商都有,比如序列、旋转、放大缩小、标注、窗宽调整、四角信息设置、定位线、比例尺、测量、裁剪、伪彩等等,三维重建是一个亮点功能,很多厂商目前由于技术瓶颈尚未实现。这套PACS系统源码是带三维重建和还原的,是符合市场需求的PACS系统。
      177 0
      PACS系统源码(医学图像存储与传输)支持三维重建与还原