傳統 Windows 攝像頭開發透過 Video for Windows (VFW) API (avicap32.dll) 實作影像預覽、擷取。雖然 .NET 後期有 DirectShow (DirectX.Capture) 與 Media Foundation 封裝,但 VFW 因簡單輕量仍常用於簡易應用。本文提供完整 P/Invoke 封裝與 WinForms 整合範例。
using System;
using System.Runtime.InteropServices;
public static class Avicap32
{
public const int WM_USER = 0x400;
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_PREVIEW = WM_USER + 50;
public const int WM_CAP_SET_PREVIEWRATE = WM_USER + 52;
public const int WM_CAP_SET_CALLBACK_FRAME = WM_USER + 5;
public const int WM_CAP_SET_VIDEOFORMAT = WM_USER + 45;
public const int WS_CHILD = 0x40000000;
public const int WS_VISIBLE = 0x10000000;
[DllImport("avicap32.dll", EntryPoint = "capCreateCaptureWindowA")]
public static extern IntPtr CreateCaptureWindow(string name, int style, int x, int y, int w, int h, IntPtr parent, int id);
[DllImport("avicap32.dll", EntryPoint = "capGetDriverDescriptionA")]
public static extern bool GetDriverDescription(short index, StringBuilder name, int nameSize, StringBuilder ver, int verSize);
[DllImport("user32.dll", EntryPoint = "SendMessage")]
public static extern bool SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
[DllImport("user32.dll", EntryPoint = "SendMessage")]
public static extern bool SendMessage(IntPtr hWnd, int msg, int wParam, FrameCallback lParam);
[DllImport("user32.dll", EntryPoint = "SendMessage")]
public static extern bool SendMessage(IntPtr hWnd, int msg, int wParam, ref BitmapInfo lParam);
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
public static extern int SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int y, int cx, int cy, int flags);
[StructLayout(LayoutKind.Sequential)]
public struct BitmapInfoHeader
{
public int biSize, biWidth, biHeight;
public short biPlanes, biBitCount;
public int biCompression, biSizeImage, biXPelsPerMeter, biYPelsPerMeter, biClrUsed, biClrImportant;
}
[StructLayout(LayoutKind.Sequential)]
public struct BitmapInfo
{
public BitmapInfoHeader bmiHeader;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024)]
public int[] bmiColors;
}
[StructLayout(LayoutKind.Sequential)]
public struct VideoHdr
{
public IntPtr lpData;
public int dwBufferLength, dwBytesUsed, dwTimeCaptured, dwUser, dwFlags;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public int[] dwReserved;
}
public delegate void FrameCallback(IntPtr hWnd, IntPtr lpVHdr);
}
public class WebCamera
{
private IntPtr _capHwnd;
private readonly IntPtr _parentHandle;
private readonly int _width, _height;
private Avicap32.FrameCallback _frameCallback;
public event Action FrameReceived;
public WebCamera(IntPtr parentHandle, int width, int height)
{
_parentHandle = parentHandle;
_width = width; _height = height;
}
public void Start()
{
StringBuilder name = new StringBuilder(100), ver = new StringBuilder(100);
Avicap32.GetDriverDescription(0, name, 100, ver, 100);
_capHwnd = Avicap32.CreateCaptureWindow(name.ToString(),
Avicap32.WS_VISIBLE | Avicap32.WS_CHILD, 0, 0, _width, _height, _parentHandle, 0);
if (Send(Avicap32.WM_CAP_DRIVER_CONNECT, 0))
{
Send(Avicap32.WM_CAP_SET_PREVIEWRATE, 66); // ~15fps
Send(Avicap32.WM_CAP_SET_PREVIEW, 1);
var bmi = new Avicap32.BitmapInfo();
bmi.bmiHeader.biSize = Marshal.SizeOf(typeof(Avicap32.BitmapInfoHeader));
bmi.bmiHeader.biWidth = 352;
bmi.bmiHeader.biHeight = 288;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 24;
Send(Avicap32.WM_CAP_SET_VIDEOFORMAT, ref bmi);
_frameCallback = OnFrameCallback;
Send(Avicap32.WM_CAP_SET_CALLBACK_FRAME, _frameCallback);
Avicap32.SetWindowPos(_capHwnd, 0, 0, 0, _width, _height, 6);
}
}
public void Stop()
{
if (_capHwnd != IntPtr.Zero)
Send(Avicap32.WM_CAP_DRIVER_DISCONNECT);
}
private bool Send(int msg, int param) => Avicap32.SendMessage(_capHwnd, msg, param, 0);
private bool Send(int msg, ref Avicap32.BitmapInfo param) => Avicap32.SendMessage(_capHwnd, msg, 0, ref param);
private bool Send(int msg, Avicap32.FrameCallback cb) => Avicap32.SendMessage(_capHwnd, msg, 0, cb);
private void OnFrameCallback(IntPtr hWnd, IntPtr lpVHdr)
{
var vh = (Avicap32.VideoHdr)Marshal.PtrToStructure(lpVHdr, typeof(Avicap32.VideoHdr));
byte[] data = new byte[vh.dwBytesUsed];
Marshal.Copy(vh.lpData, data, 0, vh.dwBytesUsed);
FrameReceived?.Invoke(data);
}
}
public partial class MainForm : Form
{
private WebCamera _cam;
private Bitmap _currentFrame;
public MainForm()
{
InitializeComponent();
_cam = new WebCamera(panelPreview.Handle, panelPreview.Width, panelPreview.Height);
_cam.FrameReceived += OnFrameReceived;
}
private void BtnStart_Click(object sender, EventArgs e)
{
_cam.Start();
btnStart.Enabled = false; btnStop.Enabled = true;
}
private void BtnStop_Click(object sender, EventArgs e)
{
_cam.Stop();
btnStart.Enabled = true; btnStop.Enabled = false;
}
private void OnFrameReceived(byte[] data)
{
if (IsDisposed) return;
using (var ms = new MemoryStream(data))
{
Bitmap bmp = new Bitmap(ms);
// 於 UI 執行緒更新
BeginInvoke((Action)(() => {
if (pictureBox1.Image != null) pictureBox1.Image.Dispose();
pictureBox1.Image = bmp;
}));
}
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
_cam?.Stop();
base.OnFormClosed(e);
}
}
VFW 僅支援舊式類比/早期 USB 攝像頭,解析度、格式受限,新專案建議採用上述現代框架。