mirror of https://github.com/auygun/kaliber.git
Implement AudioDeviceWASAPI
This commit is contained in:
parent
261e7f41d6
commit
2823aa3197
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
@ -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
|
|
@ -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.";
|
||||||
|
|
|
@ -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") {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue