海康威视视频监控客户端开发实践

本文记录采用海康SDK二次开发视频监控客户端从0开始的学习知识点,供参考交流。海康视频监控SDK和示例程序可以从官网下载。


目录

SDK关键点:

1.预览控制 

单画面播放流程:

多画面播放流程:

多画面播放时停止单通道画面流程:

录像开始/停止/保存:

2.画面分割

对话框创建:

对话框选择:

双击全屏:

 

3.设备管理

右击设备树:

单击设备树控件:

通道和通道号号相关说明:

直接登陆:

4.录像回放

按时间回放:

按文件名回放:

5.异常控制

6.报警

MFC客户端界面

1.预览控制

画面分割:

播放全屏:

Q&A:

设备和通道什么意思?

全局变量为什么定义在CPP中?

这段代码怎么理解typedef int (_stdcall *ChannelOpenProc)(long, HANDLE*);


 

SDK关键点:

1.预览控制 

NET_DVR_RealPlay_V40  实时预览;

NET_DVR_StopRealPlay  停止预览;

NET_DVR_SetRealDataCallBack  注册回调函数,捕获实时码流数据;

单画面播放流程:

创建通道树CreateDeviceTree--双击通道数,播放选中通道OnDblclkTreeChan(如果点击的是设备则跳出,如果点击的是具体通道号则调用双击播放函数)--双击播放DbPlayChannel--开始1路播放StartPlay

{

建立一个NET_DVR_CLIENTINFO预览参数结构体;

调用NET_DVR_RealPlay_V40 ;

获取播放返回状态;

设置标志位;

}

多画面播放流程:

采用对话框作为播放画面的窗口,在父类中保存当前使用的对话框序号,双击设备树播放选中通道DblPlayDevice(m_iCurDeviceIndex, m_iCurWndIndex); 取得当前正在播放的设备,如果设备正在播放则先关闭g_dlgOutput[i].StopPlay(),如果没有则开始播放StartPlayInsideDecode,关闭自身正在预览的通道,然后开始新通道的预览。

在多画面播放时,必须先选择确定的通道树,判断通道的播放状态,进行关闭或者显示,所以如果之前对设备所有通道进行播放,会没有确定的通道号,这时候不能单独播放,必须先停止全部播放功能。

流程:

1.双击设备树,获取通道或设备;

2.通道则获取当前选择焦点子对话框;设备则开始全部播放;

3.判断当前设备的当前通道是否正在播放,如果正在播放则停止播放;

4.没有播放则进入选择的对话框序号开始准备播放,先停止当前对话框的输出;

4.将通道参数传入子对话框开始1路播放StartPlay;

 

多画面播放时停止单通道画面流程:

当双击设备树播放选中通道正在播放时对其停止播放。StopPlayedChan(iDeviceIndex,iChanIndex);停止播放时遍历所有正在播放的通道,找到需要停止的通道。

 

录像开始/停止/保存:

创建路径,char m_chRecordPath[100];

NET_DVR_SaveRealData(m_lPlayHandle, chRecordPath);//开始并保存

NET_DVR_StopSaveRealData(m_lPlayHandle);//停止

 

2.画面分割

OnCbnSelchangeComboWndNum()  客户端demo中,通过该函数进行1-4-9....的画面分割;

demo中是通过CDlgOutput g_dlgOutput[MAX_OUTPUTS];//video output dialog 来支持设备的多通道播放的;

在ArrangeOutputs(int iNumber)查找关于对话框的位置显示;

对话框创建:

通过combo-box Control控件创建下拉列表控制m_iCurWndNum当前画面窗口数;创建改变对话框数量的消息,调用ArrangeOutputs(int iNumber)函数控制输出对话框在界面中的布局;

void ArrangeOutputs(int iNumber)

{
	if (iNumber == 0)
	{
		return;
	}
	int i = 0;

	int iSqrtNum = 0;//sqrt value of window number
	int iWidth = 0;//window width
	int iHeight = 0;//window height

	iSqrtNum = (int)sqrt((double)iNumber);

	for (i = 0; i < MAX_OUTPUTS; i++)
	{
		g_dlgOutput[i].ShowWindow(SW_HIDE);
	}

    /*m_rectPreviewBG在初始化话里用CRect STCrect;	
	GetDlgItem(IDC_STATIC_PLAY)->GetWindowRect(&STCrect);//获取控件的屏幕坐标
	g_pMainDlg->ScreenToClient(&STCrect);//转换为对话框上的客户坐标
	m_rectPreviewBG = STCrect; 初始化*/

	iWidth = (m_rectPreviewBG.Width() - OUTPUT_INTERVAL*(iSqrtNum - 1)) / iSqrtNum ;//a single pic width in partition
	iHeight = (m_rectPreviewBG.Height() - OUTPUT_INTERVAL*(iSqrtNum - 1)) / iSqrtNum;//a single pic height in partition
	int iPlayIndex = 0;
	

	for (i = 0; i < iNumber; i++)
	{
		iPlayIndex = i;

		g_dlgOutput[iPlayIndex].MoveWindow(m_rectPreviewBG.left + (i%iSqrtNum)*(iWidth + OUTPUT_INTERVAL), \
			m_rectPreviewBG.top + (i / iSqrtNum)*(iHeight + OUTPUT_INTERVAL), iWidth, iHeight, TRUE);
		g_dlgOutput[iPlayIndex].ShowWindow(SW_SHOW);
		g_dlgOutput[iPlayIndex].DrawOutputBorder();  //画边界有点问题
	}
}

(对话框有个很重要的属性需要更改,如果不改为Child,将不能弹出到主对话框上的对应位置)属性改为Child以后,对话框会跟随主对话框移动。三种属性之间的区别:https://blog.csdn.net/dafenqie/article/details/53836150

海康威视视频监控客户端开发实践_第1张图片

对话框选择:

