WASAPI VoiceEngine implementation

This commit is contained in:
Jack Andersen 2016-03-23 15:50:34 -10:00
parent 1eb46301c0
commit 290d40641d
3 changed files with 191 additions and 210 deletions

View File

@ -28,12 +28,6 @@ void BaseAudioVoiceEngine::_pumpAndMixVoices(size_t frames, float* dataOut)
vox->pumpAndMix(m_mixInfo, frames, dataOut); vox->pumpAndMix(m_mixInfo, frames, dataOut);
} }
BaseAudioVoiceEngine::BaseAudioVoiceEngine
(const std::function<AudioVoiceEngineMixInfo()>& getEngineMixInfo)
{
m_mixInfo = getEngineMixInfo();
}
std::unique_ptr<IAudioVoice> std::unique_ptr<IAudioVoice>
BaseAudioVoiceEngine::allocateNewMonoVoice(double sampleRate, BaseAudioVoiceEngine::allocateNewMonoVoice(double sampleRate,
IAudioVoiceCallback* cb, IAudioVoiceCallback* cb,

View File

@ -31,8 +31,6 @@ protected:
void _pumpAndMixVoices(size_t frames, float* dataOut); void _pumpAndMixVoices(size_t frames, float* dataOut);
public: public:
BaseAudioVoiceEngine(const std::function<AudioVoiceEngineMixInfo()>& getEngineMixInfo);
std::unique_ptr<IAudioVoice> allocateNewMonoVoice(double sampleRate, std::unique_ptr<IAudioVoice> allocateNewMonoVoice(double sampleRate,
IAudioVoiceCallback* cb, IAudioVoiceCallback* cb,
bool dynamicPitch=false); bool dynamicPitch=false);

View File

@ -1,5 +1,5 @@
#include "../win/Win32Common.hpp" #include "../win/Win32Common.hpp"
#include "boo/audiodev/IAudioVoiceAllocator.hpp" #include "AudioVoiceEngine.hpp"
#include "logvisor/logvisor.hpp" #include "logvisor/logvisor.hpp"
#include <Mmdeviceapi.h> #include <Mmdeviceapi.h>
@ -14,177 +14,16 @@ namespace boo
{ {
static logvisor::Module Log("boo::WASAPI"); static logvisor::Module Log("boo::WASAPI");
struct WASAPIAudioVoice : IAudioVoice struct WASAPIAudioVoiceEngine : BaseAudioVoiceEngine
{
struct WASAPIAudioVoiceAllocator& m_parent;
std::list<WASAPIAudioVoice*>::iterator m_parentIt;
ChannelMap m_map;
IAudioVoiceCallback* m_cb;
ComPtr<IAudioClient> m_audClient;
ComPtr<IAudioRenderClient> m_renderClient;
UINT32 m_bufferFrames = 1024;
size_t m_frameSize;
const ChannelMap& channelMap() const {return m_map;}
WASAPIAudioVoice(WASAPIAudioVoiceAllocator& parent, IMMDevice* dev, AudioChannelSet set,
unsigned sampleRate, IAudioVoiceCallback* cb)
: m_parent(parent), m_cb(cb)
{
unsigned chCount = ChannelCount(set);
WAVEFORMATEX desc = {};
desc.wFormatTag = WAVE_FORMAT_PCM;
desc.nChannels = chCount;
desc.nSamplesPerSec = sampleRate;
desc.wBitsPerSample = 16;
desc.nBlockAlign = desc.nChannels * desc.wBitsPerSample / 8;
desc.nAvgBytesPerSec = desc.nSamplesPerSec * desc.nBlockAlign;
if (FAILED(dev->Activate(IID_IAudioClient, CLSCTX_ALL,
nullptr, &m_audClient)))
{
Log.report(logvisor::Fatal, "unable to create audio client");
return;
}
WAVEFORMATEX* works;
m_audClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &desc, &works);
HRESULT hr = m_audClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0,
1000000, 0, &desc, nullptr);
if (FAILED(hr))
{
Log.report(logvisor::Fatal, "unable to initialize audio client");
return;
}
if (FAILED(m_audClient->GetBufferSize(&m_bufferFrames)))
{
Log.report(logvisor::Fatal, "unable to obtain audio buffer size");
return;
}
if (FAILED(m_audClient->GetService(IID_IAudioRenderClient, &m_renderClient)))
{
Log.report(logvisor::Fatal, "unable to create audio render client");
return;
}
switch (chCount)
{
case 2:
m_map.m_channelCount = 2;
m_map.m_channels[0] = AudioChannel::FrontLeft;
m_map.m_channels[1] = AudioChannel::FrontRight;
break;
case 4:
m_map.m_channelCount = 4;
m_map.m_channels[0] = AudioChannel::FrontLeft;
m_map.m_channels[1] = AudioChannel::FrontRight;
m_map.m_channels[2] = AudioChannel::RearLeft;
m_map.m_channels[3] = AudioChannel::RearRight;
break;
case 5:
m_map.m_channelCount = 5;
m_map.m_channels[0] = AudioChannel::FrontLeft;
m_map.m_channels[1] = AudioChannel::FrontRight;
m_map.m_channels[2] = AudioChannel::FrontCenter;
m_map.m_channels[3] = AudioChannel::RearLeft;
m_map.m_channels[4] = AudioChannel::RearRight;
break;
case 6:
m_map.m_channelCount = 6;
m_map.m_channels[0] = AudioChannel::FrontLeft;
m_map.m_channels[1] = AudioChannel::FrontRight;
m_map.m_channels[2] = AudioChannel::FrontCenter;
m_map.m_channels[3] = AudioChannel::LFE;
m_map.m_channels[4] = AudioChannel::RearLeft;
m_map.m_channels[5] = AudioChannel::RearRight;
break;
case 8:
m_map.m_channelCount = 8;
m_map.m_channels[0] = AudioChannel::FrontLeft;
m_map.m_channels[1] = AudioChannel::FrontRight;
m_map.m_channels[2] = AudioChannel::FrontCenter;
m_map.m_channels[3] = AudioChannel::LFE;
m_map.m_channels[4] = AudioChannel::RearLeft;
m_map.m_channels[5] = AudioChannel::RearRight;
m_map.m_channels[6] = AudioChannel::SideLeft;
m_map.m_channels[7] = AudioChannel::SideRight;
break;
default:
Log.report(logvisor::Error, "unknown channel layout %u; using stereo", chCount);
m_map.m_channelCount = 2;
m_map.m_channels[0] = AudioChannel::FrontLeft;
m_map.m_channels[1] = AudioChannel::FrontRight;
break;
}
while (m_map.m_channelCount < chCount)
m_map.m_channels[m_map.m_channelCount++] = AudioChannel::Unknown;
m_frameSize = chCount * 2;
for (unsigned i=0 ; i<3 ; ++i)
m_cb->needsNextBuffer(*this, m_bufferFrames);
}
void bufferSampleData(const int16_t* data, size_t frames)
{
BYTE* dataOut;
if (FAILED(m_renderClient->GetBuffer(frames, &dataOut)))
{
Log.report(logvisor::Fatal, L"unable to obtain audio buffer");
return;
}
memcpy(dataOut, data, frames * m_frameSize);
if (FAILED(m_renderClient->ReleaseBuffer(frames, 0)))
{
Log.report(logvisor::Fatal, L"unable to release audio buffer");
return;
}
}
void start()
{
m_audClient->Start();
}
void stop()
{
m_audClient->Stop();
}
void pump()
{
UINT32 padding;
if (FAILED(m_audClient->GetCurrentPadding(&padding)))
{
Log.report(logvisor::Fatal, L"unable to obtain audio buffer padding");
return;
}
INT32 available = m_bufferFrames - padding;
m_cb->needsNextBuffer(*this, available);
}
~WASAPIAudioVoice();
};
struct WASAPIAudioVoiceAllocator : IAudioVoiceAllocator
{ {
ComPtr<IMMDevice> m_device; ComPtr<IMMDevice> m_device;
AudioChannelSet m_maxSet = AudioChannelSet::Unknown; ComPtr<IAudioClient> m_audClient;
std::list<WASAPIAudioVoice*> m_allocatedVoices; ComPtr<IAudioRenderClient> m_renderClient;
WASAPIAudioVoiceAllocator() WASAPIAudioVoiceEngine()
{ {
/* Enumerate default audio device */
ComPtr<IMMDeviceEnumerator> pEnumerator; ComPtr<IMMDeviceEnumerator> pEnumerator;
if (FAILED(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, if (FAILED(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr,
CLSCTX_ALL, IID_IMMDeviceEnumerator, CLSCTX_ALL, IID_IMMDeviceEnumerator,
&pEnumerator))) &pEnumerator)))
@ -196,79 +35,229 @@ struct WASAPIAudioVoiceAllocator : IAudioVoiceAllocator
if (FAILED(pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &m_device))) if (FAILED(pEnumerator->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();
return; return;
} }
ComPtr<IAudioClient> pAudioClient; if (FAILED(m_device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, &m_audClient)))
if (FAILED(m_device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, &pAudioClient)))
{ {
Log.report(logvisor::Fatal, L"unable to create audio client from device"); Log.report(logvisor::Fatal, L"unable to create audio client from device");
m_device.Reset();
return; return;
} }
WAVEFORMATEXTENSIBLE* pwfx; WAVEFORMATEXTENSIBLE* pwfx;
if (FAILED(pAudioClient->GetMixFormat((WAVEFORMATEX**)&pwfx))) if (FAILED(m_audClient->GetMixFormat((WAVEFORMATEX**)&pwfx)))
{ {
Log.report(logvisor::Fatal, L"unable to obtain audio mix format from device"); Log.report(logvisor::Fatal, L"unable to obtain audio mix format from device");
m_device.Reset();
return; return;
} }
/* Get channel information */
if ((pwfx->dwChannelMask & (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)) == (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)) if ((pwfx->dwChannelMask & (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)) == (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT))
{ {
m_maxSet = AudioChannelSet::Stereo; m_mixInfo.m_channels = AudioChannelSet::Stereo;
if ((pwfx->dwChannelMask & (SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)) == (SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)) if ((pwfx->dwChannelMask & (SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)) == (SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT))
{ {
m_maxSet = AudioChannelSet::Quad; m_mixInfo.m_channels = AudioChannelSet::Quad;
if ((pwfx->dwChannelMask & (SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY)) == (SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY)) if ((pwfx->dwChannelMask & (SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY)) == (SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY))
{ {
m_maxSet = AudioChannelSet::Surround51; m_mixInfo.m_channels = AudioChannelSet::Surround51;
if ((pwfx->dwChannelMask & (SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)) == (SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)) if ((pwfx->dwChannelMask & (SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)) == (SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT))
{ {
m_maxSet = AudioChannelSet::Surround71; m_mixInfo.m_channels = AudioChannelSet::Surround71;
} }
} }
} }
} }
ChannelMap& chMapOut = m_mixInfo.m_channelMap;
switch (pwfx->Format.nChannels)
{
case 2:
chMapOut.m_channelCount = 2;
chMapOut.m_channels[0] = AudioChannel::FrontLeft;
chMapOut.m_channels[1] = AudioChannel::FrontRight;
break;
case 4:
chMapOut.m_channelCount = 4;
chMapOut.m_channels[0] = AudioChannel::FrontLeft;
chMapOut.m_channels[1] = AudioChannel::FrontRight;
chMapOut.m_channels[2] = AudioChannel::RearLeft;
chMapOut.m_channels[3] = AudioChannel::RearRight;
break;
case 5:
chMapOut.m_channelCount = 5;
chMapOut.m_channels[0] = AudioChannel::FrontLeft;
chMapOut.m_channels[1] = AudioChannel::FrontRight;
chMapOut.m_channels[2] = AudioChannel::FrontCenter;
chMapOut.m_channels[3] = AudioChannel::RearLeft;
chMapOut.m_channels[4] = AudioChannel::RearRight;
break;
case 6:
chMapOut.m_channelCount = 6;
chMapOut.m_channels[0] = AudioChannel::FrontLeft;
chMapOut.m_channels[1] = AudioChannel::FrontRight;
chMapOut.m_channels[2] = AudioChannel::FrontCenter;
chMapOut.m_channels[3] = AudioChannel::LFE;
chMapOut.m_channels[4] = AudioChannel::RearLeft;
chMapOut.m_channels[5] = AudioChannel::RearRight;
break;
case 8:
chMapOut.m_channelCount = 8;
chMapOut.m_channels[0] = AudioChannel::FrontLeft;
chMapOut.m_channels[1] = AudioChannel::FrontRight;
chMapOut.m_channels[2] = AudioChannel::FrontCenter;
chMapOut.m_channels[3] = AudioChannel::LFE;
chMapOut.m_channels[4] = AudioChannel::RearLeft;
chMapOut.m_channels[5] = AudioChannel::RearRight;
chMapOut.m_channels[6] = AudioChannel::SideLeft;
chMapOut.m_channels[7] = AudioChannel::SideRight;
break;
default:
Log.report(logvisor::Warning, "unknown channel layout %u; using stereo", pwfx->Format.nChannels);
chMapOut.m_channelCount = 2;
chMapOut.m_channels[0] = AudioChannel::FrontLeft;
chMapOut.m_channels[1] = AudioChannel::FrontRight;
break;
}
/* Initialize audio client */
if (FAILED(m_audClient->Initialize(
AUDCLNT_SHAREMODE_SHARED,
0,
1000000,
0,
(WAVEFORMATEX*)pwfx,
nullptr)))
{
Log.report(logvisor::Fatal, L"unable to initialize audio client");
m_device.Reset();
CoTaskMemFree(pwfx);
return;
}
m_mixInfo.m_sampleRate = pwfx->Format.nSamplesPerSec;
if (pwfx->Format.wFormatTag == WAVE_FORMAT_PCM ||
(pwfx->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && pwfx->SubFormat == KSDATAFORMAT_SUBTYPE_PCM))
{
if (pwfx->Format.wBitsPerSample == 16)
{
m_mixInfo.m_sampleFormat = SOXR_INT16_I;
m_mixInfo.m_bitsPerSample = 16;
}
else if (pwfx->Format.wBitsPerSample == 32)
{
m_mixInfo.m_sampleFormat = SOXR_INT32_I;
m_mixInfo.m_bitsPerSample = 32;
}
else
{
Log.report(logvisor::Fatal, L"unsupported bits-per-sample %d", pwfx->Format.wBitsPerSample);
m_device.Reset();
return;
}
}
else if (pwfx->Format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT ||
(pwfx->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && pwfx->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
{
if (pwfx->Format.wBitsPerSample == 32)
{
m_mixInfo.m_sampleFormat = SOXR_FLOAT32_I;
m_mixInfo.m_bitsPerSample = 32;
}
else
{
Log.report(logvisor::Fatal, L"unsupported floating-point bits-per-sample %d", pwfx->Format.wBitsPerSample);
m_device.Reset();
return;
}
}
CoTaskMemFree(pwfx); CoTaskMemFree(pwfx);
UINT32 bufferFrameCount;
if (FAILED(m_audClient->GetBufferSize(&bufferFrameCount)))
{
Log.report(logvisor::Fatal, L"unable to get audio buffer frame count");
m_device.Reset();
return;
}
m_mixInfo.m_periodFrames = bufferFrameCount;
if (FAILED(m_audClient->GetService(IID_IAudioRenderClient, &m_renderClient)))
{
Log.report(logvisor::Fatal, L"unable to create audio render client");
m_device.Reset();
return;
}
} }
~WASAPIAudioVoiceAllocator() bool m_started = false;
void pumpAndMixVoices()
{ {
UINT32 numFramesPadding;
if (FAILED(m_audClient->GetCurrentPadding(&numFramesPadding)))
{
Log.report(logvisor::Fatal, L"unable to get available buffer frames");
return;
} }
AudioChannelSet getAvailableSet() size_t frames = m_mixInfo.m_periodFrames - numFramesPadding;
if (frames <= 0)
return;
BYTE* bufOut;
if (FAILED(m_renderClient->GetBuffer(frames, &bufOut)))
{ {
return m_maxSet; Log.report(logvisor::Fatal, L"unable to map audio buffer");
return;
} }
std::unique_ptr<IAudioVoice> allocateNewVoice(AudioChannelSet layoutOut, DWORD flags = 0;
unsigned sampleRate, switch (m_mixInfo.m_sampleFormat)
IAudioVoiceCallback* cb)
{ {
WASAPIAudioVoice* newVoice = new WASAPIAudioVoice(*this, m_device.Get(), layoutOut, sampleRate, cb); case SOXR_INT16_I:
newVoice->m_parentIt = m_allocatedVoices.insert(m_allocatedVoices.end(), newVoice); _pumpAndMixVoices(frames, reinterpret_cast<int16_t*>(bufOut));
std::unique_ptr<IAudioVoice> ret(newVoice); break;
if (!newVoice->m_audClient) 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");
m_device.Reset();
return;
}
m_started = true;
}
}
};
std::unique_ptr<IAudioVoiceEngine> NewAudioVoiceEngine()
{
std::unique_ptr<IAudioVoiceEngine> ret = std::make_unique<WASAPIAudioVoiceEngine>();
if (!static_cast<WASAPIAudioVoiceEngine&>(*ret).m_device)
return {}; return {};
return ret; return ret;
} }
void pumpVoices()
{
for (WASAPIAudioVoice* vox : m_allocatedVoices)
vox->pump();
}
};
WASAPIAudioVoice::~WASAPIAudioVoice()
{
m_parent.m_allocatedVoices.erase(m_parentIt);
}
std::unique_ptr<IAudioVoiceAllocator> NewAudioVoiceAllocator()
{
return std::make_unique<WASAPIAudioVoiceAllocator>();
}
} }