Files
XCEngine/engine/src/Audio/WindowsAudioBackend.cpp
ssdfasd d575532966 docs: update TEST_SPEC.md and README.md to reflect new directory structure
- TEST_SPEC.md: Updated test directory structure to reflect Core/Asset,
  Core/IO, and Resources/<Type> subdirectories
- TEST_SPEC.md: Updated module names and test counts (852 total)
- TEST_SPEC.md: Updated build commands for new Resources subdirectories
- README.md: Updated engine structure with Core/Asset/ and Core/IO/
- README.md: Updated Resources section with layered architecture
- README.md: Updated test coverage table with accurate counts
2026-03-24 16:14:05 +08:00

265 lines
7.4 KiB
C++

#ifdef _WIN32
#define NOMINMAX
#endif
#include <XCEngine/Audio/WindowsAudioBackend.h>
#include <iostream>
#include <algorithm>
namespace XCEngine {
namespace Audio {
namespace WASAPI {
WASAPIBackend::WASAPIBackend()
: m_audioBuffer1(BufferSize * 2)
, m_audioBuffer2(BufferSize * 2)
{
}
WASAPIBackend::~WASAPIBackend() {
Shutdown();
}
bool WASAPIBackend::Initialize(const AudioConfig& config) {
m_config = config;
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;
}
void WASAPIBackend::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;
}
}
std::string WASAPIBackend::GetDeviceName() const {
return m_deviceName;
}
void WASAPIBackend::GetAvailableDevices(std::vector<std::string>& devices) {
devices.clear();
for (const auto& caps : m_waveOutCaps) {
devices.push_back(caps.szPname);
}
}
bool WASAPIBackend::SetDevice(const std::string& deviceName) {
for (UINT i = 0; i < waveOutGetNumDevs(); ++i) {
WAVEOUTCAPS caps;
waveOutGetDevCaps(i, &caps, sizeof(WAVEOUTCAPS));
if (deviceName == caps.szPname) {
m_deviceName = deviceName;
return true;
}
}
return false;
}
float WASAPIBackend::GetMasterVolume() const {
return m_masterVolume.load();
}
void WASAPIBackend::SetMasterVolume(float volume) {
m_masterVolume.store(std::max(0.0f, std::min(1.0f, volume)));
}
bool WASAPIBackend::IsMuted() const {
return m_muted.load();
}
void WASAPIBackend::SetMuted(bool muted) {
m_muted.store(muted);
}
void WASAPIBackend::Start() {
if (m_isRunning.load()) {
return;
}
m_isRunning = true;
m_audioThread = std::thread(&WASAPIBackend::AudioThreadProc, this);
}
void WASAPIBackend::Stop() {
m_isRunning = false;
if (m_audioThread.joinable()) {
m_audioThread.join();
}
}
void WASAPIBackend::Suspend() {
if (m_hWaveOut != nullptr) {
waveOutReset(m_hWaveOut);
}
}
void WASAPIBackend::Resume() {
if (m_hWaveOut != nullptr) {
MMRESULT result = waveOutRestart(m_hWaveOut);
if (result != MMSYSERR_NOERROR) {
std::cout << "Failed to resume audio playback" << std::endl;
}
}
}
void WASAPIBackend::ProcessAudio(float* buffer, uint32 bufferSize,
uint32 channels, uint32 sampleRate) {
if (m_muted.load() || buffer == nullptr) {
return;
}
float volume = m_masterVolume.load();
if (volume < 0.001f) {
return;
}
uint32 sampleCount = bufferSize / sizeof(float);
int16_t* backBuffer = m_isBuffer1Front ? m_audioBuffer2.data() : m_audioBuffer1.data();
uint32 bufferSamples = static_cast<uint32>(m_audioBuffer1.size());
for (uint32 i = 0; i < sampleCount && i < bufferSamples; ++i) {
float sample = buffer[i] * volume;
sample = std::max(-1.0f, std::min(1.0f, sample));
backBuffer[i] = static_cast<int16_t>(sample * 32767.0f);
}
std::lock_guard<std::mutex> lock(m_bufferMutex);
m_dataReady = true;
m_dataReadyCond.notify_one();
}
MMRESULT WASAPIBackend::InitDevice() {
WAVEOUTCAPS waveOutCapsTemp;
for (UINT i = 0; i < waveOutGetNumDevs(); ++i) {
waveOutGetDevCaps(i, &waveOutCapsTemp, sizeof(WAVEOUTCAPS));
m_waveOutCaps.push_back(waveOutCapsTemp);
}
MMRESULT result = waveOutOpen(&m_hWaveOut, WAVE_MAPPER, &m_waveFormat,
(DWORD_PTR)&WASAPIBackend::StaticAudioCallback,
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);
if (result != MMSYSERR_NOERROR) {
std::cout << "Failed to open audio device" << std::endl;
return result;
}
m_deviceName = "Default Device";
return MMSYSERR_NOERROR;
}
MMRESULT WASAPIBackend::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;
}
DWORD WINAPI WASAPIBackend::AudioThreadProc(LPVOID lpParameter) {
WASAPIBackend* backend = static_cast<WASAPIBackend*>(lpParameter);
if (backend) {
backend->AudioThread();
}
return 0;
}
void WASAPIBackend::AudioThread() {
PlayFrontData();
SwapBuffer();
PlayFrontData();
while (m_isRunning.load()) {
std::unique_lock<std::mutex> lock(m_bufferMutex);
m_dataReadyCond.wait_for(lock, std::chrono::milliseconds(10), [this] { return m_dataReady || !m_isRunning.load(); });
if (m_dataReady) {
PrepareBackData();
SwapBuffer();
PlayFrontData();
m_dataReady = false;
}
}
}
void WASAPIBackend::OnAudioCallback(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance,
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
if (uMsg == WOM_DONE) {
PrepareBackData();
SwapBuffer();
PlayFrontData();
}
}
void CALLBACK WASAPIBackend::StaticAudioCallback(HWAVEOUT hwo, UINT uMsg,
DWORD_PTR dwInstance,
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
WASAPIBackend* backend = reinterpret_cast<WASAPIBackend*>(dwInstance);
if (backend != nullptr) {
backend->OnAudioCallback(hwo, uMsg, dwInstance, dwParam1, dwParam2);
}
}
MMRESULT WASAPIBackend::PlayFrontData() {
WAVEHDR* frontHeader = m_isBuffer1Front ? &m_waveHeader1 : &m_waveHeader2;
MMRESULT result = waveOutWrite(m_hWaveOut, frontHeader, sizeof(WAVEHDR));
if (result != MMSYSERR_NOERROR) {
std::cout << "Failed to write audio data" << std::endl;
}
return result;
}
void WASAPIBackend::PrepareBackData() {
}
void WASAPIBackend::SwapBuffer() {
m_isBuffer1Front = !m_isBuffer1Front;
}
} // namespace WASAPI
} // namespace Audio
} // namespace XCEngine