先说明一下这么干的背景。因为项目需要,视频需要转成可以直接在浏览器上播放的流格式,或者直接在浏览器显示的格式。浏览器使用的是FIREFOX 或者CHROME 之类的浏览器。另外,还需要极低的视频延迟,最好少于1秒的延迟。一开始走了不少弯路,海康摄像头本身支持RTSP 流协议,但经测试,使用这个协议取流的话,本身就有大约0.5秒到1秒的延迟,如果再对流作进一步的处理的话,就会产生约2秒或以上的延迟。最后,只有使用海康SDK的私有协议来取流。经测试,SDK私有协议取流是几乎没有延迟的,但海康SDK取流只有C++,所以,就有了C++调用SDK取视频流,再封装接口给PYTHON输出到浏览器这个做法。
C++核心部分的代码:
int cam_midware_cc::InitCam(char* camId,char* userName,char* userPwd)
{
//—————————————
// 初始化
NET_DVR_Init();
//设置连接时间与重连时间
NET_DVR_SetConnectTime(2000, 1);
NET_DVR_SetReconnect(10000, true);
//—————————————
// 注册设备
NET_DVR_DEVICEINFO_V30 struDeviceInfo;
GUserID = NET_DVR_Login_V30((char*)camId, 8000, (char*)userName, (char*)userPwd, &struDeviceInfo);
if (GUserID < 0)
{
printf(“Login error, %d\n”, NET_DVR_GetLastError());
NET_DVR_Cleanup();
}
//—————————————
//设置异常消息回调函数
NET_DVR_SetExceptionCallBack_V30(0, NULL, G_ExceptionCallBack, NULL);
returnGUserID;
}
int cam_midware_cc::StartVideo(int userId)
{
LONG lRealPlayHandle;
NET_DVR_PREVIEWINFO struPlayInfo = { 0 };
struPlayInfo.hPlayWnd = 0; //需要SDK解码时句柄设为有效值,仅取流不解码时可设为空
struPlayInfo.lChannel = 1; //预览通道号
struPlayInfo.dwStreamType = 1; //0-主码流,1-子码流,2-码流3,3-码流4,以此类推
struPlayInfo.dwLinkMode = 0; //0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4-RTP/RTSP,5-RSTP/HTTP
lRealPlayHandle = NET_DVR_RealPlay_V40(userId, &struPlayInfo, G_RealDataCallBack, NULL);
if (lRealPlayHandle < 0)
{
printf(“NET_DVR_RealPlay_V40 error\n”);
printf(“%d\n”, NET_DVR_GetLastError());
NET_DVR_Logout(userId);
NET_DVR_Cleanup();
return -1;
}
return0;
}
//解码回调 视频为YUV数据(YV12),音频为PCM数据
void CALLBACK G_DecCBFun(int nPort, char * pBuf, int nSize, FRAME_INFO * pFrameInfo, void* nReserved1, int nReserved2)
{
if (G_gbHandling)
{
G_gbHandling–;
return;
}
long lFrameType = pFrameInfo->nType;
if (lFrameType == T_YV12)
{
Mat pImg(pFrameInfo->nHeight, pFrameInfo->nWidth, CV_8UC3);
Mat src(pFrameInfo->nHeight + pFrameInfo->nHeight / 2, pFrameInfo->nWidth, CV_8UC1, pBuf);
cvtColor(src, pImg, CV_YUV2BGR_YV12);
outMat=pImg;
G_video_index++;
}
G_gbHandling = 3;
}
///实时流回调
void CALLBACK G_RealDataCallBack(LONG lRealHandle, DWORD dwDataType, BYTE *pBuffer, DWORD dwBufSize, void *pUser)
{
switch (dwDataType)
{
caseNET_DVR_SYSHEAD: //系统头
if (!PlayM4_GetPort(&G_nPort)) //获取播放库未使用的通道号
{
break;
}
//m_iPort = lPort; //第一次回调的是系统头,将获取的播放库port号赋值给全局port,下次回调数据时即使用此port号播放
if (dwBufSize > 0)
{
if (!PlayM4_SetStreamOpenMode(G_nPort, STREAME_REALTIME)) //设置实时流播放模式
{
break;
}
if (!PlayM4_OpenStream(G_nPort, pBuffer, dwBufSize, 10 * 1024 * 1024)) //打开流接口
{
break;
}
if (!PlayM4_Play(G_nPort, NULL)) //播放开始
{
break;
}
if (!PlayM4_SetDecCallBack(G_nPort, G_DecCBFun))
{
break;
}
}
break;
case NET_DVR_STREAMDATA: //码流数据
if (dwBufSize > 0 && G_nPort != -1)
{
if (!PlayM4_InputData(G_nPort, pBuffer, dwBufSize))
{
cout<<“error”<<PlayM4_GetLastError(G_nPort) <<endl;
break;
}
}
break;
default: //其他数据
if (dwBufSize > 0 && G_nPort != -1)
{
if (!PlayM4_InputData(G_nPort, pBuffer, dwBufSize))
{
break;
}
}
break;
}
}
void CALLBACK G_ExceptionCallBack(DWORD dwType, LONG lUserID, LONG lHandle, void *pUser)
{
char tempbuf[256] = { 0 };
switch (dwType)
{
case EXCEPTION_RECONNECT: //预览时重连
printf(“———-reconnect——–%d\n”, time(NULL));
break;
default:
break;
}
}
Mat cam_midware_cc::getVideoFrame()
{
return outMat;
}
这里使用了opencv 用来,所,需要下载opencv-swig 用于把C++的代码封装成SO或者DLL库
opencv-swig下载地址:
swig下载地址:
swig 在linux 上需要编译,直接 make make install 就可以了
最后还需要编写一个用来让SWIG生成对应的中间代码的 .i 文件
我写的.i 文件如下:
%module cam_midware_cc
%include <opencv.i>
%cv_instantiate_all_defaults
%{
#include “cam_midware_cc.h”
%}
%include “cam_midware_cc.h”
SWIG 的文件弄好后使用SWIG 编译,我使用的是LINUX的方法,命令如下:
swig -c++ -python cam_medware_cc.i
然后再把SWIG 生成的 .cxx 文件连同自己编写的CPP 文件编译一下就可以生成需要的.SO 或 .DLL 文件。
PYTHON 调用的核心代码如下:
import cam_midware_cc
import cv2
import numpy as np
import time
camApi=cam_midware_cc.cam_midware_cc()
testStr=camApi.testLib()
testInitCam=camApi.InitCam(IP,USERNAME,PASSWORD)
print(testStr)
print(testInitCam)
if testInitCam>-1 :
testStartVideo=camApi.StartVideo(testInitCam)
print(testStartVideo)
time.sleep(5)
whileTrue:
cv_img=camApi.getVideoFrame()
img_cv2=cv2.blur(np.asarray(cv_img),(1,1))
cv2.imshow(‘aa’,img_cv2)
c= cv2.waitKey(30) & 0xff