mirror of
https://github.com/AxioDL/boo.git
synced 2025-05-15 11:51:27 +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;
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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);
|
||||||
|
@ -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 */
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user