8.2 视频显示方案
在设计监控程序的时候,由于要使用不同的摄像头进行采集,所以可能出现分屏显示的情况;而当要对某一个摄像头采集情况进行关注时则需要单屏显示;有时根据用户的需要还要将视频数据保存成录像,并对录像进行回放,本节就来介绍如何实现这些功能。
8.2.1 视频单屏显示方案
在当今社会中,摄像头的应用已经及其广泛,在使用的过程中,视频文件的传输显示就必不可少,下面通过实例来进行视频的单屏显示,本例通过传输图片的方式将服务器端摄像头捕捉的图片传输到客户端。运行程序如图8.9 、图8.10所示。

图8.9 视频单屏显示(服务器端) 图8.10 视频单屏显示(客户端)
服务器端程序设计步骤如下:
实例位置:光盘\mr\8\8.2\8.2.1\01
(1)创建一个基于对话框的应用程序,向对话框中添加一个按钮控件。
(2)在应用程序的InitInstance方法中初始化套接字。
WSADATA wsd;
WSAStartup(MAKEWORD(2,2),&wsd);
(3)从CSocket类派生新类CClientSocket和CServerSocket,在头文件中引用对话框的头文件和afxsock.h头文件,并对对话框类进行前导声明。
(4)在对话框类的头文件中引用vfw.h头文件,连接vfw32.lib库文件。在对话框类中定义一个静态方法,作为线程函数。
#include "vfw.h"
static UINT vproc(LPVOID pParam);
(5)绑定套接字,并开始监听客户端,开始一个线程,连接设备驱动程序。
void CServerDlg::OnButlisten()
{
if(!m_pServer->Create(700))
{
MessageBox("套接字创建失败");
delete m_pServer;
m_pServer = NULL;
return;
}
if(!m_pServer->Listen())
MessageBox("监听失败");
CAPDRIVERCAPS capd;
CAPSTATUS caps;
tran=NULL;
event.ResetEvent();
AfxBeginThread(vproc,(void*)this);
::WaitForSingleObject(event,INFINITE);
if(capDriverConnect(videohwnd,0)==TRUE)
{
::SetParent(videohwnd,*this);
::SetWindowLong(videohwnd,GWL_STYLE,WS_CHILD);
::SetWindowPos(videohwnd,NULL,10,10,400,400,SWP_NOZORDER);
::ShowWindow(videohwnd,SW_SHOW);
capPreviewRate(videohwnd,30);
capPreview(videohwnd,true);
}
else
{
AfxMessageBox("创建失败");
}
capSetCallbackOnFrame(videohwnd,CaptureFun);
}
(6)在线程函数中创建视频捕捉窗口。
UINT CServerDlg::vproc(LPVOID pParam)
{
CServerDlg *pDlg=(CServerDlg*)pParam;
HWND hwnd = capCreateCaptureWindow(NULL,WS_POPUP,0,0,10,10,0,0);
temp = hwnd;
if(hwnd)
{
pDlg->videohwnd=hwnd;
pDlg->event.SetEvent();
}
MSG Msg;
while(GetMessage(&Msg,NULL,0,0))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
(7)通过capSetCallbackOnFrame宏在应用程序中设置预览模式的回调函数。这样当摄像头捕获到每一帧时,就会调用指定的回调函数。用户可以在回调函数中调用capFileSaveDIB宏将帧保存成图片。
void CaptureFun(LPVOID pParam)
{
capFileSaveDIB(temp,"c:\\a.bmp");
}
(8)在对话框中添加AcceptConnect方法,用于接受客户端的连接。
void CServerDlg::AcceptConnect()
{
m_pServer->Accept(*m_pClient);
}
(9)在对话框中添加ReceiveData方法,用于接收客户端的请求信息。
void CServerDlg::ReceiveData(CClientSocket *socket)
{
char buf[2];
DWORD ret = socket->Receive(buf,2);
buf[ret] = 0;
if(ret == SOCKET_ERROR)
{
CString str;
str.Format("接收数据失败,错误码:%d",WSAGetLastError());
MessageBox(str);
return;
}
if(buf[0]=='`')
SendData();
}
(10)在对话框中添加SendData方法,在接收客户端的请求信息后,向客户端发送图片。
void CServerDlg::SendData()
{
CFile file("c:\\a.bmp",CFile::modeRead);
DWORD size = file.GetLength();
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE,size);
char* lpBuf = (char*)GlobalLock(hGlobal);
file.ReadHuge(lpBuf,size);
int ret = m_pClient->Send(lpBuf,size);
if(ret == SOCKET_ERROR)
{
CString str;
str.Format("发送数据失败,错误码:%d",WSAGetLastError());
MessageBox(str);
return;
}
Sleep(10);
char a[1]={'^'}; //结束符
m_pClient->Send(a,1);
GlobalUnlock(hGlobal);
GlobalFree(hGlobal);
}
(11)处理对话框的WM_DESTROY消息,在窗口关闭时关闭套接字。
void CServerDlg::OnDestroy()
{
CDialog::OnDestroy();
if(m_pServer != NULL)
delete m_pServer;
if(m_pClient != NULL)
delete m_pClient;
}
客户端程序设计如下:
实例位置:光盘\mr\8\8.2\8.2.1\02
(1)创建一个基于对话框的应用程序,向对话框中添加一个图像控件、一个IP控件和一个按钮控件。对话框设计如图8.11 所示。

