some openal work in audio3d

This commit is contained in:
georgemoralis 2026-01-27 22:31:33 +02:00
parent f503e5a287
commit fe31cd5b19
2 changed files with 557 additions and 127 deletions

View File

@ -1,9 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <SDL3/SDL_audio.h>
#include <AL/al.h>
#include <AL/alc.h>
#include <magic_enum/magic_enum.hpp>
#include <iostream>
#include <alext.h>
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/libraries/audio/audioout.h"
@ -17,7 +20,6 @@
namespace Libraries::Audio3d {
static constexpr u32 AUDIO3D_SAMPLE_RATE = 48000;
static constexpr AudioOut::OrbisAudioOutParamFormat AUDIO3D_OUTPUT_FORMAT =
AudioOut::OrbisAudioOutParamFormat::S16Stereo;
static constexpr u32 AUDIO3D_OUTPUT_NUM_CHANNELS = 2;
@ -30,6 +32,280 @@ static bool Audio3dHRTFActive() {
return al.IsInitialized() && al.IsHRTFEnabled();
}
// Convert PS4 3D coordinate system to OpenAL
static void ConvertPositionToOpenAL(const float* ps4_pos, float* al_pos) {
al_pos[0] = ps4_pos[0]; // X same
al_pos[1] = ps4_pos[1]; // Y same
al_pos[2] = -ps4_pos[2]; // Z inverted
}
// Helper function to downmix 8-channel audio to stereo S16
static std::vector<std::byte> Downmix8ChannelToStereoS16(const OrbisAudio3dPcm& pcm) {
std::vector<std::byte> converted_data;
const bool is_float = (pcm.format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_FLOAT);
const u32 input_channels = 8;
// Output will be stereo S16
const size_t output_size = pcm.num_samples * 2 * sizeof(int16_t);
converted_data.resize(output_size);
// Initialize with zeros
std::memset(converted_data.data(), 0, output_size);
if (!pcm.sample_buffer || pcm.num_samples == 0) {
LOG_WARNING(Lib_Audio3d, "Empty audio buffer for downmixing");
return converted_data;
}
int16_t* output = reinterpret_cast<int16_t*>(converted_data.data());
if (is_float) {
const float* input = static_cast<const float*>(pcm.sample_buffer);
for (u32 i = 0; i < pcm.num_samples; i++) {
size_t base_idx = i * input_channels;
float fl = input[base_idx + 0];
float fr = input[base_idx + 1];
float fc = input[base_idx + 2];
float lfe = input[base_idx + 3];
float bl = input[base_idx + 4];
float br = input[base_idx + 5];
float sl = input[base_idx + 6];
float sr = input[base_idx + 7];
// 7.1 to stereo downmix with volume reduction
float left = fl * 0.7f + fc * 0.5f + sl * 0.5f + bl * 0.3f + lfe * 0.1f;
float right = fr * 0.7f + fc * 0.5f + sr * 0.5f + br * 0.3f + lfe * 0.1f;
// Clamp and convert to int16
left = std::clamp(left, -1.0f, 1.0f);
right = std::clamp(right, -1.0f, 1.0f);
output[i * 2 + 0] = static_cast<int16_t>(left * 32767.0f);
output[i * 2 + 1] = static_cast<int16_t>(right * 32767.0f);
}
} else {
const int16_t* input = static_cast<const int16_t*>(pcm.sample_buffer);
for (u32 i = 0; i < pcm.num_samples; i++) {
size_t base_idx = i * input_channels;
// Convert to float for processing
float fl = input[base_idx + 0] / 32768.0f;
float fr = input[base_idx + 1] / 32768.0f;
float fc = input[base_idx + 2] / 32768.0f;
float lfe = input[base_idx + 3] / 32768.0f;
float bl = input[base_idx + 4] / 32768.0f;
float br = input[base_idx + 5] / 32768.0f;
float sl = input[base_idx + 6] / 32768.0f;
float sr = input[base_idx + 7] / 32768.0f;
// Downmix with volume reduction
float left = fl * 0.7f + fc * 0.5f + sl * 0.5f + bl * 0.3f + lfe * 0.1f;
float right = fr * 0.7f + fc * 0.5f + sr * 0.5f + br * 0.3f + lfe * 0.1f;
// Clamp and convert back to int16
left = std::clamp(left, -1.0f, 1.0f);
right = std::clamp(right, -1.0f, 1.0f);
output[i * 2 + 0] = static_cast<int16_t>(left * 32767.0f);
output[i * 2 + 1] = static_cast<int16_t>(right * 32767.0f);
}
}
LOG_DEBUG(Lib_Audio3d, "Downmixed 8-channel to stereo S16: {} samples", pcm.num_samples);
return converted_data;
}
// Convert any input to stereo S16 format
static std::vector<std::byte> ConvertToStereoS16(const OrbisAudio3dPcm& pcm, u32 num_channels) {
std::vector<std::byte> converted_data;
if (!pcm.sample_buffer || pcm.num_samples == 0) {
LOG_ERROR(Lib_Audio3d, "Empty or invalid PCM data");
return converted_data;
}
// We only support S16 format
if (pcm.format != OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16) {
LOG_ERROR(Lib_Audio3d, "Only S16 format supported, got: {}",
magic_enum::enum_name(pcm.format));
return converted_data;
}
if (num_channels == 2) {
// Direct copy for stereo S16
const size_t total_size = pcm.num_samples * 2 * sizeof(int16_t);
converted_data.resize(total_size);
std::memcpy(converted_data.data(), pcm.sample_buffer, total_size);
} else if (num_channels == 8) {
// Downmix 8-channel to stereo S16
converted_data = Downmix8ChannelToStereoS16(pcm);
} else {
LOG_ERROR(Lib_Audio3d, "Unsupported channel count: {} (only 2 or 8 supported)",
num_channels);
return converted_data;
}
LOG_DEBUG(Lib_Audio3d, "Converted to stereo S16: {} bytes, {} samples", converted_data.size(),
pcm.num_samples);
return converted_data;
}
// Queue audio data (always converted to stereo S16)
static s32 PortQueueAudio(Port& port, const OrbisAudio3dPcm& pcm, const u32 num_channels) {
LOG_DEBUG(Lib_Audio3d, "PortQueueAudio: channels={}, samples={}, format={}", num_channels,
pcm.num_samples, magic_enum::enum_name(pcm.format));
if (!pcm.sample_buffer || pcm.num_samples == 0) {
LOG_ERROR(Lib_Audio3d, "Invalid PCM data for queue");
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
}
// Convert to stereo S16
auto converted_data = ConvertToStereoS16(pcm, num_channels);
if (converted_data.empty()) {
LOG_ERROR(Lib_Audio3d, "Failed to convert audio to stereo S16");
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
}
// Verify size matches expected stereo S16
const size_t expected_size = pcm.num_samples * 2 * sizeof(int16_t);
if (converted_data.size() != expected_size) {
LOG_ERROR(Lib_Audio3d, "Converted size mismatch: got {} bytes, expected {} bytes",
converted_data.size(), expected_size);
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
}
AudioData audio_data{
.sample_buffer = std::move(converted_data),
.num_samples = pcm.num_samples,
};
std::lock_guard lock(port.lock);
port.queue.emplace_back(std::move(audio_data));
LOG_DEBUG(Lib_Audio3d, "Queued stereo S16 audio: {} bytes, {} samples",
audio_data.sample_buffer.size(), audio_data.num_samples);
return ORBIS_OK;
}
// Clean up processed OpenAL buffers
static void CleanupProcessedBuffers(Audio3dObject& obj) {
if (obj.al_source.source_id == 0)
return;
Libraries::AudioOut::OpenALManager::Instance().MakeContextCurrent();
ALint processed = 0;
alGetSourcei(obj.al_source.source_id, AL_BUFFERS_PROCESSED, &processed);
if (processed > 0) {
std::vector<ALuint> buffers(processed);
alSourceUnqueueBuffers(obj.al_source.source_id, processed, buffers.data());
alDeleteBuffers(processed, buffers.data());
}
}
// Stream object audio (always stereo S16)
static void StreamObjectAudio(Audio3dObject& obj) {
if (!obj.in_use || !obj.active)
return;
// Clean up processed buffers first
CleanupProcessedBuffers(obj);
// Ensure OpenAL source exists
if (obj.al_source.source_id == 0) {
alGenSources(1, &obj.al_source.source_id);
if (obj.al_source.source_id == 0) {
LOG_ERROR(Lib_Audio3d, "Failed to generate OpenAL source");
return;
}
// Set default OpenAL source properties
Libraries::AudioOut::OpenALManager::Instance().MakeContextCurrent();
alSourcef(obj.al_source.source_id, AL_PITCH, obj.al_source.pitch);
alSourcef(obj.al_source.source_id, AL_GAIN, obj.al_source.gain);
alSource3f(obj.al_source.source_id, AL_POSITION, obj.al_source.position[0],
obj.al_source.position[1], obj.al_source.position[2]);
alSource3f(obj.al_source.source_id, AL_VELOCITY, obj.al_source.velocity[0],
obj.al_source.velocity[1], obj.al_source.velocity[2]);
alSourcef(obj.al_source.source_id, AL_REFERENCE_DISTANCE, obj.al_source.reference_distance);
alSourcef(obj.al_source.source_id, AL_MAX_DISTANCE, obj.al_source.max_distance);
alSourcef(obj.al_source.source_id, AL_ROLLOFF_FACTOR, obj.al_source.rolloff_factor);
alSourcei(obj.al_source.source_id, AL_LOOPING, obj.al_source.looping ? AL_TRUE : AL_FALSE);
alSourcei(obj.al_source.source_id, AL_SOURCE_RELATIVE, AL_FALSE);
}
// Check if there is audio to stream
if (obj.pcm_queue.empty())
return;
auto& data = obj.pcm_queue.front();
if (data.sample_buffer.empty()) {
LOG_WARNING(Lib_Audio3d, "Empty audio buffer in queue");
obj.pcm_queue.pop_front();
return;
}
// Generate OpenAL buffer
ALuint buffer = 0;
alGenBuffers(1, &buffer);
if (buffer == 0) {
LOG_ERROR(Lib_Audio3d, "Failed to generate OpenAL buffer");
obj.pcm_queue.pop_front();
return;
}
// Always use stereo S16 format (AL_FORMAT_STEREO16 = 0x1103)
ALenum al_format = AL_FORMAT_STEREO16;
// Upload data to buffer
alBufferData(buffer, al_format, data.sample_buffer.data(),
static_cast<ALsizei>(data.sample_buffer.size()), AUDIO3D_SAMPLE_RATE);
ALenum error = alGetError();
if (error != AL_NO_ERROR) {
LOG_ERROR(Lib_Audio3d, "Failed to upload buffer data: {}", error);
alDeleteBuffers(1, &buffer);
obj.pcm_queue.pop_front();
return;
}
// Queue buffer to source
alSourceQueueBuffers(obj.al_source.source_id, 1, &buffer);
error = alGetError();
if (error != AL_NO_ERROR) {
LOG_ERROR(Lib_Audio3d, "Failed to queue buffer: {}", error);
alDeleteBuffers(1, &buffer);
obj.pcm_queue.pop_front();
return;
}
// Check if source is playing
ALint state;
alGetSourcei(obj.al_source.source_id, AL_SOURCE_STATE, &state);
if (state != AL_PLAYING) {
alSourcePlay(obj.al_source.source_id);
error = alGetError();
if (error != AL_NO_ERROR) {
LOG_ERROR(Lib_Audio3d, "Failed to play source: {}", error);
}
}
// Remove from queue
obj.pcm_queue.pop_front();
}
s32 PS4_SYSV_ABI sceAudio3dAudioOutOutput(const s32 handle, void* ptr) {
LOG_DEBUG(Lib_Audio3d, "called, handle = {}, ptr = {}", handle, ptr);
@ -58,38 +334,6 @@ s32 PS4_SYSV_ABI sceAudio3dAudioOutOutputs(AudioOut::OrbisAudioOutOutputParam* p
return AudioOut::sceAudioOutOutputs(param, num);
}
static s32 PortQueueAudio(Port& port, const OrbisAudio3dPcm& pcm, const u32 num_channels) {
// Audio3d output is configured for stereo signed 16-bit PCM. Convert the data to match.
const SDL_AudioSpec src_spec = {
.format = pcm.format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16 ? SDL_AUDIO_S16LE
: SDL_AUDIO_F32LE,
.channels = static_cast<int>(num_channels),
.freq = AUDIO3D_SAMPLE_RATE,
};
constexpr SDL_AudioSpec dst_spec = {
.format = SDL_AUDIO_S16LE,
.channels = AUDIO3D_OUTPUT_NUM_CHANNELS,
.freq = AUDIO3D_SAMPLE_RATE,
};
const auto src_size = pcm.num_samples *
(pcm.format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16 ? 2 : 4) *
num_channels;
u8* dst_data;
int dst_len;
if (!SDL_ConvertAudioSamples(&src_spec, static_cast<u8*>(pcm.sample_buffer),
static_cast<int>(src_size), &dst_spec, &dst_data, &dst_len)) {
LOG_ERROR(Lib_Audio3d, "SDL_ConvertAudioSamples failed: {}", SDL_GetError());
return ORBIS_AUDIO3D_ERROR_OUT_OF_MEMORY;
}
port.queue.emplace_back(AudioData{
.sample_buffer = dst_data,
.num_samples = pcm.num_samples,
});
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAudio3dBedWrite(const OrbisAudio3dPortId port_id, const u32 num_channels,
const OrbisAudio3dFormat format, void* buffer,
const u32 num_samples) {
@ -114,18 +358,14 @@ s32 PS4_SYSV_ABI sceAudio3dBedWrite2(const OrbisAudio3dPortId port_id, const u32
return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
}
if (output_route > OrbisAudio3dOutputRoute::ORBIS_AUDIO3D_OUTPUT_BOTH) {
LOG_ERROR(Lib_Audio3d, "output_route > ORBIS_AUDIO3D_OUTPUT_BOTH");
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
}
if (format > OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_FLOAT) {
LOG_ERROR(Lib_Audio3d, "format > ORBIS_AUDIO3D_FORMAT_FLOAT");
// We only support stereo (2 channels) or 8-channel (for downmixing) S16 format
if (format != OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16) {
LOG_ERROR(Lib_Audio3d, "Only S16 format supported, got: {}", magic_enum::enum_name(format));
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
}
if (num_channels != 2 && num_channels != 8) {
LOG_ERROR(Lib_Audio3d, "num_channels != 2 && num_channels != 8");
LOG_ERROR(Lib_Audio3d, "Only 2 or 8 channels supported, got: {}", num_channels);
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
}
@ -134,16 +374,10 @@ s32 PS4_SYSV_ABI sceAudio3dBedWrite2(const OrbisAudio3dPortId port_id, const u32
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
}
if (format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_FLOAT) {
if ((reinterpret_cast<uintptr_t>(buffer) & 3) != 0) {
LOG_ERROR(Lib_Audio3d, "buffer & 3 != 0");
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
}
} else if (format == OrbisAudio3dFormat::ORBIS_AUDIO3D_FORMAT_S16) {
if ((reinterpret_cast<uintptr_t>(buffer) & 1) != 0) {
LOG_ERROR(Lib_Audio3d, "buffer & 1 != 0");
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
}
// Verify buffer alignment for S16 (2-byte alignment)
if ((reinterpret_cast<uintptr_t>(buffer) & 1) != 0) {
LOG_ERROR(Lib_Audio3d, "s16 buffer & 1 != 0 (not 2-byte aligned)");
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
}
return PortQueueAudio(state->ports[port_id],
@ -175,16 +409,64 @@ s32 PS4_SYSV_ABI sceAudio3dObjectSetAttributes(const OrbisAudio3dPortId port_id,
switch (attribute.attribute_id) {
case OrbisAudio3dAttributeId::ORBIS_AUDIO3D_OBJECT_ATTRIBUTE_PCM: {
const auto pcm = static_cast<OrbisAudio3dPcm*>(attribute.value);
// Object audio has 1 channel.
if (const auto ret = PortQueueAudio(port, *pcm, 1); ret != ORBIS_OK) {
return ret;
if (object_id >= port.objects.size())
return ORBIS_AUDIO3D_ERROR_INVALID_OBJECT;
auto& obj = port.objects[object_id];
const auto* pcm = static_cast<OrbisAudio3dPcm*>(attribute.value);
// Convert to stereo S16 (objects are mono, but we'll handle as stereo)
auto converted_data = ConvertToStereoS16(*pcm, 1);
if (converted_data.empty()) {
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
}
AudioData audio_data{
.sample_buffer = std::move(converted_data),
.num_samples = pcm->num_samples,
};
obj.pcm_queue.push_back(std::move(audio_data));
break;
}
case OrbisAudio3dAttributeId::ORBIS_AUDIO3D_OBJECT_ATTRIBUTE_POSITION: {
if (object_id >= port.objects.size()) {
return ORBIS_AUDIO3D_ERROR_INVALID_OBJECT;
}
auto& obj = port.objects[object_id];
if (attribute.value_size >= sizeof(float) * 3) {
const float* position = static_cast<const float*>(attribute.value);
ConvertPositionToOpenAL(position, obj.al_source.position);
if (obj.al_source.source_id != 0) {
Libraries::AudioOut::OpenALManager::Instance().MakeContextCurrent();
alSource3f(obj.al_source.source_id, AL_POSITION, obj.al_source.position[0],
obj.al_source.position[1], obj.al_source.position[2]);
}
}
break;
}
case OrbisAudio3dAttributeId::ORBIS_AUDIO3D_OBJECT_ATTRIBUTE_GAIN: {
if (object_id >= port.objects.size()) {
return ORBIS_AUDIO3D_ERROR_INVALID_OBJECT;
}
auto& obj = port.objects[object_id];
if (attribute.value_size >= sizeof(float)) {
obj.al_source.gain = *static_cast<const float*>(attribute.value);
if (obj.al_source.source_id != 0) {
Libraries::AudioOut::OpenALManager::Instance().MakeContextCurrent();
alSourcef(obj.al_source.source_id, AL_GAIN, obj.al_source.gain);
}
}
break;
}
default:
LOG_ERROR(Lib_Audio3d, "Unsupported attribute ID: {:#x}",
static_cast<u32>(attribute.attribute_id));
LOG_INFO(Lib_Audio3d, "Processing attribute ID: {:#x}",
static_cast<u32>(attribute.attribute_id));
break;
}
}
@ -193,32 +475,23 @@ s32 PS4_SYSV_ABI sceAudio3dObjectSetAttributes(const OrbisAudio3dPortId port_id,
}
s32 PS4_SYSV_ABI sceAudio3dPortAdvance(const OrbisAudio3dPortId port_id) {
LOG_DEBUG(Lib_Audio3d, "called, port_id = {}", port_id);
LOG_DEBUG(Lib_Audio3d, "sceAudio3dPortAdvance(port_id={})", port_id);
if (!state->ports.contains(port_id)) {
LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)");
auto it = state->ports.find(port_id);
if (it == state->ports.end())
return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
}
if (state->ports[port_id].parameters.buffer_mode ==
OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_NO_ADVANCE) {
LOG_ERROR(Lib_Audio3d, "port doesn't have advance capability");
Port& port = it->second;
if (port.parameters.buffer_mode == OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_NO_ADVANCE) {
return ORBIS_AUDIO3D_ERROR_NOT_SUPPORTED;
}
auto& port = state->ports[port_id];
if (port.current_buffer.has_value()) {
// Free existing buffer before replacing.
SDL_free(port.current_buffer->sample_buffer);
}
if (!port.queue.empty()) {
port.current_buffer = port.queue.front();
port.current_buffer = std::move(port.queue.front());
port.queue.pop_front();
} else {
// Nothing to advance to.
LOG_DEBUG(Lib_Audio3d, "Port advance with no buffer queued");
port.current_buffer = std::nullopt;
port.current_buffer.reset();
}
return ORBIS_OK;
@ -226,28 +499,22 @@ s32 PS4_SYSV_ABI sceAudio3dPortAdvance(const OrbisAudio3dPortId port_id) {
s32 PS4_SYSV_ABI sceAudio3dPortGetQueueLevel(const OrbisAudio3dPortId port_id, u32* queue_level,
u32* queue_available) {
LOG_DEBUG(Lib_Audio3d, "called, port_id = {}, queue_level = {}, queue_available = {}", port_id,
static_cast<void*>(queue_level), static_cast<void*>(queue_available));
if (!state->ports.contains(port_id)) {
LOG_ERROR(Lib_Audio3d, "!state->ports.contains(port_id)");
auto it = state->ports.find(port_id);
if (it == state->ports.end())
return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
}
if (!queue_level && !queue_available) {
if (!queue_level && !queue_available)
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
}
auto& port = state->ports[port_id];
const size_t size = port.queue.size();
const Port& port = it->second;
const u32 depth = port.parameters.queue_depth;
const u32 level = static_cast<u32>(port.queue.size());
if (queue_level) {
*queue_level = size;
}
if (queue_level)
*queue_level = level;
if (queue_available) {
*queue_available = port.parameters.queue_depth - size;
}
if (queue_available)
*queue_available = (level >= depth) ? 0 : (depth - level);
return ORBIS_OK;
}
@ -262,7 +529,9 @@ s32 PS4_SYSV_ABI sceAudio3dPortPush(const OrbisAudio3dPortId port_id,
return ORBIS_AUDIO3D_ERROR_INVALID_PORT;
}
const auto& port = state->ports[port_id];
auto& port = state->ports[port_id];
std::lock_guard lock(port.lock);
if (port.parameters.buffer_mode !=
OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH) {
LOG_ERROR(Lib_Audio3d, "port doesn't have push capability");
@ -270,14 +539,37 @@ s32 PS4_SYSV_ABI sceAudio3dPortPush(const OrbisAudio3dPortId port_id,
}
if (!port.current_buffer.has_value()) {
// Nothing to push.
LOG_DEBUG(Lib_Audio3d, "Port push with no buffer ready");
return ORBIS_OK;
// Generate silent audio if nothing to push
LOG_DEBUG(Lib_Audio3d, "Port push with no buffer ready - generating silence");
// Create silent buffer (stereo S16, granularity samples)
const size_t silent_size = port.parameters.granularity * 2 * sizeof(int16_t);
std::vector<std::byte> silent_buffer(silent_size, std::byte{0});
return AudioOut::sceAudioOutOutput(state->audio_out_handle, silent_buffer.data());
}
// TODO: Implement asynchronous blocking mode.
const auto& [sample_buffer, num_samples] = port.current_buffer.value();
return AudioOut::sceAudioOutOutput(state->audio_out_handle, sample_buffer);
auto& audio_data = port.current_buffer.value();
// Stream object audio
for (auto& obj : port.objects) {
StreamObjectAudio(obj);
}
// Output bed audio
if (!audio_data.sample_buffer.empty()) {
LOG_DEBUG(Lib_Audio3d, "Pushing bed audio: {} bytes", audio_data.sample_buffer.size());
return AudioOut::sceAudioOutOutput(
state->audio_out_handle,
const_cast<void*>(static_cast<const void*>(audio_data.sample_buffer.data())));
} else {
// Generate silent audio
const size_t silent_size = port.parameters.granularity * 2 * sizeof(int16_t);
std::vector<std::byte> silent_buffer(silent_size, std::byte{0});
return AudioOut::sceAudioOutOutput(state->audio_out_handle, silent_buffer.data());
}
}
s32 PS4_SYSV_ABI sceAudio3dPortSetAttribute(const OrbisAudio3dPortId port_id,
@ -297,103 +589,128 @@ s32 PS4_SYSV_ABI sceAudio3dPortSetAttribute(const OrbisAudio3dPortId port_id,
return ORBIS_AUDIO3D_ERROR_INVALID_PARAMETER;
}
// TODO
auto& port = state->ports[port_id];
switch (attribute_id) {
case OrbisAudio3dAttributeId::ORBIS_AUDIO3D_PORT_ATTRIBUTE_LATE_REVERB_LEVEL:
LOG_INFO(Lib_Audio3d, "Setting late reverb level (not implemented)");
break;
case OrbisAudio3dAttributeId::ORBIS_AUDIO3D_OBJECT_ATTRIBUTE_POSITION:
// Update listener position
if (attribute_size >= sizeof(float) * 3) {
const float* position = static_cast<const float*>(attribute);
ConvertPositionToOpenAL(position, port.listener_position);
if (Libraries::AudioOut::OpenALManager::Instance().IsInitialized()) {
Libraries::AudioOut::OpenALManager::Instance().MakeContextCurrent();
alListener3f(AL_POSITION, port.listener_position[0], port.listener_position[1],
port.listener_position[2]);
}
}
break;
default:
LOG_INFO(Lib_Audio3d, "Setting port attribute {:#x} (not fully implemented)",
static_cast<u32>(attribute_id));
break;
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAudio3dCreateSpeakerArray() {
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
LOG_INFO(Lib_Audio3d, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAudio3dDeleteSpeakerArray() {
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
LOG_INFO(Lib_Audio3d, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMemorySize() {
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
LOG_INFO(Lib_Audio3d, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMixCoefficients() {
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
LOG_INFO(Lib_Audio3d, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAudio3dGetSpeakerArrayMixCoefficients2() {
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
LOG_INFO(Lib_Audio3d, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAudio3dPortClose() {
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
LOG_INFO(Lib_Audio3d, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAudio3dPortCreate() {
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
LOG_INFO(Lib_Audio3d, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAudio3dPortDestroy() {
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
LOG_INFO(Lib_Audio3d, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAudio3dPortFlush() {
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
LOG_INFO(Lib_Audio3d, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAudio3dPortFreeState() {
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
LOG_INFO(Lib_Audio3d, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAudio3dPortGetList() {
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
LOG_INFO(Lib_Audio3d, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAudio3dPortGetParameters() {
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
LOG_INFO(Lib_Audio3d, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAudio3dPortGetState() {
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
LOG_INFO(Lib_Audio3d, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAudio3dPortGetStatus() {
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
LOG_INFO(Lib_Audio3d, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAudio3dPortQueryDebug() {
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
LOG_INFO(Lib_Audio3d, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAudio3dReportRegisterHandler() {
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
LOG_INFO(Lib_Audio3d, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAudio3dReportUnregisterHandler() {
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
LOG_INFO(Lib_Audio3d, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAudio3dSetGpuRenderer() {
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
LOG_INFO(Lib_Audio3d, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceAudio3dStrError() {
LOG_ERROR(Lib_Audio3d, "(STUBBED) called");
LOG_INFO(Lib_Audio3d, "(STUBBED) called");
return ORBIS_OK;
}
@ -433,6 +750,24 @@ s32 PS4_SYSV_ABI sceAudio3dInitialize(const s64 reserved) {
return init_ret;
}
// Initialize OpenAL for 3D audio
if (!Libraries::AudioOut::OpenALManager::Instance().Initialize(AUDIO3D_SAMPLE_RATE)) {
LOG_ERROR(Lib_Audio3d, "Failed to initialize OpenAL for 3D audio");
return ORBIS_AUDIO3D_ERROR_NOT_READY;
}
// Enable HRTF if available
if (Libraries::AudioOut::OpenALManager::Instance().HasHRTF()) {
LOG_INFO(Lib_Audio3d, "HRTF is available");
}
// Set default OpenAL listener parameters
Libraries::AudioOut::OpenALManager::Instance().MakeContextCurrent();
alListener3f(AL_POSITION, 0.0f, 0.0f, 0.0f);
alListener3f(AL_VELOCITY, 0.0f, 0.0f, 0.0f);
float listener_orientation[] = {0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f};
alListenerfv(AL_ORIENTATION, listener_orientation);
AudioOut::OrbisAudioOutParamExtendedInformation ext_info{};
ext_info.data_format.Assign(AUDIO3D_OUTPUT_FORMAT);
state->audio_out_handle =
@ -451,9 +786,32 @@ s32 PS4_SYSV_ABI sceAudio3dTerminate() {
return ORBIS_AUDIO3D_ERROR_NOT_READY;
}
// Clean up OpenAL resources for each port
for (auto& [port_id, port] : state->ports) {
std::lock_guard lock(port.lock);
// Clean up OpenAL sources for objects
for (auto& obj : port.objects) {
if (obj.al_source.source_id != 0) {
Libraries::AudioOut::OpenALManager::Instance().MakeContextCurrent();
if (obj.al_source.buffer_id != 0) {
alDeleteBuffers(1, &obj.al_source.buffer_id);
}
alDeleteSources(1, &obj.al_source.source_id);
obj.al_source.source_id = 0;
obj.al_source.buffer_id = 0;
}
}
port.objects.clear();
port.queue.clear();
port.current_buffer.reset();
}
state->ports.clear();
AudioOut::sceAudioOutOutput(state->audio_out_handle, nullptr);
AudioOut::sceAudioOutClose(state->audio_out_handle);
state.release();
state.reset();
return ORBIS_OK;
}
@ -613,7 +971,7 @@ s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen(
return ORBIS_AUDIO3D_ERROR_NOT_READY;
}
return audio_out_handle; // TODO missing more stuff but let's keep it simple for now
return audio_out_handle;
}
s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(const s32 handle) {
@ -643,6 +1001,36 @@ s32 PS4_SYSV_ABI sceAudio3dObjectReserve(const OrbisAudio3dPortId port_id,
auto& obj = port.objects[i];
if (!obj.in_use) {
obj = {}; // reset
// Create OpenAL source for this object
if (Libraries::AudioOut::OpenALManager::Instance().IsInitialized()) {
Libraries::AudioOut::OpenALManager::Instance().MakeContextCurrent();
alGenSources(1, &obj.al_source.source_id);
ALenum error = alGetError();
if (error != AL_NO_ERROR) {
LOG_ERROR(Lib_Audio3d, "Failed to generate OpenAL source: {}", error);
return ORBIS_AUDIO3D_ERROR_OUT_OF_RESOURCES;
}
// Set default OpenAL source properties for stereo S16
alSourcef(obj.al_source.source_id, AL_PITCH, obj.al_source.pitch);
alSourcef(obj.al_source.source_id, AL_GAIN, obj.al_source.gain);
alSource3f(obj.al_source.source_id, AL_POSITION, obj.al_source.position[0],
obj.al_source.position[1], obj.al_source.position[2]);
alSource3f(obj.al_source.source_id, AL_VELOCITY, obj.al_source.velocity[0],
obj.al_source.velocity[1], obj.al_source.velocity[2]);
alSourcef(obj.al_source.source_id, AL_REFERENCE_DISTANCE,
obj.al_source.reference_distance);
alSourcef(obj.al_source.source_id, AL_MAX_DISTANCE, obj.al_source.max_distance);
alSourcef(obj.al_source.source_id, AL_ROLLOFF_FACTOR, obj.al_source.rolloff_factor);
alSourcei(obj.al_source.source_id, AL_LOOPING,
obj.al_source.looping ? AL_TRUE : AL_FALSE);
// Enable 3D positioning
alSourcei(obj.al_source.source_id, AL_SOURCE_RELATIVE, AL_FALSE);
}
obj.in_use = true;
obj.active = true;
*object_id = i;
@ -676,9 +1064,19 @@ s32 PS4_SYSV_ABI sceAudio3dObjectUnreserve(OrbisAudio3dPortId port_id,
if (!obj.in_use)
return ORBIS_AUDIO3D_ERROR_INVALID_OBJECT;
// Clean up OpenAL resources
if (obj.al_source.source_id != 0) {
Libraries::AudioOut::OpenALManager::Instance().MakeContextCurrent();
if (obj.al_source.buffer_id != 0) {
alDeleteBuffers(1, &obj.al_source.buffer_id);
}
alDeleteSources(1, &obj.al_source.source_id);
}
// Mark free
obj.in_use = false;
obj.active = false;
obj.al_source = {}; // Reset OpenAL source
return ORBIS_OK;
}
@ -700,7 +1098,7 @@ s32 sceAudio3dPortGetAttributesSupported(OrbisAudio3dPortId portId, OrbisAudio3d
std::vector<OrbisAudio3dAttributeId> supported;
// Always supported
// Always supported for S16 stereo
supported.push_back(OrbisAudio3dAttributeId::ORBIS_AUDIO3D_OBJECT_ATTRIBUTE_PCM);
supported.push_back(OrbisAudio3dAttributeId::ORBIS_AUDIO3D_OBJECT_ATTRIBUTE_GAIN);
supported.push_back(OrbisAudio3dAttributeId::ORBIS_AUDIO3D_OBJECT_ATTRIBUTE_PRIORITY);
@ -710,7 +1108,7 @@ s32 sceAudio3dPortGetAttributesSupported(OrbisAudio3dPortId portId, OrbisAudio3d
supported.push_back(OrbisAudio3dAttributeId::ORBIS_AUDIO3D_OBJECT_ATTRIBUTE_RESTRICTED);
if (hrtf) {
// Object-based renderer
// Object-based renderer with HRTF
supported.push_back(OrbisAudio3dAttributeId::ORBIS_AUDIO3D_OBJECT_ATTRIBUTE_POSITION);
supported.push_back(OrbisAudio3dAttributeId::ORBIS_AUDIO3D_OBJECT_ATTRIBUTE_SPREAD);
supported.push_back(OrbisAudio3dAttributeId::ORBIS_AUDIO3D_OBJECT_ATTRIBUTE_OUTPUT_ROUTE);
@ -787,4 +1185,4 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("WW1TS2iz5yc", "libSceAudio3d", 1, "libSceAudio3d", sceAudio3dTerminate);
};
} // namespace Libraries::Audio3d
} // namespace Libraries::Audio3d

View File

@ -3,9 +3,14 @@
#pragma once
#include <deque>
#include <mutex>
#include <optional>
#include <unordered_map>
#include <vector>
#include <queue>
#include <al.h>
#include "common/types.h"
#include "core/libraries/audio/audioout.h"
@ -87,22 +92,48 @@ struct OrbisAudio3dAttribute {
u64 value_size;
};
// OpenAL-specific structures for 3D audio
struct OpenAL3dSource {
ALuint source_id = 0;
ALuint buffer_id = 0;
bool active = false;
float position[3] = {0.0f, 0.0f, 0.0f};
float velocity[3] = {0.0f, 0.0f, 0.0f};
float gain = 1.0f;
float pitch = 1.0f;
float reference_distance = 1.0f;
float max_distance = 100.0f;
float rolloff_factor = 1.0f;
float cone_inner_angle = 360.0f;
float cone_outer_angle = 360.0f;
float cone_outer_gain = 0.0f;
bool looping = false;
};
// Simplified audio data - always stereo S16
struct AudioData {
u8* sample_buffer;
u32 num_samples;
std::vector<std::byte> sample_buffer; // Always stereo S16 format
u32 num_samples; // Number of stereo samples
};
struct Audio3dObject {
bool in_use = false;
bool active = false;
OpenAL3dSource al_source;
std::deque<AudioData> pcm_queue; // Stereo S16 audio data
};
struct Port {
OrbisAudio3dOpenParameters parameters{};
std::deque<AudioData> queue; // Only stores PCM buffers for now
std::deque<AudioData> queue; // Stereo S16 audio data
std::optional<AudioData> current_buffer{};
std::vector<Audio3dObject> objects;
std::mutex lock;
// OpenAL listener for this port
float listener_position[3] = {0.0f, 0.0f, 0.0f};
float listener_velocity[3] = {0.0f, 0.0f, 0.0f};
float listener_orientation[6] = {0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f};
};
struct Audio3dState {
@ -110,6 +141,7 @@ struct Audio3dState {
s32 audio_out_handle;
};
// Function declarations
s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(s32 handle);
s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen(OrbisAudio3dPortId port_id,
Libraries::UserService::OrbisUserServiceUserId user_id,
@ -165,4 +197,4 @@ s32 PS4_SYSV_ABI sceAudio3dStrError();
s32 PS4_SYSV_ABI sceAudio3dTerminate();
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Audio3d
} // namespace Libraries::Audio3d