添加输出对话框的左键消息OnLButtonDown;点击之后改变当前选中对话框;并且判断声音播放的选择;

添加输出对话框左键双击消息OnLButtonDblClk;切换焦点对话框--是否在播放状态(是的话可以放大缩小)--如果在全屏则还原--如果当前界面输出窗口之后一个则全屏--如果当前界面有多个输出窗口则调整为一个;

双击全屏:

在Output对话框中添加双击响应函数,首先判断是否处于播放状态,如果不是则不做动作直接返回,然后判断最大化标志,如果已经最大化则还原,输出对话框调整为原来的个数。如果没有最大化则先调整输出对话框为单独的一个,然后再次双击则全屏。

全屏FullScreen()的时候先保存当前的对话框位置,然后GetDlgItem(IDC_STATIC_PLAY)->MoveWindow(0, 0, g_iCurScreenWidth, g_iCurScreenHeight, true);将控件移动到全屏大小。如果已经全屏则调用之前保存的对话框位置将其缩小。

void CDlgOutput::OnLButtonDblClk(UINT nFlags, CPoint point) 
{
	// TODO:  在此添加消息处理程序代码和/或调用默认值
	ChangeCurWinCfg();

	if (m_lPlayHandle < 0 && !g_pMainDlg->m_struDeviceInfo.bEnlarged)
	{
		return;
	}

	if (g_pMainDlg->m_struDeviceInfo.bFullScreen)
	{
		g_pMainDlg->m_struDeviceInfo.bEnlarged = FALSE;
		g_pMainDlg->m_struDeviceInfo.bFullScreen = FALSE;
		g_pMainDlg->GetDlgItem(IDC_COMBO_WNDNUM)->EnableWindow(TRUE);
		g_pMainDlg->FullScreen(g_pMainDlg->m_struDeviceInfo.bFullScreen);
		g_pMainDlg->ArrangeOutputs(g_pMainDlg->m_iCurWndNum);
		g_pMainDlg->GetDlgItem(IDC_STATIC_PLAY)->Invalidate(TRUE);
		return;
	}

	g_pMainDlg->GetDlgItem(IDC_COMBO_WNDNUM)->EnableWindow(FALSE);

	if (g_pMainDlg->m_struDeviceInfo.bEnlarged || 1 == g_pMainDlg->m_iCurWndNum)
	{
		g_pMainDlg->m_struDeviceInfo.bFullScreen = TRUE;

		//brush chosen line
		g_pMainDlg->GetDlgItem(IDC_STATIC_PLAY)->Invalidate(TRUE);
		g_pMainDlg->FullScreen(g_pMainDlg->m_struDeviceInfo.bFullScreen);
	}
	else
	{//single pic enlarge
		g_pMainDlg->m_struDeviceInfo.bEnlarged = TRUE;
		g_pMainDlg->ArrangeOutputs(1);
	}

	CDialogEx::OnLButtonDblClk(nFlags, point);
}

void CVideoSClientDlg::FullScreen(BOOL bFullScreen)
{
	int iShowStat = bFullScreen ? SW_HIDE : SW_SHOW;

	m_treeDevList.ShowWindow(iShowStat);

	if (bFullScreen)
	{
		//for full screen while backplay
		GetWindowPlacement(&m_struOldWndpl);

		CRect rectWholeDlg;//entire client(including title bar)
		CRect rectClient;//client area(not including title bar)
		CRect rectFullScreen;
		GetWindowRect(&rectWholeDlg);
		RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposQuery, &rectClient);
		ClientToScreen(&rectClient);

		rectFullScreen.left = rectWholeDlg.left - rectClient.left;
		rectFullScreen.top = rectWholeDlg.top - rectClient.top;
		rectFullScreen.right = rectWholeDlg.right + g_iCurScreenWidth - rectClient.right;
		rectFullScreen.bottom = rectWholeDlg.bottom + g_iCurScreenHeight - rectClient.bottom;
		//enter into full screen;
		WINDOWPLACEMENT struWndpl;
		struWndpl.length = sizeof(WINDOWPLACEMENT);
		struWndpl.flags = 0;
		struWndpl.showCmd = SW_SHOWNORMAL;
		struWndpl.rcNormalPosition = rectFullScreen;
		SetWindowPlacement(&struWndpl);
	}
	else
	{
		SetWindowPlacement(&m_struOldWndpl);
	}

	//refresh backgroud box
	if (bFullScreen)
	{
		GetDlgItem(IDC_STATIC_PLAY)->MoveWindow(0, 0, g_iCurScreenWidth, g_iCurScreenHeight, true);
	}
	else
	{
		GetDlgItem(IDC_STATIC_PLAY)->MoveWindow(&m_rectPreviewBG, true);
	}
	PreviewReferShow(!bFullScreen);
	GetDlgItem(IDC_STATIC_PLAY)->ShowWindow(SW_SHOW);

	if (bFullScreen)
	{
		g_dlgOutput[m_iCurWndIndex].MoveWindow(0, 0, g_iCurScreenWidth, g_iCurScreenHeight, TRUE);
		g_dlgOutput[m_iCurWndIndex].ShowWindow(SW_SHOW);
	}

}

 

3.设备管理

右击设备树:

进入OnMenuDeviceAdd()函数,其中在GENERALDEF_H头文件中定义了设备及设备拥有的通道两种结构体

typedef struct STRU_CHANNEL_INFO
{

NET_DVR_DECODERCFG_V30  struDecodercfg;   //通道的解码器信息

.....
}
}CHANNEL_INFO,*pCHANNEL_INFO;

typedef struct STRU_DEVICE_INFO

{

STRU_DEVICE_INFO *pNext;   //自定义设备结构体,并且建立一个单链表的形式

.....

}LOCAL_DEVICE_INFO, *PLOCAL_DEVICE_INFO;

OnMenuDeviceAdd()函数中则主要创建这两种结构体的对象,然后再显示在控件当中。

单击设备树控件:

