Android平台GB28181历史视音频文件检索规范探讨及技术实现

技术背景

我们在做Android平台GB28181设备接入侧模块的时候,特别是执法记录仪或类似场景,系统除了对常规的录像有要求,还需要能和GB28181平台侧交互,比如实现设备侧视音频文件检索、下载或回放。本文假定记录仪或相关设备已经完成录像,主要来探讨下设备视音频文件检索相关。

规范解读

先回顾下GB/T28181-2016视音频文件检索基本要求:

文件检索主要用区域、设备、录像时间段、录像地点、录像内容为条件进行查询,用 Message 消息发送检索请求和返回查询结果,传送结果的 Message 消息可以发送多条,应支持附录 N 多响应消息传输的要求。文件检索请求和应答命令采用 MANSCDP 协议格式定义。

命令流程:

信令流程描述如下:

  1. 目录检索方向目录拥有方发送目录查询请求 Message 消息,消息体中包含视音频文件检索条件;
  2. 目录拥有方向目录检索方发送 200 OK,无消息体;
  3. 目录拥有方向目录检索方发送查询结果,消息体中含文件目录,当一条 Message 消息无法传送完所有查询结果时,采用多条消息传送;
  4. 目录检索方向目录拥有方发送 200 OK,无消息体。

无查询结果的示例如下:

<?xml version="1.0" encoding="GB2312"?>
<Query><CmdType>RecordInfo</CmdType><SN>405331641</SN><DeviceID>34020000001380000001</DeviceID><StartTime>2023-09-04T00:00:00</StartTime><EndTime>2023-09-04T06:00:00</EndTime><Type>all</Type>
</Query>

没查到录像,那么设备侧回复如下,没有查询到文件的话,<SumNum>元素内容填充"0",  且不携带<RecordList>元素:

<?xml version="1.0" encoding="GB2312"?>
<Response>
<CmdType>RecordInfo</CmdType>
<SN>405331641</SN>
<DeviceID>34020000001380000001</DeviceID>
<Name>DaniuSDK</Name>
<SumNum>0</SumNum>
</Response>

有查询结果:

<Query><CmdType>RecordInfo</CmdType><SN>68331900</SN><DeviceID>34020000001380000001</DeviceID><StartTime>2023-09-04T06:00:00</StartTime><EndTime>2023-09-04T12:00:00</EndTime><Type>all</Type>
</Query>

设备侧回复如下:

<Response>
<CmdType>RecordInfo</CmdType>
<SN>68331900</SN>
<DeviceID>34020000001380000001</DeviceID>
<Name>DaniuSDK</Name>
<SumNum>6</SumNum>
<RecordList Num="3">
<Item>
<DeviceID>34020000001380000001</DeviceID>
<Name>DaniuSDK</Name>
<StartTime>2023-09-04T10:11:56</StartTime>
<EndTime>2023-09-04T10:12:58</EndTime>
<Secrecy>0</Secrecy>
</Item>
<Item>
<DeviceID>34020000001380000001</DeviceID>
<Name>DaniuSDK</Name>
<StartTime>2023-09-04T10:13:07</StartTime>
<EndTime>2023-09-04T10:15:33</EndTime>
<Secrecy>0</Secrecy>
</Item>
<Item>
<DeviceID>34020000001380000001</DeviceID>
<Name>DaniuSDK</Name>
<StartTime>2023-09-04T10:15:37</StartTime>
<EndTime>2023-09-04T10:16:32</EndTime>
<Secrecy>0</Secrecy>
</Item>
</RecordList>
</Response>

需要注意的是,会话外的SIP MESSAGE请求大小不能超过1300个字节。

技术实现

以大牛直播SDK的Android平台GB28181设备接入侧为例,设计接口逻辑如下:

package com.gb.ntsignalling;public interface GBSIPAgent {void addListener(GBSIPAgentListener listener);void addPlayListener(GBSIPAgentPlayListener playListener);void removePlayListener(GBSIPAgentPlayListener playListener);void addDownloadListener(GBSIPAgentDownloadListener downloadListener);void removeDownloadListener(GBSIPAgentDownloadListener removeListener);void addTalkListener(GBSIPAgentTalkListener talkListener);void removeTalkListener(GBSIPAgentTalkListener talkListener);void addAudioBroadcastListener(GBSIPAgentAudioBroadcastListener audioBroadcastListener);void addDeviceControlListener(GBSIPAgentDeviceControlListener deviceControlListener);void addQueryCommandListener(GBSIPAgentQueryCommandListener queryCommandListener);void addQueryRecordInfoListener(GBSIPAgentQueryRecordInfoListener queryRecordInfoListener);/*历史视音频文件检索应答*/boolean respondRecordInfoQueryCommand(String fromUserName, String fromUserNameAtDomain, String toUserName,String deviceName, RecordQueryInfo queryInfo,java.util.List<RecordFileInfo> recordList);
}

