1
zhanghua
2024-09-26 c775c6953d9759e70f08acbfa8f6d7490aaae3d1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
package com.netsdk.demo.customize.historyVaultDemo;
 
import com.netsdk.lib.NativeString;
import com.netsdk.lib.NetSDKLib;
import com.netsdk.lib.ToolKits;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.ptr.IntByReference;
 
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.*;
 
import static com.netsdk.demo.customize.historyVaultDemo.HistoryVaultLogon.m_hLoginHandle;
import static com.netsdk.demo.customize.historyVaultDemo.HistoryVaultLogon.netsdk;
import static com.netsdk.lib.Utils.getOsPrefix;
 
public class HistoryVaultWithTemp {
 
    // 查找句柄
    protected static NetSDKLib.LLong m_FindHandle = null;
    // 文字编码
 
    //       private static String encode = "GBK";        // win
    //    private static String encode = "UTF-8";     // linux
    protected static String encode;
 
    // 一次查询的最大数量
    protected static int nMaxCount = 10;
 
    // 设置一次获取数据量的大小,不能太大,不单单是启动时的等待时间会很长,需要分配的内存也会大,一条数据占用1M
    protected static NetSDKLib.MEDIAFILE_FACERECOGNITION_INFO[] faceRecognitionInfos = new NetSDKLib.MEDIAFILE_FACERECOGNITION_INFO[nMaxCount];
 
    protected static int fileMemorySize;       // faceRecognitionInfos 占用的内存大小
 
    protected static Pointer filePointer;      // 给 faceRecognitionInfos 分配的指针
 
    static {
        String osPrefix = getOsPrefix();
        if (osPrefix.toLowerCase().startsWith("win32-amd64")) {
            encode = "GBK";
        } else if (osPrefix.toLowerCase().startsWith("linux-amd64")) {
            encode = "UTF-8";
        }
 
        for (int i = 0; i < faceRecognitionInfos.length; ++i) {
            faceRecognitionInfos[i] = new NetSDKLib.MEDIAFILE_FACERECOGNITION_INFO();
            faceRecognitionInfos[i].bUseCandidatesEx = 1;  // 启用扩展结构体
        }
 
        System.out.println("size of faceRecognitionInfos: " + faceRecognitionInfos[0].size() * nMaxCount);
 
        fileMemorySize = faceRecognitionInfos[0].size() * nMaxCount;
        // 这一步非常耗时,所以我把它写在静态里
        filePointer = new Memory(fileMemorySize);
        filePointer.clear(fileMemorySize);
        // 这一步也非常耗时,所以我也把它写在静态里
        ToolKits.SetStructArrToPointerData(faceRecognitionInfos, filePointer);
    }
 
    ///////////////////////////// 枚举字典 检查获取数据的时候用 /////////////////////////////////////
    // 性别
    private static final String[] faceSexStr = {"未知", "男", "女"};
    // 情绪类型 EM_DEV_EVENT_FACEDETECT_FEATURE_TYPE
    private static final String[] emoStr = {"未知", "戴眼镜", "微笑", "愤怒", "悲伤", "厌恶", "害怕", "惊讶", "正常", "大笑", "没戴眼镜", "高兴", "困惑", "尖叫", "戴太阳眼镜"};
    // 口罩状态 0: 未做识别 1: 不戴口罩 2: 戴口罩
    private static final String[] maskStr = {"未知", "未查询", "不戴口罩", "戴口罩"};
    // 胡子状态 0: 未识别 1: 没胡子 2: 有胡子
    private static final String[] beardStr = {"未知", "未查询", "没胡子", "有胡子"};
 
