Support for runtime changing of audio output endpoint under WASAPI

This commit is contained in:
Jack Andersen 2016-05-31 18:53:57 -10:00
parent faafbb2d3f
commit 521b490d0f
5 changed files with 218 additions and 54 deletions

View File

@ -58,6 +58,9 @@ struct IAudioSubmixCallback
const ChannelMap& chanMap, double sampleRate) const=0; const ChannelMap& chanMap, double sampleRate) const=0;
virtual void applyEffect(float* audio, size_t frameCount, virtual void applyEffect(float* audio, size_t frameCount,
const ChannelMap& chanMap, double sampleRate) const=0; const ChannelMap& chanMap, double sampleRate) const=0;
/** Notify of output sample rate changes (for instance, changing the default audio device on Windows) */
virtual void resetOutputSampleRate(double sampleRate)=0;
}; };
} }

View File

@ -131,6 +131,16 @@ void AudioSubmix::_unbindFrom(std::list<AudioSubmix*>::iterator it)
m_activeSubmixes.erase(it); m_activeSubmixes.erase(it);
} }
void AudioSubmix::_resetOutputSampleRate()
{
for (AudioVoice* vox : m_activeVoices)
vox->_resetSampleRate(vox->m_sampleRateIn);
for (AudioSubmix* smx : m_activeSubmixes)
smx->_resetOutputSampleRate();
if (m_cb)
m_cb->resetOutputSampleRate(m_parent.mixInfo().m_sampleRate);
}
std::unique_ptr<IAudioVoice> AudioSubmix::allocateNewMonoVoice(double sampleRate, std::unique_ptr<IAudioVoice> AudioSubmix::allocateNewMonoVoice(double sampleRate,
IAudioVoiceCallback* cb, IAudioVoiceCallback* cb,
bool dynamicPitch) bool dynamicPitch)

View File

