如何关联 USB 耳机麦克风和 earphone/speaker

How to associate a USB headsets microphone and earphone/speaker

背景

我需要在软电话应用程序中显示连接的耳机列表(麦克风和耳机组合)。为了进行测试,我有以下设备:

用户应该能够从 ComboBox 中 select 耳机,而无需 select 单独使用麦克风和耳机。

资料

我知道在哪里可以找到关于麦克风和耳机的信息(在 Windows 中),但是我无法使用 WMIMMDevice API 获得它。

要查找信息,请右键单击任务栏右侧的 Sound(扬声器图标),然后 select Playback devices

  1. 通过双击或单击 Properties 打开属性 window。
  2. 单击属性 window 上的 Properties 按钮。
  3. 单击 Details 选项卡并在 ComboBox 中找到 Children 属性。

这将给我以下信息:

SWD\MMDEVAPI\{0.0.0.00000000}.{f2e09e37-8389-46c4-8b2b-53e08b874399}
SWD\MMDEVAPI\{0.0.1.00000000}.{3402ee6e-d862-47ca-8ab8-bb8254216032}

第一行匹配我的 Headset Earphone (Jabra PRO 9470) 和第二行 Headset Microphone (Jabra PRO 9470).

为了在 C# 中获得相同的信息,我循环遍历 Win32_USBControllerDevice class 并输出包含 "MMDEVAPI" 的所有值。在我的电脑上它将 return 6 个值(3 x 麦克风,3 x 耳机)。

foreach (var device in new ManagementObjectSearcher("SELECT * FROM Win32_USBControllerDevice").Get())
{
    foreach (var property in device.Properties)
    {
        // Gets the value of the property on the device.
        var value = property.Value == null ? string.Empty : property.Value.ToString();

        if (value.IndexOf("mmdevapi", StringComparison.OrdinalIgnoreCase) > -1)
        {
            // Output connected USB microphones and earphones.
            Console.WriteLine(property.Value);
        }
    }
}

作为参考,上面的代码会输出:

\PC9018\root\cimv2:Win32_PnPEntity.DeviceID="SWD\MMDEVAPI\{0.0.0.00000000}.{F2E09E37-8389-46C4-8B2B-53E08B874399}"
\PC9018\root\cimv2:Win32_PnPEntity.DeviceID="SWD\MMDEVAPI\{0.0.1.00000000}.{3402EE6E-D862-47CA-8AB8-BB8254216032}"
\PC9018\root\cimv2:Win32_PnPEntity.DeviceID="SWD\MMDEVAPI\{0.0.0.00000000}.{985F2B5C-2EE2-4733-BBD6-48BFDE2D5582}"
\PC9018\root\cimv2:Win32_PnPEntity.DeviceID="SWD\MMDEVAPI\{0.0.1.00000000}.{71D824EA-DAE9-4F0D-B673-4425385E3777}"
\PC9018\root\cimv2:Win32_PnPEntity.DeviceID="SWD\MMDEVAPI\{0.0.0.00000000}.{D29C0970-D515-4F91-9924-F0063CF1A196}"
\PC9018\root\cimv2:Win32_PnPEntity.DeviceID="SWD\MMDEVAPI\{0.0.1.00000000}.{C4B331E2-C56B-4D9B-A486-2ED6C11FDB8C}"

问题

我现在的大问题是,如何将正确的耳机麦克风和耳机关联到 Headset 对象中?

尝试

我曾尝试在 Google 和 Whosebug 上搜索答案或提示,但我无法使用 WMIMMDevice API 找到麦克风和耳机之间的任何共同点或关系。

如果有一种方法可以创建 Dictionary<string, List<string>>,其中 Key 是物理设备或 USB 端口独有的东西,而 Value 是关联的列表 Win32_PnPEntity.DeviceID,那我就找不到了。

本着 星球大战日 几天后的精神:“帮助我,Whosebug。你是我唯一的希望。

NAudio 和 Windows 注册表的帮助下,我似乎自己找到了答案。问题中的代码仍然是正确的,但 NAudio 使它变得更容易一些。

我在 Windows 注册表中找到了问题的关键:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Capture
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Render

每个设备都有一个名为 {b3f8fa53-0004-438e-9003-51a46e139bfc},2 的 属性,其值类似于此 {1}.USB\VID_047F&PID_0416&MI_00&21995D75&0&0000

这看起来是一个唯一的硬件ID,由耳机麦克风和耳机共享(与问题中的Children 属性相同)。

解决方法

具有单一定位方法和return耳机列表的界面。

public interface IHeadsetLocator
{
    /// <summary>
    /// Locate all connected audio devices based on the given state.
    /// </summary>
    /// <param name="deviceState"></param>
    /// <returns></returns>
    IReadOnlyCollection<Headset> LocateConnectedAudioDevices(DeviceState deviceState = DeviceState.Active);
}

并完成实施。真正的魔法是在 GetHardwareToken 方法的帮助下发生的。

public class HeadsetLocator : IHeadsetLocator
{
    /// <summary>
    /// Locate all connected audio devices based on the given state.
    /// </summary>
    /// <param name="deviceState"></param>
    /// <returns></returns>
    public IReadOnlyCollection<Headset> LocateConnectedAudioDevices(DeviceState deviceState = DeviceState.Active)
    {
        var enumerator = new MMDeviceEnumerator();
        var relatedAudioDevices = new ConcurrentDictionary<string, List<MMDevice>>();
        var headsets = new List<Headset>();

        // Locate all connected audio devices.
        foreach (var device in enumerator.EnumerateAudioEndPoints(DataFlow.All, deviceState))
        {
            // Gets the DataFlow and DeviceID from the connected audio device.
            var index = device.ID.LastIndexOf('.');
            var dataFlow = device.ID.Substring(0, index).Contains("0.0.0") ? DataFlow.Render : DataFlow.Capture;
            var deviceId = device.ID.Substring(index + 1);

            // Gets the unique hardware token.
            var hardwareToken = GetHardwareToken(dataFlow, deviceId);

            var audioDevice = relatedAudioDevices.GetOrAdd(hardwareToken, o => new List<MMDevice>());
            audioDevice.Add(device);
        }

        // Combines the related devices into a headset object.
        foreach (var devicePair in relatedAudioDevices)
        {
            var capture = devicePair.Value.FirstOrDefault(o => o.ID.Contains("0.0.1"));
            var render = devicePair.Value.FirstOrDefault(o => o.ID.Contains("0.0.0"));

            if (capture != null && render != null)
            {
                headsets.Add(new Headset("Headset", render.DeviceFriendlyName, capture, render));
            }
        }

        return new ReadOnlyCollection<Headset>(headsets);
    }

    /// <summary>
    /// Gets the token of the USB device.
    /// </summary>
    /// <param name="dataFlow"></param>
    /// <param name="audioDeviceId"></param>
    /// <returns></returns>
    public string GetHardwareToken(DataFlow dataFlow, string audioDeviceId)
    {
        using (var registryKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64))
        {
            var captureKey = registryKey.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\" + dataFlow + @"\" + audioDeviceId + @"\Properties");
            if (captureKey != null)
            {
                return captureKey.GetValue("{b3f8fa53-0004-438e-9003-51a46e139bfc},2") as string;
            }
        }

        return null;
    }
}

希望这对处于类似情况的其他人有所帮助。

编码愉快.