GB/T28181-2022之图像抓拍规范解读和设计实现
技术背景
GB/T28181-2022相对2016版,对图像抓拍有了明确的界定,图像抓拍在视频监控行业非常重要, Android平台GB28181设备接入端,无需实时上传音视频实时数据的情况下,就可以抓图上传到指定的图像存储服务器上。
图像抓拍基本要求如下:
- 源设备向目标设备发送图像抓拍配置命令,需要携带传输路径、会话ID等信息。
- 目标设备完成图像传输后,发送图像抓拍传输完成通知命令,采用IETF RFC3428中的MESSAGE方法实现。
- 图像文件命名规则宜采用“设备编码(20位)、图像编码(2位)、时间编码(17位)、序列码(2位)”的形式
- 图像格式宜使用JPEG,图像分辨率宜采用与主码流相同的分辨率。
需要注意的是,MESSAGE消息头Content-type头域为Content-type:Application/MANSCDP+xml,采用XML封装。设备收到图像抓拍配置命令后,发送配置响应命令,响应命令中包含执行结果信息。
图像抓拍流程如下:
技术实现
大牛直播SDK的SmartGBD已经完成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、软编码速度、可变码率设置;
- 支持横屏、竖屏推流;
- 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*/package com.gb.ntsignalling;public interface GBSIPAgent {void setDeviceConfigListener(GBSIPAgentDeviceConfigListener deviceConfigListener);/** 通知图像抓拍传输完成*/boolean notifyUploadSnapShotFinished(String fromUserName, String fromUserNameAtDomain, String deviceID, String sessionID, java.util.List<String> snapShotList);}
Device配置Listener如下:
package com.gb.ntsignalling;public interface GBSIPAgentDeviceConfigListener {/** 图像抓拍配置*/void ntsOnDeviceSnapShotConfig(String from_user_name, String from_user_name_at_domain,String sn, String device_id, SnapShotConfig config,List<String> extra_info_list);
}
Snapshot配置接口如下:
public interface SnapShotConfig {int snap_num();int interval();String upload_url();String session_id();
}
图像抓拍JNI接口设计如下:
public class SmartPublisherJniV2 {/*** 截图接口, 支持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*/public native int CaptureImage(long handle, int compress_format, int quality, String file_name, String user_data_string);}
Device Snap Shot Listener 核心代码如下:
/** Author: daniusdk.com*/public class GBDeviceSnapShotListenerImpl implements GBSIPAgentDeviceControlListener {@Overridepublic void ntsOnDeviceSnapShotConfig(String from_user_name, final String from_user_name_at_domain,String sn, String device_id, final SnapShotConfig config,List<String> extra_info_list) {if (null == config)return;handler_.postDelayed(new Runnable() {@Overridepublic void run() {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_ = new SnapShotGBImpl(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_);}private String from_user_name_;private String from_user_name_at_domain_;private String sn_;private String device_id_;private SnapShotConfig snap_shot_config_;private List<String> extra_info_list_;public Runnable set(String from_user_name, String from_user_name_at_domain,String sn, String device_id, SnapShotConfig config,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;return this;}}.set(from_user_name, from_user_name_at_domain, sn, device_id, config, extra_info_list), 0);}
}public class SnapShotGBImpl extends SnapShotImpl {private List<SnapConfig> config_list_ = new LinkedList<>();public SnapShotGBImpl(String dir, Context context, android.os.Handler handler,SmartPublisherJniV2 lib_sdk, LibPublisherWrapper publisher) {super(dir, context, handler, lib_sdk, publisher);}public boolean add_config(GBSIPAgent agent, String from_user_name, String from_user_name_at_domain, String sn,String device_id, SnapShotConfig config, List<String> extra_info_list) {if (null == agent)return false;if (is_null_or_empty(device_id))return false;if (null == config)return false;if (config.snap_num() < 1)return false;if (config.interval() < 1)return false;if (is_null_or_empty(config.session_id()))return false;SnapConfig c = new SnapConfig(agent, from_user_name, from_user_name_at_domain, sn, device_id, config, extra_info_list);config_list_.add(c);return true;}public void on_captured_image(long result, String file_name, long file_date_time_ms, String user_data) {SnapConfig config = find_config(user_data);if (null == config) {super.on_captured_image(result, file_name, file_date_time_ms, user_data);return;}SnapItem item = 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);}public void on_uploaded(boolean is_ok, String file_name, String session_id, String gb_name) {SnapConfig config = find_config(session_id);if (null == config) {Log.w(TAG, "on_uploaded cannot find config, session_id:" + session_id + ", gb_name:" + gb_name);return;}SnapItem item = 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);}}@Overridepublic void on_stop() {this.config_list_.clear();shutdown(200, TimeUnit.MILLISECONDS);}private void process_upload() {android.os.Handler app_handler = os_handler();for(SnapConfig c : config_list_)c.upload_files(app_handler, this);}private void process_finished() {List<String> notified_files = null;Iterator<SnapConfig> iterator = config_list_.iterator();while(iterator.hasNext()) {SnapConfig c = iterator.next();if (!c.is_can_notify_server())continue;iterator.remove();if (null == notified_files)notified_files = new LinkedList<>();c.notify_server(notified_files);}// 暂时删除这些文件, 根据业务需求随时调整就好if(notified_files != null && !notified_files.isEmpty())execute(new DeleteFilesTask(notified_files));}private static class SnapItem {private int status_ = INITIALIZATION_STATUS;private final String device_id_;private final int sn_; // 序列码, 40~41private final String dir_;private String file_name_;}private static class SnapConfig {private WeakReference<GBSIPAgent> agent_;private final String from_user_name_;private final String from_user_name_at_domain_;private final String sn_;private final String device_id_;private final String session_id_;private final int snap_num_;private final String upload_url_;private final int interval_sec_;private final List<String> extra_info_list_;private ArrayList<SnapItem> items_;public final String session_id() {return this.session_id_;}public void upload_files(android.os.Handler os_handler, SnapShotGBImpl snap) {if (null == items_)return;for (SnapItem i : items_) {if (i.is_capture_completion_status()) {i.set_status(SnapItem.UPLOADING_STATUS);BaseUploadTask upload_task = new MyUploadTask(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");}}}}public void notify_server(List<String> notified_files) {ArrayList<String> snap_shot_list = new ArrayList(items_.size());for (SnapItem i : 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;GBSIPAgent agent = agent_.get();if (null == agent)return;agent.notifyUploadSnapShotFinished(from_user_name_, from_user_name_at_domain_, device_id_, this.session_id(), snap_shot_list);}}private static class DeleteFilesTask implements Runnable {private List<String> file_name_list_;public DeleteFilesTask(List<String> file_name_list) {this.file_name_list_ = file_name_list;}@Overridepublic void run() {if (null == file_name_list_)return;if (file_name_list_.isEmpty()) {file_name_list_ = null;return;}for (String i : file_name_list_) {try {File f = new File(i);if (!f.exists()||!f.isFile() )continue;if (f.delete())Log.i(TAG, "delete file:" + i);elseLog.w(TAG, "delete file failed, " + i);}catch(Exception e) {Log.e(TAG, "DeleteFilesTask.run Exception:", e);}}file_name_list_.clear();file_name_list_ = null;}}public static class BaseUploadTask extends CancellableTask {private final String upload_url_;private final String file_name_;private final String gb_snap_shot_file_id_;private final String session_id_;private final String gb_name_;private WeakReference<android.os.Handler> os_handler_;private WeakReference<SnapShotGBImpl> snap_;public BaseUploadTask(String upload_url, String file_name, String gb_snap_shot_file_id,String session_id, String gb_name, android.os.Handler os_handler, SnapShotGBImpl snap) {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_ = new WeakReference<>(os_handler);if (snap != null)this.snap_ = new WeakReference<>(snap);}protected final String upload_url() {return this.upload_url_;}protected final String file_name() {return this.file_name_;}protected final String gb_snap_shot_file_id() {return this.gb_snap_shot_file_id_;}protected final String session_id() {return this.session_id_;}protected final String gb_name() { return this.gb_name_; }protected final android.os.Handler os_handler() {if (os_handler_ != null)return os_handler_.get();return null;}protected final SnapShotGBImpl snap() {if (snap_ != null)return snap_.get();return null;}private static class ResultRunnable implements Runnable {private final boolean result_;private final String file_name_;private final String session_id_;private final String gb_name_;private WeakReference<SnapShotGBImpl> snap_;public ResultRunnable(boolean result, String file_name, String session_id,String gb_name, SnapShotGBImpl snap) {this.result_ = result;this.file_name_ = file_name;this.session_id_ = session_id;this.gb_name_ = gb_name;if (snap != null)this.snap_ = new WeakReference<>(snap);}@Overridepublic void run(){if (null == this.snap_)return;SnapShotGBImpl snap = this.snap_.get();if (null == snap)return;snap.on_uploaded(result_, file_name_, session_id_, gb_name_);}}protected void post_result(boolean is_ok) {android.os.Handler handler = os_handler();if (null == handler)return;SnapShotGBImpl gb_snap = snap();if (null == gb_snap)return;handler.post(new ResultRunnable(is_ok, file_name_,session_id_, gb_name_, gb_snap));}}
}
总结
以上是GB28181图像抓拍大概的流程和设计参考,权当抛砖引玉,Android终端除支持常规的音视频数据接入外,还可以支持移动设备位置(MobilePosition)订阅和通知、图像抓拍、语音广播和语音对讲、历史视音频下载和回放。感兴趣的开发者,可以单独跟我探讨。
相关文章:

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

