背景:现有多个摄像头点位和一个Secure Center综合安防管理平台,需要做一个功能,当有车辆经过监控点位时抓取一定时间段内的视频存储在服务器上用作分析。本文主要讲解如何根据官方给的SDK(VideoSDK.dll)在Windows环境下抓取视频。
思路:阅读官方文档后,整理出大概思路
第一步:使用海康威视提供的官方OpenAPI 安全认证库 (JAVA),屏蔽安全认证的复杂逻辑,使我们更加简单的调用平台的各个接口。
第二步:阅读官方文档,综合安防平台提供了订阅过车事件的接口,订阅之后,当发生对应事件,就会自动发送消息到我们的接口,比较简单,不再赘述。
第三步:根据事件的时间调用官方的监控点回放取流URL接口,获取视频的回放流URL和文件大小。
在此步骤有几个需要注意的地方:
1.需要确认自己的录像是中心存储还是设备存储。入参传错了返回的取流url会为空
2.入参的时间格式需要处理。具体代码如下
/**
* 获取BISO8601时间格式
*
* @return
*/
public String getBISO8601(Date date) {
TimeZone tz = TimeZone.getTimeZone("Asia/Shanghai");
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
df.setTimeZone(tz);
String nowAsISO = df.format(date);
return nowAsISO;
}
第三步:下载官方的SDK并阅读开发文档
由于官方给的安防平台sdk对接demo是C++写的,本人也不会C++,所以只能根据文档一点一点来,根据官方文档可见下载录像的基本流程,图中橙色部分则是上文中提到过的对接安防平台并取得回放流。
1.由于需要调用库文件,所以我们需要使用JNA(Java Native Access )工具类,这里博主就遇到了第一个坑,不要使用maven中央仓库的jna包,会一直报找不到指定的模块,更换成在海康威视官网下载的sdk包里面的jna.jar就正常了
2.创建接口,指定dll文件所在位置,并根据文档中提供的接口,定义必须的方法。代码如下。
public interface VideoSDK extends StdCallLibrary {
//申明并指定dll位置
VideoSDK INSTANCE = (VideoSDK)Native.loadLibrary("D:\\dll\\bin\\VideoSDK", VideoSDK.class);
/**
* 初始化接口0 表示成功,-1 表示失败。接口返回失败请调用 Video_GetLastError 获取错误码,通过错误码判断出错原因。
* @return
*/
int Video_Init();
/**
* 反初始化
* @return
*/
int Video_Fini();
/**
* 获取错误码接口,请在调接口失败所在线程获取错误码。
* @return
*/
int Video_GetLastError();
/**
* 录像下载
* @param pszUrl
* @param pszFileName
* @param pvideo_download_req
* @return
*/
int Video_StartDownload(String pszUrl, String pszFileName, PVIDEO_DOWNLOAD_REQ pvideo_download_req);
/**
* 停止下载
* @param code
* @return
*/
int Video_StopDownload(Long code);
}
在这里博主遇到了第二个坑,由于之前想把videoSDK.dll文件集成到代码目录中,所以只复制了videoSDK.dll和videoSDK.ini两个文件,运行的时候一直报"videoSDK.dll找不到指定的模块",由于bin目录文件过多,我也不知道哪些有用哪些没有,之后直接把官方sdk下整个bin目录复制了过来就正常了。最后使用的绝对路径,指定的dll文件所在位置
3.由于Video_StartDownload这个方法除了url和fileName之外还有个特殊的结构体入参,这里我们需要根据文档中的结构自己定义一个
/**
* 下载录像入参结构体
*
* @author
* @version 2022-03-25
*/
@Data
public class PVIDEO_DOWNLOAD_REQ extends Structure {
/**
* 回调函数
*/
public DownLoadCallBack.DownloadCallbackInterface downloadCallback;
public long pUserData; // 用户数据
public long i64FileMaxSize; // 录像分包大小,单位:字节
public long i64RecordSize; // 录像总大小,请填充查询回放 URL 时查询到的各录像片段大小之和,单位:字节
public long i64StartTimeStamp; // 录像下载开始时间,请填充查询回放 URL 时的查询开始时间戳,单位:秒
public long i64EndTimeStamp; // 录像下载结束时间,请填充查询回放 URL 时的查询结束时间戳,单位:秒
public byte[] szReserve = new byte[64];
}
同时,这个结构体中还有一个回调函数,这个也是需要我们自己定义的。当下载开始后,就会不断的执行回调函数,我们在这里打印参数即可实时获取下载进度。
/**
* 回调函数
*/
public class DownLoadCallBack {
//api函数申明
public interface DownloadCallbackInterface extends StdCallLibrary.StdCallCallback {
public void pfnDownloadCallback(long i64DownloadHandle, float fPercent, int iMsg, long pUserData);
}
public static class DownloadCallback_Realize implements DownloadCallbackInterface {
private final Logger logger = LoggerFactory.getLogger(DownLoadCallBack.class);
static VideoSDK videoSDK = VideoSDK.INSTANCE;
@Override
public void pfnDownloadCallback(long i64DownloadHandle, float fPercent, int iMsg, long pUserData) {
//
// i64DownloadHandle:网络录像下载句柄
// fPercent:已下载录像进度
// iMsg:录像下载消息,0-开始录像下载 1-录像下载中 2-录像下载完成 3-录像下载即将分包 4-录像下载分包失败 5-录像下载
// 分包完成 6-录像下载时断流 30-转封装不支持 50-断点续传不支持 100-下载失败(内部错误)。当收到录像下载成功、下载
// 断流或下载失败消息(如:2、4、6、30、50、100 等消息类型),需要停止录像下载以释放资源,但禁止在回调函数中调用
// SDK 相关的接口
if (iMsg == 0) {
logger.info("下载状态:" + "开始录像下载");
System.out.println("下载状态:" + "开始录像下载");
}
if (iMsg == 1) {
logger.info("下载状态:" + "录像下载中");
}
if (iMsg == 2) {
logger.info("下载状态:" + "录像下载完成");
}
if (iMsg == 3) {
logger.info("下载状态:" + "录像下载即将分包");
}
if (iMsg == 4) {
logger.info("下载状态:" + "录像下载分包失败");
}
if (iMsg == 5) {
logger.info("下载状态:" + "录像下载分包完成");
}
if (iMsg == 6) {
logger.info("下载状态:" + "录像下载时断流");
}
if (iMsg == 7) {
logger.info("下载状态:" + "录像下载分包完成");
}
if (iMsg == 30) {
logger.info("下载状态:" + "转封装不支持");
}
if (iMsg == 50) {
logger.info("下载状态:" + "断点续传不支持");
}
if (iMsg == 100) {
logger.error("下载状态:" + "下载失败(内部错误)");
}
}
}
}
4.调用方法并开始下载
public class HikMonitorController {
static VideoSDK videoSDK = VideoSDK.INSTANCE;
public static DownLoadCallBack.DownloadCallback_Realize callback = new DownLoadCallBack.DownloadCallback_Realize();
public void getDownLaodMp4(String startTime,
String endTime, String number, Long recordSize, String url)
throws ParseException {
//海康请求过来的流url rtsp://后面多一个空格,会造成取流异常
url = url.replace(" ", "");
int year = DateUtil.thisYear();
int month = DateUtil.thisMonth();
int day = DateUtil.thisDayOfMonth();
String s = startTime.split(" ")[1].replace(":", "");
//文件夹
String path = "D:\\" + year + "\\" + month + "\\" + day + "\\";
PVIDEO_DOWNLOAD_REQ req = new PVIDEO_DOWNLOAD_REQ();
req.setPUserData(1);
Long start = getTimeStamp(startTime);
Long end = getTimeStamp(endTime);
req.setI64StartTimeStamp(start);
req.setI64EndTimeStamp(end);
req.setI64RecordSize(recordSize);
req.setDownloadCallback(callback);
//分包大小512m
req.setI64FileMaxSize(512 * 1024 * 1024);
//初始化
int i = videoSDK.Video_Init();
//开始下载
int i1 = videoSDK.Video_StartDownload(url, path + s + number + ".mp4", req);
if (i1 == -1) {
//获取错误码
int i2 = videoSDK.Video_GetLastError();
//结束
int i3 = videoSDK.Video_Fini();
}
}
/**
* 获取秒时间戳
* @param time
* @return
* @throws ParseException
*/
public Long getTimeStamp(String time) throws ParseException {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
java.util.Date date = df.parse(time);
Calendar cal = Calendar.getInstance();
cal.setTime(date);
long timestamp = cal.getTimeInMillis();
return timestamp / 1000;
}
}