视频聊天软件

学习了Visual C++开发宝典这本书,动手学习了VFW技术来学习开发一个视频聊天软件。

视频聊天软件是通过实时获取视频头重的图像数据,并将图像转换成可进行网络传输的二进制数据发送到另一端聊天软件上,当对方接收到传过来的图片数据后,再将其转换成图片信息并绘制在视频输出窗口。当聊天的双方都是想了这一功能后即可进行交互式的视频聊天。

主要的视频函数:

 (1)#capCreateCaptureWindow函数:用于创建一个视频捕捉窗口,返回值是视频捕捉窗口句柄。语法如下:

      HWND VFWAPI capCreateWindow(LPCSTR lpszWindowName,DWORD dwStyle,int y,int nWidth,int nHeight,HWND hWnd,int nID);

 (2)capDriverConnect宏:主要将视频捕捉窗口连接的视频驱动程序上。语法如下:

      BOOL capDriverConnect(hwnd,iIndex);

 (3)capPreviewRate宏:在预览模式下设置帧的显速率。语法如下:

      BOOL capPreviewRate(hwnd,wMS);

 (4)capPreview宏:用于激活或禁止预览模式。在预览模式下,视频设备捕捉的数据以帧的形式传递到系统内存中,然后通过GDI函数显示在视频捕捉窗口中。语法如下:

      BOOL capPreview(hwnd,f)

系统实现过程:

 (1)新建一个基于对话框的工程,简历服务器端应用程序。

 (2)在对话框中添加编辑框插件,用于发送文本信息,添加RichEdit控件,用于显示发送和接收的文本信息。

 (3)在窗体初始化时创建CSocket类并初始化视频头,将通过视频头获取的图像显示在窗体中,代码如下:

BOOL CServerDlg::OnInitDialog()
{
 CDialog::OnInitDialog();

 // Add "About..." menu item to system menu.

 // IDM_ABOUTBOX must be in the system command range.
 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);
  }
 }

 // Set the icon for this dialog.  The framework does this automatically
 //  when the application's main window is not a dialog
 SetIcon(m_hIcon, TRUE);   // Set big icon
 SetIcon(m_hIcon, FALSE);  // Set small icon
 
 m_ServerSock.SetDlg(this);
 m_ClientSock.SetDlg(this);
 // TODO: Add extra initialization here
 
 m_ServerSock.Create(845);
 m_ServerSock.Listen();

 

 m_hWndVideo = capCreateCaptureWindow(NULL,WS_POPUP, 1, 1, 10, 10, m_hWnd, 0);
 if (capDriverConnect(m_hWndVideo, 0))
 {
  ::SetParent(m_hWndVideo,*this);
  ::SetWindowLong(m_hWndVideo,GWL_STYLE,WS_CHILD);

  CRect wndRC;
  m_Panel.GetClientRect(wndRC);
  m_Panel.MapWindowPoints(this,wndRC);
  wndRC.DeflateRect(1,1,1,1);

  ::SetWindowPos(m_hWndVideo,NULL,wndRC.left,wndRC.top,wndRC.Width(),wndRC.Height(),SWP_NOZORDER);
  ::ShowWindow(m_hWndVideo,SW_SHOW);

  CAPDRIVERCAPS caps;
  capDriverGetCaps(m_hWndVideo,sizeof(caps),&caps);

  if (caps.fHasOverlay)
   capOverlay(m_hWndVideo,TRUE);

  CAPTUREPARMS params;
  capCaptureGetSetup(m_hWndVideo,&params,sizeof(params));

  params.fYield           = TRUE;
  params.fAbortLeftMouse  = FALSE;
  params.fAbortRightMouse = FALSE;
  params.fLimitEnabled    = FALSE;
  params.vKeyAbort        = FALSE;
  params.fCaptureAudio    = FALSE;
  
  capCaptureSetSetup(m_hWndVideo,&params, sizeof(params));
  capSetCallbackOnVideoStream(m_hWndVideo, EncodeCallback);

  capPreviewRate(m_hWndVideo,30);
  capPreview(m_hWndVideo, TRUE);
  capCaptureSequenceNoFile(m_hWndVideo);
 
  m_bSendImage = FALSE;
 }
 SetTimer(1, 200, NULL);
 return TRUE;  // return TRUE  unless you set the focus to a control
}

 (4)实现EncodeCallback函数,该函数是视频捕捉时使用的回调函数。该函数的实现用于获取视频捕捉窗口的图像数据并存储到Cpackage类中所对应的数据缓冲区中,代码如下:

