原标题:视频融合协议安防监控系统EasyCVR支持大华SDK接入设备录像下载流程说明

上一篇我们讲了EasyCVR视频协议融合平台已经支持了大华SDK协议设备的接入,并且分享了录像回放流程,目前其他私有协议仍在拓展和开发当中。本文我们再分享一下EasyCVR接入大华SDK协议设备后的录像下载流程。

录像下载,即用户通过 SDK 获取存储设备上存有的录像并保存到本地的过程。允许用户对当前所选通道的录像进行下载,并可将视频导出到本地硬盘或者外接设备U盘等。

录像下载方式包括以下:

按文件下载

即用户传入需要下载的录像文件信息,SDK可将指定的录像文件下载并保存到用户指定的文件中。同时,用户也可以提供一个回调函数的指针,SDK将指定的录像文件的数据通过回调函数回调给用户,由用户自行处理。

  1. 完成SDK初始化流程。
  2. 初始化成功后,调用 CLIENT_LoginEx2 登录设备。
  3. 调用 CLIENT_SetDeviceMode 设置录像查询时的录像码流类型,对应emType为DH_RECORD_STREAM_TYPE,建议设置为“0-主辅码流”,否则在少数设备上会无法获得结果。如果只需要主码流录像,可以在结果中滤除辅码流录像信息。具体请参见“附录 2 枚举定义”中的 EM_USEDEV_MODE 枚举说明。
  4. 可通过以下两种方式查询录像文件:
  5. 调用CLIENT_FindFile获取录像查询句柄,再循环调用CLIENT_FindNextFile 接口,逐次获取下一个录像文件信息,最后调用CLIENT_FindClose 关闭录像查询句柄。
  6. 调用CLIENT_QueryRecordFile一次性获取某时间段内的所有录像文件信息。
  7. 获取到录像文件信息后,调用 CLIENT_DownloadByRecordFileEx 开始录像文件下载,形参 sSavedFileName 和 fDownLoadDataCallBack 中至少有一个需为有效值。
  8. 下载过程中,根据用户需求调用CLIENT_GetDownloadPos查询录像下载进度。
  9. 录像下载完毕后,调用 CLIENT_StopDownload 停止下载。
  10. 业务使用完后,调用 CLIENT_Logout 退出设备。
  11. SDK功能使用完后,调用 CLIENT_Cleanup 释放SDK资源。

