mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 08:33:12 -05:00
301 lines
13 KiB
C++
301 lines
13 KiB
C++
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <cstddef>
|
|
#include <cstring>
|
|
#include <deque>
|
|
#include <iterator>
|
|
#include <mutex>
|
|
#include <set>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <vector>
|
|
#include "Mixer.hpp"
|
|
#include "SceTypes.hpp"
|
|
#include "prx/libc/include/General.hpp"
|
|
|
|
extern "C" {
|
|
int APS5_VABI sceAudioOutOpen(int user_id, int type, int index, std::uint32_t len, std::uint32_t freq, std::uint32_t param);
|
|
int APS5_VABI sceAudioOutOutput(int handle, const void* ptr);
|
|
int APS5_VABI sceAudioOutClose(int handle);
|
|
}
|
|
|
|
namespace {
|
|
|
|
constexpr int AUDIO3D_ERROR_INVALID_PORT = static_cast<int>(0x80EA0002);
|
|
constexpr int AUDIO3D_ERROR_INVALID_OBJECT = static_cast<int>(0x80EA0003);
|
|
constexpr int AUDIO3D_ERROR_INVALID_PARAMETER = static_cast<int>(0x80EA0004);
|
|
constexpr int AUDIO3D_ERROR_NOT_SUPPORTED = static_cast<int>(0x80EA0005);
|
|
constexpr int AUDIO3D_ERROR_OUT_OF_RESOURCES = static_cast<int>(0x80EA0006);
|
|
constexpr int AUDIO3D_ERROR_NOT_READY = static_cast<int>(0x80EA0007);
|
|
|
|
constexpr int AUDIO3D_USER_ID_SYSTEM = 0xFF;
|
|
constexpr std::uint32_t AUDIO3D_PORT_ID = 0;
|
|
constexpr std::uint32_t AUDIO3D_OBJECT_INVALID = 0xFFFFFFFF;
|
|
constexpr std::uint32_t AUDIO3D_RATE_48000 = 0;
|
|
constexpr std::uint32_t AUDIO3D_SAMPLE_RATE = 48000;
|
|
constexpr std::uint32_t AUDIO3D_BUFFER_ADVANCE_AND_PUSH = 2;
|
|
constexpr int AUDIO3D_AUDIO_OUT_PORT_TYPE = 126;
|
|
constexpr std::uint32_t AUDIO3D_AUDIO_OUT_FORMAT_STEREO = 1;
|
|
constexpr std::uint32_t AUDIO3D_BLOCKING_ASYNC = 0;
|
|
constexpr std::uint32_t AUDIO3D_BLOCKING_SYNC = 1;
|
|
constexpr std::uint32_t AUDIO3D_ATTRIBUTE_RESET_STATE = 0x20000;
|
|
constexpr std::uint32_t AUDIO3D_ATTRIBUTE_LATE_REVERB_LEVEL = 0x10001;
|
|
constexpr std::uint32_t AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_RADIUS = 0x10002;
|
|
constexpr std::uint32_t AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_HEIGHT_AWARE = 0x10003;
|
|
constexpr std::uint32_t AUDIO3D_FORMAT_S16 = 0;
|
|
constexpr std::uint32_t AUDIO3D_FORMAT_FLOAT = 1;
|
|
constexpr std::uint32_t AUDIO3D_OUTPUT_ROUTE_BOTH = 0;
|
|
constexpr std::uint32_t AUDIO3D_OUTPUT_ROUTE_HMU_ONLY = 1;
|
|
constexpr std::uint32_t AUDIO3D_OUTPUT_ROUTE_TV_ONLY = 2;
|
|
|
|
struct Audio3dPort {
|
|
bool open = false;
|
|
std::uint32_t queue_depth = 0;
|
|
std::uint32_t granularity = 0;
|
|
std::uint32_t buffer_mode = 0;
|
|
std::uint32_t max_objects = 0;
|
|
std::uint32_t next_object_id = 0;
|
|
std::set<std::uint32_t> objects;
|
|
std::vector<std::uint8_t> bed;
|
|
std::uint32_t bed_channels = 0;
|
|
std::uint32_t bed_format = 0;
|
|
std::uint32_t bed_samples = 0;
|
|
std::deque<std::vector<std::int16_t>> mixed;
|
|
int audio_out = 0;
|
|
};
|
|
|
|
std::mutex g_mutex;
|
|
bool g_initialized = false;
|
|
Audio3dPort g_port;
|
|
|
|
[[noreturn]] void Unsupported(const char* function, const std::string& what) {
|
|
throw std::runtime_error(std::string(function) + ": " + what);
|
|
}
|
|
|
|
bool PortIsOpen(std::uint32_t port_id) {
|
|
return port_id == AUDIO3D_PORT_ID && g_port.open;
|
|
}
|
|
|
|
std::uint32_t QueueLevel() {
|
|
return static_cast<std::uint32_t>(g_port.mixed.size());
|
|
}
|
|
|
|
}
|
|
|
|
extern "C" {
|
|
|
|
void APS5_VABI sceAudio3dGetDefaultOpenParameters(Audio3dOpenParameters* p) {
|
|
if (p == nullptr) APS5_INVALID_ARG_EX;
|
|
constexpr Audio3dOpenParameters defaults{0x20, 256, 0, 512, 2, 2, 0, 0};
|
|
static_assert(offsetof(Audio3dOpenParameters, num_beds) == 0x20);
|
|
std::memcpy(p, &defaults, defaults.size_this);
|
|
}
|
|
|
|
int APS5_VABI sceAudio3dInitialize(int64_t reserved) {
|
|
if (reserved != 0) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
std::lock_guard lock(g_mutex);
|
|
if (g_initialized) Unsupported(__func__, "repeated initialization");
|
|
g_initialized = true;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceAudio3dTerminate() {
|
|
std::lock_guard lock(g_mutex);
|
|
if (!g_initialized || g_port.open) return AUDIO3D_ERROR_NOT_READY;
|
|
g_initialized = false;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceAudio3dPortAdvance(uint32_t port_id) {
|
|
std::lock_guard lock(g_mutex);
|
|
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
|
if (g_port.buffer_mode != AUDIO3D_BUFFER_ADVANCE_AND_PUSH) return AUDIO3D_ERROR_NOT_SUPPORTED;
|
|
if (QueueLevel() >= g_port.queue_depth) return AUDIO3D_ERROR_NOT_READY;
|
|
const Audio3dInternal::Source source{
|
|
g_port.bed.data(),
|
|
g_port.bed_samples,
|
|
g_port.bed_channels,
|
|
g_port.bed_format == AUDIO3D_FORMAT_FLOAT,
|
|
1.0f,
|
|
0.0f,
|
|
};
|
|
std::vector<std::int16_t> frame;
|
|
Audio3dInternal::MixFrame(&source, g_port.bed.empty() ? 0 : 1, g_port.granularity, frame);
|
|
g_port.mixed.push_back(std::move(frame));
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceAudio3dPortClose(uint32_t port_id) {
|
|
std::lock_guard lock(g_mutex);
|
|
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
|
if (g_port.audio_out != 0) sceAudioOutClose(g_port.audio_out);
|
|
g_port = Audio3dPort{};
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceAudio3dPortGetQueueLevel(uint32_t port_id, uint32_t* queue_level, uint32_t* queue_available) {
|
|
std::lock_guard lock(g_mutex);
|
|
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
|
if (queue_level == nullptr && queue_available == nullptr) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
const std::uint32_t level = QueueLevel();
|
|
if (queue_level != nullptr) *queue_level = level;
|
|
if (queue_available != nullptr) *queue_available = g_port.queue_depth - level;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceAudio3dPortGetAttributesSupported(uint32_t port_id, uint32_t* attributes, uint32_t* num_attributes) {
|
|
std::lock_guard lock(g_mutex);
|
|
if (num_attributes == nullptr) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
|
constexpr std::uint32_t supported[]{
|
|
AUDIO3D_ATTRIBUTE_LATE_REVERB_LEVEL,
|
|
AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_RADIUS,
|
|
AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_HEIGHT_AWARE,
|
|
};
|
|
if (attributes == nullptr) {
|
|
*num_attributes = std::size(supported);
|
|
return 0;
|
|
}
|
|
const std::uint32_t count = std::min<std::uint32_t>(*num_attributes, std::size(supported));
|
|
std::copy_n(std::begin(supported), count, attributes);
|
|
*num_attributes = count;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceAudio3dPortOpen(int user_id, const Audio3dOpenParameters* parameters, uint32_t* id) {
|
|
std::lock_guard lock(g_mutex);
|
|
if (!g_initialized) return AUDIO3D_ERROR_NOT_READY;
|
|
if (user_id != AUDIO3D_USER_ID_SYSTEM || parameters == nullptr || id == nullptr) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
std::uint32_t num_beds = 2;
|
|
switch (parameters->size_this) {
|
|
case 0x10:
|
|
case 0x18:
|
|
Unsupported(__func__, "parameter size " + std::to_string(parameters->size_this) + " selects an unsupported buffer mode");
|
|
case 0x20:
|
|
break;
|
|
case 0x28:
|
|
num_beds = parameters->num_beds;
|
|
break;
|
|
default:
|
|
return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
}
|
|
if (parameters->rate != AUDIO3D_RATE_48000) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
if (parameters->granularity < 0x100 || (parameters->granularity & 0xFF) != 0) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
if (parameters->max_objects == 0 || parameters->queue_depth == 0) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
if (parameters->buffer_mode > AUDIO3D_BUFFER_ADVANCE_AND_PUSH) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
if (num_beds != 2 && num_beds != 3) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
if (parameters->buffer_mode != AUDIO3D_BUFFER_ADVANCE_AND_PUSH) {
|
|
Unsupported(__func__, "buffer mode " + std::to_string(parameters->buffer_mode));
|
|
}
|
|
if (num_beds != 2) Unsupported(__func__, "3 beds");
|
|
if (g_port.open) return AUDIO3D_ERROR_OUT_OF_RESOURCES;
|
|
g_port = Audio3dPort{};
|
|
g_port.open = true;
|
|
g_port.queue_depth = parameters->queue_depth;
|
|
g_port.granularity = parameters->granularity;
|
|
g_port.buffer_mode = parameters->buffer_mode;
|
|
g_port.max_objects = parameters->max_objects;
|
|
*id = AUDIO3D_PORT_ID;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceAudio3dPortPush(uint32_t port_id, uint32_t blocking) {
|
|
std::lock_guard lock(g_mutex);
|
|
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
|
if (blocking != AUDIO3D_BLOCKING_ASYNC && blocking != AUDIO3D_BLOCKING_SYNC) {
|
|
Unsupported(__func__, "blocking mode " + std::to_string(blocking));
|
|
}
|
|
if (g_port.audio_out == 0) {
|
|
const int handle = sceAudioOutOpen(AUDIO3D_USER_ID_SYSTEM, AUDIO3D_AUDIO_OUT_PORT_TYPE, 0,
|
|
g_port.granularity, AUDIO3D_SAMPLE_RATE, AUDIO3D_AUDIO_OUT_FORMAT_STEREO);
|
|
if (handle < 0) return handle;
|
|
g_port.audio_out = handle;
|
|
}
|
|
while (QueueLevel() >= g_port.queue_depth) {
|
|
sceAudioOutOutput(g_port.audio_out, g_port.mixed.front().data());
|
|
g_port.mixed.pop_front();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceAudio3dPortSetAttribute(uint32_t port_id, uint32_t attribute_id, const void* attribute, size_t attribute_size) {
|
|
std::lock_guard lock(g_mutex);
|
|
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
|
if (attribute == nullptr) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
if (attribute_size != 4) Unsupported(__func__, "attribute size " + std::to_string(attribute_size));
|
|
switch (attribute_id) {
|
|
case AUDIO3D_ATTRIBUTE_LATE_REVERB_LEVEL:
|
|
case AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_RADIUS:
|
|
case AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_HEIGHT_AWARE:
|
|
return 0;
|
|
default:
|
|
Unsupported(__func__, "attribute " + std::to_string(attribute_id));
|
|
}
|
|
}
|
|
|
|
int APS5_VABI sceAudio3dObjectReserve(uint32_t port_id, uint32_t* object_id) {
|
|
if (object_id == nullptr) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
*object_id = AUDIO3D_OBJECT_INVALID;
|
|
std::lock_guard lock(g_mutex);
|
|
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
|
if (g_port.objects.size() >= g_port.max_objects) return AUDIO3D_ERROR_OUT_OF_RESOURCES;
|
|
do {
|
|
++g_port.next_object_id;
|
|
} while (g_port.next_object_id == 0 || g_port.next_object_id == AUDIO3D_OBJECT_INVALID || g_port.objects.contains(g_port.next_object_id));
|
|
g_port.objects.insert(g_port.next_object_id);
|
|
*object_id = g_port.next_object_id;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceAudio3dObjectUnreserve(uint32_t port_id, uint32_t object_id) {
|
|
std::lock_guard lock(g_mutex);
|
|
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
|
if (g_port.objects.erase(object_id) == 0) return AUDIO3D_ERROR_INVALID_OBJECT;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceAudio3dObjectSetAttributes(uint32_t port_id, uint32_t object_id, uint64_t num_attributes, const Audio3dAttribute* attribute_array) {
|
|
std::lock_guard lock(g_mutex);
|
|
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
|
if (num_attributes == 0 || attribute_array == nullptr) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
if (!g_port.objects.contains(object_id)) return AUDIO3D_ERROR_INVALID_OBJECT;
|
|
for (std::uint64_t i = 0; i < num_attributes; ++i) {
|
|
if (attribute_array[i].attribute_id == AUDIO3D_ATTRIBUTE_RESET_STATE) continue;
|
|
if (attribute_array[i].value == nullptr) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceAudio3dPortFlush(uint32_t port_id) {
|
|
std::lock_guard lock(g_mutex);
|
|
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
|
g_port.mixed.clear();
|
|
if (g_port.audio_out != 0) sceAudioOutOutput(g_port.audio_out, nullptr);
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceAudio3dBedWrite2(uint32_t port_id, uint32_t num_channels, uint32_t format, const void* buffer, uint32_t num_samples, uint32_t output_route, bool) {
|
|
std::lock_guard lock(g_mutex);
|
|
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
|
if (num_channels != 2 && num_channels != 6 && num_channels != 8) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
if (format > AUDIO3D_FORMAT_FLOAT) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
if (output_route != AUDIO3D_OUTPUT_ROUTE_BOTH && output_route != AUDIO3D_OUTPUT_ROUTE_HMU_ONLY && output_route != AUDIO3D_OUTPUT_ROUTE_TV_ONLY) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
if (buffer == nullptr || num_samples == 0) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
const std::size_t sample_size = format == AUDIO3D_FORMAT_S16 ? 2u : 4u;
|
|
if (reinterpret_cast<std::uintptr_t>(buffer) % sample_size != 0) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
const std::uint32_t samples = std::min(num_samples, g_port.granularity);
|
|
const std::size_t bytes = static_cast<std::size_t>(samples) * num_channels * sample_size;
|
|
const auto* first = static_cast<const std::uint8_t*>(buffer);
|
|
g_port.bed.assign(first, first + bytes);
|
|
g_port.bed_channels = num_channels;
|
|
g_port.bed_format = format;
|
|
g_port.bed_samples = samples;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceAudio3dBedWrite(uint32_t port_id, uint32_t num_channels, uint32_t format, const void* buffer, uint32_t num_samples) {
|
|
return sceAudio3dBedWrite2(port_id, num_channels, format, buffer, num_samples, AUDIO3D_OUTPUT_ROUTE_BOTH, false);
|
|
}
|
|
|
|
}
|