代码改变世界

VFW程序分析

2011-08-25 18:50  沐海  阅读(759)  评论(0编辑  收藏  举报

程序所在位置:http://www.cnblogs.com/mahaisong/archive/2011/08/25/2153653.html

 

 

VFW程序分析

FORM1为例

 

  private void Form1_Load(object sender, EventArgs e)

{

    wc = new WebCamera(panelPreview.Handle, panelPreview.Width, panelPreview.Height);

  wc.StartWebCam();

}

 

     private void b_play_Click(object sender, EventArgs e)

{

   wc = new WebCamera(panelPreview.Handle, panelPreview.Width, panelPreview.Height);

   wc.StartWebCam();

  }

private void b_stop_Click(object sender, EventArgs e)

{     wc.CloseWebcam();  }

 

说明:当form加载时,创建formpanel的句柄。此句柄是作为WebCamera对象的参数。

然后启动此对象实例。

play按钮的作用和加载时的作用一样。

意义:通过启动此实例。让摄像头设备

 

stop按钮的作用是用来断开句柄的连接。

  public void CloseWebcam()

  {

   this.capDriverDisconnect(this.lwndC);

  }

 

详细解说showVideo

  [DllImport("avicap32.dll")]

  public static extern IntPtr capCreateCaptureWindowA(byte[] lpszWindowName, int dwStyle, int x, int y, int nWidth, int nHeight, IntPtr hWndParent, int nID);

指示该属性化方法由非托管动态链接库 (DLL) 作为静态入口点公开。

 

extern可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。另外,extern也可用来进行链接指定。

 

 

 

 

VIDEOHDRBITMAPINFOHEADERBITMAPINFO 建立三个结构

 

  [StructLayout(LayoutKind.Sequential)] public struct VIDEOHDR

  {

   [MarshalAs(UnmanagedType.I4)] public int lpData;

公共语言运行库控制类或结构的数据字段在托管内存中的物理布局。如果类或结构需要按某种方式排列,则可以使用 StructLayoutAttribute。如果要将类传递给需要指定布局的非托管代码,则显式控制类布局是重要的。LayoutKind Sequential 用于强制将成员按其出现的顺序进行顺序布局。

 

 

 

这里要求注意其内部传递数据结构的名称和意义。

 

System.Runtime.InteropServices..::.MarshalAsAttribute 中使用 UnmanagedType 枚举,以指定在与非托管代码进行交互的过程中如何对类型进行封送处理。可以使用此枚举对使用简单值类型(I1I2I4I8R4R8U2U4 U8)、.NET Framework 中未提供的非托管类型以及各种杂项类型的代码进行封送处理。

 

 

  public static object GetStructure(IntPtr ptr,ValueType structure)//得到结构

  {

   return Marshal.PtrToStructure(ptr,structure.GetType());

  }

 

//提供值类型的基类。

 //  ValueType 用更合适的值类型实现重写 Object 中的虚方法。请参见从 ValueType 继承的 Enum

//数据类型分隔为值类型和引用类型。值类型要么是堆栈分配的,要么是在结构中以内联方式分配的。

 //引用类型是堆分配的。引用类型和值类型都是从最终的基类 Object 派生出来的。

 //当值类型需要充当对象时,就在堆上分配一个包装(该包装能使值类型看上去像引用对象一样),

//并且将该值类型的值复制给它。该包装被加上标记,以便系统知道它包含一个值类型。这个进程称为装箱,其反向进程称为取消装箱。装箱和取消装箱能够使任何类型像对象一样进行处理。

 

 

Marshal.PtrToStructure(ptr,structure.GetType());

Marshal 类中定义的 static 方法对于处理非托管代码至关重要。此类中定义的大多数方法通常由需要在托管和非托管编程模型之间提供桥梁的开发人员使用。

 

PtrToStructure将数据从非托管内存块封送到托管对象。

 

 

ptr

类型:System..::.IntPtr

指向非托管内存块的指针。

structure

类型:System..::.Object

将数据复制到其中的对象。这必须是格式化类的实例。

当结构参数表示为 System..::.IntPtr 值时,PtrToStructure COM 互操作 和平台调用中通常是必要的。此重载方法不能用于值类型。

 

 

Marshal.Copy(ptr,data,0,data.Length);

将数据从托管数组复制到非托管内存指针,或从非托管内存指针复制到托管数组。

Copy(IntPtr, array<Byte>[]()[], Int32, Int32)

将数据从非托管内存指针复制到托管 8 位无符号整数数组。

 

 

 

名称

说明

SizeOf(Object)

返回对象的非托管大小(以字节为单位)。

 

 

 

详细解说WebCamera类。

 

WebCamera

 

这个类是和showVideo有关的类。

 

ref 关键字使参数按引用传递。其效果是,当控制权传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中。若要使用 ref 参数,则方法定义和调用方法都必须显式使用 ref 关键字。

 

out 关键字会导致参数通过引用来传递。这与 ref 关键字类似,不同之处在于 ref 要求变量必须在传递之前进行初始化。若要使用 out 参数,方法定义和调用方法都必须显式使用 out 关键字。

 

 

 

capSetCallbackOnFrame回调函数在VFW中的意义及参数

 

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

 

回调函数,就是你自己写的函数,符合规定的参数和返回值类型,符合规定的调用约定,比如上面这个函数
就是回调函数,参数和返回值类型都是规定好的,调用约定为CALLBACKCALLBACK

 

其实是一个宏
#define CALLBACK__stdcall
满足一定条件时,此函数可以被系统自动调用,在回调函数当中,你可以写自己的代码完成一定功能。

 

 


比如在这里,用capSetCallbackOnFrame(m_hVideo, FrameCallbackProc)注册后,当每得到一桢数据后,系统就
调用函数FrameCallbackProc

 

 

回调函数解析:

private void FrameCallBack(IntPtr lwnd, IntPtr lpVHdr)//两个句柄

 

  //VideoHdr 结构   定义了视频数据块的头信息,在编写回调函数时常用到其数据成员lpData(指向数据缓存的指针)和dwBufferLength(数据缓存的大小)。    

  //视频帧到缓存的捕获则需要应用回调函数和相应的数据块结构 VIDEOHDR

  [StructLayout(LayoutKind.Sequential)] public struct VIDEOHDR

  {

      [MarshalAs(UnmanagedType.I4)]

      public int lpData;  /* 指向数据缓存的指针 */

 

      [MarshalAs(UnmanagedType.I4)]

      public int dwBufferLength;  /* 数据缓存的大小 */

 

      [MarshalAs(UnmanagedType.I4)]

      public int dwBytesUsed; /* Bytes actually used */

 

      [MarshalAs(UnmanagedType.I4)]

      public int dwTimeCaptured;  /* Milliseconds from start of stream */

 

      [MarshalAs(UnmanagedType.I4)]

      public int dwUser;   /* for client's use */

 

      [MarshalAs(UnmanagedType.I4)]

      public int dwFlags; /* assorted flags (see defines) */

 

      [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]

      public int[] dwReserved;   /* reserved for driver */

 

  }

// Public methods

  public static object GetStructure(IntPtr ptr,ValueType structure)//得到结构

  {

   return Marshal.PtrToStructure(ptr,structure.GetType());

  }

  //提供值类型的基类。

 //  ValueType 用更合适的值类型实现重写 Object 中的虚方法。请参见从 ValueType 继承的 Enum

//数据类型分隔为值类型和引用类型。值类型要么是堆栈分配的,要么是在结构中以内联方式分配的。

 //引用类型是堆分配的。引用类型和值类型都是从最终的基类 Object 派生出来的。

 //当值类型需要充当对象时,就在堆上分配一个包装(该包装能使值类型看上去像引用对象一样),

//并且将该值类型的值复制给它。该包装被加上标记,以便系统知道它包含一个值类型。这个进程称为装箱,其反向进程称为取消装箱。装箱和取消装箱能够使任何类型像对象一样进行处理。

 

 

 

 

  public static object GetStructure(int ptr,ValueType structure)

  {

   return GetStructure(new IntPtr(ptr),structure);

  }

    

  public static void Copy(IntPtr ptr,byte[] data)

  {

   Marshal.Copy(ptr,data,0,data.Length);

  }

    

  public static void Copy(int ptr,byte[] data)

  {

   Copy(new IntPtr(ptr),data);

  }

    

  public static int SizeOf(object structure)

  {

   return Marshal.SizeOf(structure);

  }

 }

 

 

LPVIDEOHDR 是结构体VIDEOHDR的指针,而在MSDN中察看结构体VIDEOHDR,我们就可以找到桢数据的存贮位置指针了。

VIDEOHDR定义如下:
typedef struct videohdr_tag {
LPBYTElpData;
DWORDdwBufferLength;
DWORDdwBytesUsed;
DWORDdwTimeCaptured;
DWORDdwUser;
DWORDdwFlags;
DWORD_PTRdwReserved[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;
RGBQUADbmiColors[1];
} BITMAPINFO, *PBITMAPINFO;

BITMAPINFOHEADER定义如下:

typedef struct tagBITMAPINFOHEADER{
DWORDbiSize;
LONGbiWidth;
LONGbiHeight;
WORDbiPlanes;
WORDbiBitCount;
DWORDbiCompression;
DWORDbiSizeImage;
LONGbiXPelsPerMeter;
LONGbiYPelsPerMeter;
DWORDbiClrUsed;
DWORDbiClrImportant;
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;

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

 

 

记录生活、工作、学习点滴!
E-Mail:mahaisong@hotmail.com 欢迎大家讨论。
沐海博客园,我有一颗,卓越的心!