VFW_视频截取

       我在做一个小的视频聊天软件的过程中,充分利用了vfw提供的各种特性。下面分几个部分讲述我对vfw的认识。比较适合入门级别。

视频截取

       视频聊天软件中,由一端到另一端的视频流先后要经历 视频截获、视频帧压缩、视频帧传输、视频帧解压缩、视频显示。这里就讲述视频截取我的看法。

       第一,由视频设备(usb摄像头或者其他)抓取模拟视频数据并转化为适于计算机处理的数字视频数据。

       第二,把原始的视频数据传至视频驱动程序的缓存区。

       第三,进过我的实践,在调用vfw的进程空间内,会有个缓存区,用于保存从驱动程序传过来的视频数据。

       第四,进程通过vfw窗口把进程缓存区里面的视频帧数据提取出来并且显示。

       第五,通过注册回调函数,可是处理各种情况,状态回调,错误回调,视频流回调,视频帧回调,让步回调。

(1)创建cap窗口

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

       这个函数创建截取窗口,这个窗口也就是用来显示截取的视频。

       可以通过

       CAPSTATUS CapStatus;

       capGetStatus(hWndC, &CapStatus, sizeof (CAPSTATUS));

       组合来获取截取窗口的状态,在程序中,每当需要知道窗口的状态,就必须实时调用capGetStatus函数,因为窗口的状态是随时可能改变的。

       CAPSTATUS 结构的说明:

   typedef struct { 
      UINT     uiImageWidth; 
      UINT     uiImageHeight; 
      BOOL     fLiveWindow; 
      BOOL     fOverlayWindow; 
      BOOL     fScale; 
      POINT    ptScroll; 
      BOOL     fUsingDefaultPalette; 
      BOOL     fAudioHardware; 
      BOOL     fCapFileExists; 
      DWORD    dwCurrentVideoFrame; 
      DWORD    dwCurrentVideoFramesDropped; 
      DWORD    dwCurrentWaveSamples; 
      DWORD    dwCurrentTimeElapsedMS; 
      HPALETTE hPalCurrent; 
      BOOL     fCapturingNow; 
      DWORD    dwReturn; 
      UINT     wNumVideoAllocated; 
      UINT     wNumAudioAllocated; 
    } CAPSTATUS; 
 

       uiImageWidth

             窗口的像素宽度,也就是窗口显示的图像的像素宽度。

      uiImageHeight

            窗口的像素高度,也就是窗口显示的图像的像素高度。

      fLiveWindow

            如果窗口使用预览模式(capPreview)来显示视频,那么这项为true,否则false。

      fOverlayWindow

            如果窗口使用重叠模式(capOverlay)来显示视频,那么这项为true,否则false。

      fScale

             如果窗口使用重叠模式,则此项无效。若true,图像的显示被比例被改变(capPreviewScale),即原始图像被改变比例以适应截取窗口的大小,若false,则显示原始图像比例。

     ptScroll

             截取窗口左上角像素相对于原始图像左上角的偏移(应该是因为fScale的原因)。

     fUsingDefaultPalette

             若true,驱动使用默认的调色板。

     fAudioHardware

              我不关心。

     fCapFileExists

             我不关心。

     dwCurrentVideoFrame

            当前视频截取时得到的总的帧数,包括丢失的。

     dwCurrentVideoFramesDropped

            当前视频截取时丢失的总帧数。

      dwCurrentWaveSamples

             不关心。

       dwCurrentTimeElapsedMS

             从当前视频截取开始到当前的微秒数。

       hPalCurrent

              当前调色板的句柄。

       fCapturingNow

            若true,视频截取正在进行中。

       dwReturn

             不关心。

        wNumVideoAllocated

              进程内视频缓存区的大小。

       wNumAudioAllocated

             不关心。

(2)枚举驱动-获取驱动的参数

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

for (wIndex = 0; wIndex < 10; wIndex++) 
{
    if (capGetDriverDescription(
            wIndex, 
            szDeviceName, 
            sizeof (szDeviceName), 
            szDeviceVersion, 
            sizeof (szDeviceVersion)
        )) 
    {
        // Append name to list of installed capture drivers
        // and then let the user select a driver to use.
    }
} 

以上代码段获取每一个视频驱动的描述信息 驱动名字 以及 驱动的版本号。

capGetDriverDescription的wIndex参数只能是0、1....、9,可能是系统最多支持10类型视频设备的驱动。并且capGetDriverDescription优先获取热拔插视频设备的驱动信息,比如usb视频设备。

CAPDRIVERCAPS cps;
capDriverGetCaps(hCaptureWindow,&cps,sizeof(CAPDRIVERCAPS));

以上代码用于获取 截取窗口所连接的驱动的 参数。

