#ifdef _WIN32 #define NOMINMAX #endif #include #include #include 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(config.channels); m_waveFormat.nSamplesPerSec = config.sampleRate; m_waveFormat.nAvgBytesPerSec = config.sampleRate * config.channels * (config.bitsPerSample / 8); m_waveFormat.nBlockAlign = static_cast(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& 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(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(sample * 32767.0f); } std::lock_guard 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(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(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(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(lpParameter); if (backend) { backend->AudioThread(); } return 0; } void WASAPIBackend::AudioThread() { PlayFrontData(); SwapBuffer(); PlayFrontData(); while (m_isRunning.load()) { std::unique_lock 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(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