很多咨询者或者老客户都知道TSINGSEE青犀视频开发的EasyCVR平台是支持私有协议的,最开始支持HIKSDK,随后又开发了Ehome协议的接入,到现在,我们已经完成了大华SDK的接入支持。本文讲一下EasyCVR平台接入大华SDK设备进行录像回放的流程。
录像回放是指客户端远程播放设备中指定时间段内的录像文件,寻找所需要的视频信息。 回放功能支持多种回放操作,如正常播放、暂停、快放、慢放、拖动播放等。
- 完成SDK初始化流程。
- 初始化成功后,调用 CLIENT_LoginEx2 登录设备。
- 登录成功后,两次调用 CLIENT_SetDeviceMode,分别设置回放时的码流类型和回放时的录像文件类型。
- 登录成功后,调用 CLIENT_PlayBackByTimeEx 启动回放,hWnd 参数为 NULL,fDownLoadDataCallBack 参数为有效回调函数指针。
- SDK 收到回放数据后,通过设置的fDownLoadDataCallBack回调函数回调回放数据给用户,用户在回调函数中保存回放数据,离开回调函数后,再通过第三方库解码回放录像数据。
- 回放过程中 ,根据用户需求调用 CLIENT_SlowPlayBack 实现慢放,CLIENT_FasePlayBack 实现快放, CLIENT_NormalPlayBack 实现正常播放速
度,CLIENT_PausePlayBack 实现暂停/恢复,CLIENT_SeekPlayBack 实现拖
动播放,同时需调用第三方库相应的接口。 - 回放使用完毕后,调用 CLIENT_StopPlayBack 停止回放。
- 业务使用完后,调用 CLIENT_Logout 注销用户。
- SDK功能使用完后,调用 CLIENT_Cleanup 释放SDK资源。
大华SDK解码回放流程,如下图所示:
#include <stdio.h>
#include "dhnetsdk.h"
#pragma comment(lib , "dhnetsdk.lib")
static BOOL g_bNetSDKInitFlag = FALSE;
static LLONG g_lLoginHandle = 0L;
static LLONG g_lPlayHandle = 0L;
static char g_szDevIp[32] = "172.11.1.6";
static WORD g_nPort = 37777; // tcp 连接端口,需与期望登录设备页面 tcp 端口配置一
致
static char g_szUserName[64] = "admin";
static char g_szPasswd[64] = "admin";
//*************************************************************************
********
// 常用回调集合声明
// 设备断线回调函数
// 不建议在该回调函数中调用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接口
// 通过 CLIENT_PlayBackByTimeEx 设置该回调函数,当接收到设备端的回放数据时,SDK会调用该函数
void CALLBACK DownLoadPosCallBack(LLONG lPlayHandle, DWORD dwTotalSize, DWORD
dwDownLoadSize, LDWORD dwUser);
// 回放数据回调函数
// 不建议在该回调函数中调用SDK接口
// 当设置该回调函数时,若对应的hWnd参数为 NULL,参数返回,0:表示本次回调失败,下次回调会返回相同的数据,1:表示本次回调成功,下次回调会返回后续的数据
// 当设置该回调函数时,若对应的 hWnd 参数不为 NULL,则不管回调函数返回值为多少都认为
回调成功,下次回调会返回后续的数据
// 通过CLIENT_PlayBackByTimeEx设置该回调函数,当接收到设备端的回放数据时,SDK
40 41
会调用该函数。
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);
// 设置登录超时时间和尝试次数
// 此操作为可选操作
int nWaitTime = 5000; // 登录请求响应超时时间设置为5s
int nTryTimes = 3; // 登录时尝试建立链接3次
CLIENT_SetConnectTime(nWaitTime, nTryTimes);
// 设置更多网络参数,NET_PARAM 的 nWaittime,nConnectTryNum 成员与
CLIENT_SetConnectTime 接口设置的登录设备超时时间和尝试次数意义相同
// 此操作为可选操作
NET_PARAM stuNetParm = {0}; 42
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;
} 43
// 录像回放功能
// 设置回放时的码流类型
int nStreamType = 0; // 0-主辅码流,1-主码流,2-辅码流
CLIENT_SetDeviceMode(g_lLoginHandle, DH_RECORD_STREAM_TYPE,
&nStreamType);
// 设置回放时的录像文件类型
NET_RECORD_TYPE emFileType = NET_RECORD_TYPE_ALL; // 所有录像
CLIENT_SetDeviceMode(g_lLoginHandle, DH_RECORD_TYPE, &emFileType);
//开启录像回放
int nChannelID = 0; // 通道号
NET_TIME stuStartTime = {0};
stuStartTime.dwYear = 2015;
stuStartTime.dwMonth = 11;
stuStartTime.dwDay = 20;
NET_TIME stuStopTime = {0};
stuStopTime.dwYear = 2015;
stuStopTime.dwMonth = 11;
stuStopTime.dwDay = 21;
// 函数形参 hWnd 需为 NULL
// 函数形参 fDownLoadDataCallBack 需为 有效回调函数指针
g_lPlayHandle = CLIENT_PlayBackByTimeEx(g_lLoginHandle, nChannelID,
&stuStartTime, &stuStopTime, NULL, &DownLoadPosCallBack, NULL, &DataCallBack,
NULL);
if (g_lPlayHandle == 0)
{
printf("CLIENT_PlayBackByTimeEx: failed! Error code: %x.\n",
CLIENT_GetLastError());
}
// 用户可根据需求实现回放控制
// 由于是第三方库解码,用户在调用 SDK 的回放控制接口的同时,还需要调用第三方库相应的控制接口
// 由于这个是控制台 demo,无法将录像回放和回放控制同时展现给用户,这里提供示例
代码用于参考
// CLIENT_SlowPlayBack 实现慢放
/* 示例代码
if (FALSE == CLIENT_SlowPlayBack (g_lPlayHandle))
{ 44
printf("CLIENT_SlowPlayBack Failed, g_lPlayHandle[%x]!Last
Error[%x]\n" , g_lPlayHandle, CLIENT_GetLastError());
}
// 第三方库相应接口调用
*/
// CLIENT_FastPlayBack 实现快放
/* 示例代码
if (FALSE == CLIENT_FastPlayBack (g_lPlayHandle))
{
printf("CLIENT_FastPlayBack Failed, g_lPlayHandle[%x]!Last
Error[%x]\n" , g_lPlayHandle, CLIENT_GetLastError());
}
// 第三方库相应接口调用
*/
// CLIENT_NormalPlayBack 实现正常播放速度
/* 示例代码
if (FALSE == CLIENT_NormalPlayBack (g_lPlayHandle))
{
printf("CLIENT_NormalPlayBack Failed, g_lPlayHandle[%x]!Last
Error[%x]\n" , g_lPlayHandle, CLIENT_GetLastError());
}
// 第三方库相应接口调用
*/
// CLIENT_PausePlayBack 实现暂停/恢复
/* 示例代码
if (FALSE == CLIENT_PausePlayBack (g_lPlayHandle, TRUE))
{
printf("CLIENT_PausePlayBack Failed, g_lPlayHandle[%x]!Last
Error[%x]\n" , g_lPlayHandle, CLIENT_GetLastError());
}
// 第三方库相应接口调用
*/
// CLIENT_SeekPlayBack 实现拖动播放
/* 示例代码
int nOffsetSeconds = 2 * 60 * 60; // 拖动至 stuStartTime 后 2*60*60 秒的位
置开始回放 if (FALSE == CLIENT_SeekPlayBack (g_lPlayHandle, nOffsetSeconds, 0))
{
printf("CLIENT_SeekPlayBack Failed, g_lPlayHandle[%x]!Last
Error[%x]\n" , g_lPlayHandle, CLIENT_GetLastError());
}
// 第三方库相应接口调用
*/
}
void EndTest()
{
printf("input any key to quit!\n");
getchar();
// 关闭回放
if (0 != g_lPlayHandle)
{
if (FALSE == CLIENT_StopPlayBack(g_lPlayHandle))
{
printf("CLIENT_StopPlayBack Failed, g_lRealHandle[%x]!Last
Error[%x]\n" , g_lPlayHandle, CLIENT_GetLastError());
}
else
{
g_lPlayHandle = 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();
45 46
g_bNetSDKInitFlag = FALSE;
}
return;
}
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 nD VRPort,
LDWORD dwUser)
{
printf("Call HaveReConnect\n");
printf("lLoginID[0x%x]", lLoginID);
if (NULL != pchDVRIP)
{
printf("pchDVRIP[%s]\n", pchDVRIP);
}
printf("nDVRPort[%d]\n", nDVRPort); 47
printf("dwUser[%p]\n", dwUser);
printf("\n");
}
void CALLBACK DownLoadPosCallBack(LLONG lPlayHandle, DWORD dwTotalSize, DWORD
dwDownLoadSize, LDWORD dwUser)
{
// 若多个回放/下载使用相同的进度回调函数,则用户可通过 lPlayHandle 进行一一对应
if (lPlayHandle == g_lPlayHandle)
{
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_lPlayHandle)
{
BOOL bSuccess = TRUE;
// 以下打印在回放/下载时会造成刷屏现象,请注意
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; 48
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;
}