mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-08-18 22:42:23 +00:00
953 lines
31 KiB
C++
953 lines
31 KiB
C++
#include "common/assert.h"
|
|
#include "common/common.h"
|
|
#include "common/logging/log.h"
|
|
#include "common/threads.h"
|
|
#include "kernel/pthread.h"
|
|
#include "libs/audio.h"
|
|
#include "libs/audio_internal.h"
|
|
#include "libs/errno.h"
|
|
#include "libs/libs.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <cstring>
|
|
#include <vector>
|
|
|
|
namespace Libs::Audio {
|
|
|
|
namespace {
|
|
|
|
constexpr int AUDIO_OUT_PORT_TYPE_MAIN = 0;
|
|
constexpr int AUDIO_OUT_PORT_TYPE_BGM = 1;
|
|
constexpr int AUDIO_OUT_PORT_TYPE_VOICE = 2;
|
|
constexpr int AUDIO_OUT_PORT_TYPE_PERSONAL = 3;
|
|
constexpr int AUDIO_OUT_PORT_TYPE_PADSPK = 4;
|
|
constexpr int AUDIO_OUT_PORT_TYPE_VIBRATION = 10;
|
|
constexpr int AUDIO_OUT_PORT_TYPE_AUX = 127;
|
|
|
|
} // namespace
|
|
|
|
namespace AudioOut2 {
|
|
|
|
LIB_NAME("AudioOut2", "AudioOut");
|
|
|
|
struct AudioOut2ContextParam {
|
|
uint32_t max_ports;
|
|
uint32_t max_object_ports;
|
|
uint32_t guarantee_object_ports;
|
|
uint32_t queue_depth;
|
|
uint32_t num_grains;
|
|
uint32_t flags;
|
|
uint32_t reserved[10];
|
|
};
|
|
|
|
struct AudioOut2PortParam {
|
|
uint16_t port_type;
|
|
uint16_t pad;
|
|
uint32_t data_format;
|
|
uint32_t sampling_freq;
|
|
uint32_t flags;
|
|
AudioOut2UserHandle user_handle;
|
|
uint32_t reserved[10];
|
|
};
|
|
|
|
struct AudioOut2Attribute {
|
|
uint32_t attribute_id;
|
|
int32_t reserved;
|
|
const void* value;
|
|
size_t value_size;
|
|
};
|
|
|
|
struct AudioOut2Pcm {
|
|
const void* data;
|
|
};
|
|
|
|
struct AudioOut2Position {
|
|
float x;
|
|
float y;
|
|
float z;
|
|
};
|
|
|
|
struct AudioOut2PortState {
|
|
uint16_t output;
|
|
uint8_t num_channels;
|
|
uint8_t pad1;
|
|
int16_t volume;
|
|
uint16_t reroute_counter;
|
|
uint32_t flags;
|
|
uint32_t pad2;
|
|
uint64_t reserved[6];
|
|
};
|
|
|
|
struct AudioOut2SystemState {
|
|
float loudness;
|
|
uint32_t pad;
|
|
uint64_t reserved[7];
|
|
};
|
|
|
|
struct AudioOut2SpeakerAngle {
|
|
int16_t azimuth;
|
|
int16_t elevation;
|
|
};
|
|
|
|
struct AudioOut2SpeakerInfo {
|
|
uint8_t type;
|
|
uint8_t pad1;
|
|
int16_t pad2;
|
|
uint32_t available_bits;
|
|
uint32_t flags;
|
|
uint32_t pad3;
|
|
AudioOut2SpeakerAngle speaker_angle[16];
|
|
};
|
|
|
|
struct AudioOut2SystemDebugStateParam {
|
|
uint32_t debug_state_id;
|
|
int32_t reserved;
|
|
void* param;
|
|
size_t param_size;
|
|
};
|
|
|
|
struct AudioOut2MasteringParamsHeader {
|
|
uint32_t params_id;
|
|
};
|
|
|
|
struct AudioOut2MasteringStatesHeader {
|
|
uint32_t states_id;
|
|
};
|
|
|
|
struct AudioOut2MasteringStatesDescriptor {
|
|
uint32_t id;
|
|
uint32_t size;
|
|
};
|
|
|
|
static constexpr uint32_t AUDIO_OUT2_MASTERING_MAX_SYSTEM_CHANNELS = 8;
|
|
static constexpr uint32_t AUDIO_OUT2_MASTERING_COMPRESSOR_BANDS = 3;
|
|
static constexpr uint32_t AUDIO_OUT2_MASTERING_COMPRESSOR_BANDS_V2 = 4;
|
|
|
|
struct AudioOut2MasteringCompressorStates {
|
|
AudioOut2MasteringStatesDescriptor descriptor;
|
|
uint32_t reserved[2];
|
|
float input_rms[AUDIO_OUT2_MASTERING_COMPRESSOR_BANDS]
|
|
[AUDIO_OUT2_MASTERING_MAX_SYSTEM_CHANNELS];
|
|
float compression_coeff[AUDIO_OUT2_MASTERING_COMPRESSOR_BANDS]
|
|
[AUDIO_OUT2_MASTERING_MAX_SYSTEM_CHANNELS];
|
|
};
|
|
|
|
struct AudioOut2MasteringCompressorStatesV2 {
|
|
AudioOut2MasteringStatesDescriptor descriptor;
|
|
uint32_t reserved[2];
|
|
float input_rms[AUDIO_OUT2_MASTERING_COMPRESSOR_BANDS_V2]
|
|
[AUDIO_OUT2_MASTERING_MAX_SYSTEM_CHANNELS];
|
|
float compression_coeff[AUDIO_OUT2_MASTERING_COMPRESSOR_BANDS_V2]
|
|
[AUDIO_OUT2_MASTERING_MAX_SYSTEM_CHANNELS];
|
|
};
|
|
|
|
struct AudioOut2MasteringLimiterStates {
|
|
AudioOut2MasteringStatesDescriptor descriptor;
|
|
uint32_t reserved[2];
|
|
float input_peak[AUDIO_OUT2_MASTERING_MAX_SYSTEM_CHANNELS];
|
|
float output_peak[AUDIO_OUT2_MASTERING_MAX_SYSTEM_CHANNELS];
|
|
float gain_peak[AUDIO_OUT2_MASTERING_MAX_SYSTEM_CHANNELS];
|
|
};
|
|
|
|
struct AudioOut2MasteringStates {
|
|
AudioOut2MasteringStatesHeader states_header;
|
|
uint32_t reserved[3];
|
|
AudioOut2MasteringCompressorStates compressor_states;
|
|
AudioOut2MasteringLimiterStates limiter_states;
|
|
};
|
|
|
|
struct AudioOut2MasteringStatesV2 {
|
|
AudioOut2MasteringStatesHeader states_header;
|
|
uint32_t reserved[3];
|
|
AudioOut2MasteringCompressorStatesV2 compressor_states;
|
|
AudioOut2MasteringLimiterStates limiter_states;
|
|
};
|
|
|
|
static std::atomic_uint64_t g_audioout2_next_context {1};
|
|
static std::atomic_uint64_t g_audioout2_next_port {1};
|
|
static std::atomic_uint64_t g_audioout2_next_user {1};
|
|
|
|
struct AudioOut2ContextState {
|
|
bool used = false;
|
|
AudioOut2ContextHandle handle = 0;
|
|
uint32_t queue_depth = 4;
|
|
uint32_t queued = 0;
|
|
uint32_t num_grains = 512;
|
|
uint64_t last_update = 0;
|
|
};
|
|
|
|
struct AudioOut2PortStateEntry {
|
|
bool used = false;
|
|
AudioOut2PortHandle handle = 0;
|
|
AudioOut2ContextHandle context = 0;
|
|
uint16_t port_type = 0;
|
|
uint32_t data_format = 0;
|
|
uint32_t sampling_freq = 48000;
|
|
uint32_t samples_num = 512;
|
|
AudioInternal::Format audio_format = AudioInternal::Format::Unknown;
|
|
int audio_handle = 0;
|
|
const void* pcm_data = nullptr;
|
|
};
|
|
|
|
struct AudioOut2SpeakerArrayState {
|
|
bool used = false;
|
|
uint32_t num_speakers = 0;
|
|
uint8_t is_3d = 0;
|
|
uint8_t is_ambisonics = 0;
|
|
AudioOut2SpeakerArrayHandle handle = nullptr;
|
|
};
|
|
|
|
struct AudioOut2LatencyState {
|
|
bool used = false;
|
|
uint32_t user_id = 0;
|
|
uint32_t output = 0;
|
|
uint32_t latency_us = 0;
|
|
};
|
|
|
|
static Common::Mutex g_audioout2_context_mutex;
|
|
static std::array<AudioOut2ContextState, 16> g_audioout2_contexts;
|
|
static Common::Mutex g_audioout2_port_mutex;
|
|
static std::array<AudioOut2PortStateEntry, 256> g_audioout2_ports;
|
|
static Common::Mutex g_audioout2_speaker_array_mutex;
|
|
static std::array<AudioOut2SpeakerArrayState, 32> g_audioout2_speaker_arrays;
|
|
static Common::Mutex g_audioout2_latency_mutex;
|
|
static std::array<AudioOut2LatencyState, 16> g_audioout2_latencies;
|
|
|
|
static constexpr int AUDIO_OUT2_ERROR_NOT_READY = -2144960504; /* 0x80268008 */
|
|
static constexpr int AUDIO_OUT2_ERROR_PORT_FULL = -2144960494; /* 0x80268012 */
|
|
static constexpr int AUDIO_OUT2_ERROR_INVALID_PARAM = -2144960511; /* 0x80268001 */
|
|
static constexpr int AUDIO_OUT2_ERROR_MASTERING_INVALID_API_PARAM = -2144959999; /* 0x80268201 */
|
|
static constexpr int AUDIO_OUT2_ERROR_MASTERING_INVALID_STATES_ID = -2144959996; /* 0x80268204 */
|
|
static constexpr uint32_t AUDIO_OUT2_PORT_ATTRIBUTE_ID_PCM = 0;
|
|
static constexpr uint32_t AUDIO_OUT2_MASTERING_OUTPUT_RECORDING = 2;
|
|
static constexpr uint32_t AUDIO_OUT2_MASTERING_STATES_ID_DEFAULT = 1;
|
|
static constexpr uint32_t AUDIO_OUT2_MASTERING_STATES_ID_V2 = 2;
|
|
static constexpr uint32_t AUDIO_OUT2_MASTERING_STATES_STRUCT_ID_COMPRESSOR_DEFAULT = 0x01010001;
|
|
static constexpr uint32_t AUDIO_OUT2_MASTERING_STATES_STRUCT_ID_COMPRESSOR_V2 = 0x01020001;
|
|
static constexpr uint32_t AUDIO_OUT2_MASTERING_STATES_STRUCT_ID_LIMITER_DEFAULT = 0x01010003;
|
|
|
|
static AudioOut2ContextState* audioout2_find_context_locked(AudioOut2ContextHandle ctx) {
|
|
for (auto& state: g_audioout2_contexts) {
|
|
if (state.used && state.handle == ctx) {
|
|
return &state;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static uint32_t audioout2_grain_micros(uint32_t grains) {
|
|
const auto sample_count = (grains == 0 ? 512u : grains);
|
|
return std::max<uint32_t>((sample_count * 1000000u) / 48000u, 1000u);
|
|
}
|
|
|
|
static uint8_t audioout2_data_format_channels(uint32_t data_format) {
|
|
const auto channels = (data_format >> 8u) & 0xffu;
|
|
return static_cast<uint8_t>(channels == 0 ? 2u : std::min(channels, 16u));
|
|
}
|
|
|
|
static AudioInternal::Format audioout2_data_format_to_audio_format(uint32_t data_format) {
|
|
const auto channels = audioout2_data_format_channels(data_format);
|
|
const auto data_type = data_format & 0x7fu;
|
|
const auto is_std = (data_format & 0x80u) != 0;
|
|
|
|
switch (data_type) {
|
|
case 0:
|
|
switch (channels) {
|
|
case 1: return AudioInternal::Format::FloatMono;
|
|
case 2: return AudioInternal::Format::FloatStereo;
|
|
case 8:
|
|
return is_std ? AudioInternal::Format::Float8ChStd
|
|
: AudioInternal::Format::Float8Ch;
|
|
default: break;
|
|
}
|
|
break;
|
|
case 1:
|
|
switch (channels) {
|
|
case 1: return AudioInternal::Format::Signed16bitMono;
|
|
case 2: return AudioInternal::Format::Signed16bitStereo;
|
|
case 8:
|
|
return is_std ? AudioInternal::Format::Signed16bit8ChStd
|
|
: AudioInternal::Format::Signed16bit8Ch;
|
|
default: break;
|
|
}
|
|
break;
|
|
default: break;
|
|
}
|
|
|
|
return AudioInternal::Format::Unknown;
|
|
}
|
|
|
|
static int audioout2_port_type_to_audio_out_type(uint16_t port_type) {
|
|
switch (port_type & 0xffu) {
|
|
case 0: return AUDIO_OUT_PORT_TYPE_MAIN;
|
|
case 1: return AUDIO_OUT_PORT_TYPE_BGM;
|
|
case 2: return AUDIO_OUT_PORT_TYPE_VOICE;
|
|
case 3: return AUDIO_OUT_PORT_TYPE_PADSPK;
|
|
case 4: return AUDIO_OUT_PORT_TYPE_PERSONAL;
|
|
case 5: return AUDIO_OUT_PORT_TYPE_AUX;
|
|
case 6: return AUDIO_OUT_PORT_TYPE_VIBRATION;
|
|
default: return AUDIO_OUT_PORT_TYPE_MAIN;
|
|
}
|
|
}
|
|
|
|
static bool audioout2_port_type_is_object(uint16_t port_type) {
|
|
return (port_type & 0xff00u) == 0x0100u;
|
|
}
|
|
|
|
static void audioout2_update_context_locked(AudioOut2ContextState* state) {
|
|
if (state == nullptr) {
|
|
return;
|
|
}
|
|
|
|
const auto now = LibKernel::KernelGetProcessTime();
|
|
if (state->last_update == 0 || state->queued == 0) {
|
|
state->last_update = now;
|
|
return;
|
|
}
|
|
|
|
const auto grain_micros = static_cast<uint64_t>(audioout2_grain_micros(state->num_grains));
|
|
if (now <= state->last_update || grain_micros == 0) {
|
|
return;
|
|
}
|
|
|
|
const auto elapsed = now - state->last_update;
|
|
const auto drained = std::min<uint64_t>(state->queued, elapsed / grain_micros);
|
|
if (drained == 0) {
|
|
return;
|
|
}
|
|
|
|
state->queued -= static_cast<uint32_t>(drained);
|
|
state->last_update += drained * grain_micros;
|
|
if (state->queued == 0) {
|
|
state->last_update = now;
|
|
}
|
|
}
|
|
|
|
static AudioOut2PortStateEntry* audioout2_find_port_locked(AudioOut2PortHandle port) {
|
|
for (auto& state: g_audioout2_ports) {
|
|
if (state.used && state.handle == port) {
|
|
return &state;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static bool audioout2_context_has_queueable_device(AudioOut2ContextHandle ctx) {
|
|
Common::LockGuard lock(g_audioout2_port_mutex);
|
|
for (const auto& state: g_audioout2_ports) {
|
|
if (state.used && state.context == ctx && state.audio_handle > 0 &&
|
|
state.pcm_data != nullptr && AudioInternal::AudioOutHasDevice(state.audio_handle)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void audioout2_queue_context_audio(AudioOut2ContextHandle ctx, bool blocking) {
|
|
std::vector<AudioInternal::OutputParam> params;
|
|
params.reserve(AudioInternal::OUT_PORTS_MAX);
|
|
|
|
g_audioout2_port_mutex.Lock();
|
|
for (const auto& state: g_audioout2_ports) {
|
|
if (state.used && state.context == ctx && state.audio_handle > 0 &&
|
|
state.pcm_data != nullptr && params.size() < AudioInternal::OUT_PORTS_MAX) {
|
|
params.push_back(AudioInternal::OutputParam {state.audio_handle, state.pcm_data});
|
|
}
|
|
}
|
|
g_audioout2_port_mutex.Unlock();
|
|
|
|
if (params.empty()) {
|
|
return;
|
|
}
|
|
|
|
(void)AudioInternal::AudioOutOutputs(params.data(), static_cast<uint32_t>(params.size()),
|
|
blocking);
|
|
}
|
|
|
|
static void audioout2_close_audio_handle(int audio_handle) {
|
|
if (audio_handle > 0) {
|
|
AudioInternal::AudioOutClose(audio_handle);
|
|
}
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2Initialize() {
|
|
PRINT_NAME();
|
|
|
|
return OK;
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2ContextResetParam(AudioOut2ContextParam* params) {
|
|
PRINT_NAME();
|
|
|
|
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
|
|
|
std::memset(params, 0, sizeof(AudioOut2ContextParam));
|
|
params->max_ports = 256;
|
|
params->max_object_ports = 256;
|
|
params->guarantee_object_ports = 0;
|
|
params->queue_depth = 4;
|
|
params->num_grains = 512;
|
|
params->flags = 1;
|
|
|
|
return OK;
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2ContextQueryMemory(const AudioOut2ContextParam* params,
|
|
size_t* memory_size) {
|
|
PRINT_NAME();
|
|
|
|
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
|
EXIT_NOT_IMPLEMENTED(memory_size == nullptr);
|
|
|
|
const auto queue_depth = (params->queue_depth == 0 ? 4u : params->queue_depth);
|
|
*memory_size = 0x10000u + static_cast<size_t>(queue_depth) * 0x590u;
|
|
|
|
LOGF("\t memory_size = 0x%016" PRIx64 "\n", static_cast<uint64_t>(*memory_size));
|
|
|
|
return OK;
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2ContextCreate(const AudioOut2ContextParam* params, void* buffer,
|
|
size_t buffer_size, AudioOut2ContextHandle* ctx) {
|
|
PRINT_NAME();
|
|
|
|
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
|
EXIT_NOT_IMPLEMENTED(ctx == nullptr);
|
|
|
|
*ctx = g_audioout2_next_context.fetch_add(1, std::memory_order_relaxed);
|
|
|
|
g_audioout2_context_mutex.Lock();
|
|
auto* state = audioout2_find_context_locked(0);
|
|
if (state == nullptr) {
|
|
for (auto& candidate: g_audioout2_contexts) {
|
|
if (!candidate.used) {
|
|
state = &candidate;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
EXIT_NOT_IMPLEMENTED(state == nullptr);
|
|
*state = AudioOut2ContextState {};
|
|
state->used = true;
|
|
state->handle = *ctx;
|
|
state->queue_depth = (params->queue_depth == 0 ? 4u : params->queue_depth);
|
|
state->queued = 0;
|
|
state->num_grains = (params->num_grains == 0 ? 512u : params->num_grains);
|
|
state->last_update = LibKernel::KernelGetProcessTime();
|
|
g_audioout2_context_mutex.Unlock();
|
|
|
|
LOGF("\t buffer = 0x%016" PRIx64 "\n"
|
|
"\t buffer_size = 0x%016" PRIx64 "\n"
|
|
"\t ctx = 0x%016" PRIx64 "\n"
|
|
"\t queue_depth = %" PRIu32 ", num_grains = %" PRIu32 "\n",
|
|
reinterpret_cast<uint64_t>(buffer), static_cast<uint64_t>(buffer_size), *ctx,
|
|
state->queue_depth, state->num_grains);
|
|
|
|
return OK;
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2ContextDestroy(AudioOut2ContextHandle ctx) {
|
|
PRINT_NAME();
|
|
LOGF("\t ctx = 0x%016" PRIx64 "\n", ctx);
|
|
|
|
g_audioout2_context_mutex.Lock();
|
|
if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) {
|
|
*state = AudioOut2ContextState {};
|
|
}
|
|
g_audioout2_context_mutex.Unlock();
|
|
|
|
std::array<int, 256> audio_handles {};
|
|
size_t audio_handles_num = 0;
|
|
|
|
g_audioout2_port_mutex.Lock();
|
|
for (auto& port_state: g_audioout2_ports) {
|
|
if (port_state.used && port_state.context == ctx) {
|
|
if (port_state.audio_handle > 0 && audio_handles_num < audio_handles.size()) {
|
|
audio_handles[audio_handles_num++] = port_state.audio_handle;
|
|
}
|
|
port_state = AudioOut2PortStateEntry {};
|
|
}
|
|
}
|
|
g_audioout2_port_mutex.Unlock();
|
|
|
|
for (size_t i = 0; i < audio_handles_num; i++) {
|
|
audioout2_close_audio_handle(audio_handles[i]);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2ContextSetAttributes(AudioOut2ContextHandle ctx,
|
|
const AudioOut2Attribute* attributes,
|
|
uint32_t num) {
|
|
PRINT_NAME();
|
|
LOGF("\t ctx = 0x%016" PRIx64 ", num = %" PRIu32 "\n", ctx, num);
|
|
EXIT_NOT_IMPLEMENTED(num != 0 && attributes == nullptr);
|
|
return OK;
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2ContextAdvance(AudioOut2ContextHandle ctx) {
|
|
g_audioout2_context_mutex.Lock();
|
|
if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) {
|
|
audioout2_update_context_locked(state);
|
|
}
|
|
g_audioout2_context_mutex.Unlock();
|
|
|
|
return OK;
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2ContextPush(AudioOut2ContextHandle ctx, uint32_t blocking) {
|
|
uint32_t sleep_micros = audioout2_grain_micros(512);
|
|
|
|
for (;;) {
|
|
// Only a synchronous submission carrying PCM to a real device can rely on the SDL queue for
|
|
// pacing. Async pushes must retain queue-depth backpressure, and a handle without PCM (or a
|
|
// vibration/failed-open handle) has no downstream operation that can block this call.
|
|
const bool use_device_clock =
|
|
blocking != 0 && audioout2_context_has_queueable_device(ctx);
|
|
|
|
g_audioout2_context_mutex.Lock();
|
|
if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) {
|
|
audioout2_update_context_locked(state);
|
|
sleep_micros = audioout2_grain_micros(state->num_grains);
|
|
if (state->queued < state->queue_depth || use_device_clock) {
|
|
if (state->queued == 0) {
|
|
state->last_update = LibKernel::KernelGetProcessTime();
|
|
}
|
|
if (state->queued < state->queue_depth) {
|
|
state->queued++;
|
|
}
|
|
g_audioout2_context_mutex.Unlock();
|
|
audioout2_queue_context_audio(ctx, blocking != 0);
|
|
return OK;
|
|
}
|
|
}
|
|
g_audioout2_context_mutex.Unlock();
|
|
|
|
if (blocking == 0) {
|
|
return AUDIO_OUT2_ERROR_NOT_READY;
|
|
}
|
|
|
|
Common::Thread::SleepMicro(sleep_micros);
|
|
}
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2ContextGetQueueLevel(AudioOut2ContextHandle ctx, uint32_t* queue_level,
|
|
uint32_t* available_queues) {
|
|
if (queue_level != nullptr) {
|
|
*queue_level = 0;
|
|
}
|
|
if (available_queues != nullptr) {
|
|
*available_queues = 4;
|
|
}
|
|
|
|
g_audioout2_context_mutex.Lock();
|
|
if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) {
|
|
audioout2_update_context_locked(state);
|
|
if (queue_level != nullptr) {
|
|
*queue_level = state->queued;
|
|
}
|
|
if (available_queues != nullptr) {
|
|
*available_queues =
|
|
(state->queued < state->queue_depth ? state->queue_depth - state->queued : 0);
|
|
}
|
|
}
|
|
g_audioout2_context_mutex.Unlock();
|
|
|
|
return OK;
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut2PortParam* params,
|
|
AudioOut2PortHandle* port) {
|
|
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
|
EXIT_NOT_IMPLEMENTED(port == nullptr);
|
|
|
|
const auto next_port = g_audioout2_next_port.fetch_add(1, std::memory_order_relaxed);
|
|
const auto audio_format = audioout2_data_format_to_audio_format(params->data_format);
|
|
const auto audio_type = audioout2_port_type_to_audio_out_type(params->port_type);
|
|
|
|
g_audioout2_context_mutex.Lock();
|
|
const auto* context_state = audioout2_find_context_locked(ctx);
|
|
if (context_state == nullptr) {
|
|
g_audioout2_context_mutex.Unlock();
|
|
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
|
}
|
|
const auto samples_num = context_state->num_grains == 0 ? 512u : context_state->num_grains;
|
|
|
|
g_audioout2_port_mutex.Lock();
|
|
AudioOut2PortStateEntry* port_state = nullptr;
|
|
for (auto& candidate: g_audioout2_ports) {
|
|
if (!candidate.used) {
|
|
port_state = &candidate;
|
|
break;
|
|
}
|
|
}
|
|
if (port_state != nullptr) {
|
|
*port_state = AudioOut2PortStateEntry {};
|
|
port_state->used = true;
|
|
port_state->handle = next_port;
|
|
port_state->context = ctx;
|
|
port_state->port_type = params->port_type;
|
|
port_state->data_format = params->data_format;
|
|
port_state->sampling_freq = params->sampling_freq;
|
|
port_state->samples_num = samples_num;
|
|
port_state->audio_format = audio_format;
|
|
}
|
|
g_audioout2_port_mutex.Unlock();
|
|
g_audioout2_context_mutex.Unlock();
|
|
|
|
if (port_state == nullptr) {
|
|
return AUDIO_OUT2_ERROR_PORT_FULL;
|
|
}
|
|
|
|
int audio_handle = 0;
|
|
|
|
if (audio_format != AudioInternal::Format::Unknown &&
|
|
!audioout2_port_type_is_object(params->port_type)) {
|
|
audio_handle = AudioInternal::AudioOutOpen(audio_type, samples_num, params->sampling_freq,
|
|
audio_format);
|
|
}
|
|
|
|
g_audioout2_port_mutex.Lock();
|
|
const bool reserved = port_state->used && port_state->handle == next_port;
|
|
if (reserved) {
|
|
port_state->audio_handle = audio_handle;
|
|
}
|
|
g_audioout2_port_mutex.Unlock();
|
|
if (!reserved) {
|
|
audioout2_close_audio_handle(audio_handle);
|
|
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
|
}
|
|
|
|
*port = next_port;
|
|
|
|
if (next_port <= 16 || (next_port % 600) == 0) {
|
|
PRINT_NAME();
|
|
LOGF("\t ctx = 0x%016" PRIx64 "\n"
|
|
"\t port = 0x%016" PRIx64 "\n"
|
|
"\t port_type = %" PRIu16 "\n"
|
|
"\t data_format = 0x%08" PRIx32 "\n"
|
|
"\t sampling_freq = %" PRIu32 "\n",
|
|
ctx, *port, params->port_type, params->data_format, params->sampling_freq);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2PortDestroy(AudioOut2PortHandle port) {
|
|
PRINT_NAME();
|
|
LOGF("\t port = 0x%016" PRIx64 "\n", port);
|
|
|
|
int audio_handle = 0;
|
|
g_audioout2_port_mutex.Lock();
|
|
if (auto* state = audioout2_find_port_locked(port); state != nullptr) {
|
|
audio_handle = state->audio_handle;
|
|
*state = AudioOut2PortStateEntry {};
|
|
}
|
|
g_audioout2_port_mutex.Unlock();
|
|
|
|
audioout2_close_audio_handle(audio_handle);
|
|
|
|
return OK;
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2PortSetAttributes(AudioOut2PortHandle port,
|
|
const AudioOut2Attribute* attributes, uint32_t num) {
|
|
EXIT_NOT_IMPLEMENTED(num != 0 && attributes == nullptr);
|
|
|
|
const void* pcm_data = nullptr;
|
|
bool has_pcm = false;
|
|
for (uint32_t i = 0; i < num; i++) {
|
|
if (attributes[i].attribute_id == AUDIO_OUT2_PORT_ATTRIBUTE_ID_PCM &&
|
|
attributes[i].value != nullptr && attributes[i].value_size >= sizeof(AudioOut2Pcm)) {
|
|
AudioOut2Pcm pcm {};
|
|
std::memcpy(&pcm, attributes[i].value, sizeof(AudioOut2Pcm));
|
|
pcm_data = pcm.data;
|
|
has_pcm = true;
|
|
}
|
|
}
|
|
|
|
if (has_pcm) {
|
|
g_audioout2_port_mutex.Lock();
|
|
if (auto* state = audioout2_find_port_locked(port); state != nullptr) {
|
|
state->pcm_data = pcm_data;
|
|
}
|
|
g_audioout2_port_mutex.Unlock();
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2PortGetState(AudioOut2PortHandle port, AudioOut2PortState* state) {
|
|
PRINT_NAME();
|
|
|
|
EXIT_NOT_IMPLEMENTED(state == nullptr);
|
|
|
|
std::memset(state, 0, sizeof(AudioOut2PortState));
|
|
state->output = 1;
|
|
state->num_channels = 2;
|
|
state->volume = 127;
|
|
state->reroute_counter = 0;
|
|
state->flags = 0;
|
|
|
|
g_audioout2_port_mutex.Lock();
|
|
if (auto* port_state = audioout2_find_port_locked(port); port_state != nullptr) {
|
|
state->num_channels = audioout2_data_format_channels(port_state->data_format);
|
|
}
|
|
g_audioout2_port_mutex.Unlock();
|
|
|
|
// LOGF("\t port = 0x%016" PRIx64 "\n", port);
|
|
// LOGF("\t num_channels = %" PRIu8 "\n", state->num_channels);
|
|
|
|
return OK;
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2GetSystemState(AudioOut2SystemState* state) {
|
|
PRINT_NAME();
|
|
EXIT_NOT_IMPLEMENTED(state == nullptr);
|
|
std::memset(state, 0, sizeof(AudioOut2SystemState));
|
|
return OK;
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2UserCreate(uint32_t user_id, AudioOut2UserHandle* handle) {
|
|
PRINT_NAME();
|
|
EXIT_NOT_IMPLEMENTED(handle == nullptr);
|
|
*handle = static_cast<AudioOut2UserHandle>(
|
|
g_audioout2_next_user.fetch_add(1, std::memory_order_relaxed));
|
|
LOGF("\t user_id = %" PRIu32 ", handle = 0x%016" PRIx64 "\n", user_id,
|
|
static_cast<uint64_t>(*handle));
|
|
return OK;
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2UserDestroy(AudioOut2UserHandle handle) {
|
|
PRINT_NAME();
|
|
LOGF("\t handle = 0x%016" PRIx64 "\n", static_cast<uint64_t>(handle));
|
|
return OK;
|
|
}
|
|
|
|
size_t KYTY_SYSV_ABI AudioOut2GetSpeakerArrayMemorySize(uint32_t num_speakers, uint8_t is_3d,
|
|
uint8_t is_ambisonics) {
|
|
PRINT_NAME();
|
|
|
|
const auto speakers = std::clamp<uint32_t>(num_speakers, 1, 32);
|
|
const auto size = static_cast<size_t>(0x400 + speakers * (is_ambisonics != 0 ? 0x100 : 0x40) +
|
|
(is_3d != 0 ? 0x200 : 0));
|
|
|
|
LOGF("\t num_speakers = %" PRIu32 "\n"
|
|
"\t is_3d = %" PRIu8 "\n"
|
|
"\t is_ambisonics = %" PRIu8 "\n"
|
|
"\t memory_size = 0x%016" PRIx64 "\n",
|
|
num_speakers, is_3d, is_ambisonics, static_cast<uint64_t>(size));
|
|
|
|
return size;
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2SpeakerArrayCreate(AudioOut2SpeakerArrayHandle* handle,
|
|
const void* vbap_params, const void* ambi_params) {
|
|
PRINT_NAME();
|
|
EXIT_NOT_IMPLEMENTED(handle == nullptr);
|
|
|
|
*handle = nullptr;
|
|
|
|
g_audioout2_speaker_array_mutex.Lock();
|
|
for (auto& state: g_audioout2_speaker_arrays) {
|
|
if (!state.used) {
|
|
state = AudioOut2SpeakerArrayState {};
|
|
state.used = true;
|
|
state.handle = &state;
|
|
state.num_speakers = 2;
|
|
*handle = state.handle;
|
|
break;
|
|
}
|
|
}
|
|
g_audioout2_speaker_array_mutex.Unlock();
|
|
|
|
LOGF("\t handle = 0x%016" PRIx64 "\n"
|
|
"\t vbap_params = 0x%016" PRIx64 "\n"
|
|
"\t ambi_params = 0x%016" PRIx64 "\n",
|
|
reinterpret_cast<uint64_t>(*handle), reinterpret_cast<uint64_t>(vbap_params),
|
|
reinterpret_cast<uint64_t>(ambi_params));
|
|
|
|
return (*handle != nullptr ? OK : AUDIO_OUT2_ERROR_PORT_FULL);
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2SpeakerArrayDestroy(AudioOut2SpeakerArrayHandle handle) {
|
|
PRINT_NAME();
|
|
LOGF("\t handle = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(handle));
|
|
|
|
g_audioout2_speaker_array_mutex.Lock();
|
|
for (auto& state: g_audioout2_speaker_arrays) {
|
|
if (state.used && state.handle == handle) {
|
|
state = AudioOut2SpeakerArrayState {};
|
|
break;
|
|
}
|
|
}
|
|
g_audioout2_speaker_array_mutex.Unlock();
|
|
|
|
return OK;
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2GetSpeakerArrayCoefficients(
|
|
AudioOut2SpeakerArrayHandle handle, AudioOut2Position pos, float spread, float* coefficients,
|
|
uint32_t num_coefficients, uint8_t height_aware, float downmix_spread_radius) {
|
|
PRINT_NAME();
|
|
EXIT_NOT_IMPLEMENTED(coefficients == nullptr && num_coefficients != 0);
|
|
|
|
if (coefficients != nullptr) {
|
|
std::fill(coefficients, coefficients + num_coefficients, 0.0f);
|
|
if (num_coefficients > 0) {
|
|
coefficients[0] = 1.0f;
|
|
}
|
|
if (num_coefficients > 1) {
|
|
coefficients[1] = 1.0f;
|
|
}
|
|
}
|
|
|
|
LOGF("\t handle = 0x%016" PRIx64 ", coeffs = %" PRIu32
|
|
", pos = (%f, %f, %f), spread = %f, height = %" PRIu8 ", downmix = %f\n",
|
|
reinterpret_cast<uint64_t>(handle), num_coefficients, static_cast<double>(pos.x),
|
|
static_cast<double>(pos.y), static_cast<double>(pos.z), static_cast<double>(spread),
|
|
height_aware, static_cast<double>(downmix_spread_radius));
|
|
|
|
return OK;
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2GetSpeakerArrayAmbisonicsCoefficients(AudioOut2SpeakerArrayHandle handle,
|
|
uint32_t ambisonics_channel,
|
|
float* coefficients,
|
|
uint32_t num_coefficients) {
|
|
PRINT_NAME();
|
|
EXIT_NOT_IMPLEMENTED(coefficients == nullptr && num_coefficients != 0);
|
|
|
|
if (coefficients != nullptr) {
|
|
std::fill(coefficients, coefficients + num_coefficients, 0.0f);
|
|
if (num_coefficients > 0) {
|
|
coefficients[0] =
|
|
(ambisonics_channel == 0 || ambisonics_channel == 64 ? 0.70710677f : 1.0f);
|
|
}
|
|
}
|
|
|
|
LOGF("\t handle = 0x%016" PRIx64 ", channel = %" PRIu32 ", coeffs = %" PRIu32 "\n",
|
|
reinterpret_cast<uint64_t>(handle), ambisonics_channel, num_coefficients);
|
|
|
|
return OK;
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2GetSpeakerInfo(AudioOut2SpeakerInfo* info, uint32_t flags) {
|
|
EXIT_NOT_IMPLEMENTED(info == nullptr);
|
|
std::memset(info, 0, sizeof(AudioOut2SpeakerInfo));
|
|
info->type = 0;
|
|
info->available_bits = 0x03;
|
|
info->flags = 0;
|
|
info->speaker_angle[0] = {-30, 0};
|
|
info->speaker_angle[1] = {30, 0};
|
|
|
|
return OK;
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2SetSystemDebugState(const AudioOut2SystemDebugStateParam* param) {
|
|
PRINT_NAME();
|
|
LOGF("\t param = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(param));
|
|
return OK;
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2Set3DLatency(uint32_t user_id, uint32_t output, uint32_t latency_us) {
|
|
// Not sure
|
|
|
|
PRINT_NAME();
|
|
LOGF("\t user_id = %" PRIu32 ", output = %" PRIu32 ", latency_us = %" PRIu32 "\n", user_id,
|
|
output, latency_us);
|
|
|
|
if (output > AUDIO_OUT2_MASTERING_OUTPUT_RECORDING) {
|
|
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
|
}
|
|
|
|
g_audioout2_latency_mutex.Lock();
|
|
AudioOut2LatencyState* free_state = nullptr;
|
|
for (auto& state: g_audioout2_latencies) {
|
|
if (state.used && state.user_id == user_id && state.output == output) {
|
|
state.latency_us = latency_us;
|
|
g_audioout2_latency_mutex.Unlock();
|
|
return OK;
|
|
}
|
|
if (!state.used && free_state == nullptr) {
|
|
free_state = &state;
|
|
}
|
|
}
|
|
|
|
if (free_state != nullptr) {
|
|
*free_state = AudioOut2LatencyState {true, user_id, output, latency_us};
|
|
}
|
|
g_audioout2_latency_mutex.Unlock();
|
|
|
|
return (free_state != nullptr ? OK : AUDIO_OUT2_ERROR_PORT_FULL);
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2MasteringInit(uint32_t flags) {
|
|
PRINT_NAME();
|
|
LOGF("\t flags = 0x%08" PRIx32 "\n", flags);
|
|
return OK;
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2MasteringSetParam(const AudioOut2MasteringParamsHeader* param,
|
|
uint32_t output, uint32_t flags) {
|
|
PRINT_NAME();
|
|
LOGF("\t param = 0x%016" PRIx64 ", output = %" PRIu32 ", flags = 0x%08" PRIx32 "\n",
|
|
reinterpret_cast<uint64_t>(param), output, flags);
|
|
return OK;
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2MasteringGetState(AudioOut2MasteringStatesHeader* state, uint32_t output,
|
|
AudioOut2UserHandle user) {
|
|
PRINT_NAME();
|
|
LOGF("\t state = 0x%016" PRIx64 ", output = %" PRIu32 ", user = 0x%016" PRIx64 "\n",
|
|
reinterpret_cast<uint64_t>(state), output, static_cast<uint64_t>(user));
|
|
|
|
if (state == nullptr) {
|
|
return AUDIO_OUT2_ERROR_MASTERING_INVALID_API_PARAM;
|
|
}
|
|
|
|
const auto states_id = state->states_id;
|
|
switch (states_id) {
|
|
case AUDIO_OUT2_MASTERING_STATES_ID_DEFAULT: {
|
|
auto* full_state = reinterpret_cast<AudioOut2MasteringStates*>(state);
|
|
std::memset(full_state, 0, sizeof(AudioOut2MasteringStates));
|
|
full_state->states_header.states_id = AUDIO_OUT2_MASTERING_STATES_ID_DEFAULT;
|
|
full_state->compressor_states.descriptor.id =
|
|
AUDIO_OUT2_MASTERING_STATES_STRUCT_ID_COMPRESSOR_DEFAULT;
|
|
full_state->compressor_states.descriptor.size =
|
|
sizeof(AudioOut2MasteringCompressorStates);
|
|
full_state->limiter_states.descriptor.id =
|
|
AUDIO_OUT2_MASTERING_STATES_STRUCT_ID_LIMITER_DEFAULT;
|
|
full_state->limiter_states.descriptor.size = sizeof(AudioOut2MasteringLimiterStates);
|
|
return OK;
|
|
}
|
|
case AUDIO_OUT2_MASTERING_STATES_ID_V2: {
|
|
auto* full_state = reinterpret_cast<AudioOut2MasteringStatesV2*>(state);
|
|
std::memset(full_state, 0, sizeof(AudioOut2MasteringStatesV2));
|
|
full_state->states_header.states_id = AUDIO_OUT2_MASTERING_STATES_ID_V2;
|
|
full_state->compressor_states.descriptor.id =
|
|
AUDIO_OUT2_MASTERING_STATES_STRUCT_ID_COMPRESSOR_V2;
|
|
full_state->compressor_states.descriptor.size =
|
|
sizeof(AudioOut2MasteringCompressorStatesV2);
|
|
full_state->limiter_states.descriptor.id =
|
|
AUDIO_OUT2_MASTERING_STATES_STRUCT_ID_LIMITER_DEFAULT;
|
|
full_state->limiter_states.descriptor.size = sizeof(AudioOut2MasteringLimiterStates);
|
|
return OK;
|
|
}
|
|
default: return AUDIO_OUT2_ERROR_MASTERING_INVALID_STATES_ID;
|
|
}
|
|
}
|
|
|
|
int KYTY_SYSV_ABI AudioOut2MasteringTerm() {
|
|
PRINT_NAME();
|
|
return OK;
|
|
}
|
|
|
|
} // namespace AudioOut2
|
|
} // namespace Libs::Audio
|