图8.11 对话框设计
(2)打开Picture控件的属性窗口,图像控件的属性设置如图8.12 所示。

图8.12 Picture控件的属性窗口
(3)从CSocket类中派生一个子类CClientSocket。在头文件中引用Afxsock.h头文件,目的是使用CSocket类;引用主对话框的头文件,并对主对话框进行前导声明,因为在CClientSocket类中需要定义主对话框类指针。
(4)在对话框中定义一个CClientSocket对象指针,创建套接字。
BOOL CClientDlg::OnInitDialog()
{
……//此处代码省略
m_pClient = new CClientSocket(this);
if(!m_pClient->Create()) //创建套接字
{
delete m_pClient;
MessageBox("套接字创建失败.");
return FALSE;
}
char buf[256];
::GetCurrentDirectory(256,buf);
m_name.Format("%s\\a.BMP",buf);
return TRUE;
}
(5)处理“连接”按钮的单击事件,连接服务器,并在连接以后开启定时器。
void CClientDlg::OnButconnect()
{
// TODO: Add your control notification handler code here
UpdateData(TRUE);
CString sip;
m_IP.GetWindowText(sip); //读取服务器IP
int port;
port = 700; //获取端口
if(! m_pClient->Connect(sip,port)) //连接服务器
{
MessageBox("连接服务器失败!");
return;
}
SetTimer(1,200,NULL);
}
(6)在定时器中向服务器发送请求信息,并显示接收后的图片。
void CClientDlg::OnTimer(UINT nIDEvent)
{
char a[1]={'`'}; //请求信息
int ret = m_pClient->Send(a,1);
CString str;
CFileFind fFind;
if(fFind.FindFile(m_name))
{
HBITMAP hBitmap = (HBITMAP)LoadImage(NULL,m_name,
IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
if(hBitmap != NULL)
{
CDC* pDC = m_Picture.GetDC();
CRect r;
m_Picture.GetClientRect(&r);
//将位图选进设备场景中
CDC memdc;
memdc.CreateCompatibleDC( pDC );
memdc.SelectObject(hBitmap);
BITMAP bmp;
GetObject(hBitmap,sizeof(bmp),&bmp);
pDC->StretchBlt(r.left,r.top,r.Width(),r.Height(),&memdc,0,0,
bmp.bmWidth,bmp.bmHeight,SRCCOPY);
memdc.DeleteDC();
}
}
CDialog::OnTimer(nIDEvent);
}
(7)在对话框中添加ReceiveData方法,用于接收服务器端发送的图片。
void CClientDlg::ReceiveData(CClientSocket *socket)
{
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE,60000);
void* lpBuf = GlobalLock(hGlobal);
DWORD size = m_pClient->Receive(lpBuf,60000);
if(size == SOCKET_ERROR)
{
CString str;
str.Format("接收数据失败,错误码:%d",WSAGetLastError());
MessageBox(str);
return;
}
if(m_IsStop)
{
m_file.Open(m_name,CFile::modeCreate|CFile::modeWrite);
m_file.WriteHuge(lpBuf,size);
m_IsStop = FALSE;
GlobalUnlock(hGlobal);
GlobalFree(hGlobal);
return;
}
char* temp = (char*)lpBuf;
if((temp[size-1]=='^'))
{
m_file.Close();
m_IsStop = TRUE;
}
else
{
m_file.WriteHuge(lpBuf,size);
GlobalUnlock(hGlobal);
GlobalFree(hGlobal);
}
}
(8)处理对话框的WM_DESTROY消息,在窗口关闭时关闭套接字。
void CServerDlg::OnDestroy()
{
CDialog::OnDestroy();
if(m_pClient != NULL)
delete m_pClient;
}
8.2.2 视频多屏显示方案
在开发视频应用程序时,客户端有时需要同时显示多路视频,即在同一个窗口中显示多个摄像头捕捉的信息。通常,视频采集卡提供有多路显示的SDK函数,以天敏的VC4000为例,用户可以使用MTASetSplitMode函数分割视频显示窗口,有关天敏SDK函数请参考第2章2.4.4小节 开发包分析。下面笔者以一个实例形式介绍视频多屏显示方案的实现,效果如图8.13所示。