RecordQueryInfo设计如下:

//GBSIPAgentQueryRecordInfoListener
//Author: daniusdk.compackage com.gb.ntsignalling;public interface GBSIPAgentQueryRecordInfoListener {void ntsOnQueryRecordInfoCommand(String fromUserName, String fromUserNameAtDomain,String toUserName,RecordQueryInfo recordQueryInfo);
}package com.gb.ntsignalling;
public interface RecordQueryInfo {/**命令序列号(必选)*/String getSN();/** 目录设备/视频监控联网系统/区域编码(必选)*/String getDeviceID();/** 录像起始时间(必选)*/String getStartTime();/** 录像终止时间(必选)*/String getEndTime();/** 文件路径名 (可选)*/String getFilePath();/** 录像地址(可选 支持不完全查询)*/String getAddress();/** 保密属性(可选)缺省为0;0:不涉密,1:涉密*/String getSecrecy();/** 录像产生类型(可选)time或alarm 或 manual或all*/String getType();/** 录像触发者ID(可选)*/String getRecorderID();/**录像模糊查询属性(可选)缺省为0;0:不进行模糊查询,此时根据 SIP 消息中 To头域*URI中的ID值确定查询录像位置,若ID值为本域系统ID 则进行中心历史记录检索,若为前*端设备ID则进行前端设备历史记录检索;1:进行模糊查询,此时设备所在域应同时进行中心*检索和前端检索并将结果统一返回.*/String getIndistinctQuery();
}

RecordFileInfo设计如下:

//RecordFileInfo.java
//Author: daniusdk.compackage com.gb.ntsignalling;public class RecordFileInfo {/* 设备/区域编码(必选) */private String mDeviceID;/* 设备/区域名称(必选) */private String mName;/*文件路径名 (可选)*/private String mFilePath;/*录像地址(可选)*/private String mAddress;/*录像开始时间(可选)*/private String mStartTime;/*录像结束时间(可选)*/private String mEndTime;/*保密属性(必选)缺省为0;0:不涉密,1:涉密*/private String mSecrecy = "0";/*录像产生类型(可选)time或alarm 或 manual*/private String mType;/*录像触发者ID(可选)*/private String mRecorderID;/*录像文件大小,单位:Byte(可选)*/private String mFileSize;public RecordFileInfo() { }public RecordFileInfo(String deviceID) {this.setDeviceID(deviceID);}public RecordFileInfo(String deviceID, String name) {this.setDeviceID(deviceID);this.setName(name);}public String getDeviceID() {return mDeviceID;}public void setDeviceID(String deviceID) {this.mDeviceID = deviceID;}public String getName() {return mName;}public void setName(String name) {this.mName = name;}public String getFilePath() {return mFilePath;}public void setFilePath(String filePath) {this.mFilePath = filePath;}public String getAddress() {return mAddress;}public void setAddress(String address) {this.mAddress = address;}public String getStartTime() {return mStartTime;}public void setStartTime(String startTime) {this.mStartTime = startTime;}public String getEndTime() {return mEndTime;}public void setEndTime(String endTime) {this.mEndTime = endTime;}public String getSecrecy() {return mSecrecy;}public void setSecrecy(String secrecy) {this.mSecrecy = secrecy;}public String getType() {return mType;}public void setType(String type) {this.mType = type;}public String getRecorderID() {return mRecorderID;}public void setRecorderID(String recorderID) {this.mRecorderID = recorderID;}public String getFileSize() {return mFileSize;}public void setFileSize(String fileSize) {this.mFileSize = fileSize;}
}

调用逻辑如下:

