发布新日志

  • 一种实现视频捕捉的简单方法

    2009-05-28 16:50:41

     摘 要  当前,随着视频监控、可视电话、电视会议等多媒体应用技术的迅速发展,对数字视频捕获技术的要求越来越高。实现视频捕获的方法有很多,本文主要介绍了微软公司VFW(Video for Windows)软件包中的AVICap窗口类的成员函数和一些关键宏,以及与编写视频捕捉程序紧密相关的几个结构体。最后,通过一个视频捕捉应用程序,展示了如何具体实现视频捕捉,以及要注意的相关问题。
        关键词 VFW;AVICap窗口类;视频捕捉;捕捉窗口;视频对话框
     
    0  引言
     
        随着数字视频监控、可视电话、电视会议等多媒体技术应用的迅速兴起,越来越多的场合需要对数字视频信号进行捕捉。通常来说,捕捉实时数字视频信号是一个比较复杂的过程,但是,微软公司开发的Video for Windows SDK软件包中AVICap窗口类提供了一条捷径,借助于该窗口类,能够很方便地将视频捕捉的各种功能组合到应用程序中去。

    1  AVICap窗口类简介

        ACICap支持实时的视频流捕捉和视频单帧捕捉。使用ACICap窗口类可创建具有一些基本功能的窗口,例如视频图像的预览、设置捕捉参数的对话框、音频、视频捕捉的独立控制等。ACICap中的回调函数可使应用程序向用户提供有关捕捉的状态,包括进行的过程指示,以及任何可能产生的错误。开发人员可以设置一个标志用来指示在什么时候采集到音频,什么时候采集到视频。这样,应用程序可以直接使用数据而无需写入AVI文件中。
        AVICap窗口类提供了以下功能:
        ◆  单独控制音频、视频的采集;
        ◆  采用overlay(实时叠加)或preview(预览)方式显示视频图像;
        ◆  与ICM和ACM同时工作,将音频和视频数据直接压缩到应用程序中;
        ◆  将音频、视频流直接压缩入AVI文件而不需要开发人员详细了解AVI文件格式的细节;
        ◆  动态了解视频和音频的输入设备;
        ◆  创建、保存和载入调色板;
        ◆  将图像调色板拷贝到剪切板上;
        ◆  控制MCI设备;
        ◆  捕捉单帧图像并以DIB格式保存。

    2  AVICap窗口类的主要函数、宏简介

        AVICap提供给开发人员一整套函数,用这些函数可以实现许多视频捕捉程序所需的窗口管理;同时,在整个捕捉过程中仍然保留全部的控制。这些函数形式简单,采用基于消息的接口来获取硬件里的音频和视频信号,同时控制着视频流采集到磁盘的过程。
        ACICap的函数能够使开发人员以很少的投入来创建具有基本捕捉功能的采集程序,这些函数是高级的、经过优化的、为开发人员创建具有自己特性的应用程序留有很大的灵活性。 
        下面是ACICap提供给开发人员编写捕捉程序的几个重要函数和宏。
        ◆ 创建捕捉窗口
    HWND VFWAPI capCreateCaptureWindow(
          LPCSTR lpszWindowName,// 捕捉窗口名字
          DWORD dwStyle,// 捕捉窗口的风格
          int x,// 窗口左上角x轴坐标
          int y,// 窗口左上角y轴坐标
          int nWidth,// 窗口的宽度
          int nHeight,// 窗口的高度
          HWND HWnd,// 父窗口句柄
          Int nID// 捕捉窗口的ID号
    );
        如果该函数调用成功 则函数返回窗口的句柄 否则函数返回NULL。
        ◆ 捕捉窗口与设备连接
    BOOL capDriverConnect(
        hwnd,// 捕捉窗口的句柄
        iIndex// 设备驱动号
    );
        如果连接成功,返回TRUE否则函数返回FALSE。
        ◆ 获取视频捕捉设备功能
    BOOL capDriverGetCaps(
        hwnd,// 捕捉窗口句柄
        psCaps,// 指向一个用于存储返回值CAPDRIVERCAPS结构的指针
        wSize// CAPDRIVERCAPS结构占用的字节数
    );
        如果捕获窗口与捕获驱动连接成功,则返回TURE,否则,返回FALSE。
        ◆ 设置捕捉设备的参数
    BOOL capCaptureSetSetup(
       hwnd,// 设置窗口句柄
       psCapParms,// 指向一个用于存储返回值的CAPDRIVERCAPS结构的指针
       wSize// CAPDRIVERCAPS结构占用的字节数
    );
        设置成功返回TURE,否则返回FALSE。
    ◆ 设置处理回调状态
    BOOL capSetCallbackOnStatus(
       hwnd,// 捕捉窗口句柄
       fpProc// 指向状态回调函数的指针
    );
    ◆ 设置错误处理
    BOOL capSetCallbackOnError(
       hwnd,// 捕捉窗口句柄
       fpProc// 指向错误回调函数的指针
    );
        ◆ 设置数据文件名
    BOOL capFileSetCaptureFile(
       hwnd,// 捕捉窗口句柄
       szName// 指向字符串的指针,字符串内容为捕获文件名
    );
       设置一个由szName指向的字符串作为文件名,用于存储从捕捉窗口hWnd采集的视频图像数据,成功,返回TRUE,否则返回FALSE。
        ◆ 开始捕捉视频
    BOOL capCaptureSequence(
           hwnd
    );
       触发程序开始捕捉视频图像并将其保存到数据文件。
        ◆ 视频源设置对话框
        BOOL capDlgVideoSource( hwnd ); // hwnd:捕捉窗口句柄
        视频源设置对话框对于每一个捕捉驱动程序来说,是唯一的。而且,有些驱动程序不一定支持这一功能。应用程序可以通过检测CAPDRIVERCAPS结构的成员变量fHasDlgVideoSource来判断驱动程序是否支持这一功能。
        ◆ 视频格式设置对话框
        BOOL capDlgVideoFormat( hwnd ); // hwnd:捕捉窗口句柄
    视频格式设置对话框对于每一个捕捉驱动程序来说,是唯一的。而且,有些驱动程序不一定支持这一功能。应用程序可以通过检测CAPDRIVERCAPS结构的成员变量fHasDlgVideoFormat来判断驱动程序是否支持这一功能。
         ◆ 视频显示方式设置对话框
        BOOL capDlgVideoDisplay( hwnd ); // hwnd:捕捉窗口句柄
    视频格式设置对话框对于每一个捕捉驱动程序来说,是唯一的。而且,有些驱动程序不一定支持这一功能。应用程序可以通过检测CAPDRIVERCAPS结构的成员变量fHasDlgVideoDisplay来判断驱动程序是否支持这一功能。
        ◆ 视频压缩设置对话框
        BOOL capDlgVideoDisplay( hwnd ); // hwnd:捕捉窗口句柄
        视频格式设置对话框允许用户在视频捕获期间选择不同的压缩器。

    3  几个重要结构

        编写视频捕捉程序往往要用到以下与视频捕捉相关的结构
        (1)CAPTUREPARMS:包括控制视频流捕捉过程的参数,这一结构被用来得到和设置影响捕捉速率、捕捉时的缓冲区数目、以及捕捉如何结束时的参数。
        (2)CAPSTATUS:定义了捕捉窗口的当前状态,如:以象素为单位表示图像的高、宽、 预览和重叠方式的标志量,尺寸缩放的标志量等。
    因为捕捉窗口的状态随各种各样的消息而改变,所以当应用程序需要功能菜单项,决定捕捉窗口的真实状态或者调用视频格式对话框时,都应该更新这一结构中的信息。
        (3)CAPDRIVERCAPS:定义了视频捕捉驱动程序的功能,如:驱动程序的数目索引 是否支持视频叠加功能等。当应用程序将捕捉窗口与视频捕捉驱动程序相连接时,应该发送消息WM_CAP_DREVER_GET_CAPS或者调用宏capDriverGetCaps将驱动程序的功能拷贝一份到该结构中。
        (4)VIDEOHDR:定义了视频数据块的头信息,其数据成员lpData(指向数据缓存的指针),和dwBufferLenth(数据缓存的大小)经常用到。

    4  视频捕捉举例

        这是一个视频捕捉应用程序,实现了视频捕捉的基本功能,并且可以设置视频的来源、视频的格式等。
    #include "Vfw.h"
    #include <windowsx.h>
    #pragma comment( lib, "Vfw32.lib" )   // 使用Vfw32.lib库
     
    LRESULT CALLBACK capVideoStreamCallback(HWND hWnd,LPVIDEOHDR lpVHdr);
    HWND hMyWnd;
    LPBITMAPINFO lpbi;   // 视频格式
    void CWin32VideoCaptureDlg::OnBnClickedOk()
    {
    hMyWnd=this->m_hWnd;
    // Step 1. 建立捕获窗口
    HWND hWndC=capCreateCaptureWindow(
    (LPSTR)"MyCaptureWindow",
    WS_CHILD | WS_VISIBLE,
    0,0,160,120,
    this->m_hWnd,
    0);
    // Step 2. 与驱动程序建立连接
    LRESULT fOK = ::SendMessage (hWndC, WM_CAP_ DRIVER_ CONNECT, 0, 0L);
     
    // Step 3. 列举出目前系统所拥有的捕获驱动程序
             char szDeviceName[80];
             char szDeviceVersion[80];
             CString DeviceList;
             for (int wIndex = 0; wIndex < 10; wIndex++)         {
                if (capGetDriverDescription (wIndex,szDeviceName,\
                                sizeof (szDeviceName), szDeviceVersion, \
                                sizeof (szDeviceVersion)))     {
                                // 列出系统已安装的驱动程序,让用户选择
                                DeviceList.Append(szDeviceName);
                       }
             }
             MessageBox(DeviceList,"系统中 Capture Driver 描述");
     
    // Step 4. 获得驱动程序的性能
        //我们据此判断使用者是否可以设定 Video Source 和 Video Format
        CAPDRIVERCAPS CapDriverCaps;
        ::SendMessage (hWndC, WM_CAP_DRIVER_GET _CAPS, \
                       sizeof (CAPDRIVERCAPS), (LONG) (LPVOID) &CapDriverCaps);
     
    // Step 5.选择视频源
             if (CapDriverCaps.fHasDlgVideoSource)
                       capDlgVideoSource(hWndC);  
     
    // Step 6.调整视频格式
           CAPSTATUS CapStatus;
             if (CapDriverCaps. FhasDlg VideoFormat){
                       capDlgVideoFormat(hWndC);
                       capGetStatus(hWndC, &CapStatus, sizeof (CAPSTATUS));
                       }
    // Step 7.调整视频输出特性(亮度,对比度,色深)
             if (CapDriverCaps.fHasDlgVideoDisplay)
                       capDlgVideoDisplay(hWndC);
     
    // Step 8. 获得硬件状态
             BOOL bOK=capGetStatus(hWndC, &CapStatus, sizeof (CAPSTATUS));
     
    // Step 9. 调整捕捉窗口大小
             ::SetWindowPos(hWndC, NULL, 0, 0, CapStatus. uiImage Width, \
                        CapStatus.uiImageHeight,
    SWP_NOZORDER | SWP_NOMOVE);
     
    // Step 10. 设置视频格式
             DWORD dwSize;
             dwSize = capGetVideoFormatSize(hWndC);
             lpbi = (LPBITMAPINFO) GlobalAllocPtr(GHND, dwSize);
             capGetVideoFormat(hWndC, lpbi, dwSize);
     
    // Step 11. 视频预览
             capPreviewRate(hWndC, 66);    
             capPreview(hWndC, TRUE);     
     
    // Step 12. 当填满一帧后,调用回调函数处理
    BOOL bOk= capSetCallbackOnFrame(hWndC, capVideo StreamCallback);
    }
    int gdwFrameNum=0;
    char *gachBuffer=new char[100];
    LRESULT CALLBACK capVideoStreamCallback(HWND hWnd,LPVIDEOHDR lpVHdr){
       if (!hWnd)
            return FALSE;
            wsprintf(gachBuffer, "Preview frame# %ld 一个pixel=%d bits; 宽=%d; 高=%d;
                   (RGB)=(%d,%d,%d) 使用长度=%d" ,\
                   gdwFrameNum++, lpbi->bmiHeader. biBit Count,\
    lpbi->bmiHeader.biWidth, lpbi-> bmiHeader.  biHeight,\
                                   (int)*(lpVHdr->lpData),\
                                   (int)*(lpVHdr->lpData+1),\
                                   (int)*(lpVHdr->lpData+2),\
                                   lpVHdr->dwBytesUsed  );
            SetWindowText(hMyWnd, (LPSTR)gachBuffer);
            return (LRESULT) TRUE ;
      }
        这里要指出的一点是,如果在程序Link中出现不能识别函数的错误时,可以采取两种方式:
        * 在#include<vfw.h>后加入一行预编译代码:#pragma  comment(lib,“vfw32”)
        * 或者,在Link设置中加入vfw32.lib。

    5  结束语

        微软公司开发的Video For Windows具有简单易用、开发容易的优点,可以使得不了解组件编程技术的软件编程人员快速开发出适合自己的视频捕获应用程序。但是,它也具有一些不足之处,诸如:不能处理音、视频同步问题;不支持变长帧编码技术等。针对这些不足,微软目前使用DirectShow与WDM Stream Class来解决问题,但是,这些技术比较复杂,实现起来相对困难,因此,可以预见,在今后的一段时间里,AVICap技术仍将是视频捕获实现的重要选项。

    参考文献

        1  Microsoft Inc. Video for Windows Developed Toolkit Programming Guide. 2001
        2  张基温,贾中宁,李伟. Visual C++ 程序开发基础. 北京:高等教育出版社. 2002
        3 王超龙,陈志华. Visual C++ 6.0 入门与提高. 北京:人民邮电出版社. 2002
        作者简介:沈 旭 (1979-),男,山东单县人,硕士,计算机专业教师,主要研究方向:嵌入式系统、视频水印等,sjrgl@sina.com
  • VFW视频采集详细介绍-转

    2009-05-27 18:29:15

    关键词 VFW 视频采集 VC

    作者(原创)逄格民 2007-11-06

    刚刚做了一个利用VFW(Video For Windows)的视频采集程序,就想写出来,给需要的人分享一下。程序并不复杂,
    关键是在没人指导的情况下,学习是比较痛苦和漫长的过程,我经历了这个过程,如果大家想避免走弯路,直接看我
    下面的解释就好了。由于我仅仅作出了结果,对很多东西的理解也许并不完全正确或者是完全错误,愿请指教。

    提前说一句,我的程序是在Visual C++6.0平台下写的。

    下面我慢慢说,你也慢慢听。

    1 什么是VFW

    VFW 是微软的一个软件包,至少可以用来开发视频采集程序,当然还有别的用处,但不是我想关心的。VFW提供了
    基于消息的接口,而这些接口,也可以利用它本省定义的宏来实现。

    2 怎么使用VFW

    写之前提示一句,可以参照MSDN看下面的内容,一定会更好。

    (1)创建一个基于对话框的程序,工程名称Grasp

    因为要用VFW,所以要包含头文件

    可在GraspDlg.h中加入 #include<Vfw.h>,然后Project

    ->Settings,在link标签页的Object/library modules :里面加入

    Vfw32.lib

    (2)在CGraspDlg类中添加一个窗口句柄

    HWND m_hVideo;

    (3)利用capCreateCaptureWindow函数创建窗口,并且得到返回的窗口句柄。

    m_hVideo=::capCreateCaptureWindow("Me",WS_CHILD | WS_VISIBLE,
    0,0,500,500,m_hWnd,0);

    上面这个函数写在BOOL CGraspDlg::OnInitDialog()中。参数m_hWnd是你的工程中

    对话框的句柄,窗口类中都有这个成员变量,而对话框的类是窗口类的子类,记得?

    (4)用capSetCallbackOnFrame宏注册回调函数,也写在BOOL CGraspDlg::OnInitDialog()中。

    capSetCallbackOnFrame(m_hVideo, FrameCallbackProc);

    上面第二个参数是回调函数的地址,名字可以自己来定义,但是回调函数必须有如下参数和返回值。

    LRESULT CALLBACK FrameCallbackProc(HWND hWnd, LPVIDEOHDR lpVHdr);
    人家规定的,咱们也没办法,就照着写就好啦。

    解释一下,什么是回调函数呢,它有什么用处?

    回调函数,就是你自己写的函数,符合规定的参数和返回值类型,符合规定的调用约定,比如上面这个函数
    就是回调函数,参数和返回值类型都是规定好的,调用约定为CALLBACK,CALLBACK其实是一个宏
    #define CALLBACK __stdcall
    满足一定条件时,此函数可以被系统自动调用,在回调函数当中,你可以写自己的代码完成一定功能。
    比如在这里,用capSetCallbackOnFrame(m_hVideo, FrameCallbackProc)注册后,当每得到一桢数据后,系统就
    调用函数FrameCallbackProc。
    (5)因为注册了回调函数,所以,当然要自己写出这个函数了。在GraspDlg.cpp中,且在
    BOOL CGraspDlg::OnInitDialog()函数之前写下面代码:

    LRESULT CALLBACK FrameCallbackProc(HWND hWnd, LPVIDEOHDR lpVHdr)
    {
    if (!ghVideo)
    return FALSE;

    return (LRESULT) TRUE ;
    }
    目前为止,该回调函数还没有什么作用,一会儿我们再来编写函数当中的代码,现在我就写的话,你也不见得看懂,
    不是么。一会儿写的话,你就可以轻松明白了。
    注意在这个函数中的ghVideo 了么?其实就和上面的m_hVideo一样,可是这里是全局函数,m_hVideo是对话框类的成员变量,
    我写m_hVideo编译器是不认识的,对吧,所以,我又在GraspDlg.cpp当中定义了一个全局变量
    HWND ghVideo;
    并且,在m_hVideo=::capCreateCaptureWindow("Me",WS_CHILD | WS_VISIBLE,
    0,0,500,500,m_hWnd,0);
    之后加上一句ghVideo=m_hVideo; 这样就可以用ghVideo了。
    (6)在BOOL CGraspDlg::OnInitDialog()中继续添加如下代码:
    char szDeviceName[80];
    char szDeviceVersion[80];
    int wIndex;

    for (wIndex = 0; wIndex < 10; wIndex++)
    {
    if (capGetDriverDe
    scription (wIndex, szDeviceName,
    sizeof (szDeviceName), szDeviceVersion,
    sizeof (szDeviceVersion)) )
    {
    if(capDriverConnect(m_hVideo,wIndex))
    {

    }
    }

    }
    上面代码中,capGetDriverDe
    scription是列举所有可用视频的驱动程序,如果列举成功,用capDriverConnect进行连接。
    其实,我的机器上就装了一个摄像头,所以,只有当wIndex=0的时候,列举成功,并且连接也成功。这段代码好像很奇怪,因为
    列举成功之后,不论是否连接上,都没有做任何事情。其实可以用下面代码代替:

    char szDeviceName[80];
    char szDeviceVersion[80];

    //Get Driver description
    //and the code can also be deleted as you want.
    capGetDriverDe
    scription (0 szDeviceName,
    sizeof (szDeviceName), szDeviceVersion,
    sizeof (szDeviceVersion));
    //connect window to driver
    capDriverConnect(m_hVideo,0);
    (7)到这里,再加下面两句话你就会有成就感了,在BOOL CGraspDlg::OnInitDialog()中继续添加如下代码:
    capPreviewRate(m_hVideo, 40); // 设置Preview模式的显示速率
    capPreview(m_hVideo, TRUE); //启动Preview模式
    如果到此为止,已经完成了视频采集的全过程,你运行一下,就可以看到摄像头拍摄的画面了,显示在你的对话框上。
    但是,我的目的还没有达到,我其实想在每一桢显示之前,能处理一下这一桢的数据,那么,去哪里找这桢数据存
    放的位置呢?
    (8)为了完成我的目标,我把步骤(7)中的两句代码先注释掉。在对话框上加一个按钮,并在对单击做出响应的响应函数
    中写下面代码:
    capGrabFrame(m_hVideo);
    这是一个宏,将鼠标移动到这段代码上,右键单击,选择Go To Definition of capGrabFrame,你会看到
    #define capGrabFrame(hwnd) ((BOOL)AVICapSM(hwnd, WM_CAP_GRAB_FRAME, (WPARAM)0, (LPARAM)0L))
    而继续察看AVICapSM宏你会看到其实是在调用SendMessage函数呢,对吧,其实就是在发送消息。至于消息谁处理了,我们就不去
    关心了,我们关心的是,发送消息后,系统会调用我们刚才注册的回调函数
    LRESULT CALLBACK FrameCallbackProc(HWND hWnd, LPVIDEOHDR lpVHdr) ;
    (9)好了,如果你单击按钮,capGrabFrame(m_hVideo)就发送消息了,然后,我们就进入回调函数了,这太好了。
    看到回调函数传递的两个参数了么?我们更关心第二个参数,这个就是单击按钮我们捕捉到的一桢数据的入口啊!
    LPVIDEOHDR 是结构体VIDEOHDR的指针,而在MSDN中察看结构体VIDEOHDR,我们就可以找到桢数据的存贮位置指针了。

    VIDEOHDR定义如下:
    typedef struct videohdr_tag {
    LPBYTE lpData;
    DWORD dwBufferLength;
    DWORD dwBytesUsed;
    DWORD dwTimeCaptured;
    DWORD dwUser;
    DWORD dwFlags;
    DWORD_PTR dwReserved[4];
    } VIDEOHDR, NEAR *PVIDEOHDR, FAR * LPVIDEOHDR;
    看到结构体中第一个参数了么?这个就是我们想要的桢数据的指针!后面参数,包括缓冲区长度等。
    (10)终于得到了缓冲区的数据,可是,又一个问题出现了,缓冲区中的数据到底具体是啥含义啊?
    这桢图像多大啊?size 是多少乘多少的啊?就是我们想要的像素信息么?
    好的,我先告诉你,缓冲区中全部是像素信息,我们照着我的步骤这样做,其实是默认了一些参数,包括图像的
    长度,宽度,色彩数,等等,那么,这个默认的值是多少呢?
    (11)用一下capGetVideoFormat宏吧,你会得到想要的东西。
    在BOOL CGraspDlg::OnInitDialog()中继续添加如下代码:
    BITMAPINFO bmpInfo;
    capGetVideoFormat(m_hVideo,&bmpInfo,sizeof(BITMAPINFO));
    BITMAPINFO结构体内容自己看MSDN.定义如下

    typedef struct tagBITMAPINFO {
    BITMAPINFOHEADER bmiHeader;
    RGBQUAD bmiColors[1];
    } BITMAPINFO, *PBITMAPINFO;

    而BITMAPINFOHEADER定义如下:

    typedef struct tagBITMAPINFOHEADER{
    DWORD biSize;
    LONG biWidth;
    LONG biHeight;
    WORD biPlanes;
    WORD biBitCount;
    DWORD biCompression;
    DWORD biSizeImage;
    LONG biXPelsPerMeter;
    LONG biYPelsPerMeter;
    DWORD biClrUsed;
    DWORD biClrImportant;
    } BITMAPINFOHEADER, *PBITMAPINFOHEADER;

    加入步骤(11)的两句话,调试运行,我发现,bmpInfo.bmiHeader.biWidth为320,就是采集的图像宽度;
    bmpInfo.bmiHeader.biHeight为240,就是采集的图像高度,这些都是默认值,也可以改变这些值,通过
    capSetVideoFormat宏来实现。
    (12)那么,我们在步骤(9)中,回调函数第二个参数对应的结构体VIDEOHDR中,图像数据缓冲区的大小
    dwBufferLength是多少呢?我们可以在回调函数中加一个MessageBox函数,输出这个值,
    我们就可以发现,为230400,这个数很好,正好等于图像宽度X图像高度的3倍,
    也就是说,是图像像素数目的3倍,这就对了,每个像素用3个字节存储的嘛。好啦,我们知道了,桢缓冲区中,存储
    的完全是图像的像素信息,那么,具体哪个值对应哪个像素呢?
    存储顺序是这样的:先从图像最下面一行开始,从左向右,依次存储,每一个像素用连续的3个字节,分别为B(蓝色分量),
    G(绿色分量),R(红色分量)。然后存储倒数第二行,仍然按照图像从左向右存储,然后倒数第三行,
    倒数第四行。。。。。。等等,最后存储正数第一行。

    好啦,我所有想说的都说完啦,你明白了么?好累,现在是晚上,快10点钟了,我正好要去跑步去了。

Open Toolbar