又一个摄像头控件






4.88/5 (88投票s)
在本文中,您将找到另一个摄像头控件的实现。
引言
在本文中,您将找到另一个摄像头控件的实现。该控件简单易用:没有额外的依赖项,界面简洁。
该控件提供以下功能:
- 获取系统中可用的摄像头设备列表。
- 显示摄像头设备的视频流。
- 获取当前捕获的图像。
要求
- 该控件的 WinForms 版本使用 .NET Framework 2.0 实现。
- 该控件的 WPF 版本使用 .NET Framework 4 Client Profile 实现。
- 该控件使用 VMR-7 渲染器过滤器,该过滤器自 Windows XP SP1 起可用。
该控件支持 x86 和 x64 平台目标。
背景
在 Windows 中有多种捕获视频流的方法。不一一列举,基本的方法是 DirectShow 框架和 AVICap 库。我们将使用基于 DirectShow 的方法,因为它更强大、更灵活。
DirectShow 框架使用如图所示的概念进行操作:图、过滤器和引脚。过滤器构成捕获图,媒体流在其中流动。图中的过滤器通过引脚相互连接。摄像头是视频流开始的捕获过滤器。控件的窗口被传递给渲染器过滤器,该过滤器接收并显示视频流。可能存在其他中间过滤器,例如颜色空间转换过滤器。以上就是捕获图的全部内容。有关更多信息,请参阅 MSDN DirectShow 文档。
实现细节
如果您对实现细节不感兴趣,可以 跳过 本节。
实现分为三个层。
- 底层实现为本地 DLL 模块,它将我们的调用转发到 DirectShow 框架。
- 为了方便分发,本地 DLL 模块被嵌入到控件的程序集中作为资源。在运行时,DLL 模块将被提取到磁盘上的临时文件中,并通过后期绑定技术使用。一旦控件被释放,临时文件将被删除。换句话说,该控件作为单个文件分发。所有这些操作都由中间层实现。
- 顶层实现了控件类本身以及用于标识摄像头设备的
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 日 - 初始版本。