package com.mydemo;import com.gb.ntsignalling.GBSIPAgentQueryRecordInfoListener;public class AndroidG8181DemoImpl implements GBSIPAgentQueryRecordInfoListener {private static class QueryRecordInfoTask extends RecordExecutorService.CancelableTask {@Overridepublic void run() {RecordBaseQuery base_query = new RecordBaseQuery(get_canceler(), rec_dir_);java.util.Date start_time_lower =  base_query.parser_xml_date_time(record_query_info_.getStartTime());java.util.Date start_time_upper = base_query.parser_xml_date_time(record_query_info_.getEndTime());if (null == start_time_lower || null == start_time_upper) {Log.e(TAG, "start_time_lower:" + start_time_lower + " or start_time_upper:" + start_time_upper + " is null");return;}base_query.set_start_time_lower(start_time_lower);base_query.set_start_time_upper(start_time_upper);List<RecordFileDescription> file_list =  base_query.execute();if (is_cancel())return;file_list =  base_query.sort_by_start_time_asc(file_list);if (is_cancel())return;List<com.gb.ntsignalling.RecordFileInfo> list = base_query.to_record_file_info_list(file_list, record_query_info_.getDeviceID(), null);if (is_cancel())return;if (file_list != null) {for (RecordFileDescription i : file_list)Log.i(TAG, i.toString(base_query.get_print_begin_date_time_format(), base_query.get_print_end_date_time_format()));}if (is_cancel() ||null == handler_ || null == sip_agent_)return;Handler handler = handler_.get();GBSIPAgent sip_agent = sip_agent_.get();if (null == handler || null == sip_agent)return;handler.post(new Runnable() {@Overridepublic void run() {if (null == this.sip_agent_)return;GBSIPAgent sip_agent = this.sip_agent_.get();if (null == sip_agent)return;if (this.canceler_ != null && this.canceler_.get())return;String device_name = null;sip_agent.respondRecordInfoQueryCommand(from_user_name_, from_user_name_at_domain_,to_user_name_, device_name, this.record_query_info_, this.record_list_);}private WeakReference<GBSIPAgent> sip_agent_;private AtomicBoolean canceler_;private String from_user_name_;private String from_user_name_at_domain_;private String to_user_name_;private RecordQueryInfo record_query_info_;private List<RecordFileInfo> record_list_;public Runnable set(GBSIPAgent sip_agent, AtomicBoolean canceler, String from_user_name, String from_user_name_at_domain, String to_user_name,RecordQueryInfo record_query_info, List<RecordFileInfo> record_list) {this.sip_agent_ = new WeakReference<>(sip_agent);this.canceler_ = canceler;this.from_user_name_ = from_user_name;this.from_user_name_at_domain_ = from_user_name_at_domain;this.to_user_name_ = to_user_name;this.record_query_info_ = record_query_info;this.record_list_ = record_list;return this;}}.set(sip_agent, get_canceler(), this.from_user_name_, this.from_user_name_at_domain_, this.to_user_name_,this.record_query_info_, list));}public QueryRecordInfoTask set(Handler handler, GBSIPAgent sip_agent, String rec_dir,String from_user_name, String from_user_name_at_domain,String to_user_name, RecordQueryInfo query_info) {this.handler_ = new WeakReference<>(handler);this.sip_agent_ = new WeakReference<>(sip_agent);this.rec_dir_ = rec_dir;this.from_user_name_ = from_user_name;this.from_user_name_at_domain_ = from_user_name_at_domain;this.to_user_name_ = to_user_name;this.record_query_info_ = query_info;return this;}private WeakReference<Handler> handler_;private WeakReference<GBSIPAgent> sip_agent_;private String rec_dir_;private String from_user_name_;private String from_user_name_at_domain_;private String to_user_name_;private RecordQueryInfo record_query_info_;}@Overridepublic void ntsOnQueryRecordInfoCommand(String fromUserName, String fromUserNameAtDomain, final String toUserName,RecordQueryInfo recordQueryInfo) {handler_.post(new Runnable() {@Overridepublic void run() {Log.i(TAG, "ntsOnQueryRecordInfoCommand from_user_name:" + from_user_name_ + ", to_user_name:" + to_user_name_+ ", sn:" + record_query_info_.getSN()  + ", device_id:" + record_query_info_.getDeviceID() +", start_time:" + record_query_info_.getStartTime() + ", end_time:" + record_query_info_.getEndTime());QueryRecordInfoTask query_task = new QueryRecordInfoTask();query_task.set(handler_, gb28181_agent_, recDir, from_user_name_, from_user_name_at_domain_, to_user_name_, record_query_info_);if (!record_executor_.submit(query_task))Log.e(TAG, "ntsOnQueryRecordInfoCommand call record_executor_.submit failed");}private String from_user_name_;private String from_user_name_at_domain_;private String to_user_name_;private RecordQueryInfo record_query_info_;public Runnable set(String from_user_name, String from_user_name_at_domain, String to_user_name, RecordQueryInfo record_query_info) {this.from_user_name_ = from_user_name;this.from_user_name_at_domain_ = from_user_name_at_domain;this.to_user_name_ = to_user_name;this.record_query_info_ = record_query_info;return this;}}.set(fromUserName, fromUserNameAtDomain, toUserName, recordQueryInfo));}
}

总结

GB28181设备接入侧视音频历史文件查询,看似不难,实际上需要处理的逻辑还很多,感兴趣的开发者,可以通过平台,和我私信探讨。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#循环定时上传数据,失败重传解决方案,数据库标识

