From a958ca170d8ae7024055ebba1ffc52d55297642d Mon Sep 17 00:00:00 2001 From: kode54 Date: Sat, 4 Aug 2012 22:15:46 +0000 Subject: [PATCH] Added device change notifier to XAudio2 outputs git-svn-id: https://svn.code.sf.net/p/vbam/code/trunk@1108 a31d4220-a93d-0410-bf67-fe4944624d44 --- src/win32/XAudio2.cpp | 155 +++++++++++++++++++++++++++++++++++++++++- src/wx/xaudio2.cpp | 155 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 308 insertions(+), 2 deletions(-) diff --git a/src/win32/XAudio2.cpp b/src/win32/XAudio2.cpp index 3a048848..a2601726 100644 --- a/src/win32/XAudio2.cpp +++ b/src/win32/XAudio2.cpp @@ -12,11 +12,130 @@ // XAudio2 #include +// MMDevice API +#include +#include +#include + // Internals #include "../System.h" // for systemMessage() #include "../gba/Globals.h" +class XAudio2_Output; + +static void xaudio2_device_changed( XAudio2_Output * ); + +class XAudio2_Device_Notifier : public IMMNotificationClient +{ + volatile LONG registered; + IMMDeviceEnumerator *pEnumerator; + + std::wstring last_device; + + CRITICAL_SECTION lock; + std::vector instances; + +public: + XAudio2_Device_Notifier() : registered( 0 ) + { + InitializeCriticalSection( &lock ); + } + ~XAudio2_Device_Notifier() + { + DeleteCriticalSection( &lock ); + } + + ULONG STDMETHODCALLTYPE AddRef() + { + return 1; + } + + ULONG STDMETHODCALLTYPE Release() + { + return 1; + } + + HRESULT STDMETHODCALLTYPE QueryInterface( REFIID riid, VOID **ppvInterface ) + { + if (IID_IUnknown == riid) + { + *ppvInterface = (IUnknown*)this; + } + else if (__uuidof(IMMNotificationClient) == riid) + { + *ppvInterface = (IMMNotificationClient*)this; + } + else + { + *ppvInterface = NULL; + return E_NOINTERFACE; + } + return S_OK; + } + + HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged( EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId ) + { + if ( flow == eRender && last_device.compare( pwstrDeviceId ) != 0 ) + { + last_device = pwstrDeviceId; + + EnterCriticalSection( &lock ); + for ( auto it = instances.begin(); it < instances.end(); ++it ) + { + xaudio2_device_changed( *it ); + } + LeaveCriticalSection( &lock ); + } + + return S_OK; + } + + HRESULT STDMETHODCALLTYPE OnDeviceAdded( LPCWSTR pwstrDeviceId ) { return S_OK; } + HRESULT STDMETHODCALLTYPE OnDeviceRemoved( LPCWSTR pwstrDeviceId ) { return S_OK; } + HRESULT STDMETHODCALLTYPE OnDeviceStateChanged( LPCWSTR pwstrDeviceId, DWORD dwNewState ) { return S_OK; } + HRESULT STDMETHODCALLTYPE OnPropertyValueChanged( LPCWSTR pwstrDeviceId, const PROPERTYKEY key ) { return S_OK; } + + void do_register(XAudio2_Output * p_instance) + { + if ( InterlockedIncrement( ®istered ) == 1 ) + { + pEnumerator = NULL; + HRESULT hr = CoCreateInstance( __uuidof( MMDeviceEnumerator ), NULL, CLSCTX_INPROC_SERVER, __uuidof( IMMDeviceEnumerator ), ( void** ) &pEnumerator ); + if ( SUCCEEDED( hr ) ) + { + pEnumerator->RegisterEndpointNotificationCallback( this ); + registered = true; + } + } + + EnterCriticalSection( &lock ); + instances.push_back( p_instance ); + LeaveCriticalSection( &lock ); + } + + void do_unregister( XAudio2_Output * p_instance ) + { + if ( InterlockedDecrement( ®istered ) == 0 ) + { + pEnumerator->UnregisterEndpointNotificationCallback( this ); + pEnumerator->Release(); + registered = false; + } + + EnterCriticalSection( &lock ); + for ( auto it = instances.begin(); it < instances.end(); ++it ) + { + if ( *it == p_instance ) + { + instances.erase( it ); + break; + } + } + LeaveCriticalSection( &lock ); + } +} g_notifier; + // Synchronization Event class XAudio2_BufferNotify : public IXAudio2VoiceCallback { @@ -68,6 +187,8 @@ public: void pause(); void resume(); void reset(); + void close(); + void device_change(); // Configuration Changes void setThrottle( unsigned short throttle ); @@ -82,6 +203,8 @@ private: int currentBuffer; int soundBufferLen; + volatile bool device_changed; + IXAudio2 *xaud; IXAudio2MasteringVoice *mVoice; // listener IXAudio2SourceVoice *sVoice; // sound source @@ -101,16 +224,25 @@ XAudio2_Output::XAudio2_Output() bufferCount = theApp.xa2BufferCount; buffers = NULL; currentBuffer = 0; + device_changed = false; xaud = NULL; mVoice = NULL; sVoice = NULL; ZeroMemory( &buf, sizeof( buf ) ); ZeroMemory( &vState, sizeof( vState ) ); + + g_notifier.do_register( this ); } XAudio2_Output::~XAudio2_Output() +{ + g_notifier.do_unregister( this ); + close(); +} + +void XAudio2_Output::close() { initialized = false; @@ -120,6 +252,7 @@ XAudio2_Output::~XAudio2_Output() ASSERT( hr == S_OK ); } sVoice->DestroyVoice(); + sVoice = NULL; } if( buffers ) { @@ -129,6 +262,7 @@ XAudio2_Output::~XAudio2_Output() if( mVoice ) { mVoice->DestroyVoice(); + mVoice = NULL; } if( xaud ) { @@ -137,6 +271,11 @@ XAudio2_Output::~XAudio2_Output() } } +void XAudio2_Output::device_change() +{ + device_changed = true; +} + bool XAudio2_Output::init(long sampleRate) { @@ -276,6 +415,8 @@ bool XAudio2_Output::init(long sampleRate) ASSERT( hr == S_OK ); playing = true; + currentBuffer = 0; + device_changed = false; initialized = true; return true; @@ -287,6 +428,11 @@ void XAudio2_Output::write(u16 * finalWave, int length) if( !initialized || failed ) return; while( true ) { + if ( device_changed ) { + close(); + if (!init(freq)) return; + } + sVoice->GetState( &vState ); ASSERT( vState.BuffersQueued <= bufferCount ); @@ -305,7 +451,9 @@ void XAudio2_Output::write(u16 * finalWave, int length) // the maximum number of buffers is currently queued if( synchronize && !speedup && !theApp.throttle ) { // wait for one buffer to finish playing - WaitForSingleObject( notify.hBufferEndEvent, INFINITE ); + if (WaitForSingleObject( notify.hBufferEndEvent, 10000 ) == WAIT_TIMEOUT) { + device_changed = true; + } } else { // drop current audio frame return; @@ -375,6 +523,11 @@ void XAudio2_Output::setThrottle( unsigned short throttle ) ASSERT( hr == S_OK ); } +void xaudio2_device_changed( XAudio2_Output * instance ) +{ + instance->device_change(); +} + SoundDriver *newXAudio2_Output() { return new XAudio2_Output(); diff --git a/src/wx/xaudio2.cpp b/src/wx/xaudio2.cpp index 940083c8..8b31ced9 100644 --- a/src/wx/xaudio2.cpp +++ b/src/wx/xaudio2.cpp @@ -10,11 +10,130 @@ // XAudio2 #include +// MMDevice API +#include +#include +#include + // Internals #include "../System.h" // for systemMessage() #include "../gba/Globals.h" +class XAudio2_Output; + +static void xaudio2_device_changed( XAudio2_Output * ); + +class XAudio2_Device_Notifier : public IMMNotificationClient +{ + volatile LONG registered; + IMMDeviceEnumerator *pEnumerator; + + std::wstring last_device; + + CRITICAL_SECTION lock; + std::vector instances; + +public: + XAudio2_Device_Notifier() : registered( 0 ) + { + InitializeCriticalSection( &lock ); + } + ~XAudio2_Device_Notifier() + { + DeleteCriticalSection( &lock ); + } + + ULONG STDMETHODCALLTYPE AddRef() + { + return 1; + } + + ULONG STDMETHODCALLTYPE Release() + { + return 1; + } + + HRESULT STDMETHODCALLTYPE QueryInterface( REFIID riid, VOID **ppvInterface ) + { + if (IID_IUnknown == riid) + { + *ppvInterface = (IUnknown*)this; + } + else if (__uuidof(IMMNotificationClient) == riid) + { + *ppvInterface = (IMMNotificationClient*)this; + } + else + { + *ppvInterface = NULL; + return E_NOINTERFACE; + } + return S_OK; + } + + HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged( EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId ) + { + if ( flow == eRender && last_device.compare( pwstrDeviceId ) != 0 ) + { + last_device = pwstrDeviceId; + + EnterCriticalSection( &lock ); + for ( auto it = instances.begin(); it < instances.end(); ++it ) + { + xaudio2_device_changed( *it ); + } + LeaveCriticalSection( &lock ); + } + + return S_OK; + } + + HRESULT STDMETHODCALLTYPE OnDeviceAdded( LPCWSTR pwstrDeviceId ) { return S_OK; } + HRESULT STDMETHODCALLTYPE OnDeviceRemoved( LPCWSTR pwstrDeviceId ) { return S_OK; } + HRESULT STDMETHODCALLTYPE OnDeviceStateChanged( LPCWSTR pwstrDeviceId, DWORD dwNewState ) { return S_OK; } + HRESULT STDMETHODCALLTYPE OnPropertyValueChanged( LPCWSTR pwstrDeviceId, const PROPERTYKEY key ) { return S_OK; } + + void do_register(sound_out_i_xaudio2 * p_instance) + { + if ( InterlockedIncrement( ®istered ) == 1 ) + { + pEnumerator = NULL; + HRESULT hr = CoCreateInstance( __uuidof( MMDeviceEnumerator ), NULL, CLSCTX_INPROC_SERVER, __uuidof( IMMDeviceEnumerator ), ( void** ) &pEnumerator ); + if ( SUCCEEDED( hr ) ) + { + pEnumerator->RegisterEndpointNotificationCallback( this ); + registered = true; + } + } + + EnterCriticalSection( &lock ); + instances.push_back( p_instance ); + LeaveCriticalSection( &lock ); + } + + void do_unregister( sound_out_i_xaudio2 * p_instance ) + { + if ( InterlockedDecrement( ®istered ) == 0 ) + { + pEnumerator->UnregisterEndpointNotificationCallback( this ); + pEnumerator->Release(); + registered = false; + } + + EnterCriticalSection( &lock ); + for ( auto it = instances.begin(); it < instances.end(); ++it ) + { + if ( *it == p_instance ) + { + instances.erase( it ); + break; + } + } + LeaveCriticalSection( &lock ); + } +} g_notifier; + // Synchronization Event class XAudio2_BufferNotify : public IXAudio2VoiceCallback { @@ -66,6 +185,8 @@ public: void pause(); void resume(); void reset(); + void close(); + void device_change(); // Configuration Changes void setThrottle( unsigned short throttle ); @@ -80,6 +201,8 @@ private: int currentBuffer; int soundBufferLen; + volatile bool device_changed; + IXAudio2 *xaud; IXAudio2MasteringVoice *mVoice; // listener IXAudio2SourceVoice *sVoice; // sound source @@ -99,16 +222,25 @@ XAudio2_Output::XAudio2_Output() bufferCount = gopts.audio_buffers; buffers = NULL; currentBuffer = 0; + device_changed = false; xaud = NULL; mVoice = NULL; sVoice = NULL; ZeroMemory( &buf, sizeof( buf ) ); ZeroMemory( &vState, sizeof( vState ) ); + + g_notifier.do_register( this ); } XAudio2_Output::~XAudio2_Output() +{ + g_notifier.do_unregister( this ); + close(); +} + +void XAudio2_Output::close() { initialized = false; @@ -118,6 +250,7 @@ XAudio2_Output::~XAudio2_Output() assert( hr == S_OK ); } sVoice->DestroyVoice(); + sVoice = NULL; } if( buffers ) { @@ -127,6 +260,7 @@ XAudio2_Output::~XAudio2_Output() if( mVoice ) { mVoice->DestroyVoice(); + mVoice = NULL; } if( xaud ) { @@ -135,6 +269,11 @@ XAudio2_Output::~XAudio2_Output() } } +void XAudio2_Output::device_change() +{ + device_changed = true; +} + static int XA2GetDev(IXAudio2 *); @@ -277,6 +416,8 @@ bool XAudio2_Output::init(long sampleRate) assert( hr == S_OK ); playing = true; + currentBuffer = 0; + device_changed = false; initialized = true; return true; @@ -288,6 +429,11 @@ void XAudio2_Output::write(u16 * finalWave, int length) if( !initialized || failed ) return; while( true ) { + if ( device_changed ) { + close(); + if (!init(freq)) return; + } + sVoice->GetState( &vState ); assert( vState.BuffersQueued <= bufferCount ); @@ -306,7 +452,9 @@ void XAudio2_Output::write(u16 * finalWave, int length) // the maximum number of buffers is currently queued if( synchronize && !speedup && !gopts.throttle ) { // wait for one buffer to finish playing - WaitForSingleObject( notify.hBufferEndEvent, INFINITE ); + if (WaitForSingleObject( notify.hBufferEndEvent, 10000 ) == WAIT_TIMEOUT) { + device_changed = true; + } } else { // drop current audio frame return; @@ -376,6 +524,11 @@ void XAudio2_Output::setThrottle( unsigned short throttle ) assert( hr == S_OK ); } +void xaudio2_device_changed( XAudio2_Output * instance ) +{ + instance->device_change(); +} + SoundDriver *newXAudio2_Output() { return new XAudio2_Output();