触发NM_CLICK消息,取得点击位置判断设备和通道号。

通道和通道号号相关说明:

  • 预览、回放、通道相关参数配置等接口,通过通道号区分需要访问的设备视频通道,我司设备有DVR、混合型DVR、NVR、网络摄像机等,通道有两种类型:模拟通道和IP通道(数字通道)。DVR视频输入接模拟摄像机,其视音频通道称为模拟通道;网络摄像机通道也是模拟通道;混合型DVR、NVR、CVR设备等设备支持网络摄像机、编码器接入,对应通道称为IP通道(或者数字通道),配置相关参数时需调用IP接入配置参数来进行资源的获取和重新分配。
  • 客户端通过注册设备(NET_DVR_Login_V40)返回的设备信息NET_DVR_DEVICEINFO_V30获取模拟通道个数(byChanNum)、模拟通道起始通道号(byStartChan)和设备支持的最大IP通道数(byIPChanNum+ byHighDChanNum*256)、数字通道起始通道号(byStartDChan)。
  • 从byStartChan到byStartChan+byChanNum-1对应为模拟通道的通道号,IP通道的通道号为byStartDChan到byStartDChan+ (byIPChanNum + byHighDChanNum*256) -1。DVR只有模拟通道,NVR只有IP通道,混合型DVR同时支持模拟通道和IP通道。
  • 如果设备支持IP通道个数大于0,则可以通过远程参数配置接口NET_DVR_GetDVRConfig(配置命令:NET_DVR_GET_IPPARACFG_V40)可以获取得到设备详细的IP资源信息(NET_DVR_IPPARACFG_V40),包括模拟通道是否禁用(byAnalogChanEnable)、IP通道个数(dwDChanNum)、IP通道起始通道号(dwStartDChan)、IP通道取流模式、IP通道有效状态和在线状态等。通过远程参数配置接口NET_DVR_SetDVRConfig(配置命令:NET_DVR_SET_IPPARACFG_V40)可对设备进行IP资源配置,包括添加、修改、删除IP通道等。
  • 混合型DVR、NVR或CVR的IP报警输入和报警输出的通道是在音视频IP通道资源分配好后,由设备自动分配的。如果要对IP报警参数进行配置,首先通过命令NET_DVR_GET_IPALARMINCFG_V40和NET_DVR_GET_IPALARMOUTCFG_V40获取IP报警输入资源(NET_DVR_IPALARMINCFG_V40)和IP报警输出资源(NET_DVR_IPALARMOUTCFG_V40),相关接口:NET_DVR_GetDVRConfig。然后通过命令NET_DVR_SET_ALARMINCFG_V40可以配置报警数相关参数(NET_DVR_ALARMINCFG_V40),包括报警输入名称、报警器类型、布防时间、联动方式等,通过命令NET_DVR_SET_ALARMOUTCFG_V30可以配置报警输出相关参数(NET_DVR_ALARMOUTCFG_V30),比如报警输出名称、布防时间、输出报警延时等,相关接口:NET_DVR_GetDVRConfigNET_DVR_SetDVRConfig
  • 目前SDK私有协议对接时64路以下的NVR的IP通道号是从33开始的,64路以及以上的NVR的IP通道从1开始,起始通道号具体取值可以参考上述说明通过登录接口返回的设备信息获取。但是V5.1.5.2版本以及之后版本新增了NET_DVR_STDXMLConfig透传ISAPI协议的功能(ISAPI协议是基于标准HTTP协议的),该协议URL和XML、JSON数据里面通道号不区分模拟还是数字通道,起始通道号都是1,按顺序递增。因此,不同接口对接存在通道号不一致的问题,需要调用接口NET_DVR_SDKChannelToISAPI(V5.2.7.45以及之后版本支持)进行转换。

直接登陆:

根据标志位判断是否登陆,如果未登陆则登陆DoLoginEx,然后获取设备通道资源DoGetDeviceResoureCfg(),存入m_struDeviceInfo结构体中,接下来根据结构体的信息创建通道树。创建完毕之后进行客户端相应显示,改变标志位。

4.录像回放

有按时间回放和按文件名回放两种方式。

海康威视视频监控客户端开发实践_第2张图片

按时间回放:

海康威视视频监控客户端开发实践_第3张图片

按文件名回放:

初始化----查找文件---按文件名回放----开始回放----停止回放----注销设备

查找文件:

-----初始化设备------获取到设备登陆ID,lLoginID--建立查找文件的接口NET_DVR_FILECOND_V40 m_struFileCond ------初始化接口,设置查找文件类型时间等-----得到返回的文件列表ID,m_lFileHandle = NET_DVR_FindFile_V40(m_lLoginID, &m_struFileCond)------建立查找文件的结构体NET_DVR_FINDDATA_V40 m_struFindData -----清空list控件,开始查找线程,设置对话框状态CreateThread(NULL, 0, LPTHREAD_START_ROUTINE(GetFileThread), this, 0, &dwThreadId);

