Windows 系統安裝攝像頭驅動程式後,會註冊 avicap32.dll(Video for Windows 捕獲介面)。透過 P/Invoke 呼叫此非受控 DLL,即可控制攝像頭擷取影像。
定義所有必要的 Win32 API 宣告、常數、結構體與委派:
public class 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);
// 取得驅動程式描述
[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);
// 常數定義
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;
// 結構體定義
[StructLayout(LayoutKind.Sequential)]
public struct VIDEOHDR
{
public int lpData;
public int dwBufferLength;
public int dwBytesUsed;
public int dwTimeCaptured;
public int dwUser;
public int dwFlags;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public int[] dwReserved;
}
[StructLayout(LayoutKind.Sequential)]
public struct BITMAPINFOHEADER
{
public int biSize;
public int biWidth;
public int biHeight;
public short biPlanes;
public short biBitCount;
public int biCompression;
public int biSizeImage;
public int biXPelsPerMeter;
public int biYPelsPerMeter;
public int biClrUsed;
public int biClrImportant;
}
[StructLayout(LayoutKind.Sequential)]
public struct BITMAPINFO
{
public BITMAPINFOHEADER bmiHeader;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024)]
public int[] bmiColors;
}
// 幀回呼委派
public delegate void FrameEventHandler(IntPtr lwnd, IntPtr lpVHdr);
// 協助方法
public static object GetStructure(IntPtr ptr, ValueType structure)
=> Marshal.PtrToStructure(ptr, structure.GetType());
public static void Copy(IntPtr ptr, byte[] data)
=> Marshal.Copy(ptr, data, 0, data.Length);
public static int SizeOf(object structure)
=> Marshal.SizeOf(structure);
}
public class WebCamera
{
private IntPtr _hwndCapture;
private IntPtr _controlHandle;
private int _width, _height;
private ShowVideo.FrameEventHandler _frameHandler;
public delegate void FrameReceivedEventHandler(byte[] frameData);
public event FrameReceivedEventHandler FrameReceived;
public WebCamera(IntPtr controlHandle, int width, int height)
{
_controlHandle = controlHandle;
_width = width; _height = height;
}
public void Start()
{
byte[] name = new byte[100], ver = new byte[100];
ShowVideo.capGetDriverDescriptionA(0, name, 100, ver, 100);
_hwndCapture = ShowVideo.capCreateCaptureWindowA(
name, ShowVideo.WS_VISIBLE | ShowVideo.WS_CHILD,
0, 0, _width, _height, _controlHandle, 0);
if (SendDriverConnect())
{
SetPreviewRate(66);
SetPreview(true);
SetVideoFormat();
_frameHandler = FrameCallback;
SetCallbackOnFrame(_frameHandler);
ShowVideo.SetWindowPos(_hwndCapture, 0, 0, 0, _width, _height,
ShowVideo.SWP_NOMOVE | ShowVideo.SWP_NOZORDER);
}
}
public void Stop()
{
ShowVideo.SendMessage(_hwndCapture, ShowVideo.WM_CAP_DRIVER_DISCONNECT, 0, 0);
}
private bool SendDriverConnect()
=> ShowVideo.SendMessage(_hwndCapture, ShowVideo.WM_CAP_DRIVER_CONNECT, (short)0, 0);
private void SetPreviewRate(short rate)
=> ShowVideo.SendMessage(_hwndCapture, ShowVideo.WM_CAP_SET_PREVIEWRATE, rate, 0);
private void SetPreview(bool enable)
=> ShowVideo.SendMessage(_hwndCapture, ShowVideo.WM_CAP_SET_PREVIEW, enable, 0);
private void SetCallbackOnFrame(ShowVideo.FrameEventHandler handler)
=> ShowVideo.SendMessage(_hwndCapture, ShowVideo.WM_CAP_SET_CALLBACK_FRAME, 0, handler);
private void SetVideoFormat()
{
var bmi = new ShowVideo.BITMAPINFO();
bmi.bmiHeader.biSize = ShowVideo.SizeOf(bmi.bmiHeader);
bmi.bmiHeader.biWidth = 352;
bmi.bmiHeader.biHeight = 288;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 24;
ShowVideo.SendMessage(_hwndCapture, ShowVideo.WM_CAP_SET_VIDEOFORMAT,
ShowVideo.SizeOf(bmi), ref bmi);
}
private void FrameCallback(IntPtr lwnd, IntPtr lpVHdr)
{
var vhdr = (ShowVideo.VIDEOHDR)ShowVideo.GetStructure(lpVHdr, new ShowVideo.VIDEOHDR());
byte[] data = new byte[vhdr.dwBytesUsed];
ShowVideo.Copy(vhdr.lpData, data);
FrameReceived?.Invoke(data);
}
}
// 在 Form 載入時
var cam = new WebCamera(pictureBox1.Handle, 320, 240);
cam.FrameReceived += (data) => {
// 將 byte[] 顯示於 PictureBox(需轉換為 Bitmap)
};
cam.Start();
// 關閉時
cam.Stop();
0 代表第一台裝置,多裝置需列舉