#include <windows.h>
#include <stdio.h>
#include <vector>
#include "dhnetsdk.h"
#pragma comment(lib , "dhnetsdk.lib")
static BOOL g_bNetSDKInitFlag = FALSE;
static LLONG g_lLoginHandle = 0L;
static LLONG g_lDownloadHandle = 0L;
static char g_szDevIp[32] = "172.11.1.30";
static WORD g_nPort = 37777; // tcp 连接端口,需与期望登录设备页面 tcp 端口配置一致
static char g_szUserName[64] = "admin";
static char g_szPasswd[64] = "admin";
static const int g_nMaxRecordFileCount = 5000;
//*************************************************************************
********
// 常用回调集合声明
// 设备断线回调函数
// 不建议在该回调函数中调用 SDK 接口
// 通过 CLIENT_Init 设置该回调函数,当设备出现断线时,SDK 会调用该函数
void CALLBACK DisConnectFunc(LLONG lLoginID, char *pchDVRIP, LONG nDVRPort,
DWORD dwUser);
// 断线重连成功回调函数
// 不建议在该回调函数中调用SDK接口
// 通过 CLIENT_SetAutoReconnect 设置该回调函数,当已断线的设备重连成功时,SDK会调用该函数
void CALLBACK HaveReConnect(LLONG lLoginID, char *pchDVRIP, LONG nDVRPort,
LDWORD dwUser);
// 回放/下载进度回调函数
// 不建议在该回调函数中调用SDK接口
// dwDownLoadSize: -1 时表示本次回放/下载结束,-2 表示写文件失败,其他值表示有效数据
// 通过 CLIENT_DownloadByRecordFileEx 设置该回调函数,当 SDK 收到回放/下载数据时,
54 55
SDK 会调用该函数
void CALLBACK DownLoadPosCallBack(LLONG lPlayHandle, DWORD dwTotalSize, DWORD
dwDownLoadSize, LDWORD dwUser);
// 回放/下载 数据回调函数
// 不建议在该回调函数中调用 SDK 接口
// 回放时:参数返回,0:表示本次回调失败,下次回调会返回相同的数据,1:表示本次回调
成功,下次回调会返回后续的数据
// 下载时:不管回调函数返回值为多少都认为回调成功,下次回调会返回后续的数据
// 通过 CLIENT_DownloadByRecordFileEx 设置该回调函数,当 SDK 收到回放/下载数据时,
SDK 会调用该函数
int CALLBACK DataCallBack(LLONG lRealHandle, DWORD dwDataType, BYTE *pBuffer,
DWORD dwBufSize, LDWORD dwUser);
//*************************************************************************
********
void InitTest()
{
// 初始化 SDK
g_bNetSDKInitFlag = CLIENT_Init(DisConnectFunc, 0);
if (FALSE == g_bNetSDKInitFlag)
{
printf("Initialize client SDK fail; \n");
return;
}
else
{
printf("Initialize client SDK done; \n");
}
// 获取 SDK 版本信息
// 此操作为可选操作
DWORD dwNetSdkVersion = CLIENT_GetSDKVersion();
printf("NetSDK version is [%d]\n", dwNetSdkVersion);
// 设置断线重连回调接口,设置过断线重连成功回调函数后,当设备出现断线情况,SDK
内部会自动进行重连操作
// 此操作为可选操作,但建议用户进行设置
CLIENT_SetAutoReconnect(&HaveReConnect, 0);
// 设置登录超时时间和尝试次数 56
// 此操作为可选操作
int nWaitTime = 5000; // 登录请求响应超时时间设置为 5s
int nTryTimes = 3; // 登录时尝试建立链接 3 次
CLIENT_SetConnectTime(nWaitTime, nTryTimes);
// 设置更多网络参数,NET_PARAM 的 nWaittime,nConnectTryNum 成员与
CLIENT_SetConnectTime 接口设置的登录设备超时时间和尝试次数意义相同
// 此操作为可选操作
NET_PARAM stuNetParm = {0};
stuNetParm.nConnectTime = 3000; // 登录时尝试建立链接的超时时间
CLIENT_SetNetworkParam(&stuNetParm);
NET_DEVICEINFO_Ex stDevInfoEx = {0};
int nError = 0;
while(0 == g_lLoginHandle)
{
// 登录设备
g_lLoginHandle = CLIENT_LoginEx2(g_szDevIp, g_nPort, g_szUserName,
g_szPasswd, EM_LOGIN_SPEC_CAP_TCP, NULL, &stDevInfoEx, &nError);
if (0 == g_lLoginHandle)
{
// 根据错误码,可以在 dhnetsdk.h 中找到相应的解释,此处打印的是 16 进制,
头文件中是十进制,其中的转换需注意
// 例如:
// #define NET_NOT_SUPPORTED_EC(23) // 当前 SDK 未支持该功能,对应的
错误码为 0x80000017, 23 对应的 16 进制为 0x17
printf("CLIENT_LoginEx2 %s[%d]Failed!Last Error[%x]\n" , g_szDevIp ,
g_nPort , CLIENT_GetLastError());
}
else
{
printf("CLIENT_LoginEx2 %s[%d] Success\n" , g_szDevIp , g_nPort);
}
// 用户初次登录设备,可能要初始化一些数据才能正常实现业务功能,所以建议登录后等待一小段时间,具体等待时间因设备而异。
Sleep(1000);
printf("\n");
}
}void RunTest()
{
if (FALSE == g_bNetSDKInitFlag)
{
return;
}
if (0 == g_lLoginHandle)
{
return;
}
// 录像文件查询
// 设置查询时的录像码流类型
int nStreamType = 0; // 0-主辅码流,1-主码流,2-辅码流
CLIENT_SetDeviceMode(g_lLoginHandle, DH_RECORD_STREAM_TYPE,
&nStreamType);
// 录像查询有两种实现方式:1,一次取完时间段内的所有录像文件;2,分次取时间段内
的所有录像文件。
// 此处通过第二种方案实现,第一种方案的实现可参考 CLIENT_QueryRecordFile 接口
说明。
int nChannelID = 0; // 通道号
NET_TIME stuStartTime = {0};
stuStartTime.dwYear = 2015;
stuStartTime.dwMonth = 9;
stuStartTime.dwDay = 20;
NET_TIME stuStopTime = {0};
stuStopTime.dwYear = 2015;
stuStopTime.dwMonth = 9;
stuStopTime.dwDay = 30;
int lFindHandle = CLIENT_FindFile(g_lLoginHandle, nChannelID, 0, NULL,
&stuStartTime, &stuStopTime, FALSE, 5000);
if (0 == lFindHandle)
{
printf("CLIENT_FindFile Failed!Last
Error[%x]\n",CLIENT_GetLastError());
return;
}
// demo 的示例代码,以最大支持 g_nMaxRecordFileCount 录像文件为例。
std::vector<NET_RECORDFILE_INFO> bufFileInfo(g_nMaxRecordFileCount);
for (int nFileIndex = 0; nFileIndex < g_nMaxRecordFileCount; ++nFileIndex)
57 {
int result = CLIENT_FindNextFile(lFindHandle,
&bufFileInfo[nFileIndex]);
if (0 == result)// 录像文件信息数据取完
{
break;
}
else if (1 != result)// 参数出错
{
printf("CLIENT_FindNextFile Failed!Last
Error[%x]\n",CLIENT_GetLastError());
break;
}
}
//停止查找
if(0 != lFindHandle)
{
CLIENT_FindClose(lFindHandle);
}
// 将查询过来的第一个文件设置为下载文件
NET_RECORDFILE_INFO stuNetFileInfo;
if (nFileIndex > 0)
{
memcpy(&stuNetFileInfo, (void *)&bufFileInfo[0],
sizeof(stuNetFileInfo));
}
else
{
printf("no record, return\n");
return;
}
// 录像文件下载
// 开启录像下载
// 函数形参 sSavedFileName 和 fDownLoadDataCallBack 至少有一个为有效值
// 实际应用中,一般根据需求选择直接保存至 sSavedFileName 或回调处理数据两者之
g_lDownloadHandle = CLIENT_DownloadByRecordFileEx(g_lLoginHandle,
&stuNetFileInfo, "test.dav", DownLoadPosCallBack, NULL, DataCallBack, NULL);
if (0 == g_lDownloadHandle)
{
printf("CLIENT_DownloadByRecordFileEx: failed! Error code: %x.\n",
58 59
CLIENT_GetLastError());
}
}
void EndTest()
{
printf("input any key to quit!\n");
getchar();
// 关闭下载,可在下载结束后调用,也可在下载中调用。
if (0 != g_lDownloadHandle)
{
if (FALSE == CLIENT_StopDownload(g_lDownloadHandle))
{
printf("CLIENT_StopDownload Failed, g_lDownloadHandle[%x]!Last
Error[%x]\n" , g_lDownloadHandle, CLIENT_GetLastError());
}
else
{
g_lDownloadHandle = 0;
}
}
// 退出设备
if (0 != g_lLoginHandle)
{
if(FALSE == CLIENT_Logout(g_lLoginHandle))
{
printf("CLIENT_Logout Failed!Last Error[%x]\n",
CLIENT_GetLastError());
}
else
{
g_lLoginHandle = 0;
}
}
// 清理初始化资源
if (TRUE == g_bNetSDKInitFlag)
{
CLIENT_Cleanup();
g_bNetSDKInitFlag = FALSE;
}
return;
} 60
int main()
{
InitTest();
RunTest();
EndTest();
return 0;
}
//*************************************************************************
********
// 常用回调集合定义
void CALLBACK DisConnectFunc(LLONG lLoginID, char *pchDVRIP, LONG nDVRPort,
DWORD dwUser)
{
printf("Call DisConnectFunc\n");
printf("lLoginID[0x%x]", lLoginID);
if (NULL != pchDVRIP)
{
printf("pchDVRIP[%s]\n", pchDVRIP);
}
printf("nDVRPort[%d]\n", nDVRPort);
printf("dwUser[%p]\n", dwUser);
printf("\n");
}
void CALLBACK HaveReConnect(LLONG lLoginID, char *pchDVRIP, LONG nDVRPort,
LDWORD dwUser)
{
printf("Call HaveReConnect\n");
printf("lLoginID[0x%x]", lLoginID);
if (NULL != pchDVRIP)
{
printf("pchDVRIP[%s]\n", pchDVRIP);
}
printf("nDVRPort[%d]\n", nDVRPort);
printf("dwUser[%p]\n", dwUser);
printf("\n");
}
void CALLBACK DownLoadPosCallBack(LLONG lPlayHandle, DWORD dwTotalSize, DWORD
dwDownLoadSize, LDWORD dwUser)
{
// 若多个回放/下载使用相同的进度回调函数,则用户可通过 lPlayHandle 进行一一对 61
if (lPlayHandle == g_lDownloadHandle)
{
printf("lPlayHandle[%p]\n", lPlayHandle);
printf("dwTotalSize[%d]\n", dwTotalSize);
printf("dwDownLoadSize[%d]\n", dwDownLoadSize);
printf("dwUser[%p]\n", dwUser);
printf("\n");
}
}
int CALLBACK DataCallBack(LLONG lRealHandle, DWORD dwDataType, BYTE *pBuffer,
DWORD dwBufSize, LDWORD dwUser)
{
int nRet = 0;
printf("call DataCallBack\n");
// 若多个回放/下载使用相同的数据回调函数,则用户可通过 lRealHandle 进行一一对
if(lRealHandle == g_lDownloadHandle)
{
printf("lPlayHandle[%p]\n", lRealHandle);
printf("dwDataType[%d]\n", dwDataType);
printf("pBuffer[%p]\n", pBuffer);
printf("dwBufSize[%d]\n", dwBufSize);
printf("dwUser[%p]\n", dwUser);
printf("\n");
switch(dwDataType)
{
case 0:
//Original data
// 用户在此处保存码流数据,离开回调函数后再进行解码或转发等一系列处理
nRet = 1;
break;
case 1:
//Standard video data
break;
case 2:
//yuv data
break;
case 3:
//pcm audio data
break;
case 4:
//Original audio data
break;
default:
break;
}
}
return nRet;
}

相关文章