图8.13 视频多屏显示方案
程序设计具体步骤如下:
实例位置:光盘\mr\8\8.2\8.2.2\01
(1)创建一个基于对话框的工程,在对话框中添加按钮、图像等控件,如图8.14所示。

图8.14 对话框设计窗口
(2)在对话框类的头文件中引用天敏SDK开发包中的头文件,并连接相应的库文件。
#include "Sa7134Capture.h"
#include "MediaTransmit.h"
#pragma comment (lib,"Sa7134Capture.lib")
#pragma comment (lib,"MediaTransmit.lib")
(3)向对话框类中添加成员变量。
int m_CardID[4]; //卡号
int m_CurIndex;
int m_BackID; //回放ID
CString m_ServerIP; //服务器IP
int m_Port; //服务器端口
int m_Chanel; //视频通道
(4)定义一个全局函数CmdRespond,用于确定网络连接情况。
void CmdRespond(int nCallId,unsigned char biCmd,int nResult,BYTE *pRetData)
{
CString id;
id.Format("%i",nCallId+1);
if (nResult==6)
AfxMessageBox(id+"路网络资源创建错误!",0,0);
}
(5)在对话框初始化时创建视频显示窗口。
BOOL CClientDlg::OnInitDialog()
{
CDialog::OnInitDialog();
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
SetIcon(m_hIcon, TRUE);
SetIcon(m_hIcon, FALSE);
CRect rc;
m_Panel.GetClientRect(rc);
MTACreateVideoDevice(m_Panel.GetSafeHwnd(),m_hWnd,rc,200,100,2,FALSE);
MTASetSplitMode(2);
SetTimer(1,1000,NULL);
return TRUE;
}
(6)创建一个对话框类CServerInfo,在对话框中添加按钮、编辑框、复选框的控件,如图8.15所示。

图8.15 连接服务器对话框资源窗口
(7)利用类向导窗口为对话框中的控件命名,如图8.16所示。