阿赵UE学习笔记——10、Blender材质和绘制网格体
阿赵UE学习笔记目录 大家好,我是阿赵。 之前介绍了虚幻引擎的材质和材质实例。这次来介绍一个比较有趣的内置的Blender材质。 在用Unity的时候,我做过一个多通道混合地表贴图的效果,而要做过一个刷顶点颜色混合地表和水面的效果。…...

数据结构--串
本文为复习的草稿笔记,,,有点乱 1. 串的基本概念和基本操作 串是由零个或多个字符组成的有限序列 2. 串的存储结构 3.串的应用 模式匹配 BF算法(简单匹配算法 穷举法 算法思路:从子串的每一个字符开始依次与主串…...

RabbitMQ交换机(3)-Topic
1.Topic模式 RabbitMQ的Topic模式是一种基于主题的消息传递模式。它允许发送者向一个特定的主题(topic)发布消息,同时,订阅者也可以针对自己感兴趣的主题进行订阅。 在Topic模式中, 主题通过一个由单词和点号组成的字…...
前端密钥怎么存储,以及临时存储一些数据,如何存储才最安全?
前端密钥存储安全的方案: 1、使用浏览器提供的本地存储:现代浏览器提供了本地存储机制,例如 Web Storage(localStorage 和 sessionStorage)或 IndexedDB。可以将密钥存储在这些本地存储中,并使用浏览器提供…...

第16章_网络编程拓展练习(TCP编程,UDP编程)
文章目录 第16章_网络编程拓展练习TCP编程1、学生与老师交互2、查询单词3、拓展:查询单词4、图片上传5、拓展:图片上传6、多个客户端上传文件7、群聊 UDP编程8、群发消息 第16章_网络编程拓展练习 TCP编程 1、学生与老师交互 案例:客户端模…...

深入Docker5:安装nginx部署完整项目
目录 准备 为什么要使用nginx mysql容器构建 1.删除容器 2.创建文件夹 3.上传配置文件 4.命令构建mysql容器 5.进入mysql容器,授予root所有权限 6.在mysql中用命令运行sql文件 7.创建指定数据库shop 8.执行指定的sql文件 nginx安装与部署 1.拉取镜像 2…...
HBASE学习四:常用命令汇总梳理(包括数据库、zk、hdfs相关操作与配置)
1、服务状态 1、后台查询 hbase shell #进入hbase的shell页面,配置环境变量可直接执行。status #查看当前服务状态status detailed #查看当前详细服务信息,包括master的active和standby信息version 查看版本信息 2、页面查询 http://HMASTERip:16010 #查看master 状态 …...

Android平台RTSP|RTMP播放端实时快照保存JPG还是PNG?
JPG还是PNG? 实际上,在前几天的blog,我们有从压缩方式、图像质量、透明效果、可编辑性等各方面做过差异化的介绍。 压缩方式:JPG是一种有损压缩格式,通过丢弃图像数据来减小文件大小,因此可能会损失一些图…...
【人工智能】之深入了解嵌入模型中的 Token:NLP 中的语义之旅(1)
自然语言处理(NLP)领域的发展在很大程度上受到了嵌入模型的推动。嵌入模型通过将文本中的每个 token 转换为向量表示,为计算机理解语言提供了强大的工具。本文将深入研究嵌入模型中的 token,揭示它在 NLP 中的重要性以及在语义表示…...

UML-实现图(组件图和部署图)
实现图是从系统的层次来描述的,描述硬件的组成和布局,描述软件系统划分和功能实现。 UML-实现图(组件图和部署图) 一、组件图1.组件图的元素(1)组件(2)接口(3)…...

苹果Find My可查找添加32件物品,伦茨科技ST17H6x芯片加速产品赋能
苹果最近更新的支持文档证实,从 iOS 16 开始,"Find My"可查找添加物品从16件增加到32件,AirTag 和“查找”网络中的物品利用“查找”网络的强大功能来发挥作用,这个网络由数亿台加密的匿名 Apple 设备构成。“查找”网络…...

postman后端测试时invalid token报错+token失效报错解决方案
报错信息1{“msg”:“invalid token”,“code”:401} 没有添加postman的token信息 报错信息2{“msg”: “token失效,请重新登录”,“code”: 401} 写了token但是token信息写的是错的,会提示token失效 解决方案如下 仅写完后端的查询,但是前端还没写的时候,可…...

使用 mybatis-plus 的mybaits的一对多时, total和record的不匹配问题
应该是框架的问题,去官方仓库提了个issues,等回复 https://github.com/baomidou/mybatis-plus/issues/5923 回复来了: 背景 发现 record是两条,但是total显示3 使用resultMap一对多时,三条数据会变成两条࿰…...

SpringCloud之Nacos
一、微服务介绍 1. 什么是微服务 2014年,Martin Fowler(马丁福勒 ) 提出了微服务的概念,定义了微服务是由以单一应用程序构成的小服务,自己拥有自己的进程与轻量化处理,服务依业务功能设计,以全自动的方式部署,与其他服务使用 HTTP API 通信。同时服务会使用最小的规模…...

小封装高稳定性振荡器 Sg2520egn / sg2520vgn, sg2520ehn / sg2520vhn
描述 随着物联网和ADAS等5G应用的实施,数据流量不断增长,网络基础设施变得比以往任何时候都更加重要。IT供应商一直在快速建设数据中心,并且对安装在数据中心内部/内部的光模块有很大的需求。此应用需要具有“小”,“低抖动”和“…...

使用 Apache POI 更新/覆盖 特定的单元格
使用 Apache POI 更新特定的单元格 一. 需求二. 实现三. 效果 一. 需求 将以下表中第4行,第4列的单元格由“张宇”更新为“汤家凤”,并将更行后的结果写入新的Excel文件中; 二. 实现 使用Apache POI,可以精确定位到需要更改的单…...

Spring Boot整合MyBatis-Plus
引言 在现代软件开发中,我们经常需要处理大量的数据。为了有效地管理这些数据,我们需要使用一些强大的框架。其中,Spring Boot和MyBatis-Plus是两个非常流行的框架。Spring Boot是一个基于Spring的开源Java框架,可以用于创建独立…...
springboot项目之AOP角色权限的判断
引言 开发的项目中,可能遇到不同的角色,不同的角色有不通的权限定义。AOP切面是个很好的解决方案。 实践 1. 定义MerchRoles Retention(RetentionPolicy.RUNTIME) Target(ElementType.METHOD) public interface MerchRoles {} 2. 定义切点 public c…...
Twincat PLC 跳出循环
在TwinCAT PLC编程中,要跳出循环结构通常可以通过以下几种方式实现: 使用Break指令: 在TwinCAT 3的PLC编程环境中(IEC 61131-3标准),可以使用BREAK指令来立即终止最内层的循环。例如,在FOR或WH…...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
在 Spring Boot 项目里,MYSQL中json类型字段使用
前言: 因为程序特殊需求导致,需要mysql数据库存储json类型数据,因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...
深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏
一、引言 在深度学习中,我们训练出的神经网络往往非常庞大(比如像 ResNet、YOLOv8、Vision Transformer),虽然精度很高,但“太重”了,运行起来很慢,占用内存大,不适合部署到手机、摄…...

nnUNet V2修改网络——暴力替换网络为UNet++
更换前,要用nnUNet V2跑通所用数据集,证明nnUNet V2、数据集、运行环境等没有问题 阅读nnU-Net V2 的 U-Net结构,初步了解要修改的网络,知己知彼,修改起来才能游刃有余。 U-Net存在两个局限,一是网络的最佳深度因应用场景而异,这取决于任务的难度和可用于训练的标注数…...

uni-app学习笔记三十五--扩展组件的安装和使用
由于内置组件不能满足日常开发需要,uniapp官方也提供了众多的扩展组件供我们使用。由于不是内置组件,需要安装才能使用。 一、安装扩展插件 安装方法: 1.访问uniapp官方文档组件部分:组件使用的入门教程 | uni-app官网 点击左侧…...