void CDlgPlayBack::OnBnClickedBtnRemoteSearchList()
{
	// TODO:  在此添加控件通知处理程序代码
	UpdateData(TRUE);
	char szLan[128] = { 0 };
	if (m_lLoginID < 0 || (g_pMainDlg->m_struDeviceInfo.lLoginID <= -1))
	{
		MessageBox("查询设备未登陆!"); 
		return;
	}

	if (!InitDevice())
	{
		MessageBox("获取设备失败!");
		return;
	}

	if (!m_bSearching)
	{
		memset(&m_struFileCond, 0, sizeof(NET_DVR_FILECOND_V40));
		m_iFileType = m_comboFileType.GetItemData(m_comboFileType.GetCurSel());
		m_struFileCond.dwFileType = m_iFileType;
		m_struFileCond.lChannel = g_pMainDlg->m_struDeviceInfo.struChanInfo[m_iChanIndex].iChanIndex;
		m_struFileCond.dwIsLocked = 0xFF;
		m_struFileCond.dwUseCardNo = 0;
		m_struFileCond.struStartTime.dwYear = (WORD)m_ctDateStart.GetYear();
		m_struFileCond.struStartTime.dwMonth = (char)m_ctDateStart.GetMonth();
		m_struFileCond.struStartTime.dwDay = (char)m_ctDateStart.GetDay();
		m_struFileCond.struStartTime.dwHour = (char)m_ctTimeStart.GetHour();
		m_struFileCond.struStartTime.dwMinute = (char)m_ctTimeStart.GetMinute();
		m_struFileCond.struStartTime.dwSecond = (char)m_ctTimeStart.GetSecond();
		m_struFileCond.struStopTime.dwYear = (WORD)m_ctDateStop.GetYear();
		m_struFileCond.struStopTime.dwMonth = (char)m_ctDateStop.GetMonth();
		m_struFileCond.struStopTime.dwDay = (char)m_ctDateStop.GetDay();
		m_struFileCond.struStopTime.dwHour = (char)m_ctTimeStop.GetHour();
		m_struFileCond.struStopTime.dwMinute = (char)m_ctTimeStop.GetMinute();
		m_struFileCond.struStopTime.dwSecond = (char)m_ctTimeStop.GetSecond();

		m_lFileHandle = NET_DVR_FindFile_V40(m_lLoginID, &m_struFileCond);

		if (m_lFileHandle < 0)
		{
			g_StringLanType(szLan, "获取文件列表失败!", "Fail to get file list");
			MessageBox(szLan);
			return;
		}
		m_listRemoteFile.DeleteAllItems();
		DWORD dwThreadId;
		if (m_hFileThread == NULL)
		{
			m_hFileThread = CreateThread(NULL, 0, LPTHREAD_START_ROUTINE(GetFileThread), this, 0, &dwThreadId);
		}
		if (m_hFileThread == NULL)
		{
			g_StringLanType(szLan, "打开查找线程失败!", "Fail to open finding thread!");
			AfxMessageBox(szLan);
			return;
		}
		g_StringLanType(szLan, "停止查找", "Stop Searching");
		GetDlgItem(IDC_BTN_REMOTE_SEARCH_LIST)->SetWindowText(szLan);
		m_bSearching = TRUE;
		GetDlgItem(IDC_STATIC_REMOTE_SEARCH_STATE)->ShowWindow(SW_SHOW);
	}
	else
	{
		if (m_hFileThread)
		{
			m_bQuit = TRUE;
			//TerminateThread(m_hFileThread, 0);
		}
		CloseHandle(m_hFileThread);
		m_hFileThread = NULL;
		NET_DVR_FindClose(m_lFileHandle);
		g_StringLanType(szLan, "查找", "Search");
		GetDlgItem(IDC_BTN_REMOTE_SEARCH_LIST)->SetWindowText(szLan);
		m_bSearching = FALSE;
		GetDlgItem(IDC_STATIC_REMOTE_SEARCH_STATE)->ShowWindow(SW_HIDE);
		m_iFileNum = 0;
	}

}

GetFileThread查找线程:

