Search code examples
visual-studio-2017directshowdirectsound

Console app vs Win32 app - DirectSound capture-device enumeration gives different results


I am looking for a reliable method to map a DirectShow capture device GUID to its corresponding waveID value.

I found the following project by Chris_P:

The solution works great, and it relies on an a rather obscure IKsPropertySet interface to retrieve the mapping.

Unfortunately, if I attempt the same technique from a C++/CLI library, the code fails with E_NOTIMPL (this behavior has been described on this question, - see the answer by Vladimir Hmelyoff)

So, I figured that I could write a simple console-based auxiliary app to retrieve the mappings and print them. My library could then launch this auxiliary app and parse the redirected output to obtain the mappings.

The console program runs fine, however, the GUIDs that are being passed to the enumeration callback are completely different to the ones passed by Chris_P's solution.

In fact they all share the same basic structure:

lpGuid = 0x02ad0808 {BDF35A00-B9AC-11D0-A619-00AA00A7C000}

The only variation occurs in the last digits of the GUID, where coincidentally, they match the mapped waveId value.

Another weird thing is that the capture device description is truncated to 31 characters, as if the enumeration was being performed using WaveIn APIs!

It would almost seem that some DirectSound facade is now wrapping the WaveIn API.

Any pointers on what could be happening?, Can I disable this behavior and enumerate the same GUIDS that the WIN32 app is enumerating?

Here is the code for the console application:

#include "stdafx.h"
#include <mmreg.h>
#include <initguid.h>
#include <Dsound.h>
#include <dsconf.h>


static BOOL CALLBACK DSEnumCallback(
   LPGUID  lpGuid,
   LPCTSTR  lpcstrDescription,
   LPCTSTR  lpcstrModule,
   LPVOID  lpContext
);

static BOOL GetInfoFromDSoundGUID(GUID i_sGUID, DWORD &dwWaveID);
static HRESULT DirectSoundPrivateCreate(OUT LPKSPROPERTYSET * ppKsPropertySet);

typedef WINUSERAPI HRESULT(WINAPI *LPFNDLLGETCLASSOBJECT) (const CLSID &, const IID &, void **);



BOOL GetInfoFromDSoundGUID(GUID i_sGUID, DWORD &dwWaveID) {
   LPKSPROPERTYSET         pKsPropertySet = NULL;
   HRESULT                 hr;
   BOOL                 retval = FALSE;

   PDSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_DATA psDirectSoundDeviceDescription = NULL;
   DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_DATA sDirectSoundDeviceDescription;

   memset(&sDirectSoundDeviceDescription, 0, sizeof(sDirectSoundDeviceDescription));
   hr = DirectSoundPrivateCreate(&pKsPropertySet);
   if( SUCCEEDED(hr) ) {
      ULONG ulBytesReturned = 0;
      sDirectSoundDeviceDescription.DeviceId = i_sGUID;

      // On the first call the final size is unknown so pass the size of the struct in order to receive
      // "Type" and "DataFlow" values, ulBytesReturned will be populated with bytes required for struct+strings.
      hr = pKsPropertySet->Get(DSPROPSETID_DirectSoundDevice,
                               DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION,
                               NULL,
                               0,
                               &sDirectSoundDeviceDescription,
                               sizeof(sDirectSoundDeviceDescription),
                               &ulBytesReturned
      );

      if( ulBytesReturned ) {
         // On the first call it notifies us of the required amount of memory in order to receive the strings.
         // Allocate the required memory, the strings will be pointed to the memory space directly after the struct.
         psDirectSoundDeviceDescription = (PDSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_DATA)new BYTE[ulBytesReturned];
         *psDirectSoundDeviceDescription = sDirectSoundDeviceDescription;

         hr = pKsPropertySet->Get(DSPROPSETID_DirectSoundDevice,
                                  DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION,
                                  NULL,
                                  0,
                                  psDirectSoundDeviceDescription,
                                  ulBytesReturned,
                                  &ulBytesReturned
         );

         dwWaveID = psDirectSoundDeviceDescription->WaveDeviceId;
         delete[] psDirectSoundDeviceDescription;
         retval = TRUE;
      }

      pKsPropertySet->Release();
   }

   return retval;
}



HRESULT DirectSoundPrivateCreate(OUT LPKSPROPERTYSET * ppKsPropertySet) {
   HMODULE                 hLibDsound = NULL;
   LPFNDLLGETCLASSOBJECT   pfnDllGetClassObject = NULL;
   LPCLASSFACTORY          pClassFactory = NULL;
   LPKSPROPERTYSET         pKsPropertySet = NULL;
   HRESULT                 hr = DS_OK;

   // Load dsound.dll 
   hLibDsound = LoadLibrary(TEXT("dsound.dll"));

   if( !hLibDsound ) {
      hr = DSERR_GENERIC;
   }

   // Find DllGetClassObject 
   if( SUCCEEDED(hr) ) {
      pfnDllGetClassObject =
         (LPFNDLLGETCLASSOBJECT)GetProcAddress(hLibDsound, "DllGetClassObject");


      if( !pfnDllGetClassObject ) {
         hr = DSERR_GENERIC;
      }
   }

   // Create a class factory object     
   if( SUCCEEDED(hr) ) {
      hr = pfnDllGetClassObject(CLSID_DirectSoundPrivate, IID_IClassFactory, (LPVOID *)&pClassFactory);
   }

   // Create the DirectSoundPrivate object and query for an IKsPropertySet 
   // interface 
   if( SUCCEEDED(hr) ) {
      hr = pClassFactory->CreateInstance(NULL, IID_IKsPropertySet, (LPVOID *)&pKsPropertySet);
   }

   // Release the class factory 
   if( pClassFactory ) {
      pClassFactory->Release();
   }

   // Handle final success or failure 
   if( SUCCEEDED(hr) ) {
      *ppKsPropertySet = pKsPropertySet;
   } else if( pKsPropertySet ) {
      pKsPropertySet->Release();
   }

   FreeLibrary(hLibDsound);

   return hr;
}




BOOL CALLBACK DSEnumCallback(
   LPGUID  lpGuid,
   LPCTSTR  lpcstrDescription,
   LPCTSTR  lpcstrModule,
   LPVOID  lpContext
) {



   LPWSTR psz = NULL;
   StringFromCLSID(*lpGuid, &psz);
   DWORD WaveID = 0xFFFFFFFF;

   if( lpGuid ) {
      GUID i_guid = *lpGuid;
      GetInfoFromDSoundGUID(i_guid, WaveID);
   }

   if( WaveID != 0xFFFFFFFF ) 
      wprintf(_T("%d %s\r\n"), WaveID, psz);

   if( psz ) {
      CoTaskMemFree(psz);
   }

   return TRUE;
}


int main()
{
    DirectSoundCaptureEnumerate(DSEnumCallback, NULL);
    Sleep(10000);
    return 0;
}

Solution

  • It turns out I was not initializing COM.

    I added the following snippet at the beginning of my main() procedure and the program retrieved the expected GUIDs:

       HRESULT hr = NULL;
       hr = CoInitialize(NULL);
       if( FAILED(hr) ) {
          printf("Failed to initialize COM");
          return -1;
       }
    

    So I guess that if COM is not initialized, the DirectSound engine falls back to the WaveIn API (creating a DirectShow facade around it).