图8.16 类向导窗口
(8)处理“确定”按钮的单击事件,更新数据,关闭对话框。
void CServerInfo::OnConfirm()
{
UpdateData();
EndDialog(IDOK);
}
(9)在主对话框中处理“连接服务器”按钮的单击事件,设置服务器连接信息和请求的视频通道。
void CClientDlg::OnLinkServer()
{
CServerInfo ServerDlg;
if (ServerDlg.DoModal()==IDOK)
{
m_ServerIP = ServerDlg.m_ServerIP;
m_Port = ServerDlg.m_Port;
MTASetMpeg4Version(MPEG4_V1);
if (!MTALoadLibrary(1008,WORK_AS_CLIENT))
{
MessageBox("加载网络库失败!");
MTAFreeLibrary();
return;
}
for (int m = 0 ; m<4 ; m++)
MTAClearCall(m_CardID[m],FALSE);
CRect rc;
m_Panel.GetClientRect(rc);
MTACreateVideoDevice(m_Panel.GetSafeHwnd(),m_hWnd,rc,240,320,2,FALSE);
MTASetSplitMode(2);
m_CurIndex = 0;
unsigned char data[] = "Admin";
if (ServerDlg.m_Chanel1)
m_CardID[0] = MTANewCall(m_ServerIP.GetBuffer(0),m_Port,0);
if (ServerDlg.m_Chanel2)
m_CardID[1] = MTANewCall(m_ServerIP.GetBuffer(0),m_Port,1);
if (ServerDlg.m_Chanel3)
m_CardID[2] = MTANewCall(m_ServerIP.GetBuffer(0),m_Port,2);
if (ServerDlg.m_Chanel4)
m_CardID[3] = MTANewCall(m_ServerIP.GetBuffer(0),m_Port,3);
for (int i= 0 ; i<4; i++)
{
MTASetVideoOut(m_CardID[i],i);
MTAMakeCall(m_CardID[i],REQ_VI_STREAM,FALSE,data,10,NULL,CmdRespond);
}
}
}
(10)在对话框关闭时清空视频请求的呼叫通道。
void CClientDlg::OnCancel()
{
for (int i= 0; i<4; i++)
{
if (m_CardID[i] != -1)
MTAClearCall(m_CardID[i],FALSE);
}
CDialog::OnCancel();
}
8.2.3 视频存储方案
视频文件在传输中,除了可以实时监控外还应该具有录像功能,这样可以方便以后查阅,视频存储程序运行如图8.17 、图8.18所示。

图8.17 视频单屏显示(服务器端) 图8.18 视频单屏显示(客户端)
客户端程序设计如下:
实例位置:光盘\mr\8\8.2\8.2.3\01
(1)创建一个基于对话框的应用程序,向对话框中添加一个IP控件和一个按钮控件。对话框设计如图8.19 所示。

