mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-04-04 20:37:51 -06:00
Improved sceAudioOut and SDL3 backend (#3984)
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
Some checks are pending
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / get-info (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-sdl-gcc (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
* improved sdl backend * small cleanups * misc * adjustments and new definations * cleanups * more debuging * rewrote sceAudioOut calls * fixed a trace * fixed audio3d port * small debug fixes * small additions * using shared_ptr * compile fixes * make macOS happy * using shared mutex * implemented audio input backend * fixed port construction based on decompile * implemented partially sceAudioInGetSilentState to return correct code if mic device is null * improved sdl volume handling * dynamic volume update * this one is for @UltraDaCat --------- Co-authored-by: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com>
This commit is contained in:
parent
ef5b40c70b
commit
0f9cc89ae5
@ -282,15 +282,16 @@ set(AJM_LIB src/core/libraries/ajm/ajm.cpp
|
||||
|
||||
set(AUDIO_LIB src/core/libraries/audio/audioin.cpp
|
||||
src/core/libraries/audio/audioin.h
|
||||
src/core/libraries/audio/sdl_in.h
|
||||
src/core/libraries/audio/sdl_in.cpp
|
||||
src/core/libraries/audio/audioin_backend.h
|
||||
src/core/libraries/audio/audioin_error.h
|
||||
src/core/libraries/audio/sdl_audio_in.cpp
|
||||
src/core/libraries/voice/voice.cpp
|
||||
src/core/libraries/voice/voice.h
|
||||
src/core/libraries/audio/audioout.cpp
|
||||
src/core/libraries/audio/audioout.h
|
||||
src/core/libraries/audio/audioout_backend.h
|
||||
src/core/libraries/audio/audioout_error.h
|
||||
src/core/libraries/audio/sdl_audio.cpp
|
||||
src/core/libraries/audio/sdl_audio_out.cpp
|
||||
src/core/libraries/ngs2/ngs2.cpp
|
||||
src/core/libraries/ngs2/ngs2.h
|
||||
)
|
||||
|
||||
@ -1299,6 +1299,9 @@ hotkey_toggle_mouse_to_joystick = f7
|
||||
hotkey_toggle_mouse_to_gyro = f6
|
||||
hotkey_toggle_mouse_to_touchpad = delete
|
||||
hotkey_quit = lctrl, lshift, end
|
||||
|
||||
hotkey_volume_up = kpplus
|
||||
hotkey_volume_down = kpminus
|
||||
)";
|
||||
}
|
||||
|
||||
|
||||
@ -1,23 +1,264 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <shared_mutex>
|
||||
#include "audioin_backend.h"
|
||||
#include "audioin_error.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/audio/audioin.h"
|
||||
#include "core/libraries/audio/sdl_in.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
|
||||
namespace Libraries::AudioIn {
|
||||
|
||||
static std::unique_ptr<SDLAudioIn> audio = std::make_unique<SDLAudioIn>();
|
||||
std::array<std::shared_ptr<PortIn>, ORBIS_AUDIO_IN_NUM_PORTS> port_table{};
|
||||
std::shared_mutex port_table_mutex;
|
||||
std::mutex port_allocation_mutex;
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInChangeAppModuleState() {
|
||||
LOG_ERROR(Lib_AudioIn, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
static std::unique_ptr<AudioInBackend> audio;
|
||||
|
||||
/*
|
||||
* Helper functions
|
||||
**/
|
||||
static int GetPortId(s32 handle) {
|
||||
int port_id = handle & 0xFF;
|
||||
|
||||
if (port_id >= ORBIS_AUDIO_IN_NUM_PORTS) {
|
||||
LOG_ERROR(Lib_AudioIn, "Invalid port");
|
||||
return ORBIS_AUDIO_IN_ERROR_PORT_FULL;
|
||||
}
|
||||
|
||||
if ((handle & 0x7f000000) != 0x30000000) {
|
||||
LOG_ERROR(Lib_AudioIn, "Invalid handle format");
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
return port_id;
|
||||
}
|
||||
|
||||
static s32 GetPortType(s32 handle) {
|
||||
return (handle >> 16) & 0xFF;
|
||||
}
|
||||
|
||||
static int AllocatePort(OrbisAudioInType type) {
|
||||
// TODO implement port type ranges if needed
|
||||
for (int i = 0; i <= ORBIS_AUDIO_IN_NUM_PORTS; i++) {
|
||||
std::shared_lock read_lock{port_table_mutex};
|
||||
if (!port_table[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
/*
|
||||
* sceAudioIn implementation
|
||||
**/
|
||||
static bool initOnce = false;
|
||||
int PS4_SYSV_ABI sceAudioInOpen(Libraries::UserService::OrbisUserServiceUserId userId, u32 type,
|
||||
u32 index, u32 len, u32 freq, u32 param) {
|
||||
LOG_INFO(Lib_AudioIn, "called, userId={}, type={}, index={}, len={}, freq={}, param={}", userId,
|
||||
type, index, len, freq, param);
|
||||
if (!initOnce) {
|
||||
// sceAudioInInit doesn't seem to be called by most apps before sceAudioInOpen so we init
|
||||
// here
|
||||
audio = std::make_unique<SDLAudioIn>();
|
||||
initOnce = true;
|
||||
}
|
||||
|
||||
if (len == 0 || len > 2048) {
|
||||
LOG_ERROR(Lib_AudioIn, "Invalid size");
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
// Validate parameters
|
||||
OrbisAudioInType in_type = static_cast<OrbisAudioInType>(type);
|
||||
OrbisAudioInParamFormat format = static_cast<OrbisAudioInParamFormat>(param);
|
||||
|
||||
if (format != OrbisAudioInParamFormat::S16Mono &&
|
||||
format != OrbisAudioInParamFormat::S16Stereo) {
|
||||
LOG_ERROR(Lib_AudioIn, "Invalid format");
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (freq != 16000 && freq != 48000) {
|
||||
LOG_ERROR(Lib_AudioIn, "Invalid sample rate");
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_FREQ;
|
||||
}
|
||||
|
||||
std::unique_lock lock{port_allocation_mutex};
|
||||
|
||||
// Allocate port
|
||||
int port_id = AllocatePort(in_type);
|
||||
if (port_id < 0) {
|
||||
LOG_ERROR(Lib_AudioIn, "No free audio input ports available");
|
||||
return ORBIS_AUDIO_IN_ERROR_PORT_FULL;
|
||||
}
|
||||
|
||||
// Create port object
|
||||
std::shared_ptr<PortIn> port;
|
||||
try {
|
||||
port = std::make_shared<PortIn>();
|
||||
|
||||
port->type = in_type;
|
||||
port->format = format;
|
||||
port->samples_num = len;
|
||||
port->freq = freq;
|
||||
|
||||
// Determine channel count and sample size based on format
|
||||
switch (format) {
|
||||
case OrbisAudioInParamFormat::S16Mono:
|
||||
port->channels_num = 1;
|
||||
port->sample_size = 2;
|
||||
break;
|
||||
case OrbisAudioInParamFormat::S16Stereo:
|
||||
port->channels_num = 2;
|
||||
port->sample_size = 2;
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Lib_AudioIn, "Unsupported audio format: {}", static_cast<u32>(format));
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// Open backend
|
||||
port->impl = audio->Open(*port);
|
||||
if (!port->impl) {
|
||||
throw std::runtime_error("Failed to create audio backend");
|
||||
}
|
||||
|
||||
} catch (const std::bad_alloc&) {
|
||||
LOG_ERROR(Lib_AudioIn, "Failed to allocate memory for audio port");
|
||||
return ORBIS_AUDIO_IN_ERROR_OUT_OF_MEMORY;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(Lib_AudioIn, "Failed to open audio input port: {}", e.what());
|
||||
return ORBIS_AUDIO_IN_ERROR_NOT_OPENED;
|
||||
}
|
||||
|
||||
// Store the port pointer with write lock
|
||||
{
|
||||
std::unique_lock write_lock{port_table_mutex};
|
||||
port_table[port_id] = port;
|
||||
}
|
||||
|
||||
// Create handle
|
||||
s32 handle = (type << 16) | port_id | 0x30000000;
|
||||
|
||||
LOG_INFO(Lib_AudioIn, "Opened audio input port {}: type={}, samples={}, freq={}, format={}",
|
||||
handle, static_cast<u32>(in_type), len, freq, static_cast<u32>(format));
|
||||
return handle;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInHqOpen(Libraries::UserService::OrbisUserServiceUserId userId, u32 type,
|
||||
u32 index, u32 len, u32 freq, u32 param) {
|
||||
LOG_INFO(Lib_AudioIn, "called, userId={}, type={}, index={}, len={}, freq={}, param={}", userId,
|
||||
type, index, len, freq, param);
|
||||
int result = sceAudioInOpen(userId, type, index, len, freq, param);
|
||||
if (result < 0) {
|
||||
LOG_ERROR(Lib_AudioIn, "Error returned {:#x}", result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInClose(s32 handle) {
|
||||
audio->AudioInClose(handle);
|
||||
LOG_INFO(Lib_AudioIn, "called, handle={:#x}", handle);
|
||||
|
||||
int port_id = GetPortId(handle);
|
||||
if (port_id < 0) {
|
||||
LOG_ERROR(Lib_AudioIn, "Invalid port id");
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
std::unique_lock lock{port_allocation_mutex};
|
||||
std::shared_ptr<PortIn> port;
|
||||
|
||||
// Get and clear the port pointer with write lock
|
||||
{
|
||||
std::unique_lock write_lock{port_table_mutex};
|
||||
port = std::move(port_table[port_id]);
|
||||
if (!port) {
|
||||
LOG_ERROR(Lib_AudioIn, "Port wasn't open {}", port_id);
|
||||
return ORBIS_AUDIO_IN_ERROR_NOT_OPENED;
|
||||
}
|
||||
port_table[port_id].reset();
|
||||
}
|
||||
|
||||
// Free resources
|
||||
std::scoped_lock port_lock{port->mutex};
|
||||
port->impl.reset();
|
||||
|
||||
LOG_INFO(Lib_AudioIn, "Closed audio input port {}", handle);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInInput(s32 handle, void* dest) {
|
||||
LOG_TRACE(Lib_AudioIn, "called, handle={:#x}, dest={}", handle, fmt::ptr(dest));
|
||||
|
||||
int port_id = GetPortId(handle);
|
||||
if (port_id < 0) {
|
||||
LOG_ERROR(Lib_AudioIn, "Invalid port id");
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
if (!dest) {
|
||||
LOG_ERROR(Lib_AudioIn, "Invalid output buffer pointer");
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_POINTER;
|
||||
}
|
||||
|
||||
// Get port with read lock
|
||||
std::shared_ptr<PortIn> port;
|
||||
{
|
||||
std::shared_lock read_lock{port_table_mutex};
|
||||
if (port_id < 0 || port_id >= static_cast<int>(port_table.size())) {
|
||||
LOG_ERROR(Lib_AudioIn, "Invalid port id: {}", port_id);
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
port = port_table[port_id];
|
||||
}
|
||||
|
||||
if (!port || !port->impl) {
|
||||
LOG_ERROR(Lib_AudioIn, "Audio input port {} is not open", handle);
|
||||
return ORBIS_AUDIO_IN_ERROR_NOT_OPENED;
|
||||
}
|
||||
|
||||
std::scoped_lock lock{port->mutex};
|
||||
return port->impl->Read(dest);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInGetSilentState(s32 handle) {
|
||||
LOG_TRACE(Lib_AudioIn, "called, handle={:#x}", handle);
|
||||
int port_id = GetPortId(handle);
|
||||
if (port_id < 0) {
|
||||
LOG_ERROR(Lib_AudioIn, "Invalid port id");
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
// Get port with read lock
|
||||
std::shared_ptr<PortIn> port;
|
||||
{
|
||||
std::shared_lock read_lock{port_table_mutex};
|
||||
if (port_id < 0 || port_id >= static_cast<int>(port_table.size())) {
|
||||
LOG_ERROR(Lib_AudioIn, "Invalid port id: {}", port_id);
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
port = port_table[port_id];
|
||||
}
|
||||
|
||||
if (!port || !port->impl) {
|
||||
LOG_ERROR(Lib_AudioIn, "Audio input port {} is not open", handle);
|
||||
return ORBIS_AUDIO_IN_ERROR_NOT_OPENED;
|
||||
}
|
||||
|
||||
u32 silent_state = 0;
|
||||
std::scoped_lock lock{port->mutex};
|
||||
if (!port->impl->IsAvailable()) { // if no mic exist or is not available
|
||||
silent_state |= ORBIS_AUDIO_IN_SILENT_STATE_DEVICE_NONE;
|
||||
}
|
||||
return silent_state;
|
||||
}
|
||||
|
||||
/*
|
||||
* Stubbed functions
|
||||
**/
|
||||
int PS4_SYSV_ABI sceAudioInChangeAppModuleState() {
|
||||
LOG_ERROR(Lib_AudioIn, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -91,20 +332,6 @@ int PS4_SYSV_ABI sceAudioInGetRerouteCount() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInGetSilentState() {
|
||||
LOG_ERROR(Lib_AudioIn, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInHqOpen(Libraries::UserService::OrbisUserServiceUserId userId, u32 type,
|
||||
u32 index, u32 len, u32 freq, u32 param) {
|
||||
int result = audio->AudioInOpen(type, len, freq, param);
|
||||
if (result < 0) {
|
||||
LOG_ERROR(Lib_AudioIn, "Error returned {:#x}", result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInHqOpenEx() {
|
||||
LOG_ERROR(Lib_AudioIn, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
@ -115,10 +342,6 @@ int PS4_SYSV_ABI sceAudioInInit() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInInput(s32 handle, void* dest) {
|
||||
return audio->AudioInInput(handle, dest);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInInputs() {
|
||||
LOG_ERROR(Lib_AudioIn, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
@ -129,15 +352,6 @@ int PS4_SYSV_ABI sceAudioInIsSharedDevice() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInOpen(Libraries::UserService::OrbisUserServiceUserId userId, u32 type,
|
||||
u32 index, u32 len, u32 freq, u32 param) {
|
||||
int result = audio->AudioInOpen(type, len, freq, param);
|
||||
if (result < 0) {
|
||||
LOG_ERROR(Lib_AudioIn, "Error returned {:#x}", result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInOpenEx() {
|
||||
LOG_ERROR(Lib_AudioIn, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <core/libraries/system/userservice.h>
|
||||
#include "common/types.h"
|
||||
|
||||
@ -12,10 +13,31 @@ class SymbolsResolver;
|
||||
|
||||
namespace Libraries::AudioIn {
|
||||
|
||||
class PortInBackend;
|
||||
|
||||
constexpr s32 ORBIS_AUDIO_IN_NUM_PORTS = 7;
|
||||
|
||||
enum class OrbisAudioInParamFormat : u32 { S16Mono = 0, S16Stereo = 2 };
|
||||
|
||||
enum class OrbisAudioInType : u32 { VoiceChat = 0, General = 1, VoiceRecognition = 5 };
|
||||
|
||||
constexpr int ORBIS_AUDIO_IN_SILENT_STATE_DEVICE_NONE = 0x00000001;
|
||||
constexpr int ORBIS_AUDIO_IN_SILENT_STATE_PRIORITY_LOW = 0x00000002;
|
||||
constexpr int ORBIS_AUDIO_IN_SILENT_STATE_USER_SETTING = 0x0000000;
|
||||
constexpr int ORBIS_AUDIO_IN_SILENT_STATE_UNABLE_FORMAT = 0x00000008;
|
||||
|
||||
struct PortIn {
|
||||
std::mutex mutex;
|
||||
std::unique_ptr<PortInBackend> impl{};
|
||||
OrbisAudioInType type;
|
||||
OrbisAudioInParamFormat format;
|
||||
|
||||
u32 samples_num = 0;
|
||||
u32 freq = 0;
|
||||
u32 channels_num = 0;
|
||||
u32 sample_size = 0;
|
||||
};
|
||||
|
||||
int PS4_SYSV_ABI sceAudioInChangeAppModuleState();
|
||||
int PS4_SYSV_ABI sceAudioInClose(s32 handle);
|
||||
int PS4_SYSV_ABI sceAudioInCountPorts();
|
||||
@ -32,7 +54,7 @@ int PS4_SYSV_ABI sceAudioInExtSetAecMode();
|
||||
int PS4_SYSV_ABI sceAudioInGetGain();
|
||||
int PS4_SYSV_ABI sceAudioInGetHandleStatusInfo();
|
||||
int PS4_SYSV_ABI sceAudioInGetRerouteCount();
|
||||
int PS4_SYSV_ABI sceAudioInGetSilentState();
|
||||
int PS4_SYSV_ABI sceAudioInGetSilentState(s32 handle);
|
||||
int PS4_SYSV_ABI sceAudioInHqOpen(Libraries::UserService::OrbisUserServiceUserId userId, u32 type,
|
||||
u32 index, u32 len, u32 freq, u32 param);
|
||||
int PS4_SYSV_ABI sceAudioInHqOpenEx();
|
||||
|
||||
31
src/core/libraries/audio/audioin_backend.h
Normal file
31
src/core/libraries/audio/audioin_backend.h
Normal file
@ -0,0 +1,31 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
#include <memory>
|
||||
|
||||
namespace Libraries::AudioIn {
|
||||
|
||||
struct PortIn;
|
||||
|
||||
class PortInBackend {
|
||||
public:
|
||||
virtual ~PortInBackend() = default;
|
||||
virtual int Read(void* out_buffer) = 0;
|
||||
virtual void Clear() = 0;
|
||||
virtual bool IsAvailable() = 0;
|
||||
};
|
||||
|
||||
class AudioInBackend {
|
||||
public:
|
||||
AudioInBackend() = default;
|
||||
virtual ~AudioInBackend() = default;
|
||||
virtual std::unique_ptr<PortInBackend> Open(PortIn& port) = 0;
|
||||
};
|
||||
|
||||
class SDLAudioIn final : public AudioInBackend {
|
||||
public:
|
||||
std::unique_ptr<PortInBackend> Open(PortIn& port) override;
|
||||
};
|
||||
|
||||
} // namespace Libraries::AudioIn
|
||||
20
src/core/libraries/audio/audioin_error.h
Normal file
20
src/core/libraries/audio/audioin_error.h
Normal file
@ -0,0 +1,20 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/libraries/error_codes.h"
|
||||
|
||||
// AudioIn library
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_HANDLE = 0x80260101;
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_SIZE = 0x80260102;
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_FREQ = 0x80260103;
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_TYPE = 0x80260104;
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_POINTER = 0x80260105;
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_INVALID_PARAM = 0x80260106;
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_PORT_FULL = 0x80260107;
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_OUT_OF_MEMORY = 0x80260108;
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_NOT_OPENED = 0x80260109;
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_BUSY = 0x8026010A;
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_SYSTEM_MEMORY = 0x8026010B;
|
||||
constexpr int ORBIS_AUDIO_IN_ERROR_SYSTEM_IPC = 0x8026010C;
|
||||
File diff suppressed because it is too large
Load Diff
@ -17,8 +17,32 @@ class PortBackend;
|
||||
|
||||
// Main up to 8 ports, BGM 1 port, voice up to 4 ports,
|
||||
// personal up to 4 ports, padspk up to 5 ports, aux 1 port
|
||||
constexpr s32 SCE_AUDIO_OUT_NUM_PORTS = 22;
|
||||
constexpr s32 SCE_AUDIO_OUT_VOLUME_0DB = 32768; // max volume value
|
||||
constexpr s32 ORBIS_AUDIO_OUT_NUM_PORTS = 25;
|
||||
constexpr s32 ORBIS_AUDIO_OUT_VOLUME_0DB = 32768; // max volume value
|
||||
|
||||
constexpr s32 ORBIS_AUDIO_OUT_MIXLEVEL_PADSPK_DEFAULT = 11626; // default -9db
|
||||
constexpr s32 ORBIS_AUDIO_OUT_MIXLEVEL_PADSPK_0DB = 32768; // max volume
|
||||
|
||||
constexpr s32 ORBIS_AUDIO_OUT_PARAM_ATTR_RESTRICTED = 0x00010000;
|
||||
constexpr s32 ORBIS_AUDIO_OUT_PARAM_ATTR_MIX_TO_MAIN = 0x00020000;
|
||||
|
||||
// Volume flags
|
||||
constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_L_CH = (1u << 0);
|
||||
constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_R_CH = (1u << 1);
|
||||
constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_C_CH = (1u << 2);
|
||||
constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_LFE_CH = (1u << 3);
|
||||
constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_LS_CH = (1u << 4);
|
||||
constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_RS_CH = (1u << 5);
|
||||
constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_LE_CH = (1u << 6);
|
||||
constexpr u32 ORBIS_AUDIO_VOLUME_FLAG_RE_CH = (1u << 7);
|
||||
|
||||
// Port state constants
|
||||
constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_UNKNOWN = 0x00;
|
||||
constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_PRIMARY = 0x01;
|
||||
constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_SECONDARY = 0x02;
|
||||
constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_TERTIARY = 0x04;
|
||||
constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_HEADPHONE = 0x40;
|
||||
constexpr u16 ORBIS_AUDIO_OUT_STATE_OUTPUT_CONNECTED_EXTERNAL = 0x80;
|
||||
|
||||
enum class OrbisAudioOutPort {
|
||||
Main = 0,
|
||||
@ -53,6 +77,9 @@ union OrbisAudioOutParamExtendedInformation {
|
||||
BitField<16, 4, OrbisAudioOutParamAttr> attributes;
|
||||
BitField<20, 10, u32> reserve1;
|
||||
BitField<31, 1, u32> unused;
|
||||
u32 Unpack() const {
|
||||
return *reinterpret_cast<const u32*>(this);
|
||||
}
|
||||
};
|
||||
|
||||
struct OrbisAudioOutOutputParam {
|
||||
@ -77,6 +104,7 @@ struct AudioFormatInfo {
|
||||
/// Layout array remapping channel indices, specified in this order:
|
||||
/// FL, FR, FC, LFE, BL, BR, SL, SR
|
||||
std::array<int, 8> channel_layout;
|
||||
bool is_std;
|
||||
|
||||
[[nodiscard]] u16 FrameSize() const {
|
||||
return sample_size * num_channels;
|
||||
@ -87,100 +115,100 @@ struct PortOut {
|
||||
std::mutex mutex;
|
||||
std::unique_ptr<PortBackend> impl{};
|
||||
|
||||
void* output_buffer;
|
||||
void* output_buffer = nullptr;
|
||||
std::condition_variable_any output_cv;
|
||||
bool output_ready;
|
||||
bool output_ready = false;
|
||||
Kernel::Thread output_thread{};
|
||||
|
||||
OrbisAudioOutPort type;
|
||||
AudioFormatInfo format_info;
|
||||
u32 sample_rate;
|
||||
u32 buffer_frames;
|
||||
u64 last_output_time;
|
||||
u32 sample_rate = 48000;
|
||||
u32 buffer_frames = 1024;
|
||||
u64 last_output_time = 0;
|
||||
std::array<s32, 8> volume;
|
||||
|
||||
[[nodiscard]] bool IsOpen() const {
|
||||
return impl != nullptr;
|
||||
}
|
||||
s32 userId = 0;
|
||||
s32 mixLevelPadSpk = ORBIS_AUDIO_OUT_MIXLEVEL_PADSPK_DEFAULT;
|
||||
bool is_restricted = false;
|
||||
bool is_mix_to_main = false;
|
||||
|
||||
[[nodiscard]] u32 BufferSize() const {
|
||||
return buffer_frames * format_info.FrameSize();
|
||||
}
|
||||
};
|
||||
|
||||
int PS4_SYSV_ABI sceAudioOutDeviceIdOpen();
|
||||
int PS4_SYSV_ABI sceAudioDeviceControlGet();
|
||||
int PS4_SYSV_ABI sceAudioDeviceControlSet();
|
||||
int PS4_SYSV_ABI sceAudioOutA3dControl();
|
||||
int PS4_SYSV_ABI sceAudioOutA3dExit();
|
||||
int PS4_SYSV_ABI sceAudioOutA3dInit();
|
||||
int PS4_SYSV_ABI sceAudioOutAttachToApplicationByPid();
|
||||
int PS4_SYSV_ABI sceAudioOutChangeAppModuleState();
|
||||
int PS4_SYSV_ABI sceAudioOutClose(s32 handle);
|
||||
int PS4_SYSV_ABI sceAudioOutDetachFromApplicationByPid();
|
||||
int PS4_SYSV_ABI sceAudioOutExConfigureOutputMode();
|
||||
int PS4_SYSV_ABI sceAudioOutExGetSystemInfo();
|
||||
int PS4_SYSV_ABI sceAudioOutExPtClose();
|
||||
int PS4_SYSV_ABI sceAudioOutExPtGetLastOutputTime();
|
||||
int PS4_SYSV_ABI sceAudioOutExPtOpen();
|
||||
int PS4_SYSV_ABI sceAudioOutExSystemInfoIsSupportedAudioOutExMode();
|
||||
int PS4_SYSV_ABI sceAudioOutGetFocusEnablePid();
|
||||
int PS4_SYSV_ABI sceAudioOutGetHandleStatusInfo();
|
||||
int PS4_SYSV_ABI sceAudioOutGetInfo();
|
||||
int PS4_SYSV_ABI sceAudioOutGetInfoOpenNum();
|
||||
int PS4_SYSV_ABI sceAudioOutGetLastOutputTime(s32 handle, u64* output_time);
|
||||
int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state);
|
||||
int PS4_SYSV_ABI sceAudioOutGetSimulatedBusUsableStatusByBusType();
|
||||
int PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo();
|
||||
int PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo2();
|
||||
int PS4_SYSV_ABI sceAudioOutGetSparkVss();
|
||||
int PS4_SYSV_ABI sceAudioOutGetSystemState();
|
||||
int PS4_SYSV_ABI sceAudioOutInit();
|
||||
int PS4_SYSV_ABI sceAudioOutInitIpmiGetSession();
|
||||
int PS4_SYSV_ABI sceAudioOutMasteringGetState();
|
||||
int PS4_SYSV_ABI sceAudioOutMasteringInit();
|
||||
int PS4_SYSV_ABI sceAudioOutMasteringSetParam();
|
||||
int PS4_SYSV_ABI sceAudioOutMasteringTerm();
|
||||
int PS4_SYSV_ABI sceAudioOutMbusInit();
|
||||
s32 PS4_SYSV_ABI sceAudioOutDeviceIdOpen();
|
||||
s32 PS4_SYSV_ABI sceAudioDeviceControlGet();
|
||||
s32 PS4_SYSV_ABI sceAudioDeviceControlSet();
|
||||
s32 PS4_SYSV_ABI sceAudioOutA3dControl();
|
||||
s32 PS4_SYSV_ABI sceAudioOutA3dExit();
|
||||
s32 PS4_SYSV_ABI sceAudioOutA3dInit();
|
||||
s32 PS4_SYSV_ABI sceAudioOutAttachToApplicationByPid();
|
||||
s32 PS4_SYSV_ABI sceAudioOutChangeAppModuleState();
|
||||
s32 PS4_SYSV_ABI sceAudioOutClose(s32 handle);
|
||||
s32 PS4_SYSV_ABI sceAudioOutDetachFromApplicationByPid();
|
||||
s32 PS4_SYSV_ABI sceAudioOutExConfigureOutputMode();
|
||||
s32 PS4_SYSV_ABI sceAudioOutExGetSystemInfo();
|
||||
s32 PS4_SYSV_ABI sceAudioOutExPtClose();
|
||||
s32 PS4_SYSV_ABI sceAudioOutExPtGetLastOutputTime();
|
||||
s32 PS4_SYSV_ABI sceAudioOutExPtOpen();
|
||||
s32 PS4_SYSV_ABI sceAudioOutExSystemInfoIsSupportedAudioOutExMode();
|
||||
s32 PS4_SYSV_ABI sceAudioOutGetFocusEnablePid();
|
||||
s32 PS4_SYSV_ABI sceAudioOutGetHandleStatusInfo();
|
||||
s32 PS4_SYSV_ABI sceAudioOutGetInfo();
|
||||
s32 PS4_SYSV_ABI sceAudioOutGetInfoOpenNum();
|
||||
s32 PS4_SYSV_ABI sceAudioOutGetLastOutputTime(s32 handle, u64* output_time);
|
||||
s32 PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state);
|
||||
s32 PS4_SYSV_ABI sceAudioOutGetSimulatedBusUsableStatusByBusType();
|
||||
s32 PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo();
|
||||
s32 PS4_SYSV_ABI sceAudioOutGetSimulatedHandleStatusInfo2();
|
||||
s32 PS4_SYSV_ABI sceAudioOutGetSparkVss();
|
||||
s32 PS4_SYSV_ABI sceAudioOutGetSystemState();
|
||||
s32 PS4_SYSV_ABI sceAudioOutInit();
|
||||
s32 PS4_SYSV_ABI sceAudioOutInitIpmiGetSession();
|
||||
s32 PS4_SYSV_ABI sceAudioOutMasteringGetState();
|
||||
s32 PS4_SYSV_ABI sceAudioOutMasteringInit(u32 flags);
|
||||
s32 PS4_SYSV_ABI sceAudioOutMasteringSetParam();
|
||||
s32 PS4_SYSV_ABI sceAudioOutMasteringTerm();
|
||||
s32 PS4_SYSV_ABI sceAudioOutMbusInit();
|
||||
s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id,
|
||||
OrbisAudioOutPort port_type, s32 index, u32 length,
|
||||
u32 sample_rate, OrbisAudioOutParamExtendedInformation param_type);
|
||||
int PS4_SYSV_ABI sceAudioOutOpenEx();
|
||||
s32 PS4_SYSV_ABI sceAudioOutOpenEx();
|
||||
s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, void* ptr);
|
||||
s32 PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num);
|
||||
int PS4_SYSV_ABI sceAudioOutPtClose();
|
||||
int PS4_SYSV_ABI sceAudioOutPtGetLastOutputTime();
|
||||
int PS4_SYSV_ABI sceAudioOutPtOpen();
|
||||
int PS4_SYSV_ABI sceAudioOutSetConnections();
|
||||
int PS4_SYSV_ABI sceAudioOutSetConnectionsForUser();
|
||||
int PS4_SYSV_ABI sceAudioOutSetDevConnection();
|
||||
int PS4_SYSV_ABI sceAudioOutSetHeadphoneOutMode();
|
||||
int PS4_SYSV_ABI sceAudioOutSetJediJackVolume();
|
||||
int PS4_SYSV_ABI sceAudioOutSetJediSpkVolume();
|
||||
int PS4_SYSV_ABI sceAudioOutSetMainOutput();
|
||||
int PS4_SYSV_ABI sceAudioOutSetMixLevelPadSpk();
|
||||
int PS4_SYSV_ABI sceAudioOutSetMorpheusParam();
|
||||
int PS4_SYSV_ABI sceAudioOutSetMorpheusWorkingMode();
|
||||
int PS4_SYSV_ABI sceAudioOutSetPortConnections();
|
||||
int PS4_SYSV_ABI sceAudioOutSetPortStatuses();
|
||||
int PS4_SYSV_ABI sceAudioOutSetRecMode();
|
||||
int PS4_SYSV_ABI sceAudioOutSetSparkParam();
|
||||
int PS4_SYSV_ABI sceAudioOutSetUsbVolume();
|
||||
s32 PS4_SYSV_ABI sceAudioOutPtClose();
|
||||
s32 PS4_SYSV_ABI sceAudioOutPtGetLastOutputTime();
|
||||
s32 PS4_SYSV_ABI sceAudioOutPtOpen();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetConnections();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetConnectionsForUser();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetDevConnection();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetHeadphoneOutMode();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetJediJackVolume();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetJediSpkVolume();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetMainOutput();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetMixLevelPadSpk(s32 handle, s32 mixLevel);
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetMorpheusParam();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetMorpheusWorkingMode();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetPortConnections();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetPortStatuses();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetRecMode();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetSparkParam();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetUsbVolume();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol);
|
||||
int PS4_SYSV_ABI sceAudioOutSetVolumeDown();
|
||||
int PS4_SYSV_ABI sceAudioOutStartAuxBroadcast();
|
||||
int PS4_SYSV_ABI sceAudioOutStartSharePlay();
|
||||
int PS4_SYSV_ABI sceAudioOutStopAuxBroadcast();
|
||||
int PS4_SYSV_ABI sceAudioOutStopSharePlay();
|
||||
int PS4_SYSV_ABI sceAudioOutSuspendResume();
|
||||
int PS4_SYSV_ABI sceAudioOutSysConfigureOutputMode();
|
||||
int PS4_SYSV_ABI sceAudioOutSysGetHdmiMonitorInfo();
|
||||
int PS4_SYSV_ABI sceAudioOutSysGetSystemInfo();
|
||||
int PS4_SYSV_ABI sceAudioOutSysHdmiMonitorInfoIsSupportedAudioOutMode();
|
||||
int PS4_SYSV_ABI sceAudioOutSystemControlGet();
|
||||
int PS4_SYSV_ABI sceAudioOutSystemControlSet();
|
||||
int PS4_SYSV_ABI sceAudioOutSparkControlSetEqCoef();
|
||||
int PS4_SYSV_ABI sceAudioOutSetSystemDebugState();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetVolumeDown();
|
||||
s32 PS4_SYSV_ABI sceAudioOutStartAuxBroadcast();
|
||||
s32 PS4_SYSV_ABI sceAudioOutStartSharePlay();
|
||||
s32 PS4_SYSV_ABI sceAudioOutStopAuxBroadcast();
|
||||
s32 PS4_SYSV_ABI sceAudioOutStopSharePlay();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSuspendResume();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSysConfigureOutputMode();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSysGetHdmiMonitorInfo();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSysGetSystemInfo();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSysHdmiMonitorInfoIsSupportedAudioOutMode();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSystemControlGet();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSystemControlSet();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSparkControlSetEqCoef();
|
||||
s32 PS4_SYSV_ABI sceAudioOutSetSystemDebugState();
|
||||
|
||||
void AdjustVol();
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
|
||||
@ -1,156 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
#include <SDL3/SDL_audio.h>
|
||||
#include <SDL3/SDL_hints.h>
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/audio/audioout.h"
|
||||
#include "core/libraries/audio/audioout_backend.h"
|
||||
|
||||
#define SDL_INVALID_AUDIODEVICEID 0 // Defined in SDL_audio.h but not made a macro
|
||||
namespace Libraries::AudioOut {
|
||||
|
||||
class SDLPortBackend : public PortBackend {
|
||||
public:
|
||||
explicit SDLPortBackend(const PortOut& port)
|
||||
: frame_size(port.format_info.FrameSize()), guest_buffer_size(port.BufferSize()) {
|
||||
const SDL_AudioSpec fmt = {
|
||||
.format = port.format_info.is_float ? SDL_AUDIO_F32LE : SDL_AUDIO_S16LE,
|
||||
.channels = port.format_info.num_channels,
|
||||
.freq = static_cast<int>(port.sample_rate),
|
||||
};
|
||||
|
||||
// Determine port type
|
||||
std::string port_name = port.type == OrbisAudioOutPort::PadSpk
|
||||
? Config::getPadSpkOutputDevice()
|
||||
: Config::getMainOutputDevice();
|
||||
SDL_AudioDeviceID dev_id = SDL_INVALID_AUDIODEVICEID;
|
||||
if (port_name == "None") {
|
||||
stream = nullptr;
|
||||
return;
|
||||
} else if (port_name == "Default Device") {
|
||||
dev_id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
|
||||
} else {
|
||||
try {
|
||||
SDL_AudioDeviceID* dev_array = SDL_GetAudioPlaybackDevices(nullptr);
|
||||
for (; dev_array != 0;) {
|
||||
std::string dev_name(SDL_GetAudioDeviceName(*dev_array));
|
||||
if (dev_name == port_name) {
|
||||
dev_id = *dev_array;
|
||||
break;
|
||||
} else {
|
||||
dev_array++;
|
||||
}
|
||||
}
|
||||
if (dev_id == SDL_INVALID_AUDIODEVICEID) {
|
||||
LOG_WARNING(Lib_AudioOut, "Audio device not found: {}", port_name);
|
||||
dev_id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(Lib_AudioOut, "Invalid audio output device: {}", port_name);
|
||||
stream = nullptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Open the audio stream
|
||||
stream = SDL_OpenAudioDeviceStream(dev_id, &fmt, nullptr, nullptr);
|
||||
if (stream == nullptr) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to create SDL audio stream: {}", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
CalculateQueueThreshold();
|
||||
if (!SDL_SetAudioStreamInputChannelMap(stream, port.format_info.channel_layout.data(),
|
||||
port.format_info.num_channels)) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to configure SDL audio stream channel map: {}",
|
||||
SDL_GetError());
|
||||
SDL_DestroyAudioStream(stream);
|
||||
stream = nullptr;
|
||||
return;
|
||||
}
|
||||
if (!SDL_ResumeAudioStreamDevice(stream)) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to resume SDL audio stream: {}", SDL_GetError());
|
||||
SDL_DestroyAudioStream(stream);
|
||||
stream = nullptr;
|
||||
return;
|
||||
}
|
||||
SDL_SetAudioStreamGain(stream, Config::getVolumeSlider() / 100.0f);
|
||||
}
|
||||
|
||||
~SDLPortBackend() override {
|
||||
if (!stream) {
|
||||
return;
|
||||
}
|
||||
SDL_DestroyAudioStream(stream);
|
||||
stream = nullptr;
|
||||
}
|
||||
|
||||
void Output(void* ptr) override {
|
||||
if (!stream) {
|
||||
return;
|
||||
}
|
||||
// AudioOut library manages timing, but we still need to guard against the SDL
|
||||
// audio queue stalling, which may happen during device changes, for example.
|
||||
// Otherwise, latency may grow over time unbounded.
|
||||
if (const auto queued = SDL_GetAudioStreamQueued(stream); queued >= queue_threshold) {
|
||||
LOG_INFO(Lib_AudioOut, "SDL audio queue backed up ({} queued, {} threshold), clearing.",
|
||||
queued, queue_threshold);
|
||||
SDL_ClearAudioStream(stream);
|
||||
// Recalculate the threshold in case this happened because of a device change.
|
||||
CalculateQueueThreshold();
|
||||
}
|
||||
if (!SDL_PutAudioStreamData(stream, ptr, static_cast<int>(guest_buffer_size))) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to output to SDL audio stream: {}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
void SetVolume(const std::array<int, 8>& ch_volumes) override {
|
||||
if (!stream) {
|
||||
return;
|
||||
}
|
||||
// SDL does not have per-channel volumes, for now just take the maximum of the channels.
|
||||
const auto vol = *std::ranges::max_element(ch_volumes);
|
||||
if (!SDL_SetAudioStreamGain(stream, static_cast<float>(vol) / SCE_AUDIO_OUT_VOLUME_0DB *
|
||||
Config::getVolumeSlider() / 100.0f)) {
|
||||
LOG_WARNING(Lib_AudioOut, "Failed to change SDL audio stream volume: {}",
|
||||
SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void CalculateQueueThreshold() {
|
||||
SDL_AudioSpec discard;
|
||||
int sdl_buffer_frames;
|
||||
if (!SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(stream), &discard,
|
||||
&sdl_buffer_frames)) {
|
||||
LOG_WARNING(Lib_AudioOut, "Failed to get SDL audio stream buffer size: {}",
|
||||
SDL_GetError());
|
||||
sdl_buffer_frames = 0;
|
||||
}
|
||||
const auto sdl_buffer_size = sdl_buffer_frames * frame_size;
|
||||
const auto new_threshold = std::max(guest_buffer_size, sdl_buffer_size) * 4;
|
||||
if (host_buffer_size != sdl_buffer_size || queue_threshold != new_threshold) {
|
||||
host_buffer_size = sdl_buffer_size;
|
||||
queue_threshold = new_threshold;
|
||||
LOG_INFO(Lib_AudioOut,
|
||||
"SDL audio buffers: guest = {} bytes, host = {} bytes, threshold = {} bytes",
|
||||
guest_buffer_size, host_buffer_size, queue_threshold);
|
||||
}
|
||||
}
|
||||
|
||||
u32 frame_size;
|
||||
u32 guest_buffer_size;
|
||||
u32 host_buffer_size{};
|
||||
u32 queue_threshold{};
|
||||
SDL_AudioStream* stream{};
|
||||
};
|
||||
|
||||
std::unique_ptr<PortBackend> SDLAudioOut::Open(PortOut& port) {
|
||||
return std::make_unique<SDLPortBackend>(port);
|
||||
}
|
||||
|
||||
} // namespace Libraries::AudioOut
|
||||
135
src/core/libraries/audio/sdl_audio_in.cpp
Normal file
135
src/core/libraries/audio/sdl_audio_in.cpp
Normal file
@ -0,0 +1,135 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <common/config.h>
|
||||
#include <common/logging/log.h>
|
||||
#include "audioin.h"
|
||||
#include "audioin_backend.h"
|
||||
|
||||
namespace Libraries::AudioIn {
|
||||
|
||||
class SDLInPortBackend : public PortInBackend {
|
||||
public:
|
||||
explicit SDLInPortBackend(const PortIn& port) : port(port) {
|
||||
SDL_AudioFormat sampleFormat = SDL_AUDIO_S16; // PS4 uses S16 format
|
||||
|
||||
SDL_AudioSpec fmt;
|
||||
SDL_zero(fmt);
|
||||
fmt.format = sampleFormat;
|
||||
fmt.channels = static_cast<Uint8>(port.channels_num);
|
||||
fmt.freq = static_cast<int>(port.freq);
|
||||
|
||||
std::string micDevStr = Config::getMicDevice();
|
||||
uint32_t devId = 0;
|
||||
if (micDevStr == "None") {
|
||||
nullDevice = true;
|
||||
LOG_INFO(Lib_AudioIn, "Audio input disabled by configuration");
|
||||
} else if (micDevStr == "Default Device") {
|
||||
devId = SDL_AUDIO_DEVICE_DEFAULT_RECORDING;
|
||||
LOG_INFO(Lib_AudioIn, "Using default audio input device");
|
||||
} else {
|
||||
try {
|
||||
devId = static_cast<uint32_t>(std::stoul(micDevStr));
|
||||
LOG_INFO(Lib_AudioIn, "Using audio input device ID: {}", devId);
|
||||
} catch (const std::exception& e) {
|
||||
nullDevice = true;
|
||||
LOG_WARNING(Lib_AudioIn, "Invalid device ID '{}', disabling input", micDevStr);
|
||||
}
|
||||
}
|
||||
|
||||
if (!nullDevice) {
|
||||
stream = SDL_OpenAudioDeviceStream(devId, &fmt, nullptr, nullptr);
|
||||
if (stream) {
|
||||
if (SDL_ResumeAudioStreamDevice(stream)) {
|
||||
LOG_INFO(Lib_AudioIn, "Audio input opened: {} Hz, {} channels, format {}",
|
||||
port.freq, port.channels_num, static_cast<u32>(port.format));
|
||||
} else {
|
||||
SDL_DestroyAudioStream(stream);
|
||||
stream = nullptr;
|
||||
LOG_ERROR(Lib_AudioIn, "Failed to resume audio input stream");
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(Lib_AudioIn, "Failed to open audio input device: {}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate internal buffer for null device simulation
|
||||
if (!stream) {
|
||||
const size_t bufferSize = port.samples_num * port.sample_size * port.channels_num;
|
||||
internal_buffer = std::malloc(bufferSize);
|
||||
if (internal_buffer) {
|
||||
// Fill with silence
|
||||
std::memset(internal_buffer, 0, bufferSize);
|
||||
LOG_INFO(Lib_AudioIn, "Created null audio input buffer of {} bytes", bufferSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~SDLInPortBackend() override {
|
||||
if (stream) {
|
||||
SDL_DestroyAudioStream(stream);
|
||||
}
|
||||
if (internal_buffer) {
|
||||
std::free(internal_buffer);
|
||||
internal_buffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int Read(void* out_buffer) override {
|
||||
const int bytesToRead = port.samples_num * port.sample_size * port.channels_num;
|
||||
|
||||
if (stream) {
|
||||
// Read from actual audio device
|
||||
int attempts = 0;
|
||||
while (SDL_GetAudioStreamAvailable(stream) < bytesToRead) {
|
||||
SDL_Delay(1);
|
||||
if (++attempts > 1000) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const int bytesRead = SDL_GetAudioStreamData(stream, out_buffer, bytesToRead);
|
||||
if (bytesRead < 0) {
|
||||
LOG_ERROR(Lib_AudioIn, "Audio input read error: {}", SDL_GetError());
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int framesRead = bytesRead / (port.sample_size * port.channels_num);
|
||||
return framesRead;
|
||||
} else if (internal_buffer) {
|
||||
// Return silence from null device buffer
|
||||
std::memcpy(out_buffer, internal_buffer, bytesToRead);
|
||||
return port.samples_num;
|
||||
} else {
|
||||
// No device available
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Clear() override {
|
||||
if (stream) {
|
||||
SDL_ClearAudioStream(stream);
|
||||
}
|
||||
}
|
||||
bool IsAvailable() override {
|
||||
if (nullDevice) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const PortIn& port;
|
||||
SDL_AudioStream* stream = nullptr;
|
||||
void* internal_buffer = nullptr;
|
||||
bool nullDevice = false;
|
||||
};
|
||||
|
||||
std::unique_ptr<PortInBackend> SDLAudioIn::Open(PortIn& port) {
|
||||
return std::make_unique<SDLInPortBackend>(port);
|
||||
}
|
||||
|
||||
} // namespace Libraries::AudioIn
|
||||
455
src/core/libraries/audio/sdl_audio_out.cpp
Normal file
455
src/core/libraries/audio/sdl_audio_out.cpp
Normal file
@ -0,0 +1,455 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
#include <SDL3/SDL_audio.h>
|
||||
#include <SDL3/SDL_hints.h>
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/audio/audioout.h"
|
||||
#include "core/libraries/audio/audioout_backend.h"
|
||||
#include "core/libraries/kernel/threads.h"
|
||||
|
||||
#define SDL_INVALID_AUDIODEVICEID 0
|
||||
|
||||
namespace Libraries::AudioOut {
|
||||
|
||||
// Volume constants
|
||||
constexpr float VOLUME_0DB = 32768.0f; // 1 << 15
|
||||
|
||||
// Channel positions
|
||||
enum ChannelPos {
|
||||
FL = 0,
|
||||
FR = 1,
|
||||
FC = 2,
|
||||
LF = 3,
|
||||
SL = 4,
|
||||
SR = 5,
|
||||
BL = 6,
|
||||
BR = 7,
|
||||
STD_SL = 6,
|
||||
STD_SR = 7,
|
||||
STD_BL = 4,
|
||||
STD_BR = 5
|
||||
};
|
||||
|
||||
class SDLPortBackend : public PortBackend {
|
||||
public:
|
||||
explicit SDLPortBackend(const PortOut& port)
|
||||
: frame_size(port.format_info.FrameSize()), guest_buffer_size(port.BufferSize()),
|
||||
buffer_frames(port.buffer_frames), sample_rate(port.sample_rate),
|
||||
num_channels(port.format_info.num_channels), is_float(port.format_info.is_float),
|
||||
is_std(port.format_info.is_std), channel_layout(port.format_info.channel_layout) {
|
||||
|
||||
// Calculate timing
|
||||
period_us = (1000000ULL * buffer_frames + sample_rate / 2) / sample_rate;
|
||||
last_output_time = 0;
|
||||
next_output_time = 0;
|
||||
|
||||
// Allocate internal buffer
|
||||
internal_buffer_size = buffer_frames * sizeof(float) * num_channels;
|
||||
internal_buffer = std::malloc(internal_buffer_size);
|
||||
if (!internal_buffer) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to allocate internal audio buffer");
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize current gain
|
||||
current_gain.store(Config::getVolumeSlider() / 100.0f);
|
||||
|
||||
// Select converter function
|
||||
SelectConverter();
|
||||
|
||||
// Open SDL device
|
||||
if (!OpenDevice(port.type)) {
|
||||
std::free(internal_buffer);
|
||||
internal_buffer = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
CalculateQueueThreshold();
|
||||
}
|
||||
|
||||
~SDLPortBackend() override {
|
||||
if (stream) {
|
||||
SDL_DestroyAudioStream(stream);
|
||||
}
|
||||
if (internal_buffer) {
|
||||
std::free(internal_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void Output(void* ptr) override {
|
||||
if (!stream || !internal_buffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for volume changes and update if needed
|
||||
UpdateVolumeIfChanged();
|
||||
|
||||
// Get current time in microseconds
|
||||
u64 current_time = Kernel::sceKernelGetProcessTime();
|
||||
|
||||
if (ptr != nullptr) {
|
||||
// Simple format conversion (no volume application)
|
||||
convert(ptr, internal_buffer, buffer_frames, nullptr);
|
||||
|
||||
if (next_output_time == 0) {
|
||||
next_output_time = current_time + period_us;
|
||||
} else if (current_time > next_output_time) {
|
||||
next_output_time = current_time + period_us;
|
||||
} else {
|
||||
u64 wait_until = next_output_time;
|
||||
next_output_time += period_us;
|
||||
|
||||
if (current_time < wait_until) {
|
||||
u64 sleep_us = wait_until - current_time;
|
||||
if (sleep_us > 10) {
|
||||
sleep_us -= 10;
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(sleep_us));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
last_output_time = current_time;
|
||||
|
||||
// Check queue and clear if backed up
|
||||
if (const auto queued = SDL_GetAudioStreamQueued(stream); queued >= queue_threshold) {
|
||||
LOG_DEBUG(Lib_AudioOut, "Clearing backed up audio queue ({} >= {})", queued,
|
||||
queue_threshold);
|
||||
SDL_ClearAudioStream(stream);
|
||||
CalculateQueueThreshold();
|
||||
}
|
||||
|
||||
if (!SDL_PutAudioStreamData(stream, internal_buffer, internal_buffer_size)) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to output to SDL audio stream: {}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetVolume(const std::array<int, 8>& ch_volumes) override {
|
||||
if (!stream) {
|
||||
return;
|
||||
}
|
||||
float max_channel_gain = 0.0f;
|
||||
for (int i = 0; i < num_channels && i < 8; i++) {
|
||||
float channel_gain = static_cast<float>(ch_volumes[i]) / VOLUME_0DB;
|
||||
max_channel_gain = std::max(max_channel_gain, channel_gain);
|
||||
}
|
||||
|
||||
// Combine with global volume slider
|
||||
float total_gain = max_channel_gain * (Config::getVolumeSlider() / 100.0f);
|
||||
|
||||
std::lock_guard<std::mutex> lock(volume_mutex);
|
||||
if (SDL_SetAudioStreamGain(stream, total_gain)) {
|
||||
current_gain.store(total_gain);
|
||||
LOG_DEBUG(Lib_AudioOut,
|
||||
"Set combined audio gain to {:.3f} (channel: {:.3f}, slider: {:.3f})",
|
||||
total_gain, max_channel_gain, Config::getVolumeSlider() / 100.0f);
|
||||
} else {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to set audio stream gain: {}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
u64 GetLastOutputTime() const {
|
||||
return last_output_time;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<bool> volume_update_needed{false};
|
||||
u64 last_volume_check_time{0};
|
||||
static constexpr u64 VOLUME_CHECK_INTERVAL_US = 50000; // Check every 50ms
|
||||
|
||||
void UpdateVolumeIfChanged() {
|
||||
u64 current_time = Kernel::sceKernelGetProcessTime();
|
||||
|
||||
// Only check volume every 50ms to reduce overhead
|
||||
if (current_time - last_volume_check_time >= VOLUME_CHECK_INTERVAL_US) {
|
||||
last_volume_check_time = current_time;
|
||||
|
||||
float config_volume = Config::getVolumeSlider() / 100.0f;
|
||||
float stored_gain = current_gain.load();
|
||||
|
||||
if (std::abs(config_volume - stored_gain) > 0.001f) {
|
||||
if (SDL_SetAudioStreamGain(stream, config_volume)) {
|
||||
current_gain.store(config_volume);
|
||||
LOG_DEBUG(Lib_AudioOut, "Updated audio gain to {:.3f}", config_volume);
|
||||
} else {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to set audio stream gain: {}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bool OpenDevice(OrbisAudioOutPort type) {
|
||||
const SDL_AudioSpec fmt = {
|
||||
.format = SDL_AUDIO_F32LE, // Always use float for internal processing
|
||||
.channels = static_cast<u8>(num_channels),
|
||||
.freq = static_cast<int>(sample_rate),
|
||||
};
|
||||
|
||||
// Determine device name
|
||||
std::string device_name = GetDeviceName(type);
|
||||
SDL_AudioDeviceID dev_id = SDL_INVALID_AUDIODEVICEID;
|
||||
|
||||
if (device_name == "None") {
|
||||
LOG_INFO(Lib_AudioOut, "Audio device disabled for port type {}",
|
||||
static_cast<int>(type));
|
||||
return false;
|
||||
} else if (device_name.empty() || device_name == "Default Device") {
|
||||
dev_id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
|
||||
} else {
|
||||
int num_devices = 0;
|
||||
SDL_AudioDeviceID* dev_array = SDL_GetAudioPlaybackDevices(&num_devices);
|
||||
|
||||
if (dev_array) {
|
||||
bool found = false;
|
||||
for (int i = 0; i < num_devices; i++) {
|
||||
const char* dev_name = SDL_GetAudioDeviceName(dev_array[i]);
|
||||
if (dev_name && std::string(dev_name) == device_name) {
|
||||
dev_id = dev_array[i];
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
SDL_free(dev_array);
|
||||
|
||||
if (!found) {
|
||||
LOG_WARNING(Lib_AudioOut, "Audio device '{}' not found, using default",
|
||||
device_name);
|
||||
dev_id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
|
||||
}
|
||||
} else {
|
||||
LOG_WARNING(Lib_AudioOut, "No audio devices found, using default");
|
||||
dev_id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
|
||||
}
|
||||
}
|
||||
|
||||
// Create audio stream
|
||||
stream = SDL_OpenAudioDeviceStream(dev_id, &fmt, nullptr, nullptr);
|
||||
if (!stream) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to create SDL audio stream: {}", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set channel map
|
||||
if (num_channels > 0) {
|
||||
std::vector<int> channel_map(num_channels);
|
||||
|
||||
if (is_std && num_channels == 8) {
|
||||
// Standard 8CH layout
|
||||
channel_map = {FL, FR, FC, LF, STD_SL, STD_SR, STD_BL, STD_BR};
|
||||
} else {
|
||||
// Use provided channel layout
|
||||
for (int i = 0; i < num_channels; i++) {
|
||||
channel_map[i] = channel_layout[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!SDL_SetAudioStreamInputChannelMap(stream, channel_map.data(), num_channels)) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to set channel map: {}", SDL_GetError());
|
||||
SDL_DestroyAudioStream(stream);
|
||||
stream = nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Set initial volume
|
||||
float initial_gain = current_gain.load();
|
||||
if (!SDL_SetAudioStreamGain(stream, initial_gain)) {
|
||||
LOG_WARNING(Lib_AudioOut, "Failed to set initial audio gain: {}", SDL_GetError());
|
||||
}
|
||||
|
||||
// Start playback
|
||||
if (!SDL_ResumeAudioStreamDevice(stream)) {
|
||||
LOG_ERROR(Lib_AudioOut, "Failed to resume audio stream: {}", SDL_GetError());
|
||||
SDL_DestroyAudioStream(stream);
|
||||
stream = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO(Lib_AudioOut, "Opened audio device: {} ({} Hz, {} ch, gain: {:.3f})", device_name,
|
||||
sample_rate, num_channels, initial_gain);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GetDeviceName(OrbisAudioOutPort type) {
|
||||
switch (type) {
|
||||
case OrbisAudioOutPort::Main:
|
||||
case OrbisAudioOutPort::Bgm:
|
||||
return Config::getMainOutputDevice();
|
||||
// case OrbisAudioOutPort::Voice:
|
||||
// case OrbisAudioOutPort::Personal:
|
||||
// return Config::getHeadphoneOutputDevice();
|
||||
case OrbisAudioOutPort::PadSpk:
|
||||
return Config::getPadSpkOutputDevice();
|
||||
// case OrbisAudioOutPort::Aux:
|
||||
// return Config::getSpecialOutputDevice();
|
||||
default:
|
||||
return Config::getMainOutputDevice();
|
||||
}
|
||||
}
|
||||
|
||||
void SelectConverter() {
|
||||
if (is_float) {
|
||||
switch (num_channels) {
|
||||
case 1:
|
||||
convert = &ConvertF32Mono;
|
||||
break;
|
||||
case 2:
|
||||
convert = &ConvertF32Stereo;
|
||||
break;
|
||||
case 8:
|
||||
if (is_std) {
|
||||
convert = &ConvertF32Std8CH;
|
||||
} else {
|
||||
convert = &ConvertF32_8CH;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Lib_AudioOut, "Unsupported float channel count: {}", num_channels);
|
||||
convert = nullptr;
|
||||
}
|
||||
} else {
|
||||
switch (num_channels) {
|
||||
case 1:
|
||||
convert = &ConvertS16Mono;
|
||||
break;
|
||||
case 2:
|
||||
convert = &ConvertS16Stereo;
|
||||
break;
|
||||
case 8:
|
||||
convert = &ConvertS16_8CH;
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Lib_AudioOut, "Unsupported S16 channel count: {}", num_channels);
|
||||
convert = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CalculateQueueThreshold() {
|
||||
if (!stream)
|
||||
return;
|
||||
|
||||
SDL_AudioSpec discard;
|
||||
int sdl_buffer_frames;
|
||||
if (!SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(stream), &discard,
|
||||
&sdl_buffer_frames)) {
|
||||
LOG_WARNING(Lib_AudioOut, "Failed to get SDL buffer size: {}", SDL_GetError());
|
||||
sdl_buffer_frames = 0;
|
||||
}
|
||||
|
||||
u32 sdl_buffer_size = sdl_buffer_frames * sizeof(float) * num_channels;
|
||||
queue_threshold = std::max(guest_buffer_size, sdl_buffer_size) * 4;
|
||||
|
||||
LOG_DEBUG(Lib_AudioOut, "Audio queue threshold: {} bytes (SDL buffer: {} frames)",
|
||||
queue_threshold, sdl_buffer_frames);
|
||||
}
|
||||
|
||||
using ConverterFunc = void (*)(const void* src, void* dst, u32 frames, const float* volumes);
|
||||
|
||||
// Remove volume parameter and application from all converters
|
||||
static void ConvertS16Mono(const void* src, void* dst, u32 frames, const float*) {
|
||||
const s16* s = static_cast<const s16*>(src);
|
||||
float* d = static_cast<float*>(dst);
|
||||
|
||||
constexpr float inv_scale = 1.0f / VOLUME_0DB;
|
||||
|
||||
for (u32 i = 0; i < frames; i++) {
|
||||
d[i] = s[i] * inv_scale;
|
||||
}
|
||||
}
|
||||
|
||||
static void ConvertS16Stereo(const void* src, void* dst, u32 frames, const float*) {
|
||||
const s16* s = static_cast<const s16*>(src);
|
||||
float* d = static_cast<float*>(dst);
|
||||
|
||||
constexpr float inv_scale = 1.0f / VOLUME_0DB;
|
||||
|
||||
for (u32 i = 0; i < frames; i++) {
|
||||
d[i * 2] = s[i * 2] * inv_scale;
|
||||
d[i * 2 + 1] = s[i * 2 + 1] * inv_scale;
|
||||
}
|
||||
}
|
||||
|
||||
static void ConvertS16_8CH(const void* src, void* dst, u32 frames, const float*) {
|
||||
const s16* s = static_cast<const s16*>(src);
|
||||
float* d = static_cast<float*>(dst);
|
||||
|
||||
constexpr float inv_scale = 1.0f / VOLUME_0DB;
|
||||
|
||||
for (u32 i = 0; i < frames; i++) {
|
||||
for (int ch = 0; ch < 8; ch++) {
|
||||
d[i * 8 + ch] = s[i * 8 + ch] * inv_scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Float converters become simple memcpy or passthrough
|
||||
static void ConvertF32Mono(const void* src, void* dst, u32 frames, const float*) {
|
||||
std::memcpy(dst, src, frames * sizeof(float));
|
||||
}
|
||||
|
||||
static void ConvertF32Stereo(const void* src, void* dst, u32 frames, const float*) {
|
||||
std::memcpy(dst, src, frames * 2 * sizeof(float));
|
||||
}
|
||||
|
||||
static void ConvertF32_8CH(const void* src, void* dst, u32 frames, const float*) {
|
||||
std::memcpy(dst, src, frames * 8 * sizeof(float));
|
||||
}
|
||||
|
||||
static void ConvertF32Std8CH(const void* src, void* dst, u32 frames, const float*) {
|
||||
const float* s = static_cast<const float*>(src);
|
||||
float* d = static_cast<float*>(dst);
|
||||
|
||||
for (u32 i = 0; i < frames; i++) {
|
||||
d[i * 8 + FL] = s[i * 8 + FL];
|
||||
d[i * 8 + FR] = s[i * 8 + FR];
|
||||
d[i * 8 + FC] = s[i * 8 + FC];
|
||||
d[i * 8 + LF] = s[i * 8 + LF];
|
||||
d[i * 8 + SL] = s[i * 8 + STD_SL]; // Channel remapping still needed
|
||||
d[i * 8 + SR] = s[i * 8 + STD_SR];
|
||||
d[i * 8 + BL] = s[i * 8 + STD_BL];
|
||||
d[i * 8 + BR] = s[i * 8 + STD_BR];
|
||||
}
|
||||
}
|
||||
|
||||
// Member variables
|
||||
u32 frame_size;
|
||||
u32 guest_buffer_size;
|
||||
u32 buffer_frames;
|
||||
u32 sample_rate;
|
||||
u32 num_channels;
|
||||
bool is_float;
|
||||
bool is_std;
|
||||
std::array<int, 8> channel_layout;
|
||||
|
||||
u64 period_us;
|
||||
u64 last_output_time;
|
||||
u64 next_output_time;
|
||||
|
||||
// Buffers
|
||||
u32 internal_buffer_size;
|
||||
void* internal_buffer;
|
||||
|
||||
// Converter function
|
||||
ConverterFunc convert;
|
||||
|
||||
// Volume tracking
|
||||
std::atomic<float> current_gain{1.0f};
|
||||
mutable std::mutex volume_mutex;
|
||||
|
||||
// SDL
|
||||
SDL_AudioStream* stream{};
|
||||
u32 queue_threshold{};
|
||||
};
|
||||
|
||||
std::unique_ptr<PortBackend> SDLAudioOut::Open(PortOut& port) {
|
||||
return std::make_unique<SDLPortBackend>(port);
|
||||
}
|
||||
|
||||
} // namespace Libraries::AudioOut
|
||||
@ -1,139 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
#include <common/config.h>
|
||||
#include <common/logging/log.h>
|
||||
#include "sdl_in.h"
|
||||
|
||||
int SDLAudioIn::AudioInit() {
|
||||
return SDL_InitSubSystem(SDL_INIT_AUDIO);
|
||||
}
|
||||
|
||||
int SDLAudioIn::AudioInOpen(int type, uint32_t samples_num, uint32_t freq, uint32_t format) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
for (int id = 0; id < static_cast<int>(portsIn.size()); ++id) {
|
||||
auto& port = portsIn[id];
|
||||
if (!port.isOpen) {
|
||||
port.isOpen = true;
|
||||
port.type = type;
|
||||
port.samples_num = samples_num;
|
||||
port.freq = freq;
|
||||
port.format = format;
|
||||
|
||||
SDL_AudioFormat sampleFormat;
|
||||
switch (format) {
|
||||
case Libraries::AudioIn::ORBIS_AUDIO_IN_PARAM_FORMAT_S16_MONO:
|
||||
sampleFormat = SDL_AUDIO_S16;
|
||||
port.channels_num = 1;
|
||||
port.sample_size = 2;
|
||||
break;
|
||||
case Libraries::AudioIn::ORBIS_AUDIO_IN_PARAM_FORMAT_S16_STEREO:
|
||||
sampleFormat = SDL_AUDIO_S16;
|
||||
port.channels_num = 2;
|
||||
port.sample_size = 2;
|
||||
break;
|
||||
default:
|
||||
port.isOpen = false;
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_PORT;
|
||||
}
|
||||
|
||||
SDL_AudioSpec fmt;
|
||||
SDL_zero(fmt);
|
||||
fmt.format = sampleFormat;
|
||||
fmt.channels = port.channels_num;
|
||||
fmt.freq = port.freq;
|
||||
|
||||
std::string micDevStr = Config::getMicDevice();
|
||||
uint32_t devId;
|
||||
|
||||
bool nullDevice = false;
|
||||
if (micDevStr == "None") {
|
||||
nullDevice = true;
|
||||
} else if (micDevStr == "Default Device") {
|
||||
devId = SDL_AUDIO_DEVICE_DEFAULT_RECORDING;
|
||||
} else {
|
||||
try {
|
||||
devId = static_cast<uint32_t>(std::stoul(micDevStr));
|
||||
} catch (const std::exception& e) {
|
||||
nullDevice = true;
|
||||
}
|
||||
}
|
||||
|
||||
port.stream =
|
||||
nullDevice ? nullptr : SDL_OpenAudioDeviceStream(devId, &fmt, nullptr, nullptr);
|
||||
|
||||
if (!port.stream) {
|
||||
// if stream is null, either due to configuration disabling the input,
|
||||
// or no input devices present in the system, still return a valid id
|
||||
// as some games require that (e.g. L.A. Noire)
|
||||
return id + 1;
|
||||
}
|
||||
|
||||
if (SDL_ResumeAudioStreamDevice(port.stream) == false) {
|
||||
SDL_DestroyAudioStream(port.stream);
|
||||
port = {};
|
||||
return ORBIS_AUDIO_IN_ERROR_STREAM_FAIL;
|
||||
}
|
||||
|
||||
return id + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_PORT;
|
||||
}
|
||||
|
||||
int SDLAudioIn::AudioInInput(int handle, void* out_buffer) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
if (handle < 1 || handle > static_cast<int>(portsIn.size()) || !out_buffer)
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_PORT;
|
||||
|
||||
auto& port = portsIn[handle - 1];
|
||||
if (!port.isOpen)
|
||||
return ORBIS_AUDIO_IN_ERROR_INVALID_PORT;
|
||||
|
||||
const int bytesToRead = port.samples_num * port.sample_size * port.channels_num;
|
||||
|
||||
if (out_buffer == nullptr) {
|
||||
int attempts = 0;
|
||||
while (SDL_GetAudioStreamAvailable(port.stream) > 0) {
|
||||
SDL_Delay(1);
|
||||
if (++attempts > 1000) {
|
||||
return ORBIS_AUDIO_IN_ERROR_TIMEOUT;
|
||||
}
|
||||
}
|
||||
return 0; // done
|
||||
}
|
||||
|
||||
int attempts = 0;
|
||||
while (SDL_GetAudioStreamAvailable(port.stream) < bytesToRead) {
|
||||
SDL_Delay(1);
|
||||
if (++attempts > 1000) {
|
||||
return ORBIS_AUDIO_IN_ERROR_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
const int bytesRead = SDL_GetAudioStreamData(port.stream, out_buffer, bytesToRead);
|
||||
if (bytesRead < 0) {
|
||||
// SDL_GetAudioStreamData failed
|
||||
LOG_ERROR(Lib_AudioIn, "AudioInInput error: {}", SDL_GetError());
|
||||
return ORBIS_AUDIO_IN_ERROR_STREAM_FAIL;
|
||||
}
|
||||
const int framesRead = bytesRead / (port.sample_size * port.channels_num);
|
||||
return framesRead;
|
||||
}
|
||||
|
||||
void SDLAudioIn::AudioInClose(int handle) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (handle < 1 || handle > (int)portsIn.size())
|
||||
return;
|
||||
|
||||
auto& port = portsIn[handle - 1];
|
||||
if (!port.isOpen)
|
||||
return;
|
||||
|
||||
SDL_DestroyAudioStream(port.stream);
|
||||
port = {};
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
namespace Libraries::AudioIn {
|
||||
enum OrbisAudioInParam {
|
||||
ORBIS_AUDIO_IN_PARAM_FORMAT_S16_MONO = 0,
|
||||
ORBIS_AUDIO_IN_PARAM_FORMAT_S16_STEREO = 2
|
||||
};
|
||||
}
|
||||
|
||||
#define ORBIS_AUDIO_IN_ERROR_INVALID_PORT -1
|
||||
#define ORBIS_AUDIO_IN_ERROR_TIMEOUT -2
|
||||
#define ORBIS_AUDIO_IN_ERROR_STREAM_FAIL -3
|
||||
|
||||
class SDLAudioIn {
|
||||
public:
|
||||
int AudioInit();
|
||||
int AudioInOpen(int type, uint32_t samples_num, uint32_t freq, uint32_t format);
|
||||
int AudioInInput(int handle, void* out_buffer);
|
||||
void AudioInClose(int handle);
|
||||
|
||||
private:
|
||||
struct AudioInPort {
|
||||
bool isOpen = false;
|
||||
int type = 0;
|
||||
uint32_t samples_num = 0;
|
||||
uint32_t freq = 0;
|
||||
int channels_num = 0;
|
||||
int sample_size = 0;
|
||||
uint32_t format = 0;
|
||||
SDL_AudioStream* stream = nullptr;
|
||||
};
|
||||
|
||||
std::array<AudioInPort, 8> portsIn;
|
||||
std::mutex m_mutex;
|
||||
};
|
||||
@ -108,6 +108,8 @@ auto output_array = std::array{
|
||||
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_GYRO),
|
||||
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD),
|
||||
ControllerOutput(HOTKEY_RENDERDOC),
|
||||
ControllerOutput(HOTKEY_VOLUME_UP),
|
||||
ControllerOutput(HOTKEY_VOLUME_DOWN),
|
||||
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID),
|
||||
};
|
||||
@ -562,6 +564,9 @@ void ControllerOutput::FinalizeUpdate() {
|
||||
case RIGHTJOYSTICK_HALFMODE:
|
||||
rightjoystick_halfmode = new_button_state;
|
||||
break;
|
||||
case HOTKEY_RELOAD_INPUTS:
|
||||
ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial()));
|
||||
break;
|
||||
case HOTKEY_FULLSCREEN:
|
||||
PushSDLEvent(SDL_EVENT_TOGGLE_FULLSCREEN);
|
||||
break;
|
||||
@ -571,9 +576,6 @@ void ControllerOutput::FinalizeUpdate() {
|
||||
case HOTKEY_SIMPLE_FPS:
|
||||
PushSDLEvent(SDL_EVENT_TOGGLE_SIMPLE_FPS);
|
||||
break;
|
||||
case HOTKEY_RELOAD_INPUTS:
|
||||
PushSDLEvent(SDL_EVENT_RELOAD_INPUTS);
|
||||
break;
|
||||
case HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK:
|
||||
PushSDLEvent(SDL_EVENT_MOUSE_TO_JOYSTICK);
|
||||
break;
|
||||
@ -586,6 +588,12 @@ void ControllerOutput::FinalizeUpdate() {
|
||||
case HOTKEY_RENDERDOC:
|
||||
PushSDLEvent(SDL_EVENT_RDOC_CAPTURE);
|
||||
break;
|
||||
case HOTKEY_VOLUME_UP:
|
||||
Config::setVolumeSlider(Config::getVolumeSlider() + 10, true);
|
||||
break;
|
||||
case HOTKEY_VOLUME_DOWN:
|
||||
Config::setVolumeSlider(Config::getVolumeSlider() - 10, true);
|
||||
break;
|
||||
case HOTKEY_QUIT:
|
||||
PushSDLEvent(SDL_EVENT_QUIT_DIALOG);
|
||||
break;
|
||||
|
||||
@ -55,6 +55,8 @@
|
||||
#define HOTKEY_TOGGLE_MOUSE_TO_GYRO 0xf0000007
|
||||
#define HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD 0xf0000008
|
||||
#define HOTKEY_RENDERDOC 0xf0000009
|
||||
#define HOTKEY_VOLUME_UP 0xf000000a
|
||||
#define HOTKEY_VOLUME_DOWN 0xf000000b
|
||||
|
||||
#define SDL_UNMAPPED UINT32_MAX - 1
|
||||
|
||||
@ -145,6 +147,8 @@ const std::map<std::string, u32> string_to_cbutton_map = {
|
||||
{"hotkey_toggle_mouse_to_gyro", HOTKEY_TOGGLE_MOUSE_TO_GYRO},
|
||||
{"hotkey_toggle_mouse_to_touchpad", HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD},
|
||||
{"hotkey_renderdoc_capture", HOTKEY_RENDERDOC},
|
||||
{"hotkey_volume_up", HOTKEY_VOLUME_UP},
|
||||
{"hotkey_volume_down", HOTKEY_VOLUME_DOWN},
|
||||
};
|
||||
|
||||
const std::map<std::string, AxisMapping> string_to_axis_map = {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user