//-------------------------------------------------------------------------------------- // File: AudioEngine.cpp // // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright (c) Microsoft Corporation. All rights reserved. // // http://go.microsoft.com/fwlink/?LinkId=248929 //-------------------------------------------------------------------------------------- #include "pch.h" #include "Audio.h" #include "SoundCommon.h" #include #include using namespace DirectX; using Microsoft::WRL::ComPtr; //#define VERBOSE_TRACE namespace { struct EngineCallback : public IXAudio2EngineCallback { EngineCallback() { #if (_WIN32_WINNT >= _WIN32_WINNT_VISTA) mCriticalError.reset( CreateEventEx( nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE ) ); #else mCriticalError.reset( CreateEvent( nullptr, FALSE, FALSE, nullptr ) ); #endif if ( !mCriticalError ) { throw std::exception( "CreateEvent" ); } }; virtual ~EngineCallback() { } STDMETHOD_(void, OnProcessingPassStart) () override {} STDMETHOD_(void, OnProcessingPassEnd)() override {} STDMETHOD_(void, OnCriticalError) (THIS_ HRESULT error) { #ifndef _DEBUG UNREFERENCED_PARAMETER(error); #endif DebugTrace( "ERROR: AudioEngine encountered critical error (%08X)\n", error ); SetEvent( mCriticalError.get() ); } ScopedHandle mCriticalError; }; struct VoiceCallback : public IXAudio2VoiceCallback { VoiceCallback() { #if (_WIN32_WINNT >= _WIN32_WINNT_VISTA) mBufferEnd.reset( CreateEventEx( nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE ) ); #else mBufferEnd.reset( CreateEvent( nullptr, FALSE, FALSE, nullptr ) ); #endif if ( !mBufferEnd ) { throw std::exception( "CreateEvent" ); } } virtual ~VoiceCallback() { } STDMETHOD_(void, OnVoiceProcessingPassStart) (UINT32) override {} STDMETHOD_(void, OnVoiceProcessingPassEnd)() override {} STDMETHOD_(void, OnStreamEnd)() override {} STDMETHOD_(void, OnBufferStart)( void* ) override {} STDMETHOD_(void, OnBufferEnd)( void* context ) override { if ( context ) { auto inotify = reinterpret_cast( context ); inotify->OnBufferEnd(); SetEvent( mBufferEnd.get() ); } } STDMETHOD_(void, OnLoopEnd)( void* ) override {} STDMETHOD_(void, OnVoiceError)( void*, HRESULT ) override {} ScopedHandle mBufferEnd; }; static const XAUDIO2FX_REVERB_I3DL2_PARAMETERS gReverbPresets[] = { XAUDIO2FX_I3DL2_PRESET_DEFAULT, // Reverb_Off XAUDIO2FX_I3DL2_PRESET_DEFAULT, // Reverb_Default XAUDIO2FX_I3DL2_PRESET_GENERIC, // Reverb_Generic XAUDIO2FX_I3DL2_PRESET_FOREST, // Reverb_Forest XAUDIO2FX_I3DL2_PRESET_PADDEDCELL, // Reverb_PaddedCell XAUDIO2FX_I3DL2_PRESET_ROOM, // Reverb_Room XAUDIO2FX_I3DL2_PRESET_BATHROOM, // Reverb_Bathroom XAUDIO2FX_I3DL2_PRESET_LIVINGROOM, // Reverb_LivingRoom XAUDIO2FX_I3DL2_PRESET_STONEROOM, // Reverb_StoneRoom XAUDIO2FX_I3DL2_PRESET_AUDITORIUM, // Reverb_Auditorium XAUDIO2FX_I3DL2_PRESET_CONCERTHALL, // Reverb_ConcertHall XAUDIO2FX_I3DL2_PRESET_CAVE, // Reverb_Cave XAUDIO2FX_I3DL2_PRESET_ARENA, // Reverb_Arena XAUDIO2FX_I3DL2_PRESET_HANGAR, // Reverb_Hangar XAUDIO2FX_I3DL2_PRESET_CARPETEDHALLWAY, // Reverb_CarpetedHallway XAUDIO2FX_I3DL2_PRESET_HALLWAY, // Reverb_Hallway XAUDIO2FX_I3DL2_PRESET_STONECORRIDOR, // Reverb_StoneCorridor XAUDIO2FX_I3DL2_PRESET_ALLEY, // Reverb_Alley XAUDIO2FX_I3DL2_PRESET_CITY, // Reverb_City XAUDIO2FX_I3DL2_PRESET_MOUNTAINS, // Reverb_Mountains XAUDIO2FX_I3DL2_PRESET_QUARRY, // Reverb_Quarry XAUDIO2FX_I3DL2_PRESET_PLAIN, // Reverb_Plain XAUDIO2FX_I3DL2_PRESET_PARKINGLOT, // Reverb_ParkingLot XAUDIO2FX_I3DL2_PRESET_SEWERPIPE, // Reverb_SewerPipe XAUDIO2FX_I3DL2_PRESET_UNDERWATER, // Reverb_Underwater XAUDIO2FX_I3DL2_PRESET_SMALLROOM, // Reverb_SmallRoom XAUDIO2FX_I3DL2_PRESET_MEDIUMROOM, // Reverb_MediumRoom XAUDIO2FX_I3DL2_PRESET_LARGEROOM, // Reverb_LargeRoom XAUDIO2FX_I3DL2_PRESET_MEDIUMHALL, // Reverb_MediumHall XAUDIO2FX_I3DL2_PRESET_LARGEHALL, // Reverb_LargeHall XAUDIO2FX_I3DL2_PRESET_PLATE, // Reverb_Plate }; inline unsigned int makeVoiceKey( _In_ const WAVEFORMATEX* wfx ) { assert( IsValid(wfx) ); if ( wfx->nChannels > 0x7F ) return 0; union KeyGen { struct { unsigned int tag : 9; unsigned int channels : 7; unsigned int bitsPerSample : 8; } pcm; struct { unsigned int tag : 9; unsigned int channels : 7; unsigned int samplesPerBlock : 16; } adpcm; #if defined(_XBOX_ONE) && defined(_TITLE) struct { unsigned int tag : 9; unsigned int channels : 7; unsigned int encoderVersion : 8; } xma; #endif unsigned int key; } result; static_assert( sizeof(KeyGen) == sizeof(unsigned int), "KeyGen is invalid" ); result.key = 0; if ( wfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE ) { // We reuse EXTENSIBLE only if it is equivalent to the standard form auto wfex = reinterpret_cast( wfx ); if ( wfex->Samples.wValidBitsPerSample != 0 && wfex->Samples.wValidBitsPerSample != wfx->wBitsPerSample ) return 0; if ( wfex->dwChannelMask != 0 && wfex->dwChannelMask != GetDefaultChannelMask( wfx->nChannels ) ) return 0; } uint32_t tag = GetFormatTag( wfx ); switch( tag ) { case WAVE_FORMAT_PCM: static_assert( WAVE_FORMAT_PCM < 0x1ff, "KeyGen tag is too small" ); result.pcm.tag = WAVE_FORMAT_PCM; result.pcm.channels = wfx->nChannels; result.pcm.bitsPerSample = wfx->wBitsPerSample; break; case WAVE_FORMAT_IEEE_FLOAT: static_assert( WAVE_FORMAT_IEEE_FLOAT < 0x1ff, "KeyGen tag is too small" ); if ( wfx->wBitsPerSample != 32 ) return 0; result.pcm.tag = WAVE_FORMAT_IEEE_FLOAT; result.pcm.channels = wfx->nChannels; result.pcm.bitsPerSample = 32; break; case WAVE_FORMAT_ADPCM: static_assert( WAVE_FORMAT_ADPCM < 0x1ff, "KeyGen tag is too small" ); result.adpcm.tag = WAVE_FORMAT_ADPCM; result.adpcm.channels = wfx->nChannels; { auto wfadpcm = reinterpret_cast( wfx ); result.adpcm.samplesPerBlock = wfadpcm->wSamplesPerBlock; } break; #if defined(_XBOX_ONE) && defined(_TITLE) case WAVE_FORMAT_XMA2: static_assert( WAVE_FORMAT_XMA2 < 0x1ff, "KeyGen tag is too small" ); result.xma.tag = WAVE_FORMAT_XMA2; result.xma.channels = wfx->nChannels; { auto xmaFmt = reinterpret_cast( wfx ); if ( ( xmaFmt->LoopBegin > 0 ) || ( xmaFmt->PlayBegin > 0 ) ) return 0; result.xma.encoderVersion = xmaFmt->EncoderVersion; } break; #endif default: return 0; } return result.key; } } static_assert( _countof(gReverbPresets) == Reverb_MAX, "AUDIO_ENGINE_REVERB enum mismatch" ); //====================================================================================== // AudioEngine //====================================================================================== #define SAFE_DESTROY_VOICE(voice) if ( voice ) { voice->DestroyVoice(); voice = nullptr; } // Internal object implementation class. class AudioEngine::Impl { public: Impl() : mMasterVoice( nullptr ), mReverbVoice( nullptr ), masterChannelMask( 0 ), masterChannels( 0 ), masterRate( 0 ), defaultRate( 44100 ), maxVoiceOneshots( SIZE_MAX ), maxVoiceInstances( SIZE_MAX ), mMasterVolume( 1.f ), mCriticalError( false ), mReverbEnabled( false ), mEngineFlags( AudioEngine_Default ), mCategory( AudioCategory_GameEffects ), mVoiceInstances( 0 ) #if (_WIN32_WINNT < _WIN32_WINNT_WIN8) ,mDLL(nullptr) #endif { memset( &mX3DAudio, 0, X3DAUDIO_HANDLE_BYTESIZE ); }; #if (_WIN32_WINNT < _WIN32_WINNT_WIN8) ~Impl() { if (mDLL) { FreeLibrary(mDLL); mDLL = nullptr; } } #endif HRESULT Initialize( AUDIO_ENGINE_FLAGS flags, _In_opt_ const WAVEFORMATEX* wfx, _In_opt_z_ const wchar_t* deviceId, AUDIO_STREAM_CATEGORY category ); HRESULT Reset( _In_opt_ const WAVEFORMATEX* wfx, _In_opt_z_ const wchar_t* deviceId ); void SetSilentMode(); void Shutdown(); bool Update(); void SetReverb( _In_opt_ const XAUDIO2FX_REVERB_PARAMETERS* native ); void SetMasteringLimit( int release, int loudness ); AudioStatistics GetStatistics() const; void TrimVoicePool(); void AllocateVoice( _In_ const WAVEFORMATEX* wfx, SOUND_EFFECT_INSTANCE_FLAGS flags, bool oneshot, _Outptr_result_maybenull_ IXAudio2SourceVoice** voice ); void DestroyVoice( _In_ IXAudio2SourceVoice* voice ); void RegisterNotify( _In_ IVoiceNotify* notify, bool usesUpdate ); void UnregisterNotify( _In_ IVoiceNotify* notify, bool oneshots, bool usesUpdate ); ComPtr xaudio2; IXAudio2MasteringVoice* mMasterVoice; IXAudio2SubmixVoice* mReverbVoice; uint32_t masterChannelMask; uint32_t masterChannels; uint32_t masterRate; int defaultRate; size_t maxVoiceOneshots; size_t maxVoiceInstances; float mMasterVolume; X3DAUDIO_HANDLE mX3DAudio; bool mCriticalError; bool mReverbEnabled; AUDIO_ENGINE_FLAGS mEngineFlags; private: typedef std::set notifylist_t; typedef std::list> oneshotlist_t; typedef std::unordered_multimap voicepool_t; AUDIO_STREAM_CATEGORY mCategory; ComPtr mReverbEffect; ComPtr mVolumeLimiter; oneshotlist_t mOneShots; voicepool_t mVoicePool; notifylist_t mNotifyObjects; notifylist_t mNotifyUpdates; size_t mVoiceInstances; VoiceCallback mVoiceCallback; EngineCallback mEngineCallback; #if (_WIN32_WINNT < _WIN32_WINNT_WIN8) HMODULE mDLL; #endif }; _Use_decl_annotations_ HRESULT AudioEngine::Impl::Initialize( AUDIO_ENGINE_FLAGS flags, const WAVEFORMATEX* wfx, const wchar_t* deviceId, AUDIO_STREAM_CATEGORY category ) { mEngineFlags = flags; mCategory = category; return Reset( wfx, deviceId ); } _Use_decl_annotations_ HRESULT AudioEngine::Impl::Reset( const WAVEFORMATEX* wfx, const wchar_t* deviceId ) { if ( wfx ) { if ( wfx->wFormatTag != WAVE_FORMAT_PCM ) return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); if ( !wfx->nChannels || wfx->nChannels > XAUDIO2_MAX_AUDIO_CHANNELS ) return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); if ( wfx->nSamplesPerSec < XAUDIO2_MIN_SAMPLE_RATE || wfx->nSamplesPerSec > XAUDIO2_MAX_SAMPLE_RATE ) return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); // We don't use other data members of WAVEFORMATEX here to describe the device format, so no need to fully validate } assert( !xaudio2 ); assert( !mMasterVoice ); assert( !mReverbVoice ); masterChannelMask = masterChannels = masterRate = 0; memset( &mX3DAudio, 0, X3DAUDIO_HANDLE_BYTESIZE ); mCriticalError = false; mReverbEnabled = false; // // Create XAudio2 engine // UINT32 eflags = 0; #if (_WIN32_WINNT < _WIN32_WINNT_WIN8) if ( mEngineFlags & AudioEngine_Debug ) { if ( !mDLL ) { mDLL = LoadLibraryEx( L"XAudioD2_7.DLL", nullptr, 0x00000800 /* LOAD_LIBRARY_SEARCH_SYSTEM32 */ ); if ( !mDLL ) { DebugTrace( "ERROR: XAudio 2.7 debug version not installed on system (install the DirectX SDK Developer Runtime)\n" ); return HRESULT_FROM_WIN32( ERROR_NOT_FOUND ); } } eflags |= XAUDIO2_DEBUG_ENGINE; } else if ( !mDLL ) { mDLL = LoadLibraryEx( L"XAudio2_7.DLL", nullptr, 0x00000800 /* LOAD_LIBRARY_SEARCH_SYSTEM32 */ ); if ( !mDLL ) { DebugTrace( "ERROR: XAudio 2.7 not installed on system (install the DirectX End-user Runtimes (June 2010))\n" ); return HRESULT_FROM_WIN32( ERROR_NOT_FOUND ); } } #endif HRESULT hr = XAudio2Create( xaudio2.ReleaseAndGetAddressOf(), eflags ); if( FAILED( hr ) ) { #if (_WIN32_WINNT < _WIN32_WINNT_WIN8) DebugTrace( "ERROR: XAudio 2.7 not found (have you called CoInitialize?)\n" ); #endif return hr; } if ( mEngineFlags & AudioEngine_Debug ) { XAUDIO2_DEBUG_CONFIGURATION debug ={0}; debug.TraceMask = XAUDIO2_LOG_ERRORS | XAUDIO2_LOG_WARNINGS; debug.BreakMask = XAUDIO2_LOG_ERRORS; xaudio2->SetDebugConfiguration( &debug, nullptr ); #if (_WIN32_WINNT >= _WIN32_WINNT_WIN10) || defined(_XBOX_ONE) DebugTrace("INFO: XAudio 2.9 debugging enabled\n"); #elif (_WIN32_WINNT >= _WIN32_WINNT_WIN8) // To see the trace output, you need to view ETW logs for this application: // Go to Control Panel, Administrative Tools, Event Viewer. // View->Show Analytic and Debug Logs. // Applications and Services Logs / Microsoft / Windows / XAudio2. // Right click on Microsoft Windows XAudio2 debug logging, Properties, then Enable Logging, and hit OK DebugTrace( "INFO: XAudio 2.8 debugging enabled\n" ); #else // To see the trace output, see the debug output channel window DebugTrace( "INFO: XAudio 2.7 debugging enabled\n" ); #endif } if ( mEngineFlags & AudioEngine_DisableVoiceReuse ) { DebugTrace( "INFO: Voice reuse is disabled\n" ); } hr = xaudio2->RegisterForCallbacks( &mEngineCallback ); if ( FAILED(hr) ) { xaudio2.Reset(); return hr; } // // Create mastering voice for device // #if (_WIN32_WINNT >= _WIN32_WINNT_WIN8) hr = xaudio2->CreateMasteringVoice( &mMasterVoice, (wfx) ? wfx->nChannels : XAUDIO2_DEFAULT_CHANNELS, (wfx) ? wfx->nSamplesPerSec : XAUDIO2_DEFAULT_SAMPLERATE, 0, deviceId, nullptr, mCategory ); if ( FAILED(hr) ) { xaudio2.Reset(); return hr; } DWORD dwChannelMask; hr = mMasterVoice->GetChannelMask( &dwChannelMask ); if ( FAILED(hr) ) { SAFE_DESTROY_VOICE( mMasterVoice ); xaudio2.Reset(); return hr; } XAUDIO2_VOICE_DETAILS details; mMasterVoice->GetVoiceDetails( &details ); masterChannelMask = dwChannelMask; masterChannels = details.InputChannels; masterRate = details.InputSampleRate; #else UINT32 count = 0; hr = xaudio2->GetDeviceCount( &count ); if ( FAILED(hr) ) { xaudio2.Reset(); return hr; } if ( !count ) { xaudio2.Reset(); return HRESULT_FROM_WIN32( ERROR_NOT_FOUND ); } UINT32 devIndex = 0; if ( deviceId ) { // Translate device ID back into device index devIndex = UINT32(-1); for( UINT32 j = 0; j < count; ++j ) { XAUDIO2_DEVICE_DETAILS details; hr = xaudio2->GetDeviceDetails( j, &details ); if ( SUCCEEDED(hr) ) { if ( wcsncmp( deviceId, details.DeviceID, 256 ) == 0 ) { devIndex = j; masterChannelMask = details.OutputFormat.dwChannelMask; break; } } } if ( devIndex == UINT32(-1) ) { xaudio2.Reset(); return HRESULT_FROM_WIN32( ERROR_NOT_FOUND ); } } else { // No search needed XAUDIO2_DEVICE_DETAILS details; hr = xaudio2->GetDeviceDetails( 0, &details ); if ( FAILED(hr) ) { xaudio2.Reset(); return hr; } masterChannelMask = details.OutputFormat.dwChannelMask; } hr = xaudio2->CreateMasteringVoice( &mMasterVoice, (wfx) ? wfx->nChannels : XAUDIO2_DEFAULT_CHANNELS, (wfx) ? wfx->nSamplesPerSec : XAUDIO2_DEFAULT_SAMPLERATE, 0, devIndex, nullptr ); if ( FAILED(hr) ) { xaudio2.Reset(); return hr; } XAUDIO2_VOICE_DETAILS details; mMasterVoice->GetVoiceDetails( &details ); masterChannels = details.InputChannels; masterRate = details.InputSampleRate; #endif DebugTrace( "INFO: mastering voice has %u channels, %u sample rate, %08X channel mask\n", masterChannels, masterRate, masterChannelMask ); if ( mMasterVolume != 1.f ) { hr = mMasterVoice->SetVolume( mMasterVolume ); if ( FAILED(hr) ) { SAFE_DESTROY_VOICE( mMasterVoice ); xaudio2.Reset(); return hr; } } // // Setup mastering volume limiter (optional) // if ( mEngineFlags & AudioEngine_UseMasteringLimiter ) { FXMASTERINGLIMITER_PARAMETERS params = {0}; params.Release = FXMASTERINGLIMITER_DEFAULT_RELEASE; params.Loudness = FXMASTERINGLIMITER_DEFAULT_LOUDNESS; #if (_WIN32_WINNT >= _WIN32_WINNT_WIN8) hr = CreateFX( __uuidof(FXMasteringLimiter), mVolumeLimiter.ReleaseAndGetAddressOf(), ¶ms, sizeof(params) ); #else hr = CreateFX( __uuidof(FXMasteringLimiter), mVolumeLimiter.ReleaseAndGetAddressOf() ); #endif if ( FAILED(hr) ) { SAFE_DESTROY_VOICE( mMasterVoice ); xaudio2.Reset(); return hr; } XAUDIO2_EFFECT_DESCRIPTOR desc = {0}; desc.InitialState = TRUE; desc.OutputChannels = masterChannels; desc.pEffect = mVolumeLimiter.Get(); XAUDIO2_EFFECT_CHAIN chain = { 1, &desc }; hr = mMasterVoice->SetEffectChain( &chain ); if ( FAILED(hr) ) { SAFE_DESTROY_VOICE( mMasterVoice ); mVolumeLimiter.Reset(); xaudio2.Reset(); return hr; } #if (_WIN32_WINNT < _WIN32_WINNT_WIN8) hr = mMasterVoice->SetEffectParameters( 0, ¶ms, sizeof(params) ); if ( FAILED(hr) ) { SAFE_DESTROY_VOICE( mMasterVoice ); mVolumeLimiter.Reset(); xaudio2.Reset(); return hr; } #endif DebugTrace( "INFO: Mastering volume limiter enabled\n" ); } // // Setup environmental reverb for 3D audio (optional) // if ( mEngineFlags & AudioEngine_EnvironmentalReverb ) { UINT32 rflags = 0; #if (_WIN32_WINNT < _WIN32_WINNT_WIN8) if ( mEngineFlags & AudioEngine_Debug ) { rflags |= XAUDIO2FX_DEBUG; } #endif hr = XAudio2CreateReverb( mReverbEffect.ReleaseAndGetAddressOf(), rflags ); if ( FAILED(hr) ) { SAFE_DESTROY_VOICE( mMasterVoice ); mVolumeLimiter.Reset(); xaudio2.Reset(); return hr; } XAUDIO2_EFFECT_DESCRIPTOR effects[] = { { mReverbEffect.Get(), TRUE, 1 } }; XAUDIO2_EFFECT_CHAIN effectChain = { 1, effects }; mReverbEnabled = true; hr = xaudio2->CreateSubmixVoice( &mReverbVoice, 1, masterRate, (mEngineFlags & AudioEngine_ReverbUseFilters ) ? XAUDIO2_VOICE_USEFILTER : 0, 0, nullptr, &effectChain ); if ( FAILED(hr) ) { SAFE_DESTROY_VOICE( mMasterVoice ); mReverbEffect.Reset(); mVolumeLimiter.Reset(); xaudio2.Reset(); return hr; } XAUDIO2FX_REVERB_PARAMETERS native; ReverbConvertI3DL2ToNative( &gReverbPresets[ Reverb_Default ], &native ); hr = mReverbVoice->SetEffectParameters( 0, &native, sizeof( XAUDIO2FX_REVERB_PARAMETERS ) ); if ( FAILED(hr) ) { SAFE_DESTROY_VOICE( mReverbVoice ); SAFE_DESTROY_VOICE( mMasterVoice ); mReverbEffect.Reset(); mVolumeLimiter.Reset(); xaudio2.Reset(); return hr; } DebugTrace( "INFO: I3DL2 reverb effect enabled for 3D positional audio\n" ); } // // Setup 3D audio // const float SPEEDOFSOUND = X3DAUDIO_SPEED_OF_SOUND; #if (_WIN32_WINNT >= _WIN32_WINNT_WIN8) hr = X3DAudioInitialize( masterChannelMask, SPEEDOFSOUND, mX3DAudio ); if ( FAILED(hr) ) { SAFE_DESTROY_VOICE( mReverbVoice ); SAFE_DESTROY_VOICE( mMasterVoice ); mReverbEffect.Reset(); mVolumeLimiter.Reset(); xaudio2.Reset(); return hr; } #else X3DAudioInitialize( masterChannelMask, SPEEDOFSOUND, mX3DAudio ); #endif // // Inform any notify objects we are ready to go again // for( auto it = mNotifyObjects.begin(); it != mNotifyObjects.end(); ++it ) { assert( *it != 0 ); (*it)->OnReset(); } return S_OK; } void AudioEngine::Impl::SetSilentMode() { for( auto it = mNotifyObjects.begin(); it != mNotifyObjects.end(); ++it ) { assert( *it != 0 ); (*it)->OnCriticalError(); } for( auto it = mOneShots.begin(); it != mOneShots.end(); ++it ) { assert( it->second != 0 ); it->second->DestroyVoice(); } mOneShots.clear(); for( auto it = mVoicePool.begin(); it != mVoicePool.end(); ++it ) { assert( it->second != 0 ); it->second->DestroyVoice(); } mVoicePool.clear(); mVoiceInstances = 0; SAFE_DESTROY_VOICE( mReverbVoice ); SAFE_DESTROY_VOICE( mMasterVoice ); mReverbEffect.Reset(); mVolumeLimiter.Reset(); xaudio2.Reset(); } void AudioEngine::Impl::Shutdown() { for( auto it = mNotifyObjects.begin(); it != mNotifyObjects.end(); ++it ) { assert( *it != 0 ); (*it)->OnDestroyEngine(); } if ( xaudio2 ) { xaudio2->UnregisterForCallbacks( &mEngineCallback ); xaudio2->StopEngine(); for( auto it = mOneShots.begin(); it != mOneShots.end(); ++it ) { assert( it->second != 0 ); it->second->DestroyVoice(); } mOneShots.clear(); for( auto it = mVoicePool.begin(); it != mVoicePool.end(); ++it ) { assert( it->second != 0 ); it->second->DestroyVoice(); } mVoicePool.clear(); mVoiceInstances = 0; SAFE_DESTROY_VOICE( mReverbVoice ); SAFE_DESTROY_VOICE( mMasterVoice ); mReverbEffect.Reset(); mVolumeLimiter.Reset(); xaudio2.Reset(); masterChannelMask = masterChannels = masterRate = 0; mCriticalError = false; mReverbEnabled = false; memset( &mX3DAudio, 0, X3DAUDIO_HANDLE_BYTESIZE ); } } bool AudioEngine::Impl::Update() { if ( !xaudio2 ) return false; HANDLE events[2] = { mEngineCallback.mCriticalError.get(), mVoiceCallback.mBufferEnd.get() }; DWORD result = WaitForMultipleObjectsEx( 2, events, FALSE, 0, FALSE ); switch( result ) { case WAIT_TIMEOUT: break; case WAIT_OBJECT_0: // OnCritialError mCriticalError = true; SetSilentMode(); return false; case WAIT_OBJECT_0 + 1: // OnBufferEnd // Scan for completed one-shot voices for( auto it = mOneShots.begin(); it != mOneShots.end(); ) { assert( it->second != 0 ); XAUDIO2_VOICE_STATE xstate; #if (_WIN32_WINNT >= _WIN32_WINNT_WIN8) it->second->GetState( &xstate, XAUDIO2_VOICE_NOSAMPLESPLAYED ); #else it->second->GetState( &xstate ); #endif if ( !xstate.BuffersQueued ) { it->second->Stop( 0 ); if ( it->first ) { // Put voice back into voice pool for reuse since it has a non-zero voiceKey #ifdef VERBOSE_TRACE DebugTrace( "INFO: One-shot voice being saved for reuse (%08X)\n", it->first ); #endif voicepool_t::value_type v( it->first, it->second ); mVoicePool.emplace( v ); } else { // Voice is to be destroyed rather than reused #ifdef VERBOSE_TRACE DebugTrace( "INFO: Destroying one-shot voice\n" ); #endif it->second->DestroyVoice(); } it = mOneShots.erase( it ); } else ++it; } break; case WAIT_FAILED: throw std::exception( "WaitForMultipleObjects" ); } // // Inform any notify objects of updates // for( auto it = mNotifyUpdates.begin(); it != mNotifyUpdates.end(); ++it ) { assert( *it != 0 ); (*it)->OnUpdate(); } return true; } _Use_decl_annotations_ void AudioEngine::Impl::SetReverb( const XAUDIO2FX_REVERB_PARAMETERS* native ) { if ( !mReverbVoice ) return; if ( native ) { if ( !mReverbEnabled ) { mReverbEnabled = true; mReverbVoice->EnableEffect( 0 ); } mReverbVoice->SetEffectParameters( 0, native, sizeof( XAUDIO2FX_REVERB_PARAMETERS ) ); } else if ( mReverbEnabled ) { mReverbEnabled = false; mReverbVoice->DisableEffect( 0 ); } } void AudioEngine::Impl::SetMasteringLimit( int release, int loudness ) { if ( !mVolumeLimiter || !mMasterVoice ) return; if ( ( release < FXMASTERINGLIMITER_MIN_RELEASE ) || ( release > FXMASTERINGLIMITER_MAX_RELEASE ) ) throw std::out_of_range( "AudioEngine::SetMasteringLimit" ); if ( ( loudness < FXMASTERINGLIMITER_MIN_LOUDNESS ) || ( loudness > FXMASTERINGLIMITER_MAX_LOUDNESS ) ) throw std::out_of_range( "AudioEngine::SetMasteringLimit" ); FXMASTERINGLIMITER_PARAMETERS params = {0}; params.Release = static_cast( release ); params.Loudness = static_cast( loudness ); HRESULT hr = mMasterVoice->SetEffectParameters( 0, ¶ms, sizeof(params) ); ThrowIfFailed( hr ); } AudioStatistics AudioEngine::Impl::GetStatistics() const { AudioStatistics stats; memset( &stats, 0, sizeof(stats) ); stats.allocatedVoices = stats.allocatedVoicesOneShot = mOneShots.size() + mVoicePool.size(); stats.allocatedVoicesIdle = mVoicePool.size(); for( auto it = mNotifyObjects.begin(); it != mNotifyObjects.end(); ++it ) { assert( *it != 0 ); (*it)->GatherStatistics( stats ); } assert( stats.allocatedVoices == ( mOneShots.size() + mVoicePool.size() + mVoiceInstances ) ); return stats; } void AudioEngine::Impl::TrimVoicePool() { for( auto it = mNotifyObjects.begin(); it != mNotifyObjects.end(); ++it ) { assert( *it != 0 ); (*it)->OnTrim(); } for( auto it = mVoicePool.begin(); it != mVoicePool.end(); ++it ) { assert( it->second != 0 ); it->second->DestroyVoice(); } mVoicePool.clear(); } _Use_decl_annotations_ void AudioEngine::Impl::AllocateVoice( const WAVEFORMATEX* wfx, SOUND_EFFECT_INSTANCE_FLAGS flags, bool oneshot, IXAudio2SourceVoice** voice ) { if ( !wfx ) throw std::exception( "Wave format is required\n" ); // No need to call IsValid on wfx because CreateSourceVoice will do that if ( !voice ) throw std::exception("Voice pointer must be non-null"); *voice = nullptr; if ( !xaudio2 || mCriticalError ) return; #ifndef NDEBUG float maxFrequencyRatio = XAudio2SemitonesToFrequencyRatio(12); assert( maxFrequencyRatio <= XAUDIO2_DEFAULT_FREQ_RATIO ); #endif unsigned int voiceKey = 0; if ( oneshot ) { if ( flags & ( SoundEffectInstance_Use3D | SoundEffectInstance_ReverbUseFilters | SoundEffectInstance_NoSetPitch ) ) { DebugTrace( ( flags & SoundEffectInstance_NoSetPitch ) ? "ERROR: One-shot voices must support pitch-shifting for voice reuse\n" : "ERROR: One-use voices cannot use 3D positional audio\n" ); throw std::exception( "Invalid flags for one-shot voice" ); } #ifdef VERBOSE_TRACE if ( wfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE ) { DebugTrace( "INFO: Requesting one-shot: Format Tag EXTENSIBLE %u, %u channels, %u-bit, %u blkalign, %u Hz\n", GetFormatTag( wfx ), wfx->nChannels, wfx->wBitsPerSample, wfx->nBlockAlign, wfx->nSamplesPerSec ); } else { DebugTrace( "INFO: Requesting one-shot: Format Tag %u, %u channels, %u-bit, %u blkalign, %u Hz\n", wfx->wFormatTag, wfx->nChannels, wfx->wBitsPerSample, wfx->nBlockAlign, wfx->nSamplesPerSec ); } #endif if ( !( mEngineFlags & AudioEngine_DisableVoiceReuse ) ) { voiceKey = makeVoiceKey( wfx ); if ( voiceKey != 0 ) { auto it = mVoicePool.find( voiceKey ); if ( it != mVoicePool.end() ) { // Found a matching (stopped) voice to reuse assert( it->second != 0 ); *voice = it->second; mVoicePool.erase( it ); // Reset any volume/pitch-shifting HRESULT hr = (*voice)->SetVolume(1.f); ThrowIfFailed( hr ); hr = (*voice)->SetFrequencyRatio(1.f); ThrowIfFailed( hr ); if (wfx->nChannels == 1 || wfx->nChannels == 2) { // Reset any panning float matrix[16]; memset( matrix, 0, sizeof(float) * 16 ); ComputePan( 0.f, wfx->nChannels, matrix ); hr = (*voice)->SetOutputMatrix(nullptr, wfx->nChannels, masterChannels, matrix); ThrowIfFailed( hr ); } } else if ( ( mVoicePool.size() + mOneShots.size() + 1 ) >= maxVoiceOneshots ) { DebugTrace( "WARNING: Too many one-shot voices in use (%Iu + %Iu >= %Iu); one-shot not played\n", mVoicePool.size(), mOneShots.size() + 1, maxVoiceOneshots ); return; } else { // makeVoiceKey already constrained the supported wfx formats to those supported for reuse char buff[64] = {0}; auto wfmt = reinterpret_cast( buff ); uint32_t tag = GetFormatTag( wfx ); switch( tag ) { case WAVE_FORMAT_PCM: CreateIntegerPCM( wfmt, defaultRate, wfx->nChannels, wfx->wBitsPerSample ); break; case WAVE_FORMAT_IEEE_FLOAT: CreateFloatPCM( wfmt, defaultRate, wfx->nChannels ); break; case WAVE_FORMAT_ADPCM: { auto wfadpcm = reinterpret_cast( wfx ); CreateADPCM( wfmt, 64, defaultRate, wfx->nChannels, wfadpcm->wSamplesPerBlock ); } break; #if defined(_XBOX_ONE) && defined(_TITLE) case WAVE_FORMAT_XMA2: CreateXMA2( wfmt, 64, defaultRate, wfx->nChannels, 65536, 2, 0 ); break; #endif } #ifdef VERBOSE_TRACE DebugTrace( "INFO: Allocate reuse voice: Format Tag %u, %u channels, %u-bit, %u blkalign, %u Hz\n", wfmt->wFormatTag, wfmt->nChannels, wfmt->wBitsPerSample, wfmt->nBlockAlign, wfmt->nSamplesPerSec ); #endif assert( voiceKey == makeVoiceKey( wfmt ) ); HRESULT hr = xaudio2->CreateSourceVoice( voice, wfmt, 0, XAUDIO2_DEFAULT_FREQ_RATIO, &mVoiceCallback, nullptr, nullptr ); if ( FAILED(hr) ) { DebugTrace( "ERROR: CreateSourceVoice (reuse) failed with error %08X\n", hr ); throw std::exception( "CreateSourceVoice" ); } } assert( *voice != 0 ); HRESULT hr = (*voice)->SetSourceSampleRate( wfx->nSamplesPerSec ); if ( FAILED(hr) ) { DebugTrace( "ERROR: SetSourceSampleRate failed with error %08X\n", hr ); throw std::exception( "SetSourceSampleRate" ); } } } } if ( !*voice ) { if ( oneshot ) { if ( ( mVoicePool.size() + mOneShots.size() + 1 ) >= maxVoiceOneshots ) { DebugTrace( "WARNING: Too many one-shot voices in use (%Iu + %Iu >= %Iu); one-shot not played; see TrimVoicePool\n", mVoicePool.size(), mOneShots.size() + 1, maxVoiceOneshots ); return; } } else if ( ( mVoiceInstances + 1 ) >= maxVoiceInstances ) { DebugTrace( "ERROR: Too many instance voices (%Iu >= %Iu); see TrimVoicePool\n", mVoiceInstances + 1, maxVoiceInstances ); throw std::exception( "Too many instance voices" ); } UINT32 vflags = ( flags & SoundEffectInstance_NoSetPitch ) ? XAUDIO2_VOICE_NOPITCH : 0; HRESULT hr; if ( flags & SoundEffectInstance_Use3D ) { XAUDIO2_SEND_DESCRIPTOR sendDescriptors[2]; sendDescriptors[0].Flags = sendDescriptors[1].Flags = (flags & SoundEffectInstance_ReverbUseFilters) ? XAUDIO2_SEND_USEFILTER : 0; sendDescriptors[0].pOutputVoice = mMasterVoice; sendDescriptors[1].pOutputVoice = mReverbVoice; const XAUDIO2_VOICE_SENDS sendList = { mReverbVoice ? 2U : 1U, sendDescriptors }; #ifdef VERBOSE_TRACE DebugTrace( "INFO: Allocate voice 3D: Format Tag %u, %u channels, %u-bit, %u blkalign, %u Hz\n", wfx->wFormatTag, wfx->nChannels, wfx->wBitsPerSample, wfx->nBlockAlign, wfx->nSamplesPerSec ); #endif hr = xaudio2->CreateSourceVoice( voice, wfx, vflags, XAUDIO2_DEFAULT_FREQ_RATIO, &mVoiceCallback, &sendList, nullptr ); } else { #ifdef VERBOSE_TRACE DebugTrace( "INFO: Allocate voice: Format Tag %u, %u channels, %u-bit, %u blkalign, %u Hz\n", wfx->wFormatTag, wfx->nChannels, wfx->wBitsPerSample, wfx->nBlockAlign, wfx->nSamplesPerSec ); #endif hr = xaudio2->CreateSourceVoice( voice, wfx, vflags, XAUDIO2_DEFAULT_FREQ_RATIO, &mVoiceCallback, nullptr, nullptr ); } if ( FAILED(hr) ) { DebugTrace( "ERROR: CreateSourceVoice failed with error %08X\n", hr ); throw std::exception( "CreateSourceVoice" ); } else if ( !oneshot ) { ++mVoiceInstances; } } if ( oneshot ) { assert( *voice != 0 ); mOneShots.emplace_back( std::make_pair( voiceKey, *voice ) ); } } void AudioEngine::Impl::DestroyVoice( _In_ IXAudio2SourceVoice* voice ) { if ( !voice ) return; #ifndef NDEBUG for( auto it = mOneShots.cbegin(); it != mOneShots.cend(); ++it ) { if ( it->second == voice ) { DebugTrace( "ERROR: DestroyVoice should not be called for a one-shot voice\n" ); throw std::exception( "DestroyVoice" ); } } for( auto it = mVoicePool.cbegin(); it != mVoicePool.cend(); ++it ) { if ( it->second == voice ) { DebugTrace( "ERROR: DestroyVoice should not be called for a one-shot voice; see TrimVoicePool\n" ); throw std::exception( "DestroyVoice" ); } } #endif assert( mVoiceInstances > 0 ); --mVoiceInstances; voice->DestroyVoice(); } void AudioEngine::Impl::RegisterNotify( _In_ IVoiceNotify* notify, bool usesUpdate ) { assert( notify != 0 ); mNotifyObjects.insert( notify ); if ( usesUpdate ) { mNotifyUpdates.insert( notify ); } } void AudioEngine::Impl::UnregisterNotify( _In_ IVoiceNotify* notify, bool usesOneShots, bool usesUpdate ) { assert( notify != 0 ); mNotifyObjects.erase( notify ); // Check for any pending one-shots for this notification object if ( usesOneShots ) { bool setevent = false; for( auto it = mOneShots.begin(); it != mOneShots.end(); ++it ) { assert( it->second != 0 ); XAUDIO2_VOICE_STATE state; #if (_WIN32_WINNT >= _WIN32_WINNT_WIN8) it->second->GetState(&state, XAUDIO2_VOICE_NOSAMPLESPLAYED ); #else it->second->GetState(&state); #endif if ( state.pCurrentBufferContext == notify ) { it->second->Stop( 0 ); it->second->FlushSourceBuffers(); setevent = true; } } if (setevent) { // Trigger scan on next call to Update... SetEvent( mVoiceCallback.mBufferEnd.get() ); } } if ( usesUpdate ) { mNotifyUpdates.erase( notify ); } } //-------------------------------------------------------------------------------------- // AudioEngine //-------------------------------------------------------------------------------------- // Public constructor. _Use_decl_annotations_ AudioEngine::AudioEngine( AUDIO_ENGINE_FLAGS flags, const WAVEFORMATEX* wfx, const wchar_t* deviceId, AUDIO_STREAM_CATEGORY category ) : pImpl(new Impl() ) { HRESULT hr = pImpl->Initialize( flags, wfx, deviceId, category ); if ( FAILED(hr) ) { if ( hr == HRESULT_FROM_WIN32( ERROR_NOT_FOUND ) ) { if ( flags & AudioEngine_ThrowOnNoAudioHW ) { DebugTrace( "ERROR: AudioEngine found no default audio device\n" ); throw std::exception( "AudioEngineNoAudioHW" ); } else { DebugTrace( "WARNING: AudioEngine found no default audio device; running in 'silent mode'\n" ); } } else { DebugTrace( "ERROR: AudioEngine failed (%08X) to initialize using device [%ls]\n", hr, ( deviceId ) ? deviceId : L"default" ); throw std::exception( "AudioEngine" ); } } } // Move constructor. AudioEngine::AudioEngine(AudioEngine&& moveFrom) : pImpl(std::move(moveFrom.pImpl)) { } // Move assignment. AudioEngine& AudioEngine::operator= (AudioEngine&& moveFrom) { pImpl = std::move(moveFrom.pImpl); return *this; } // Public destructor. AudioEngine::~AudioEngine() { if ( pImpl ) { pImpl->Shutdown(); } } // Public methods. bool AudioEngine::Update() { return pImpl->Update(); } _Use_decl_annotations_ bool AudioEngine::Reset( const WAVEFORMATEX* wfx, const wchar_t* deviceId ) { if ( pImpl->xaudio2 ) { DebugTrace( "WARNING: Called Reset for active audio graph; going silent in preparation for migration\n" ); pImpl->SetSilentMode(); } HRESULT hr = pImpl->Reset( wfx, deviceId ); if ( FAILED(hr) ) { if ( hr == HRESULT_FROM_WIN32( ERROR_NOT_FOUND ) ) { if ( pImpl->mEngineFlags & AudioEngine_ThrowOnNoAudioHW ) { DebugTrace( "ERROR: AudioEngine found no default audio device on Reset\n" ); throw std::exception( "AudioEngineNoAudioHW" ); } else { DebugTrace( "WARNING: AudioEngine found no default audio device on Reset; running in 'silent mode'\n" ); return false; } } else { DebugTrace( "ERROR: AudioEngine failed (%08X) to Reset using device [%ls]\n", hr, ( deviceId ) ? deviceId : L"default" ); throw std::exception( "AudioEngine::Reset" ); } } DebugTrace( "INFO: AudioEngine Reset using device [%ls]\n", ( deviceId ) ? deviceId : L"default" ); return true; } void AudioEngine::Suspend() { if ( !pImpl->xaudio2 ) return; pImpl->xaudio2->StopEngine(); } void AudioEngine::Resume() { if ( !pImpl->xaudio2 ) return; HRESULT hr = pImpl->xaudio2->StartEngine(); ThrowIfFailed( hr ); } float AudioEngine::GetMasterVolume() const { return pImpl->mMasterVolume; } void AudioEngine::SetMasterVolume( float volume ) { assert( volume >= -XAUDIO2_MAX_VOLUME_LEVEL && volume <= XAUDIO2_MAX_VOLUME_LEVEL ); pImpl->mMasterVolume = volume; if ( pImpl->mMasterVoice ) { HRESULT hr = pImpl->mMasterVoice->SetVolume( volume ); ThrowIfFailed( hr ); } } void AudioEngine::SetReverb( AUDIO_ENGINE_REVERB reverb ) { if ( reverb < 0 || reverb >= Reverb_MAX ) throw std::out_of_range( "AudioEngine::SetReverb" ); if ( reverb == Reverb_Off ) { pImpl->SetReverb( nullptr ); } else { XAUDIO2FX_REVERB_PARAMETERS native; ReverbConvertI3DL2ToNative( &gReverbPresets[ reverb ], &native ); pImpl->SetReverb( &native ); } } _Use_decl_annotations_ void AudioEngine::SetReverb( const XAUDIO2FX_REVERB_PARAMETERS* native ) { pImpl->SetReverb( native ); } void AudioEngine::SetMasteringLimit( int release, int loudness ) { pImpl->SetMasteringLimit( release, loudness ); } // Public accessors. AudioStatistics AudioEngine::GetStatistics() const { return pImpl->GetStatistics(); } WAVEFORMATEXTENSIBLE AudioEngine::GetOutputFormat() const { WAVEFORMATEXTENSIBLE wfx; memset( &wfx, 0, sizeof(WAVEFORMATEXTENSIBLE) ); if ( !pImpl->xaudio2 ) return wfx; wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; wfx.Format.wBitsPerSample = wfx.Samples.wValidBitsPerSample = 16; // This is a guess wfx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); wfx.Format.nChannels = static_cast( pImpl->masterChannels ); wfx.Format.nSamplesPerSec = pImpl->masterRate; wfx.dwChannelMask = pImpl->masterChannelMask; wfx.Format.nBlockAlign = WORD( wfx.Format.nChannels * wfx.Format.wBitsPerSample / 8 ); wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign; static const GUID s_pcm = { WAVE_FORMAT_PCM, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }; memcpy( &wfx.SubFormat, &s_pcm, sizeof(GUID) ); return wfx; } uint32_t AudioEngine::GetChannelMask() const { return pImpl->masterChannelMask; } int AudioEngine::GetOutputChannels() const { return pImpl->masterChannels; } bool AudioEngine::IsAudioDevicePresent() const { return ( pImpl->xaudio2.Get() != 0 ) && !pImpl->mCriticalError; } bool AudioEngine::IsCriticalError() const { return pImpl->mCriticalError; } // Voice management. void AudioEngine::SetDefaultSampleRate( int sampleRate ) { if ( ( sampleRate < XAUDIO2_MIN_SAMPLE_RATE ) || ( sampleRate > XAUDIO2_MAX_SAMPLE_RATE ) ) throw std::exception( "Default sample rate is out of range" ); pImpl->defaultRate = sampleRate; } void AudioEngine::SetMaxVoicePool( size_t maxOneShots, size_t maxInstances ) { if ( maxOneShots > 0 ) pImpl->maxVoiceOneshots = maxOneShots; if ( maxInstances > 0 ) pImpl->maxVoiceInstances = maxInstances; } void AudioEngine::TrimVoicePool() { pImpl->TrimVoicePool(); } _Use_decl_annotations_ void AudioEngine::AllocateVoice( const WAVEFORMATEX* wfx, SOUND_EFFECT_INSTANCE_FLAGS flags, bool oneshot, IXAudio2SourceVoice** voice ) { pImpl->AllocateVoice( wfx, flags, oneshot, voice ); } void AudioEngine::DestroyVoice( _In_ IXAudio2SourceVoice* voice ) { pImpl->DestroyVoice( voice ); } void AudioEngine::RegisterNotify( _In_ IVoiceNotify* notify, bool usesUpdate ) { pImpl->RegisterNotify( notify, usesUpdate ); } void AudioEngine::UnregisterNotify( _In_ IVoiceNotify* notify, bool oneshots, bool usesUpdate ) { pImpl->UnregisterNotify( notify, oneshots, usesUpdate ); } IXAudio2* AudioEngine::GetInterface() const { return pImpl->xaudio2.Get(); } IXAudio2MasteringVoice* AudioEngine::GetMasterVoice() const { return pImpl->mMasterVoice; } IXAudio2SubmixVoice* AudioEngine::GetReverbVoice() const { return pImpl->mReverbVoice; } X3DAUDIO_HANDLE& AudioEngine::Get3DHandle() const { return pImpl->mX3DAudio; } // Static methods. #if defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP #include #elif defined(_XBOX_ONE) #include #include #elif (_WIN32_WINNT >= _WIN32_WINNT_WIN8) #pragma comment(lib,"runtimeobject.lib") #include #include #endif std::vector AudioEngine::GetRendererDetails() { std::vector list; #if defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP LPCWSTR id = GetDefaultAudioRenderId( Default ); if ( !id ) return list; RendererDetail device; device.deviceId = id; device.description = L"Default"; CoTaskMemFree( (LPVOID)id ); #elif defined(_XBOX_ONE) using namespace Microsoft::WRL; using namespace Microsoft::WRL::Wrappers; using namespace ABI::Windows::Foundation; using namespace ABI::Windows::Media::Devices; ComPtr mdStatics; HRESULT hr = GetActivationFactory( HStringReference(RuntimeClass_Windows_Media_Devices_MediaDevice).Get(), &mdStatics ); ThrowIfFailed( hr ); HString id; hr = mdStatics->GetDefaultAudioRenderId( AudioDeviceRole_Default, id.GetAddressOf() ); ThrowIfFailed( hr ); RendererDetail device; device.deviceId = id.GetRawBuffer( nullptr ); device.description = L"Default"; list.emplace_back( device ); #elif (_WIN32_WINNT >= _WIN32_WINNT_WIN8) #if defined(__cplusplus_winrt) // Enumerating with WinRT using C++/CX (Windows Store apps) using Windows::Devices::Enumeration::DeviceClass; using Windows::Devices::Enumeration::DeviceInformation; using Windows::Devices::Enumeration::DeviceInformationCollection; auto operation = DeviceInformation::FindAllAsync(DeviceClass::AudioRender); while (operation->Status != Windows::Foundation::AsyncStatus::Completed) ; DeviceInformationCollection^ devices = operation->GetResults(); for (unsigned i = 0; i < devices->Size; ++i) { using Windows::Devices::Enumeration::DeviceInformation; DeviceInformation^ d = devices->GetAt(i); RendererDetail device; device.deviceId = d->Id->Data(); device.description = d->Name->Data(); list.emplace_back(device); } #else // Enumerating with WinRT using WRL (Win32 desktop app for Windows 8.x) using namespace Microsoft::WRL; using namespace Microsoft::WRL::Wrappers; using namespace ABI::Windows::Foundation; using namespace ABI::Windows::Foundation::Collections; using namespace ABI::Windows::Devices::Enumeration; RoInitializeWrapper initialize(RO_INIT_MULTITHREADED); HRESULT hr = initialize; ThrowIfFailed( hr ); ComPtr diFactory; hr = GetActivationFactory( HStringReference(RuntimeClass_Windows_Devices_Enumeration_DeviceInformation).Get(), &diFactory ); ThrowIfFailed( hr ); Event findCompleted( CreateEventEx( nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, WRITE_OWNER | EVENT_ALL_ACCESS ) ); if ( !findCompleted.IsValid() ) throw std::exception( "CreateEventEx" ); auto callback = Callback>( [&findCompleted,list]( IAsyncOperation* aDevices, AsyncStatus status ) -> HRESULT { UNREFERENCED_PARAMETER(aDevices); UNREFERENCED_PARAMETER(status); SetEvent( findCompleted.Get() ); return S_OK; }); ComPtr> operation; hr = diFactory->FindAllAsyncDeviceClass( DeviceClass_AudioRender, operation.GetAddressOf() ); ThrowIfFailed( hr ); operation->put_Completed( callback.Get() ); (void)WaitForSingleObjectEx( findCompleted.Get(), INFINITE, FALSE ); ComPtr> devices; operation->GetResults( devices.GetAddressOf() ); unsigned int count = 0; hr = devices->get_Size( &count ); ThrowIfFailed( hr ); if ( !count ) return list; for( unsigned int j = 0; j < count; ++j ) { ComPtr deviceInfo; hr = devices->GetAt( j, deviceInfo.GetAddressOf() ); if ( SUCCEEDED(hr) ) { HString id; deviceInfo->get_Id( id.GetAddressOf() ); HString name; deviceInfo->get_Name( name.GetAddressOf() ); RendererDetail device; device.deviceId = id.GetRawBuffer( nullptr ); device.description = name.GetRawBuffer( nullptr ); list.emplace_back( device ); } } #endif #else // _WIN32_WINNT < _WIN32_WINNT_WIN8 // Enumerating with XAudio 2.7 ComPtr pXAudio2; HRESULT hr = XAudio2Create( pXAudio2.GetAddressOf() ); if ( FAILED(hr) ) { DebugTrace( "ERROR: XAudio 2.7 not found (have you called CoInitialize?)\n"); throw std::exception( "XAudio2Create" ); } UINT32 count = 0; hr = pXAudio2->GetDeviceCount( &count ); ThrowIfFailed(hr); if ( !count ) return list; list.reserve( count ); for( UINT32 j = 0; j < count; ++j ) { XAUDIO2_DEVICE_DETAILS details; hr = pXAudio2->GetDeviceDetails( j, &details ); if ( SUCCEEDED(hr) ) { RendererDetail device; device.deviceId = details.DeviceID; device.description = details.DisplayName; list.emplace_back( device ); } } #endif return list; }