图8.19 对话框设计
(2)从CSocket类中派生一个子类CClientSocket。在头文件中引用Afxsock.h头文件,目的是使用CSocket类;引用主对话框的头文件,并对主对话框进行前导声明,因为在CClientSocket类中需要定义主对话框类指针。
(3)在应用程序的InitInstance方法中初始化套接字。
WSADATA wsd;
WSAStartup(MAKEWORD(2,2),&wsd);
(4)在对话框类的头文件中引用vfw.h头文件,连接vfw32.lib库文件。在对话框类中定义一个静态方法,作为线程函数。
#include "vfw.h"
static UINT vproc(LPVOID pParam);
(5)在对话框中定义一个CClientSocket对象指针,创建套接字。
BOOL CClientDlg::OnInitDialog()
{
……//此处代码省略
m_pClient = new CClientSocket(this);
if(!m_pClient->Create()) //创建套接字
{
delete m_pClient;
MessageBox("套接字创建失败.");
return FALSE;
}
return TRUE;
}
(6)处理“连接”按钮的单击事件,连接服务器,并在连接以后开始一个线程,连接设备驱动程序。
void CClientDlg::OnButconnect()
{
// TODO: Add your control notification handler code here
UpdateData(TRUE);
CString sip;
m_IP.GetWindowText(sip); //读取服务器IP
int port;
port = 700; //获取端口
if(! m_pClient->Connect(sip,port)) //连接服务器
{
MessageBox("连接服务器失败!");
return;
}
CAPDRIVERCAPS capd;
CAPSTATUS caps;
tran=NULL;
event.ResetEvent();
AfxBeginThread(vproc,(void*)this);
::WaitForSingleObject(event,INFINITE);
if(capDriverConnect(videohwnd,0)==TRUE)
{
::SetParent(videohwnd,*this);
::SetWindowLong(videohwnd,GWL_STYLE,WS_CHILD);
::SetWindowPos(videohwnd,NULL,10,10,400,400,SWP_NOZORDER);
::ShowWindow(videohwnd,SW_SHOW);
capPreviewRate(videohwnd,30);
capPreview(videohwnd,true);
}
else
{
AfxMessageBox("创建失败");
}
capSetCallbackOnFrame(videohwnd,CaptureFun);
}
(7)在线程函数中创建视频捕捉窗口。
UINT CClientDlg::vproc(LPVOID pParam)
{
CClientDlg *pDlg=(CClientDlg*)pParam;
HWND hwnd = capCreateCaptureWindow(NULL,WS_POPUP,0,0,10,10,0,0);
temp = hwnd;
if(hwnd)
{
pDlg->videohwnd=hwnd;
pDlg->event.SetEvent();
}
MSG Msg;
while(GetMessage(&Msg,NULL,0,0))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
(8)通过capSetCallbackOnFrame宏在应用程序中设置预览模式的回调函数。这样当摄像头捕获到每一帧时,就会调用指定的回调函数。用户可以在回调函数中调用capFileSaveDIB宏将帧保存成图片。
void CaptureFun(LPVOID pParam)
{
capFileSaveDIB(temp,"c:\\a.bmp");
}
(9)在对话框中添加ReceiveData方法,用于接收服务器发送的请求信息。
void CClientDlg::ReceiveData(CClientSocket *socket)
{
char buf[2];
DWORD ret = socket->Receive(buf,2);
buf[ret] = 0;
if(ret == SOCKET_ERROR)
{
CString str;
str.Format("接收数据失败,错误码:%d",WSAGetLastError());
MessageBox(str);
return;
}
if(buf[0]=='`')
SendData();
}
(10)在对话框中添加SendData方法,在接收到服务器发送的请求信息以后,向服务器发送图片。
void CClientDlg::SendData()
{
CFile file("c:\\a.bmp",CFile::modeRead);
DWORD size = file.GetLength();
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE,size);
char* lpBuf = (char*)GlobalLock(hGlobal);
file.ReadHuge(lpBuf,size);
int ret = m_pClient->Send(lpBuf,size);
if(ret == SOCKET_ERROR)
{
CString str;
str.Format("发送数据失败,错误码:%d",WSAGetLastError());
MessageBox(str);
return;
}
Sleep(10);
char a[1]={'^'}; //结束符
m_pClient->Send(a,1);
GlobalUnlock(hGlobal);
GlobalFree(hGlobal);
}
(11)处理对话框的WM_DESTROY消息,在窗口关闭时关闭套接字。
void CClientDlg::OnDestroy()
{
CDialog::OnDestroy();
if(m_pClient != NULL)
delete m_pClient;
}
服务器端程序设计步骤如下:
实例位置:光盘\mr\8\8.2\8.2.3\02
(1)创建一个基于对话框的应用程序,向对话框中添加一个图像控件和一个按钮控件。对话框设计如图8.20 所示。

图8.20 对话框设计
(2)打开图像控件的属性窗口,图像控件的属性设置如图8.21 所示。

