WASAPI: Improve error handling and sysinfo display

This commit is contained in:
Henrik Rydgård
2026-03-14 23:42:49 +01:00
parent 0d2f7055fd
commit 2cc8e87389
6 changed files with 187 additions and 40 deletions
+1
View File
@@ -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) {}
};
+34 -18
View File
@@ -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;
+1
View File
@@ -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);
+1 -1
View File
@@ -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);
+129 -20
View File
@@ -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<AudioDeviceDesc> *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<std::mutex> guard(errorLock_);
errorString_ = temp;
}
void WASAPIContext::ClearErrorString() {
std::lock_guard<std::mutex> 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<IMMDevice> 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<std::mutex> 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<float[]>(reportedBufferSize_ * 2);
@@ -345,6 +432,7 @@ void WASAPIContext::Stop() {
{
std::lock_guard<std::mutex> 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) {
+21 -1
View File
@@ -3,6 +3,7 @@
#include "Audio/AudioBackend.h"
#include <atomic>
#include <mutex>
#include <string>
#include <windows.h>
#include <mmdeviceapi.h>
@@ -62,10 +63,22 @@ public:
std::string GetCurrentDeviceName() const override {
std::lock_guard<std::mutex> guard(deviceLock_);
return curDeviceId_;
return curDeviceName_ + ":" + curDeviceId_;
}
std::string GetErrorString() const override {
std::string temp;
{
std::lock_guard<std::mutex> 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<bool> 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<IMMDeviceEnumerator> 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<float[]> tempBuf_;
};