最近一个星期一直在研究海康威视的网络摄像机二次开发问题,纠结了很久。今天终于走通了一部分,记下来希望能够帮助到更多人。现在是用的 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 <iostream> #include "Windows.h" #include "HCNetSDK.h" #include "plaympeg4.h" #include <opencv2\opencv.hpp> #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; }