1735 lines
52 KiB
C++
1735 lines
52 KiB
C++
|
//--------------------------------------------------------------------------------------
|
||
|
// 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 <list>
|
||
|
#include <unordered_map>
|
||
|
|
||
|
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<IVoiceNotify*>( 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<const WAVEFORMATEXTENSIBLE*>( 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<const ADPCMWAVEFORMAT*>( 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<const XMA2WAVEFORMATEX*>( 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<IXAudio2> 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<IVoiceNotify*> notifylist_t;
|
||
|
typedef std::list<std::pair<unsigned int, IXAudio2SourceVoice*>> oneshotlist_t;
|
||
|
typedef std::unordered_multimap<unsigned int, IXAudio2SourceVoice*> voicepool_t;
|
||
|
|
||
|
AUDIO_STREAM_CATEGORY mCategory;
|
||
|
ComPtr<IUnknown> mReverbEffect;
|
||
|
ComPtr<IUnknown> 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<UINT32>( release );
|
||
|
params.Loudness = static_cast<UINT32>( 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<WAVEFORMATEX*>( 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<const ADPCMWAVEFORMAT*>( 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<WORD>( 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 <phoneaudioclient.h>
|
||
|
#elif defined(_XBOX_ONE)
|
||
|
#include <Windows.Media.Devices.h>
|
||
|
#include <wrl.h>
|
||
|
#elif (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
|
||
|
#pragma comment(lib,"runtimeobject.lib")
|
||
|
#include <Windows.Devices.Enumeration.h>
|
||
|
#include <wrl.h>
|
||
|
#endif
|
||
|
|
||
|
std::vector<AudioEngine::RendererDetail> AudioEngine::GetRendererDetails()
|
||
|
{
|
||
|
std::vector<RendererDetail> 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<IMediaDeviceStatics> 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<IDeviceInformationStatics> 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<IAsyncOperationCompletedHandler<DeviceInformationCollection*>>(
|
||
|
[&findCompleted,list]( IAsyncOperation<DeviceInformationCollection*>* aDevices, AsyncStatus status ) -> HRESULT
|
||
|
{
|
||
|
UNREFERENCED_PARAMETER(aDevices);
|
||
|
UNREFERENCED_PARAMETER(status);
|
||
|
SetEvent( findCompleted.Get() );
|
||
|
return S_OK;
|
||
|
});
|
||
|
|
||
|
ComPtr<IAsyncOperation<DeviceInformationCollection*>> operation;
|
||
|
hr = diFactory->FindAllAsyncDeviceClass( DeviceClass_AudioRender, operation.GetAddressOf() );
|
||
|
ThrowIfFailed( hr );
|
||
|
|
||
|
operation->put_Completed( callback.Get() );
|
||
|
|
||
|
(void)WaitForSingleObjectEx( findCompleted.Get(), INFINITE, FALSE );
|
||
|
|
||
|
ComPtr<IVectorView<DeviceInformation*>> 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<IDeviceInformation> 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<IXAudio2> 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;
|
||
|
}
|