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.
|
||||
"/wd5045", # compiler will insert Spectre mitigation for memory load if
|
||||
# switch specified.
|
||||
"/wd4355", # 'this': used in base member initializer list
|
||||
|
||||
# TODO: Not sure how I feel about these conversion warnings.
|
||||
"/Wv:18",
|
||||
|
|
|
@ -21,7 +21,10 @@ source_set("audio") {
|
|||
]
|
||||
libs += [ "asound" ]
|
||||
} else if (target_os == "win") {
|
||||
sources += [ "audio_device_null.h" ]
|
||||
sources += [
|
||||
"audio_device_wasapi.cc",
|
||||
"audio_device_wasapi.h",
|
||||
]
|
||||
} else if (target_os == "android") {
|
||||
sources += [
|
||||
"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__)
|
||||
#include "engine/audio/audio_device_alsa.h"
|
||||
#elif defined(_WIN32)
|
||||
#include "engine/audio/audio_device_null.h"
|
||||
#include "engine/audio/audio_device_wasapi.h"
|
||||
#endif
|
||||
|
||||
using namespace base;
|
||||
|
@ -26,8 +26,7 @@ AudioMixer::AudioMixer()
|
|||
#elif defined(__linux__)
|
||||
audio_device_{std::make_unique<AudioDeviceAlsa>(this)} {
|
||||
#elif defined(_WIN32)
|
||||
// TODO: Implement AudioDeviceWindows
|
||||
audio_device_{std::make_unique<AudioDeviceNull>()} {
|
||||
audio_device_{std::make_unique<AudioDeviceWASAPI>(this)} {
|
||||
#endif
|
||||
bool res = audio_device_->Initialize();
|
||||
CHECK(res) << "Failed to initialize audio device.";
|
||||
|
|
|
@ -24,6 +24,7 @@ source_set("platform") {
|
|||
libs = [
|
||||
"gdi32.lib", # Graphics
|
||||
"user32.lib", # Win32 API core functionality.
|
||||
"Ole32.lib", # COM
|
||||
"opengl32.lib",
|
||||
]
|
||||
} else if (target_os == "android") {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "engine/platform/platform.h"
|
||||
|
||||
#include <combaseapi.h>
|
||||
|
||||
#include "base/log.h"
|
||||
#include "base/vecmath.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) << "Shared data path: " << shared_data_path_.c_str();
|
||||
|
||||
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
CHECK(SUCCEEDED(hr)) << "Unable to initialize COM: " << hr;
|
||||
|
||||
WNDCLASSEXW wcex;
|
||||
wcex.cbSize = sizeof(WNDCLASSEX);
|
||||
wcex.style = CS_HREDRAW | CS_VREDRAW;
|
||||
|
|
Loading…
Reference in New Issue