mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-07-11 01:25:07 +02:00
WASAPI: Improve error handling and sysinfo display
This commit is contained in:
@@ -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
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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_;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user