16

I have been using waveInGetDevCaps to get the name of waveIn devices, but the WAVEINCAPS structure only supports 31 characters plus a null, meaning that on my computer, the device names I get back are truncated:

Microphone / Line In (SigmaTel 
Microphone Array (SigmaTel High, 

I am sure that there must be a way of getting the full device name, but does anyone know what that is?

Mark Heath
  • 48,273
  • 29
  • 137
  • 194

7 Answers7

12

Yes, there's a workaround. I've solved this problem several times in shipping code.

Enumerate audio capture devices with DirectSoundCapture. The API is DirectSoundCaptureEnumerate. It will return you the full length name of the devices.

Of course, you're probably thinking "That's great, but the rest of my code is setup to use the Wave API, not DirectSound. I don't want to switch it all over. So how can I map the GUID IDs returned by DirectSoundCaptureEnumerate to the integer IDs used by the WaveIn API?"

The solution is to CoCreateInstance for the DirectSoundPrivate object (or call GetClassObject directly from dsound.dll) to get a pointer to an IKsPropertySet interface. From this interface, you can obtain the DSound GUID to Wave ID mapping. For more details see this web page:

http://msdn.microsoft.com/en-us/library/bb206182(VS.85).aspx

You want to use the DSPROPERTY_DIRECTSOUNDDEVICE_WAVEDEVICEMAPPING as described on the web page listed above.

selbie
  • 100,020
  • 15
  • 103
  • 173
  • thanks, this is really helpful (although shame it has to be so convoluted) – Mark Heath Sep 20 '09 at 12:48
  • 2
    that link seems to be broken. do you have another link or at least the title of the article on msdn? – Padu Merloti Dec 17 '09 at 22:55
  • 4
    Another method of getting the GUID is to call waveOutGetDevCaps with a WAVEOUTCAPS2-structure, rather than the regular WAVEOUTCAPS-structure. http://msdn.microsoft.com/en-us/library/windows/hardware/ff536382%28v=vs.85%29.aspx – leiflundgren Mar 22 '12 at 10:39
  • @leiflundgren: method isn't good, the GUIDs returned from DSound aren't present in results from waveOutGetDevCaps – Pavel P Aug 01 '12 at 20:13
  • 1
    hi selbie ..could you please show how this could be done exactly? I don't have any knowledge on how to use the cocreateinstance and iam not sure if this is the best way to enumerate sound devices now (after 4 years)... thanks in advance – lebhero Oct 14 '13 at 13:54
8

Improved/full C# WPF code based on @Andrea Bertucelli answer

using NAudio.CoreAudioApi;
using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.Windows;

namespace WpfApp2
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            foreach (KeyValuePair<string, MMDevice> device in GetInputAudioDevices())
            {
                Console.WriteLine("Name: {0}, State: {1}", device.Key, device.Value.State);
            }
        }

        public Dictionary<string, MMDevice> GetInputAudioDevices()
        {
            Dictionary<string, MMDevice> retVal = new Dictionary<string, MMDevice>();
            MMDeviceEnumerator enumerator = new MMDeviceEnumerator();
            int waveInDevices = WaveIn.DeviceCount;
            for (int waveInDevice = 0; waveInDevice < waveInDevices; waveInDevice++)
            {
                WaveInCapabilities deviceInfo = WaveIn.GetCapabilities(waveInDevice);
                foreach (MMDevice device in enumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.All))
                {
                    if (device.FriendlyName.StartsWith(deviceInfo.ProductName))
                    {
                        retVal.Add(device.FriendlyName, device);
                        break;
                    }
                }
            }

            return retVal;
        }
    }
}
Zunair
  • 1,085
  • 1
  • 13
  • 21
5

I completed the names of waveIn devices, exploring the names returned from MMDeviceEnumerator. For each waveIn device, when the name incompleted is part of full name of one of EnumerateAudioEndPoints, I used this full name for populate combobox in the same order of waveIn devices.

VisualBasic .NET:

   Dim wain = New WaveIn()
    Dim DeviceInfoI As WaveInCapabilities
    Dim nomedevice As String
    For de = 0 To wain.DeviceCount - 1
        DeviceInfoI = wain.GetCapabilities(de)
        nomedevice = DeviceInfoI.ProductName
        For deg = 0 To devices.Count - 1
            If InStr(devices.Item(deg).FriendlyName, nomedevice) Then
                nomedevice = devices.Item(deg).FriendlyName
                Exit For
            End If
        Next
        cmbMessaggiVocaliMIC.Items.Add(nomedevice)
    Next
    cmbMessaggiVocaliMIC.SelectedIndex = 0
    waveIn.DeviceNumber = cmbMessaggiVocaliMIC.SelectedIndex
4

