mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-08-05 17:02:40 +00:00
Merge branch 'shadps4-emu:main' into qt-style-test
This commit is contained in:
commit
776f1ee5ec
@ -65,6 +65,8 @@ static bool vkCrashDiagnostic = false;
|
|||||||
static s16 cursorState = HideCursorState::Idle;
|
static s16 cursorState = HideCursorState::Idle;
|
||||||
static int cursorHideTimeout = 5; // 5 seconds (default)
|
static int cursorHideTimeout = 5; // 5 seconds (default)
|
||||||
static bool separateupdatefolder = false;
|
static bool separateupdatefolder = false;
|
||||||
|
static bool compatibilityData = false;
|
||||||
|
static bool checkCompatibilityOnStartup = false;
|
||||||
|
|
||||||
// Gui
|
// Gui
|
||||||
std::vector<std::filesystem::path> settings_install_dirs = {};
|
std::vector<std::filesystem::path> settings_install_dirs = {};
|
||||||
@ -229,6 +231,14 @@ bool getSeparateUpdateEnabled() {
|
|||||||
return separateupdatefolder;
|
return separateupdatefolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool getCompatibilityEnabled() {
|
||||||
|
return compatibilityData;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getCheckCompatibilityOnStartup() {
|
||||||
|
return checkCompatibilityOnStartup;
|
||||||
|
}
|
||||||
|
|
||||||
void setGpuId(s32 selectedGpuId) {
|
void setGpuId(s32 selectedGpuId) {
|
||||||
gpuId = selectedGpuId;
|
gpuId = selectedGpuId;
|
||||||
}
|
}
|
||||||
@ -353,6 +363,14 @@ void setSeparateUpdateEnabled(bool use) {
|
|||||||
separateupdatefolder = use;
|
separateupdatefolder = use;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setCompatibilityEnabled(bool use) {
|
||||||
|
compatibilityData = use;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCheckCompatibilityOnStartup(bool use) {
|
||||||
|
checkCompatibilityOnStartup = use;
|
||||||
|
}
|
||||||
|
|
||||||
void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) {
|
void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) {
|
||||||
main_window_geometry_x = x;
|
main_window_geometry_x = x;
|
||||||
main_window_geometry_y = y;
|
main_window_geometry_y = y;
|
||||||
@ -553,6 +571,9 @@ void load(const std::filesystem::path& path) {
|
|||||||
isShowSplash = toml::find_or<bool>(general, "showSplash", true);
|
isShowSplash = toml::find_or<bool>(general, "showSplash", true);
|
||||||
isAutoUpdate = toml::find_or<bool>(general, "autoUpdate", false);
|
isAutoUpdate = toml::find_or<bool>(general, "autoUpdate", false);
|
||||||
separateupdatefolder = toml::find_or<bool>(general, "separateUpdateEnabled", false);
|
separateupdatefolder = toml::find_or<bool>(general, "separateUpdateEnabled", false);
|
||||||
|
compatibilityData = toml::find_or<bool>(general, "compatibilityEnabled", false);
|
||||||
|
checkCompatibilityOnStartup =
|
||||||
|
toml::find_or<bool>(general, "checkCompatibilityOnStartup", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.contains("Input")) {
|
if (data.contains("Input")) {
|
||||||
@ -666,6 +687,8 @@ void save(const std::filesystem::path& path) {
|
|||||||
data["General"]["showSplash"] = isShowSplash;
|
data["General"]["showSplash"] = isShowSplash;
|
||||||
data["General"]["autoUpdate"] = isAutoUpdate;
|
data["General"]["autoUpdate"] = isAutoUpdate;
|
||||||
data["General"]["separateUpdateEnabled"] = separateupdatefolder;
|
data["General"]["separateUpdateEnabled"] = separateupdatefolder;
|
||||||
|
data["General"]["compatibilityEnabled"] = compatibilityData;
|
||||||
|
data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup;
|
||||||
data["Input"]["cursorState"] = cursorState;
|
data["Input"]["cursorState"] = cursorState;
|
||||||
data["Input"]["cursorHideTimeout"] = cursorHideTimeout;
|
data["Input"]["cursorHideTimeout"] = cursorHideTimeout;
|
||||||
data["Input"]["backButtonBehavior"] = backButtonBehavior;
|
data["Input"]["backButtonBehavior"] = backButtonBehavior;
|
||||||
@ -787,6 +810,8 @@ void setDefaultValues() {
|
|||||||
m_language = 1;
|
m_language = 1;
|
||||||
gpuId = -1;
|
gpuId = -1;
|
||||||
separateupdatefolder = false;
|
separateupdatefolder = false;
|
||||||
|
compatibilityData = false;
|
||||||
|
checkCompatibilityOnStartup = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Config
|
} // namespace Config
|
||||||
|
@ -21,6 +21,8 @@ bool getPlayBGM();
|
|||||||
int getBGMvolume();
|
int getBGMvolume();
|
||||||
bool getEnableDiscordRPC();
|
bool getEnableDiscordRPC();
|
||||||
bool getSeparateUpdateEnabled();
|
bool getSeparateUpdateEnabled();
|
||||||
|
bool getCompatibilityEnabled();
|
||||||
|
bool getCheckCompatibilityOnStartup();
|
||||||
|
|
||||||
std::string getLogFilter();
|
std::string getLogFilter();
|
||||||
std::string getLogType();
|
std::string getLogType();
|
||||||
@ -70,6 +72,8 @@ void setUserName(const std::string& type);
|
|||||||
void setUpdateChannel(const std::string& type);
|
void setUpdateChannel(const std::string& type);
|
||||||
void setSeparateUpdateEnabled(bool use);
|
void setSeparateUpdateEnabled(bool use);
|
||||||
void setGameInstallDirs(const std::vector<std::filesystem::path>& settings_install_dirs_config);
|
void setGameInstallDirs(const std::vector<std::filesystem::path>& settings_install_dirs_config);
|
||||||
|
void setCompatibilityEnabled(bool use);
|
||||||
|
void setCheckCompatibilityOnStartup(bool use);
|
||||||
|
|
||||||
void setCursorState(s16 cursorState);
|
void setCursorState(s16 cursorState);
|
||||||
void setCursorHideTimeout(int newcursorHideTimeout);
|
void setCursorHideTimeout(int newcursorHideTimeout);
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <shared_mutex>
|
||||||
#include <magic_enum/magic_enum.hpp>
|
#include <magic_enum/magic_enum.hpp>
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
@ -13,7 +15,22 @@
|
|||||||
|
|
||||||
namespace Libraries::AudioOut {
|
namespace Libraries::AudioOut {
|
||||||
|
|
||||||
static std::unique_ptr<SDLAudioOut> audio;
|
struct PortOut {
|
||||||
|
void* impl;
|
||||||
|
u32 samples_num;
|
||||||
|
u32 freq;
|
||||||
|
OrbisAudioOutParamFormat format;
|
||||||
|
OrbisAudioOutPort type;
|
||||||
|
int channels_num;
|
||||||
|
bool is_float;
|
||||||
|
std::array<int, 8> volume;
|
||||||
|
u8 sample_size;
|
||||||
|
bool is_open;
|
||||||
|
};
|
||||||
|
std::shared_mutex ports_mutex;
|
||||||
|
std::array<PortOut, SCE_AUDIO_OUT_NUM_PORTS> ports_out{};
|
||||||
|
|
||||||
|
static std::unique_ptr<AudioOutBackend> audio;
|
||||||
|
|
||||||
static std::string_view GetAudioOutPort(OrbisAudioOutPort port) {
|
static std::string_view GetAudioOutPort(OrbisAudioOutPort port) {
|
||||||
switch (port) {
|
switch (port) {
|
||||||
@ -70,6 +87,58 @@ static std::string_view GetAudioOutParamAttr(OrbisAudioOutParamAttr attr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool IsFormatFloat(const OrbisAudioOutParamFormat format) {
|
||||||
|
switch (format) {
|
||||||
|
case OrbisAudioOutParamFormat::S16Mono:
|
||||||
|
case OrbisAudioOutParamFormat::S16Stereo:
|
||||||
|
case OrbisAudioOutParamFormat::S16_8CH:
|
||||||
|
case OrbisAudioOutParamFormat::S16_8CH_Std:
|
||||||
|
return false;
|
||||||
|
case OrbisAudioOutParamFormat::FloatMono:
|
||||||
|
case OrbisAudioOutParamFormat::FloatStereo:
|
||||||
|
case OrbisAudioOutParamFormat::Float_8CH:
|
||||||
|
case OrbisAudioOutParamFormat::Float_8CH_Std:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
UNREACHABLE_MSG("Unknown format");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int GetFormatNumChannels(const OrbisAudioOutParamFormat format) {
|
||||||
|
switch (format) {
|
||||||
|
case OrbisAudioOutParamFormat::S16Mono:
|
||||||
|
case OrbisAudioOutParamFormat::FloatMono:
|
||||||
|
return 1;
|
||||||
|
case OrbisAudioOutParamFormat::S16Stereo:
|
||||||
|
case OrbisAudioOutParamFormat::FloatStereo:
|
||||||
|
return 2;
|
||||||
|
case OrbisAudioOutParamFormat::S16_8CH:
|
||||||
|
case OrbisAudioOutParamFormat::Float_8CH:
|
||||||
|
case OrbisAudioOutParamFormat::S16_8CH_Std:
|
||||||
|
case OrbisAudioOutParamFormat::Float_8CH_Std:
|
||||||
|
return 8;
|
||||||
|
default:
|
||||||
|
UNREACHABLE_MSG("Unknown format");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static u8 GetFormatSampleSize(const OrbisAudioOutParamFormat format) {
|
||||||
|
switch (format) {
|
||||||
|
case OrbisAudioOutParamFormat::S16Mono:
|
||||||
|
case OrbisAudioOutParamFormat::S16Stereo:
|
||||||
|
case OrbisAudioOutParamFormat::S16_8CH:
|
||||||
|
case OrbisAudioOutParamFormat::S16_8CH_Std:
|
||||||
|
return 2;
|
||||||
|
case OrbisAudioOutParamFormat::FloatMono:
|
||||||
|
case OrbisAudioOutParamFormat::FloatStereo:
|
||||||
|
case OrbisAudioOutParamFormat::Float_8CH:
|
||||||
|
case OrbisAudioOutParamFormat::Float_8CH_Std:
|
||||||
|
return 4;
|
||||||
|
default:
|
||||||
|
UNREACHABLE_MSG("Unknown format");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAudioOutDeviceIdOpen() {
|
int PS4_SYSV_ABI sceAudioOutDeviceIdOpen() {
|
||||||
LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
|
LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
@ -110,8 +179,21 @@ int PS4_SYSV_ABI sceAudioOutChangeAppModuleState() {
|
|||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAudioOutClose() {
|
int PS4_SYSV_ABI sceAudioOutClose(s32 handle) {
|
||||||
LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
|
LOG_INFO(Lib_AudioOut, "handle = {}", handle);
|
||||||
|
if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) {
|
||||||
|
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::scoped_lock lock(ports_mutex);
|
||||||
|
auto& port = ports_out.at(handle - 1);
|
||||||
|
if (!port.is_open) {
|
||||||
|
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio->Close(port.impl);
|
||||||
|
port.impl = nullptr;
|
||||||
|
port.is_open = false;
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,16 +262,21 @@ int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* sta
|
|||||||
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
|
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto [type, channels_num] = audio->GetStatus(handle);
|
std::scoped_lock lock(ports_mutex);
|
||||||
|
const auto& port = ports_out.at(handle - 1);
|
||||||
|
if (!port.is_open) {
|
||||||
|
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
|
||||||
|
}
|
||||||
|
|
||||||
state->rerouteCounter = 0;
|
state->rerouteCounter = 0;
|
||||||
state->volume = 127;
|
state->volume = 127;
|
||||||
|
|
||||||
switch (type) {
|
switch (port.type) {
|
||||||
case OrbisAudioOutPort::Main:
|
case OrbisAudioOutPort::Main:
|
||||||
case OrbisAudioOutPort::Bgm:
|
case OrbisAudioOutPort::Bgm:
|
||||||
case OrbisAudioOutPort::Voice:
|
case OrbisAudioOutPort::Voice:
|
||||||
state->output = 1;
|
state->output = 1;
|
||||||
state->channel = (channels_num > 2 ? 2 : channels_num);
|
state->channel = port.channels_num > 2 ? 2 : port.channels_num;
|
||||||
break;
|
break;
|
||||||
case OrbisAudioOutPort::Personal:
|
case OrbisAudioOutPort::Personal:
|
||||||
case OrbisAudioOutPort::Padspk:
|
case OrbisAudioOutPort::Padspk:
|
||||||
@ -276,7 +363,7 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id,
|
|||||||
u32 sample_rate,
|
u32 sample_rate,
|
||||||
OrbisAudioOutParamExtendedInformation param_type) {
|
OrbisAudioOutParamExtendedInformation param_type) {
|
||||||
LOG_INFO(Lib_AudioOut,
|
LOG_INFO(Lib_AudioOut,
|
||||||
"AudioOutOpen id = {} port_type = {} index = {} lenght= {} sample_rate = {} "
|
"id = {} port_type = {} index = {} length = {} sample_rate = {} "
|
||||||
"param_type = {} attr = {}",
|
"param_type = {} attr = {}",
|
||||||
user_id, GetAudioOutPort(port_type), index, length, sample_rate,
|
user_id, GetAudioOutPort(port_type), index, length, sample_rate,
|
||||||
GetAudioOutParamFormat(param_type.data_format),
|
GetAudioOutParamFormat(param_type.data_format),
|
||||||
@ -310,7 +397,26 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id,
|
|||||||
LOG_ERROR(Lib_AudioOut, "Invalid format attribute");
|
LOG_ERROR(Lib_AudioOut, "Invalid format attribute");
|
||||||
return ORBIS_AUDIO_OUT_ERROR_INVALID_FORMAT;
|
return ORBIS_AUDIO_OUT_ERROR_INVALID_FORMAT;
|
||||||
}
|
}
|
||||||
return audio->Open(port_type, length, sample_rate, format);
|
|
||||||
|
std::scoped_lock lock{ports_mutex};
|
||||||
|
const auto port = std::ranges::find(ports_out, false, &PortOut::is_open);
|
||||||
|
if (port == ports_out.end()) {
|
||||||
|
LOG_ERROR(Lib_AudioOut, "Audio ports are full");
|
||||||
|
return ORBIS_AUDIO_OUT_ERROR_PORT_FULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
port->is_open = true;
|
||||||
|
port->type = port_type;
|
||||||
|
port->samples_num = length;
|
||||||
|
port->freq = sample_rate;
|
||||||
|
port->format = format;
|
||||||
|
port->is_float = IsFormatFloat(format);
|
||||||
|
port->channels_num = GetFormatNumChannels(format);
|
||||||
|
port->sample_size = GetFormatSampleSize(format);
|
||||||
|
port->volume.fill(SCE_AUDIO_OUT_VOLUME_0DB);
|
||||||
|
|
||||||
|
port->impl = audio->Open(port->is_float, port->channels_num, port->freq);
|
||||||
|
return std::distance(ports_out.begin(), port) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAudioOutOpenEx() {
|
int PS4_SYSV_ABI sceAudioOutOpenEx() {
|
||||||
@ -326,7 +432,15 @@ s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, const void* ptr) {
|
|||||||
// Nothing to output
|
// Nothing to output
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
return audio->Output(handle, ptr);
|
|
||||||
|
auto& port = ports_out.at(handle - 1);
|
||||||
|
if (!port.is_open) {
|
||||||
|
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t data_size = port.samples_num * port.sample_size * port.channels_num;
|
||||||
|
audio->Output(port.impl, ptr, data_size);
|
||||||
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num) {
|
int PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num) {
|
||||||
@ -431,7 +545,42 @@ s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol) {
|
|||||||
if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) {
|
if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) {
|
||||||
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
|
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
|
||||||
}
|
}
|
||||||
return audio->SetVolume(handle, flag, vol);
|
|
||||||
|
std::scoped_lock lock(ports_mutex);
|
||||||
|
auto& port = ports_out.at(handle - 1);
|
||||||
|
if (!port.is_open) {
|
||||||
|
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < port.channels_num; i++, flag >>= 1u) {
|
||||||
|
auto bit = flag & 0x1u;
|
||||||
|
if (bit == 1) {
|
||||||
|
int src_index = i;
|
||||||
|
if (port.format == OrbisAudioOutParamFormat::Float_8CH_Std ||
|
||||||
|
port.format == OrbisAudioOutParamFormat::S16_8CH_Std) {
|
||||||
|
switch (i) {
|
||||||
|
case 4:
|
||||||
|
src_index = 6;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
src_index = 7;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
src_index = 4;
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
src_index = 5;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
port.volume[i] = vol[src_index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
audio->SetVolume(port.impl, port.volume);
|
||||||
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAudioOutSetVolumeDown() {
|
int PS4_SYSV_ABI sceAudioOutSetVolumeDown() {
|
||||||
|
@ -64,7 +64,7 @@ int PS4_SYSV_ABI sceAudioOutA3dExit();
|
|||||||
int PS4_SYSV_ABI sceAudioOutA3dInit();
|
int PS4_SYSV_ABI sceAudioOutA3dInit();
|
||||||
int PS4_SYSV_ABI sceAudioOutAttachToApplicationByPid();
|
int PS4_SYSV_ABI sceAudioOutAttachToApplicationByPid();
|
||||||
int PS4_SYSV_ABI sceAudioOutChangeAppModuleState();
|
int PS4_SYSV_ABI sceAudioOutChangeAppModuleState();
|
||||||
int PS4_SYSV_ABI sceAudioOutClose();
|
int PS4_SYSV_ABI sceAudioOutClose(s32 handle);
|
||||||
int PS4_SYSV_ABI sceAudioOutDetachFromApplicationByPid();
|
int PS4_SYSV_ABI sceAudioOutDetachFromApplicationByPid();
|
||||||
int PS4_SYSV_ABI sceAudioOutExConfigureOutputMode();
|
int PS4_SYSV_ABI sceAudioOutExConfigureOutputMode();
|
||||||
int PS4_SYSV_ABI sceAudioOutExGetSystemInfo();
|
int PS4_SYSV_ABI sceAudioOutExGetSystemInfo();
|
||||||
|
19
src/core/libraries/audio/audioout_backend.h
Normal file
19
src/core/libraries/audio/audioout_backend.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Libraries::AudioOut {
|
||||||
|
|
||||||
|
class AudioOutBackend {
|
||||||
|
public:
|
||||||
|
AudioOutBackend() = default;
|
||||||
|
virtual ~AudioOutBackend() = default;
|
||||||
|
|
||||||
|
virtual void* Open(bool is_float, int num_channels, u32 sample_rate) = 0;
|
||||||
|
virtual void Close(void* impl) = 0;
|
||||||
|
virtual void Output(void* impl, const void* ptr, size_t size) = 0;
|
||||||
|
virtual void SetVolume(void* impl, std::array<int, 8> ch_volumes) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Libraries::AudioOut
|
@ -1,141 +1,44 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
#include <SDL3/SDL_audio.h>
|
#include <SDL3/SDL_audio.h>
|
||||||
#include <SDL3/SDL_init.h>
|
#include <SDL3/SDL_init.h>
|
||||||
#include <SDL3/SDL_timer.h>
|
#include <SDL3/SDL_timer.h>
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "core/libraries/audio/audioout_error.h"
|
|
||||||
#include "core/libraries/audio/sdl_audio.h"
|
#include "core/libraries/audio/sdl_audio.h"
|
||||||
|
|
||||||
namespace Libraries::AudioOut {
|
namespace Libraries::AudioOut {
|
||||||
|
|
||||||
constexpr int AUDIO_STREAM_BUFFER_THRESHOLD = 65536; // Define constant for buffer threshold
|
constexpr int AUDIO_STREAM_BUFFER_THRESHOLD = 65536; // Define constant for buffer threshold
|
||||||
|
|
||||||
s32 SDLAudioOut::Open(OrbisAudioOutPort type, u32 samples_num, u32 freq,
|
void* SDLAudioOut::Open(bool is_float, int num_channels, u32 sample_rate) {
|
||||||
OrbisAudioOutParamFormat format) {
|
|
||||||
std::scoped_lock lock{m_mutex};
|
|
||||||
const auto port = std::ranges::find(ports_out, false, &PortOut::is_open);
|
|
||||||
if (port == ports_out.end()) {
|
|
||||||
LOG_ERROR(Lib_AudioOut, "Audio ports are full");
|
|
||||||
return ORBIS_AUDIO_OUT_ERROR_PORT_FULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
port->is_open = true;
|
|
||||||
port->type = type;
|
|
||||||
port->samples_num = samples_num;
|
|
||||||
port->freq = freq;
|
|
||||||
port->format = format;
|
|
||||||
SDL_AudioFormat sampleFormat;
|
|
||||||
switch (format) {
|
|
||||||
case OrbisAudioOutParamFormat::S16Mono:
|
|
||||||
sampleFormat = SDL_AUDIO_S16;
|
|
||||||
port->channels_num = 1;
|
|
||||||
port->sample_size = 2;
|
|
||||||
break;
|
|
||||||
case OrbisAudioOutParamFormat::FloatMono:
|
|
||||||
sampleFormat = SDL_AUDIO_F32;
|
|
||||||
port->channels_num = 1;
|
|
||||||
port->sample_size = 4;
|
|
||||||
break;
|
|
||||||
case OrbisAudioOutParamFormat::S16Stereo:
|
|
||||||
sampleFormat = SDL_AUDIO_S16;
|
|
||||||
port->channels_num = 2;
|
|
||||||
port->sample_size = 2;
|
|
||||||
break;
|
|
||||||
case OrbisAudioOutParamFormat::FloatStereo:
|
|
||||||
sampleFormat = SDL_AUDIO_F32;
|
|
||||||
port->channels_num = 2;
|
|
||||||
port->sample_size = 4;
|
|
||||||
break;
|
|
||||||
case OrbisAudioOutParamFormat::S16_8CH:
|
|
||||||
sampleFormat = SDL_AUDIO_S16;
|
|
||||||
port->channels_num = 8;
|
|
||||||
port->sample_size = 2;
|
|
||||||
break;
|
|
||||||
case OrbisAudioOutParamFormat::Float_8CH:
|
|
||||||
sampleFormat = SDL_AUDIO_F32;
|
|
||||||
port->channels_num = 8;
|
|
||||||
port->sample_size = 4;
|
|
||||||
break;
|
|
||||||
case OrbisAudioOutParamFormat::S16_8CH_Std:
|
|
||||||
sampleFormat = SDL_AUDIO_S16;
|
|
||||||
port->channels_num = 8;
|
|
||||||
port->sample_size = 2;
|
|
||||||
break;
|
|
||||||
case OrbisAudioOutParamFormat::Float_8CH_Std:
|
|
||||||
sampleFormat = SDL_AUDIO_F32;
|
|
||||||
port->channels_num = 8;
|
|
||||||
port->sample_size = 4;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
UNREACHABLE_MSG("Unknown format");
|
|
||||||
}
|
|
||||||
|
|
||||||
port->volume.fill(Libraries::AudioOut::SCE_AUDIO_OUT_VOLUME_0DB);
|
|
||||||
|
|
||||||
SDL_AudioSpec fmt;
|
SDL_AudioSpec fmt;
|
||||||
SDL_zero(fmt);
|
SDL_zero(fmt);
|
||||||
fmt.format = sampleFormat;
|
fmt.format = is_float ? SDL_AUDIO_F32 : SDL_AUDIO_S16;
|
||||||
fmt.channels = port->channels_num;
|
fmt.channels = num_channels;
|
||||||
fmt.freq = freq;
|
fmt.freq = sample_rate;
|
||||||
port->stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &fmt, NULL, NULL);
|
|
||||||
SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(port->stream));
|
auto* stream =
|
||||||
return std::distance(ports_out.begin(), port) + 1;
|
SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &fmt, nullptr, nullptr);
|
||||||
|
SDL_ResumeAudioStreamDevice(stream);
|
||||||
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 SDLAudioOut::Output(s32 handle, const void* ptr) {
|
void SDLAudioOut::Close(void* impl) {
|
||||||
auto& port = ports_out.at(handle - 1);
|
SDL_DestroyAudioStream(static_cast<SDL_AudioStream*>(impl));
|
||||||
if (!port.is_open) {
|
}
|
||||||
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size_t data_size = port.samples_num * port.sample_size * port.channels_num;
|
void SDLAudioOut::Output(void* impl, const void* ptr, size_t size) {
|
||||||
bool result = SDL_PutAudioStreamData(port.stream, ptr, data_size);
|
auto* stream = static_cast<SDL_AudioStream*>(impl);
|
||||||
while (SDL_GetAudioStreamAvailable(port.stream) > AUDIO_STREAM_BUFFER_THRESHOLD) {
|
SDL_PutAudioStreamData(stream, ptr, size);
|
||||||
|
while (SDL_GetAudioStreamAvailable(stream) > AUDIO_STREAM_BUFFER_THRESHOLD) {
|
||||||
SDL_Delay(0);
|
SDL_Delay(0);
|
||||||
}
|
}
|
||||||
return result ? ORBIS_OK : -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 SDLAudioOut::SetVolume(s32 handle, s32 bitflag, s32* volume) {
|
void SDLAudioOut::SetVolume(void* impl, std::array<int, 8> ch_volumes) {
|
||||||
using Libraries::AudioOut::OrbisAudioOutParamFormat;
|
// Not yet implemented
|
||||||
auto& port = ports_out.at(handle - 1);
|
|
||||||
if (!port.is_open) {
|
|
||||||
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < port.channels_num; i++, bitflag >>= 1u) {
|
|
||||||
auto bit = bitflag & 0x1u;
|
|
||||||
|
|
||||||
if (bit == 1) {
|
|
||||||
int src_index = i;
|
|
||||||
if (port.format == OrbisAudioOutParamFormat::Float_8CH_Std ||
|
|
||||||
port.format == OrbisAudioOutParamFormat::S16_8CH_Std) {
|
|
||||||
switch (i) {
|
|
||||||
case 4:
|
|
||||||
src_index = 6;
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
src_index = 7;
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
src_index = 4;
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
src_index = 5;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
port.volume[i] = volume[src_index];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ORBIS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Libraries::AudioOut
|
} // namespace Libraries::AudioOut
|
||||||
|
@ -3,40 +3,16 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <shared_mutex>
|
#include "core/libraries/audio/audioout_backend.h"
|
||||||
#include <SDL3/SDL_audio.h>
|
|
||||||
#include "core/libraries/audio/audioout.h"
|
|
||||||
|
|
||||||
namespace Libraries::AudioOut {
|
namespace Libraries::AudioOut {
|
||||||
|
|
||||||
class SDLAudioOut {
|
class SDLAudioOut final : public AudioOutBackend {
|
||||||
public:
|
public:
|
||||||
explicit SDLAudioOut() = default;
|
void* Open(bool is_float, int num_channels, u32 sample_rate) override;
|
||||||
~SDLAudioOut() = default;
|
void Close(void* impl) override;
|
||||||
|
void Output(void* impl, const void* ptr, size_t size) override;
|
||||||
s32 Open(OrbisAudioOutPort type, u32 samples_num, u32 freq, OrbisAudioOutParamFormat format);
|
void SetVolume(void* impl, std::array<int, 8> ch_volumes) override;
|
||||||
s32 Output(s32 handle, const void* ptr);
|
|
||||||
s32 SetVolume(s32 handle, s32 bitflag, s32* volume);
|
|
||||||
|
|
||||||
constexpr std::pair<OrbisAudioOutPort, int> GetStatus(s32 handle) const {
|
|
||||||
const auto& port = ports_out.at(handle - 1);
|
|
||||||
return std::make_pair(port.type, port.channels_num);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct PortOut {
|
|
||||||
SDL_AudioStream* stream;
|
|
||||||
u32 samples_num;
|
|
||||||
u32 freq;
|
|
||||||
OrbisAudioOutParamFormat format;
|
|
||||||
OrbisAudioOutPort type;
|
|
||||||
int channels_num;
|
|
||||||
std::array<int, 8> volume;
|
|
||||||
u8 sample_size;
|
|
||||||
bool is_open;
|
|
||||||
};
|
|
||||||
std::shared_mutex m_mutex;
|
|
||||||
std::array<PortOut, Libraries::AudioOut::SCE_AUDIO_OUT_NUM_PORTS> ports_out{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Libraries::AudioOut
|
} // namespace Libraries::AudioOut
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QProgressDialog>
|
#include <QProgressDialog>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
#include "common/path_util.h"
|
#include "common/path_util.h"
|
||||||
#include "compatibility_info.h"
|
#include "compatibility_info.h"
|
||||||
@ -22,7 +23,8 @@ void CompatibilityInfoClass::UpdateCompatibilityDatabase(QWidget* parent) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
QNetworkReply* reply = FetchPage(1);
|
QNetworkReply* reply = FetchPage(1);
|
||||||
WaitForReply(reply);
|
if (!WaitForReply(reply))
|
||||||
|
return;
|
||||||
|
|
||||||
QProgressDialog dialog(tr("Fetching compatibility data, please wait"), tr("Cancel"), 0, 0,
|
QProgressDialog dialog(tr("Fetching compatibility data, please wait"), tr("Cancel"), 0, 0,
|
||||||
parent);
|
parent);
|
||||||
@ -57,12 +59,17 @@ void CompatibilityInfoClass::UpdateCompatibilityDatabase(QWidget* parent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
future_watcher.setFuture(QtConcurrent::map(replies, WaitForReply));
|
future_watcher.setFuture(QtConcurrent::map(replies, WaitForReply));
|
||||||
connect(&future_watcher, &QFutureWatcher<QByteArray>::finished, [&]() {
|
connect(&future_watcher, &QFutureWatcher<void>::finished, [&]() {
|
||||||
for (int i = 0; i < remaining_pages; i++) {
|
for (int i = 0; i < remaining_pages; i++) {
|
||||||
if (replies[i]->error() == QNetworkReply::NoError) {
|
if (replies[i]->bytesAvailable()) {
|
||||||
ExtractCompatibilityInfo(replies[i]->readAll());
|
if (replies[i]->error() == QNetworkReply::NoError) {
|
||||||
|
ExtractCompatibilityInfo(replies[i]->readAll());
|
||||||
|
}
|
||||||
|
replies[i]->deleteLater();
|
||||||
|
} else {
|
||||||
|
// This means the request timed out
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
replies[i]->deleteLater();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QFile compatibility_file(m_compatibility_filename);
|
QFile compatibility_file(m_compatibility_filename);
|
||||||
@ -83,6 +90,16 @@ void CompatibilityInfoClass::UpdateCompatibilityDatabase(QWidget* parent) {
|
|||||||
|
|
||||||
dialog.reset();
|
dialog.reset();
|
||||||
});
|
});
|
||||||
|
connect(&future_watcher, &QFutureWatcher<void>::canceled, [&]() {
|
||||||
|
// Cleanup if user cancels pulling data
|
||||||
|
for (int i = 0; i < remaining_pages; i++) {
|
||||||
|
if (!replies[i]->bytesAvailable()) {
|
||||||
|
replies[i]->deleteLater();
|
||||||
|
} else if (!replies[i]->isFinished()) {
|
||||||
|
replies[i]->abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
connect(&dialog, &QProgressDialog::canceled, &future_watcher, &QFutureWatcher<void>::cancel);
|
connect(&dialog, &QProgressDialog::canceled, &future_watcher, &QFutureWatcher<void>::cancel);
|
||||||
dialog.setRange(0, remaining_pages);
|
dialog.setRange(0, remaining_pages);
|
||||||
connect(&future_watcher, &QFutureWatcher<void>::progressValueChanged, &dialog,
|
connect(&future_watcher, &QFutureWatcher<void>::progressValueChanged, &dialog,
|
||||||
@ -105,20 +122,34 @@ QNetworkReply* CompatibilityInfoClass::FetchPage(int page_num) {
|
|||||||
return reply;
|
return reply;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CompatibilityInfoClass::WaitForReply(QNetworkReply* reply) {
|
bool CompatibilityInfoClass::WaitForReply(QNetworkReply* reply) {
|
||||||
|
// Returns true if reply succeeded, false if reply timed out
|
||||||
|
QTimer timer;
|
||||||
|
timer.setSingleShot(true);
|
||||||
|
|
||||||
QEventLoop loop;
|
QEventLoop loop;
|
||||||
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||||
|
connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
|
||||||
|
timer.start(5000);
|
||||||
loop.exec();
|
loop.exec();
|
||||||
return;
|
|
||||||
|
if (timer.isActive()) {
|
||||||
|
timer.stop();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
disconnect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
||||||
|
reply->abort();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
CompatibilityEntry CompatibilityInfoClass::GetCompatibilityInfo(const std::string& serial) {
|
CompatibilityEntry CompatibilityInfoClass::GetCompatibilityInfo(const std::string& serial) {
|
||||||
QString title_id = QString::fromStdString(serial);
|
QString title_id = QString::fromStdString(serial);
|
||||||
if (m_compatibility_database.contains(title_id)) {
|
if (m_compatibility_database.contains(title_id)) {
|
||||||
{
|
{
|
||||||
|
QJsonObject compatibility_obj = m_compatibility_database[title_id].toObject();
|
||||||
for (int os_int = 0; os_int != static_cast<int>(OSType::Last); os_int++) {
|
for (int os_int = 0; os_int != static_cast<int>(OSType::Last); os_int++) {
|
||||||
QString os_string = OSTypeToString.at(static_cast<OSType>(os_int));
|
QString os_string = OSTypeToString.at(static_cast<OSType>(os_int));
|
||||||
QJsonObject compatibility_obj = m_compatibility_database[title_id].toObject();
|
|
||||||
if (compatibility_obj.contains(os_string)) {
|
if (compatibility_obj.contains(os_string)) {
|
||||||
QJsonObject compatibility_entry_obj = compatibility_obj[os_string].toObject();
|
QJsonObject compatibility_entry_obj = compatibility_obj[os_string].toObject();
|
||||||
CompatibilityEntry compatibility_entry{
|
CompatibilityEntry compatibility_entry{
|
||||||
@ -133,7 +164,9 @@ CompatibilityEntry CompatibilityInfoClass::GetCompatibilityInfo(const std::strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return CompatibilityEntry{CompatibilityStatus::Unknown};
|
|
||||||
|
return CompatibilityEntry{CompatibilityStatus::Unknown, "", QDateTime::currentDateTime(), "",
|
||||||
|
0};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CompatibilityInfoClass::LoadCompatibilityFile() {
|
bool CompatibilityInfoClass::LoadCompatibilityFile() {
|
||||||
|
@ -57,6 +57,7 @@ class CompatibilityInfoClass : public QObject {
|
|||||||
public:
|
public:
|
||||||
// Please think of a better alternative
|
// Please think of a better alternative
|
||||||
inline static const std::unordered_map<QString, CompatibilityStatus> LabelToCompatStatus = {
|
inline static const std::unordered_map<QString, CompatibilityStatus> LabelToCompatStatus = {
|
||||||
|
{QStringLiteral("status-unknown"), CompatibilityStatus::Unknown},
|
||||||
{QStringLiteral("status-nothing"), CompatibilityStatus::Nothing},
|
{QStringLiteral("status-nothing"), CompatibilityStatus::Nothing},
|
||||||
{QStringLiteral("status-boots"), CompatibilityStatus::Boots},
|
{QStringLiteral("status-boots"), CompatibilityStatus::Boots},
|
||||||
{QStringLiteral("status-menus"), CompatibilityStatus::Menus},
|
{QStringLiteral("status-menus"), CompatibilityStatus::Menus},
|
||||||
@ -87,7 +88,7 @@ public:
|
|||||||
bool LoadCompatibilityFile();
|
bool LoadCompatibilityFile();
|
||||||
CompatibilityEntry GetCompatibilityInfo(const std::string& serial);
|
CompatibilityEntry GetCompatibilityInfo(const std::string& serial);
|
||||||
void ExtractCompatibilityInfo(QByteArray response);
|
void ExtractCompatibilityInfo(QByteArray response);
|
||||||
static void WaitForReply(QNetworkReply* reply);
|
static bool WaitForReply(QNetworkReply* reply);
|
||||||
QNetworkReply* FetchPage(int page_num);
|
QNetworkReply* FetchPage(int page_num);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -80,6 +80,11 @@ GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get,
|
|||||||
QDesktopServices::openUrl(QUrl(m_game_info->m_games[row].compatibility.url));
|
QDesktopServices::openUrl(QUrl(m_game_info->m_games[row].compatibility.url));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Do not show status column if it is not enabled
|
||||||
|
if (!Config::getCompatibilityEnabled()) {
|
||||||
|
this->setColumnHidden(2, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameListFrame::onCurrentCellChanged(int currentRow, int currentColumn, int previousRow,
|
void GameListFrame::onCurrentCellChanged(int currentRow, int currentColumn, int previousRow,
|
||||||
|
@ -198,7 +198,9 @@ void MainWindow::CreateDockWindows() {
|
|||||||
|
|
||||||
void MainWindow::LoadGameLists() {
|
void MainWindow::LoadGameLists() {
|
||||||
// Update compatibility database
|
// Update compatibility database
|
||||||
m_compat_info->UpdateCompatibilityDatabase(this);
|
if (Config::getCheckCompatibilityOnStartup()) {
|
||||||
|
m_compat_info->UpdateCompatibilityDatabase(this);
|
||||||
|
}
|
||||||
// Get game info from game folders.
|
// Get game info from game folders.
|
||||||
m_game_info->GetGameInfo(this);
|
m_game_info->GetGameInfo(this);
|
||||||
if (isTableList) {
|
if (isTableList) {
|
||||||
|
Loading…
Reference in New Issue
Block a user