mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 22:06:59 +01:00
audio: support 512 object ports per AudioOut2 context
This commit is contained in:
+103
-78
@@ -13,6 +13,7 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <span>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace Libs::Audio {
|
namespace Libs::Audio {
|
||||||
@@ -168,17 +169,12 @@ struct AudioOut2MasteringStatesV2 {
|
|||||||
|
|
||||||
static std::atomic_uint64_t g_audioout2_next_context {1};
|
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_port {1};
|
||||||
static AudioOut2UserHandle g_audioout2_next_user = 1;
|
static AudioOut2UserHandle g_audioout2_next_user = 1;
|
||||||
|
|
||||||
struct AudioOut2ContextState {
|
static constexpr uint32_t AUDIOOUT2_CHANNEL_PORTS_MAX = 32;
|
||||||
bool used = false;
|
static constexpr uint32_t AUDIOOUT2_OBJECT_PORTS_MAX = 512;
|
||||||
AudioOut2ContextHandle handle = 0;
|
static constexpr uint32_t AUDIOOUT2_PORT_SLOT_BITS = 10;
|
||||||
uint32_t queue_depth = 4;
|
static constexpr uint32_t AUDIOOUT2_CONTEXT_SLOT_BITS = 4;
|
||||||
uint32_t queued = 0;
|
|
||||||
uint32_t num_grains = 512;
|
|
||||||
uint64_t last_update = 0;
|
|
||||||
int object_audio_handle = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AudioOut2PortStateEntry {
|
struct AudioOut2PortStateEntry {
|
||||||
bool used = false;
|
bool used = false;
|
||||||
@@ -196,6 +192,19 @@ struct AudioOut2PortStateEntry {
|
|||||||
bool attributes_locked = false;
|
bool attributes_locked = false;
|
||||||
std::vector<uint8_t> pcm_data;
|
std::vector<uint8_t> pcm_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
int object_audio_handle = 0;
|
||||||
|
uint32_t max_ports = 0;
|
||||||
|
// Channel slots precede object slots; each partition has its own creation limit.
|
||||||
|
std::vector<AudioOut2PortStateEntry> ports;
|
||||||
|
};
|
||||||
struct AudioOut2SpeakerArrayState {
|
struct AudioOut2SpeakerArrayState {
|
||||||
bool used = false;
|
bool used = false;
|
||||||
uint32_t num_speakers = 0;
|
uint32_t num_speakers = 0;
|
||||||
@@ -223,13 +232,16 @@ struct AudioOut2Submission {
|
|||||||
static Common::Mutex g_audioout2_state_mutex;
|
static Common::Mutex g_audioout2_state_mutex;
|
||||||
static std::array<AudioOut2ContextState, 16> g_audioout2_contexts;
|
static std::array<AudioOut2ContextState, 16> g_audioout2_contexts;
|
||||||
static std::array<AudioOut2Submission, 16> g_audioout2_submissions;
|
static std::array<AudioOut2Submission, 16> g_audioout2_submissions;
|
||||||
static std::array<AudioOut2PortStateEntry, 256> g_audioout2_ports;
|
static std::vector<AudioOut2UserHandle> g_audioout2_users;
|
||||||
static std::vector<AudioOut2UserHandle> g_audioout2_users;
|
|
||||||
static Common::Mutex g_audioout2_speaker_array_mutex;
|
static Common::Mutex g_audioout2_speaker_array_mutex;
|
||||||
static std::array<AudioOut2SpeakerArrayState, 32> g_audioout2_speaker_arrays;
|
static std::array<AudioOut2SpeakerArrayState, 32> g_audioout2_speaker_arrays;
|
||||||
static Common::Mutex g_audioout2_latency_mutex;
|
static Common::Mutex g_audioout2_latency_mutex;
|
||||||
static std::array<AudioOut2LatencyState, 16> g_audioout2_latencies;
|
static std::array<AudioOut2LatencyState, 16> g_audioout2_latencies;
|
||||||
|
|
||||||
|
static_assert(AUDIOOUT2_CHANNEL_PORTS_MAX + AUDIOOUT2_OBJECT_PORTS_MAX <=
|
||||||
|
(1u << AUDIOOUT2_PORT_SLOT_BITS));
|
||||||
|
static_assert(g_audioout2_contexts.size() == (1u << AUDIOOUT2_CONTEXT_SLOT_BITS));
|
||||||
|
|
||||||
static constexpr int AUDIO_OUT2_ERROR_NOT_READY = -2144960504; /* 0x80268008 */
|
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_PORT_FULL = -2144960494; /* 0x80268012 */
|
||||||
static constexpr int AUDIO_OUT2_ERROR_INVALID_PARAM = -2144960511; /* 0x80268001 */
|
static constexpr int AUDIO_OUT2_ERROR_INVALID_PARAM = -2144960511; /* 0x80268001 */
|
||||||
@@ -359,24 +371,28 @@ static void audioout2_update_context_locked(AudioOut2ContextState* state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static AudioOut2PortStateEntry* audioout2_find_port_locked(AudioOut2PortHandle port) {
|
static AudioOut2PortStateEntry* audioout2_find_port_locked(AudioOut2PortHandle port) {
|
||||||
for (auto& state: g_audioout2_ports) {
|
const auto context_slot =
|
||||||
if (state.used && state.handle == port) {
|
(port >> AUDIOOUT2_PORT_SLOT_BITS) & ((1u << AUDIOOUT2_CONTEXT_SLOT_BITS) - 1u);
|
||||||
return &state;
|
const auto port_slot = port & ((1u << AUDIOOUT2_PORT_SLOT_BITS) - 1u);
|
||||||
}
|
auto& context = g_audioout2_contexts[context_slot];
|
||||||
|
if (context.used && port_slot < context.ports.size()) {
|
||||||
|
auto& state = context.ports[port_slot];
|
||||||
|
if (state.used && state.handle == port) return &state;
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t audioout2_pcm_size(const AudioOut2PortStateEntry& state) {
|
static size_t audioout2_pcm_size(const AudioOut2PortStateEntry& state) {
|
||||||
const auto bytes_per_sample = (state.data_format & 0x7fu) == 1 ? sizeof(int16_t) : sizeof(float);
|
const auto bytes_per_sample =
|
||||||
|
(state.data_format & 0x7fu) == 1 ? sizeof(int16_t) : sizeof(float);
|
||||||
return static_cast<size_t>(state.samples_num) *
|
return static_cast<size_t>(state.samples_num) *
|
||||||
audioout2_data_format_channels(state.data_format) * bytes_per_sample;
|
audioout2_data_format_channels(state.data_format) * bytes_per_sample;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool audioout2_context_has_queueable_device(const AudioOut2ContextState& context) {
|
static bool audioout2_context_has_queueable_device(const AudioOut2ContextState& context) {
|
||||||
for (const auto& state: g_audioout2_ports) {
|
for (const auto& state: context.ports) {
|
||||||
if (state.used && state.context == context.handle && !state.pcm_data.empty()) {
|
if (state.used && !state.pcm_data.empty()) {
|
||||||
const int handle = audioout2_port_type_is_object(state.port_type)
|
const int handle = audioout2_port_type_is_object(state.port_type)
|
||||||
? context.object_audio_handle
|
? context.object_audio_handle
|
||||||
: state.audio_handle;
|
: state.audio_handle;
|
||||||
@@ -432,8 +448,8 @@ static void audioout2_queue_context_audio(AudioOut2ContextHandle ctx, AudioOut2S
|
|||||||
params.reserve(AudioInternal::OUT_PORTS_MAX);
|
params.reserve(AudioInternal::OUT_PORTS_MAX);
|
||||||
// A port's PCM covers one grain: the push consumes it, so a port the game stops feeding
|
// A port's PCM covers one grain: the push consumes it, so a port the game stops feeding
|
||||||
// falls silent.
|
// falls silent.
|
||||||
for (auto& state: g_audioout2_ports) {
|
for (auto& state: context->ports) {
|
||||||
if (!state.used || state.context != ctx || state.pcm_data.empty()) {
|
if (!state.used || state.pcm_data.empty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (audioout2_port_type_is_object(state.port_type)) {
|
if (audioout2_port_type_is_object(state.port_type)) {
|
||||||
@@ -473,22 +489,28 @@ int KYTY_SYSV_ABI AudioOut2ContextResetParam(AudioOut2ContextParam* params) {
|
|||||||
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
||||||
|
|
||||||
std::memset(params, 0, sizeof(AudioOut2ContextParam));
|
std::memset(params, 0, sizeof(AudioOut2ContextParam));
|
||||||
params->max_ports = 256;
|
params->max_ports = 8;
|
||||||
params->max_object_ports = 256;
|
params->queue_depth = 1;
|
||||||
params->guarantee_object_ports = 0;
|
params->num_grains = 256;
|
||||||
params->queue_depth = 4;
|
|
||||||
params->num_grains = 512;
|
|
||||||
params->flags = 1;
|
|
||||||
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool audioout2_valid_context_capacity(const AudioOut2ContextParam& params) {
|
||||||
|
return params.max_ports <= AUDIOOUT2_CHANNEL_PORTS_MAX &&
|
||||||
|
params.max_object_ports <= AUDIOOUT2_OBJECT_PORTS_MAX &&
|
||||||
|
params.guarantee_object_ports <= params.max_object_ports;
|
||||||
|
}
|
||||||
|
|
||||||
int KYTY_SYSV_ABI AudioOut2ContextQueryMemory(const AudioOut2ContextParam* params,
|
int KYTY_SYSV_ABI AudioOut2ContextQueryMemory(const AudioOut2ContextParam* params,
|
||||||
size_t* memory_size) {
|
size_t* memory_size) {
|
||||||
PRINT_NAME();
|
PRINT_NAME();
|
||||||
|
|
||||||
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
||||||
EXIT_NOT_IMPLEMENTED(memory_size == nullptr);
|
EXIT_NOT_IMPLEMENTED(memory_size == nullptr);
|
||||||
|
if (!audioout2_valid_context_capacity(*params)) {
|
||||||
|
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
const auto queue_depth = (params->queue_depth == 0 ? 4u : params->queue_depth);
|
const auto queue_depth = (params->queue_depth == 0 ? 4u : params->queue_depth);
|
||||||
*memory_size = 0x10000u + static_cast<size_t>(queue_depth) * 0x590u;
|
*memory_size = 0x10000u + static_cast<size_t>(queue_depth) * 0x590u;
|
||||||
@@ -504,6 +526,9 @@ int KYTY_SYSV_ABI AudioOut2ContextCreate(const AudioOut2ContextParam* params, vo
|
|||||||
|
|
||||||
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
||||||
EXIT_NOT_IMPLEMENTED(ctx == nullptr);
|
EXIT_NOT_IMPLEMENTED(ctx == nullptr);
|
||||||
|
if (!audioout2_valid_context_capacity(*params)) {
|
||||||
|
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
const auto num_grains = params->num_grains == 0 ? 512u : params->num_grains;
|
const auto num_grains = params->num_grains == 0 ? 512u : params->num_grains;
|
||||||
const int object_audio_handle =
|
const int object_audio_handle =
|
||||||
@@ -532,6 +557,8 @@ int KYTY_SYSV_ABI AudioOut2ContextCreate(const AudioOut2ContextParam* params, vo
|
|||||||
state->num_grains = num_grains;
|
state->num_grains = num_grains;
|
||||||
state->last_update = audioout2_clock();
|
state->last_update = audioout2_clock();
|
||||||
state->object_audio_handle = object_audio_handle;
|
state->object_audio_handle = object_audio_handle;
|
||||||
|
state->max_ports = params->max_ports;
|
||||||
|
state->ports.resize(params->max_ports + params->max_object_ports);
|
||||||
g_audioout2_state_mutex.Unlock();
|
g_audioout2_state_mutex.Unlock();
|
||||||
|
|
||||||
LOGF("\t buffer = 0x%016" PRIx64 "\n"
|
LOGF("\t buffer = 0x%016" PRIx64 "\n"
|
||||||
@@ -547,8 +574,8 @@ int KYTY_SYSV_ABI AudioOut2ContextDestroy(AudioOut2ContextHandle ctx) {
|
|||||||
PRINT_NAME();
|
PRINT_NAME();
|
||||||
LOGF("\t ctx = 0x%016" PRIx64 "\n", ctx);
|
LOGF("\t ctx = 0x%016" PRIx64 "\n", ctx);
|
||||||
|
|
||||||
std::array<int, 257> audio_handles {};
|
std::array<int, AUDIOOUT2_CHANNEL_PORTS_MAX + 1> audio_handles {};
|
||||||
size_t audio_handles_num = 0;
|
size_t audio_handles_num = 0;
|
||||||
|
|
||||||
g_audioout2_state_mutex.Lock();
|
g_audioout2_state_mutex.Lock();
|
||||||
auto* state = audioout2_find_context_locked(ctx);
|
auto* state = audioout2_find_context_locked(ctx);
|
||||||
@@ -556,15 +583,12 @@ int KYTY_SYSV_ABI AudioOut2ContextDestroy(AudioOut2ContextHandle ctx) {
|
|||||||
if (state->object_audio_handle > 0) {
|
if (state->object_audio_handle > 0) {
|
||||||
audio_handles[audio_handles_num++] = state->object_audio_handle;
|
audio_handles[audio_handles_num++] = state->object_audio_handle;
|
||||||
}
|
}
|
||||||
*state = AudioOut2ContextState {};
|
for (const auto& port_state: state->ports) {
|
||||||
}
|
if (port_state.used && port_state.audio_handle > 0) {
|
||||||
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;
|
audio_handles[audio_handles_num++] = port_state.audio_handle;
|
||||||
}
|
}
|
||||||
port_state = AudioOut2PortStateEntry {};
|
|
||||||
}
|
}
|
||||||
|
*state = AudioOut2ContextState {};
|
||||||
}
|
}
|
||||||
g_audioout2_state_mutex.Unlock();
|
g_audioout2_state_mutex.Unlock();
|
||||||
|
|
||||||
@@ -592,9 +616,7 @@ int KYTY_SYSV_ABI AudioOut2ContextAdvance(AudioOut2ContextHandle ctx) {
|
|||||||
g_audioout2_state_mutex.Lock();
|
g_audioout2_state_mutex.Lock();
|
||||||
if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) {
|
if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) {
|
||||||
audioout2_update_context_locked(state);
|
audioout2_update_context_locked(state);
|
||||||
}
|
for (auto& port: state->ports) {
|
||||||
for (auto& port: g_audioout2_ports) {
|
|
||||||
if (port.used && port.context == ctx) {
|
|
||||||
port.attributes_locked = false;
|
port.attributes_locked = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -669,21 +691,19 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
|||||||
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
||||||
EXIT_NOT_IMPLEMENTED(port == 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_format = audioout2_data_format_to_audio_format(params->data_format);
|
||||||
const auto audio_type = audioout2_port_type_to_audio_out_type(params->port_type);
|
const auto audio_type = audioout2_port_type_to_audio_out_type(params->port_type);
|
||||||
|
const bool is_object = audioout2_port_type_is_object(params->port_type);
|
||||||
|
|
||||||
g_audioout2_state_mutex.Lock();
|
g_audioout2_state_mutex.Lock();
|
||||||
const auto* context_state = audioout2_find_context_locked(ctx);
|
auto* context_state = audioout2_find_context_locked(ctx);
|
||||||
if (context_state == nullptr) {
|
if (context_state == nullptr) {
|
||||||
g_audioout2_state_mutex.Unlock();
|
g_audioout2_state_mutex.Unlock();
|
||||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
if (audioout2_port_type_is_object(params->port_type) &&
|
if (is_object && (params->port_type != 0x100 || params->sampling_freq != 48000 ||
|
||||||
(params->port_type != 0x100 || context_state->object_audio_handle == 0 ||
|
(audio_format != AudioInternal::Format::FloatMono &&
|
||||||
params->sampling_freq != 48000 ||
|
audio_format != AudioInternal::Format::Signed16bitMono))) {
|
||||||
(audio_format != AudioInternal::Format::FloatMono &&
|
|
||||||
audio_format != AudioInternal::Format::Signed16bitMono))) {
|
|
||||||
g_audioout2_state_mutex.Unlock();
|
g_audioout2_state_mutex.Unlock();
|
||||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
@@ -694,42 +714,45 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
|||||||
g_audioout2_state_mutex.Unlock();
|
g_audioout2_state_mutex.Unlock();
|
||||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
AudioOut2PortStateEntry* port_state = nullptr;
|
auto slots = std::span(context_state->ports);
|
||||||
for (auto& candidate: g_audioout2_ports) {
|
slots =
|
||||||
if (!candidate.used) {
|
is_object ? slots.subspan(context_state->max_ports) : slots.first(context_state->max_ports);
|
||||||
port_state = &candidate;
|
const auto available = std::find_if(slots.begin(), slots.end(),
|
||||||
break;
|
[](const auto& candidate) { return !candidate.used; });
|
||||||
}
|
if (available == slots.end()) {
|
||||||
}
|
g_audioout2_state_mutex.Unlock();
|
||||||
if (port_state != nullptr) {
|
|
||||||
*port_state = AudioOut2PortStateEntry {};
|
|
||||||
port_state->used = true;
|
|
||||||
port_state->handle = next_port;
|
|
||||||
port_state->context = ctx;
|
|
||||||
port_state->user = params->user_handle;
|
|
||||||
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;
|
|
||||||
port_state->gains.fill(1.0f);
|
|
||||||
}
|
|
||||||
g_audioout2_state_mutex.Unlock();
|
|
||||||
|
|
||||||
if (port_state == nullptr) {
|
|
||||||
return AUDIO_OUT2_ERROR_PORT_FULL;
|
return AUDIO_OUT2_ERROR_PORT_FULL;
|
||||||
}
|
}
|
||||||
|
const auto serial = g_audioout2_next_port.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
const auto context_slot = static_cast<uint64_t>(context_state - g_audioout2_contexts.data());
|
||||||
|
const auto port_slot = static_cast<uint64_t>(&*available - context_state->ports.data());
|
||||||
|
const auto next_port = (serial << (AUDIOOUT2_PORT_SLOT_BITS + AUDIOOUT2_CONTEXT_SLOT_BITS)) |
|
||||||
|
(context_slot << AUDIOOUT2_PORT_SLOT_BITS) | port_slot;
|
||||||
|
auto* port_state = &*available;
|
||||||
|
*port_state = AudioOut2PortStateEntry {};
|
||||||
|
port_state->used = true;
|
||||||
|
port_state->handle = next_port;
|
||||||
|
port_state->context = ctx;
|
||||||
|
port_state->user = params->user_handle;
|
||||||
|
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;
|
||||||
|
port_state->gains.fill(1.0f);
|
||||||
|
g_audioout2_state_mutex.Unlock();
|
||||||
|
|
||||||
int audio_handle = 0;
|
int audio_handle = 0;
|
||||||
|
|
||||||
if (audio_format != AudioInternal::Format::Unknown &&
|
if (audio_format != AudioInternal::Format::Unknown && !is_object) {
|
||||||
!audioout2_port_type_is_object(params->port_type)) {
|
|
||||||
audio_handle = AudioInternal::AudioOutOpen(audio_type, samples_num, params->sampling_freq,
|
audio_handle = AudioInternal::AudioOutOpen(audio_type, samples_num, params->sampling_freq,
|
||||||
audio_format);
|
audio_format);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_audioout2_state_mutex.Lock();
|
g_audioout2_state_mutex.Lock();
|
||||||
const bool reserved = port_state->used && port_state->handle == next_port;
|
// Context destruction may have freed or replaced the slots while the backend opened.
|
||||||
|
port_state = audioout2_find_port_locked(next_port);
|
||||||
|
const bool reserved = port_state != nullptr;
|
||||||
if (reserved) {
|
if (reserved) {
|
||||||
port_state->audio_handle = audio_handle;
|
port_state->audio_handle = audio_handle;
|
||||||
}
|
}
|
||||||
@@ -741,7 +764,7 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
|||||||
|
|
||||||
*port = next_port;
|
*port = next_port;
|
||||||
|
|
||||||
if (next_port <= 16 || (next_port % 600) == 0) {
|
if (serial <= 16 || (serial % 600) == 0) {
|
||||||
PRINT_NAME();
|
PRINT_NAME();
|
||||||
LOGF("\t ctx = 0x%016" PRIx64 "\n"
|
LOGF("\t ctx = 0x%016" PRIx64 "\n"
|
||||||
"\t port = 0x%016" PRIx64 "\n"
|
"\t port = 0x%016" PRIx64 "\n"
|
||||||
@@ -907,21 +930,23 @@ int KYTY_SYSV_ABI AudioOut2UserDestroy(AudioOut2UserHandle handle) {
|
|||||||
PRINT_NAME();
|
PRINT_NAME();
|
||||||
LOGF("\t handle = 0x%016" PRIx64 "\n", static_cast<uint64_t>(handle));
|
LOGF("\t handle = 0x%016" PRIx64 "\n", static_cast<uint64_t>(handle));
|
||||||
Common::LockGuard lock(g_audioout2_state_mutex);
|
Common::LockGuard lock(g_audioout2_state_mutex);
|
||||||
const auto it = std::find(g_audioout2_users.begin(), g_audioout2_users.end(), handle);
|
const auto it = std::find(g_audioout2_users.begin(), g_audioout2_users.end(), handle);
|
||||||
if (it == g_audioout2_users.end()) {
|
if (it == g_audioout2_users.end()) {
|
||||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
if (std::any_of(g_audioout2_ports.begin(), g_audioout2_ports.end(),
|
for (const auto& context: g_audioout2_contexts) {
|
||||||
[handle](const auto& port) { return port.used && port.user == handle; })) {
|
if (std::any_of(context.ports.begin(), context.ports.end(),
|
||||||
return AUDIO_OUT2_ERROR_BUSY;
|
[handle](const auto& port) { return port.used && port.user == handle; })) {
|
||||||
|
return AUDIO_OUT2_ERROR_BUSY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
g_audioout2_users.erase(it);
|
g_audioout2_users.erase(it);
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int KYTY_SYSV_ABI AudioOut2UserGetSupportedAttributes(AudioOut2UserHandle handle,
|
int KYTY_SYSV_ABI AudioOut2UserGetSupportedAttributes(AudioOut2UserHandle handle,
|
||||||
uint32_t* context_attributes,
|
uint32_t* context_attributes,
|
||||||
uint32_t* port_attributes) {
|
uint32_t* port_attributes) {
|
||||||
if (context_attributes == nullptr || port_attributes == nullptr) {
|
if (context_attributes == nullptr || port_attributes == nullptr) {
|
||||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,8 +111,10 @@ PortParam MakeParam(uint32_t data_format = 0x200) {
|
|||||||
return param;
|
return param;
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioOut2::AudioOut2ContextHandle CreateContext(uint32_t queue_depth = 4, uint32_t max_object_ports = 0) {
|
AudioOut2::AudioOut2ContextHandle
|
||||||
|
CreateContext(uint32_t queue_depth = 4, uint32_t max_object_ports = 0, uint32_t max_ports = 8) {
|
||||||
ContextParam param {};
|
ContextParam param {};
|
||||||
|
param.max_ports = max_ports;
|
||||||
param.max_object_ports = max_object_ports;
|
param.max_object_ports = max_object_ports;
|
||||||
param.queue_depth = queue_depth;
|
param.queue_depth = queue_depth;
|
||||||
param.num_grains = 512;
|
param.num_grains = 512;
|
||||||
@@ -281,13 +283,139 @@ void TestSlotReuse() {
|
|||||||
AudioOut2::AudioOut2ContextDestroy(context);
|
AudioOut2::AudioOut2ContextDestroy(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestObjectPortCapacityPerContext() {
|
||||||
|
constexpr int port_full = static_cast<int32_t>(0x80268012u);
|
||||||
|
constexpr int invalid_port = static_cast<int32_t>(0x80268009u);
|
||||||
|
std::array<AudioOut2::AudioOut2ContextHandle, 3> contexts {};
|
||||||
|
std::array<AudioOut2::AudioOut2PortHandle, 3> last_ports {};
|
||||||
|
auto object = MakeParam(0x100);
|
||||||
|
object.port_type = 0x100;
|
||||||
|
for (size_t i = 0; i < contexts.size(); i++) {
|
||||||
|
contexts[i] = CreateContext(4, 512, 32);
|
||||||
|
CreatePort(contexts[i], MakeParam());
|
||||||
|
for (size_t j = 0; j < 512; j++) {
|
||||||
|
AudioOut2::AudioOut2PortHandle port = 0;
|
||||||
|
Check(AudioOut2::AudioOut2PortCreate(contexts[i], AsParam(&object), &port) == OK,
|
||||||
|
"context could not create all 512 object ports");
|
||||||
|
last_ports[i] = port;
|
||||||
|
}
|
||||||
|
AudioOut2::AudioOut2PortHandle overflow = UINT64_MAX;
|
||||||
|
Check(AudioOut2::AudioOut2PortCreate(contexts[i], AsParam(&object), &overflow) ==
|
||||||
|
port_full &&
|
||||||
|
overflow == UINT64_MAX,
|
||||||
|
"object capacity did not reject the 513th port without changing its output");
|
||||||
|
CreatePort(contexts[i], MakeParam());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < contexts.size(); i++) {
|
||||||
|
const auto stale = last_ports[i];
|
||||||
|
AudioOut2::AudioOut2PortDestroy(stale);
|
||||||
|
last_ports[i] = CreatePort(contexts[i], object);
|
||||||
|
Check(last_ports[i] != stale &&
|
||||||
|
AudioOut2::AudioOut2PortSetAttributes(stale, nullptr, 0) == invalid_port,
|
||||||
|
"reused object slot accepted its old handle");
|
||||||
|
std::array<float, 512> pcm;
|
||||||
|
pcm.fill(0.25f * static_cast<float>(i + 1));
|
||||||
|
SetPcm(last_ports[i], pcm.data());
|
||||||
|
const auto output = PushPcm(contexts[i], 512 * 2 * sizeof(float));
|
||||||
|
const float expected = pcm[0] * 0.70710678f;
|
||||||
|
Check(output.size() == 1, "last object port did not reach its context's mixer");
|
||||||
|
CheckSamples(output[0], {expected, expected}, "object audio crossed context boundaries");
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioOut2::AudioOut2ContextDestroy(contexts[0]);
|
||||||
|
Check(AudioOut2::AudioOut2PortSetAttributes(last_ports[0], nullptr, 0) == invalid_port,
|
||||||
|
"destroyed context retained its object handles");
|
||||||
|
const auto replacement = CreateContext(4, 512);
|
||||||
|
for (size_t i = 0; i < 512; i++) {
|
||||||
|
CreatePort(replacement, object);
|
||||||
|
}
|
||||||
|
Check(AudioOut2::AudioOut2PortSetAttributes(last_ports[0], nullptr, 0) == invalid_port,
|
||||||
|
"reused context accepted an old object handle");
|
||||||
|
for (size_t i = 1; i < contexts.size(); i++) {
|
||||||
|
Check(AudioOut2::AudioOut2PortSetAttributes(last_ports[i], nullptr, 0) == OK,
|
||||||
|
"destroying another context invalidated a live object port");
|
||||||
|
AudioOut2::AudioOut2ContextDestroy(contexts[i]);
|
||||||
|
}
|
||||||
|
AudioOut2::AudioOut2ContextDestroy(replacement);
|
||||||
|
Check(LiveDeviceCount() == 0, "object capacity test leaked audio devices");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestContextPortLimits() {
|
||||||
|
constexpr int port_full = static_cast<int32_t>(0x80268012u);
|
||||||
|
ContextParam param {};
|
||||||
|
param.max_ports = 1;
|
||||||
|
param.max_object_ports = 2;
|
||||||
|
param.num_grains = 512;
|
||||||
|
AudioOut2::AudioOut2ContextHandle context = 0;
|
||||||
|
Check(AudioOut2::AudioOut2ContextCreate(AsParam(¶m), nullptr, 0, &context) == OK,
|
||||||
|
"small context create failed");
|
||||||
|
auto object = MakeParam(0x100);
|
||||||
|
object.port_type = 0x100;
|
||||||
|
const auto channel = CreatePort(context, MakeParam());
|
||||||
|
const auto first_object = CreatePort(context, object);
|
||||||
|
CreatePort(context, object);
|
||||||
|
AudioOut2::AudioOut2PortHandle overflow = 0;
|
||||||
|
const auto channel_param = MakeParam();
|
||||||
|
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(&channel_param), &overflow) ==
|
||||||
|
port_full &&
|
||||||
|
AudioOut2::AudioOut2PortCreate(context, AsParam(&object), &overflow) == port_full,
|
||||||
|
"declared channel/object capacities were ignored");
|
||||||
|
AudioOut2::AudioOut2PortDestroy(channel);
|
||||||
|
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(&object), &overflow) == port_full,
|
||||||
|
"object port borrowed a channel slot");
|
||||||
|
CreatePort(context, channel_param);
|
||||||
|
AudioOut2::AudioOut2PortDestroy(first_object);
|
||||||
|
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(&channel_param), &overflow) == port_full,
|
||||||
|
"channel port borrowed an object slot");
|
||||||
|
CreatePort(context, object);
|
||||||
|
AudioOut2::AudioOut2ContextDestroy(context);
|
||||||
|
param.max_ports = param.max_object_ports = 0;
|
||||||
|
Check(AudioOut2::AudioOut2ContextCreate(AsParam(¶m), nullptr, 0, &context) == OK,
|
||||||
|
"empty context create failed");
|
||||||
|
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(&channel_param), &overflow) ==
|
||||||
|
port_full &&
|
||||||
|
AudioOut2::AudioOut2PortCreate(context, AsParam(&object), &overflow) == port_full,
|
||||||
|
"zero port capacity was ignored");
|
||||||
|
AudioOut2::AudioOut2ContextDestroy(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestContextCapacityParameters() {
|
||||||
|
constexpr int invalid_param = static_cast<int32_t>(0x80268001u);
|
||||||
|
ContextParam param;
|
||||||
|
std::memset(¶m, 0xff, sizeof(param));
|
||||||
|
Check(AudioOut2::AudioOut2ContextResetParam(
|
||||||
|
reinterpret_cast<AudioOut2::AudioOut2ContextParam*>(¶m)) == OK &&
|
||||||
|
param.max_ports == 8 && param.max_object_ports == 0 &&
|
||||||
|
param.guarantee_object_ports == 0 && param.num_grains == 256 &&
|
||||||
|
param.queue_depth == 1 && param.flags == 0 &&
|
||||||
|
std::all_of(std::begin(param.reserved), std::end(param.reserved),
|
||||||
|
[](uint32_t word) { return word == 0; }),
|
||||||
|
"context reset did not produce valid default parameters");
|
||||||
|
const auto defaults = param;
|
||||||
|
for (size_t i = 0; i < 3; i++) {
|
||||||
|
param = defaults;
|
||||||
|
if (i == 0) param.max_ports = 33;
|
||||||
|
if (i == 1) param.max_object_ports = 513;
|
||||||
|
if (i == 2) param.guarantee_object_ports = 1;
|
||||||
|
size_t memory = SIZE_MAX;
|
||||||
|
AudioOut2::AudioOut2ContextHandle context = UINT64_MAX;
|
||||||
|
Check(AudioOut2::AudioOut2ContextQueryMemory(AsParam(¶m), &memory) == invalid_param &&
|
||||||
|
memory == SIZE_MAX &&
|
||||||
|
AudioOut2::AudioOut2ContextCreate(AsParam(¶m), nullptr, 0, &context) ==
|
||||||
|
invalid_param &&
|
||||||
|
context == UINT64_MAX && LiveDeviceCount() == 0,
|
||||||
|
"invalid capacities allocated resources or modified outputs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TestFullTableRecovers() {
|
void TestFullTableRecovers() {
|
||||||
const auto context = CreateContext();
|
const auto context = CreateContext();
|
||||||
const auto param = MakeParam();
|
const auto param = MakeParam();
|
||||||
std::vector<AudioOut2::AudioOut2PortHandle> ports;
|
std::vector<AudioOut2::AudioOut2PortHandle> ports;
|
||||||
ports.reserve(256);
|
ports.reserve(8);
|
||||||
|
|
||||||
for (int i = 0; i < 256; i++) {
|
for (int i = 0; i < 8; i++) {
|
||||||
AudioOut2::AudioOut2PortHandle port = 0;
|
AudioOut2::AudioOut2PortHandle port = 0;
|
||||||
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port) == OK,
|
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port) == OK,
|
||||||
"port table filled early");
|
"port table filled early");
|
||||||
@@ -324,6 +452,11 @@ void TestConcurrentCreates() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
WaitForDeviceOpens(thread_count);
|
WaitForDeviceOpens(thread_count);
|
||||||
|
AudioOut2::AudioOut2PortHandle overflow = 0;
|
||||||
|
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &overflow) ==
|
||||||
|
static_cast<int32_t>(0x80268012u) &&
|
||||||
|
overflow == 0,
|
||||||
|
"pending creates did not reserve their channel capacity");
|
||||||
ReleaseDeviceOpens();
|
ReleaseDeviceOpens();
|
||||||
for (auto& thread: threads) {
|
for (auto& thread: threads) {
|
||||||
thread.join();
|
thread.join();
|
||||||
@@ -351,17 +484,31 @@ void TestContextDestroyCancelsPendingCreate() {
|
|||||||
[&]() { result = AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port); });
|
[&]() { result = AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port); });
|
||||||
WaitForDeviceOpens(1);
|
WaitForDeviceOpens(1);
|
||||||
AudioOut2::AudioOut2ContextDestroy(context);
|
AudioOut2::AudioOut2ContextDestroy(context);
|
||||||
|
const auto replacement = CreateContext();
|
||||||
|
AudioOut2::AudioOut2PortHandle replacement_port = 0;
|
||||||
|
int replacement_result = OK;
|
||||||
|
std::thread replacement_creator([&]() {
|
||||||
|
replacement_result =
|
||||||
|
AudioOut2::AudioOut2PortCreate(replacement, AsParam(¶m), &replacement_port);
|
||||||
|
});
|
||||||
|
WaitForDeviceOpens(2);
|
||||||
ReleaseDeviceOpens();
|
ReleaseDeviceOpens();
|
||||||
creator.join();
|
creator.join();
|
||||||
|
replacement_creator.join();
|
||||||
|
|
||||||
Check(result != OK, "destroyed context retained a pending port create");
|
Check(result != OK, "destroyed context retained a pending port create");
|
||||||
|
Check(replacement_result == OK &&
|
||||||
|
AudioOut2::AudioOut2PortSetAttributes(replacement_port, nullptr, 0) == OK &&
|
||||||
|
LiveDeviceCount() == 1,
|
||||||
|
"cancelled create corrupted the replacement context or leaked a device");
|
||||||
|
AudioOut2::AudioOut2ContextDestroy(replacement);
|
||||||
Check(LiveDeviceCount() == 0, "cancelled port create leaked a device");
|
Check(LiveDeviceCount() == 0, "cancelled port create leaked a device");
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestSynchronousDevicePushBypassesModelledQueue() {
|
void TestSynchronousDevicePushBypassesModelledQueue() {
|
||||||
const auto context = CreateContext(1);
|
const auto context = CreateContext(1);
|
||||||
const auto param = MakeParam();
|
const auto param = MakeParam();
|
||||||
AudioOut2::AudioOut2PortHandle port = 0;
|
AudioOut2::AudioOut2PortHandle port = 0;
|
||||||
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port) == OK,
|
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port) == OK,
|
||||||
"device port create failed");
|
"device port create failed");
|
||||||
|
|
||||||
@@ -778,6 +925,9 @@ int main() {
|
|||||||
Check(AudioOut2::AudioOut2UserCreate(1000, &g_user_handle) == OK, "test user create failed");
|
Check(AudioOut2::AudioOut2UserCreate(1000, &g_user_handle) == OK, "test user create failed");
|
||||||
TestUserSupportedAttributes();
|
TestUserSupportedAttributes();
|
||||||
TestSlotReuse();
|
TestSlotReuse();
|
||||||
|
TestObjectPortCapacityPerContext();
|
||||||
|
TestContextPortLimits();
|
||||||
|
TestContextCapacityParameters();
|
||||||
TestFullTableRecovers();
|
TestFullTableRecovers();
|
||||||
TestConcurrentCreates();
|
TestConcurrentCreates();
|
||||||
TestContextDestroyCancelsPendingCreate();
|
TestContextDestroyCancelsPendingCreate();
|
||||||
|
|||||||
Reference in New Issue
Block a user