There's a way involving the registry that's simpler than using DirectSound. If you use the WAVEINCAPS2 structure, it has a name GUID that references a key under HKLM\System\CurrentControlSet\Control\MediaCategories. If the key doesn't exist, then just use the name in the structure. This is documented on http://msdn.microsoft.com/en-us/library/windows/hardware/ff536382%28v=vs.85%29.aspx. Here's an example:

public static ICollection<AudioInputDevice> GetDevices()
{
  RegistryKey namesKey = Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Control\MediaCategories");

  List<AudioInputDevice> devices = new List<AudioInputDevice>();
  for(int i=0, deviceCount=waveInGetNumDevs(); i<deviceCount; i++)
  {
    WAVEINCAPS2 caps;
    if(waveInGetDevCaps(new IntPtr(i), out caps, Marshal.SizeOf(typeof(WAVEINCAPS2))) == 0 && caps.Formats != 0)
    {
      string name = null;
      if(namesKey != null)
      {
        RegistryKey nameKey = namesKey.OpenSubKey(caps.NameGuid.ToString("B"));
        if(nameKey != null) name = nameKey.GetValue("Name") as string;
      }
      devices.Add(new AudioInputDevice(name ?? caps.Name, caps.ProductGuid));
    }
  }
  return devices;
}

struct WAVEINCAPS2
{
  public short ManufacturerId, ProductId;
  public uint DriverVersion;
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)] public string Name;
  public uint Formats;
  public short Channels;
  ushort Reserved;
  public Guid ManufacturerGuid, ProductGuid, NameGuid;
}

[DllImport("winmm.dll")]
static extern int waveInGetDevCaps(IntPtr deviceId, out WAVEINCAPS2 caps, int capsSize);

[DllImport("winmm.dll", ExactSpelling=true)]
static extern int waveInGetNumDevs();
Adam M.
  • 189
  • 5
  • 4
    thanks for drawing this to my attention. It looks promising, but sadly my tests (on Win 7) have shown that although the WAVEINCAPS2 structure has Guids present, they are not contained in the registry. Looks like audio device driver manufacturers are simply not bothering to modify these values: http://www.tech-archive.net/Archive/Development/microsoft.public.win32.programmer.mmedia/2006-08/msg00102.html – Mark Heath Jul 04 '13 at 13:28
  • Not all GUIDs will exist in the registry, in which case you're supposed to use the name within the structure. (Usually the registry is only used if the name is longer than the 31 characters available in the structure.) If the name is truncated to 31 characters but the key is not in the registry, then perhaps the manufacturers were lazy. :-( – Adam M. Oct 28 '13 at 22:47
  • Thanks for this info...I was having problems but I was able to pull it off with the code you posted. I did it in one call as follows: fullname = nameskey.OpenSubKey(input.NameGuid.ToString("B")).GetValue("Name").ToString(); – Anthony Tristan Jun 25 '18 at 18:49
3

Looks like DirectSoundPrivate has some issues. I am trying to access it from an empty project and it works fine. However, when I try to access it from COM DLL or from a DLL thread it returns E_NOTIMPL error from IKsPropertySet::Get.

But I figured out another trick. It seems DirectSound enumerates capture and render devices in wave id order (excluding first default).

We still have to interact with old Wave API and it still lacks a proper way to do that. DirectShow provides audio input devices based on WaveIn and I need to get corresponding a WASAPI id and vice-versa.

Nik Reiman
  • 39,067
  • 29
  • 104
  • 160
0

I have found another way using the registry to find audio devices' full name, both Input and Output.

Works on Windows 7 and Windows 10.

This method tries Adam M.'s approach in the first place. His method didn't work for me, but just in case it works for you I added as preferred method.

Delphi:

procedure TForm_Config.FormCreate(Sender: TObject);
type
  tagWAVEOUTCAPS2A = packed record
    wMid: WORD;
    wPid: WORD;
    vDriverVersion: MMVERSION;
    szPname: array[0..MAXPNAMELEN-1] of AnsiChar;
    dwFormats: DWORD;
    wChannels: WORD;
    wReserved1: WORD;
    dwSupport: DWORD;
    ManufacturerGuid: System.TGUID;
    ProductGuid: System.TGUID;
    NameGuid: System.TGUID;
  end;
var
  i,outdevs: Integer;
  woCaps: tagWAVEOUTCAPS2A;
  RegistryService: TRegistry;
  iClasses, iSubClasses, iNames: Integer;
  audioDeviceClasses, audioDeviceSubClasses, audioDeviceNames: TStringList;
  initialDeviceName, partialDeviceName, fullDeviceName: string;
