C# 使用com获取Windows摄像头列表

 

前言

想使用ffmpeg打开摄像头,需要输入摄像头的名称,而ffmpeg本身的枚举摄像头列表功能不是接口
,所以需要用其他方式获取到设备列表。C++获取视频设备列表的方法有不少,但C#获取视频设备列表的方法网上提供的解决方案基本都是依赖第三方库的,为了获取视频设备列表而引入一整个视频库实在是不太必要。经过思考,Windows的directshow和mediafudation都是基于com的,而且C#对com的支持是很好的,基于上述两点我们完全可以在C#中直接调用com。

 

一、定义com接口

我们使用directshow获取视频设备列表,由于com的跨语言特性,完全可以直接在C#中调用,而不用通过C++封装一层dll给C#使用。我们首先定义需要的com对象接口。

static readonly Guid SystemDeviceEnum = new Guid(0x62BE5D10, 0x60EB, 0x11D0, 0xBD, 0x3B, 0x00, 0xA0, 0xC9, 0x11, 0xCE, 0x86);
static readonly Guid VideoInputDevice = new Guid(0x860BB310, 0x5D01, 0x11D0, 0xBD, 0x3B, 0x00, 0xA0, 0xC9, 0x11, 0xCE, 0x86);
[Flags]
enum CDef
{
  None = 0x0,
  ClassDefault = 0x1,
  BypassClassManager = 0x2,
  ClassLegacy = 0x4,
  MeritAboveDoNotUse = 0x8,
  DevmonCMGRDevice = 0x10,
  DevmonDMO = 0x20,
  DevmonPNPDevice = 0x40,
  DevmonFilter = 0x80,
  DevmonSelectiveMask = 0xF0
}
[ComImport]
[SuppressUnmanagedCodeSecurity]
[Guid("3127CA40-446E-11CE-8135-00AA004BB851")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IErrorLog
{
  [PreserveSig]
  int AddError([In][MarshalAs(UnmanagedType.LPWStr)] string pszPropName, [In] System.Runtime.InteropServices.ComTypes.EXCEPINFO pExcepInfo);
}
[ComImport]
[Localizable(false)]
[SuppressUnmanagedCodeSecurity]
[Guid("55272A00-42CB-11CE-8135-00AA004BB851")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IPropertyBag
{
  [PreserveSig]
  int Read([In][MarshalAs(UnmanagedType.LPWStr)] string pszPropName, [MarshalAs(UnmanagedType.Struct)] out object pVar, [In] IErrorLog pErrorLog);

  [PreserveSig]
  int Write([In][MarshalAs(UnmanagedType.LPWStr)] string pszPropName, [In][MarshalAs(UnmanagedType.Struct)] ref object pVar);
}

[ComImport]
[SuppressUnmanagedCodeSecurity]
[Guid("29840822-5B84-11D0-BD3B-00A0C911CE86")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface ICreateDevEnum
{
  [PreserveSig]
  int CreateClassEnumerator([In][MarshalAs(UnmanagedType.LPStruct)] Guid pType, out IEnumMoniker ppEnumMoniker, [In] CDef dwFlags);
}

 

二、枚举设备

与directshow流程一样,调用com枚举设备即可,本文只展示获取设备名称(FriendlyName),获取其他属性可以参照c++调用directshow的实现。

        /// <summary>
      /// 枚举视频设备
      /// </summary>
      public static IEnumerable<string> Devices
      {
          get
          {
              IMoniker[] monikers = new IMoniker[5];
              var devEnum = Activator.CreateInstance(Type.GetTypeFromCLSID(SystemDeviceEnum)) as ICreateDevEnum;
              IEnumMoniker moniker;
              if (devEnum.CreateClassEnumerator(VideoInputDevice, out moniker, 0) == 0)
              {
                  while (true)
                  {
                      int r = moniker.Next(1, monikers, IntPtr.Zero);
                      if (r != 0 || monikers[0] == null)
                          break;
                      yield return GetName(monikers[0]);
                      foreach (var i in monikers)
                      {
                          if(i!=null)
                          Marshal.ReleaseComObject(i);
                      }                      
                  }
                  Marshal.ReleaseComObject(moniker);
              }
              Marshal.ReleaseComObject(devEnum);
          }
      }
      /// <summary>
      /// 获取设备名称
      /// </summary>
      /// <param name="moniker"></param>
      /// <returns></returns>
      static string GetName(IMoniker moniker)
      {
          IPropertyBag property;
          object value;
          object temp = null;
          try
          {
              Guid guid = typeof(IPropertyBag).GUID;
              moniker.BindToStorage(null, null, ref guid, out temp);
              property = temp as IPropertyBag;
              int hr = property.Read("FriendlyName", out value, null);
              Marshal.ThrowExceptionForHR(hr);
              return value as string;
          }
          catch (Exception)
          {
              return null;
          }
          finally
          {
              if (temp != null)
              {
                  Marshal.ReleaseComObject(temp);
              }
          }
      }

 

三、完整代码

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Security;
namespace AC
{
  public class EnumDevices
  {
      /// <summary>
      /// 枚举视频设备
      /// </summary>
      public static IEnumerable<string> Devices
      {
          get
          {
              IMoniker[] monikers = new IMoniker[5];
              var devEnum = Activator.CreateInstance(Type.GetTypeFromCLSID(SystemDeviceEnum)) as ICreateDevEnum;
              IEnumMoniker moniker;
              if (devEnum.CreateClassEnumerator(VideoInputDevice, out moniker, 0) == 0)
              {
                  while (true)
                  {
                      int hr = moniker.Next(1, monikers, IntPtr.Zero);
                      if (hr != 0 || monikers[0] == null)
                          break;
                      yield return GetName(monikers[0]);
                      foreach (var i in monikers)
                      {
                          if(i!=null)
                          Marshal.ReleaseComObject(i);
                      }                      
                  }
                  Marshal.ReleaseComObject(moniker);
              }
              Marshal.ReleaseComObject(devEnum);
          }
      }
      /// <summary>
      /// 获取设备名称
      /// </summary>
      /// <param name="moniker"></param>
      /// <returns></returns>
      static string GetName(IMoniker moniker)
      {
          IPropertyBag property;
          object value;
          object temp = null;
          try
          {
              Guid guid = typeof(IPropertyBag).GUID;
              moniker.BindToStorage(null, null, ref guid, out temp);
              property = temp as IPropertyBag;
              int hr = property.Read("FriendlyName", out value, null);
              Marshal.ThrowExceptionForHR(hr);
              return value as string;
          }
          catch (Exception)
          {
              return null;
          }
          finally
          {
              if (temp != null)
              {
                  Marshal.ReleaseComObject(temp);
              }
          }
      }
      static readonly Guid SystemDeviceEnum = new Guid(0x62BE5D10, 0x60EB, 0x11D0, 0xBD, 0x3B, 0x00, 0xA0, 0xC9, 0x11, 0xCE, 0x86);
      static readonly Guid VideoInputDevice = new Guid(0x860BB310, 0x5D01, 0x11D0, 0xBD, 0x3B, 0x00, 0xA0, 0xC9, 0x11, 0xCE, 0x86);
      [Flags]
      enum CDef
      {
          None = 0x0,
          ClassDefault = 0x1,
          BypassClassManager = 0x2,
          ClassLegacy = 0x4,
          MeritAboveDoNotUse = 0x8,
          DevmonCMGRDevice = 0x10,
          DevmonDMO = 0x20,
          DevmonPNPDevice = 0x40,
          DevmonFilter = 0x80,
          DevmonSelectiveMask = 0xF0
      }
      [ComImport]
      [SuppressUnmanagedCodeSecurity]
      [Guid("3127CA40-446E-11CE-8135-00AA004BB851")]
      [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
      interface IErrorLog
      {
          [PreserveSig]
          int AddError([In][MarshalAs(UnmanagedType.LPWStr)] string pszPropName, [In] System.Runtime.InteropServices.ComTypes.EXCEPINFO pExcepInfo);
      }
      [ComImport]
      [Localizable(false)]
      [SuppressUnmanagedCodeSecurity]
      [Guid("55272A00-42CB-11CE-8135-00AA004BB851")]
      [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
      interface IPropertyBag
      {
          [PreserveSig]
          int Read([In][MarshalAs(UnmanagedType.LPWStr)] string pszPropName, [MarshalAs(UnmanagedType.Struct)] out object pVar, [In] IErrorLog pErrorLog);

          [PreserveSig]
          int Write([In][MarshalAs(UnmanagedType.LPWStr)] string pszPropName, [In][MarshalAs(UnmanagedType.Struct)] ref object pVar);
      }

      [ComImport]
      [SuppressUnmanagedCodeSecurity]
      [Guid("29840822-5B84-11D0-BD3B-00A0C911CE86")]
      [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
      interface ICreateDevEnum
      {
          [PreserveSig]
          int CreateClassEnumerator([In][MarshalAs(UnmanagedType.LPStruct)] Guid pType, out IEnumMoniker ppEnumMoniker, [In] CDef dwFlags);
      }      
  }
}

 

四、使用示例

.net 6.0代码示例如下

// See https://aka.ms/new-console-template for more information
using AC;
//枚举设备
foreach (var i in EnumDevices.Devices)
{
  //打印设备名称
  Console.WriteLine(i);
}

效果:

在这里插入图片描述

 

总结

以上就是今天要讲的内容,本文介绍了C#直接调用com获取视频设备列表的方法,只要知道了com的一些基本原理以及c#和com的关系,很容易就能实现c#直接使用directshow的功能,第三方的库也是做了类似的工作,定义了完整的directshow的接口,只是笔者使用的环境中只需要枚举视频设备列表,不需要其他功能,引入完整的directshow接口有点大材小用,所以还不如自己定义几个必要的接口来的实在。

关于C# 使用com获取Windows摄像头列表的文章就介绍至此,更多相关C# 获取Windows摄像头列表内容请搜索编程宝库以前的文章,希望以后支持编程宝库

 利用GZip和Brotli压缩方法的优势,减少字符串数据的大小,提高.NET核心应用程序的性能。在开发应用程序时,你经常需要处理字符串。由于字符串对象在性能方面的成本很高,你经常想压缩你的字符 ...