From 290d40641d5169c6643446f7d4999bc64f0930c7 Mon Sep 17 00:00:00 2001 From: Jack Andersen Date: Wed, 23 Mar 2016 15:50:34 -1000 Subject: [PATCH] WASAPI VoiceEngine implementation --- lib/audiodev/AudioVoiceEngine.cpp | 6 - lib/audiodev/AudioVoiceEngine.hpp | 2 - lib/audiodev/WASAPI.cpp | 393 +++++++++++++++--------------- 3 files changed, 191 insertions(+), 210 deletions(-) diff --git a/lib/audiodev/AudioVoiceEngine.cpp b/lib/audiodev/AudioVoiceEngine.cpp index ebeb762..dcef01c 100644 --- a/lib/audiodev/AudioVoiceEngine.cpp +++ b/lib/audiodev/AudioVoiceEngine.cpp @@ -28,12 +28,6 @@ void BaseAudioVoiceEngine::_pumpAndMixVoices(size_t frames, float* dataOut) vox->pumpAndMix(m_mixInfo, frames, dataOut); } -BaseAudioVoiceEngine::BaseAudioVoiceEngine - (const std::function& getEngineMixInfo) -{ - m_mixInfo = getEngineMixInfo(); -} - std::unique_ptr BaseAudioVoiceEngine::allocateNewMonoVoice(double sampleRate, IAudioVoiceCallback* cb, diff --git a/lib/audiodev/AudioVoiceEngine.hpp b/lib/audiodev/AudioVoiceEngine.hpp index 57430a1..884d76b 100644 --- a/lib/audiodev/AudioVoiceEngine.hpp +++ b/lib/audiodev/AudioVoiceEngine.hpp @@ -31,8 +31,6 @@ protected: void _pumpAndMixVoices(size_t frames, float* dataOut); public: - BaseAudioVoiceEngine(const std::function& getEngineMixInfo); - std::unique_ptr allocateNewMonoVoice(double sampleRate, IAudioVoiceCallback* cb, bool dynamicPitch=false); diff --git a/lib/audiodev/WASAPI.cpp b/lib/audiodev/WASAPI.cpp index 3d8fc9d..6c05b61 100644 --- a/lib/audiodev/WASAPI.cpp +++ b/lib/audiodev/WASAPI.cpp @@ -1,5 +1,5 @@ #include "../win/Win32Common.hpp" -#include "boo/audiodev/IAudioVoiceAllocator.hpp" +#include "AudioVoiceEngine.hpp" #include "logvisor/logvisor.hpp" #include @@ -14,177 +14,16 @@ namespace boo { static logvisor::Module Log("boo::WASAPI"); -struct WASAPIAudioVoice : IAudioVoice -{ - struct WASAPIAudioVoiceAllocator& m_parent; - std::list::iterator m_parentIt; - - ChannelMap m_map; - IAudioVoiceCallback* m_cb; - ComPtr m_audClient; - ComPtr 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 +struct WASAPIAudioVoiceEngine : BaseAudioVoiceEngine { ComPtr m_device; - AudioChannelSet m_maxSet = AudioChannelSet::Unknown; - std::list m_allocatedVoices; + ComPtr m_audClient; + ComPtr m_renderClient; - WASAPIAudioVoiceAllocator() + WASAPIAudioVoiceEngine() { + /* Enumerate default audio device */ ComPtr pEnumerator; - if (FAILED(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, &pEnumerator))) @@ -196,79 +35,229 @@ struct WASAPIAudioVoiceAllocator : IAudioVoiceAllocator if (FAILED(pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &m_device))) { Log.report(logvisor::Fatal, L"unable to obtain default audio device"); + m_device.Reset(); return; } - ComPtr pAudioClient; - if (FAILED(m_device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, &pAudioClient))) + if (FAILED(m_device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, &m_audClient))) { Log.report(logvisor::Fatal, L"unable to create audio client from device"); + m_device.Reset(); return; } 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"); + m_device.Reset(); return; } + /* Get channel information */ 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)) { - 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)) { - 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)) { - 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); + + 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; - AudioChannelSet getAvailableSet() + void pumpAndMixVoices() { - return m_maxSet; - } + UINT32 numFramesPadding; + if (FAILED(m_audClient->GetCurrentPadding(&numFramesPadding))) + { + Log.report(logvisor::Fatal, L"unable to get available buffer frames"); + return; + } - std::unique_ptr allocateNewVoice(AudioChannelSet layoutOut, - unsigned sampleRate, - IAudioVoiceCallback* cb) - { - WASAPIAudioVoice* newVoice = new WASAPIAudioVoice(*this, m_device.Get(), layoutOut, sampleRate, cb); - newVoice->m_parentIt = m_allocatedVoices.insert(m_allocatedVoices.end(), newVoice); - std::unique_ptr ret(newVoice); - if (!newVoice->m_audClient) - return {}; - return ret; - } + size_t frames = m_mixInfo.m_periodFrames - numFramesPadding; + if (frames <= 0) + return; - void pumpVoices() - { - for (WASAPIAudioVoice* vox : m_allocatedVoices) - vox->pump(); + BYTE* bufOut; + if (FAILED(m_renderClient->GetBuffer(frames, &bufOut))) + { + 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(bufOut)); + break; + case SOXR_INT32_I: + _pumpAndMixVoices(frames, reinterpret_cast(bufOut)); + break; + case SOXR_FLOAT32_I: + _pumpAndMixVoices(frames, reinterpret_cast(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; + } } }; -WASAPIAudioVoice::~WASAPIAudioVoice() +std::unique_ptr NewAudioVoiceEngine() { - m_parent.m_allocatedVoices.erase(m_parentIt); -} - -std::unique_ptr NewAudioVoiceAllocator() -{ - return std::make_unique(); + std::unique_ptr ret = std::make_unique(); + if (!static_cast(*ret).m_device) + return {}; + return ret; } }