mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2024-11-10 11:35:19 -05:00
174 lines
4.4 KiB
C++
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 = this->GetSampleRate();
|
|
desired.nAvgBytesPerSec = desired.nSamplesPerSec * 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 2480;
|
|
}
|
|
|
|
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 |