    /**
     * 查询目标识别历史库数据
     * ********************************************************
     * 本例子基本给出了 IVSS 设备历史库的所有 sdk 支持的查询条件
     * 【包括】:
     * 查询数据类型:jpg 格式图片          【固定,不能修改】
     * 开始时间:2020-05-11 00:00:00    【必填】
     * 结束时间:2020-05-11 23:59:59    【必填】
     * 视频所在通道 :2                   【必填】
     * 相似度:80-100                    【选填】
     * 目标检测事件类型:1 所有            【选填】
     * 性别:1 男                        【选填】
     * 年龄区间:20-39                   【选填】
     * 眼镜:1 不戴                      【选填】
     * 口罩:2 戴口罩                    【选填】
     * 胡子:1 没胡子                    【选填】
     * 表情:惊讶                        【选填】
     * ********************************************************
     * 查询只支持对结果集的循环查询,无法跳转
     * ********************************************************
     * IVSS设备支持查询到数据包括:
     * 【抓拍图 事件数据】:
     * 抓拍图来源通道,抓拍图事件发生时间,抓拍人脸图路径,抓拍人脸图在屏幕的型心坐标
     * 【抓拍图 人脸数据 基本数据】:
     * 性别 年龄 表情 眼睛 嘴巴 口罩 胡子 魅力 眼镜
     * 【抓拍图 人脸数据 温度数据】:
     * 温度 是否超温 是否低的温度 温度单位
     * 【候选人的数据】:
     * 匹配的相似度 姓名 UID 所在注册库 ID 备注 候选人图片地址
     * <p>
     * 需要指出
     * 1. 年龄和相似度查询条件不能都不设置,否则在获取查询数量时设备会报错
     * 1. sdk 的眼镜类型只有 3 个枚举(未知,戴,不戴)其他类型,如 “黑框眼镜” 都不识别,会默认为 0
     * 2. 可见光图地址 和 热成像图地址 的获取 sdk 也还不支持
     */
    public void findFaceRecognitionFile() {
 
        /////////////////////////// 【人脸库AI历史库检索】 下发检索条件,获取检索句柄 ///////////////////////
        ///////////////////////////////////////////////////////////////////////////////////////////////
        // 选择查询类型->人脸历史库
        int type = NetSDKLib.EM_FILE_QUERY_TYPE.NET_FILE_QUERY_FACE;
 
        // <<<<<-----定义检索条件----->>>>>
 
        NetSDKLib.MEDIAFILE_FACERECOGNITION_PARAM findContent = new NetSDKLib.MEDIAFILE_FACERECOGNITION_PARAM();
 
        // 查询文件类型 jpg,这条必须写,且IVSS设备固定为1不可修改
        findContent.nFileType = 1;
 
        // 历史库开始时间->"StartTime"
        findContent.stStartTime = new NetSDKLib.NET_TIME() {{
            setTime(2020, 5, 11, 0, 0, 0);
        }};
        // 历史库结束时间->"EndTime"
        findContent.stEndTime = new NetSDKLib.NET_TIME() {{
            setTime(2020, 5, 11, 23, 59, 59);
        }};
 
        // AI历史库来源通道号
        findContent.nChannelId = 2;
 
        // 设置检测相似度区间 80 至 100
        findContent.bSimilaryRangeEnable = 1;      // 启用相似度检索
        findContent.nSimilaryRange[0] = 80;
        findContent.nSimilaryRange[1] = 100;
 
        // 目标检测事件类型 查询所有类型,NetSDKLib.EM_FACERECOGNITION_ALARM_TYPE
        findContent.nAlarmType = NetSDKLib.EM_FACERECOGNITION_ALARM_TYPE.NET_FACERECOGNITION_ALARM_TYPE_ALL;
 
        // 启用扩展信息检索
        findContent.abPersonInfoEx = 1;    // 如果要设置下面的搜索信息,这项必须要写,且不可修改
 
        findContent.stPersonInfoEx.bySex = 1;                            // 性别 0: 所有 1: 男 2: 女
 
        findContent.stPersonInfoEx.bAgeEnable = 1;                       // 启用年龄检索
        findContent.stPersonInfoEx.nAgeRange[0] = 20;                    // 年龄下区间
        findContent.stPersonInfoEx.nAgeRange[1] = 39;                    // 年龄上区间
 
        findContent.stPersonInfoEx.byGlasses = 0;                        // 是否戴眼镜 0:未知 1:不戴 2:戴
        findContent.stPersonInfoEx.emMask = 2;                           // 是否带口罩 0: 未知 1: 未识别【一般不用】 2: 没戴口罩 3:戴口罩
        findContent.stPersonInfoEx.emBeard = 2;                          // 是否有胡子 0: 未知 1: 未识别【一般不用】 2: 没胡子 3: 有胡子
 
        findContent.stPersonInfoEx.nEmotionValidNum = 1;                 // 人脸特征数组有效个数, 如果为 0 则表示查询所有表情
        // 查询一种表情,惊讶 EM_DEV_EVENT_FACEDETECT_FEATURE_TYPE
        findContent.stPersonInfoEx.emEmotions[0] = NetSDKLib.EM_DEV_EVENT_FACEDETECT_FEATURE_TYPE.EM_DEV_EVENT_FACEDETECT_FEATURE_TYPE_SURPRISE;
 
        // 特别指出,sdk的协议现在不支持设置测温检索条件
 
        // 参数写入内存
        findContent.write();
        // 调用 SDK FindFile(FaceRecognition) 接口,成功了会获取检索结果集的句柄 lFindHandle
        NetSDKLib.LLong lFindHandle = netsdk.CLIENT_FindFileEx(m_hLoginHandle, type, findContent.getPointer(), null, 2000);
        if (lFindHandle.longValue() == 0) {
            System.err.println("FindFile(FaceRecognition) Failed!" + ToolKits.getErrorCode());
            return;
        }
        System.out.println("检索指令下发成功,检索句柄:" + lFindHandle.longValue());
        findContent.read();
 
        ///////////////////////////  【人脸库AI历史库检索】 从结果集查询数据 ///////////////////////////////
        ///////////////////////////////////////////////////////////////////////////////////////////////
 
        IntByReference pCount = new IntByReference();
 
        boolean rt = netsdk.CLIENT_GetTotalFileCount(lFindHandle, pCount, null, 2000);
        if (!rt) {
            System.err.println("获取搜索句柄:" + lFindHandle + " 的搜索内容量失败。");
            return;
        }
        System.out.println("搜索句柄:" + lFindHandle + " 共获取到:" + pCount.getValue() + " 条数据。");
 
        // <<<<<-----循环查询----->>>>>
        int nCurCount = 0; // 记录查询的次数
        int nFindCount;    // 记录总计的查询数据条数
        ArrayList<LinkedHashMap<String, Object>> queryList = new ArrayList<LinkedHashMap<String, Object>>();
 
        while (true) {
            // lFindHandle(刚才获取的检索句柄) 必须一致,这是服务器判断哪一次检索的依据
            // filePointer 和 fileMemorySize 必须匹配,否则解析数据会失败;
            // 解析的时候 nMaxCount 告诉服务器要返回几个数据,实际可能没有那么多数据
            // 3000 是阻塞等待时间,这个值需要依据网络状况和数据量作调整
            // nRetCount 是实际从服务器返回的数据量
            int nRetCount = netsdk.CLIENT_FindNextFileEx(lFindHandle, nMaxCount, filePointer, fileMemorySize, null, 5000);
 
            // 从指针自定义获取数据,由于结构体非常大,为了速度考虑,我只能把需要的数据拷贝出指针地址
            // 下面列出的数据基本包括了所有 IVSS 设备支持返回的数据,部分返回是空的数据没有列出
            // 修改 GetPointerDataToStructArrFaceInfo 及它的附属方法一定要小心,偏移量一旦有误获取的数据会混乱。
            GetPointerDataToStructArrFaceInfo(filePointer, faceRecognitionInfos);
 
            if (nRetCount <= 0) break;
 
            System.out.println("从检索句柄:" + lFindHandle + " 查询成功,查询到:" + nRetCount + " 条数据");
 
            for (int i = 0; i < nRetCount; i++) {
 
                // 处理数据,如何转存数据依据实际情况调整,这里就保存起来然后打印一下
                LinkedHashMap<String, Object> queryMap = new LinkedHashMap<String, Object>();
 
                // <<<<<-----查询序列----->>>>>
                nFindCount = i + nCurCount * nMaxCount;
                queryMap.put("FindCount", nFindCount);
                queryMap.put("SearchCount", nCurCount + 1);
 
                // <<<<<-----抓拍图的事件数据----->>>>>
                // 抓拍图来源通道
                queryMap.put("Channel", faceRecognitionInfos[i].nChannelId);
                // 抓拍图事件发生时间
                queryMap.put("EventTime", faceRecognitionInfos[i].stTime.toStringTime());
                // 抓拍人脸图长度(这个数据其实没有用处,因为返回的是图片地址。但由于设备有返回这个数据所以这里列一下)
                queryMap.put("FacePicLength", faceRecognitionInfos[i].stObjectPic.dwFileLenth);
                // 抓拍人脸图路径
                queryMap.put("FacePicPath", new String(faceRecognitionInfos[i].stObjectPic.szFilePath).trim());
 
                // 尝试下载抓拍图
                DownloadIVSSRemoteFile((String) queryMap.get("FacePicPath"),
                        (Integer) queryMap.get("Channel"), (String) queryMap.get("EventTime"), "snap");
 
                // 抓拍人脸图 图片在屏幕的型心, 0-8191相对坐标
                int[] FacePicCenter = new int[2];
                FacePicCenter[0] = faceRecognitionInfos[i].stuFaceCenter.nx;
                FacePicCenter[1] = faceRecognitionInfos[i].stuFaceCenter.ny;
                queryMap.put("FacePicCenter", FacePicCenter);
 
                // <<<<<-----抓拍图的人脸数据 普通数据----->>>>>
 
                // 抓拍图 性别 0 "未知", 1 "男", 2 "女"
                queryMap.put("Sex", faceRecognitionInfos[i].stuFaceInfoObject.emSex);
                // 抓拍图 年龄
                queryMap.put("Age", faceRecognitionInfos[i].stuFaceInfoObject.nAge);
                // 抓拍图 表情 参考 NetSDKLib.EM_EMOTION_TYPE
                queryMap.put("Emotion", faceRecognitionInfos[i].stuFaceInfoObject.emEmotion);
                // 抓拍图 眼睛 参考 NetSDKLib.EM_EYE_STATE_TYPE
                queryMap.put("Eye", faceRecognitionInfos[i].stuFaceInfoObject.emEye);
                // 抓拍图 嘴巴 参考 NetSDKLib.EM_MOUTH_STATE_TYPE
                queryMap.put("Mouth", faceRecognitionInfos[i].stuFaceInfoObject.emMouth);
                // 抓拍图 口罩 参考 NetSDKLib.EM_MASK_STATE_TYPE
                queryMap.put("Mask", faceRecognitionInfos[i].stuFaceInfoObject.emMask);
                // 抓拍图 胡子 参考 NetSDKLib.EM_BEARD_STATE_TYPE
                queryMap.put("Beard", faceRecognitionInfos[i].stuFaceInfoObject.emBeard);
                // 抓拍图 魅力(0-100) 越高越有魅力
                queryMap.put("Attractive", faceRecognitionInfos[i].stuFaceInfoObject.nAttractive);
                // 抓拍图 眼镜 参考 0-未知 1-不戴 2-戴 其他值默认 0
                queryMap.put("Glasses", faceRecognitionInfos[i].stuFaceInfoObject.emGlasses);
 
                // <<<<<-----抓拍图的人脸数据 温度数据----->>>>>
 
                // 抓拍图 温度信息
                queryMap.put("MaxTemp", faceRecognitionInfos[i].stuFaceInfoObject.fMaxTemp);
                // 抓拍图 是否超温
                queryMap.put("IsOverTemp", faceRecognitionInfos[i].stuFaceInfoObject.nIsOverTemp);
                // 抓拍图 是否低的温度
                queryMap.put("IsUnderTemp", faceRecognitionInfos[i].stuFaceInfoObject.nIsUnderTemp);
                // 温度单位 参考 NetSDKLib.EM_TEMPERATURE_UNIT
                queryMap.put("TempUnit", faceRecognitionInfos[i].stuFaceInfoObject.emTempUnit);
 
                // <<<<<----- 候选人的数据 ----->>>>>
                queryMap.put("nCandidateNum", faceRecognitionInfos[i].nCandidateExNum); // 匹配到的候选人数量
 
                ArrayList<LinkedHashMap<String, Object>> candidates = new ArrayList<LinkedHashMap<String, Object>>();
                // 保存候选人信息
                // 除了以下列出的条目,其他的信息设备都没有解析,如果想要知道,可以根据获取的 szUID,用 findFaceRecognitionDB() 来查询人员信息。
                // 具体请参见 src/com/netsdk/demo/example/FaceRecognition 中的 findFaceRecognitionDB() 方法
                // 需要指出 findFaceRecognitionDB() 示例是根据 GroupId 来查询的,使用的时候,GroupId不填,根据 szUID 来查询
                for (int j = 0; j < faceRecognitionInfos[i].nCandidateExNum; j++) {
                    LinkedHashMap<String, Object> candidate = new LinkedHashMap<String, Object>();
 
                    // 候选人 匹配的相似度
                    candidate.put("candidateSimilarity", faceRecognitionInfos[i].stuCandidatesEx[j].bySimilarity);
                    // 候选人 姓名
                    try {
                        candidate.put("candidateName", new String(faceRecognitionInfos[i].stuCandidatesEx[j].stPersonInfo.szPersonName, encode).trim());
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                    // 候选人 UID
                    candidate.put("candidateUID", new String(faceRecognitionInfos[i].stuCandidatesEx[j].stPersonInfo.szUID).trim());
                    // 候选人 所在注册库
                    candidate.put("candidateGroup", new String(faceRecognitionInfos[i].stuCandidatesEx[j].stPersonInfo.szGroupName));
                    // 候选人 ID
                    candidate.put("candidateID", new String(faceRecognitionInfos[i].stuCandidatesEx[j].stPersonInfo.szID).trim());
                    // 候选人 备注
                    candidate.put("candidateComment", new String(faceRecognitionInfos[i].stuCandidatesEx[j].stPersonInfo.szComment).trim());
                    // 候选人 图片地址 可能有多张
                    ArrayList<String> candidatePicPath = new ArrayList<String>();
                    for (int k = 0; k < faceRecognitionInfos[i].stuCandidatesPic[j].nFileCount; k++) {
                        candidatePicPath.add(new String(faceRecognitionInfos[i].stuCandidatesPic[j].stFiles[k].szFilePath).trim());
 
                        // 尝试下载候选人图片
                        DownloadIVSSRemoteFile(candidatePicPath.get(k),
                                (Integer) queryMap.get("Channel"), (String) queryMap.get("EventTime"), "candidate" + "_" + String.valueOf(k));
                    }
                    candidate.put("candidatePicPath", candidatePicPath);
 
                    candidates.add(candidate);
                }
                queryMap.put("Candidate", candidates);
 
                queryList.add(queryMap);
            }
 
 
            // nRetCount < nMaxCount 说明已经没有后续数据了,退出循环
            if (nRetCount < nMaxCount) {
                break;
            } else {
                nCurCount++;
            }
        }
 
        ///////////////////////////  【人脸库AI历史库检索】 结束查询 ///////////////////////////////
        ///////////////////////////////////////////////////////////////////////////////////////////////
 
        boolean ret = netsdk.CLIENT_FindCloseEx(lFindHandle);
 
        if (!ret) {
            System.err.println("FindCloseEx failed!" + ToolKits.getErrorCode());
            return;
        }
        System.out.println("结束检索指令下发成功");
 
        // 打印出来看一看
        DisplayQueryData(queryList);
    }
 
 
    /**
     * 从指针地址获取结构体数据
     *
     * @param pNativeData     数据指针
     * @param pFaceInfoStuArr faceInfo结构体数组
     */
    public static void GetPointerDataToStructArrFaceInfo(Pointer pNativeData, Structure[] pFaceInfoStuArr) {
        long offset = 0;
        for (int i = 0; i < pFaceInfoStuArr.length; ++i) {
            GetPointerDataToStructFaceInfo(pNativeData, offset, pFaceInfoStuArr[i]);
            offset += pFaceInfoStuArr[i].size();
        }
    }
 
    /**
     * 从指针拷贝数据到 faceInfo 结构体
     *
     * @param pNativeData         数据指针
     * @param OffsetOfpNativeData 偏移量
     * @param pFaceInfoStu        faceInfo结构体
     */
    public static void GetPointerDataToStructFaceInfo(Pointer pNativeData, long OffsetOfpNativeData, Structure pFaceInfoStu) {
        Pointer pJavaMem = pFaceInfoStu.getPointer();
        pJavaMem.write(0, pNativeData.getByteArray(OffsetOfpNativeData, pFaceInfoStu.size()), 0,
                pFaceInfoStu.size());
        pFaceInfoStu.readField("nChannelId");
        pFaceInfoStu.readField("stTime");
        pFaceInfoStu.readField("stObjectPic");
        pFaceInfoStu.readField("stuFaceCenter");
        pFaceInfoStu.readField("stuFaceInfoObject");
        // 如果确信抓拍图的主结构体有其他字段可以获取数据,可以在这里添加
 
        int nCandidateNum = (Integer) pFaceInfoStu.readField("nCandidateExNum");
 
        long offsetCandidatePic = ((NetSDKLib.MEDIAFILE_FACERECOGNITION_INFO) pFaceInfoStu).fieldOffset("stuCandidatesPic");
        long offsetPicTotal = offsetCandidatePic + OffsetOfpNativeData;
        long candiPicSize = ((NetSDKLib.MEDIAFILE_FACERECOGNITION_INFO) pFaceInfoStu).stuCandidatesPic[0].size();
        for (int i = 0; i < nCandidateNum; i++) {
            GetPointerDataToStructCandidatesPic(pNativeData, offsetPicTotal, ((NetSDKLib.MEDIAFILE_FACERECOGNITION_INFO) pFaceInfoStu).stuCandidatesPic[i]);
            offsetPicTotal = offsetPicTotal + candiPicSize;
        }
 
        long offsetCandidateInfo = ((NetSDKLib.MEDIAFILE_FACERECOGNITION_INFO) pFaceInfoStu).fieldOffset("stuCandidatesEx");
        long offsetInfoTotal = offsetCandidateInfo + OffsetOfpNativeData;
        long candidateInfoSize = ((NetSDKLib.MEDIAFILE_FACERECOGNITION_INFO) pFaceInfoStu).stuCandidatesEx[0].size();
        for (int i = 0; i < nCandidateNum; i++) {
            GetPointerDataToStructCandidatesInfo(pNativeData, offsetInfoTotal, ((NetSDKLib.MEDIAFILE_FACERECOGNITION_INFO) pFaceInfoStu).stuCandidatesEx[i]);
            offsetInfoTotal = offsetInfoTotal + candidateInfoSize;
        }
    }
 
    /**
     * 从指针拷贝数据到 candidatePic 结构体
     *
     * @param pNativeData         数据指针
     * @param OffsetOfpNativeData 偏移量
     * @param pCandidatePic       candidatePic结构体
     */
    public static void GetPointerDataToStructCandidatesPic(Pointer pNativeData, long OffsetOfpNativeData, Structure pCandidatePic) {
        pCandidatePic.write();
        Pointer pJavaMem = pCandidatePic.getPointer();
        pJavaMem.write(0, pNativeData.getByteArray(OffsetOfpNativeData, pCandidatePic.size()), 0, pCandidatePic.size());
        int picFilesNum = (Integer) pCandidatePic.readField("nFileCount");
 
        long offsetPicInfo = ((NetSDKLib.NET_CANDIDAT_PIC_PATHS) pCandidatePic).fieldOffset("stFiles");
        long offsetTotal = OffsetOfpNativeData + offsetPicInfo;
        long stFileSize = ((NetSDKLib.NET_CANDIDAT_PIC_PATHS) pCandidatePic).stFiles[0].size();
        for (int i = 0; i < picFilesNum; i++) {
            ToolKits.GetPointerDataToStruct(pNativeData, offsetTotal, ((NetSDKLib.NET_CANDIDAT_PIC_PATHS) pCandidatePic).stFiles[i]);
            offsetTotal = offsetTotal + stFileSize;
        }
    }
 
    /**
     * 从指针拷贝数据到 candidateInfo 结构体
     *
     * @param pNativeData         数据指针
     * @param OffsetOfpNativeData 偏移量
     * @param pCandidateInfo      candidateInfo结构体
     */
    public static void GetPointerDataToStructCandidatesInfo(Pointer pNativeData, long OffsetOfpNativeData, Structure pCandidateInfo) {
        pCandidateInfo.write();
        Pointer pJavaMem = pCandidateInfo.getPointer();
        pJavaMem.write(0, pNativeData.getByteArray(OffsetOfpNativeData, pCandidateInfo.size()), 0, pCandidateInfo.size());
        pCandidateInfo.readField("bySimilarity");
        // 如果确信候选人主结构体 stuCandidatesEx 有其他数据可以获取,可以在这里添加
 
        long offsetPersonInfo = ((NetSDKLib.CANDIDATE_INFOEX) pCandidateInfo).fieldOffset("stPersonInfo");
        long offsetTotal = offsetPersonInfo + OffsetOfpNativeData;
        GetPointerDataToStructCandidatesPerson(pNativeData, offsetTotal, ((NetSDKLib.CANDIDATE_INFOEX) pCandidateInfo).stPersonInfo);
    }
 
    /**
     * 从指针拷贝数据到 CandidatesPerson 结构体
     *
     * @param pNativeData         数据指针
     * @param OffsetOfpNativeData 偏移量
     * @param pCandidatePerson    CandidatePerson结构体
     */
    public static void GetPointerDataToStructCandidatesPerson(Pointer pNativeData, long OffsetOfpNativeData, Structure pCandidatePerson) {
        pCandidatePerson.write();
        Pointer pJavaMem = pCandidatePerson.getPointer();
        pJavaMem.write(0, pNativeData.getByteArray(OffsetOfpNativeData, pCandidatePerson.size()), 0, pCandidatePerson.size());
 
        pCandidatePerson.readField("szPersonName");
        pCandidatePerson.readField("szID");
        pCandidatePerson.readField("szUID");
        pCandidatePerson.readField("szComment");
        pCandidatePerson.readField("szGroupName");
        // 如果确信候选人信息结构体 stuCandidatesEx.stPersonInfo 有其他数据可以获取,可以在这里添加
    }
 
    /**
     * 这里添加了一个打印控制台,json格式输出
     *
     * @param displayList 获取的数据
     */
    private static void DisplayQueryData(ArrayList<LinkedHashMap<String, Object>> displayList) {
 
        Scanner sc = new Scanner(System.in);
        System.out.println("共获取到" + displayList.size() + "条数据,你想看看几条数据?");
        int w = sc.nextInt();
 
        int n = w < displayList.size() ? w : displayList.size();
 
        for (int i = 0; i < n; i++) {
            // 为了不引入其它包,这里直接打印
            System.out.println(displayList.get(i));
        }
    }
 
    /**
     * 下载图片用,如果报 21 错误,说明 IVSS 上找不到图片,可以去网页上确认下是不是也获取不到
     */
    public void DownloadIVSSRemoteFile(String filePath, int channel, String timeStr, String comment) {
 
        String saveName = String.valueOf(channel)
                + "_"
                + timeStr.replace('/', '_').replace(' ', '_').replace(':', '_')
                + "_"
                + comment
                + ".jpg";
 
        NetSDKLib.NET_IN_DOWNLOAD_REMOTE_FILE pInParam = new NetSDKLib.NET_IN_DOWNLOAD_REMOTE_FILE();
        pInParam.pszFileName = new NativeString(filePath).getPointer();
 
        File path = new File("./historyPic/");
        if (!path.exists()) path.mkdir();
        pInParam.pszFileDst = new NativeString(path + "/" + saveName).getPointer();
        NetSDKLib.NET_OUT_DOWNLOAD_REMOTE_FILE pOutParam = new NetSDKLib.NET_OUT_DOWNLOAD_REMOTE_FILE();
        if (!netsdk.CLIENT_DownloadRemoteFile(m_hLoginHandle, pInParam, pOutParam, 3000)) {
            System.err.printf("CLIENT_DownloadRemoteFile failed, ErrCode=%s\n", ToolKits.getErrorCode());
        } else {
            System.out.println("CLIENT_DownloadRemoteFile success");
        }
    }
 
    /************************************ 简易Demo控制台 *****************************************
     * ******************************************************************************************
     * ******************************************************************************************
     */
    public static void main(String[] args) {
 
 
        // 登陆初始化
        HistoryVaultLogon.init(HistoryVaultLogon.DisConnectCallBack.getInstance(),
                HistoryVaultLogon.HaveReConnectCallBack.getInstance());
 
        // 设备登陆,如果不支持高安全,请使用普通的TCP登陆函数 login(), Demo中所有涉及句柄 m_hLoginHandle 也要相应的换掉
        HistoryVaultLogon.LoginWithHighLevel();
 
        HistoryVaultWithTemp demo = new HistoryVaultWithTemp();
 
        //********************简易控制台菜单********************************
        Scanner sc = new Scanner(System.in);
        System.out.println("00 : 退出,\n" +
                "11 : 运行测试例子");
 
        command:
        while (true) {
            String input = sc.next();
 
            if ("00".equals(input)) {
                break command;
            } else if ("11".equals(input)) {
                demo.findFaceRecognitionFile();
            } else {
                System.out.println("No such command");
            }
        }
 
        // 退出登陆
        HistoryVaultLogon.logOut();
        // 清理资源并退出程序
        HistoryVaultLogon.cleanAndExit();
    }
}