none
XAudio2 - How to get device ID for mastering voice? RRS feed

  • Question

  • I have a simple problem, for Xaudio2 running on Win7/8

    How do you get the necessary szDeviceId parameter required by IXAudio2::CreateMasteringVoice() that allows you to select which device the audio is sent to?

    The documentation says default == null, but it doesn't explain how to obtain a valid identifier. I tried using the result from IMMDevice::GetID() without success (HRESULT was E_FAIL when called CreateMasterVoice()... )... I'm sortof clueless as to what to try next.


    Thanks for any insights,

    Rich

    Thursday, March 7, 2013 7:24 AM

Answers

All replies

  • XAudio2 in Windows 8 does not support IMMDevice IDs.

    Use XInputAudioGetDeviceIds to get the device IDs to pass to IXAudio2::CreateMasteringVoice.

    You can associate this with a WASAPI endpoint by using XInputGetDSoundAudioDeviceGuids to get the DirectSound GUIDs associated with the controller. These should correspond to the PKEY_AudioEndpoint_GUID device property on the audio device.


    Matthew van Eerde

    Thursday, March 7, 2013 4:51 PM
    Moderator
  • Never mind, I see that XInputAudioGetDeviceIds replaces XInputGetDSoundAudioDeviceGuids. Looking into this further.

    Matthew van Eerde

    Thursday, March 7, 2013 5:54 PM
    Moderator
  • OK, figured it out.

    As of Windows 8, pass a software device ID (this is not the same as an IMMDevice ID.)

    For example a software device ID looks something like this:

    \\?\SWD#MMDEVAPI#{0.0.0.00000000}.{16f47bbc-7538-44b9-92ef-73906c7b5788}#{e6327cad-dcec-4949-ae8a-991e976a79d2}

    You get a software device ID by using Windows::Devices::Enumeration::DeviceInformation::FindAllAsync with Windows::Media::Devices::MediaDevice::GetAudioRenderSelector(), something like this:

        auto completion = concurrency::create_task(
            Windows::Devices::Enumeration::DeviceInformation::FindAllAsync(
                Windows::Media::Devices::MediaDevice::GetAudioRenderSelector()
            )
        ).then([=]( Windows::Devices::Enumeration::DeviceInformationCollection ^devices ) {
            LOG(L"Audio render devices");
           
            for (UINT d = 0; d < devices->Size; d++) {
                auto device = devices->GetAt( d );
                LOG(
                    L"%u: %s\n"
                    L"    Id: %s",
                    d + 1,
                    device->Name->Data(),
                    device->Id->Data()
                );
               
                IXAudio2MasteringVoice *pXAudio2MasteringVoice = nullptr;
                HRESULT hr = pXAudio2->CreateMasteringVoice(
                    &pXAudio2MasteringVoice,
                    XAUDIO2_DEFAULT_CHANNELS,
                    XAUDIO2_DEFAULT_SAMPLERATE,
                    0,
                    device->Id->Data()
                );


    Matthew van Eerde

    Thursday, March 7, 2013 6:37 PM
    Moderator
  • You tie software devices back to IMMDevices (if you need to) the same way; using PKEY_AudioEndpoint_GUID, which is a queriable property on both IMMDevices and software devices.

    Matthew van Eerde

    Thursday, March 7, 2013 9:32 PM
    Moderator
  • Thanks Mathew for the nice direction.  I'm trying to get your code working on win8 desktop, but having some difficulties because it is using CLI and managed objects (our code base is currently just C++).  Is it possible to get the device ID in windows 8 without CLI?  I understand we may support Win8 RT in the near future, but for now we are focusing on writing the audio backend for win7+win8.

    If not, do you have any tips for making the above WinRT calls work with our existing codebase?

    I believe I found that for windows 7/vista, you get the device ID with IXAudio2::GetDeviceDetails(), although that is clearly not in the win8 version of xaudio.

    Thanks again for the help,

    Rich

    Thursday, March 7, 2013 10:10 PM
  • You can consume some (but not all) WinRT APIs in a desktop app.

    The WinRT APIs in question here are consumable from a desktop app.

    Here's my complete program:

    // main.cpp
    
    #include <initguid.h>
    #include <windows.h>
    #include <collection.h>
    #include <roapi.h>
    #include <ppltasks.h>
    #include <stdio.h>
    #include <strsafe.h>
    #include <propkey.h>
    #include <mmdeviceapi.h>
    #include <devpkey.h>
    #include <propkey.h>
    #include <functiondiscoverykeys_devpkey.h>
    #include <xaudio2.h>
    
    using namespace Platform;
    using namespace Windows::Devices::Enumeration;
    using namespace Windows::Foundation::Collections;
    using namespace Windows::Media::Devices;
    
    #define LOG(format, ...) wprintf(format L"\n", __VA_ARGS__)
    
    #define CASE_RETURN(x) case x: return L ## #x
    
    class RoUninitializeOnExit {
    public:
        RoUninitializeOnExit() {}
        ~RoUninitializeOnExit() { RoUninitialize(); }
    };
    
    class CoTaskMemFreeOnExit {
    public:
        CoTaskMemFreeOnExit(PVOID p) : m_p(p) {}
        ~CoTaskMemFreeOnExit() { CoTaskMemFree(m_p); }
    private:
        PVOID m_p;
    };
    
    class ReleaseOnExit {
    public:
        ReleaseOnExit(IUnknown *p) : m_p(p) {}
        ~ReleaseOnExit() { m_p->Release(); }
    private:
        IUnknown *m_p;
    };
    
    class DestroyVoiceOnExit {
    public:
        DestroyVoiceOnExit(IXAudio2Voice *p) : m_p(p) {}
        ~DestroyVoiceOnExit() { m_p->DestroyVoice(); }
    private:
        IXAudio2Voice *m_p;
    };
    
    LPCWSTR StringFromTaskGroupStatus( concurrency::task_group_status s ) {
        switch (s) {
            CASE_RETURN(concurrency::task_group_status::not_complete);
            CASE_RETURN(concurrency::task_group_status::completed);
            CASE_RETURN(concurrency::task_group_status::canceled);
            default: return L"Unrecognized";
        }
    }
    
    [Platform::MTAThread]
    int _cdecl main( Platform::Array< Platform::String^ >^ args ) {
    
        HRESULT hr = RoInitialize(RO_INIT_MULTITHREADED);
        if (FAILED(hr)) {
            LOG(L"RoInitialize failed: hr = 0x%08x", hr);
            return -__LINE__;
        }
        RoUninitializeOnExit ru;
        
        IXAudio2 *pXAudio2 = nullptr;
        hr = XAudio2Create(&pXAudio2, 0);
        if (FAILED(hr)) {
            LOG(L"XAudio2Create failed: hr = 0x%08x", hr);
            return -__LINE__;
        }
        ReleaseOnExit releaseXAudio2(pXAudio2);
    
        auto completion = concurrency::create_task(
            Windows::Devices::Enumeration::DeviceInformation::FindAllAsync(
                Windows::Media::Devices::MediaDevice::GetAudioRenderSelector()
            )
        ).then([=]( Windows::Devices::Enumeration::DeviceInformationCollection ^devices ) {
            LOG(L"Audio render devices");
            
            for (UINT d = 0; d < devices->Size; d++) {
                auto device = devices->GetAt( d );
                LOG(
                    L"%u: %s\n"
                    L"    Id: %s",
                    d + 1,
                    device->Name->Data(),
                    device->Id->Data()
                );
                
                IXAudio2MasteringVoice *pXAudio2MasteringVoice = nullptr;
                HRESULT hr = pXAudio2->CreateMasteringVoice(
                    &pXAudio2MasteringVoice,
                    XAUDIO2_DEFAULT_CHANNELS,
                    XAUDIO2_DEFAULT_SAMPLERATE,
                    0,
                    device->Id->Data()
                );
                
                if (FAILED(hr)) {
                    LOG(L"IXAudio2::CreateMasteringVoice failed: hr = 0x%08x", hr);
                    continue;
                }
                
                DestroyVoiceOnExit destroyVoice(pXAudio2MasteringVoice);
            } // for each device
        }).wait();
        
        LOG(L"Completion: %u (%s)", completion, StringFromTaskGroupStatus(completion));
            
        return 0;
    }
    


    Matthew van Eerde

    Thursday, March 7, 2013 10:45 PM
    Moderator
  • If you want to get the SWD device ID without opening the WinRT can of worms, you could use SetupAPI to look for DEVINTERFACE_AUDIO_RENDER and DEVINTERFACE_AUDIO_CAPTURE devices.

    Matthew van Eerde

    Thursday, March 7, 2013 10:49 PM
    Moderator
  • Good to know there is an alternative route to getting this device ID for now (although we'll probabably be supported WinRT in the future, just not yet).  


    I had a go at querying DEVINTERFACE_AUDIO_RENDER, which I must say is tough because I cannot find any documentation on its properties.  Do you know which property corresponds to 'SWD device ID'?  I spent a while scouring the net but couldn't come up with much more than examples of how to query devices other than audio.  Anyways, the attempt I made with SeteupApi is below, which just prints SPDRP_FRIENDLYNAME (useful) and SPDRP_HARDWAREID (not so useful, prints 'MMDEVAPI\AudioEndpoints' for everything).  I feel like I'm close, though.

    void AudioDevice::printAllWin8()
    {

    ::HDEVINFO devInfoSet;
    ::DWORD devCount = 0;
    ::SP_DEVINFO_DATA devInfo;
    ::SP_DEVICE_INTERFACE_DATA devInterface;

    DWORD size = 0;

    devInfoSet = SetupDiGetClassDevs( &DEVINTERFACE_AUDIO_RENDER, 0, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT ); // DIGCF_PRESENT 
    if( devInfoSet == INVALID_HANDLE_VALUE ) {
    LOG_V << "INVALID_HANDLE_VALUE" << endl;
    return;
    }

    devInfo.cbSize = sizeof(SP_DEVINFO_DATA);
    devInterface.cbSize = sizeof( ::SP_DEVICE_INTERFACE_DATA );

    DWORD deviceIndex = 0;

    while ( true ) {

    if( ! SetupDiEnumDeviceInterfaces( devInfoSet, 0, &DEVINTERFACE_AUDIO_RENDER, deviceIndex, &devInterface ) ) {
    DWORD error = GetLastError();
    if( error == ERROR_NO_MORE_ITEMS ) {
    LOG_V << "out of items." << endl;
    } else {
    LOG_V << "get device returned false. error: " << error << endl;
    }
    break;
    }
    deviceIndex++;


    // See how large a buffer we require for the device interface details (ignore error, it should be returning ERROR_INSUFFICIENT_BUFFER
    SetupDiGetDeviceInterfaceDetail( devInfoSet, &devInterface, 0, 0, &size, 0 );

    shared_ptr<::SP_DEVICE_INTERFACE_DETAIL_DATA> interfaceDetail( (::SP_DEVICE_INTERFACE_DETAIL_DATA*)calloc( 1, size ), free );
    if( interfaceDetail ) {
    interfaceDetail->cbSize = sizeof( ::SP_DEVICE_INTERFACE_DETAIL_DATA );
    devInfo.cbSize = sizeof( ::SP_DEVINFO_DATA );
    if( ! ::SetupDiGetDeviceInterfaceDetail( devInfoSet, &devInterface, interfaceDetail.get(), size, 0, &devInfo ) ) {
    continue;
    DWORD error = GetLastError();
    LOG_V << "get device returned false. error: " << error << endl;
    }

    char friendlyName[2048];
    size = sizeof( friendlyName );
    friendlyName[0] = 0;
    ::DWORD propertyDataType;
    if( ! SetupDiGetDeviceRegistryPropertyA( devInfoSet, &devInfo, SPDRP_FRIENDLYNAME, &propertyDataType, (LPBYTE)friendlyName, size, 0 ) ) {
    DWORD error = GetLastError();
    LOG_V << "get device returned false. error: " << error << endl;
    continue;
    }
    LOG_V << "name: " << friendlyName << endl;

    char hardwareId[2048];
    size = sizeof( hardwareId );
    hardwareId[0] = 0;
    if( ! SetupDiGetDeviceRegistryPropertyA( devInfoSet, &devInfo, SPDRP_HARDWAREID, &propertyDataType, (LPBYTE)hardwareId, size, 0 ) ) {
    DWORD error = GetLastError();
    LOG_V << "get device returned false. error: " << error << endl;
    continue;
    }
    LOG_V << "hardwareId: " << hardwareId << endl;
    }

    }

    if (devInfoSet) {
    SetupDiDestroyDeviceInfoList( devInfoSet );
    }
    }




    Once again, I really appreciate all of your thorough help in solving these tasks.

    cheers,
    Rich
    Friday, March 8, 2013 4:03 AM
  • You want the device instance path. I believe the relevant SetupAPI function is SetupDiGetDeviceInstanceId.

    Matthew van Eerde

    Friday, March 8, 2013 4:33 PM
    Moderator
  • Dang.  I can get the SWD instance ID's from SetupDiGetDeviceInstanceId, which look very similar to the example you posted a couple days ago, but it's not working with Xaudio2::CreateMasteringVoice().  I've made a standalone test app, would you mind looking at it? It just seems like this should work...

    #include<windows.h>
    #include <setupapi.h>
    #pragma comment(lib, "setupapi.lib")
    
    #include <initguid.h> // must be included before mmdeviceapi.h for the pkey defines to be properly instantiated. Both must be first included from a translation unit.
    #include <mmdeviceapi.h>
    #include <Functiondiscoverykeys_devpkey.h>
    
    #include <xaudio2.h>
    #pragma comment(lib, "xaudio2.lib")
    
    #include <tchar.h>
    #include <memory>
    #include <cassert>
    #include <iostream>
    
    using namespace std;
    
    struct ComReleaser {
    	template <typename T>
    	void operator()(T* ptr) {
    		ptr->Release();
    	}
    };
    
    struct VoiceDeleter {
    	template <typename T>
    	void operator()(T *voice) {
    		voice->DestroyVoice();
    	}
    };
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	::HDEVINFO devInfoSet;
    	::DWORD devCount = 0;
    	::SP_DEVINFO_DATA devInfo;
    	::SP_DEVICE_INTERFACE_DATA devInterface;
    
    	DWORD size = 0;
    
    	IXAudio2* pXAudio2 = NULL;
    	HRESULT hr = XAudio2Create( &pXAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR );
    	if (FAILED(hr)) {
    		throw exception( "XAudio2Create failed." );
    	}
    	auto xaudioPtr = unique_ptr<IXAudio2, ComReleaser>( pXAudio2 );
    
    	devInfoSet = SetupDiGetClassDevs( &DEVINTERFACE_AUDIO_RENDER, 0, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT ); // DIGCF_PRESENT 
    	if( devInfoSet == INVALID_HANDLE_VALUE ) {
    		throw exception( "INVALID_HANDLE_VALUE" );
    	}
    
    	devInfo.cbSize = sizeof(SP_DEVINFO_DATA);
    	devInterface.cbSize = sizeof( ::SP_DEVICE_INTERFACE_DATA );
    	DWORD deviceIndex = 0;
    
    	while ( true ) {
    
    		if( ! SetupDiEnumDeviceInterfaces( devInfoSet, 0, &DEVINTERFACE_AUDIO_RENDER, deviceIndex, &devInterface ) ) {
    			DWORD error = GetLastError();
    			if( error == ERROR_NO_MORE_ITEMS ) {
    				// ok, we're done.
    			} else {
    				throw exception( "SetupDiEnumDeviceInterfaces failed" );
    			}
    			break;
    		}
    		deviceIndex++;
    
    		// See how large a buffer we require for the device interface details (ignore error, it should be returning ERROR_INSUFFICIENT_BUFFER
    		SetupDiGetDeviceInterfaceDetail( devInfoSet, &devInterface, 0, 0, &size, 0 );
    
    		shared_ptr<::SP_DEVICE_INTERFACE_DETAIL_DATA> interfaceDetail( (::SP_DEVICE_INTERFACE_DETAIL_DATA*)calloc( 1, size ), free );
    		if( interfaceDetail ) {
    			interfaceDetail->cbSize = sizeof( ::SP_DEVICE_INTERFACE_DETAIL_DATA );
    			devInfo.cbSize = sizeof( ::SP_DEVINFO_DATA );
    			if( ! ::SetupDiGetDeviceInterfaceDetail( devInfoSet, &devInterface, interfaceDetail.get(), size, 0, &devInfo ) ) {
    				continue;
    				DWORD error = GetLastError();
    				throw exception( "SetupDiGetDeviceInterfaceDetail failed" );
    			}
    
    			char friendlyName[2048];
    			size = sizeof( friendlyName );
    			friendlyName[0] = 0;
    			::DWORD propertyDataType;
    			if( ! SetupDiGetDeviceRegistryPropertyA( devInfoSet, &devInfo, SPDRP_FRIENDLYNAME, &propertyDataType, (LPBYTE)friendlyName, size, 0 ) ) {
    				DWORD error = GetLastError();
    				throw exception( "SetupDiGetDeviceRegistryPropertyA failed" );
    				//continue;
    			}
    
    			wchar_t deviceId[2048];
    			size = sizeof( deviceId );
    			deviceId[0] = 0;
    			DWORD requiredSize;
    			if( ! SetupDiGetDeviceInstanceId( devInfoSet, &devInfo, deviceId, size, &requiredSize ) ) {
    				DWORD error = GetLastError();
    				throw exception( "SetupDiGetDeviceInstanceId failed" );
    				//continue;
    			}
    			assert( requiredSize <= 2048 );
    
    			IXAudio2MasteringVoice *pXAudio2MasteringVoice = nullptr;
    			HRESULT hr = pXAudio2->CreateMasteringVoice( &pXAudio2MasteringVoice, XAUDIO2_DEFAULT_CHANNELS, XAUDIO2_DEFAULT_SAMPLERATE, 0, deviceId	);
    			assert( ! FAILED( hr ) ); // E_FAIL
    
    			auto masterVoicePtr = unique_ptr<IXAudio2MasteringVoice, VoiceDeleter>( pXAudio2MasteringVoice );
    		}
    	}
    
    	if (devInfoSet) {
    		SetupDiDestroyDeviceInfoList( devInfoSet );
    	}
    
    	return 0;
    }
    


    Friday, March 8, 2013 8:53 PM
  • What is the value of deviceId that you're passing to pXAudio2->CreateMasteringVoice, and what is the HRESULT being returned?

    Matthew van Eerde

    Friday, March 8, 2013 10:02 PM
    Moderator
  • > size = sizeof( deviceId );

    By the way, this is a bug. This sets size to 2048 * sizeof(WCHAR), when it should be 2048. But that isn't causing the problem.


    Matthew van Eerde

    Friday, March 8, 2013 10:21 PM
    Moderator
  • Here's an ouput when logging the friendly name and device ID for the two audio output devices on this machine:

    name: Acer T232HL (Intel(R) Display Audio)
    deviceId: SWD\MMDEVAPI\{0.0.0.00000000}.{70A47EB9-0523-41D9-98DF-D32353C9CCA7}
    name: Speakers (Realtek High Definition Audio)
    deviceId: SWD\MMDEVAPI\{0.0.0.00000000}.{EE4CE973-C5E0-4E16-9CC4-C05211E703E8}

    The HRESULT when trying either one is E_FAIL.

    Saturday, March 9, 2013 12:21 AM
  • OK, I'm wrong, you don't want the Instance ID. What do you have for interfaceDetail.DevicePath?

    You want something that looks like this:

    \\?\SWD#MMDEVAPI#{0.0.0.00000000}.{16f47bbc-7538-44b9-92ef-73906c7b5788}#{e6327cad-dcec-4949-ae8a-991e976a79d2}

    Not this:

    SWD\MMDEVAPI\{0.0.0.00000000}.{70A47EB9-0523-41D9-98DF-D32353C9CCA7}


    Matthew van Eerde

    Saturday, March 9, 2013 12:48 AM
    Moderator

  • Sorry for the late reply, don't think I noticed your response since it was so quick!

    interfaceDetails->DevicePath is exactly what I need, just finished implementing this and now I can programatically choose which audio output device to use for the master voice with XAudio2.8 - one of the last missing components of the system we're building.  Thanks again for helping me wade through this.

    cheers!

    Rich

    Tuesday, April 16, 2013 4:04 AM