代码改变世界

VFW实例程序。简单 “视频捕获”,没有压缩、线程、保存、等更多的功能

2011-08-25 18:47  沐海  阅读(2290)  评论(8编辑  收藏  举报
 
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace webcam
{
    public class showVideo
 {
  // showVideo calls
  [DllImport("avicap32.dll")] 
  public static extern IntPtr capCreateCaptureWindowA(byte[] lpszWindowName, int dwStyle, int x, int y, int nWidth, int nHeight, IntPtr hWndParent, int nID);
  [DllImport("avicap32.dll")] 
  public static extern bool capGetDriverDescriptionA(short wDriver, byte[] lpszName, int cbName, byte[] lpszVer, int cbVer);
  [DllImport("User32.dll")] 
  public static extern bool SendMessage(IntPtr hWnd, int wMsg, bool wParam, int lParam); 
  [DllImport("User32.dll")] 
  public static extern bool SendMessage(IntPtr hWnd, int wMsg, short wParam, int lParam); 
  [DllImport("User32.dll")] 
  public static extern bool SendMessage(IntPtr hWnd, int wMsg, short wParam, FrameEventHandler lParam); 
  [DllImport("User32.dll")] 
  public static extern bool SendMessage(IntPtr hWnd, int wMsg, int wParam, ref BITMAPINFO lParam);
  [DllImport("User32.dll")] 
  public static extern int SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int y, int cx, int cy, int wFlags);
  [DllImport("avicap32.dll")]
  public static extern int capGetVideoFormat(IntPtr hWnd, IntPtr psVideoFormat, int wSize );
 
//下面这些都是系统USER32.DLL里面的函数 SendMessage使用的消息的类型。是指定的16进制值。
//这里的字母的含义是用来让我们明白的。重点在于,其后面的16进制值在系统默认中代表什么含义。要和系统默认表值对应。


  // Constants
  public const int WM_USER = 0x400;
  public const int WS_CHILD = 0x40000000;
  public const int WS_VISIBLE = 0x10000000;
  public const int SWP_NOMOVE = 0x2;
  public const int SWP_NOZORDER = 0x4;
  public const int WM_CAP_DRIVER_CONNECT = WM_USER + 10;   /// 驱动程序连接
  public const int WM_CAP_DRIVER_DISCONNECT = WM_USER + 11;/// 断开启动程序连接
  public const int WM_CAP_SET_CALLBACK_FRAME = WM_USER + 5;
  public const int WM_CAP_SET_PREVIEW = WM_USER + 50;
  public const int WM_CAP_SET_PREVIEWRATE = WM_USER + 52;
  public const int WM_CAP_SET_VIDEOFORMAT = WM_USER + 45;

  //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 */

  }

  //BitmapInfoHeader定义了位图的头部信息


  [StructLayout(LayoutKind.Sequential)] public struct BITMAPINFOHEADER  //MS定义的结构类型。
  {
   [MarshalAs(UnmanagedType.I4)] public Int32 biSize ;
   [MarshalAs(UnmanagedType.I4)] public Int32 biWidth ;
   [MarshalAs(UnmanagedType.I4)] public Int32 biHeight ;
   [MarshalAs(UnmanagedType.I2)] public short biPlanes;
   [MarshalAs(UnmanagedType.I2)] public short biBitCount ;
   [MarshalAs(UnmanagedType.I4)] public Int32 biCompression;
   [MarshalAs(UnmanagedType.I4)] public Int32 biSizeImage;
   [MarshalAs(UnmanagedType.I4)] public Int32 biXPelsPerMeter;
   [MarshalAs(UnmanagedType.I4)] public Int32 biYPelsPerMeter;
   [MarshalAs(UnmanagedType.I4)] public Int32 biClrUsed;
   [MarshalAs(UnmanagedType.I4)] public Int32 biClrImportant;
  }

  //BitmapInfo   位图信息

  [StructLayout(LayoutKind.Sequential)] public struct BITMAPINFO
  {
   [MarshalAs(UnmanagedType.Struct, SizeConst=40)] public BITMAPINFOHEADER bmiHeader;//头部信息结构
   [MarshalAs(UnmanagedType.ByValArray, SizeConst=1024)] public Int32[] bmiColors;//像素值数组
  }
     
  public delegate void FrameEventHandler(IntPtr lwnd, IntPtr lpVHdr);//创建委托
     
  // 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); 
  }
 }

  //Web Camera Class
 public class WebCamera
 {
  // Constructur
  public WebCamera(IntPtr handle, int width,int height)
  {
   mControlPtr = handle;
   mWidth = width;
   mHeight = height;
  }
     
  // delegate for frame callback
  public delegate void RecievedFrameEventHandler(byte[] data);
  public event RecievedFrameEventHandler RecievedFrame;

  private IntPtr lwndC;    //保存无符号句柄

  private IntPtr mControlPtr; //保存管理指示器

  private int mWidth;
  private int mHeight;
     
  private showVideo.FrameEventHandler mFrameEventHandler; // Delegate instance for the frame callback - must keep alive! gc should NOT collect it
     
  // Close the web camera
  public void CloseWebcam()
  {
   this.capDriverDisconnect(this.lwndC);
  }
     
  // start the web camera
  public void StartWebCam()
  {
   byte[] lpszName = new byte[100];
   byte[] lpszVer = new byte[100];

   showVideo.capGetDriverDescriptionA(0, lpszName, 100, lpszVer, 100);//创建缓冲区//取得视频采集设备描述信息
   //参数:其中,参数0指明设备的索引号,参数lpszName指明设备的名称缓冲区的地址,参数100指明设备的名称缓冲区的大小,//参数lpszVer指明设备的描述缓冲区的地址,参数100指明设备的描述缓冲区的大小。
 this.lwndC = showVideo.capCreateCaptureWindowA(lpszName, showVideo.WS_VISIBLE + showVideo.WS_CHILD, 0, 0, mWidth, mHeight, mControlPtr, 0);
   //,参数lpszWindowName指明窗口的名称,参数dwStyle指明窗口的风格,参数x、y、nWidth和nHeight指明窗口的位置和大小,//参数hwndParent指明父窗口的句柄,参数nID指明窗口标识。 


   //函数返回值Hwnd类型,即摄像设备的句柄,以后的操作基本上都是针对设备句柄进行的.
//:= CapCreateCaptureWindow 
//( PChar(‘KruwoSoft'), //捕获窗口的名字 
//WS_CHILD or WS_VISIBLE,//窗口样式 
//0, //X坐标 
//0, //Y坐标 
//gCapVideoArea.Width, //窗口宽 
//gCapVideoArea.Height, //窗口高 
//gCapVideoArea.Handle, //窗口句柄 
//0); //一般为0 


   if (this.capDriverConnect(this.lwndC, 0))
   {
    this.capPreviewRate(this.lwndC, 66);
    this.capPreview(this.lwndC, true);//打开预览
    showVideo.BITMAPINFO bitmapinfo = new showVideo.BITMAPINFO(); //创建结构的对象。
       //这里把值类型转换为 引用类型。
       //装箱与拆箱的区别
       //要把苹果放到水果篮子里,就需要把苹果当成水果装。否则苹果只能放在苹果篮子里。那么反过来拿出来的时候,还要还原为苹果。//装箱拆箱本来是为了方便程序员的,

    bitmapinfo.bmiHeader.biSize = showVideo.SizeOf(bitmapinfo.bmiHeader);//帧图片 头部定义
    //帧图片 头部定义
    bitmapinfo.bmiHeader.biWidth = 352;//宽
    bitmapinfo.bmiHeader.biHeight = 288;//高
    bitmapinfo.bmiHeader.biPlanes = 1;//Specifies the number of planes for the target device. This value must be set to 1
                                      //为目标设备指定的平面值,不存在多维度。
    bitmapinfo.bmiHeader.biBitCount = 24;
    //biBitCount成员的结构决定了BITMAPINFOHEADER的比特数定义的最大数量,每一个像素色彩的位图。


    this.capSetVideoFormat(this.lwndC, ref bitmapinfo, showVideo.SizeOf(bitmapinfo));

       //委托和事件的定义。
    this.mFrameEventHandler = new showVideo.FrameEventHandler(FrameCallBack);


    this.capSetCallbackOnFrame(this.lwndC, this.mFrameEventHandler);//回调函数。
    showVideo.SetWindowPos(this.lwndC, 0, 0, 0, mWidth , mHeight , 6);
   } 
  }
 
  // private functions
  private bool capDriverConnect(IntPtr lwnd, short i)
  {
      return showVideo.SendMessage(lwnd, showVideo.WM_CAP_DRIVER_CONNECT, i, 0);//参数 句柄,1024+100,0,0,
  }
 
     //   hWnd:窗口句柄。 wMsg:将要发送的消息。 wParam、lParam:消息的参数,每个消息都有两个参数,参数设置由发送的消息而定。
     //SendMessage(hHwnd, 0x40a, 0, 0),第一个参数是句柄,第二个参数表示的是一个消息,是哪个消息呢?后两个参数是到底为消息提供什么样的东西呢?
//回答:0x40a 表示 WM_CAP_DRIVER_CONNECT消息,后面两个参数表示消息附带的两个参数,每个消息都有两个参数的。按照VFW的规定,//第一个参数是捕获设备的索引。这儿是0,表示你的第一个捕获设备。第二参数恒为0。为什么?VFW就是这么规定的。
//具体参见MSND中Enumerating Installed Capture Drivers相关章节。就OK了。

  private bool capDriverDisconnect(IntPtr lwnd)
  {
   return showVideo.SendMessage(lwnd, showVideo.WM_CAP_DRIVER_DISCONNECT, 0, 0);
  }
     
  private bool capPreview(IntPtr lwnd, bool f)
  {
      return showVideo.SendMessage(lwnd, showVideo.WM_CAP_SET_PREVIEW, f, 0);//打开预览
  }
 
  private bool capPreviewRate(IntPtr lwnd, short wMS)
  {
   return showVideo.SendMessage(lwnd, showVideo.WM_CAP_SET_PREVIEWRATE, wMS, 0);
  }
  //设置预览时的帧频率 
  //   hWnd:窗口句柄。 wMsg:将要发送的消息。 帧率86,0
    //设置在PREVIEW模式下设定视频窗口的刷新率 设置每40毫秒显示一帧,即显示帧速为每秒25帧

  private bool capSetCallbackOnFrame(IntPtr lwnd, showVideo.FrameEventHandler lpProc)
  {     
   return showVideo.SendMessage(lwnd, showVideo.WM_CAP_SET_CALLBACK_FRAME, 0, lpProc);
  }

  //回调函数。当在这个句柄中数据接收到一帧时。WM_CAP_SET_CALLBACK_FRAME时。发消息给当前句柄。调用回调函数。









  //    this.capSetVideoFormat(this.lwndC, ref bitmapinfo, showVideo.SizeOf(bitmapinfo));
  //返回对象的非托管大小(以字节为单位)

     //这里使用 REF保证图片改变时全部相关信息对应改变。
  //ref 关键字使参数按引用传递。其效果是,当控制权传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中。//若要使用 ref 参数,则方法定义和调用方法都必须显式使用 ref 关键字。
  private bool capSetVideoFormat(IntPtr hCapWnd, ref showVideo.BITMAPINFO BmpFormat, int CapFormatSize)
  {
   return showVideo.SendMessage(hCapWnd, showVideo.WM_CAP_SET_VIDEOFORMAT, CapFormatSize, ref BmpFormat);//捕获视频数据。//大小和结构,把消息发送到这个句柄上。
  }
 




     //每次回调时进入这里
    
  private void FrameCallBack(IntPtr lwnd, IntPtr lpVHdr)//两个句柄
  {
      showVideo.VIDEOHDR videoHeader = new showVideo.VIDEOHDR();//VideoHdr 结构定义
   byte[] VideoData;
   videoHeader = (showVideo.VIDEOHDR)showVideo.GetStructure(lpVHdr,videoHeader);//得到这样的一个结构 内容   参数:句柄,值类型(结构)
   //调用Marshal.PtrToStructure(ptr,structure.GetType());方法

   //将数据从非托管内存块封送到托管对象。
   //指向非托管内存块的指针。lpVHdr
   //将数据复制到其中的对象。这必须是格式化类的实例。videoHeader


   VideoData = new byte[videoHeader.dwBytesUsed];//实际使用的大小,初始化Byte缓冲大小定义

   showVideo.Copy(videoHeader.lpData, VideoData);//从非托管内存指针复制到托管数组。
                  //指向数据缓存的指针
   if (this.RecievedFrame != null)
    this.RecievedFrame (VideoData);
  }
 }



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