@ -14,6 +14,7 @@ class AudioVoice;
class AudioSubmix : public IAudioSubmix, public IAudioMix class AudioSubmix : public IAudioSubmix, public IAudioMix
{ {
friend class BaseAudioVoiceEngine; friend class BaseAudioVoiceEngine;
friend struct WASAPIAudioVoiceEngine;
/* Mixer-engine relationships */ /* Mixer-engine relationships */
BaseAudioVoiceEngine& m_root; BaseAudioVoiceEngine& m_root;
@ -48,6 +49,8 @@ class AudioSubmix : public IAudioSubmix, public IAudioMix
void _unbindFrom(std::list<AudioVoice*>::iterator it); void _unbindFrom(std::list<AudioVoice*>::iterator it);
void _unbindFrom(std::list<AudioSubmix*>::iterator it); void _unbindFrom(std::list<AudioSubmix*>::iterator it);
void _resetOutputSampleRate();
public: public:
~AudioSubmix(); ~AudioSubmix();
AudioSubmix(BaseAudioVoiceEngine& root, IAudioMix& parent, IAudioSubmixCallback* cb); AudioSubmix(BaseAudioVoiceEngine& root, IAudioMix& parent, IAudioSubmixCallback* cb);

View File

@ -16,6 +16,7 @@ class AudioVoice : public IAudioVoice
{ {
friend class BaseAudioVoiceEngine; friend class BaseAudioVoiceEngine;
friend class AudioSubmix; friend class AudioSubmix;
friend struct WASAPIAudioVoiceEngine;
protected: protected:
/* Mixer-engine relationships */ /* Mixer-engine relationships */

View File

@ -17,25 +17,112 @@ namespace boo
{ {
static logvisor::Module Log("boo::WASAPI"); static logvisor::Module Log("boo::WASAPI");
#define SAFE_RELEASE(punk) \
if ((punk) != NULL) \
{ (punk)->Release(); (punk) = NULL; }
struct WASAPIAudioVoiceEngine : BaseAudioVoiceEngine struct WASAPIAudioVoiceEngine : BaseAudioVoiceEngine
{ {
ComPtr<IMMDeviceEnumerator> m_enumerator;
ComPtr<IMMDevice> m_device; ComPtr<IMMDevice> m_device;
ComPtr<IAudioClient> m_audClient; ComPtr<IAudioClient> m_audClient;
ComPtr<IAudioRenderClient> m_renderClient; ComPtr<IAudioRenderClient> m_renderClient;
WASAPIAudioVoiceEngine() struct NotificationClient : public IMMNotificationClient
{ {
/* Enumerate default audio device */ WASAPIAudioVoiceEngine& m_parent;
ComPtr<IMMDeviceEnumerator> pEnumerator;
if (FAILED(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, LONG _cRef;
CLSCTX_ALL, IID_IMMDeviceEnumerator, IMMDeviceEnumerator *_pEnumerator;
&pEnumerator)))
NotificationClient(WASAPIAudioVoiceEngine& parent)
: m_parent(parent),
_cRef(1),
_pEnumerator(nullptr)
{}
~NotificationClient()
{ {
Log.report(logvisor::Fatal, L"unable to create MMDeviceEnumerator instance"); SAFE_RELEASE(_pEnumerator)
return;
} }
if (FAILED(pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &m_device)))
// IUnknown methods -- AddRef, Release, and QueryInterface
ULONG STDMETHODCALLTYPE AddRef()
{
return InterlockedIncrement(&_cRef);
}
ULONG STDMETHODCALLTYPE Release()
{
ULONG ulRef = InterlockedDecrement(&_cRef);
if (0 == ulRef)
{
delete this;
}
return ulRef;
}
HRESULT STDMETHODCALLTYPE QueryInterface(
REFIID riid, VOID **ppvInterface)
{
if (IID_IUnknown == riid)
{
AddRef();
*ppvInterface = (IUnknown*)this;
}
else if (__uuidof(IMMNotificationClient) == riid)
{
AddRef();
*ppvInterface = (IMMNotificationClient*)this;
}
else
{
*ppvInterface = NULL;
return E_NOINTERFACE;
}
return S_OK;
}
// Callback methods for device-event notifications.
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(
EDataFlow flow, ERole role,
LPCWSTR pwstrDeviceId)
{
m_parent.m_rebuild = true;
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;
}
} m_notificationClient;
void _buildAudioRenderClient()
{
if (FAILED(m_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &m_device)))
{ {
Log.report(logvisor::Fatal, L"unable to obtain default audio device"); Log.report(logvisor::Fatal, L"unable to obtain default audio device");
m_device.Reset(); m_device.Reset();
@ -198,60 +285,120 @@ struct WASAPIAudioVoiceEngine : BaseAudioVoiceEngine
} }
} }
WASAPIAudioVoiceEngine()
: m_notificationClient(*this)
{
/* Enumerate default audio device */
if (FAILED(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr,
CLSCTX_ALL, IID_IMMDeviceEnumerator,
&m_enumerator)))
{
Log.report(logvisor::Fatal, L"unable to create MMDeviceEnumerator instance");
return;
}
if (FAILED(m_enumerator->RegisterEndpointNotificationCallback(&m_notificationClient)))
{
Log.report(logvisor::Fatal, L"unable to register multimedia event callback");
m_device.Reset();
return;
}
_buildAudioRenderClient();
}
bool m_started = false; bool m_started = false;
bool m_rebuild = false;
void _rebuildAudioRenderClient()
{
soxr_datatype_t oldFmt = m_mixInfo.m_sampleFormat;
_buildAudioRenderClient();
m_rebuild = false;
m_started = false;
if (m_mixInfo.m_sampleFormat != oldFmt)
Log.report(logvisor::Fatal, L"audio device sample format changed, boo doesn't support this!!");
for (AudioVoice* vox : m_activeVoices)
vox->_resetSampleRate(vox->m_sampleRateIn);
for (AudioSubmix* smx : m_activeSubmixes)
smx->_resetOutputSampleRate();
}
void pumpAndMixVoices() void pumpAndMixVoices()
{ {
UINT32 numFramesPadding; int attempt = 0;
if (FAILED(m_audClient->GetCurrentPadding(&numFramesPadding))) while (true)
{ {
Log.report(logvisor::Fatal, L"unable to get available buffer frames"); if (attempt >= 10)
return; Log.report(logvisor::Fatal, L"unable to setup AudioRenderClient");
}
size_t frames = m_mixInfo.m_periodFrames - numFramesPadding; if (m_rebuild)
if (frames <= 0) _rebuildAudioRenderClient();
return;
BYTE* bufOut; HRESULT res;
if (FAILED(m_renderClient->GetBuffer(frames, &bufOut))) if (!m_started)
{
Log.report(logvisor::Fatal, L"unable to map audio buffer");
return;
}
DWORD flags = 0;
switch (m_mixInfo.m_sampleFormat)
{
case SOXR_INT16_I:
_pumpAndMixVoices(frames, reinterpret_cast<int16_t*>(bufOut));
break;
case SOXR_INT32_I:
_pumpAndMixVoices(frames, reinterpret_cast<int32_t*>(bufOut));
break;
case SOXR_FLOAT32_I:
_pumpAndMixVoices(frames, reinterpret_cast<float*>(bufOut));
break;
default:
flags = AUDCLNT_BUFFERFLAGS_SILENT;
break;
}
if (FAILED(m_renderClient->ReleaseBuffer(frames, flags)))
{
Log.report(logvisor::Fatal, L"unable to unmap audio buffer");
return;
}
if (!m_started)
{
if (FAILED(m_audClient->Start()))
{ {
Log.report(logvisor::Fatal, L"unable to start audio client"); res = m_audClient->Start();
m_device.Reset(); if (FAILED(res))
return; {
m_rebuild = true;
++attempt;
continue;
}
m_started = true;
} }
m_started = true;
UINT32 numFramesPadding;
res = m_audClient->GetCurrentPadding(&numFramesPadding);
if (FAILED(res))
{
m_rebuild = true;
++attempt;
continue;
}
size_t frames = m_mixInfo.m_periodFrames - numFramesPadding;
if (frames <= 0)
return;
BYTE* bufOut;
res = m_renderClient->GetBuffer(frames, &bufOut);
if (FAILED(res))
{
m_rebuild = true;
++attempt;
continue;
}
DWORD flags = 0;
switch (m_mixInfo.m_sampleFormat)
{
case SOXR_INT16_I:
_pumpAndMixVoices(frames, reinterpret_cast<int16_t*>(bufOut));
break;
case SOXR_INT32_I:
_pumpAndMixVoices(frames, reinterpret_cast<int32_t*>(bufOut));
break;
case SOXR_FLOAT32_I:
_pumpAndMixVoices(frames, reinterpret_cast<float*>(bufOut));
break;
default:
flags = AUDCLNT_BUFFERFLAGS_SILENT;
break;
}
res = m_renderClient->ReleaseBuffer(frames, flags);
if (FAILED(res))
{
m_rebuild = true;
++attempt;
continue;
}
break;
} }
} }