前言

使用C#做音频录制时需要获取音频设备信息,比如使用ffmpeg进行录制需要先获取音频设备名称,再Windows上获取音频设备的方法有不少,其中比较简单的就是使用winmm,这是一套比较旧的api但是使用方法简单,当然有个缺陷就是音频名称不能超过32个字符,超过会被截断,当然如果作为winmm的采集或播放配套使用则不会有问题。


一、如何实现?

需要先导入winmm的api,以及定义存放音频设备信息的实体,最后通过调用api实现枚举设备功能。

1、DllImport接口

导入相关的api

(1)、方法

[DllImport("winmm.dll")]
public static extern uint waveInGetNumDevs();
[DllImport("winmm.dll")]
public static extern uint waveOutGetNumDevs();
[DllImport("winmm.dll")]
public static extern MMRESULT waveInGetDevCapsW(uint uDeviceID, out WAVEINCAPS pwic, uint cbwic);
[DllImport("winmm.dll")]
public static extern MMRESULT waveOutGetDevCapsW(uint uDeviceID, out WAVEOUTCAPS pwoc, uint cbwoc);

(2)、结构体

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct WAVEINCAPS
{
    public ushort wMid;
    public ushort wPid;
    public uint vDriverVersion;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string szPname;
    public uint dwFormats;
    public ushort wChannels;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct WAVEOUTCAPS
{
    public ushort wMid;
    public ushort wPid;
    public uint vDriverVersion;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string szPname;
    public uint dwFormats;
    public ushort wChannels;
    public WAVECAPS dwSupport;
}
public enum DWFormats
{
    WAVE_INVALIDFORMAT = 0x00000000,     /* invalid format */
    WAVE_FORMAT_1M08 = 0x00000001,    /* 11.025 kHz, Mono,   8-bit  */
    WAVE_FORMAT_1S08 = 0x00000002,    /* 11.025 kHz, Stereo, 8-bit  */
    WAVE_FORMAT_1M16 = 0x00000004,    /* 11.025 kHz, Mono,   16-bit */
    WAVE_FORMAT_1S16 = 0x00000008,    /* 11.025 kHz, Stereo, 16-bit */
    WAVE_FORMAT_2M08 = 0x00000010,    /* 22.05  kHz, Mono,   8-bit  */
    WAVE_FORMAT_2S08 = 0x00000020,    /* 22.05  kHz, Stereo, 8-bit  */
    WAVE_FORMAT_2M16 = 0x00000040,    /* 22.05  kHz, Mono,   16-bit */
    WAVE_FORMAT_2S16 = 0x00000080,    /* 22.05  kHz, Stereo, 16-bit */
    WAVE_FORMAT_4M08 = 0x00000100,    /* 44.1   kHz, Mono,   8-bit  */
    WAVE_FORMAT_4S08 = 0x00000200,    /* 44.1   kHz, Stereo, 8-bit  */
    WAVE_FORMAT_4M16 = 0x00000400,    /* 44.1   kHz, Mono,   16-bit */
    WAVE_FORMAT_4S16 = 0x00000800,    /* 44.1   kHz, Stereo, 16-bit */
    WAVE_FORMAT_44M08 = 0x00000100,     /* 44.1   kHz, Mono,   8-bit  */
    WAVE_FORMAT_44S08 = 0x00000200,     /* 44.1   kHz, Stereo, 8-bit  */
    WAVE_FORMAT_44M16 = 0x00000400,     /* 44.1   kHz, Mono,   16-bit */
    WAVE_FORMAT_44S16 = 0x00000800,     /* 44.1   kHz, Stereo, 16-bit */
    WAVE_FORMAT_48M08 = 0x00001000,     /* 48     kHz, Mono,   8-bit  */
    WAVE_FORMAT_48S08 = 0x00002000,     /* 48     kHz, Stereo, 8-bit  */
    WAVE_FORMAT_48M16 = 0x00004000,     /* 48     kHz, Mono,   16-bit */
    WAVE_FORMAT_48S16 = 0x00008000,     /* 48     kHz, Stereo, 16-bit */
    WAVE_FORMAT_96M08 = 0x00010000,     /* 96     kHz, Mono,   8-bit  */
    WAVE_FORMAT_96S08 = 0x00020000,     /* 96     kHz, Stereo, 8-bit  */
    WAVE_FORMAT_96M16 = 0x00040000,     /* 96     kHz, Mono,   16-bit */
    WAVE_FORMAT_96S16 = 0x00080000,     /* 96     kHz, Stereo, 16-bit */
}

2、定义实体

需要定义声音格式

class SampleFormat
{
    /// <summary>
    /// 声道数
    /// </summary>
    public ushort Channels { set; get; }
    /// <summary>
    /// 采样率
    /// </summary>
    public uint SampleRate { set; get; }
    /// <summary>
    /// 位深
    /// </summary>
    public ushort BitsPerSample { set; get; }
};

以及声音设备实体

class AudioDevice
{
    /// <summary>
    /// 设备Id
    /// </summary>
    public uint Id { set; get; }
    /// <summary>
    /// 设备名称
    /// </summary>
    public string Name { set; get; } = "";
    /// <summary>
    /// 声道数
    /// </summary>
    public int Channels { set; get; }
    /// <summary>
    /// 支持的格式
    /// </summary>
    public IEnumerable<SampleFormat>? SupportedFormats { set; get; }
};

3、实现枚举

音频采集设备

/// <summary>
/// 枚举声音采集设备
/// </summary>
public static IEnumerable<AudioDevice> WaveInDevices
{
    get
    {
        var deviceCount = waveInGetNumDevs();
        for (uint i = 0; i < deviceCount; i++)
        {
            var sd = GetWaveInDeviceById(i);
            if (sd != null) yield return sd;
        }
    }
}
/// <summary>
/// 通过id获取声音采集设备信息
/// </summary>
/// <param name="id">设备id</param>
/// <returns>设备对象</returns>
public static AudioDevice? GetWaveInDeviceById(uint id)
{
    WAVEINCAPS pwic;//声音设备功能信息
    var result = waveInGetDevCapsW(id, out pwic, (uint)Marshal.SizeOf<WAVEINCAPS>());
    if (result != MMRESULT.MMSYSERR_NOERROR) return null;
    AudioDevice sd = new AudioDevice();
    sd.Id = id;
    sd.Channels = pwic.wChannels;
    sd.Name = pwic.szPname;
    sd.SupportedFormats = _GetSurportFormats(pwic.dwFormats);
    return sd;
}

音频播放设备

 /// <summary>
 /// 枚举声音播放设备
 /// </summary>
 public static IEnumerable<AudioDevice> WaveOutDevices
 {
     get
     {
         var deviceCount = waveOutGetNumDevs();
         for (uint i = 0; i < deviceCount; i++)
         {
             var sd = GetWaveOutDeviceById(i);
             if (sd != null) yield return sd;
         }
     }
 }
/// <summary>
/// 通过id获取声音播放设备信息
/// </summary>
/// <param name="id">设备id</param>
/// <returns>设备对象</returns>
public static AudioDevice? GetWaveOutDeviceById(uint id)
{
    WAVEOUTCAPS pwic;//声音设备功能信息
    var result = waveOutGetDevCapsW(id, out pwic, (uint)Marshal.SizeOf<WAVEOUTCAPS>());
    if (result != MMRESULT.MMSYSERR_NOERROR) return null;
    AudioDevice sd = new AudioDevice();
    sd.Id = id;
    sd.Channels = pwic.wChannels;
    sd.Name = pwic.szPname;
    sd.SupportedFormats = _GetSurportFormats(pwic.dwFormats);
    return sd;
}


二、完整代码

class Winmm
{
    /// <summary>
    /// 枚举声音采集设备
    /// </summary>
    public static IEnumerable<AudioDevice> WaveInDevices
    {
        get
        {
            var deviceCount = waveInGetNumDevs();
            for (uint i = 0; i < deviceCount; i++)
            {
                var sd = GetWaveInDeviceById(i);
                if (sd != null) yield return sd;
            }
        }
    }

    /// <summary>
    /// 枚举声音播放设备
    /// </summary>
    public static IEnumerable<AudioDevice> WaveOutDevices
    {
        get
        {
            var deviceCount = waveOutGetNumDevs();
            for (uint i = 0; i < deviceCount; i++)
            {
                var sd = GetWaveOutDeviceById(i);
                if (sd != null) yield return sd;
            }
        }
    }
    /// <summary>
    /// 通过id获取声音采集设备信息
    /// </summary>
    /// <param name="id">设备id</param>
    /// <returns>设备对象</returns>
    public static AudioDevice? GetWaveInDeviceById(uint id)
    {
        WAVEINCAPS pwic;//声音设备功能信息
        var result = waveInGetDevCapsW(id, out pwic, (uint)Marshal.SizeOf<WAVEINCAPS>());
        if (result != MMRESULT.MMSYSERR_NOERROR) return null;
        AudioDevice sd = new AudioDevice();
        sd.Id = id;
        sd.Channels = pwic.wChannels;
        sd.Name = pwic.szPname;
        sd.SupportedFormats = _GetSurportFormats(pwic.dwFormats);
        return sd;
    }
    /// <summary>
    /// 通过id获取声音播放设备信息
    /// </summary>
    /// <param name="id">设备id</param>
    /// <returns>设备对象</returns>
    public static AudioDevice? GetWaveOutDeviceById(uint id)
    {
        WAVEOUTCAPS pwic;//声音设备功能信息
        var result = waveOutGetDevCapsW(id, out pwic, (uint)Marshal.SizeOf<WAVEOUTCAPS>());
        if (result != MMRESULT.MMSYSERR_NOERROR) return null;
        AudioDevice sd = new AudioDevice();
        sd.Id = id;
        sd.Channels = pwic.wChannels;
        sd.Name = pwic.szPname;
        sd.SupportedFormats = _GetSurportFormats(pwic.dwFormats);
        return sd;
    }
    static List<SampleFormat> _GetSurportFormats(uint foramts)
    {
        var sfs = new List<SampleFormat>();
        foreach (var j in Enum.GetValues<DWFormats>())
        {
            if ((foramts & (uint)j) != 0)
            {
                var name = Enum.GetName(j)!.Split("_").Last();
                var sp = name.Substring(0, name.Length - 3);
                var ch = name.Substring(name.Length - 3, 1);
                var bp = name.Substring(name.Length - 2, 2);
                uint isp = 0;
                switch (sp)
                {
                    case "1": isp = 11025; break;
                    case "2": isp = 22050; break;
                    case "4": isp = 44100; break;
                    case "44": isp = 44100; break;
                    case "48": isp = 48000; break;
                    case "96": isp = 96000; break;
                }
                sfs.Add(new SampleFormat() { Channels = (ushort)(ch == "S" ? 1 : 2), SampleRate = isp, BitsPerSample = ushort.Parse(bp) });
            }
        }
        return sfs;
    }
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    public struct WAVEINCAPS
    {
        public ushort wMid;
        public ushort wPid;
        public uint vDriverVersion;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string szPname;
        public uint dwFormats;
        public ushort wChannels;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    public struct WAVEOUTCAPS
    {
        public ushort wMid;
        public ushort wPid;
        public uint vDriverVersion;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string szPname;
        public uint dwFormats;
        public ushort wChannels;
        public WAVECAPS dwSupport;
    }
    public enum DWFormats
    {
        WAVE_INVALIDFORMAT = 0x00000000,     /* invalid format */
        WAVE_FORMAT_1M08 = 0x00000001,    /* 11.025 kHz, Mono,   8-bit  */
        WAVE_FORMAT_1S08 = 0x00000002,    /* 11.025 kHz, Stereo, 8-bit  */
        WAVE_FORMAT_1M16 = 0x00000004,    /* 11.025 kHz, Mono,   16-bit */
        WAVE_FORMAT_1S16 = 0x00000008,    /* 11.025 kHz, Stereo, 16-bit */
        WAVE_FORMAT_2M08 = 0x00000010,    /* 22.05  kHz, Mono,   8-bit  */
        WAVE_FORMAT_2S08 = 0x00000020,    /* 22.05  kHz, Stereo, 8-bit  */
        WAVE_FORMAT_2M16 = 0x00000040,    /* 22.05  kHz, Mono,   16-bit */
        WAVE_FORMAT_2S16 = 0x00000080,    /* 22.05  kHz, Stereo, 16-bit */
        WAVE_FORMAT_4M08 = 0x00000100,    /* 44.1   kHz, Mono,   8-bit  */
        WAVE_FORMAT_4S08 = 0x00000200,    /* 44.1   kHz, Stereo, 8-bit  */
        WAVE_FORMAT_4M16 = 0x00000400,    /* 44.1   kHz, Mono,   16-bit */
        WAVE_FORMAT_4S16 = 0x00000800,    /* 44.1   kHz, Stereo, 16-bit */
        WAVE_FORMAT_44M08 = 0x00000100,     /* 44.1   kHz, Mono,   8-bit  */
        WAVE_FORMAT_44S08 = 0x00000200,     /* 44.1   kHz, Stereo, 8-bit  */
        WAVE_FORMAT_44M16 = 0x00000400,     /* 44.1   kHz, Mono,   16-bit */
        WAVE_FORMAT_44S16 = 0x00000800,     /* 44.1   kHz, Stereo, 16-bit */
        WAVE_FORMAT_48M08 = 0x00001000,     /* 48     kHz, Mono,   8-bit  */
        WAVE_FORMAT_48S08 = 0x00002000,     /* 48     kHz, Stereo, 8-bit  */
        WAVE_FORMAT_48M16 = 0x00004000,     /* 48     kHz, Mono,   16-bit */
        WAVE_FORMAT_48S16 = 0x00008000,     /* 48     kHz, Stereo, 16-bit */
        WAVE_FORMAT_96M08 = 0x00010000,     /* 96     kHz, Mono,   8-bit  */
        WAVE_FORMAT_96S08 = 0x00020000,     /* 96     kHz, Stereo, 8-bit  */
        WAVE_FORMAT_96M16 = 0x00040000,     /* 96     kHz, Mono,   16-bit */
        WAVE_FORMAT_96S16 = 0x00080000,     /* 96     kHz, Stereo, 16-bit */
    }
    [DllImport("winmm.dll")]
    public static extern uint waveInGetNumDevs();
    [DllImport("winmm.dll")]
    public static extern uint waveOutGetNumDevs();
    [DllImport("winmm.dll")]
    public static extern MMRESULT waveInGetDevCapsW(uint uDeviceID, out WAVEINCAPS pwic, uint cbwic);
    [DllImport("winmm.dll")]
    public static extern MMRESULT waveOutGetDevCapsW(uint uDeviceID, out WAVEOUTCAPS pwoc, uint cbwoc);
}

三、使用示例

foreach (var i in Winmm.WaveInDevices)
{
    Console.WriteLine("音频采集设备" + i.Id + ":" + i.Name);
}
foreach (var i in Winmm.WaveOutDevices)
{    
    Console.WriteLine("音频播放设备"+i.Id+":" +i.Name);
}

C# 通过winmm枚举音频设备-LMLPHP


总结

以上就是今天要讲的内容,使用winnm枚举设备还是比较简单的,唯一麻烦一点的地方就是支持格式的获取,但是通过C#使用字符串处理,实现也变得很简单。总的来说,这算是一种获取音频设备信息的方法,能够满足一些使用场景的需求。

10-09 15:35