Files
XCEngine/engine/src/Audio/WindowsAudioBackend.cpp

368 lines
11 KiB
C++
Raw Normal View History

#ifdef _WIN32
#define NOMINMAX
#endif
#include <XCEngine/Audio/WindowsAudioBackend.h>
#include <iostream>
#include <algorithm>
namespace XCEngine {
namespace Audio {
2026-04-14 16:30:02 +08:00
namespace WaveOut {
namespace {
std::string ConvertWaveOutDeviceName(const TCHAR* deviceName) {
if (deviceName == nullptr) {
return {};
}
#if defined(UNICODE) || defined(_UNICODE)
const int requiredBytes = WideCharToMultiByte(
CP_UTF8,
0,
deviceName,
-1,
nullptr,
0,
nullptr,
nullptr);
if (requiredBytes <= 1) {
return {};
}
std::string utf8Name(static_cast<std::size_t>(requiredBytes - 1), '\0');
WideCharToMultiByte(
CP_UTF8,
0,
deviceName,
-1,
utf8Name.data(),
requiredBytes,
nullptr,
nullptr);
return utf8Name;
#else
return std::string(deviceName);
#endif
}
} // namespace
2026-04-14 16:30:02 +08:00
WaveOutBackend::WaveOutBackend()
: m_audioBuffer1(BufferSize * 2)
, m_audioBuffer2(BufferSize * 2)
{
}
2026-04-14 16:30:02 +08:00
WaveOutBackend::~WaveOutBackend() {
Shutdown();
}
2026-04-14 16:30:02 +08:00
bool WaveOutBackend::Initialize(const AudioConfig& config) {
m_config = config;
2026-04-14 16:30:02 +08:00
const size_t bufferSampleCount = static_cast<size_t>(config.bufferSize) * config.channels;
m_audioBuffer1.assign(bufferSampleCount, 0);
m_audioBuffer2.assign(bufferSampleCount, 0);
m_renderBuffer.assign(bufferSampleCount, 0.0f);
2026-04-14 16:30:02 +08:00
m_buffer1Available = true;
m_buffer2Available = true;
m_waveFormat.wFormatTag = WAVE_FORMAT_PCM;
m_waveFormat.nChannels = static_cast<WORD>(config.channels);
m_waveFormat.nSamplesPerSec = config.sampleRate;
m_waveFormat.nAvgBytesPerSec = config.sampleRate * config.channels * (config.bitsPerSample / 8);
m_waveFormat.nBlockAlign = static_cast<WORD>(config.channels * (config.bitsPerSample / 8));
m_waveFormat.wBitsPerSample = config.bitsPerSample;
m_waveFormat.cbSize = 0;
MMRESULT result = InitDevice();
if (result != MMSYSERR_NOERROR) {
return false;
}
result = InitBuffer();
if (result != MMSYSERR_NOERROR) {
waveOutClose(m_hWaveOut);
return false;
}
return true;
}
2026-04-14 16:30:02 +08:00
void WaveOutBackend::Shutdown() {
if (m_isRunning.load()) {
Stop();
}
if (m_hWaveOut != nullptr) {
waveOutUnprepareHeader(m_hWaveOut, &m_waveHeader1, sizeof(WAVEHDR));
waveOutUnprepareHeader(m_hWaveOut, &m_waveHeader2, sizeof(WAVEHDR));
waveOutClose(m_hWaveOut);
m_hWaveOut = nullptr;
}
}
2026-04-14 16:30:02 +08:00
std::string WaveOutBackend::GetDeviceName() const {
return m_deviceName;
}
2026-04-14 16:30:02 +08:00
void WaveOutBackend::GetAvailableDevices(std::vector<std::string>& devices) {
devices.clear();
for (const auto& caps : m_waveOutCaps) {
devices.push_back(ConvertWaveOutDeviceName(caps.szPname));
}
}
2026-04-14 16:30:02 +08:00
bool WaveOutBackend::SetDevice(const std::string& deviceName) {
const bool useDefaultDevice = deviceName.empty() || deviceName == "Default Device";
const std::string requestedName = useDefaultDevice ? "Default Device" : deviceName;
if (m_hWaveOut != nullptr || m_isRunning.load()) {
return requestedName == m_deviceName;
}
if (useDefaultDevice) {
m_requestedDeviceName.clear();
m_deviceName = "Default Device";
return true;
}
for (UINT i = 0; i < waveOutGetNumDevs(); ++i) {
WAVEOUTCAPS caps;
waveOutGetDevCaps(i, &caps, sizeof(WAVEOUTCAPS));
if (deviceName == ConvertWaveOutDeviceName(caps.szPname)) {
2026-04-14 16:30:02 +08:00
m_requestedDeviceName = deviceName;
m_deviceName = deviceName;
return true;
}
}
return false;
}
2026-04-14 16:30:02 +08:00
void WaveOutBackend::Start() {
if (m_isRunning.load()) {
return;
}
m_isRunning = true;
2026-04-14 16:30:02 +08:00
m_audioThread = std::thread(&WaveOutBackend::AudioThreadProc, this);
}
2026-04-14 16:30:02 +08:00
void WaveOutBackend::Stop() {
m_isRunning = false;
m_bufferReadyCond.notify_all();
2026-04-14 16:30:02 +08:00
if (m_hWaveOut != nullptr) {
waveOutReset(m_hWaveOut);
}
if (m_audioThread.joinable()) {
m_audioThread.join();
}
}
2026-04-14 16:30:02 +08:00
void WaveOutBackend::Suspend() {
if (m_hWaveOut != nullptr) {
waveOutReset(m_hWaveOut);
}
}
2026-04-14 16:30:02 +08:00
void WaveOutBackend::Resume() {
if (m_hWaveOut != nullptr) {
MMRESULT result = waveOutRestart(m_hWaveOut);
if (result != MMSYSERR_NOERROR) {
std::cout << "Failed to resume audio playback" << std::endl;
}
}
}
2026-04-14 16:30:02 +08:00
void WaveOutBackend::ProcessAudio(float* buffer, uint32 frameCount,
uint32 channels, uint32 sampleRate) {
(void)buffer;
(void)frameCount;
(void)channels;
2026-04-14 16:30:02 +08:00
(void)sampleRate;
}
void WaveOutBackend::SetRenderCallback(RenderCallback callback) {
std::lock_guard<std::mutex> lock(m_bufferMutex);
m_renderCallback = std::move(callback);
}
2026-04-14 16:30:02 +08:00
MMRESULT WaveOutBackend::InitDevice() {
m_waveOutCaps.clear();
WAVEOUTCAPS waveOutCapsTemp;
for (UINT i = 0; i < waveOutGetNumDevs(); ++i) {
waveOutGetDevCaps(i, &waveOutCapsTemp, sizeof(WAVEOUTCAPS));
m_waveOutCaps.push_back(waveOutCapsTemp);
}
2026-04-14 16:30:02 +08:00
UINT deviceId = WAVE_MAPPER;
if (!m_requestedDeviceName.empty()) {
bool foundRequestedDevice = false;
for (UINT i = 0; i < m_waveOutCaps.size(); ++i) {
if (m_requestedDeviceName == ConvertWaveOutDeviceName(m_waveOutCaps[i].szPname)) {
deviceId = i;
foundRequestedDevice = true;
break;
}
}
if (!foundRequestedDevice) {
std::cout << "Requested audio device is no longer available" << std::endl;
return MMSYSERR_BADDEVICEID;
}
}
MMRESULT result = waveOutOpen(&m_hWaveOut, deviceId, &m_waveFormat,
(DWORD_PTR)&WaveOutBackend::StaticAudioCallback,
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);
if (result != MMSYSERR_NOERROR) {
std::cout << "Failed to open audio device" << std::endl;
return result;
}
2026-04-14 16:30:02 +08:00
m_deviceName = m_requestedDeviceName.empty() ? "Default Device" : m_requestedDeviceName;
return MMSYSERR_NOERROR;
}
2026-04-14 16:30:02 +08:00
MMRESULT WaveOutBackend::InitBuffer() {
m_waveHeader1.lpData = (LPSTR)m_audioBuffer1.data();
m_waveHeader1.dwBufferLength = static_cast<DWORD>(m_audioBuffer1.size() * sizeof(int16_t));
m_waveHeader1.dwFlags = 0;
MMRESULT result = waveOutPrepareHeader(m_hWaveOut, &m_waveHeader1, sizeof(WAVEHDR));
if (result != MMSYSERR_NOERROR) {
std::cout << "Failed to prepare audio header1" << std::endl;
return result;
}
m_waveHeader2.lpData = (LPSTR)m_audioBuffer2.data();
m_waveHeader2.dwBufferLength = static_cast<DWORD>(m_audioBuffer2.size() * sizeof(int16_t));
m_waveHeader2.dwFlags = 0;
result = waveOutPrepareHeader(m_hWaveOut, &m_waveHeader2, sizeof(WAVEHDR));
if (result != MMSYSERR_NOERROR) {
std::cout << "Failed to prepare audio header2" << std::endl;
waveOutUnprepareHeader(m_hWaveOut, &m_waveHeader1, sizeof(WAVEHDR));
return result;
}
return MMSYSERR_NOERROR;
}
2026-04-14 16:30:02 +08:00
DWORD WINAPI WaveOutBackend::AudioThreadProc(LPVOID lpParameter) {
WaveOutBackend* backend = static_cast<WaveOutBackend*>(lpParameter);
if (backend) {
backend->AudioThread();
}
return 0;
}
2026-04-14 16:30:02 +08:00
void WaveOutBackend::AudioThread() {
while (m_isRunning.load()) {
std::unique_lock<std::mutex> lock(m_bufferMutex);
m_bufferReadyCond.wait(lock, [this] {
return !m_isRunning.load() || m_buffer1Available || m_buffer2Available;
2026-04-14 16:30:02 +08:00
});
if (!m_isRunning.load()) {
break;
}
WAVEHDR* targetHeader = nullptr;
std::vector<int16_t>* targetBuffer = nullptr;
if (m_buffer1Available) {
targetHeader = &m_waveHeader1;
targetBuffer = &m_audioBuffer1;
m_buffer1Available = false;
} else if (m_buffer2Available) {
targetHeader = &m_waveHeader2;
targetBuffer = &m_audioBuffer2;
m_buffer2Available = false;
} else {
continue;
}
RenderCallback renderCallback;
const uint32 frameCount = m_config.bufferSize;
const uint32 channels = m_config.channels;
const uint32 sampleRate = m_config.sampleRate;
const size_t sampleCount = static_cast<size_t>(frameCount) * channels;
if (targetBuffer->size() != sampleCount) {
targetBuffer->assign(sampleCount, 0);
} else {
std::fill(targetBuffer->begin(), targetBuffer->end(), 0);
}
if (m_renderBuffer.size() != sampleCount) {
m_renderBuffer.assign(sampleCount, 0.0f);
} else {
std::fill(m_renderBuffer.begin(), m_renderBuffer.end(), 0.0f);
}
renderCallback = m_renderCallback;
2026-04-14 16:30:02 +08:00
lock.unlock();
if (renderCallback && !m_renderBuffer.empty()) {
renderCallback(m_renderBuffer.data(), frameCount, channels, sampleRate);
}
FillPcm16Buffer(*targetBuffer, m_renderBuffer);
2026-04-14 16:30:02 +08:00
if (SubmitBuffer(*targetHeader, *targetBuffer) != MMSYSERR_NOERROR) {
std::lock_guard<std::mutex> restoreLock(m_bufferMutex);
if (targetHeader == &m_waveHeader1) {
m_buffer1Available = true;
} else {
m_buffer2Available = true;
}
m_bufferReadyCond.notify_one();
}
}
}
2026-04-14 16:30:02 +08:00
void WaveOutBackend::OnAudioCallback(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance,
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
2026-04-14 16:30:02 +08:00
(void)hwo;
(void)dwInstance;
(void)dwParam2;
if (uMsg == WOM_DONE) {
2026-04-14 16:30:02 +08:00
std::lock_guard<std::mutex> lock(m_bufferMutex);
WAVEHDR* completedHeader = reinterpret_cast<WAVEHDR*>(dwParam1);
if (completedHeader == &m_waveHeader1) {
m_buffer1Available = true;
} else if (completedHeader == &m_waveHeader2) {
m_buffer2Available = true;
}
m_bufferReadyCond.notify_one();
}
}
2026-04-14 16:30:02 +08:00
void CALLBACK WaveOutBackend::StaticAudioCallback(HWAVEOUT hwo, UINT uMsg,
DWORD_PTR dwInstance,
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
2026-04-14 16:30:02 +08:00
WaveOutBackend* backend = reinterpret_cast<WaveOutBackend*>(dwInstance);
if (backend != nullptr) {
backend->OnAudioCallback(hwo, uMsg, dwInstance, dwParam1, dwParam2);
}
}
2026-04-14 16:30:02 +08:00
MMRESULT WaveOutBackend::SubmitBuffer(WAVEHDR& header, std::vector<int16_t>& samples) {
header.lpData = reinterpret_cast<LPSTR>(samples.data());
header.dwBufferLength = static_cast<DWORD>(samples.size() * sizeof(int16_t));
header.dwFlags &= ~WHDR_DONE;
MMRESULT result = waveOutWrite(m_hWaveOut, &header, sizeof(WAVEHDR));
if (result != MMSYSERR_NOERROR) {
std::cout << "Failed to write audio data" << std::endl;
}
return result;
}
2026-04-14 16:30:02 +08:00
} // namespace WaveOut
} // namespace Audio
} // namespace XCEngine