begin
  audioDeviceClasses := TStringList.Create;
  audioDeviceSubClasses := TStringList.Create;
  audioDeviceNames := TStringList.Create;
  try
    RegistryService := TRegistry.Create;
    try
      RegistryService.RootKey := HKEY_LOCAL_MACHINE;
      if RegistryService.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Enum\HDAUDIO\') then begin
        RegistryService.GetKeyNames(audioDeviceClasses);
        RegistryService.CloseKey();
        for iClasses := 0 to audioDeviceClasses.Count - 1 do begin
          if RegistryService.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Enum\HDAUDIO\'+audioDeviceClasses[iClasses]) then begin
            RegistryService.GetKeyNames(audioDeviceSubClasses);
            RegistryService.CloseKey();
            for iSubClasses := 0 to audioDeviceSubClasses.Count - 1 do begin
              if RegistryService.OpenKeyReadOnly('\SYSTEM\CurrentControlSet\Enum\HDAUDIO\'+audioDeviceClasses[iClasses]+'\'+audioDeviceSubClasses[iSubClasses]) then begin
                if RegistryService.ValueExists('DeviceDesc') then begin
                  fullDeviceName := Trim(RegistryService.ReadString('DeviceDesc'));
                  if AnsiPos(';',fullDeviceName) > 0 then begin
                    fullDeviceName := Trim(AnsiMidStr(fullDeviceName, AnsiPos(';',fullDeviceName)+1, Length(fullDeviceName)));
                  end;
                  audioDeviceNames.Add(fullDeviceName);
                end;
                RegistryService.CloseKey();
              end;
            end;
          end;
        end;
      end;
    finally
      FreeAndNil(RegistryService);
    end;
    
    // WaveOutDevComboBox is a selection box (combo) placed in the form and will receive the list of output audio devices
    WaveOutDevComboBox.Clear;

    try
      outdevs := waveOutGetNumDevs;
      for i := 0 to outdevs - 1 do begin
        ZeroMemory(@woCaps, sizeof(woCaps));
        if waveOutGetDevCaps(i, @woCaps, sizeof(woCaps)) = MMSYSERR_NOERROR then begin
          RegistryService := TRegistry.Create;
          try
            RegistryService.RootKey := HKEY_LOCAL_MACHINE;
            if RegistryService.OpenKeyReadOnly('\System\CurrentControlSet\Control\MediaCategories\' + GUIDToString(woCaps.NameGuid)) then begin
              WaveOutDevComboBox.Items.Add(RegistryService.ReadString('Name'));
              RegistryService.CloseKey();
            end
            else begin
              initialDeviceName := '';
              partialDeviceName := Trim(woCaps.szPname);
              if AnsiPos('(',partialDeviceName) > 0 then begin
                initialDeviceName := Trim(AnsiLeftStr(partialDeviceName,AnsiPos('(',partialDeviceName)-1));
                partialDeviceName := Trim(AnsiMidStr(partialDeviceName,AnsiPos('(',partialDeviceName)+1,Length(partialDeviceName)));
                if AnsiPos(')',partialDeviceName) > 0 then begin
                  partialDeviceName := Trim(AnsiLeftStr(partialDeviceName,AnsiPos(')',partialDeviceName)-1));
                end;
              end;
              for iNames := 0 to audioDeviceNames.Count - 1 do begin
                fullDeviceName := audioDeviceNames[iNames];
                if AnsiStartsText(partialDeviceName,fullDeviceName) then begin
                  break;
                end
                else begin
                  fullDeviceName := partialDeviceName;
                end;
              end;
              WaveOutDevComboBox.Items.Add(initialDeviceName + IfThen(initialDeviceName<>EmptyStr,' (','') + fullDeviceName + IfThen(initialDeviceName<>EmptyStr,')',''));
            end;
          finally
            FreeAndNil(RegistryService);
          end;
        end;
      end;
    except
      WaveOutDevComboBox.Enabled := False;
    end;
  finally
    FreeAndNil(audioDeviceClasses);
    FreeAndNil(audioDeviceSubClasses);
    FreeAndNil(audioDeviceNames);
  end;
end;
0

Using NAudio, i use this code to get full device name...

using NAudio.CoreAudioApi;
using NAudio.Wave;

For getting all recording devices:

//create enumerator
var enumerator = new MMDeviceEnumerator();
//cycle through all audio devices
for (int i = 0; i < WaveIn.DeviceCount; i++)
    Console.WriteLine(enumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active)[i]);
//clean up
enumerator.Dispose();

For getting all capture devices:

//create enumerator
var enumerator = new MMDeviceEnumerator();
//cyckle trough all audio devices
for (int i = 0; i < WaveOut.DeviceCount; i++)
    Console.WriteLine(enumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active)[i]);
//clean up
enumerator.Dispose();
IntegratedHen
  • 399
  • 4
  • 4
  • enumerator.EnumerateAudioEndPoints has nothing to do with WaveIn.DeviceCount or WaveOut.DeviceCount, so both for loops would return the exact same devices. Instead of using enumerator.EnumerateAudioEndPoints, use WaveIn.GetCapabilites(i) – Mark Entingh Sep 17 '22 at 21:28