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;
virtual void applyEffect(float* audio, size_t frameCount,
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);
}
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,
IAudioVoiceCallback* cb,
bool dynamicPitch)

View File

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

View File

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

View File

@ -17,25 +17,112 @@ namespace boo
{
static logvisor::Module Log("boo::WASAPI");
#define SAFE_RELEASE(punk) \
if ((punk) != NULL) \
{ (punk)->Release(); (punk) = NULL; }
struct WASAPIAudioVoiceEngine : BaseAudioVoiceEngine
{
ComPtr<IMMDeviceEnumerator> m_enumerator;
ComPtr<IMMDevice> m_device;
ComPtr<IAudioClient> m_audClient;
ComPtr<IAudioRenderClient> m_renderClient;
WASAPIAudioVoiceEngine()
struct NotificationClient : public IMMNotificationClient
{
/* Enumerate default audio device */
ComPtr<IMMDeviceEnumerator> pEnumerator;
if (FAILED(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr,
CLSCTX_ALL, IID_IMMDeviceEnumerator,
&pEnumerator)))
WASAPIAudioVoiceEngine& m_parent;
LONG _cRef;
IMMDeviceEnumerator *_pEnumerator;
NotificationClient(WASAPIAudioVoiceEngine& parent)
: m_parent(parent),
_cRef(1),
_pEnumerator(nullptr)
{}
~NotificationClient()
{
Log.report(logvisor::Fatal, L"unable to create MMDeviceEnumerator instance");
return;
SAFE_RELEASE(_pEnumerator)
}
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");
m_device.Reset();
@ -198,15 +285,79 @@ 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_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()
{
UINT32 numFramesPadding;
if (FAILED(m_audClient->GetCurrentPadding(&numFramesPadding)))
int attempt = 0;
while (true)
{
Log.report(logvisor::Fatal, L"unable to get available buffer frames");
return;
if (attempt >= 10)
Log.report(logvisor::Fatal, L"unable to setup AudioRenderClient");
if (m_rebuild)
_rebuildAudioRenderClient();
HRESULT res;
if (!m_started)
{
res = m_audClient->Start();
if (FAILED(res))
{
m_rebuild = true;
++attempt;
continue;
}
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;
@ -214,10 +365,12 @@ struct WASAPIAudioVoiceEngine : BaseAudioVoiceEngine
return;
BYTE* bufOut;
if (FAILED(m_renderClient->GetBuffer(frames, &bufOut)))
res = m_renderClient->GetBuffer(frames, &bufOut);
if (FAILED(res))
{
Log.report(logvisor::Fatal, L"unable to map audio buffer");
return;
m_rebuild = true;
++attempt;
continue;
}
DWORD flags = 0;
@ -237,21 +390,15 @@ struct WASAPIAudioVoiceEngine : BaseAudioVoiceEngine
break;
}
if (FAILED(m_renderClient->ReleaseBuffer(frames, flags)))
res = m_renderClient->ReleaseBuffer(frames, flags);
if (FAILED(res))
{
Log.report(logvisor::Fatal, L"unable to unmap audio buffer");
return;
m_rebuild = true;
++attempt;
continue;
}
if (!m_started)
{
if (FAILED(m_audClient->Start()))
{
Log.report(logvisor::Fatal, L"unable to start audio client");
m_device.Reset();
return;
}
m_started = true;
break;
}
}