查找下一个文件lRet = NET_DVR_FindNextFile_V40(g_pDlgPlayRemoteFile->m_lFileHandle, &struFileInfo);------判断查找结果lRet----NET_DVR_FILE_SUCCESS(成功)/NET_DVR_ISFINDING(等待)/((lRet == NET_DVR_NOMOREFILE) || (lRet == NET_DVR_FILE_NOFIND)(失败)-----查找完毕释放资源关闭线程NET_DVR_FindClose_V30(g_pDlgPlayRemoteFile->m_lFileHandle);

UINT GetFileThread(LPVOID pParam)
{
	LONG lRet = -1;
	NET_DVR_FINDDATA_V40 struFileInfo = { 0 };
	//memset(&struFileInfo, 0, sizeof(struFileInfo));
	//NET_DVR_FINDDATA_V40 struFileInfo;
	//memset(&struFileInfo, 0, sizeof(struFileInfo));
	CString csTmp;
	char m_szFileName[100];
	char szLan[128] = { 0 };

	while (!g_pDlgPlayRemoteFile->m_bQuit)
	{
		lRet = NET_DVR_FindNextFile_V40(g_pDlgPlayRemoteFile->m_lFileHandle, &struFileInfo);
		//lRet = NET_DVR_FindNextFile_V50(g_pDlgPlayRemoteFile->m_lFileHandle, &struFileInfo);
		if (lRet == NET_DVR_FILE_SUCCESS)
		{
			memcpy(&g_pDlgPlayRemoteFile->m_struFindData, &struFileInfo, sizeof(struFileInfo));

			strcpy(m_szFileName, struFileInfo.sFileName);
			g_pDlgPlayRemoteFile->m_listRemoteFile.InsertItem(g_pDlgPlayRemoteFile->m_iFileNum, m_szFileName, 0);
			if (struFileInfo.dwFileSize / 1024 == 0)
			{
				csTmp.Format("%d", struFileInfo.dwFileSize);
			}
			else if (struFileInfo.dwFileSize / 1024 > 0 && struFileInfo.dwFileSize / (1024 * 1024) == 0)
			{
				csTmp.Format("%dK", struFileInfo.dwFileSize / 1024);
			}
			else// if ()
			{
				csTmp.Format("%dM", struFileInfo.dwFileSize / 1024 / 1024);//different from hard disk capacity, files need tranformation
			}
			//csTmp.Format("%d",struFileInfo.dwFileSize);
			g_pDlgPlayRemoteFile->m_listRemoteFile.SetItemText(g_pDlgPlayRemoteFile->m_iFileNum, 1, csTmp);
		    //开始时间
			csTmp.Format("%04d%02d%02d%02d%02d%02d", struFileInfo.struStartTime.dwYear, \
				struFileInfo.struStartTime.dwMonth, struFileInfo.struStartTime.dwDay, \
				struFileInfo.struStartTime.dwHour, struFileInfo.struStartTime.dwMinute, \
				struFileInfo.struStartTime.dwSecond);	
			g_pDlgPlayRemoteFile->m_listRemoteFile.SetItemText(g_pDlgPlayRemoteFile->m_iFileNum, 2, csTmp);
			//结束时间
			csTmp.Format("%04d%02d%02d%02d%02d%02d", struFileInfo.struStopTime.dwYear, struFileInfo.struStopTime.dwMonth, \
				struFileInfo.struStopTime.dwDay, struFileInfo.struStopTime.dwHour, \
				struFileInfo.struStopTime.dwMinute, struFileInfo.struStopTime.dwSecond);		
			g_pDlgPlayRemoteFile->m_listRemoteFile.SetItemText(g_pDlgPlayRemoteFile->m_iFileNum, 3, csTmp);

			if (struFileInfo.byLocked == 1)
			{
				g_StringLanType(szLan, "锁定", "Lock");
				csTmp.Format(szLan);
			}
			else if (struFileInfo.byLocked == 0)
			{
				g_StringLanType(szLan, "未锁", "Unlock");
				csTmp.Format(szLan);
			}
			g_pDlgPlayRemoteFile->m_listRemoteFile.SetItemText(g_pDlgPlayRemoteFile->m_iFileNum, 4, csTmp);
			g_pDlgPlayRemoteFile->m_listRemoteFile.SetItemData(g_pDlgPlayRemoteFile->m_iFileNum, struFileInfo.byLocked);

			csTmp.Format("%d", struFileInfo.dwFileIndex);
			g_pDlgPlayRemoteFile->m_listRemoteFile.SetItemText(g_pDlgPlayRemoteFile->m_iFileNum, 5, csTmp);

			csTmp.Format("%d", struFileInfo.byFileType);
			g_pDlgPlayRemoteFile->m_listRemoteFile.SetItemText(g_pDlgPlayRemoteFile->m_iFileNum, 6, csTmp);

			if (struFileInfo.byStreamType == 0)
			{
				g_StringLanType(szLan, "主码流", "Main Stream");
				csTmp.Format(szLan);
			}
			else if (struFileInfo.byStreamType == 1)
			{
				g_StringLanType(szLan, "子码流", "Sub Stream");
				csTmp.Format(szLan);
			}
			else if (struFileInfo.byStreamType == 2)
			{
				g_StringLanType(szLan, "三码流", "Three Stream");
				csTmp.Format(szLan);
			}
			g_pDlgPlayRemoteFile->m_listRemoteFile.SetItemText(g_pDlgPlayRemoteFile->m_iFileNum, 7, csTmp);
			g_pDlgPlayRemoteFile->m_iFileNum++;
		}
		else
		{
			if (lRet == NET_DVR_ISFINDING)
			{
				Sleep(5);
				continue;
			}
			if ((lRet == NET_DVR_NOMOREFILE) || (lRet == NET_DVR_FILE_NOFIND))
			{
				g_StringLanType(szLan, "查找", "Search");
				g_pDlgPlayRemoteFile->GetDlgItem(IDC_BTN_REMOTE_SEARCH_LIST)->SetWindowText(szLan);
				g_pDlgPlayRemoteFile->m_bSearching = FALSE;
				(g_pDlgPlayRemoteFile->GetDlgItem(IDC_STATIC_REMOTE_SEARCH_STATE))->ShowWindow(SW_HIDE);
				//g_pMainDlg->AddLog(g_pDlgRemoteFile->m_iDeviceIndex, OPERATION_SUCC_T, "NET_DVR_FindNextFile_V50 file num[%d]", g_pDlgRemoteFile->m_iFileNum);
				// 				if (g_pDlgRemoteFile->m_iFileNum > 0)
				// 				{
				// 					g_pMainDlg->AddLog(g_pDlgRemoteFile->m_iDeviceIndex, OPERATION_SUCC_T, "NET_DVR_FindNextFile_V30 file num[%d]", g_pDlgRemoteFile->m_iFileNum);
				// 					g_StringLanType(szLan, "获取文件列表结束,文件已经全部列出", "Finish to get file list, and documents have all been listed");
				// 					AfxMessageBox(szLan);
				// 				}
				// 				else
				// 				{
				// 					g_StringLanType(szLan, "获取文件列表结束,没有录像文件", "Finish to get file list, and There is no record file");
				// 					AfxMessageBox(szLan);
				// 				}

				g_pDlgPlayRemoteFile->m_iFileNum = 0;
				if (g_pDlgPlayRemoteFile->m_nPlayHandle == -1)
				{
					//g_pDlgPlayRemoteFile->SetStopState();
					g_pDlgPlayRemoteFile->GetDlgItem(IDC_BTN_REMOTE_FILE_PLAY)->EnableWindow(FALSE);
				}
				break;
			}
			else
			{
				g_pDlgPlayRemoteFile->GetDlgItem(IDC_BTN_REMOTE_SEARCH_LIST)->SetWindowText("查找");
				g_pDlgPlayRemoteFile->m_bSearching = FALSE;
				(g_pDlgPlayRemoteFile->GetDlgItem(IDC_STATIC_REMOTE_SEARCH_STATE))->ShowWindow(SW_HIDE);
				//g_pMainDlg->AddLog(g_pDlgRemoteFile->m_iDeviceIndex, OPERATION_FAIL_T, "NET_DVR_FindNextFile_V50");
				g_StringLanType(szLan, "由于服务器忙,或网络故障,获取文件列表异常终止", "Since the server is busy, or network failure, abnormal termination of access to the file list");
				AfxMessageBox(szLan);
				g_pDlgPlayRemoteFile->m_iFileNum = 0;
				if (g_pDlgPlayRemoteFile->m_nPlayHandle == -1)
				{
					//g_pDlgPlayRemoteFile->SetStopState();
					g_pDlgPlayRemoteFile->GetDlgItem(IDC_BTN_REMOTE_FILE_PLAY)->EnableWindow(FALSE);
				}
				break;
			}
		}
	}
	CloseHandle(g_pDlgPlayRemoteFile->m_hFileThread);
	g_pDlgPlayRemoteFile->m_hFileThread = NULL;
	NET_DVR_FindClose_V30(g_pDlgPlayRemoteFile->m_lFileHandle);
	g_pDlgPlayRemoteFile->m_lFileHandle = -1;
	g_pDlgPlayRemoteFile->m_bSearching = FALSE;
	return 0;
}

按文件名回放----开始回放:

判断选中的文件名----建立NET_DVR_PLAY_BY_NAME_PARA struPlayByName,播放接口----获取正放/倒放控制入口号,m_nPlayHandle = NET_DVR_PlayBackByName_V50(m_lLoginID, &struPlayByName)/NET_DVR_PlayBackReverseByName_V50(m_lLoginID, &struPlayByName); ----改变控件状态----开始播放NET_DVR_PlayBackControl(m_nPlayHandle, NET_DVR_PLAYSTART, NULL, NULL)-----设置进度条SetTimer(REMOTE_PLAY_STATE_TIMER, 1000, NULL);

void CDlgPlayBack::PlayBack()
{
	UpdateData(TRUE);

	CString csFileName;
	int iFileSelPos = 0;
	HWND hPlayWnd = m_staticPlayWnd.GetSafeHwnd();
	POSITION  posItem = m_listRemoteFile.GetFirstSelectedItemPosition();
	char szLan[128] = { 0 };
	if (posItem == NULL)
	{
		g_StringLanType(szLan, "请选择要播放的文件!", "Please select the file to play");
		AfxMessageBox(szLan);
		return;
	}

	iFileSelPos = m_listRemoteFile.GetNextSelectedItem(posItem);
	csFileName.Format("%s", m_listRemoteFile.GetItemText(iFileSelPos, 0));
	if (csFileName.IsEmpty())
	{
		return;
	}

	sprintf(m_szFileName, "%s", csFileName);
	//	remoteplay_info.srcfilename=m_szFileName;
	if (m_nPlayHandle >= 0)
	{
		if (NET_DVR_StopPlayBack(m_nPlayHandle))
		{
			//g_pMainDlg->AddLog(m_iDeviceIndex, OPERATION_SUCC_T, "NET_DVR_StopPlayBack");
		}
		m_nPlayHandle = -1;
		Sleep(400);
	}

	NET_DVR_PLAY_BY_NAME_PARA struPlayByName = { 0 };
	memcpy(struPlayByName.szFileName, m_szFileName, 100);
	struPlayByName.hWnd = hPlayWnd;

	//正放倒放控制

	if (!((CButton *)GetDlgItem(IDC_RADIO_REVERSE))->GetCheck())
	{
		memcpy(m_szCurFileName, m_szFileName, 200);
		m_nPlayHandle = NET_DVR_PlayBackByName_V50(m_lLoginID, &struPlayByName);
	}
	else
	{
		memcpy(m_szCurFileName, m_szFileName, 200);
		m_nPlayHandle = NET_DVR_PlayBackReverseByName_V50(m_lLoginID, &struPlayByName);
	}


	if (m_nPlayHandle == -1)
	{
		//g_pMainDlg->AddLog(m_iDeviceIndex, OPERATION_FAIL_T, "NET_DVR_PlayBackByName[%s]", m_szFileName);
		char szLan[1024] = { 0 };
		g_StringLanType(szLan, "回放失败!", "Play Back Failed!");
		AfxMessageBox(szLan);
		return;
	}
	 
	//改变播放控件状态
	GetDlgItem(IDC_BTN_REMOTE_FILE_PLAY)->SetWindowText("暂停");
	GetDlgItem(IDC_BTN_REMOTE_FILE_PLAY)->EnableWindow(TRUE);
	GetDlgItem(IDC_BTN_REMOTE_FILE_STOP)->EnableWindow(TRUE);
	//开始
	if (NET_DVR_PlayBackControl(m_nPlayHandle, NET_DVR_PLAYSTART, NULL, NULL))//NET_DVR_PlayBackControl(m_nPlayHandle, NET_DVR_PLAYSTART, m_iOffset, NULL)
	{
		//g_pMainDlg->AddLog(m_iDeviceIndex, OPERATION_SUCC_T, "NET_DVR_PLAYSTART offset[%d]", m_iOffset);
	}
	else
	{
		//g_pMainDlg->AddLog(m_iDeviceIndex, OPERATION_FAIL_T, "NET_DVR_PLAYSTART offset[%d]", m_iOffset);
		char szLan[1024] = { 0 };
		NET_DVR_StopPlayBack(m_nPlayHandle);
		//SetStopState();
		GetDlgItem(IDC_BTN_REMOTE_FILE_PLAY)->SetWindowText("播放");
		m_nPlayHandle = -1;
		g_StringLanType(szLan, "回放失败!", "NET_DVR_PLAYSTART Failed!");
		AfxMessageBox(szLan);
		return;
	}

	//设置定时器控制进度条(先屏蔽进度条功能)
	m_bGetMaxTime = FALSE;
	SetTimer(REMOTE_PLAY_STATE_TIMER, 1000, NULL);
}

设置进度条:

用定时器实现,调用NET_DVR_PlayBackControl(m_nPlayHandle, NET_DVR_PLAYGETPOS, 0, &nPos)来获取进度,m_sliderPlayProgress.SetPos(nPos);设置进度条

void CDlgPlayRemoteTime::OnTimer(UINT_PTR nIDEvent)
{
	// TODO:  在此添加消息处理程序代码和/或调用默认值
	DWORD nPos;
	char szLan[128] = { 0 };
	//	NET_DVR_TIME struOsdTime;
	if (nIDEvent == PLAYBYTIME_TIMER)
	{
		if (m_nPlayHandle >= 0)
		{
			if (NET_DVR_PlayBackControl(m_nPlayHandle, NET_DVR_PLAYGETPOS, 0, &nPos))
			{
				//g_pMainDlg->AddLog(m_iDeviceIndex, OPERATION_SUCC_T, "NET_DVR_PLAYGETPOS pos[%d]", nPos);
				m_sliderPlayProgress.SetPos(nPos);
			}

			if (nPos > 100)
			{
				//g_pMainDlg->AddLog(m_iDeviceIndex, OPERATION_FAIL_T, "NET_DVR_PLAYGETPOS %d, err = %d", nPos, NET_DVR_GetLastError());
				StopPlay();
				g_StringLanType(szLan, "由于网络原因或DVR忙,回放异常终止!", " Due to network reasons or DVR is busy, playback abnormal termination");
				AfxMessageBox(szLan);
			}
			if ((((CButton *)GetDlgItem(IDC_RIO_REVERSE))->GetCheck()) ? (nPos == 100) : (nPos == 0))
			{
				StopPlay();
				g_StringLanType(szLan, "按时间回放结束", "playback by time over");
				AfxMessageBox(szLan);
			}
		}
	}

	CDialogEx::OnTimer(nIDEvent);
}

结束回放:

调用NET_DVR_StopPlayBack(m_nPlayHandle);

5.异常控制

当设备掉线或者断电时应该有异常判断并做出处理;这时应注销设备,释放资源,并可以设置重连等待时间。

通过查阅《网络设备SDK》发现有NET_DVR_SetExceptionCallBack_V30 等函数可以通过回调函数的方式来获取到不同类型的异常并处理。

NET_DVR_SetExceptionCallBack_V30(WM_NULL/*WM_MYCOMMAND*/, NULL/*this->m_hWnd*/, g_ExceptionCallBack, NULL);
NET_DVR_SetReconnect(5000, m_struDeviceInfo.bReconnect);   //不调用的化系统也默认启动,重连时间为5S

回调函数g_ExceptionCallBack中可以获取到各种异常消息和设备通道ID,由于系统可以自动设置重连操作,所以在异常回调函数中可以根据需要做其他异常处理,一般情况各种异常都需要记录日志,这里当预览发生异常时关闭对应的输出对话框的输出,并改变对话框状态。

void CALLBACK g_ExceptionCallBack(DWORD dwType, LONG lUserID, LONG lHandle, void *pUser)
{
	UNREFERENCED_PARAMETER(pUser);
	if (g_bExitDemo)  //退出时不需要发送异常消息
	{
		return;
	}
	int i = 0;
	//int iDeviceIndex = -1;
	//CString sTemp;
	//char szTmpBuf[256] = {0};

	switch (dwType)
	{
	case EXCEPTION_ALARM:			//network exception while uploading alarm

		break;
	case EXCEPTION_PREVIEW:			// exception while preview
		//g_pMainDlg->AddLog(iDeviceIndex, OPERATION_FAIL_T, "PREVIEW dev[%d]exception[%d]", iDeviceIndex, dwType);
		for (i = 0; i < MAX_OUTPUTS; i++)
		{
			if (lHandle == g_dlgOutput[i].m_lPlayHandle)
			{
				//g_dlgOutput[i].AddLog(OPERATION_FAIL_T, "preview exception!");
				if (g_dlgOutput[i].m_bIsRecording)
				{
					g_dlgOutput[i].StopLocalRecord();
				}
				g_dlgOutput[i].StopPlay();
				g_pMainDlg->ChangePlayBtnState();
				goto ExceptionOut;
			}
		}
		break;

	default:
		//g_pMainDlg->AddLog(iDeviceIndex, OPERATION_FAIL_T, "else exception dev[%d]exception[0x%x]", iDeviceIndex, dwType);
		break;
	}
ExceptionOut:
	return;
}

6.报警

海康威视视频监控客户端开发实践_第4张图片

 


MFC客户端界面

1.预览控制

画面分割:

实现显示窗口边缘画线的效果:

由于将子对话框显示在主对话框控件上的相对位子,所以我们在主对话框上的控件上画不同颜色的线就可以实现看起来像选中了子对话框的线一样的效果;

海康威视视频监控客户端开发实践_第5张图片

代码:

void CDlgOutput::DrawOutputBorder()
{
	if (!IsWindowVisible())
	{
		return;
	}

	CPen *pOldPen = NULL;
	CPen pPen;
	CRect rc(0, 0, 0, 0);
	GetWindowRect(&rc);    //获取到对话框在屏幕上的绝对坐标
	g_pMainDlg->GetDlgItem(IDC_STATIC_PLAY)->ScreenToClient(&rc);   //将对话框的绝对坐标转化到控件IDC_STATIC_PLAY中的相对坐标
	if (g_pMainDlg->m_iCurWndIndex == m_iSubWndIndex)
	{
		pPen.CreatePen(PS_SOLID, 2, RGB(0, 255, 0));//green
	}
	else
	{
		pPen.CreatePen(PS_SOLID, 2, RGB(125, 125, 116));
	}
	// 	rc.top-=1;
	// 	rc.left-=1;
	rc.right += OUTPUT_INTERVAL / 2;
	rc.bottom += OUTPUT_INTERVAL / 2;

	CDC *pDC = g_pMainDlg->GetDlgItem(IDC_STATIC_PLAY)->GetDC();
	ASSERT(pDC);

	pDC->SelectStockObject(NULL_BRUSH);
	pOldPen = pDC->SelectObject(&pPen);
	pDC->Rectangle(&rc);

	if (pOldPen)
	{
		pDC->SelectObject(pOldPen);
	}

	ReleaseDC(pDC);
}

 

播放全屏:

获取窗口当前大小rect,用于全屏恢复-----隐藏其他控件----将窗口移动到全屏(这里代码做了一个额外操作,及当前输出画面不是一个时先放大为一个画面,然后再次点击则全屏)

void CDlgOutput::OnLButtonDblClk(UINT nFlags, CPoint point)
{
	// TODO:  在此添加消息处理程序代码和/或调用默认值
	ChangeCurWinCfg();  //设置当前选中的窗口

//	if (m_lPlayHandle < 0 && !g_pMainDlg->m_struDeviceInfo.bEnlarged)
//	{
//		return;
//	}

	if (g_pMainDlg->m_struDeviceInfo.bFullScreen)
	{
		g_pMainDlg->m_struDeviceInfo.bEnlarged = FALSE;
		g_pMainDlg->m_struDeviceInfo.bFullScreen = FALSE;
		g_pMainDlg->GetDlgItem(IDC_COMBO_WNDNUM)->EnableWindow(TRUE);
		g_pMainDlg->FullScreen(g_pMainDlg->m_struDeviceInfo.bFullScreen);
		g_pMainDlg->ArrangeOutputs(g_pMainDlg->m_iCurWndNum);
		g_pMainDlg->GetDlgItem(IDC_STATIC_PLAY)->Invalidate(TRUE);
		return;
	}

	g_pMainDlg->GetDlgItem(IDC_COMBO_WNDNUM)->EnableWindow(FALSE);

	if (g_pMainDlg->m_struDeviceInfo.bEnlarged || 1 == g_pMainDlg->m_iCurWndNum)
	{
		g_pMainDlg->m_struDeviceInfo.bFullScreen = TRUE;

		//brush chosen line
		g_pMainDlg->GetDlgItem(IDC_STATIC_PLAY)->Invalidate(TRUE);
		g_pMainDlg->FullScreen(g_pMainDlg->m_struDeviceInfo.bFullScreen);    //全屏/恢复
	}
	else
	{//single pic enlarge
		g_pMainDlg->m_struDeviceInfo.bEnlarged = TRUE;
		g_pMainDlg->ArrangeOutputs(1);
	}

	CDialogEx::OnLButtonDblClk(nFlags, point);
}
void CVideoSClientDlg::FullScreen(BOOL bFullScreen)
{
	int iShowStat = bFullScreen ? SW_HIDE : SW_SHOW;

	m_treeDevList.ShowWindow(iShowStat);
	GetDlgItem(IDC_BUTTON_LOGIN)->ShowWindow(iShowStat);
	GetDlgItem(IDC_IPADDRESS_DEV)->ShowWindow(iShowStat);
	GetDlgItem(IDC_EDIT_PORT)->ShowWindow(iShowStat);
	GetDlgItem(IDC_EDIT_USER)->ShowWindow(iShowStat);
	GetDlgItem(IDC_EDIT_PWD)->ShowWindow(iShowStat);
	
	if (bFullScreen)
	{
		//for full screen while backplay
		GetWindowPlacement(&m_struOldWndpl);

		CRect rectWholeDlg;//entire client(including title bar)
		CRect rectClient;//client area(not including title bar)
		CRect rectFullScreen;
		GetWindowRect(&rectWholeDlg);
		RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposQuery, &rectClient);
		ClientToScreen(&rectClient);

		rectFullScreen.left = rectWholeDlg.left - rectClient.left;
		rectFullScreen.top = rectWholeDlg.top - rectClient.top;
		rectFullScreen.right = rectWholeDlg.right + g_iCurScreenWidth - rectClient.right;
		rectFullScreen.bottom = rectWholeDlg.bottom + g_iCurScreenHeight - rectClient.bottom;
		//enter into full screen;
		WINDOWPLACEMENT struWndpl;
		struWndpl.length = sizeof(WINDOWPLACEMENT);
		struWndpl.flags = 0;
		struWndpl.showCmd = SW_SHOWNORMAL;
		struWndpl.rcNormalPosition = rectFullScreen;
		SetWindowPlacement(&struWndpl);
	}
	else
	{
		SetWindowPlacement(&m_struOldWndpl);
	}

	//refresh backgroud box
	if (bFullScreen)
	{
		GetDlgItem(IDC_STATIC_PLAY)->MoveWindow(0, 0, g_iCurScreenWidth, g_iCurScreenHeight, true);
	}
	else
	{
		GetDlgItem(IDC_STATIC_PLAY)->MoveWindow(&m_rectPreviewBG, true);
	}
	PreviewReferShow(!bFullScreen);
	

	if (bFullScreen)
	{
		g_dlgOutput[m_iCurWndIndex].MoveWindow(0, 0, g_iCurScreenWidth, g_iCurScreenHeight, TRUE);
		g_dlgOutput[m_iCurWndIndex].ShowWindow(SW_SHOW);
	}

}

 

  • 深入解析MFChttps://www.cnblogs.com/qq78292959/archive/2010/05/20/2077050.html ;
  • MFC消息机制:https://www.cnblogs.com/findumars/p/3948427.html;

Q&A:

设备和通道什么意思?

 

全局变量为什么定义在CPP中?

Declare all the global variables in ClientDemo.cpp, then add extern to them in ClientDemo.h. 
if other files need to call these global variables, just include ClientDemo.h file?

把全局变量定义在cpp里然后在头文件中声明extern,这样符合头文件中不做定义的习惯。如果在头文件中定义,很容易出现包含相同头文件时,变量被重复定义的问题

这段代码怎么理解typedef int (_stdcall *ChannelOpenProc)(long, HANDLE*);

用typedef定义了一种函数指针类型ChannelOpenProc,这种指针可以指向下面这种函数:
int _stdcall f(long, HANDLE*)    //返回int 有一个long类型和一个HANDLE*类型的参数,_stdcall说明该函数参数是从右往左入栈的。于是可以这样使用topenPort类型:ChannelOpenProc pf1; //定义一个指针pf1=f; //指向f

你可能感兴趣的:(个人记录)