图8.21 图像控件的属性窗口
(3)在应用程序的InitInstance方法中初始化套接字。
WSADATA wsd;
WSAStartup(MAKEWORD(2,2),&wsd);
(4)从CSocket类派生新类CClientSocket和CServerSocket,在头文件中引用对话框的头文件和afxsock.h头文件,并对对话框类进行前导声明。
(5)在对话框的OnInitDialog方法中定义套接字,并创建一个保存BMP文件的文件夹。
BOOL CServerDlg::OnInitDialog()
{
……//此处代码省略
m_pServer = new CServerSocket(this);
m_pClient = new CClientSocket(this);
char buf[256];
::GetCurrentDirectory(256,buf);
strcat(buf,"\\BMP");
m_folder.Format("%s",buf);
CreateDirectory(m_folder,NULL);
m_num = 1;
return TRUE;
}
(6)绑定套接字,并开始监听客户端。
void CServerDlg::OnButlisten()
{
// TODO: Add your control notification handler code here
if(!m_pServer->Create(700))
{
MessageBox("套接字创建失败");
delete m_pServer;
m_pServer = NULL;
return;
}
if(!m_pServer->Listen())
MessageBox("监听失败");
}
(7)在对话框中添加AcceptConnect方法,用于接受客户端的连接,在接通连接以后开启定时器。
void CServerDlg::AcceptConnect()
{
if(m_pServer->Accept(*m_pClient))
{
SetTimer(1,200,NULL);
}
}
(8)在定时器中向客户端发送请求信息,并显示接收后的图片。
void CServerDlg::OnTimer(UINT nIDEvent)
{
char a[1]={'`'}; //请求信息
int ret = m_pClient->Send(a,1);
CString str;
CFileFind fFind;
if(fFind.FindFile(m_name))
{
HANDLE hBmp = LoadImage(NULL,m_name,IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
if(hBmp!=NULL)
{
m_Picture.SetBitmap((HBITMAP)hBmp);
}
}
CDialog::OnTimer(nIDEvent);
}
(9)在对话框中添加ReceiveData方法,用于接收客户端发送的数据。
void CServerDlg::ReceiveData(CClientSocket *socket)
{
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE,60000);
void* lpBuf = GlobalLock(hGlobal);
CString str;
str.Format("\\%04d.BMP",m_num);
m_name = m_folder + str;
DWORD size = m_pClient->Receive(lpBuf,60000);
if(size == SOCKET_ERROR)
{
CString str;
str.Format("发送数据失败,错误码:%d",WSAGetLastError());
MessageBox(str);
return;
}
if(m_IsStop)
{
m_file.Open(m_name,CFile::modeCreate|CFile::modeWrite);
m_file.WriteHuge(lpBuf,size);
m_IsStop = FALSE;
GlobalUnlock(hGlobal);
GlobalFree(hGlobal);
return;
}
char* temp = (char*)lpBuf;
if((temp[size-1]=='^'))
{
m_file.Close();
m_num++;
m_IsStop = TRUE;
}
else
{
m_file.WriteHuge(lpBuf,size);
GlobalUnlock(hGlobal);
GlobalFree(hGlobal);
}
}
(10)在对话框中添加BMPtoAVI方法,用于将接收的BMP图片转换成AVI文件。
void CServerDlg::BMPtoAVI(CString szAVIName, CString strBmpDir)
{
KillTimer(1);
CFileFind find;
strBmpDir += _T("\\*.*");
AVIFileInit();
AVISTREAMINFO strhdr;
PAVIFILE pfile;
PAVISTREAM ps;
int nFrames =0;
HRESULT hr;
BOOL bFind = find.FindFile(strBmpDir);
while(bFind)
{
bFind = find.FindNextFile();
if(!bFind)
break;
if(!find.IsDots() && !find.IsDirectory())
{
CString str = find.GetFilePath();
FILE *fp = fopen(str,"rb");
BITMAPFILEHEADER bmpFileHdr;
BITMAPINFOHEADER bmpInfoHdr;
fseek( fp,0,SEEK_SET);
fread(&bmpFileHdr,sizeof(BITMAPFILEHEADER),1, fp);
fread(&bmpInfoHdr,sizeof(BITMAPINFOHEADER),1, fp);
BYTE *tmp_buf = NULL;
if(nFrames ==0 )
{
AVIFileOpen(&pfile,szAVIName,OF_WRITE | OF_CREATE,NULL);
memset(&strhdr, 0, sizeof(strhdr));
strhdr.fccType = streamtypeVIDEO;
strhdr.fccHandler = 0;
strhdr.dwScale = 1;
strhdr.dwRate = 15;
strhdr.dwSuggestedBufferSize = bmpInfoHdr.biSizeImage ;
SetRect(&strhdr.rcFrame, 0, 0, bmpInfoHdr.biWidth,
bmpInfoHdr.biHeight);
hr = AVIFileCreateStream(pfile,&ps,&strhdr);
}
tmp_buf = new BYTE[bmpInfoHdr.biWidth * bmpInfoHdr.biHeight * 3];
fread(tmp_buf, 1, bmpInfoHdr.biWidth * bmpInfoHdr.biHeight * 3, fp);
fclose(fp);
hr = AVIStreamSetFormat(ps,nFrames,&bmpInfoHdr,sizeof(bmpInfoHdr));
hr = AVIStreamWrite(ps, nFrames, 1, (LPBYTE)tmp_buf,
bmpInfoHdr.biSizeImage,
AVIIF_KEYFRAME, NULL, NULL);
nFrames ++;
delete [] tmp_buf;
}
}
AVIStreamClose(ps);
if(pfile != NULL)
AVIFileRelease(pfile);
AVIFileExit();
MessageBox("保存成功");
}
(11)处理“保存”按钮的单击时间,当按钮被按下时调用BMPtoAVI方法。
void CServerDlg::OnButplay()
{
CString str = m_folder + "\\";
str += "kinescope.avi";
BMPtoAVI(str,m_folder);
}
(12)处理对话框的WM_DESTROY消息,在窗口关闭时关闭套接字。
void CServerDlg::OnDestroy()
{
CDialog::OnDestroy();
if(m_pServer != NULL)
delete m_pServer;
if(m_pClient != NULL)
delete m_pClient;
}
8.2.4 视频回放设计方案
AVI是现今比较流行的影音文件,很多视频传输程序的录像都是AVI文件。而视频回放就是将这些AVI录像播放出来,视频回放程序如图8.22 所示。