有些时候我们需要定时的上传一些数据库的数据&#xff0c;在数据不完整的情况下可能上传失败&#xff0c;上传失败后我们需要定时在重新上传失败的数据&#xff0c;该怎么合理的制定解决方案呢&#xff1f;下面一起看一下&#xff1a; 当然本篇文章只是提供一个思路&#xff0…

使用boost::geometry::union_ 合并边界(内、外)- 方案一

使用boost::geometry::union_ 合并边界&#xff08;内、外&#xff09;&#xff1a;方案一 结合 boost::geometry::read_wkt() 函数 #include <iostream> #include <vector>#include <boost/geometry.hpp> #include <boost/geometry/geometries/point_x…

了解 glTF 2.0 格式

推荐&#xff1a;使用 NSDT场景编辑器快速搭建3D应用场景 介绍 glTF 代表 GL 传输格式。 glTF 是一种用于存储和加载 3D 场景的标准化文件格式&#xff0c;其基本目的是由 3D 创建工具轻松生成并被任何图形应用程序使用&#xff0c;无论使用何种 API&#xff0c;处理最少。 …

Grad-CAM,即梯度加权类激活映射 (Gradient-weighted Class Activation Mapping)

Grad-CAM&#xff0c;即梯度加权类激活映射 (Gradient-weighted Class Activation Mapping)&#xff0c;是一种用于解释卷积神经网络决策的方法。它通过可视化模型对于给定输入的关注区域来提供洞察。 原理: Grad-CAM的关键思想是将输出类别的梯度&#xff08;相对于特定卷积…

SeaTunnel扩展Transform插件,自定义转换插件

代码结构 在seatunnel-transforms-v2中新建数据包名&#xff0c;新建XXXTransform&#xff0c;XXXTransformConfig&#xff0c;XXXTransformFactory三个类 自定义转换插件功能说明 这是个适配KafkaSource的转换插件&#xff0c;接收到的原文格式为&#xff1a; {"path&…

本地缓存、Redis数据缓存策略

目录 需求看似简单&#xff0c;一取一传但是&#xff0c;又出现了一个新的问题&#xff0c;数据丢了。 一、缓存缓存有哪些分类&#xff1a; 二、分析一下本地缓存的优势三、本地缓存解决方案&#xff1f;1、基于Guava Cache实现本地缓存2、基于Caffeine实现本地缓存3、基于Enc…

猫头虎博主赠书二期:《Go黑帽子 渗透测试编程之道(安全技术经典译丛) 》

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

Apache实现weblogic集群配置

安装apache&#xff0c;安装相对稳定的版本。如果安装后测试能否正常启动&#xff0c;可以通过访问http://localhost/进行测试。安装Weblogic&#xff0c;参见文档将bea安装目录 weblogic81/server/bin 下的 mod_wl_20.so 文件copy到 apache安装目录下Apache2/modules/目录下A…

PCIe 配置空间:Command 寄存器

在 type 0 header 中,command 寄存器的位置如下图所示: 在 type 1 header 中,command 寄存器的位置如下图所示: Command 寄存器的结构如下图: 对于 PCIe,只有 Bit 0/1/2/6/8/10 是有效的,其他必须配置为 0 。 IO Space Enable 该位用于控制设别如何响应 I/O 空间的访…

OpenCV(二十):图像卷积

1.图像卷积原理 图像卷积是一种在图像上应用卷积核的操作。卷积核是一个小的窗口矩阵&#xff0c;它通过在图像上滑动并与图像的像素进行逐元素相乘&#xff0c;然后求和来计算新图像中每个像素的值。通过滑动卷积核并在图像上进行逐像素运算&#xff0c;可以实现一系列图像处理…

uniapp中UView中 u-form表单在v-for循环下如何进行表单校验

1、数据data格式 注&#xff1a;rule绑定的tableFromRule中要和表单tableFrom下面放置一个同名数组&#xff0c;确保u-form能找到 tableFrom: {tableData: [//数据详情列表]},tableFromRule: {//校验tableData: [//数据详情列表]},formRules:{localation:[{required: true,mes…

OSCS 安全周报第 58 期:VMware Aria Operations SSH 身份验证绕过漏洞 (CVE-2023-34039)

​ 本周安全态势综述 OSCS 社区共收录安全漏洞 3 个&#xff0c;公开漏洞值得关注的是 VMware Aria Operations SSH 身份验证绕过漏洞( CVE-2023-34039 )、Apache Airflow Spark Provider 反序列化漏洞( CVE-2023-40195 )。 针对 NPM 仓库&#xff0c;共监测到 324 个不同版本…