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); } } }
本人声明:
个人主页:沐海(http://www.cnblogs.com/mahaisong)
以上文章都是经过本人设计实践和阅读其他文档得出。如果需要探讨或指教可以留言或加我QQ!欢迎交流!