Implement AudioDeviceWASAPI

This commit is contained in:
Attila Uygun 2023-10-04 13:08:26 +02:00
parent 261e7f41d6
commit 2823aa3197
8 changed files with 272 additions and 27 deletions

View File

@ -184,6 +184,7 @@ config("warnings") {
"/wd4820", # padding added after data member. "/wd4820", # padding added after data member.
"/wd5045", # compiler will insert Spectre mitigation for memory load if "/wd5045", # compiler will insert Spectre mitigation for memory load if
# switch specified. # switch specified.
"/wd4355", # 'this': used in base member initializer list
# TODO: Not sure how I feel about these conversion warnings. # TODO: Not sure how I feel about these conversion warnings.
"/Wv:18", "/Wv:18",

View File

@ -21,7 +21,10 @@ source_set("audio") {
] ]
libs += [ "asound" ] libs += [ "asound" ]
} else if (target_os == "win") { } else if (target_os == "win") {
sources += [ "audio_device_null.h" ] sources += [
"audio_device_wasapi.cc",
"audio_device_wasapi.h",
]
} else if (target_os == "android") { } else if (target_os == "android") {
sources += [ sources += [
"audio_device_oboe.cc", "audio_device_oboe.cc",

View File

@ -1,23 +0,0 @@
#ifndef ENGINE_AUDIO_AUDIO_DEVICE_NULL_H
#define ENGINE_AUDIO_AUDIO_DEVICE_NULL_H
#include "engine/audio/audio_device.h"
namespace eng {
class AudioDeviceNull final : public AudioDevice {
public:
AudioDeviceNull() = default;
~AudioDeviceNull() final = default;
bool Initialize() final { return true; }
void Suspend() final {}
void Resume() final {}
size_t GetHardwareSampleRate() final { return 0; }
};
} // namespace eng
#endif // ENGINE_AUDIO_AUDIO_DEVICE_NULL_H

View File

@ -0,0 +1,210 @@
#include "engine/audio/audio_device_wasapi.h"
#include "base/log.h"
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioClient = __uuidof(IAudioClient);
const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
using namespace base;
namespace eng {
AudioDeviceWASAPI::AudioDeviceWASAPI(AudioDevice::Delegate* delegate)
: delegate_(delegate) {}
AudioDeviceWASAPI::~AudioDeviceWASAPI() {
LOG(0) << "Shutting down audio.";
TerminateAudioThread();
if (shutdown_event_)
CloseHandle(shutdown_event_);
if (ready_event_)
CloseHandle(ready_event_);
if (device_)
device_->Release();
if (audio_client_)
audio_client_->Release();
if (render_client_)
render_client_->Release();
if (device_enumerator_)
device_enumerator_->Release();
}
bool AudioDeviceWASAPI::Initialize() {
LOG(0) << "Initializing audio.";
HRESULT hr;
do {
hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
IID_IMMDeviceEnumerator, (void**)&device_enumerator_);
if (FAILED(hr)) {
LOG(0) << "Unable to instantiate device enumerator: " << hr;
break;
}
hr = device_enumerator_->GetDefaultAudioEndpoint(eRender, eConsole,
&device_);
if (FAILED(hr)) {
LOG(0) << "Unable to get default audio endpoint: " << hr;
break;
}
hr = device_->Activate(IID_IAudioClient, CLSCTX_ALL, NULL,
(void**)&audio_client_);
if (FAILED(hr)) {
LOG(0) << "Unable to activate audio client: " << hr;
break;
}
// Use float format.
WAVEFORMATEX* closest_match = nullptr;
WAVEFORMATEXTENSIBLE wfxex = {0};
wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
wfxex.dwChannelMask = KSAUDIO_SPEAKER_STEREO;
wfxex.Format.nChannels = 2;
wfxex.Format.nSamplesPerSec = 48000;
wfxex.Format.wBitsPerSample = 32;
wfxex.Samples.wValidBitsPerSample = 32;
wfxex.Format.nBlockAlign =
wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
wfxex.Format.nAvgBytesPerSec =
wfxex.Format.nBlockAlign * wfxex.Format.nSamplesPerSec;
wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
hr = audio_client_->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED,
&wfxex.Format, &closest_match);
if (FAILED(hr)) {
LOG(0) << "Unsupported sample format.";
break;
}
WAVEFORMATEX* format = closest_match ? closest_match : &wfxex.Format;
if ((format->wFormatTag != WAVE_FORMAT_IEEE_FLOAT &&
(format->wFormatTag != WAVE_FORMAT_EXTENSIBLE ||
reinterpret_cast<WAVEFORMATEXTENSIBLE*>(format)->SubFormat !=
KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) ||
format->nChannels != 2) {
LOG(0) << "Unsupported sample format.";
break;
}
sample_rate_ = format->nSamplesPerSec;
HRESULT hr = audio_client_->Initialize(
AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, 0, 0,
format, NULL);
if (closest_match)
CoTaskMemFree(closest_match);
if (FAILED(hr)) {
LOG(0) << "Unable to initialize audio client: " << hr;
break;
}
// Get the actual size of the allocated buffer.
hr = audio_client_->GetBufferSize(&buffer_size_);
if (FAILED(hr)) {
LOG(0) << "Unable to get audio client buffer size: " << hr;
break;
}
hr = audio_client_->GetService(IID_IAudioRenderClient,
(void**)&render_client_);
if (FAILED(hr)) {
LOG(0) << "Unable to get audio render client: " << hr;
break;
}
shutdown_event_ =
CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
if (shutdown_event_ == NULL) {
LOG(0) << "Unable to create shutdown event: " << hr;
break;
}
ready_event_ =
CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
if (ready_event_ == NULL) {
LOG(0) << "Unable to create samples ready event: " << hr;
break;
}
hr = audio_client_->SetEventHandle(ready_event_);
if (FAILED(hr)) {
LOG(0) << "Unable to set ready event: " << hr;
break;
}
StartAudioThread();
hr = audio_client_->Start();
if (FAILED(hr)) {
break;
}
return true;
} while (false);
return false;
}
void AudioDeviceWASAPI::Suspend() {
audio_client_->Stop();
}
void AudioDeviceWASAPI::Resume() {
audio_client_->Start();
}
size_t AudioDeviceWASAPI::GetHardwareSampleRate() {
return sample_rate_;
}
void AudioDeviceWASAPI::StartAudioThread() {
DCHECK(!audio_thread_.joinable());
LOG(0) << "Starting audio thread.";
audio_thread_ = std::thread(&AudioDeviceWASAPI::AudioThreadMain, this);
}
void AudioDeviceWASAPI::TerminateAudioThread() {
LOG(0) << "Terminating audio thread";
SetEvent(shutdown_event_);
audio_thread_.join();
}
void AudioDeviceWASAPI::AudioThreadMain() {
DCHECK(delegate_);
HANDLE wait_array[] = {shutdown_event_, ready_event_};
for (;;) {
switch (WaitForMultipleObjects(2, wait_array, FALSE, INFINITE)) {
case WAIT_OBJECT_0 + 0: // shutdown_event_
return;
case WAIT_OBJECT_0 + 1: { // ready_event_
UINT32 padding;
HRESULT hr = audio_client_->GetCurrentPadding(&padding);
if (SUCCEEDED(hr)) {
BYTE* pData;
UINT32 frames_available = buffer_size_ - padding;
hr = render_client_->GetBuffer(frames_available, &pData);
if (SUCCEEDED(hr)) {
delegate_->RenderAudio(reinterpret_cast<float*>(pData),
frames_available);
hr = render_client_->ReleaseBuffer(frames_available, 0);
if (!SUCCEEDED(hr))
DLOG(0) << "Unable to release buffer: " << hr;
} else {
DLOG(0) << "Unable to get buffer: " << hr;
}
}
} break;
}
}
}
} // namespace eng

View File

@ -0,0 +1,49 @@
#ifndef ENGINE_AUDIO_AUDIO_DEVICE_WASAPI_H
#define ENGINE_AUDIO_AUDIO_DEVICE_WASAPI_H
#include <thread>
#include <AudioClient.h>
#include <MMDeviceAPI.h>
#include "engine/audio/audio_device.h"
namespace eng {
class AudioDeviceWASAPI final : public AudioDevice {
public:
AudioDeviceWASAPI(AudioDevice::Delegate* delegate);
~AudioDeviceWASAPI() final;
bool Initialize() final;
void Suspend() final;
void Resume() final;
size_t GetHardwareSampleRate() final;
private:
IMMDevice* device_ = nullptr;
IAudioClient* audio_client_ = nullptr;
IAudioRenderClient* render_client_ = nullptr;
IMMDeviceEnumerator* device_enumerator_ = nullptr;
HANDLE shutdown_event_ = nullptr;
HANDLE ready_event_ = nullptr;
std::thread audio_thread_;
UINT32 buffer_size_ = 0;
size_t sample_rate_ = 0;
AudioDevice::Delegate* delegate_ = nullptr;
void StartAudioThread();
void TerminateAudioThread();
void AudioThreadMain();
};
} // namespace eng
#endif // ENGINE_AUDIO_AUDIO_DEVICE_WASAPI_H

View File

@ -12,7 +12,7 @@
#elif defined(__linux__) #elif defined(__linux__)
#include "engine/audio/audio_device_alsa.h" #include "engine/audio/audio_device_alsa.h"
#elif defined(_WIN32) #elif defined(_WIN32)
#include "engine/audio/audio_device_null.h" #include "engine/audio/audio_device_wasapi.h"
#endif #endif
using namespace base; using namespace base;
@ -26,8 +26,7 @@ AudioMixer::AudioMixer()
#elif defined(__linux__) #elif defined(__linux__)
audio_device_{std::make_unique<AudioDeviceAlsa>(this)} { audio_device_{std::make_unique<AudioDeviceAlsa>(this)} {
#elif defined(_WIN32) #elif defined(_WIN32)
// TODO: Implement AudioDeviceWindows audio_device_{std::make_unique<AudioDeviceWASAPI>(this)} {
audio_device_{std::make_unique<AudioDeviceNull>()} {
#endif #endif
bool res = audio_device_->Initialize(); bool res = audio_device_->Initialize();
CHECK(res) << "Failed to initialize audio device."; CHECK(res) << "Failed to initialize audio device.";

View File

@ -24,6 +24,7 @@ source_set("platform") {
libs = [ libs = [
"gdi32.lib", # Graphics "gdi32.lib", # Graphics
"user32.lib", # Win32 API core functionality. "user32.lib", # Win32 API core functionality.
"Ole32.lib", # COM
"opengl32.lib", "opengl32.lib",
] ]
} else if (target_os == "android") { } else if (target_os == "android") {

View File

@ -1,5 +1,7 @@
#include "engine/platform/platform.h" #include "engine/platform/platform.h"
#include <combaseapi.h>
#include "base/log.h" #include "base/log.h"
#include "base/vecmath.h" #include "base/vecmath.h"
#include "engine/input_event.h" #include "engine/input_event.h"
@ -36,6 +38,9 @@ Platform::Platform(HINSTANCE instance, int cmd_show)
LOG(0) << "Data path: " << data_path_.c_str(); LOG(0) << "Data path: " << data_path_.c_str();
LOG(0) << "Shared data path: " << shared_data_path_.c_str(); LOG(0) << "Shared data path: " << shared_data_path_.c_str();
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
CHECK(SUCCEEDED(hr)) << "Unable to initialize COM: " << hr;
WNDCLASSEXW wcex; WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX); wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.style = CS_HREDRAW | CS_VREDRAW;