65.9K
CodeProject 正在变化。 阅读更多。
Home

又一个摄像头控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (88投票s)

2012年2月14日

CPOL

5分钟阅读

viewsIcon

611732

downloadIcon

34938

在本文中,您将找到另一个摄像头控件的实现。


330177/screen.jpg

引言

在本文中,您将找到另一个摄像头控件的实现。该控件简单易用:没有额外的依赖项,界面简洁。

该控件提供以下功能:

  1. 获取系统中可用的摄像头设备列表。
  2. 显示摄像头设备的视频流。
  3. 获取当前捕获的图像。

要求

  1. 该控件的 WinForms 版本使用 .NET Framework 2.0 实现。
  2. 该控件的 WPF 版本使用 .NET Framework 4 Client Profile 实现。
  3. 该控件使用 VMR-7 渲染器过滤器,该过滤器自 Windows XP SP1 起可用。

该控件支持 x86 和 x64 平台目标。

背景

在 Windows 中有多种捕获视频流的方法。不一一列举,基本的方法是 DirectShow 框架和 AVICap 库。我们将使用基于 DirectShow 的方法,因为它更强大、更灵活。

DirectShow 框架使用如图所示的概念进行操作:图、过滤器和引脚。过滤器构成捕获图,媒体流在其中流动。图中的过滤器通过引脚相互连接。摄像头是视频流开始的捕获过滤器。控件的窗口被传递给渲染器过滤器,该过滤器接收并显示视频流。可能存在其他中间过滤器,例如颜色空间转换过滤器。以上就是捕获图的全部内容。有关更多信息,请参阅 MSDN DirectShow 文档

实现细节

如果您对实现细节不感兴趣,可以 跳过 本节。

实现分为三个层。

  1. 底层实现为本地 DLL 模块,它将我们的调用转发到 DirectShow 框架。
  2. 为了方便分发,本地 DLL 模块被嵌入到控件的程序集中作为资源。在运行时,DLL 模块将被提取到磁盘上的临时文件中,并通过后期绑定技术使用。一旦控件被释放,临时文件将被删除。换句话说,该控件作为单个文件分发。所有这些操作都由中间层实现。
  3. 顶层实现了控件类本身以及用于标识摄像头设备的 WebCameraId 类。

下图显示了实现逻辑结构。

只有顶层供客户端使用。

底层

底层实现了以下用于处理捕获图的实用程序。

/// <summary>
/// Enumerates video input devices in a system.
/// </summary>
/// <param name="callback">A callback method.</param>
DSUTILS_API void __stdcall EnumVideoInputDevices(EnumVideoInputDevicesCallback callback);

/// <summary>
/// Builds a video capture graph.
/// </summary>
/// <returns>If the function succeeds, the return value is zero.</returns>
DSUTILS_API int __stdcall BuildCaptureGraph();

/// <summary>
/// Adds a renderer filter to a video capture graph,
/// which renders a video stream within a container window.
/// </summary>
/// <param name="hWnd">A container window that video should be clipped to.</param>
/// <returns>If the function succeeds, the return value is zero.</returns>
DSUTILS_API int __stdcall AddRenderFilter(HWND hWnd);

/// <summary>
/// Adds a video stream source to a video capture graph.
/// </summary>
/// <param name="devicePath">A device path of a video capture filter to add.</param>
/// <returns>If the function succeeds, the return value is zero.</returns>
DSUTILS_API int __stdcall AddCaptureFilter(BSTR devicePath);

/// <summary>
/// Removes a video stream source from a video capture graph.
/// </summary>
/// <returns>If the function succeeds, the return value is zero.</returns>
DSUTILS_API int __stdcall ResetCaptureGraph();

/// <summary>
/// Runs all the filters in a video capture graph. While the graph is running,
/// data moves through the graph and is rendered. 
/// </summary>
/// <returns>If the function succeeds, the return value is zero.</returns>
DSUTILS_API int __stdcall Start();

/// <summary>
/// Stops all the filters in a video capture graph.
/// </summary>
/// <returns>If the function succeeds, the return value is zero.</returns>
DSUTILS_API int __stdcall Stop();

/// <summary>
/// Retrieves the current image being displayed by the renderer filter.
/// </summary>
/// <param name="ppDib">Address of a pointer to a BYTE that will receive the DIB.</param>
/// <returns>If the function succeeds, the return value is zero.</returns>
DSUTILS_API int __stdcall GetCurrentImage(BYTE **ppDib);

/// <summary>
/// Retrieves the unstretched video size.
/// </summary>
/// <param name="lpWidth">A pointer to a LONG that will receive the width.</param>
/// <param name="lpHeight">A pointer to a LONG that will receive the height.</param>
/// <returns>If the function succeeds, the return value is zero.</returns>
DSUTILS_API int __stdcall GetVideoSize(LONG *lpWidth, LONG *lpHeight);

/// <summary>
/// Destroys a video capture graph.
/// </summary>
DSUTILS_API void __stdcall DestroyCaptureGraph();

中间层

中间层在 DirectShowProxy 类中实现。

首先,我们应该做的是从资源中提取捕获图工具 DLL 模块,并将其保存到临时文件中。

_dllFile = Path.GetTempFileName();
using (FileStream stream = new FileStream(_dllFile, FileMode.Create, FileAccess.Write))
{
    using (BinaryWriter writer = new BinaryWriter(stream))
    {
        writer.Write(IsX86Platform ?
            Resources.DirectShowFacade : Resources.DirectShowFacade64);
    }
}

然后我们将 DLL 模块加载到调用进程的地址空间中。

