// Copyright 2018 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. #include "AudioCommon/WASAPIStream.h" #ifdef _WIN32 // clang-format off #include #include #include #include #include #include // clang-format on #include "Common/Logging/Log.h" #include "Common/StringUtil.h" #include "Common/Thread.h" #include "Core/ConfigManager.h" #include "VideoCommon/OnScreenDisplay.h" WASAPIStream::WASAPIStream() { CoInitialize(nullptr); m_format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; m_format.Format.nChannels = 2; m_format.Format.nSamplesPerSec = GetMixer()->GetSampleRate(); m_format.Format.nAvgBytesPerSec = m_format.Format.nSamplesPerSec * 4; m_format.Format.nBlockAlign = 4; m_format.Format.wBitsPerSample = 16; m_format.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); m_format.Samples.wValidBitsPerSample = m_format.Format.wBitsPerSample; m_format.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; m_format.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; } WASAPIStream::~WASAPIStream() { if (m_enumerator) m_enumerator->Release(); if (m_need_data_event) CloseHandle(m_need_data_event); if (m_running) { m_running = false; if (m_thread.joinable()) m_thread.join(); } CoUninitialize(); } bool WASAPIStream::isValid() { return true; } static bool HandleWinAPI(std::string_view message, HRESULT result) { if (result != S_OK) { _com_error err(result); std::string error = TStrToUTF8(err.ErrorMessage()); switch (result) { case AUDCLNT_E_DEVICE_IN_USE: error = "Audio endpoint already in use!"; break; } ERROR_LOG_FMT(AUDIO, "WASAPI: {}: {}", message, error); } return result == S_OK; } std::vector WASAPIStream::GetAvailableDevices() { CoInitialize(nullptr); IMMDeviceEnumerator* enumerator = nullptr; HRESULT result = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), reinterpret_cast(&enumerator)); if (!HandleWinAPI("Failed to create MMDeviceEnumerator", result)) return {}; std::vector device_names; IMMDeviceCollection* devices; result = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &devices); if (!HandleWinAPI("Failed to get available devices", result)) { CoUninitialize(); return {}; } UINT count; devices->GetCount(&count); for (u32 i = 0; i < count; i++) { IMMDevice* device; devices->Item(i, &device); if (!HandleWinAPI("Failed to get device " + std::to_string(i), result)) continue; LPWSTR device_id; device->GetId(&device_id); IPropertyStore* device_properties; result = device->OpenPropertyStore(STGM_READ, &device_properties); if (!HandleWinAPI("Failed to initialize IPropertyStore", result)) continue; PROPVARIANT device_name; PropVariantInit(&device_name); device_properties->GetValue(PKEY_Device_FriendlyName, &device_name); device_names.push_back(TStrToUTF8(device_name.pwszVal)); PropVariantClear(&device_name); } devices->Release(); enumerator->Release(); CoUninitialize(); return device_names; } IMMDevice* WASAPIStream::GetDeviceByName(std::string name) { CoInitialize(nullptr); IMMDeviceEnumerator* enumerator = nullptr; HRESULT result = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), reinterpret_cast(&enumerator)); if (!HandleWinAPI("Failed to create MMDeviceEnumerator", result)) return nullptr; IMMDeviceCollection* devices; result = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &devices); if (!HandleWinAPI("Failed to get available devices", result)) { return {}; } UINT count; devices->GetCount(&count); for (u32 i = 0; i < count; i++) { IMMDevice* device; devices->Item(i, &device); if (!HandleWinAPI("Failed to get device " + std::to_string(i), result)) continue; LPWSTR device_id; device->GetId(&device_id); IPropertyStore* device_properties; result = device->OpenPropertyStore(STGM_READ, &device_properties); if (!HandleWinAPI("Failed to initialize IPropertyStore", result)) continue; PROPVARIANT device_name; PropVariantInit(&device_name); device_properties->GetValue(PKEY_Device_FriendlyName, &device_name); if (TStrToUTF8(device_name.pwszVal) == name) { devices->Release(); enumerator->Release(); CoUninitialize(); return device; } PropVariantClear(&device_name); } devices->Release(); enumerator->Release(); CoUninitialize(); return nullptr; } bool WASAPIStream::Init() { HRESULT result = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), reinterpret_cast(&m_enumerator)); if (!HandleWinAPI("Failed to create MMDeviceEnumerator", result)) return false; return true; } bool WASAPIStream::SetRunning(bool running) { if (running) { IMMDevice* device = nullptr; HRESULT result; if (SConfig::GetInstance().sWASAPIDevice == "default") { result = m_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device); } else { result = S_OK; device = GetDeviceByName(SConfig::GetInstance().sWASAPIDevice); if (!device) { ERROR_LOG_FMT(AUDIO, "Can't find device '{}', falling back to default", SConfig::GetInstance().sWASAPIDevice); result = m_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device); } } if (!HandleWinAPI("Failed to obtain default endpoint", result)) return false; LPWSTR device_id; device->GetId(&device_id); // Show a friendly name in the log IPropertyStore* device_properties; result = device->OpenPropertyStore(STGM_READ, &device_properties); if (!HandleWinAPI("Failed to initialize IPropertyStore", result)) return false; PROPVARIANT device_name; PropVariantInit(&device_name); device_properties->GetValue(PKEY_Device_FriendlyName, &device_name); INFO_LOG_FMT(AUDIO, "Using audio endpoint '{}'", TStrToUTF8(device_name.pwszVal)); PropVariantClear(&device_name); // Get IAudioDevice result = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, nullptr, reinterpret_cast(&m_audio_client)); if (!HandleWinAPI("Failed to activate IAudioClient", result)) { device->Release(); return false; } REFERENCE_TIME device_period = 0; result = m_audio_client->GetDevicePeriod(nullptr, &device_period); device_period += SConfig::GetInstance().iLatency * (10000 / m_format.Format.nChannels); INFO_LOG_FMT(AUDIO, "Audio period set to {}", device_period); if (!HandleWinAPI("Failed to obtain device period", result)) { device->Release(); m_audio_client->Release(); m_audio_client = nullptr; return false; } result = m_audio_client->Initialize( AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, device_period, device_period, reinterpret_cast(&m_format), nullptr); if (result == AUDCLNT_E_UNSUPPORTED_FORMAT) { OSD::AddMessage("Your current audio device doesn't support 16-bit 48000 hz PCM audio. WASAPI " "exclusive mode won't work.", 6000U); return false; } if (result == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { result = m_audio_client->GetBufferSize(&m_frames_in_buffer); m_audio_client->Release(); if (!HandleWinAPI("Failed to get aligned buffer size", result)) { device->Release(); m_audio_client->Release(); m_audio_client = nullptr; return false; } // Get IAudioDevice result = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, nullptr, reinterpret_cast(&m_audio_client)); if (!HandleWinAPI("Failed to reactivate IAudioClient", result)) { device->Release(); return false; } device_period = static_cast( 10000.0 * 1000 * m_frames_in_buffer / m_format.Format.nSamplesPerSec + 0.5) + SConfig::GetInstance().iLatency * 10000; result = m_audio_client->Initialize( AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, device_period, device_period, reinterpret_cast(&m_format), nullptr); } if (!HandleWinAPI("Failed to initialize IAudioClient", result)) { device->Release(); m_audio_client->Release(); m_audio_client = nullptr; return false; } result = m_audio_client->GetBufferSize(&m_frames_in_buffer); if (!HandleWinAPI("Failed to get buffer size from IAudioClient", result)) { device->Release(); m_audio_client->Release(); m_audio_client = nullptr; return false; } result = m_audio_client->GetService(__uuidof(IAudioRenderClient), reinterpret_cast(&m_audio_renderer)); if (!HandleWinAPI("Failed to get IAudioRenderClient from IAudioClient", result)) { device->Release(); m_audio_client->Release(); m_audio_client = nullptr; return false; } m_need_data_event = CreateEvent(nullptr, FALSE, FALSE, nullptr); m_audio_client->SetEventHandle(m_need_data_event); result = m_audio_client->Start(); if (!HandleWinAPI("Failed to get IAudioRenderClient from IAudioClient", result)) { device->Release(); m_audio_renderer->Release(); m_audio_renderer = nullptr; m_audio_client->Release(); m_audio_client = nullptr; CloseHandle(m_need_data_event); return false; } device->Release(); INFO_LOG_FMT(AUDIO, "WASAPI: Successfully initialized!"); m_running = true; m_thread = std::thread([this] { SoundLoop(); }); m_thread.detach(); } else { m_running = false; if (m_thread.joinable()) m_thread.join(); while (!m_stopped) { } if (m_audio_client) { m_audio_renderer->Release(); m_audio_renderer = nullptr; m_audio_client->Release(); m_audio_client = nullptr; } } return true; } void WASAPIStream::SoundLoop() { Common::SetCurrentThreadName("WASAPI Handler"); BYTE* data; if (m_audio_renderer) { m_audio_renderer->GetBuffer(m_frames_in_buffer, &data); m_audio_renderer->ReleaseBuffer(m_frames_in_buffer, AUDCLNT_BUFFERFLAGS_SILENT); } m_stopped = false; while (m_running) { if (!m_audio_renderer) continue; WaitForSingleObject(m_need_data_event, 1000); m_audio_renderer->GetBuffer(m_frames_in_buffer, &data); GetMixer()->Mix(reinterpret_cast(data), m_frames_in_buffer); float volume = SConfig::GetInstance().m_IsMuted ? 0 : SConfig::GetInstance().m_Volume / 100.; for (u32 i = 0; i < m_frames_in_buffer * 2; i++) reinterpret_cast(data)[i] = static_cast(reinterpret_cast(data)[i] * volume); m_audio_renderer->ReleaseBuffer(m_frames_in_buffer, 0); } m_stopped = true; } #endif // _WIN32