Shipwright/libultraship/libultraship/WasapiAudioPlayer.cpp

174 lines
4.4 KiB
C++

#ifdef _WIN32
#include "WasapiAudioPlayer.h"
#include "spdlog/spdlog.h"
// These constants are currently missing from the MinGW headers.
#ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
# define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
#endif
#ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY
# define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000
#endif
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioClient = __uuidof(IAudioClient);
const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
namespace Ship {
void WasapiAudioPlayer::ThrowIfFailed(HRESULT res) {
if (FAILED(res)) {
throw res;
}
}
bool WasapiAudioPlayer::SetupStream(void) {
try {
ThrowIfFailed(DeviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device));
ThrowIfFailed(device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, IID_PPV_ARGS_Helper(&client)));
WAVEFORMATEX desired;
desired.wFormatTag = WAVE_FORMAT_PCM;
desired.nChannels = 2;
desired.nSamplesPerSec = 32000;
desired.nAvgBytesPerSec = 32000 * 2 * 2;
desired.nBlockAlign = 4;
desired.wBitsPerSample = 16;
desired.cbSize = 0;
ThrowIfFailed(client->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, 2000000, 0, &desired, nullptr));
ThrowIfFailed(client->GetBufferSize(&bufferFrameCount));
ThrowIfFailed(client->GetService(IID_PPV_ARGS(&rclient)));
started = false;
initialized = true;
}
catch (HRESULT res) {
return false;
}
return true;
}
bool WasapiAudioPlayer::Init(void) {
try {
ThrowIfFailed(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&DeviceEnumerator)));
}
catch (HRESULT res) {
return false;
}
ThrowIfFailed(DeviceEnumerator->RegisterEndpointNotificationCallback(this));
return true;
}
int WasapiAudioPlayer::Buffered(void) {
if (!initialized) {
if (!SetupStream()) {
return 0;
}
}
try {
UINT32 padding;
ThrowIfFailed(client->GetCurrentPadding(&padding));
return padding;
}
catch (HRESULT res) {
return 0;
}
}
int WasapiAudioPlayer::GetDesiredBuffered(void) {
return 1680;
}
void WasapiAudioPlayer::Play(const uint8_t* Buffer, uint32_t BufferLen) {
if (!initialized) {
if (!SetupStream()) {
return;
}
}
try {
UINT32 frames = BufferLen / 4;
UINT32 padding;
ThrowIfFailed(client->GetCurrentPadding(&padding));
UINT32 available = bufferFrameCount - padding;
if (available < frames) {
frames = available;
}
if (available == 0) {
return;
}
BYTE* data;
ThrowIfFailed(rclient->GetBuffer(frames, &data));
memcpy(data, Buffer, frames * 4);
ThrowIfFailed(rclient->ReleaseBuffer(frames, 0));
if (!started && padding + frames > 1500) {
started = true;
ThrowIfFailed(client->Start());
}
}
catch (HRESULT res) {
}
}
HRESULT STDMETHODCALLTYPE WasapiAudioPlayer::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) {
return S_OK;
}
HRESULT STDMETHODCALLTYPE WasapiAudioPlayer::OnDeviceAdded(LPCWSTR pwstrDeviceId) {
return S_OK;
}
HRESULT STDMETHODCALLTYPE WasapiAudioPlayer::OnDeviceRemoved(LPCWSTR pwstrDeviceId) {
return S_OK;
}
HRESULT STDMETHODCALLTYPE WasapiAudioPlayer::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId) {
if (flow == eRender && role == eConsole) {
// This callback runs on a separate thread,
// but it's not important how fast this write takes effect.
initialized = false;
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE WasapiAudioPlayer::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) {
return S_OK;
}
ULONG STDMETHODCALLTYPE WasapiAudioPlayer::AddRef() {
return InterlockedIncrement(&refcount);
}
ULONG STDMETHODCALLTYPE WasapiAudioPlayer::Release() {
ULONG rc = InterlockedDecrement(&refcount);
if (rc == 0) {
delete this;
}
return rc;
}
HRESULT STDMETHODCALLTYPE WasapiAudioPlayer::QueryInterface(REFIID riid, VOID** ppvInterface) {
if (riid == __uuidof(IUnknown)) {
AddRef();
*ppvInterface = (IUnknown*)this;
}
else if (riid == __uuidof(IMMNotificationClient)) {
AddRef();
*ppvInterface = (IMMNotificationClient*)this;
}
else {
*ppvInterface = nullptr;
return E_NOINTERFACE;
}
return S_OK;
}
}
#endif