最近一个星期一直在研究海康威视的网络摄像机二次开发问题,纠结了很久。今天终于走通了一部分,记下来希望能够帮助到更多人。现在是用的 VS2013 + opencv 进行测试。
首先推荐两篇对我帮助很大的博文:
http://blog.csdn.net/wanghuiqi2008/article/details/31410509
这篇很详细的讲解了二次开发的框架,如果充分理解了作者的代码,那基本就差不多了。记得将#include "PlayM4.h"改为#include "plaympeg4.h",海康的SDK更新了
http://blog.csdn.net/shangtao1989/article/details/50260661
这篇博文很重要的就是关于YV12转换到RGB的内容,我建议大家使用作者提供的第二方法去替代第一篇博文中YV12转RGB的方法。另外建议大家事先开辟用来转换的两个图像空间。这样在实时回调过程中能提高实时性。
我的代码放在文章末尾(非计算机专业学生),希望对大家有用,我在这先给出一些这段时间遇到的问题,以及解决办法,可能有些笨拙,在此抛砖引玉。
首先是配置的问题首先按照开发文档添加包含目录以及库目录,接着在链接器-输入-附加依赖项中添加HCNetSDK.lib;PlayCtrl.lib;ws2_32.lib;winmm.lib;GdiPlus.lib文件
接着将代码进行编译,这时可能会弹出缺少两个dll文件。注意:如果你是在debug模式下编译的,那就在生成的debug文件夹下添加相应文件。
截取一段开发文档中的原话:
1. 更新设备网络SDK时,SDK开发包【库文件】里的HCNetSDK.dll、HCCore.dll、PlayCtrl.dll、SuperRender.dll、AudioRender.dll、HCNetSDKCom文件夹等文件均要加载到程序里面,
【HCNetSDKCom文件夹】(包含里面的功能组件dll库文件)需要和HCNetSDK.dll、HCCore.dll一起加载,放在同一个目录下,且HCNetSDKCom文件夹名不能修改。
再次编译调试就能正常运行了。
接着你会发现播放有些卡顿,一般有5S延时,甚至运行一段时间后会打印出很多 PlayM4_InputData failed 接着会报 error happened出错。
这个原因在SDK文档中说: 回调函数中不能执行可能会占用时间较长的接口或操作,不建议调用该SDK(HCNetSDK.dll)本身的接口
个人认为就是说在解码回调函数void CALLBACK DecCBFun(*******)中不能运行耗时代码,不信你把imshow()和waitkey()注释掉就好了。
解决办法有很多:
1.可以尝试利用多线程在另一个线程中显示。
2.在realse模式下运行,记得编译后在realse文件夹下添加相关文件。(realse模式运行速度立马提升,可以达到实时性,并且CPU占用也大大减小)
3.登陆网络摄像机系统(网址就是自己改的IP),配置-视音频中降低分辨率(我的默认的是2560*1440,当我调到1280*720后在debug模式下也能满足实时性)
以上基本就是我遇到的问题,我也只是个小白,所以大家看后不要见笑。
最后给出自己的代码,非计算机专业,希望对大家有用。
#include
#include "Windows.h"
#include "HCNetSDK.h"
#include "plaympeg4.h"
#include
#include "cv.h"
#include "highgui.h"
using namespace std;
using namespace cv;
int iPicNum = 0;//Set channel NO.
LONG nPort = -1;
HWND hWnd = NULL;
//解码回调 视频为YUV数据(YV12),音频为PCM数据
void CALLBACK DecCBFun(long nPort, char * pBuf, long nSize, FRAME_INFO * pFrameInfo, long nReserved1, long nReserved2)
{
long lFrameType = pFrameInfo->nType;
if (lFrameType == T_YV12)
{
Mat dst(pFrameInfo->nHeight, pFrameInfo->nWidth, CV_8UC3);//这里nHeight为720,nWidth为1280,8UC3表示8bit uchar 无符号类型,3通道值
Mat src(pFrameInfo->nHeight + pFrameInfo->nHeight / 2, pFrameInfo->nWidth, CV_8UC1, (uchar*)pBuf);
cvtColor(src, dst, CV_YUV2BGR_YV12);
imshow("IPCamera", dst);
waitKey(10);
//此时是YV12格式的视频数据,保存在pBuf中,可以fwrite(pBuf,nSize,1,Videofile);
//fwrite(pBuf,nSize,1,fp);
}
/***************
else if (lFrameType ==T_AUDIO16)
{
//此时是音频数据,数据保存在pBuf中,可以fwrite(pBuf,nSize,1,Audiofile);
}
else
{
}
*******************/
}
///实时流回调
void CALLBACK fRealDataCallBack(LONG lRealHandle, DWORD dwDataType, BYTE *pBuffer, DWORD dwBufSize, void *pUser)
{
DWORD dRet = 0;
BOOL inData = FALSE;
switch (dwDataType)
{
case NET_DVR_SYSHEAD: //系统头
if (nPort >= 0)
{
break; //同一路码流不需要多次调用开流接口
}
if (!PlayM4_GetPort(&nPort)) //获取播放库未使用的通道号
{
break;
}
if (dwBufSize > 0)
{
if (!PlayM4_SetStreamOpenMode(nPort, STREAME_REALTIME)) //设置实时流播放模式
{
cout << "PlayM4_SetStreamOpenMode failed " << endl;
break;
}
if (!PlayM4_OpenStream(nPort, pBuffer, dwBufSize, 1024 * 1024)) //查询
{
cout << "PlayM4_OpenStream failed " << endl;
dRet = PlayM4_GetLastError(nPort);
break;
}
//设置解码回调函数 只解码不显示
if (!PlayM4_SetDecCallBack(nPort, DecCBFun)) //查询
{
dRet = PlayM4_GetLastError(nPort);
break;
}
//设置解码回调函数 解码且显示
//if (!PlayM4_SetDecCallBackEx(nPort,DecCBFun,NULL,NULL))
//{
// dRet=PlayM4_GetLastError(nPort);
// break;
//}
//打开视频解码
if (!PlayM4_Play(nPort, hWnd))
{
dRet = PlayM4_GetLastError(nPort);
break;
}
//打开音频解码, 需要码流是复合流
/*if (!PlayM4_PlaySound(nPort))
{
dRet = PlayM4_GetLastError(nPort);
break;
}*/
}
break;
case NET_DVR_STREAMDATA: //码流数据
inData = PlayM4_InputData(nPort, pBuffer, dwBufSize);
while (!inData)
{
Sleep(10);
inData = PlayM4_InputData(nPort, pBuffer, dwBufSize);
cout << "PlayM4_InputData failed 11111" << endl;
break;
}
break;
default:
inData = PlayM4_InputData(nPort, pBuffer, dwBufSize);
while (!inData)
{
Sleep(10);
inData = PlayM4_InputData(nPort, pBuffer, dwBufSize);
cout << "PlayM4_InputData failed 22222" << endl;
break;
}
break;
}
}
void CALLBACK g_ExceptionCallBack(DWORD dwType, LONG lUserID, LONG lHandle, void *pUser)
{
char tempbuf[256] = { 0 };
switch (dwType)
{
case EXCEPTION_RECONNECT: //预览时重连
cout << "----------reconnect--------" << endl;
break;
default:
break;
}
}
void main()
{
//---------------------------------------
// 初始化
NET_DVR_Init();
//设置连接时间与重连时间
NET_DVR_SetConnectTime(2000, 1);
NET_DVR_SetReconnect(10000, true);
//---------------------------------------
// 注册设备
LONG lUserID;
NET_DVR_USER_LOGIN_INFO struLoginInfo = { 0 };
NET_DVR_DEVICEINFO_V40 struDeviceInfo = { 0 };
strcpy((char *)struLoginInfo.sDeviceAddress, "192.168.x.x"); //设备 IP 地址
strcpy((char *)struLoginInfo.sUserName, "admin"); //设备登录用户名
strcpy((char *)struLoginInfo.sPassword, "mima123456"); //设备登录密码
struLoginInfo.wPort = 8000;
struLoginInfo.bUseAsynLogin = 0; //同步登录,登录接口返回成功即登录成功
lUserID = NET_DVR_Login_V40(&struLoginInfo, &struDeviceInfo);
if (lUserID < 0)
{
cout << "NET_DVR_Login_V40 failed, error code: " << NET_DVR_GetLastError() << endl;
NET_DVR_Cleanup();
return;
}
int iRet;
//获取通道 1 的压缩参数
DWORD dwReturnLen;
NET_DVR_COMPRESSIONCFG_V30 struParams = { 0 };
iRet = NET_DVR_GetDVRConfig(lUserID, NET_DVR_GET_COMPRESSCFG_V30, 1, &struParams, \
sizeof(NET_DVR_COMPRESSIONCFG_V30), &dwReturnLen);
if (!iRet)
{
printf("NET_DVR_GetDVRConfig NET_DVR_GET_COMPRESSCFG_V30 error.\n");
NET_DVR_Logout(lUserID);
NET_DVR_Cleanup();
return;
}
//设置通道 1 的压缩参数
struParams.struNormHighRecordPara.dwVideoBitrate = 0.5;
iRet = NET_DVR_SetDVRConfig(lUserID, NET_DVR_SET_COMPRESSCFG_V30, 1, \
&struParams, sizeof(NET_DVR_COMPRESSIONCFG_V30));
if (!iRet)
{
printf("NET_DVR_GetDVRConfig NET_DVR_SET_COMPRESSCFG_V30 error.\n");
NET_DVR_Logout(lUserID);
NET_DVR_Cleanup();
return;
}
//获取通道 1 的压缩参数
iRet = NET_DVR_GetDVRConfig(lUserID, NET_DVR_GET_COMPRESSCFG_V30, 1, \
&struParams, sizeof(NET_DVR_COMPRESSIONCFG_V30), &dwReturnLen);
if (!iRet)
{
printf("NET_DVR_GetDVRConfig NET_DVR_GET_COMPRESSCFG_V30 error.\n");
NET_DVR_Logout(lUserID);
NET_DVR_Cleanup();
return;
}
printf("Video Bitrate is %d\n", struParams.struNormHighRecordPara.dwVideoBitrate);
//---------------------------------------
//设置异常消息回调函数
NET_DVR_SetExceptionCallBack_V30(0, NULL, g_ExceptionCallBack, NULL);
NET_DVR_PREVIEWINFO StruPlayInfo = { 0 };
StruPlayInfo.hPlayWnd = NULL; //窗口为空,设备SDK不解码只取流
StruPlayInfo.lChannel = 1; //预览通道号
StruPlayInfo.dwStreamType = 0; //0-主流码,1-子流码,2-流码3,3-流码4,以此类推
StruPlayInfo.dwLinkMode = 0; //0-TCP方式,1-UDP方式,2-多播方式,3-RTP方式,4-RTP/RTSP,5-RSTP/HTTP
StruPlayInfo.bBlocked = 1; //0-非堵塞取流,1-堵塞取流
LONG lRealPlayHandle;
lRealPlayHandle = NET_DVR_RealPlay_V40(lUserID, &StruPlayInfo, fRealDataCallBack, NULL);
if (lRealPlayHandle<0)
{
cout << "NET_DVR_RealPlay_V40 failed! Error number: " << NET_DVR_GetLastError() << endl;
return;
}
cout << "The program is successful !!" << endl;
Sleep(-1);
//---------------------------------------
//关闭预览
if (!NET_DVR_StopRealPlay(lRealPlayHandle))
{
cout << "NET_DVR_StopRealPlay error! Error number: " << NET_DVR_GetLastError() << endl;
NET_DVR_Logout(lUserID);
NET_DVR_Cleanup();
return;
}
//注销用户
NET_DVR_Logout(lUserID);
NET_DVR_Cleanup();
return;
}