#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 #include #include #include #include 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; std::vector pcm_data; }; 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 g_audioout2_contexts; static Common::Mutex g_audioout2_port_mutex; static std::array g_audioout2_ports; static Common::Mutex g_audioout2_speaker_array_mutex; static std::array g_audioout2_speaker_arrays; static Common::Mutex g_audioout2_latency_mutex; static std::array 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((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(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; case 12: if (!is_std) { return AudioInternal::Format::Float12Ch; } break; 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(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(state->queued, elapsed / grain_micros); if (drained == 0) { return; } state->queued -= static_cast(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 size_t audioout2_pcm_size(const AudioOut2PortStateEntry& state) { const auto bytes_per_sample = (state.data_format & 0x7fu) == 1 ? sizeof(int16_t) : sizeof(float); return static_cast(state.samples_num) * audioout2_data_format_channels(state.data_format) * bytes_per_sample; } 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.empty() && AudioInternal::AudioOutHasDevice(state.audio_handle)) { return true; } } return false; } static void audioout2_queue_context_audio(AudioOut2ContextHandle ctx, bool blocking) { // The backend consumes the buffers synchronously, but can block while pacing SDL's queue. // Keep both PCM storage and port handles alive until it returns. std::vector params; params.reserve(AudioInternal::OUT_PORTS_MAX); 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.empty() && params.size() < AudioInternal::OUT_PORTS_MAX) { params.push_back(AudioInternal::OutputParam {state.audio_handle, state.pcm_data.data()}); } } if (!params.empty()) { (void)AudioInternal::AudioOutOutputs(params.data(), static_cast(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(queue_depth) * 0x590u; LOGF("\t memory_size = 0x%016" PRIx64 "\n", static_cast(*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(buffer), static_cast(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 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) { if (pcm_data != nullptr && state->audio_format != AudioInternal::Format::Unknown) { const auto* bytes = static_cast(pcm_data); state->pcm_data.assign(bytes, bytes + audioout2_pcm_size(*state)); } else { state->pcm_data.clear(); } } 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( 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(*handle)); return OK; } int KYTY_SYSV_ABI AudioOut2UserDestroy(AudioOut2UserHandle handle) { PRINT_NAME(); LOGF("\t handle = 0x%016" PRIx64 "\n", static_cast(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(num_speakers, 1, 32); const auto size = static_cast(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(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(*handle), reinterpret_cast(vbap_params), reinterpret_cast(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(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(handle), num_coefficients, static_cast(pos.x), static_cast(pos.y), static_cast(pos.z), static_cast(spread), height_aware, static_cast(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(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(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(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(state), output, static_cast(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(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(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