#ifdef _WIN32 #define NOMINMAX #endif #include #include #include namespace XCEngine { namespace Audio { 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(requiredBytes - 1), '\0'); WideCharToMultiByte( CP_UTF8, 0, deviceName, -1, utf8Name.data(), requiredBytes, nullptr, nullptr); return utf8Name; #else return std::string(deviceName); #endif } } // namespace WaveOutBackend::WaveOutBackend() : m_audioBuffer1(BufferSize * 2) , m_audioBuffer2(BufferSize * 2) { } WaveOutBackend::~WaveOutBackend() { Shutdown(); } bool WaveOutBackend::Initialize(const AudioConfig& config) { m_config = config; const size_t bufferSampleCount = static_cast(config.bufferSize) * config.channels; m_audioBuffer1.assign(bufferSampleCount, 0); m_audioBuffer2.assign(bufferSampleCount, 0); m_renderBuffer.assign(bufferSampleCount, 0.0f); m_buffer1Available = true; m_buffer2Available = true; 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 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; } } std::string WaveOutBackend::GetDeviceName() const { return m_deviceName; } void WaveOutBackend::GetAvailableDevices(std::vector& devices) { devices.clear(); for (const auto& caps : m_waveOutCaps) { devices.push_back(ConvertWaveOutDeviceName(caps.szPname)); } } 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)) { m_requestedDeviceName = deviceName; m_deviceName = deviceName; return true; } } return false; } void WaveOutBackend::Start() { if (m_isRunning.load()) { return; } m_isRunning = true; m_audioThread = std::thread(&WaveOutBackend::AudioThreadProc, this); } void WaveOutBackend::Stop() { m_isRunning = false; m_bufferReadyCond.notify_all(); if (m_hWaveOut != nullptr) { waveOutReset(m_hWaveOut); } if (m_audioThread.joinable()) { m_audioThread.join(); } } void WaveOutBackend::Suspend() { if (m_hWaveOut != nullptr) { waveOutReset(m_hWaveOut); } } 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; } } } void WaveOutBackend::ProcessAudio(float* buffer, uint32 frameCount, uint32 channels, uint32 sampleRate) { (void)buffer; (void)frameCount; (void)channels; (void)sampleRate; } void WaveOutBackend::SetRenderCallback(RenderCallback callback) { std::lock_guard lock(m_bufferMutex); m_renderCallback = std::move(callback); } 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); } 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(this), CALLBACK_FUNCTION); if (result != MMSYSERR_NOERROR) { std::cout << "Failed to open audio device" << std::endl; return result; } m_deviceName = m_requestedDeviceName.empty() ? "Default Device" : m_requestedDeviceName; return MMSYSERR_NOERROR; } MMRESULT WaveOutBackend::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 WaveOutBackend::AudioThreadProc(LPVOID lpParameter) { WaveOutBackend* backend = static_cast(lpParameter); if (backend) { backend->AudioThread(); } return 0; } void WaveOutBackend::AudioThread() { while (m_isRunning.load()) { std::unique_lock lock(m_bufferMutex); m_bufferReadyCond.wait(lock, [this] { return !m_isRunning.load() || m_buffer1Available || m_buffer2Available; }); if (!m_isRunning.load()) { break; } WAVEHDR* targetHeader = nullptr; std::vector* 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(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; lock.unlock(); if (renderCallback && !m_renderBuffer.empty()) { renderCallback(m_renderBuffer.data(), frameCount, channels, sampleRate); } FillPcm16Buffer(*targetBuffer, m_renderBuffer); if (SubmitBuffer(*targetHeader, *targetBuffer) != MMSYSERR_NOERROR) { std::lock_guard restoreLock(m_bufferMutex); if (targetHeader == &m_waveHeader1) { m_buffer1Available = true; } else { m_buffer2Available = true; } m_bufferReadyCond.notify_one(); } } } void WaveOutBackend::OnAudioCallback(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { (void)hwo; (void)dwInstance; (void)dwParam2; if (uMsg == WOM_DONE) { std::lock_guard lock(m_bufferMutex); WAVEHDR* completedHeader = reinterpret_cast(dwParam1); if (completedHeader == &m_waveHeader1) { m_buffer1Available = true; } else if (completedHeader == &m_waveHeader2) { m_buffer2Available = true; } m_bufferReadyCond.notify_one(); } } void CALLBACK WaveOutBackend::StaticAudioCallback(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { WaveOutBackend* backend = reinterpret_cast(dwInstance); if (backend != nullptr) { backend->OnAudioCallback(hwo, uMsg, dwInstance, dwParam1, dwParam2); } } MMRESULT WaveOutBackend::SubmitBuffer(WAVEHDR& header, std::vector& samples) { header.lpData = reinterpret_cast(samples.data()); header.dwBufferLength = static_cast(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; } } // namespace WaveOut } // namespace Audio } // namespace XCEngine