Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add the new low-latency WASAPI backend. Implement audio device select…
…ion on Windows
  • Loading branch information
hrydgard committed Jun 16, 2025
commit 6271af52e9dc8a4f378167c61deb4ec4d6a2a8f8
27 changes: 21 additions & 6 deletions Common/Audio/AudioBackend.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,31 @@
#include <string_view>
#include <vector>

// Always 2 channels, 16-bit audio.
typedef int (*StreamCallback)(short *buffer, int numSamples, int rate, void *userdata);
// For absolute minimal latency, we do not use std::function. Might be overthinking, but...
typedef void (*RenderCallback)(float *dest, int framesToWrite, int channels, int sampleRateHz, void *userdata);

enum class LatencyMode {
Safe,
Aggressive
};

struct AudioDeviceDesc {
std::string name; // User-friendly name
std::string uniqueId; // store-able ID for settings.
};

inline float FramesToMs(int frames, int sampleRate) {
return 1000.0f * (float)frames / (float)sampleRate;
}

// Note that the backend may override the passed in sample rate. The actual sample rate
// should be returned by SampleRate though.
class AudioBackend {
public:
virtual ~AudioBackend() {}
virtual bool Init(StreamCallback _callback, void *userdata = nullptr) = 0;
virtual void EnumerateDevices(std::vector<AudioDeviceDesc> *outputDevices, bool captureDevices = false) = 0;
virtual void SetRenderCallback(RenderCallback callback, void *userdata) = 0;
virtual bool InitOutputDevice(std::string_view uniqueId, LatencyMode latencyMode, bool *revertedToDefault) = 0;
virtual int SampleRate() const = 0;
virtual int BufferSize() const = 0;
virtual int PeriodFrames() const = 0;
virtual void FrameUpdate() {}
virtual void FrameUpdate(bool allowAutoChange) {}
};
25 changes: 21 additions & 4 deletions Common/UI/PopupScreens.h
Original file line number Diff line number Diff line change
Expand Up @@ -261,19 +261,30 @@ class PopupMultiChoice : public AbstractChoiceWithValueDisplay {
// Allows passing in a dynamic vector of strings. Saves the string.
class PopupMultiChoiceDynamic : public PopupMultiChoice {
public:
PopupMultiChoiceDynamic(std::string *value, std::string_view text, std::vector<std::string> choices,
I18NCat category, ScreenManager *screenManager, UI::LayoutParams *layoutParams = nullptr)
// TODO: This all is absolutely terrible, just done this way to be conformant with the internals of PopupMultiChoice.
PopupMultiChoiceDynamic(std::string *value, std::string_view text, const std::vector<std::string> &choices,
I18NCat category, ScreenManager *screenManager, std::vector<std::string> *values = nullptr, UI::LayoutParams *layoutParams = nullptr)
: UI::PopupMultiChoice(&valueInt_, text, nullptr, 0, (int)choices.size(), category, screenManager, layoutParams),
valueStr_(value) {
if (values) {
_dbg_assert_(choices.size() == values->size());
}
choices_ = new const char *[numChoices_];
valueInt_ = 0;
for (int i = 0; i < numChoices_; i++) {
choices_[i] = new char[choices[i].size() + 1];
memcpy((char *)choices_[i], choices[i].c_str(), choices[i].size() + 1);
if (values) {
if (*value == (*values)[i])
valueInt_ = i;
}
if (*value == choices_[i])
valueInt_ = i;
}
value_ = &valueInt_;
if (values) {
choiceValues_ = *values;
}
UpdateText();
}
~PopupMultiChoiceDynamic() {
Expand All @@ -288,8 +299,13 @@ class PopupMultiChoiceDynamic : public PopupMultiChoice {
if (!valueStr_) {
return true;
}
if (*valueStr_ != choices_[num]) {
*valueStr_ = choices_[num];
const char *value = choices_[num];
if (choiceValues_.size() == numChoices_) {
value = choiceValues_[num].c_str();
}

if (*valueStr_ != value) {
*valueStr_ = value;
return true;
} else {
return false;
Expand All @@ -299,6 +315,7 @@ class PopupMultiChoiceDynamic : public PopupMultiChoice {
private:
int valueInt_;
std::string *valueStr_;
std::vector<std::string> choiceValues_;
};

class PopupSliderChoice : public AbstractChoiceWithValueDisplay {
Expand Down
37 changes: 36 additions & 1 deletion UI/GameSettingsScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <set>

#include "Common/Net/Resolve.h"
#include "Common/Audio/AudioBackend.h"
#include "Common/GPU/OpenGL/GLFeatures.h"
#include "Common/Render/DrawBuffer.h"
#include "Common/UI/Root.h"
Expand Down Expand Up @@ -677,6 +678,7 @@ void GameSettingsScreen::CreateAudioSettings(UI::ViewGroup *audioSettings) {
audioSettings->Add(new ItemHeader(a->T("Audio backend")));

bool sdlAudio = false;

#if defined(SDL)
std::vector<std::string> audioDeviceList;
SplitString(System_GetProperty(SYSPROP_AUDIO_DEVICE_LIST), '\0', audioDeviceList);
Expand All @@ -695,12 +697,45 @@ void GameSettingsScreen::CreateAudioSettings(UI::ViewGroup *audioSettings) {
#endif

#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)

extern AudioBackend *g_audioBackend;

std::vector<std::string> audioDeviceNames;
std::vector<std::string> audioDeviceIds;

std::vector<AudioDeviceDesc> deviceDescs;
g_audioBackend->EnumerateDevices(&deviceDescs);
for (auto &desc : deviceDescs) {
audioDeviceNames.push_back(desc.name);
audioDeviceIds.push_back(desc.uniqueId);
}

audioDeviceNames.insert(audioDeviceNames.begin(), std::string(a->T("Auto")));
audioDeviceIds.insert(audioDeviceIds.begin(), "");

PopupMultiChoiceDynamic *audioDevice = audioSettings->Add(new PopupMultiChoiceDynamic(&g_Config.sAudioDevice, a->T("Device"), audioDeviceNames, I18NCat::NONE, screenManager(), &audioDeviceIds));
audioDevice->OnChoice.Add([this](UI::EventParams &) {
bool reverted;
if (g_audioBackend->InitOutputDevice(g_Config.sAudioDevice, LatencyMode::Aggressive, &reverted)) {
if (reverted) {
WARN_LOG(Log::Audio, "Unexpected: After a direct choice, audio device reverted to default. '%s'", g_Config.sAudioDevice.c_str());
}
} else {
WARN_LOG(Log::Audio, "InitOutputDevice failed");
}
return UI::EVENT_DONE;
});
CheckBox *autoAudio = audioSettings->Add(new CheckBox(&g_Config.bAutoAudioDevice, a->T("Use new audio devices automatically")));
autoAudio->SetEnabledFunc([]()->bool {
return g_Config.sAudioDevice.empty();
});

const bool isWindows = true;
#else
const bool isWindows = false;
#endif

if (sdlAudio || isWindows) {
if (sdlAudio) {
audioSettings->Add(new CheckBox(&g_Config.bAutoAudioDevice, a->T("Use new audio devices automatically")));
}

Expand Down
25 changes: 23 additions & 2 deletions UI/NativeApp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,22 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch
void CallbackPostRender(UIContext *dc, void *userdata);
bool CreateGlobalPipelines();

void NativeMixWrapper(float *dest, int framesToWrite, int channels, int sampleRateHz, void *userdata) {
static int16_t *buffer;
static int bufSize;
if (bufSize < framesToWrite * channels) {
buffer = new int16_t[framesToWrite * channels];
bufSize = framesToWrite * channels;
}

NativeMix(buffer, framesToWrite, sampleRateHz, userdata);

for (int i = 0; i < framesToWrite * channels; i++) {
int16_t value = buffer[i];
dest[i] = (float)value * (float)(1.0f / 32767.0f);
}
}

bool NativeInitGraphics(GraphicsContext *graphicsContext) {
INFO_LOG(Log::System, "NativeInitGraphics");

Expand Down Expand Up @@ -840,7 +856,12 @@ bool NativeInitGraphics(GraphicsContext *graphicsContext) {

g_audioBackend = System_CreateAudioBackend();
if (g_audioBackend) {
g_audioBackend->Init(&NativeMix);
g_audioBackend->SetRenderCallback(&NativeMixWrapper, nullptr);
bool reverted = false;
g_audioBackend->InitOutputDevice(g_Config.sAudioDevice, LatencyMode::Aggressive, &reverted);
if (reverted) {
g_Config.sAudioDevice.clear();
}
}

#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP)
Expand Down Expand Up @@ -1093,7 +1114,7 @@ void NativeFrame(GraphicsContext *graphicsContext) {
g_screenManager->update();

if (g_audioBackend) {
g_audioBackend->FrameUpdate();
g_audioBackend->FrameUpdate(g_Config.bAutoAudioDevice);
}

// Do this after g_screenManager.update() so we can receive setting changes before rendering.
Expand Down
12 changes: 6 additions & 6 deletions Windows/PPSSPP.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<AdditionalDependencies>wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>avrt.lib;mmdevapi.lib;wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>../ffmpeg/Windows/x86/lib</AdditionalLibraryDirectories>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
Expand Down Expand Up @@ -235,7 +235,7 @@
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<AdditionalDependencies>wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>avrt.lib;mmdevapi.lib;wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>../ffmpeg/Windows/x86_64/lib</AdditionalLibraryDirectories>
<GenerateDebugInformation>true</GenerateDebugInformation>
<ProgramDatabaseFile>$(OutDir)$(ProjectName).pdb</ProgramDatabaseFile>
Expand Down Expand Up @@ -271,7 +271,7 @@
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<AdditionalDependencies>wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>avrt.lib;mmdevapi.lib;wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>../ffmpeg/Windows/aarch64/lib</AdditionalLibraryDirectories>
<GenerateDebugInformation>true</GenerateDebugInformation>
<ProgramDatabaseFile>$(OutDir)$(ProjectName).pdb</ProgramDatabaseFile>
Expand Down Expand Up @@ -309,7 +309,7 @@
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<AdditionalDependencies>wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>avrt.lib;mmdevapi.lib;wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>../ffmpeg/Windows/x86/lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
Expand Down Expand Up @@ -358,7 +358,7 @@
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<AdditionalDependencies>wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>avrt.lib;mmdevapi.lib;wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;opengl32.lib;glu32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>../ffmpeg/Windows/x86_64/lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
Expand Down Expand Up @@ -401,7 +401,7 @@
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<AdditionalDependencies>wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>avrt.lib;mmdevapi.lib;wbemuuid.lib;dwmapi.lib;winhttp.lib;uxtheme.lib;mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;comctl32.lib;dxguid.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>../ffmpeg/Windows/aarch64/lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
Expand Down
Loading
Loading