图8.22 视频回放
在播放AVI文件时用到了Windows Media Player控件,该控件的主要方法如表8.1所示。
表8.1 Windows Media Player控件的主要方法
|
方法 |
描述 |
|
GetMute |
获得静音属性 |
|
GetPlayState |
获得播放状态 |
|
GetVolume |
获得音量 |
|
Open |
打开播放文件 |
|
Pause |
暂停 |
|
Play |
播放 |
|
SetShowControls |
设置是否显示控制面板 |
视频回放程序设计步骤如下:
实例位置:光盘\mr\8\8.2\8.2.4\01
(1)创建一个基于对话框的应用程序。
(2)单击“Project/Add To Project/Components and Controls”菜单项,打开Components and Controls Gallery窗口,双击Registered ActiveX Controls文件夹,选择Windows Media Player文件,如图8.23所示。

图8.23 Components and Controls Gallery对话框
单击“Insert”按钮,选择要导入工程的类,单击“OK”按钮,Windows Media Player控件即被添加到控件面板中,如图8.24所示。


图8.24 控件面板
注意:Windows Media Player控件随系统的不同而有所区别。
(3)向对话框中添加一个Windows Media Player控件和3个Button控件,如图8.25所示。

图8.25 对话框设计
(4)打开Windows Media Player控件的属性窗口,Windows Media Player控件的属性设置如图8.26所示。

图8.26 属性设置
(5)处理“打开”按钮的单击事件,用于打开一个AVI文件并且使用Windows Media Player控件打开的AVI文件。
void CPlayAVIDlg::OnOpen()
{
CFileDialog dlg(TRUE,NULL,NULL,OFN_HIDEREADONLY,"AVI文件|*.avi||");
if(dlg.DoModal()==IDOK)
{
m_StrName=dlg.GetPathName();
}
if(m_StrName != "")
{
m_AviPlayer.Open(m_StrName);
}
}
(6)处理“停止”按钮的单击事件,用于停止AVI文件的播放。
void CPlayAVIDlg::OnStop()
{
if(m_AviPlayer.GetPlayState() != 2)
return;
m_AviPlayer.Stop();
}
(7)处理“退出”按钮的单击事件,用于退出应用程序。
void CPlayAVIDlg::OnExit()
{
CDialog::OnCancel();
}






