mirror of
https://github.com/AxioDL/boo.git
synced 2025-05-15 03:41:23 +00:00
Support for runtime changing of audio output endpoint under WASAPI
This commit is contained in:
parent
faafbb2d3f
commit
521b490d0f
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -16,6 +16,7 @@ class AudioVoice : public IAudioVoice
|
||||
{
|
||||
friend class BaseAudioVoiceEngine;
|
||||
friend class AudioSubmix;
|
||||
friend struct WASAPIAudioVoiceEngine;
|
||||
|
||||
protected:
|
||||
/* Mixer-engine relationships */
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user