LRESULT WINAPI EncodeCallback(HWND hWnd, LPVIDEOHDR lpVHdr)
{
 if (lpVHdr->dwFlags&VHDR_DONE)
 {
  static BOOL bSend = TRUE;
  CServerDlg* pDlg = (CServerDlg*)AfxGetMainWnd();
  int nState = pDlg->m_Video.GetCheck();
  if (nState == 1)
  {
   if (pDlg->m_bSendImage==FALSE)
   {
    //获取图象数据
    BITMAPINFO bmpInfo;
    capGetVideoFormat(pDlg->m_hWndVideo, &bmpInfo, sizeof(BITMAPINFO));
    //确定图象数据大小
    int nSize = bmpInfo.bmiHeader.biSizeImage;
    bSend = FALSE;
    HGLOBAL hGlobal = GlobalAlloc(GHND, nSize + sizeof(BITMAPINFOHEADER));
    BYTE* pData = (BYTE*)GlobalLock(hGlobal);
    memcpy(pData, &bmpInfo.bmiHeader, sizeof(BITMAPINFOHEADER));

    BYTE* pTmp = pData;
    pTmp += sizeof(BITMAPINFOHEADER);
    memcpy(pTmp, lpVHdr->lpData, nSize);

    int nPackSize = sizeof(CPackage) + nSize + sizeof(BITMAPINFOHEADER);
    
    if (pDlg->m_hGlobal != NULL)
    {
     GlobalFree(pDlg->m_hGlobal);
     pDlg->m_hGlobal = NULL;
    }

    pDlg->m_hGlobal = GlobalAlloc(GHND, nPackSize);

    BYTE* pSendData = (BYTE*)GlobalLock(pDlg->m_hGlobal);
    CPackage* pPackage = (CPackage*)pSendData;
    pPackage->m_Type = ptImage;
    pPackage->m_dwContent = nSize + sizeof(BITMAPINFOHEADER);
    pPackage->m_dwData = nSize + sizeof(BITMAPINFOHEADER);
    pPackage->m_dwSize = sizeof(CPackage);
    pTmp = pSendData;
    pTmp += sizeof(CPackage);
    memcpy(pTmp, pData, nSize + sizeof(BITMAPINFOHEADER));

    GlobalUnlock(hGlobal);
    GlobalFree(hGlobal);
    GlobalUnlock(pDlg->m_hGlobal);
   
    pDlg->m_bSendImage = TRUE;
   }
  }
 }
  return 1;
}

 (5)在窗体上单机“发送”按钮会将文本信息发送的客服端,代码如下:

void CServerDlg::OnSend()
{
 CString szSendInfo;
 m_SendInfo.GetWindowText(szSendInfo);
 if (!szSendInfo.IsEmpty())
 {
  //填充数据包
  int nLen = szSendInfo.GetLength();
  HGLOBAL hGlobal = GlobalAlloc(GHND, sizeof(CPackage) + nLen);
  BYTE* pData = (BYTE*)GlobalLock(hGlobal);
  CPackage *pPackage = (CPackage*) pData;
  pPackage->m_Type = ptText;
  pPackage->m_dwContent = nLen;
  pPackage->m_dwSize = sizeof(CPackage);
  pPackage->m_dwData = nLen;
  BYTE* pTmp = pData;
  pTmp += sizeof(CPackage);
  memcpy(pTmp, szSendInfo, nLen);
  m_ClientSock.Send(pData, sizeof(CPackage) + nLen);
  GlobalUnlock(hGlobal);
  GlobalFree(hGlobal);
  m_SendInfo.SetWindowText("");
  m_InfoList.SetSel(-1, 0);
  m_InfoList.ReplaceSel("服务器say: \n");
  m_InfoList.SetSel(-1, 0);
  m_InfoList.ReplaceSel(szSendInfo);
  m_InfoList.SetSel(-1, 0);
  m_InfoList.ReplaceSel("\n");


 }
}

 (6)在窗体中实现ReceiverData方法,该方法用于接收客户端发送的服务端的文本数据并显示在RichEdit控件中,代码如下:

void CServerDlg::ReceiveData()
{
 static int nSize = sizeof(CPackage) + 1024;
 HGLOBAL hGlobal = GlobalAlloc(GHND, nSize);
 BYTE* pBuffer = (BYTE*)GlobalLock(hGlobal);

 int nFact = m_ClientSock.Receive(pBuffer, nSize);
 if (nFact > 0)
 {
  CPackage* pPackage = (CPackage*)pBuffer;
  int nDataLen = pPackage->m_dwContent;
  if (pPackage->m_Type==ptText)
  {
   char* pszData = (char*)pBuffer;
   pszData += sizeof(CPackage);
   m_InfoList.SetSel(-1, 0);
   m_InfoList.ReplaceSel("客户端say: \n");
   m_InfoList.SetSel(-1, 0);
   m_InfoList.ReplaceSel(pszData); 
   m_InfoList.SetSel(-1, 0);
   m_InfoList.ReplaceSel("\n");    
  } 
 }
 GlobalUnlock(hGlobal);
 GlobalFree(hGlobal);
}

 (7)在窗体上实现 OnTimer消息事件,该事件用于发送视频头捕获的图像信息到客户端,代码如下:

