diff --git a/Common/Audio/AudioBackend.h b/Common/Audio/AudioBackend.h index f23588c222..4a5033ccda 100644 --- a/Common/Audio/AudioBackend.h +++ b/Common/Audio/AudioBackend.h @@ -33,5 +33,6 @@ public: virtual int PeriodFrames() const = 0; virtual void DescribeOutputFormat(char *buffer, size_t bufferSize) const { buffer[0] = '-'; buffer[1] = '\0'; } virtual std::string GetCurrentDeviceName() const { return ""; } + virtual std::string GetErrorString() const { return ""; } virtual void FrameUpdate(bool allowAutoChange) {} }; diff --git a/UI/SystemInfoScreen.cpp b/UI/SystemInfoScreen.cpp index f6fcfc53ba..5822469c0f 100644 --- a/UI/SystemInfoScreen.cpp +++ b/UI/SystemInfoScreen.cpp @@ -76,10 +76,14 @@ void SystemInfoScreen::CreateTabs() { using namespace UI; auto si = GetI18NCategory(I18NCat::SYSINFO); + auto ms = GetI18NCategory(I18NCat::MAINSETTINGS); AddTab("Device Info", si->T("Device Info"), [this](UI::LinearLayout *parent) { CreateDeviceInfoTab(parent); }); + AddTab("Audio", ms->T("Audio"), [this](UI::LinearLayout *parent) { + CreateAudioInfoTab(parent); + }); AddTab("Storage", si->T("Storage"), [this](UI::LinearLayout *parent) { CreateStorageTab(parent); }); @@ -215,24 +219,6 @@ void SystemInfoScreen::CreateDeviceInfoTab(UI::LinearLayout *deviceSpecs) { #endif osInformation->Add(new InfoItem(si->T("PPSSPP build"), build)); - CollapsibleSection *audioInformation = deviceSpecs->Add(new CollapsibleSection(si->T("Audio Information"))); - extern AudioBackend *g_audioBackend; - if (g_audioBackend) { - char fmtStr[256]; - g_audioBackend->DescribeOutputFormat(fmtStr, sizeof(fmtStr)); - audioInformation->Add(new InfoItem(si->T("Stream format"), fmtStr)); - } else { - audioInformation->Add(new InfoItem(si->T("Sample rate"), StringFromFormat(si->T_cstr("%d Hz"), System_GetPropertyInt(SYSPROP_AUDIO_SAMPLE_RATE)))); - } - int framesPerBuffer = System_GetPropertyInt(SYSPROP_AUDIO_FRAMES_PER_BUFFER); - if (framesPerBuffer > 0) { - audioInformation->Add(new InfoItem(si->T("Frames per buffer"), StringFromFormat("%d", framesPerBuffer))); - } -#if PPSSPP_PLATFORM(ANDROID) - audioInformation->Add(new InfoItem(si->T("Optimal sample rate"), StringFromFormat(si->T_cstr("%d Hz"), System_GetPropertyInt(SYSPROP_AUDIO_OPTIMAL_SAMPLE_RATE)))); - audioInformation->Add(new InfoItem(si->T("Optimal frames per buffer"), StringFromFormat("%d", System_GetPropertyInt(SYSPROP_AUDIO_OPTIMAL_FRAMES_PER_BUFFER)))); -#endif - CollapsibleSection *displayInfo = deviceSpecs->Add(new CollapsibleSection(si->T("Display Information"))); #if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(UWP) displayInfo->Add(new InfoItem(si->T("Native resolution"), StringFromFormat("%dx%d", @@ -429,6 +415,36 @@ void SystemInfoScreen::CreateCPUExtensionsTab(UI::LinearLayout *cpuExtensions) { } } +void SystemInfoScreen::CreateAudioInfoTab(UI::LinearLayout *audio) { + using namespace UI; + + auto si = GetI18NCategory(I18NCat::SYSINFO); + auto di = GetI18NCategory(I18NCat::DIALOG); + auto a = GetI18NCategory(I18NCat::AUDIO); + + CollapsibleSection *audioInformation = audio->Add(new CollapsibleSection(si->T("Audio Information"))); + extern AudioBackend *g_audioBackend; + + if (g_audioBackend) { + audioInformation->Add(new InfoItem(a->T("Device"), g_audioBackend->GetCurrentDeviceName())); + char fmtStr[256]; + g_audioBackend->DescribeOutputFormat(fmtStr, sizeof(fmtStr)); + audioInformation->Add(new InfoItem(si->T("Stream format"), fmtStr)); + std::string error = g_audioBackend->GetErrorString(); + audioInformation->Add(new InfoItem(a->T("Audio Error"), error.empty() ? di->T("None") : error)); + } else { + audioInformation->Add(new InfoItem(si->T("Sample rate"), StringFromFormat(si->T_cstr("%d Hz"), System_GetPropertyInt(SYSPROP_AUDIO_SAMPLE_RATE)))); + } + int framesPerBuffer = System_GetPropertyInt(SYSPROP_AUDIO_FRAMES_PER_BUFFER); + if (framesPerBuffer > 0) { + audioInformation->Add(new InfoItem(si->T("Frames per buffer"), StringFromFormat("%d", framesPerBuffer))); + } + if (System_GetPropertyInt(SYSPROP_AUDIO_OPTIMAL_SAMPLE_RATE) > 0) { + audioInformation->Add(new InfoItem(si->T("Optimal sample rate"), StringFromFormat(si->T_cstr("%d Hz"), System_GetPropertyInt(SYSPROP_AUDIO_OPTIMAL_SAMPLE_RATE)))); + audioInformation->Add(new InfoItem(si->T("Optimal frames per buffer"), StringFromFormat("%d", System_GetPropertyInt(SYSPROP_AUDIO_OPTIMAL_FRAMES_PER_BUFFER)))); + } +} + void SystemInfoScreen::CreateDriverBugsTab(UI::LinearLayout *driverBugs) { using namespace UI; using namespace Draw; diff --git a/UI/SystemInfoScreen.h b/UI/SystemInfoScreen.h index 0d57954dbe..37fc736353 100644 --- a/UI/SystemInfoScreen.h +++ b/UI/SystemInfoScreen.h @@ -25,6 +25,7 @@ private: void CreateStorageTab(UI::LinearLayout *storage); void CreateBuildConfigTab(UI::LinearLayout *storage); void CreateCPUExtensionsTab(UI::LinearLayout *storage); + void CreateAudioInfoTab(UI::LinearLayout *storage); void CreateDriverBugsTab(UI::LinearLayout *storage); void CreateOpenGLExtsTab(UI::LinearLayout *gpuExtensions); void CreateVulkanExtsTab(UI::LinearLayout *gpuExtensions); diff --git a/Windows/Hid/HidInputDevice.cpp b/Windows/Hid/HidInputDevice.cpp index 7f5bc59f82..fff2f9dbf2 100644 --- a/Windows/Hid/HidInputDevice.cpp +++ b/Windows/Hid/HidInputDevice.cpp @@ -301,7 +301,7 @@ int HidInputDevice::UpdateState() { prevState_ = state; return UPDATESTATE_NO_SLEEP; // The ReadFile sleeps for us, effectively. } else { - WARN_LOG(Log::System, "Failed to read controller - assuming disconnected."); + INFO_LOG(Log::System, "Failed to read controller - assuming disconnected."); // might have been disconnected. retry later. KeyMap::NotifyPadDisconnected(deviceID); ReleaseAllKeys(buttonMappings, (int)buttonMappingsSize); diff --git a/Windows/WASAPIContext.cpp b/Windows/WASAPIContext.cpp index 4c43113073..ffde4e5f70 100644 --- a/Windows/WASAPIContext.cpp +++ b/Windows/WASAPIContext.cpp @@ -12,6 +12,7 @@ #include "Common/Data/Encoding/Utf8.h" #include "Common/Log.h" +#include "Common/StringUtils.h" #include "Common/Thread/ThreadUtil.h" #include "WASAPIContext.h" @@ -136,6 +137,19 @@ void WASAPIContext::EnumerateDevices(std::vector *output, bool } } +// Also logs. +void WASAPIContext::SetErrorString(std::string_view str, HRESULT hr) { + std::string temp = StringFromFormat("%s (HRESULT: %08lx)", str.data(), hr); + ERROR_LOG(Log::Audio, "%s", temp.c_str()); + std::lock_guard guard(errorLock_); + errorString_ = temp; +} + +void WASAPIContext::ClearErrorString() { + std::lock_guard guard(errorLock_); + errorString_.clear(); +} + bool WASAPIContext::InitOutputDevice(std::string_view uniqueId, LatencyMode latencyMode, bool *revertedToDefault) { Stop(); @@ -144,17 +158,22 @@ bool WASAPIContext::InitOutputDevice(std::string_view uniqueId, LatencyMode late ComPtr device; if (uniqueId.empty()) { // Use the default device. - if (FAILED(enumerator_->GetDefaultAudioEndpoint(eRender, eConsole, &device))) { + HRESULT hr = enumerator_->GetDefaultAudioEndpoint(eRender, eConsole, &device); + if (FAILED(hr)) { + SetErrorString("Failed to get the default endpoint", hr); return false; } } else { // Use whatever device. std::wstring wId = ConvertUTF8ToWString(uniqueId); - if (FAILED(enumerator_->GetDevice(wId.c_str(), &device))) { + HRESULT hr = enumerator_->GetDevice(wId.c_str(), &device); + if (FAILED(hr)) { // Fallback to default device INFO_LOG(Log::Audio, "Falling back to default device...\n"); *revertedToDefault = true; - if (FAILED(enumerator_->GetDefaultAudioEndpoint(eRender, eConsole, &device))) { + hr = enumerator_->GetDefaultAudioEndpoint(eRender, eConsole, &device); + if (FAILED(hr)) { + SetErrorString("Failed to fallback", hr); return false; } } @@ -162,11 +181,12 @@ bool WASAPIContext::InitOutputDevice(std::string_view uniqueId, LatencyMode late AudioDeviceDesc desc{}; GetDeviceDesc(device.Get(), &desc); - INFO_LOG(Log::Audio, "Activating audio device: %s", desc.name.c_str()); + INFO_LOG(Log::Audio, "Activating audio device: %s : %s", desc.name.c_str(), desc.uniqueId.c_str()); { std::lock_guard guard(deviceLock_); - curDeviceId_ = uniqueId; + curDeviceId_ = desc.uniqueId; + curDeviceName_ = desc.name; } HRESULT hr = E_FAIL; @@ -179,17 +199,33 @@ bool WASAPIContext::InitOutputDevice(std::string_view uniqueId, LatencyMode late tempBuf_.reset(); if (SUCCEEDED(hr)) { - audioClient3_->GetMixFormat(&format_); + hr = audioClient3_->GetMixFormat(&format_); + if (FAILED(hr)) { + audioClient3_.Reset(); + SetErrorString("AudioClient3 GetMixFormat failed", hr); + return false; + } curSamplesPerSec_ = format_->nSamplesPerSec; // We only use AudioClient3 if we got the format we wanted (stereo float). if (format_->nChannels != 2 || Classify(format_) != AudioFormat::Float) { // Let's fall back to the old path. The docs seem to be wrong, if you try to create an // AudioClient3 with low latency audio with AUTOCONVERTPCM, you get the error 0x88890021. + INFO_LOG(Log::Audio, "AudioClient3: Got %d channels or non-float format, falling back to AudioClient", format_->nChannels); audioClient3_.Reset(); + // Free the format before falling through - AudioClient will allocate a new one + CoTaskMemFree(format_); + format_ = nullptr; // Fall through to AudioClient creation below. } else { - audioClient3_->GetSharedModeEnginePeriod(format_, &defaultPeriodFrames_, &fundamentalPeriodFrames_, &minPeriodFrames_, &maxPeriodFrames_); + hr = audioClient3_->GetSharedModeEnginePeriod(format_, &defaultPeriodFrames_, &fundamentalPeriodFrames_, &minPeriodFrames_, &maxPeriodFrames_); + if (FAILED(hr)) { + audioClient3_.Reset(); + CoTaskMemFree(format_); + format_ = nullptr; + SetErrorString("AudioClient3 GetSharedModeEnginePeriod failed", hr); + return false; + } INFO_LOG(Log::Audio, "AudioClient3: default: %d fundamental: %d min: %d max: %d\n", (int)defaultPeriodFrames_, (int)fundamentalPeriodFrames_, (int)minPeriodFrames_, (int)maxPeriodFrames_); INFO_LOG(Log::Audio, "initializing with %d frame period at %d Hz, meaning %0.1fms\n", (int)minPeriodFrames_, (int)format_->nSamplesPerSec, FramesToMs(minPeriodFrames_, format_->nSamplesPerSec)); @@ -204,13 +240,39 @@ bool WASAPIContext::InitOutputDevice(std::string_view uniqueId, LatencyMode late if (FAILED(result)) { WARN_LOG(Log::Audio, "Error initializing AudioClient3 shared audio stream: %08lx", result); audioClient3_.Reset(); + CoTaskMemFree(format_); + format_ = nullptr; + SetErrorString("AudioClient3 init failed", hr); return false; } actualPeriodFrames_ = minPeriodFrames_; - audioClient3_->GetBufferSize(&reportedBufferSize_); - audioClient3_->SetEventHandle(audioEvent_); - audioClient3_->GetService(IID_PPV_ARGS(&renderClient_)); + hr = audioClient3_->GetBufferSize(&reportedBufferSize_); + if (FAILED(hr)) { + audioClient3_.Reset(); + CoTaskMemFree(format_); + format_ = nullptr; + SetErrorString("AudioClient3 GetBufferSize failed", hr); + return false; + } + + hr = audioClient3_->SetEventHandle(audioEvent_); + if (FAILED(hr)) { + audioClient3_.Reset(); + CoTaskMemFree(format_); + format_ = nullptr; + SetErrorString("AudioClient3 SetEventHandle failed", hr); + return false; + } + + hr = audioClient3_->GetService(IID_PPV_ARGS(&renderClient_)); + if (FAILED(hr)) { + audioClient3_.Reset(); + CoTaskMemFree(format_); + format_ = nullptr; + SetErrorString("AudioClient3 GetService failed", hr); + return false; + } } } @@ -218,7 +280,7 @@ bool WASAPIContext::InitOutputDevice(std::string_view uniqueId, LatencyMode late // Fallback to IAudioClient (older OS) HRESULT hr = device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void**)&audioClient_); if (FAILED(hr)) { - ERROR_LOG(Log::Audio, "Failed to activate audio device: %08lx", hr); + SetErrorString("Failed to activate audio device", hr); return false; } @@ -290,15 +352,40 @@ bool WASAPIContext::InitOutputDevice(std::string_view uniqueId, LatencyMode late ); if (FAILED(hr)) { - WARN_LOG(Log::Audio, "ERROR: Failed to initialize audio with all attempted buffer sizes\n"); audioClient_.Reset(); + CoTaskMemFree(format_); + format_ = nullptr; + SetErrorString("AudioClient init failed", hr); return false; } - audioClient_->GetBufferSize(&reportedBufferSize_); + hr = audioClient_->GetBufferSize(&reportedBufferSize_); + if (FAILED(hr)) { + audioClient_.Reset(); + CoTaskMemFree(format_); + format_ = nullptr; + SetErrorString("AudioClient GetBufferSize failed", hr); + return false; + } actualPeriodFrames_ = reportedBufferSize_; // we don't have a better estimate. - audioClient_->SetEventHandle(audioEvent_); - audioClient_->GetService(IID_PPV_ARGS(&renderClient_)); + + hr = audioClient_->SetEventHandle(audioEvent_); + if (FAILED(hr)) { + audioClient_.Reset(); + CoTaskMemFree(format_); + format_ = nullptr; + SetErrorString("AudioClient SetEventHandle failed", hr); + return false; + } + + hr = audioClient_->GetService(IID_PPV_ARGS(&renderClient_)); + if (FAILED(hr)) { + audioClient_.Reset(); + CoTaskMemFree(format_); + format_ = nullptr; + SetErrorString("AudioClient GetService failed", hr); + return false; + } if (createBuffer) { tempBuf_ = std::make_unique(reportedBufferSize_ * 2); @@ -345,6 +432,7 @@ void WASAPIContext::Stop() { { std::lock_guard guard(deviceLock_); curDeviceId_.clear(); + curDeviceName_.clear(); } } @@ -385,15 +473,34 @@ void WASAPIContext::AudioLoop() { } UINT32 available; + HRESULT hr; if (audioClient3_) { - audioClient3_->Start(); - audioClient3_->GetBufferSize(&available); + hr = audioClient3_->Start(); + if (FAILED(hr)) { + SetErrorString("AudioClient3::Start failed", hr); + return; + } + hr = audioClient3_->GetBufferSize(&available); + if (FAILED(hr)) { + SetErrorString("AudioClient3::GetBufferSize failed", hr); + audioClient3_->Stop(); + return; + } } else if (audioClient_) { - audioClient_->Start(); - audioClient_->GetBufferSize(&available); + hr = audioClient_->Start(); + if (FAILED(hr)) { + SetErrorString("AudioClient::Start failed", hr); + return; + } + hr = audioClient_->GetBufferSize(&available); + if (FAILED(hr)) { + SetErrorString("AudioClient::GetBufferSize failed", hr); + audioClient_->Stop(); + return; + } } else { // No audio client, nothing to do. - WARN_LOG(Log::Audio, "No audio client"); + SetErrorString("No audio client in AudioLoop", 0); return; } @@ -405,6 +512,8 @@ void WASAPIContext::AudioLoop() { const AudioFormat format = Classify(format_); const int nChannels = format_->nChannels; + ClearErrorString(); + while (running_) { const DWORD waitResult = WaitForSingleObject(audioEvent_, INFINITE); if (waitResult != WAIT_OBJECT_0) { diff --git a/Windows/WASAPIContext.h b/Windows/WASAPIContext.h index 777b986dfd..cc85b9459c 100644 --- a/Windows/WASAPIContext.h +++ b/Windows/WASAPIContext.h @@ -3,6 +3,7 @@ #include "Audio/AudioBackend.h" #include #include +#include #include #include @@ -62,10 +63,22 @@ public: std::string GetCurrentDeviceName() const override { std::lock_guard guard(deviceLock_); - return curDeviceId_; + return curDeviceName_ + ":" + curDeviceId_; + } + + std::string GetErrorString() const override { + std::string temp; + { + std::lock_guard guard(errorLock_); + temp = errorString_; + } + return temp; } private: + void SetErrorString(std::string_view str, HRESULT hr); + void ClearErrorString(); + void Start(); void Stop(); @@ -92,8 +105,11 @@ private: UINT32 minPeriodFrames_ = 0; UINT32 maxPeriodFrames_ = 0; std::atomic running_ = true; + + // NOTE: these do not need to be atomic, due to usage. UINT32 actualPeriodFrames_ = 0; // may not be the requested. UINT32 reportedBufferSize_ = 0; + Microsoft::WRL::ComPtr enumerator_; DeviceNotificationClient notificationClient_; RenderCallback callback_{}; @@ -101,9 +117,13 @@ private: LatencyMode latencyMode_ = LatencyMode::Aggressive; mutable std::mutex deviceLock_; + std::string curDeviceName_; std::string curDeviceId_; std::string newDeviceId_; bool defaultDeviceChanged_ = false; + mutable std::mutex errorLock_; + std::string errorString_; + std::unique_ptr tempBuf_; };