以下是CAPDRIVERCAPS说明:

typedef struct { 
    UINT   wDeviceIndex; 
    BOOL   fHasOverlay; 
    BOOL   fHasDlgVideoSource; 
    BOOL   fHasDlgVideoFormat; 
    BOOL   fHasDlgVideoDisplay; 
    BOOL   fCaptureInitialized; 
    BOOL   fDriverSuppliesPalettes; 
    HANDLE hVideoIn; 
    HANDLE hVideoOut; 
    HANDLE hVideoExtIn; 
    HANDLE hVideoExtOut; 
} CAPDRIVERCAPS; 
 

wDeviceIndex

驱动序号,即0-9之一。

fHasOverlay

此驱动是否支持重叠模式。

fHasDlgVideoSource

此驱动是否支持视频源选择对话框。

fHasDlgVideoFormat

此驱动是否支持视频帧格式设置对话框。

fHasDlgVideoDisplay

此驱动是否支持视频控制对话框。

fCaptureInitialized

若true,则视频设备已经初始化完成。

fDriverSuppliesPalettes

若true,此驱动可以创建调色板。

hVideoIn

忽略。

hVideoOut

忽略。

hVideoExtIn

忽略。

hVideoExtOut

忽略。

(3)窗口连接驱动

      使用capDriverConnect(hCaptureWindow,wIndex)即可完成窗口hCaptureWindow和第wIndex号驱动的连接。窗口在现实视频之前必须先和一个驱动连接,因为视频数据由驱动程序提供给窗口,然后由窗口显示。

(4)获取并视频帧格式

     DWORD dwSize =capGetVideoFormatSize(hCaptureWindow);
     lpbi=(LPBITMAPINFO)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY|HEAP_NO_SERIALIZE,dwSize);
     capGetVideoFormat(hCaptureWindow, lpbi, dwSize); 

     以上代码段获取截 取窗口连接的 驱动的 视频帧格式。是一个BITMAPINFO可变长度结构。
     lpbi->bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
     lpbi->bmiHeader.biBitCount=8;
     lpbi->bmiHeader.biWidth=200;
     lpbi->bmiHeader.biHeight=400;
     lpbi->bmiHeader.biPlanes=1;
     lpbi->bmiHeader.biCompression=BI_RGB;
     lpbi->bmiHeader.biClrImportant=0;
     lpbi->bmiHeader.biClrUsed=0;
     lpbi->bmiHeader.biSizeImage=lpbi->bmiHeader.biWidth*lpbi->bmiHeader.biHeight;

     以上设置BITMAPINFO可变长度结构的各个项,按照自己的需求来改,但是一定要是驱动程序支持的。一般都可以使用默认值,不需要改。改的话一般只改长宽值。
     capSetVideoFormat(hCaptureWindow, lpbi, dwSize); 
     以上设置更改后的视频帧格式。

     从驱动程序截取过来的帧都是以上设置的格式。

(5)预览视频

    CAPDRIVERCAPS cps;
    capDriverGetCaps(hCaptureWindow,&cps,sizeof(CAPDRIVERCAPS));

    if (cps.fHasOverlay)  //如果驱动支持重叠模式的话,就优先选择重叠模式,我的机器不支持,仅凭msdn上的一点洋文,我无法理解何为重叠模式。
                                           一般来说,可能就是重叠模式更多的利用了硬件特性,效率速度比预览模式要高。              

      capOverlay(hCaptureWindow, TRUE);
    else
    {
               capPreviewScale(hCaptureWindow,true); //设置预览模式拓展源图像比例以适应截取窗口的大小。显示出来后会失调。
               capPreviewRate(hCaptureWindow,30);  
               capPreview(hCaptureWindow,TRUE);     //开启预览模式。
    }

    这样之后,就可以在截取窗口中看见视频了。

(6)回调函数

     回调函数是vfw里面重要的一环,很多重要的功能都需要回调函数才能完成。

     状态回调:

BOOL capSetCallbackOnStatus(
  hwnd,  
  fpProc 
);

     设置状态回调函数。

// StatusCallbackProc: 状态回调函数
// hWnd:               capture window handle 
// nID:                状态码,一个状态码对应一个特定的状态改变。
// lpStatusText:       与状态相关的文本信息。 
// 
LRESULT PASCAL StatusCallbackProc(HWND hWnd, int nID, 
    LPSTR lpStatusText) 
{ 
    if (!hWnd) 
        return FALSE; 
 
    if (nID == 0) {           // Clear old status messages. 
        SetWindowText(hWnd, (LPSTR) gachAppName); 
        return (LRESULT) TRUE; 
    } 
    // Show the status ID and status text... 
    wsprintf(gachBuffer, "Status# %d: %s", nID, lpStatusText); 
 
    SetWindowText(hWnd, (LPSTR)gachBuffer); 
    return (LRESULT) TRUE; 
} 
 

  错误回调:

BOOL capSetCallbackOnError(
  hwnd,  
  fpProc 
);

以上设置错误回调函数。

LRESULT CALLBACK capErrorCallback(
  HWND hWnd,  
  int nID,    
  LPCSTR lpsz 
);   

  相关的参数和状态回调意义相同。

 下面重点说明视频流回调,和帧回调,这也是视频截取中最最重要的部分,主要是因为msdn是说的实在是太过简单,聊聊几句带过。而网上也都是些粘贴党。

BOOL capSetCallbackOnFrame(
  hwnd,  
  fpProc 
);

   以上设置帧回调函数。下面是回调函数的原型。

LRESULT CALLBACK capFrameCallback(
  HWND hWnd,         
  LPVIDEOHDR lpVHdr  
);

    hWnd就是截取窗口的句柄值。

    lpVHdr基本上我们只关心它的两个成员即lpVHdr->lpData,lpVHdr->dwBufferLength。lpVHdr->lpData是一个指针,指向进程空间内的视频缓存区内,

    lpVHdr-  >dwBufferLength是这个缓冲区的长度。但是实际的数据长度却要通过strlen(lpVHdr->lpData)来获取。lpVHdr->lpData指向的数据仅仅是图像的实际数据,就是说不包括 BITMAPINFO结构。如果要传输的话,还需要获取当前驱动的截取的视频格式capGetVideoFormat()来得到 BITMAPINFO结构,然后把BITMAPINFO和lpVHdr->lpData数据组合起来成为一幅位图经过压缩后发送。

    capFrameCallback在什么时候被调用呢?这是个很重要的问题。看了下msdn说这个函数只用在预览模式下。在视频帧从内存缓存区传送的窗口显示之前被调用。有两点很重要条件,一是视频帧要是从内存缓存区出来的(不是直接从驱动缓存区出来的。貌似重叠模式下就是视频数据不经过进程空间的内存缓存区,而是直接由驱动缓存直送窗口显示,这样就提高了效率)。二是,帧要被送往窗口显示。这两点是我自己总结的,只要符合这两个条件,capFrameCallback就会被调用。

    那么就顺便讨论一下几个截取视频的函数capCaptureSingleFrame(),capGrabFrame(),capGrabFrameNoStop()。

    capCaptureSingleFrame(),就是用来提取单帧,这个函数就是从视频驱动缓存提出视频帧数据,然后存入内存缓存区,然后从内存缓存区送至窗口显示,也就是说满足了capFrameCallback调用的两个条件。真实的结果也是如此。

     capGrabFrame(),工作方式与capCaptureSingleFrame()一样,唯一不同的是capGrabFrame()执行完后会禁止重叠模式以及预览模式(msnd上如是说,我就不明白了,两种模式都禁止了,那之后还怎么截取视频流啊?可能这个函数可以专门用来截单帧图像,类似于QQ截图,框选完后整个画面就静止了,应该就是禁止了重叠模式以及预览模式,之后就不再需要这两种模式了,因为接完一张图我就不干了,再截图的时候再开启就可以了)。

     capGrabFrameNoStop(),capGrabFrame()工作的时候进程内其他事务就暂停就像死机一样,而这个函数之所以NoStop就是 它会开辟一个新线程来执行capGrabFrame()所干的事情。

     我觉得理解到这个程度只要对编程没有影响就可以了。

     最后是视频流回调

BOOL capSetCallbackOnVideoStream(
  hwnd,  
  fpProc 
);
LRESULT CALLBACK capVideoStreamCallback(
  HWND hWnd,         
  LPVIDEOHDR lpVHdr  
);

以上设置了视频流回调。

     这个回调函数在进程视频缓存区可用(应该就是满的时候)的时候才被调用。所以才能体现 流 这一概念。像水一样,把缓存区流满了要溢出的时候就要采取措施了,于是capVideoStreamCallback被调用了用以处理缓存区满或者可用的情况。

     也有相关的函数capCaptureSequence(),capCaptureSequenceNoFile()。

     capCaptureSequence(),作用就是从进程缓存区把视频流存入一个AVI文件(由capFileSetCaptureFile指定)。

     capCaptureSequenceNoFile(),的作用应该来说仅仅是通过不断地截取视频使内存缓存区满,来使capVideoStreamCallback不断地被调用。这个函数不会把视频流存入文件。显然这个函数适用于视频聊天程序,不断地通过回调函数获取视频帧,然后......

     

附:若转发,请注明 引用“fjaygrfjaygr”。谢谢合作。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值