void CServerDlg::OnTimer(UINT nIDEvent)
{
 if (m_bSendImage)
 {
  if (m_hGlobal != NULL)
  {
   int nSize = GlobalSize(m_hGlobal);
   BYTE* pData = (BYTE*)GlobalLock(m_hGlobal);
   m_ClientSock.Send(pData, nSize); 
   m_bSendImage = FALSE;
   GlobalUnlock(m_hGlobal);
  }
 }
 CDialog::OnTimer(nIDEvent);
}
 (8)新建一个基于对话框的应用程序,创建视频聊天程序的客户端。

 (9)在窗体上添加一个编辑框控件,用于记录需要发送的文本信息,在窗体上添加一个Rich编辑框控件,用于显示发送和接收的文本信息。添加一个按钮控件,用于向服务器端发送文本信息。添加一个Image控件,用于显示服务器端传递过来的视频图像信息。

 (10)单击窗体上的“发送”按钮,将编辑框控件中的文本信息发送到服务器端,代码如下:

void CClientDlg::OnSend()
{
 CString szSendInfo;
 m_SendContent.GetWindowText(szSendInfo);
 if (!szSendInfo.IsEmpty())
 {
  //填充数据包
  int nLen = szSendInfo.GetLength();
  HGLOBAL hGlobal = GlobalAlloc(GHND, sizeof(CPackage) + nLen);
  BYTE* pData = (BYTE*)GlobalLock(hGlobal);
  CPackage *pPackage = (CPackage*) pData;
  pPackage->m_Type = ptText;
  pPackage->m_dwContent = nLen;
  pPackage->m_dwSize = sizeof(CPackage);
  pPackage->m_dwData = nLen;
  BYTE* pTmp = pData;
  pTmp += sizeof(CPackage);
  memcpy(pTmp, szSendInfo, nLen);
  m_ClientSock.Send(pData, sizeof(CPackage) + nLen);

  GlobalUnlock(hGlobal);
  GlobalFree(hGlobal);
  m_SendContent.SetWindowText("");
  m_InfoList.SetSel(-1, 0);
  m_InfoList.ReplaceSel("客户端say: \n");
  m_InfoList.SetSel(-1, 0);
  m_InfoList.ReplaceSel(szSendInfo);
  m_InfoList.SetSel(-1, 0);
  m_InfoList.ReplaceSel("\n");
 }  
}
 (11)在窗体类中实现HandleRacData方法,该方法用于接收服务器端向客户端发送的文本数据和视频图像数据 ,代码如下:

void CClientDlg::HandleRecData(CPackage *pPackage)
{
 if (pPackage != NULL)
 {
  if (pPackage->m_Type==ptText) //文本数据
  {
   char* szData = (char* )pPackage->m_Data;
   m_InfoList.SetSel(-1, 0);
   m_InfoList.ReplaceSel("服务器say: \n");
   m_InfoList.SetSel(-1, 0);
   m_InfoList.ReplaceSel(szData);
   m_InfoList.SetSel(-1, 0);
   m_InfoList.ReplaceSel("\n"); //添加换行符
  }
  else if (pPackage->m_Type==ptImage) //图像数据
  {
   BITMAPINFOHEADER bmpHeader;
   BITMAPINFO bmpInfo;
   bmpInfo.bmiHeader = bmpHeader;
   memcpy(&bmpHeader, pPackage->m_Data, sizeof(bmpHeader));
   BYTE* pBmpData = pPackage->m_Data;
   pBmpData += sizeof(bmpHeader);
   CDC* pDC = GetDC();
   HBITMAP hBmp = CreateDIBitmap(pDC->m_hDC, &bmpHeader, CBM_INIT, pBmpData, (BITMAPINFO*)&bmpHeader, DIB_RGB_COLORS);
   HBITMAP hOldBmp = m_Image.SetBitmap(hBmp);
   if (hOldBmp != NULL)
   {
    DeleteObject(hOldBmp);
   }
   //显示图像
   //...
   ReleaseDC(pDC);
  }  
 
 }
}

你可能感兴趣的:(C++,VWF,CSocket)