如何在WPF(C#)语音录制程序中实现无第三方API的音频输入选择ComboBox
不依赖第三方库实现WPF麦克风选择方案
没问题!完全可以通过Windows原生的Core Audio APIs来实现,不需要任何第三方库。下面是一套完整的思路和可直接复用的代码示例:
核心思路
Windows从Vista开始提供了Core Audio API,其中的MMDeviceEnumerator组件可以枚举系统中的音频设备,我们可以通过它筛选出所有活动的麦克风捕获设备,然后将设备信息绑定到ComboBox供用户选择。
步骤1:定义Core Audio API互操作接口
因为Core Audio是COM API,我们需要手动定义必要的接口、枚举和结构体(也可以直接添加COM引用,但手动定义更灵活,避免版本依赖问题):
using System; using System.Runtime.InteropServices; // Core Audio API接口定义 [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface IMMDeviceEnumerator { void NotImpl1(); int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMMDevice ppDevice); int EnumAudioEndpoints(EDataFlow dataFlow, EDeviceState stateMask, out IMMDeviceCollection ppDevices); } [Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface IMMDeviceCollection { int GetCount(out uint pcDevices); int Item(uint nDevice, out IMMDevice ppDevice); } [Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface IMMDevice { int GetId([MarshalAs(UnmanagedType.LPWStr)] out string ppstrId); int GetState(out uint pdwState); int GetPropertyStore(int stgmAccess, out IPropertyStore ppProperties); } [Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface IPropertyStore { int GetCount(out uint cProps); int GetAt(uint iProp, out PropertyKey pkey); int GetValue(ref PropertyKey key, out PropVariant pv); } // 音频数据流方向枚举 internal enum EDataFlow { eRender, // 播放设备 eCapture, // 捕获设备(麦克风) eAll } // 设备角色枚举 internal enum ERole { eConsole, eMultimedia, eCommunications } // 设备状态枚举 internal enum EDeviceState { Active = 0x00000001, // 活动设备 Disabled = 0x00000002, NotPresent = 0x00000004, Unplugged = 0x00000008, All = 0x0000000F } // 属性键结构体 [StructLayout(LayoutKind.Sequential)] internal struct PropertyKey { public Guid fmtid; public uint pid; } // 属性值变体结构体 [StructLayout(LayoutKind.Sequential)] internal struct PropVariant { public ushort vt; public ushort wReserved1; public ushort wReserved2; public ushort wReserved3; public IntPtr pwszVal; // 存储字符串类型的值 } // Core Audio互操作工具类 internal static class CoreAudioInterop { [DllImport("ole32.dll")] public static extern int CoCreateInstance(ref Guid rclsid, IntPtr pUnkOuter, uint dwClsContext, ref Guid riid, out IMMDeviceEnumerator ppv); // MMDeviceEnumerator类ID public static readonly Guid MMDeviceEnumeratorClassId = new Guid("BCDE0395-E52F-467C-8E3D-C4579291692E"); // IMMDeviceEnumerator接口ID public static readonly Guid IID_IMMDeviceEnumerator = new Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"); // 设备友好名称的属性键 public static readonly PropertyKey PKEY_Device_FriendlyName = new PropertyKey { fmtid = new Guid("A45C254E-DF1C-4EFD-8020-67D146A850E0"), pid = 14 }; }
步骤2:枚举可用麦克风设备
编写方法来获取所有活动的麦克风设备,封装成便于绑定的实体类:
using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; // 麦克风设备实体类 public class MicrophoneDevice { // 设备唯一ID(用于后续录制初始化) public string DeviceId { get; set; } // 设备友好名称(显示在ComboBox中) public string FriendlyName { get; set; } } public static class MicrophoneEnumerator { public static List<MicrophoneDevice> GetActiveMicrophones() { var microphones = new List<MicrophoneDevice>(); // 创建MMDeviceEnumerator实例 int hr = CoreAudioInterop.CoCreateInstance( ref CoreAudioInterop.MMDeviceEnumeratorClassId, IntPtr.Zero, 1, // CLSCTX_INPROC_SERVER ref CoreAudioInterop.IID_IMMDeviceEnumerator, out IMMDeviceEnumerator enumerator); if (hr != 0) return microphones; // 枚举所有活动的捕获设备 hr = enumerator.EnumAudioEndpoints(EDataFlow.eCapture, EDeviceState.Active, out IMMDeviceCollection deviceCollection); if (hr != 0) { Marshal.ReleaseComObject(enumerator); return microphones; } deviceCollection.GetCount(out uint deviceCount); for (uint i = 0; i < deviceCount; i++) { deviceCollection.Item(i, out IMMDevice device); // 获取设备ID device.GetId(out string deviceId); // 获取设备属性存储 device.GetPropertyStore(0, out IPropertyStore propStore); // 获取设备友好名称 propStore.GetValue(ref CoreAudioInterop.PKEY_Device_FriendlyName, out PropVariant propVariant); string friendlyName = Marshal.PtrToStringUni(propVariant.pwszVal); microphones.Add(new MicrophoneDevice { DeviceId = deviceId, FriendlyName = friendlyName }); // 释放COM资源,避免内存泄漏 Marshal.ReleaseComObject(propVariant.pwszVal); Marshal.ReleaseComObject(propStore); Marshal.ReleaseComObject(device); } // 释放剩余COM资源 Marshal.ReleaseComObject(deviceCollection); Marshal.ReleaseComObject(enumerator); return microphones; } }
步骤3:绑定到WPF的ComboBox
在WPF窗口的代码后台或ViewModel中获取麦克风列表并绑定到ComboBox:
XAML部分:
<Window x:Class="WpfMicrophoneSelector.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="麦克风选择器" Height="200" Width="300"> <Grid> <ComboBox x:Name="micComboBox" Width="250" Margin="20" DisplayMemberPath="FriendlyName" SelectedValuePath="DeviceId"/> </Grid> </Window>
C#代码后台:
public MainWindow() { InitializeComponent(); // 加载麦克风列表 var microphones = MicrophoneEnumerator.GetActiveMicrophones(); micComboBox.ItemsSource = microphones; // 默认选中第一个设备(如果有的话) if (microphones.Any()) micComboBox.SelectedIndex = 0; }
后续录制的扩展思路
当用户选择麦克风后,你可以通过选中的DeviceId,继续使用Core Audio的IAudioClient接口来初始化音频捕获会话,实现无第三方库的语音录制。如果需要这部分的代码,可以再问我!
注意事项
- 该方案支持Windows Vista及以上所有版本,兼容性良好;
- 务必记得释放所有COM对象,避免内存泄漏;
- 如果设备列表需要动态更新(比如插拔麦克风),可以监听Core Audio的设备通知事件,不过这部分相对复杂,普通场景下定期刷新列表即可。
内容的提问来源于stack exchange,提问作者Jason
相关产品推荐
相关产品推荐

