This commit is contained in:
georgemoralis 2026-07-10 07:43:50 +00:00 committed by GitHub
commit bc9c6187f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 1377 additions and 105 deletions

View File

@ -47,6 +47,12 @@ enum AudioBackend : int {
// Add more backends as needed
};
enum OpenALHrtfMode : int {
HrtfAuto, // Let OpenAL Soft decide (on for headphone-like stereo outputs)
HrtfOn, // Force HRTF binaural rendering
HrtfOff, // Never use HRTF
};
template <typename T>
struct Setting {
T default_value{};
@ -336,6 +342,7 @@ struct AudioSettings {
Setting<std::string> openal_mic_device{"Default Device"};
Setting<std::string> openal_main_output_device{"Default Device"};
Setting<std::string> openal_padSpk_output_device{"Default Device"};
Setting<u32> openal_hrtf{OpenALHrtfMode::HrtfAuto};
std::vector<OverrideItem> GetOverrideableFields() const {
return std::vector<OverrideItem>{
@ -349,14 +356,15 @@ struct AudioSettings {
make_override<AudioSettings>("openal_main_output_device",
&AudioSettings::openal_main_output_device),
make_override<AudioSettings>("openal_padSpk_output_device",
&AudioSettings::openal_padSpk_output_device)};
&AudioSettings::openal_padSpk_output_device),
make_override<AudioSettings>("openal_hrtf", &AudioSettings::openal_hrtf)};
}
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(AudioSettings, audio_backend, sdl_mic_device,
sdl_main_output_device, sdl_padSpk_output_device,
openal_mic_device, openal_main_output_device,
openal_padSpk_output_device)
openal_padSpk_output_device, openal_hrtf)
// -------------------------------
// GPU settings
@ -632,6 +640,7 @@ public:
SETTING_FORWARD(m_audio, OpenALMicDevice, openal_mic_device)
SETTING_FORWARD(m_audio, OpenALMainOutputDevice, openal_main_output_device)
SETTING_FORWARD(m_audio, OpenALPadSpkOutputDevice, openal_padSpk_output_device)
SETTING_FORWARD(m_audio, OpenALHrtf, openal_hrtf)
// Debug settings
SETTING_FORWARD_BOOL(m_debug, DebugDump, debug_dump)

View File