_hDll = LoadLibrary(_dllFile);
if (_hDll == IntPtr.Zero)
{
    throw new Win32Exception(Marshal.GetLastWin32Error());
}

并将 DLL 模块函数绑定到类实例方法。

private delegate Int32 BuildCaptureGraphDelegate();
private BuildCaptureGraphDelegate _buildCaptureGraph;

// ...

IntPtr pProcPtr = GetProcAddress(_hDll, "BuildCaptureGraph");
_buildCaptureGraph =
    (BuildCaptureGraphDelegate)Marshal.GetDelegateForFunctionPointer(pProcPtr, 
     typeof(BuildCaptureGraphDelegate));

当控件被释放时,我们将卸载 DLL 模块并将其删除。

public void Dispose()
{
    if (_hDll != IntPtr.Zero)
    {
        FreeLibrary(_hDll);
        _hDll = IntPtr.Zero;
    }

    if (File.Exists(_dllFile))
    {
        File.Delete(_dllFile);
    }
}

顶层

顶层在 WebCameraControl 类中实现,具有以下接口。

/// <summary>
/// Gets a list of available video capture devices.
/// </summary>                                 
/// <exception cref="Win32Exception">Failed to load the DirectShow utilities dll.</exception>
public IEnumerable<WebCameraId> GetVideoCaptureDevices();

/// <summary>
/// Gets a value indicating whether the control is capturing a video stream.
/// </summary>
public Boolean IsCapturing { get; }

/// <summary>
/// Starts a capture.
/// </summary>
/// <param name="camera">The camera to capture from.</param>
/// <exception cref="ArgumentNullException">A null reference is passed as an argument.</exception>
/// <exception cref="Win32Exception">Failed to load the DirectShow utilities dll.</exception>
/// <exception cref="DirectShowException">Failed to run a video capture graph.</exception>
public void StartCapture(WebCameraId camera);

/// <summary>
/// Retrieves the unstretched image being captured.
/// </summary>
/// <returns>The current image.</returns>
/// <exception cref="InvalidOperationException">The control is not capturing a video stream.</exception>
/// <exception cref="DirectShowException">Failed to get the current image.</exception>
public Bitmap GetCurrentImage();

/// <summary>
/// Gets the unstretched video size.
/// </summary>
public Size VideoSize { get; }

/// <summary>
/// Stops a capture.
/// </summary>
/// <exception cref="InvalidOperationException">The control is not capturing a video stream.</exception>
/// <exception cref="DirectShowException">Failed to stop a video capture graph.</exception>
public void StopCapture();

用法

打开 包管理器控制台 并将 nuget 包添加到您的项目中。

Install-Package WebEye.Controls.WinForms.WebCameraControl

首先,我们需要使用右键单击,然后选择“选择项...”菜单项将控件添加到 Visual Studio 设计器工具箱。然后,我们将控件放置在窗体上所需的位置和大小。控件实例变量的默认名称将是 webCameraControl1

然后,在运行时,我们需要获取系统中可用的摄像头列表。

List<WebCameraId> cameras = new List<WebCameraId>(webCameraControl1.GetVideoCaptureDevices());

以下代码从列表中的第一个摄像头开始捕获。

webCameraControl1.StartCapture(cameras[0]); 

请注意,在开始捕获之前必须创建控件的窗口,否则由于视频流没有输出引脚而导致异常。常见的错误是在 Form.Load 事件处理程序中开始捕获,此时控件的窗口尚未创建。

要获取正在捕获的图像,只需调用 GetCurrentImage() 方法。图像的分辨率和质量取决于您的摄像头设备特性。

Bitmap image = webCameraControl1.GetCurrentImage();

要停止捕获,可以使用 StopCapture() 方法。

webCameraControl1.StopCapture();

您可以使用以下代码随时查询捕获状态。

if (webCameraControl1.IsCapturing)
{
    webCameraControl1.StopCapture();
}

要从另一个摄像头开始捕获,只需再次调用 StartCapture 方法。

webCameraControl1.StartCapture(cameras[1]);

要报告错误,使用异常,所以不要忘记将代码包装在 try/catch 块中。以上就是使用方法。有关完整示例,请查看演示应用程序的源代码。

WPF 版本

在 WPF 用户控件中,没有与它们关联的 WinAPI 窗口句柄 (HWND),这是一个问题,因为 DirectShow 框架需要窗口句柄才能输出视频流。引入 VideoWindow 类来解决此问题。

<UserControl x:Class="WebCamera.WebCameraControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"
             xmlns:local="clr-namespace:WebCamera">
    <local:VideoWindow x:Name="_videoWindow" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</UserControl>

要将控件的 WPF 版本添加到您的项目中,请使用以下 nuget 命令。

Install-Package WebEye.Controls.Wpf.WebCameraControl

GitHub

该项目在以下页面上有一个 GitHub 存储库。

https://github.com/jacobbo/WebEye

欢迎提问、评论和反馈。

历史 

  • 2017 年 11 月 5 日 - 用 VMR7 替换 VMR9 以扩展支持的配置范围。
  • 2016 年 2 月 29 日 - 添加了 nuget 包。
  • 2015 年 4 月 10 日 - 添加了 x64 平台支持。
  • 2012 年 10 月 24 日 - 添加了 GitHub 存储库。
  • 2012 年 9 月 12 日 - 更新了演示应用程序以报告异常。
  • 2012 年 9 月 9 日 - 控件的 WPF 版本及演示应用程序。
  • 2012 年 2 月 19 日 - 更新了演示代码和文档。
  • 2012 年 2 月 15 日 - 初始版本。
© . All rights reserved.