@ -10,6 +10,22 @@
#include <AL/al.h>
#include <AL/alc.h>
#include <alext.h>
// Fallbacks in case the alext.h in use predates these extensions.
#ifndef ALC_SOFT_output_mode
#define ALC_OUTPUT_MODE_SOFT 0x19AC
#define ALC_MONO_SOFT 0x1500
#define ALC_STEREO_SOFT 0x1501
#define ALC_STEREO_BASIC_SOFT 0x19AE
#define ALC_STEREO_UHJ_SOFT 0x19AF
#define ALC_STEREO_HRTF_SOFT 0x19B2
#define ALC_SURROUND_5_1_SOFT 0x1504
#define ALC_SURROUND_6_1_SOFT 0x1505
#define ALC_SURROUND_7_1_SOFT 0x1506
#endif
#ifndef AL_SOFT_direct_channels_remix
#define AL_REMIX_UNMATCHED_SOFT 0x0002
#endif
#include <queue>
#include "common/logging/log.h"
#include "core/emulator_settings.h"
@ -98,6 +114,10 @@ public:
convert(ptr, al_buffer_s16.data(), buffer_frames, nullptr);
}
if (fold_lfe) {
FoldLfeIntoFronts();
}
// Reclaim processed buffers
ALint processed = 0;
alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed);
@ -166,6 +186,7 @@ public:
const float channel_gain = static_cast<float>(ch_volumes[i]) * INV_VOLUME_0DB;
max_channel_gain = std::max(max_channel_gain, channel_gain);
}
game_gain.store(max_channel_gain, std::memory_order_release);
const float slider_gain = EmulatorSettings.GetVolumeSlider() * 0.01f;
const float total_gain = max_channel_gain * slider_gain;
@ -235,13 +256,48 @@ private:
return false;
}
ALCdevice* alc_dev = alcGetContextsDevice(alcGetCurrentContext());
ALCint output_mode = 0;
bool know_output_mode = false;
if (alc_dev && alcIsExtensionPresent(alc_dev, "ALC_SOFT_output_mode")) {
alcGetIntegerv(alc_dev, ALC_OUTPUT_MODE_SOFT, 1, &output_mode);
know_output_mode = true;
}
const bool output_has_lfe = output_mode == ALC_SURROUND_5_1_SOFT ||
output_mode == ALC_SURROUND_6_1_SOFT ||
output_mode == ALC_SURROUND_7_1_SOFT;
fold_lfe = know_output_mode && !output_has_lfe && num_channels >= 6 && !downmix_to_stereo;
if (fold_lfe) {
LOG_INFO(Lib_AudioOut, "Output has no LFE channel; folding buffer LFE into fronts");
}
if (alc_dev && alcIsExtensionPresent(alc_dev, "ALC_SOFT_HRTF")) {
ALCint hrtf_on = 0;
alcGetIntegerv(alc_dev, ALC_HRTF_SOFT, 1, &hrtf_on);
hrtf_active = hrtf_on == ALC_TRUE;
}
const bool stereo_output = output_mode == ALC_MONO_SOFT || output_mode == ALC_STEREO_SOFT ||
output_mode == ALC_STEREO_BASIC_SOFT ||
output_mode == ALC_STEREO_UHJ_SOFT ||
output_mode == ALC_STEREO_HRTF_SOFT;
if (know_output_mode && stereo_output && !hrtf_active && num_channels >= 6 &&
!downmix_to_stereo) {
downmix_to_stereo = true;
use_native_float = has_float_ext;
format = has_float_ext ? AL_FORMAT_STEREO_FLOAT32 : AL_FORMAT_STEREO16;
fold_lfe = false; // The downmix converters already carry LFE.
LOG_INFO(Lib_AudioOut, "Stereo output: using internal {}ch->stereo downmix ({})",
num_channels, has_float_ext ? "float" : "int16");
}
// Allocate buffers based on format
const u32 out_channels = downmix_to_stereo ? 2u : num_channels;
if (use_native_float) {
al_buffer_float.resize(buffer_frames * num_channels);
buffer_size_bytes = buffer_frames * num_channels * sizeof(float);
al_buffer_float.resize(buffer_frames * out_channels);
buffer_size_bytes = buffer_frames * out_channels * sizeof(float);
} else {
al_buffer_s16.resize(buffer_frames * num_channels);
buffer_size_bytes = buffer_frames * num_channels * sizeof(s16);
al_buffer_s16.resize(buffer_frames * out_channels);
buffer_size_bytes = buffer_frames * out_channels * sizeof(s16);
}
// Select optimal converter function
@ -332,7 +388,8 @@ private:
last_volume_check_time = current_time;
const float config_volume = EmulatorSettings.GetVolumeSlider() * 0.01f;
const float config_volume =
EmulatorSettings.GetVolumeSlider() * 0.01f * game_gain.load(std::memory_order_acquire);
const float stored_gain = current_gain.load(std::memory_order_acquire);
if (std::abs(config_volume - stored_gain) > VOLUME_EPSILON) {
@ -372,6 +429,8 @@ private:
}
bool DetermineOpenALFormat() {
alGetError();
// Try to use native float formats if extension is available
if (is_float && has_float_ext) {
switch (num_channels) {
@ -431,6 +490,7 @@ private:
if (format == 0 || alGetError() != AL_NO_ERROR) {
LOG_WARNING(Lib_AudioOut, "5.1 format not supported, falling back to stereo");
format = AL_FORMAT_STEREO16;
downmix_to_stereo = true;
}
break;
case 8:
@ -438,6 +498,7 @@ private:
if (format == 0 || alGetError() != AL_NO_ERROR) {
LOG_WARNING(Lib_AudioOut, "7.1 format not supported, falling back to stereo");
format = AL_FORMAT_STEREO16;
downmix_to_stereo = true;
}
break;
default:
@ -458,6 +519,7 @@ private:
if (format == 0 || alGetError() != AL_NO_ERROR) {
LOG_WARNING(Lib_AudioOut, "5.1 format not supported, falling back to stereo");
format = AL_FORMAT_STEREO16;
downmix_to_stereo = true;
}
break;
case 8:
@ -465,6 +527,7 @@ private:
if (format == 0 || alGetError() != AL_NO_ERROR) {
LOG_WARNING(Lib_AudioOut, "7.1 format not supported, falling back to stereo");
format = AL_FORMAT_STEREO16;
downmix_to_stereo = true;
}
break;
default:
@ -501,11 +564,38 @@ private:
alSourcei(source, AL_LOOPING, AL_FALSE);
alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
if (alIsExtensionPresent("AL_SOFT_direct_channels")) {
if (num_channels == 2 || downmix_to_stereo) {
alSourcei(source, AL_DIRECT_CHANNELS_SOFT, AL_TRUE);
} else if (num_channels > 2 && !hrtf_active &&
alIsExtensionPresent("AL_SOFT_direct_channels_remix")) {
alSourcei(source, AL_DIRECT_CHANNELS_SOFT, AL_REMIX_UNMATCHED_SOFT);
}
}
LOG_DEBUG(Lib_AudioOut, "Created OpenAL source {} with {} buffers", source, buffers.size());
return true;
}
bool SelectConverter() {
if (downmix_to_stereo) {
if (use_native_float) {
if (is_float) {
convert =
num_channels == 8 ? &DownmixF32_8CHToStereoF32 : &DownmixF32_6CHToStereoF32;
} else {
convert =
num_channels == 8 ? &DownmixS16_8CHToStereoF32 : &DownmixS16_6CHToStereoF32;
}
} else if (is_float) {
convert =
num_channels == 8 ? &DownmixF32_8CHToStereoS16 : &DownmixF32_6CHToStereoS16;
} else {
convert = num_channels == 8 ? &DownmixS16_8CHToStereo : &DownmixS16_6CHToStereo;
}
return true;
}
if (is_float && use_native_float) {
// Native float - just copy/remap if needed
switch (num_channels) {
@ -556,7 +646,7 @@ private:
convert = &ConvertS16Stereo;
break;
case 8:
convert = &ConvertS16_8CH;
convert = is_std ? &ConvertS16Std8CH : &ConvertS16_8CH;
break;
default:
LOG_ERROR(Lib_AudioOut, "Unsupported S16 channel count: {}", num_channels);
@ -624,6 +714,146 @@ private:
const u32 num_samples = frames << 3;
std::memcpy(d, s, num_samples * sizeof(s16));
}
static void ConvertS16Std8CH(const void* src, void* dst, u32 frames, const float*) {
const s16* s = static_cast<const s16*>(src);
s16* d = static_cast<s16*>(dst);
for (u32 i = 0; i < frames; i++) {
const u32 offset = i << 3;
d[offset + FL] = s[offset + FL];
d[offset + FR] = s[offset + FR];
d[offset + FC] = s[offset + FC];
d[offset + LF] = s[offset + LF];
d[offset + SL] = s[offset + STD_SL];
d[offset + SR] = s[offset + STD_SR];
d[offset + BL] = s[offset + STD_BL];
d[offset + BR] = s[offset + STD_BR];
}
}
static inline s16 ClampSampleToS16(const float v) {
return static_cast<s16>(std::clamp(v, -32768.0f, 32767.0f));
}
static void DownmixS16_6CHToStereo(const void* src, void* dst, u32 frames, const float*) {
const s16* s = static_cast<const s16*>(src);
s16* d = static_cast<s16*>(dst);
for (u32 i = 0; i < frames; i++) {
const u32 o = i * 6;
const float center = 0.7071f * s[o + FC];
const float lfe = 0.5f * s[o + LF];
d[i * 2 + 0] = ClampSampleToS16(s[o + FL] + center + lfe + 0.7071f * s[o + 4]);
d[i * 2 + 1] = ClampSampleToS16(s[o + FR] + center + lfe + 0.7071f * s[o + 5]);
}
}
static void DownmixS16_8CHToStereo(const void* src, void* dst, u32 frames, const float*) {
const s16* s = static_cast<const s16*>(src);
s16* d = static_cast<s16*>(dst);
for (u32 i = 0; i < frames; i++) {
const u32 o = i << 3;
const float center = 0.7071f * s[o + FC];
const float lfe = 0.5f * s[o + LF];
d[i * 2 + 0] =
ClampSampleToS16(s[o + FL] + center + lfe + 0.7071f * (s[o + 4] + s[o + 6]));
d[i * 2 + 1] =
ClampSampleToS16(s[o + FR] + center + lfe + 0.7071f * (s[o + 5] + s[o + 7]));
}
}
static void DownmixF32_6CHToStereoS16(const void* src, void* dst, u32 frames, const float*) {
const float* s = static_cast<const float*>(src);
s16* d = static_cast<s16*>(dst);
for (u32 i = 0; i < frames; i++) {
const u32 o = i * 6;
const float center = 0.7071f * s[o + FC];
const float lfe = 0.5f * s[o + LF];
d[i * 2 + 0] = OrbisFloatToS16(s[o + FL] + center + lfe + 0.7071f * s[o + 4]);
d[i * 2 + 1] = OrbisFloatToS16(s[o + FR] + center + lfe + 0.7071f * s[o + 5]);
}
}
void FoldLfeIntoFronts() {
constexpr float LFE_GAIN = 0.7071f;
if (use_native_float) {
float* d = al_buffer_float.data();
for (u32 i = 0; i < buffer_frames; i++) {
float* f = d + static_cast<size_t>(i) * num_channels;
const float lfe = f[LF] * LFE_GAIN;
f[FL] += lfe;
f[FR] += lfe;
f[LF] = 0.0f;
}
} else {
s16* d = al_buffer_s16.data();
for (u32 i = 0; i < buffer_frames; i++) {
s16* f = d + static_cast<size_t>(i) * num_channels;
const float lfe = static_cast<float>(f[LF]) * LFE_GAIN;
f[FL] = ClampSampleToS16(static_cast<float>(f[FL]) + lfe);
f[FR] = ClampSampleToS16(static_cast<float>(f[FR]) + lfe);
f[LF] = 0;
}
}
}
static void DownmixS16_6CHToStereoF32(const void* src, void* dst, u32 frames, const float*) {
constexpr float INV = 1.0f / 32768.0f;
const s16* s = static_cast<const s16*>(src);
float* d = static_cast<float*>(dst);
for (u32 i = 0; i < frames; i++) {
const u32 o = i * 6;
const float center = 0.7071f * s[o + FC];
const float lfe = 0.5f * s[o + LF];
d[i * 2 + 0] = (s[o + FL] + center + lfe + 0.7071f * s[o + 4]) * INV;
d[i * 2 + 1] = (s[o + FR] + center + lfe + 0.7071f * s[o + 5]) * INV;
}
}
static void DownmixS16_8CHToStereoF32(const void* src, void* dst, u32 frames, const float*) {
constexpr float INV = 1.0f / 32768.0f;
const s16* s = static_cast<const s16*>(src);
float* d = static_cast<float*>(dst);
for (u32 i = 0; i < frames; i++) {
const u32 o = i << 3;
const float center = 0.7071f * s[o + FC];
const float lfe = 0.5f * s[o + LF];
d[i * 2 + 0] = (s[o + FL] + center + lfe + 0.7071f * (s[o + 4] + s[o + 6])) * INV;
d[i * 2 + 1] = (s[o + FR] + center + lfe + 0.7071f * (s[o + 5] + s[o + 7])) * INV;
}
}
static void DownmixF32_6CHToStereoF32(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++) {
const u32 o = i * 6;
const float center = 0.7071f * s[o + FC];
const float lfe = 0.5f * s[o + LF];
d[i * 2 + 0] = s[o + FL] + center + lfe + 0.7071f * s[o + 4];
d[i * 2 + 1] = s[o + FR] + center + lfe + 0.7071f * s[o + 5];
}
}
static void DownmixF32_8CHToStereoF32(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++) {
const u32 o = i << 3;
const float center = 0.7071f * s[o + FC];
const float lfe = 0.5f * s[o + LF];
d[i * 2 + 0] = s[o + FL] + center + lfe + 0.7071f * (s[o + 4] + s[o + 6]);
d[i * 2 + 1] = s[o + FR] + center + lfe + 0.7071f * (s[o + 5] + s[o + 7]);
}
}
static void DownmixF32_8CHToStereoS16(const void* src, void* dst, u32 frames, const float*) {
const float* s = static_cast<const float*>(src);
s16* d = static_cast<s16*>(dst);
for (u32 i = 0; i < frames; i++) {
const u32 o = i << 3;
const float center = 0.7071f * s[o + FC];
const float lfe = 0.5f * s[o + LF];
d[i * 2 + 0] =
OrbisFloatToS16(s[o + FL] + center + lfe + 0.7071f * (s[o + 4] + s[o + 6]));
d[i * 2 + 1] =
OrbisFloatToS16(s[o + FR] + center + lfe + 0.7071f * (s[o + 5] + s[o + 7]));
}
}
// Float passthrough converters (for AL_EXT_FLOAT32)
static void ConvertF32Mono(const void* src, void* dst, u32 frames, const float*) {
@ -814,12 +1044,16 @@ private:
// Extension support
bool has_float_ext{false};
bool use_native_float{false};
bool downmix_to_stereo{false};
bool fold_lfe{false};
bool hrtf_active{false};
// Converter function pointer
ConverterFunc convert{nullptr};
// Volume management
alignas(64) std::atomic<float> current_gain{1.0f};
std::atomic<float> game_gain{1.0f};
std::string device_name;
bool device_registered;

View File

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <mutex>
#include <string>
#include <unordered_map>
@ -8,6 +9,22 @@
#include <AL/al.h>
#include <AL/alc.h>
#include "common/logging/log.h"
#include "core/emulator_settings.h"
// ALC_SOFT_HRTF constants, in case the alext.h in use predates the extension.
#ifndef ALC_SOFT_HRTF
#define ALC_HRTF_SOFT 0x1992
#define ALC_DONT_CARE_SOFT 0x0002
#define ALC_HRTF_STATUS_SOFT 0x1993
#define ALC_HRTF_DISABLED_SOFT 0x0000
#define ALC_HRTF_ENABLED_SOFT 0x0001
#define ALC_HRTF_DENIED_SOFT 0x0002
#define ALC_HRTF_REQUIRED_SOFT 0x0003
#define ALC_HRTF_HEADPHONES_DETECTED_SOFT 0x0004
#define ALC_HRTF_UNSUPPORTED_FORMAT_SOFT 0x0005
#endif
namespace Libraries::AudioOut {
struct DeviceContext {
@ -196,8 +213,23 @@ private:
return false;
}
// Create context
ctx.context = alcCreateContext(ctx.device, nullptr);
std::array<ALCint, 5> attrs{};
std::size_t attr_count = 0;
attrs[attr_count++] = ALC_FREQUENCY;
attrs[attr_count++] = 48000;
const bool has_hrtf_ext = alcIsExtensionPresent(ctx.device, "ALC_SOFT_HRTF");
if (has_hrtf_ext) {
const u32 hrtf_mode = EmulatorSettings.GetOpenALHrtf();
const ALCint hrtf_value = hrtf_mode == OpenALHrtfMode::HrtfOn ? ALC_TRUE
: hrtf_mode == OpenALHrtfMode::HrtfOff ? ALC_FALSE
: ALC_DONT_CARE_SOFT;
attrs[attr_count++] = ALC_HRTF_SOFT;
attrs[attr_count++] = hrtf_value;
}
attrs[attr_count] = 0;
ctx.context = alcCreateContext(ctx.device, attrs.data());
if (!ctx.context) {
LOG_ERROR(Lib_AudioOut, "Failed to create OpenAL context");
alcCloseDevice(ctx.device);
@ -213,11 +245,47 @@ private:
actual_name = alcGetString(ctx.device, ALC_DEVICE_SPECIFIER);
}
ctx.device_name = actual_name ? actual_name : "Unknown";
ALCint mixer_rate = 0;
alcGetIntegerv(ctx.device, ALC_FREQUENCY, 1, &mixer_rate);
LOG_INFO(Lib_AudioOut, "OpenAL mixer rate for '{}': {} Hz", ctx.device_name, mixer_rate);
if (mixer_rate != 0 && mixer_rate != 48000) {
LOG_WARNING(Lib_AudioOut,
"OpenAL mixer is not running at 48000 Hz per-source resampling active");
}
if (has_hrtf_ext) {
ALCint status = ALC_HRTF_DISABLED_SOFT;
alcGetIntegerv(ctx.device, ALC_HRTF_STATUS_SOFT, 1, &status);
LOG_INFO(Lib_AudioOut, "OpenAL HRTF status for '{}': {}", ctx.device_name,
HrtfStatusString(status));
} else {
LOG_INFO(Lib_AudioOut, "OpenAL device '{}' does not support ALC_SOFT_HRTF",
ctx.device_name);
}
LOG_INFO(Lib_AudioOut, "OpenAL device initialized: '{}'", ctx.device_name);
return true;
}
static const char* HrtfStatusString(const ALCint status) {
switch (status) {
case ALC_HRTF_DISABLED_SOFT:
return "disabled";
case ALC_HRTF_ENABLED_SOFT:
return "enabled";
case ALC_HRTF_DENIED_SOFT:
return "denied by configuration";
case ALC_HRTF_REQUIRED_SOFT:
return "required by configuration";
case ALC_HRTF_HEADPHONES_DETECTED_SOFT:
return "enabled (headphones detected)";
case ALC_HRTF_UNSUPPORTED_FORMAT_SOFT:
return "unsupported output format";
default:
return "unknown";
}
}
std::unordered_map<std::string, DeviceContext> devices;
mutable std::mutex mutex;
ALCcontext* current_context{nullptr}; // For thread-local tracking

View File

@ -109,6 +109,7 @@ public:
const float channel_gain = static_cast<float>(ch_volumes[i]) * INV_VOLUME_0DB;
max_channel_gain = std::max(max_channel_gain, channel_gain);
}
game_gain.store(max_channel_gain, std::memory_order_release);
const float slider_gain = EmulatorSettings.GetVolumeSlider() * 0.01f; // Faster than /100.0f
const float total_gain = max_channel_gain * slider_gain;
@ -201,7 +202,8 @@ private:
last_volume_check_time = current_time;
const float config_volume = EmulatorSettings.GetVolumeSlider() * 0.01f;
const float config_volume =
EmulatorSettings.GetVolumeSlider() * 0.01f * game_gain.load(std::memory_order_acquire);
const float stored_gain = current_gain.load(std::memory_order_acquire);
// Only update if the difference is significant
@ -587,6 +589,7 @@ private:
// Volume management
alignas(64) std::atomic<float> current_gain{1.0f};
std::atomic<float> game_gain{1.0f};
// SDL audio stream
SDL_AudioStream* stream{nullptr};

View File

@ -24,15 +24,67 @@ static constexpr u32 AUDIO3D_OUTPUT_NUM_CHANNELS = 2;
static std::unique_ptr<Audio3dState> state;
struct AudioOutBufferInfo {
u32 channels;
u32 sample_size;
};
static AudioOutBufferInfo GetAudioOutBufferInfo(const AudioOut::OrbisAudioOutParamFormat format) {
using Format = AudioOut::OrbisAudioOutParamFormat;
switch (format) {
case Format::S16Mono:
return {1, sizeof(s16)};
case Format::S16Stereo:
return {2, sizeof(s16)};
case Format::S16_8CH:
case Format::S16_8CH_Std:
return {8, sizeof(s16)};
case Format::FloatMono:
return {1, sizeof(float)};
case Format::FloatStereo:
return {2, sizeof(float)};
case Format::Float_8CH:
case Format::Float_8CH_Std:
return {8, sizeof(float)};
default:
return {2, sizeof(s16)};
}
}
static s32 DrainAssociatedPorts(Port& port) {
while (true) {
s32 handle = -1;
std::vector<u8> buffer;
{
std::scoped_lock lock{port.mutex};
const auto it =
std::find_if(port.audioout_ports.begin(), port.audioout_ports.end(),
[](const AssociatedAudioOutPort& p) { return !p.pending.empty(); });
if (it == port.audioout_ports.end()) {
return ORBIS_OK;
}
handle = it->handle;
buffer = std::move(it->pending.front());
it->pending.pop_front();
}
const s32 ret = AudioOut::sceAudioOutOutput(handle, buffer.data());
if (ret < 0) {
return ret;
}
}
}
s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(const s32 handle) {
LOG_INFO(Lib_Audio3d, "called, handle = {}", handle);
// Remove from any port that was tracking this handle.
// Remove from any port that was tracking this handle. Pending buffers that
// were never pushed are discarded, matching an immediate close.
if (state) {
for (auto& [port_id, port] : state->ports) {
std::scoped_lock lock{port.mutex};
auto& handles = port.audioout_handles;
handles.erase(std::remove(handles.begin(), handles.end(), handle), handles.end());
std::erase_if(port.audioout_ports,
[&](const AssociatedAudioOutPort& p) { return p.handle == handle; });
}
}
@ -64,8 +116,12 @@ s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen(
return handle;
}
// Track this handle in the port so sceAudio3dPortFlush can use it for sync.
state->ports[port_id].audioout_handles.push_back(handle);
const auto info = GetAudioOutBufferInfo(param.data_format.Value());
AssociatedAudioOutPort aout{};
aout.handle = handle;
aout.buffer_bytes = len * info.channels * info.sample_size;
aout.samples_per_buffer = len * info.channels;
state->ports[port_id].audioout_ports.push_back(std::move(aout));
return handle;
}
@ -82,6 +138,36 @@ s32 PS4_SYSV_ABI sceAudio3dAudioOutOutput(const s32 handle, void* ptr) {
return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
}
if (state) {
for (auto& [port_id, port] : state->ports) {
std::scoped_lock lock{port.mutex};
for (auto& aout : port.audioout_ports) {
if (aout.handle != handle) {
continue;
}
if (aout.pending.size() >= port.parameters.queue_depth) {
LOG_DEBUG(Lib_Audio3d,
"AudioOut handle {} queue full ({}) without Push, "
"submitting oldest",
handle, aout.pending.size());
std::vector<u8> oldest = std::move(aout.pending.front());
aout.pending.pop_front();
const s32 ret = AudioOut::sceAudioOutOutput(handle, oldest.data());
if (ret < 0) {
return ret;
}
}
const u8* src = static_cast<const u8*>(ptr);
aout.pending.emplace_back(src, src + aout.buffer_bytes);
// Mirror sceAudioOutOutput's return of samples sent.
return static_cast<s32>(aout.samples_per_buffer);
}
}
}
return AudioOut::sceAudioOutOutput(handle, ptr);
}
@ -94,7 +180,14 @@ s32 PS4_SYSV_ABI sceAudio3dAudioOutOutputs(AudioOut::OrbisAudioOutOutputParam* p
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
}
return AudioOut::sceAudioOutOutputs(param, num);
for (u32 i = 0; i < num; i++) {
const s32 ret = sceAudio3dAudioOutOutput(param[i].handle, param[i].ptr);
if (ret < 0) {
return ret;
}
}
return ORBIS_OK;
}
static s32 ConvertAndEnqueue(std::deque<AudioData>& queue, const OrbisAudio3dPcm& pcm,
@ -593,10 +686,10 @@ s32 PS4_SYSV_ABI sceAudio3dPortClose(const OrbisAudio3dPortId port_id) {
port.audio_out_handle = -1;
}
for (const s32 handle : port.audioout_handles) {
AudioOut::sceAudioOutClose(handle);
for (const auto& aout : port.audioout_ports) {
AudioOut::sceAudioOutClose(aout.handle);
}
port.audioout_handles.clear();
port.audioout_ports.clear();
for (auto& data : port.mixed_queue) {
std::free(data.sample_buffer);
@ -652,9 +745,12 @@ s32 PS4_SYSV_ABI sceAudio3dPortFlush(const OrbisAudio3dPortId port_id) {
auto& port = state->ports[port_id];
std::scoped_lock lock{port.mutex};
if (!port.audioout_handles.empty()) {
for (const s32 handle : port.audioout_handles) {
const s32 ret = AudioOut::sceAudioOutOutput(handle, nullptr);
if (!port.audioout_ports.empty()) {
if (const s32 ret = DrainAssociatedPorts(port); ret < 0) {
return ret;
}
for (const auto& aout : port.audioout_ports) {
const s32 ret = AudioOut::sceAudioOutOutput(aout.handle, nullptr);
if (ret < 0) {
return ret;
}
@ -957,6 +1053,10 @@ s32 PS4_SYSV_ABI sceAudio3dPortPush(const OrbisAudio3dPortId port_id,
const u32 depth = port.parameters.queue_depth;
if (const s32 ret = DrainAssociatedPorts(port); ret < 0) {
return ret;
}
if (port.audio_out_handle < 0) {
AudioOut::OrbisAudioOutParamExtendedInformation ext_info{};
ext_info.data_format.Assign(AUDIO3D_OUTPUT_FORMAT);

View File

@ -103,13 +103,21 @@ struct ObjectState {
std::unordered_map<u32, std::vector<u8>> persistent_attributes;
};
// An AudioOut port opened by the game through sceAudio3dAudioOutOpen.
struct AssociatedAudioOutPort {
s32 handle{-1};
u32 buffer_bytes{0};
u32 samples_per_buffer{0};
std::deque<std::vector<u8>> pending;
};
struct Port {
mutable std::recursive_mutex mutex;
OrbisAudio3dOpenParameters parameters{};
// Opened lazily on the first sceAudio3dPortPush call.
s32 audio_out_handle{-1};
// Handles explicitly opened by the game via sceAudio3dAudioOutOpen.
std::vector<s32> audioout_handles;
// AudioOut ports explicitly opened by the game via sceAudio3dAudioOutOpen.
std::vector<AssociatedAudioOutPort> audioout_ports;
// Reserved objects and their state.
std::unordered_map<OrbisAudio3dObjectId, ObjectState> objects;
// increasing counter for generating unique object IDs within this port.

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@
#include <mutex>
#include <optional>
#include <string>
#include <vector>
#include <queue>
@ -82,6 +83,12 @@ enum class OrbisAudio3dAttributeId : u32 {
ORBIS_AUDIO3D_ATTRIBUTE_OUTPUT_ROUTE = 11,
};
enum class OrbisAudio3dPortAttributeId : u32 {
ORBIS_AUDIO3D_PORT_ATTRIBUTE_LATE_REVERB_LEVEL = 0x10001,
ORBIS_AUDIO3D_PORT_ATTRIBUTE_DOWNMIX_SPREAD_RADIUS = 0x10002,
ORBIS_AUDIO3D_PORT_ATTRIBUTE_DOWNMIX_SPREAD_HEIGHT_AWARE = 0x10003,
};
struct OrbisAudio3dAttribute {
OrbisAudio3dAttributeId attribute_id;
int : 32;
@ -96,9 +103,50 @@ struct AudioData {
OrbisAudio3dFormat format{OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16};
};
struct OrbisAudio3dPosition {
float x;
float y;
float z;
};
enum class OrbisAudio3dPassthrough : u32 {
ORBIS_AUDIO3D_PASSTHROUGH_NONE = 0,
ORBIS_AUDIO3D_PASSTHROUGH_LEFT = 1,
ORBIS_AUDIO3D_PASSTHROUGH_RIGHT = 2,
};
struct SpatialSource {
u32 source{0};
std::vector<u32> buffers; // ALuint ring, sized queue_depth + slack
std::vector<u32> available; // reclaimed / never-queued buffer ids
};
struct ObjectState {
std::deque<AudioData> pcm_queue;
std::unordered_map<u32, std::vector<u8>> persistent_attributes;
SpatialSource al;
};
struct SpatialObjectFrame {
OrbisAudio3dObjectId object_id{};
AudioData pcm{};
float gain{0.0f};
OrbisAudio3dPosition position{0.0f, 0.0f, 0.0f};
bool has_position{false};
float spread{0.0f};
OrbisAudio3dPassthrough passthrough{OrbisAudio3dPassthrough::ORBIS_AUDIO3D_PASSTHROUGH_NONE};
};
struct SpatialFrameBundle {
AudioData bed{};
std::vector<SpatialObjectFrame> objects;
};
struct AssociatedAudioOutPort {
s32 handle{-1};
u32 buffer_bytes{0};
u32 samples_per_buffer{0};
std::deque<std::vector<u8>> pending;
};
struct Port {
@ -106,16 +154,37 @@ struct Port {
OrbisAudio3dOpenParameters parameters{};
// Opened lazily on the first sceAudio3dPortPush call.
s32 audio_out_handle{-1};
// Handles explicitly opened by the game via sceAudio3dAudioOutOpen.
std::vector<s32> audioout_handles;
// AudioOut ports explicitly opened by the game via sceAudio3dAudioOutOpen.
std::vector<AssociatedAudioOutPort> audioout_ports;
// Reserved objects and their state.
std::unordered_map<OrbisAudio3dObjectId, ObjectState> objects;
// Increasing counter for generating unique object IDs within this port.
OrbisAudio3dObjectId next_object_id{0};
// Bed audio queue.
std::deque<AudioData> bed_queue;
// Mixed stereo frames ready to be consumed by sceAudio3dPortPush.
// Mixed stereo frames ready to be consumed by sceAudio3dPortPush
// (CPU-mix fallback path only).
std::deque<AudioData> mixed_queue;
// Per-tick frame bundles produced by PortAdvance in the spatial path.
std::deque<SpatialFrameBundle> spatial_queue;
// Spatial (direct OpenAL) output state.
bool spatial_init_attempted{false};
bool spatial_ready{false};
bool source_radius_supported{false};
bool direct_channels_supported{false};
std::string device_name;
u64 period_us{0};
u64 last_volume_check_us{0};
float current_gain{-1.0f};
SpatialSource bed;
std::vector<s16> spatial_scratch;
std::vector<s16> spatial_scratch_stereo;
// EFX late reverb
bool reverb_supported{false};
u32 reverb_slot{0}; // ALuint
u32 reverb_effect{0}; // ALuint
float late_reverb_level{0.0f};
};
struct Audio3dState {
@ -175,8 +244,8 @@ s32 PS4_SYSV_ABI sceAudio3dPortOpen(Libraries::UserService::OrbisUserServiceUser
s32 PS4_SYSV_ABI sceAudio3dPortPush(OrbisAudio3dPortId port_id, OrbisAudio3dBlocking blocking);
s32 PS4_SYSV_ABI sceAudio3dPortQueryDebug();
s32 PS4_SYSV_ABI sceAudio3dPortSetAttribute(OrbisAudio3dPortId port_id,
OrbisAudio3dAttributeId attribute_id, void* attribute,
u64 attribute_size);
OrbisAudio3dPortAttributeId attribute_id,
void* attribute, u64 attribute_size);
s32 PS4_SYSV_ABI sceAudio3dReportRegisterHandler();
s32 PS4_SYSV_ABI sceAudio3dReportUnregisterHandler();
s32 PS4_SYSV_ABI sceAudio3dSetGpuRenderer();