From 2efd31443fbc76510f876e5052a84180278b3429 Mon Sep 17 00:00:00 2001 From: nmzik Date: Tue, 27 Oct 2026 21:23:18 +0100 Subject: [PATCH] audio: support 512 object ports per AudioOut2 context --- src/libs/libAudio2.cpp | 181 ++++++++++++++++++++--------------- tests/AudioOut2PortTests.cpp | 162 +++++++++++++++++++++++++++++-- 2 files changed, 259 insertions(+), 84 deletions(-) diff --git a/src/libs/libAudio2.cpp b/src/libs/libAudio2.cpp index 92703efb0..db6a01882 100644 --- a/src/libs/libAudio2.cpp +++ b/src/libs/libAudio2.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include 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_port {1}; -static AudioOut2UserHandle g_audioout2_next_user = 1; +static AudioOut2UserHandle 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; - int object_audio_handle = 0; -}; +static constexpr uint32_t AUDIOOUT2_CHANNEL_PORTS_MAX = 32; +static constexpr uint32_t AUDIOOUT2_OBJECT_PORTS_MAX = 512; +static constexpr uint32_t AUDIOOUT2_PORT_SLOT_BITS = 10; +static constexpr uint32_t AUDIOOUT2_CONTEXT_SLOT_BITS = 4; struct AudioOut2PortStateEntry { bool used = false; @@ -196,6 +192,19 @@ struct AudioOut2PortStateEntry { bool attributes_locked = false; std::vector 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 ports; +}; struct AudioOut2SpeakerArrayState { bool used = false; uint32_t num_speakers = 0; @@ -223,13 +232,16 @@ struct AudioOut2Submission { static Common::Mutex g_audioout2_state_mutex; static std::array g_audioout2_contexts; static std::array g_audioout2_submissions; -static std::array g_audioout2_ports; -static std::vector g_audioout2_users; +static std::vector g_audioout2_users; 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_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_PORT_FULL = -2144960494; /* 0x80268012 */ 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) { - for (auto& state: g_audioout2_ports) { - if (state.used && state.handle == port) { - return &state; - } + const auto context_slot = + (port >> AUDIOOUT2_PORT_SLOT_BITS) & ((1u << AUDIOOUT2_CONTEXT_SLOT_BITS) - 1u); + 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; } 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(state.samples_num) * audioout2_data_format_channels(state.data_format) * bytes_per_sample; } static bool audioout2_context_has_queueable_device(const AudioOut2ContextState& context) { - for (const auto& state: g_audioout2_ports) { - if (state.used && state.context == context.handle && !state.pcm_data.empty()) { + for (const auto& state: context.ports) { + if (state.used && !state.pcm_data.empty()) { const int handle = audioout2_port_type_is_object(state.port_type) ? context.object_audio_handle : state.audio_handle; @@ -432,8 +448,8 @@ static void audioout2_queue_context_audio(AudioOut2ContextHandle ctx, AudioOut2S params.reserve(AudioInternal::OUT_PORTS_MAX); // A port's PCM covers one grain: the push consumes it, so a port the game stops feeding // falls silent. - for (auto& state: g_audioout2_ports) { - if (!state.used || state.context != ctx || state.pcm_data.empty()) { + for (auto& state: context->ports) { + if (!state.used || state.pcm_data.empty()) { continue; } 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); 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; + params->max_ports = 8; + params->queue_depth = 1; + params->num_grains = 256; 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, size_t* memory_size) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(params == 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); *memory_size = 0x10000u + static_cast(queue_depth) * 0x590u; @@ -504,6 +526,9 @@ int KYTY_SYSV_ABI AudioOut2ContextCreate(const AudioOut2ContextParam* params, vo EXIT_NOT_IMPLEMENTED(params == 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 int object_audio_handle = @@ -532,6 +557,8 @@ int KYTY_SYSV_ABI AudioOut2ContextCreate(const AudioOut2ContextParam* params, vo state->num_grains = num_grains; state->last_update = audioout2_clock(); 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(); LOGF("\t buffer = 0x%016" PRIx64 "\n" @@ -547,8 +574,8 @@ int KYTY_SYSV_ABI AudioOut2ContextDestroy(AudioOut2ContextHandle ctx) { PRINT_NAME(); LOGF("\t ctx = 0x%016" PRIx64 "\n", ctx); - std::array audio_handles {}; - size_t audio_handles_num = 0; + std::array audio_handles {}; + size_t audio_handles_num = 0; g_audioout2_state_mutex.Lock(); auto* state = audioout2_find_context_locked(ctx); @@ -556,15 +583,12 @@ int KYTY_SYSV_ABI AudioOut2ContextDestroy(AudioOut2ContextHandle ctx) { if (state->object_audio_handle > 0) { audio_handles[audio_handles_num++] = state->object_audio_handle; } - *state = AudioOut2ContextState {}; - } - 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()) { + for (const auto& port_state: state->ports) { + if (port_state.used && port_state.audio_handle > 0) { audio_handles[audio_handles_num++] = port_state.audio_handle; } - port_state = AudioOut2PortStateEntry {}; } + *state = AudioOut2ContextState {}; } g_audioout2_state_mutex.Unlock(); @@ -592,9 +616,7 @@ int KYTY_SYSV_ABI AudioOut2ContextAdvance(AudioOut2ContextHandle ctx) { g_audioout2_state_mutex.Lock(); if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) { audioout2_update_context_locked(state); - } - for (auto& port: g_audioout2_ports) { - if (port.used && port.context == ctx) { + for (auto& port: state->ports) { 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(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); + const bool is_object = audioout2_port_type_is_object(params->port_type); 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) { g_audioout2_state_mutex.Unlock(); return AUDIO_OUT2_ERROR_INVALID_PARAM; } - if (audioout2_port_type_is_object(params->port_type) && - (params->port_type != 0x100 || context_state->object_audio_handle == 0 || - params->sampling_freq != 48000 || - (audio_format != AudioInternal::Format::FloatMono && - audio_format != AudioInternal::Format::Signed16bitMono))) { + if (is_object && (params->port_type != 0x100 || params->sampling_freq != 48000 || + (audio_format != AudioInternal::Format::FloatMono && + audio_format != AudioInternal::Format::Signed16bitMono))) { g_audioout2_state_mutex.Unlock(); return AUDIO_OUT2_ERROR_INVALID_PARAM; } @@ -694,42 +714,45 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut g_audioout2_state_mutex.Unlock(); return AUDIO_OUT2_ERROR_INVALID_PARAM; } - 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->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) { + auto slots = std::span(context_state->ports); + slots = + is_object ? slots.subspan(context_state->max_ports) : slots.first(context_state->max_ports); + const auto available = std::find_if(slots.begin(), slots.end(), + [](const auto& candidate) { return !candidate.used; }); + if (available == slots.end()) { + g_audioout2_state_mutex.Unlock(); 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(context_state - g_audioout2_contexts.data()); + const auto port_slot = static_cast(&*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; - if (audio_format != AudioInternal::Format::Unknown && - !audioout2_port_type_is_object(params->port_type)) { + if (audio_format != AudioInternal::Format::Unknown && !is_object) { audio_handle = AudioInternal::AudioOutOpen(audio_type, samples_num, params->sampling_freq, audio_format); } 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) { port_state->audio_handle = audio_handle; } @@ -741,7 +764,7 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut *port = next_port; - if (next_port <= 16 || (next_port % 600) == 0) { + if (serial <= 16 || (serial % 600) == 0) { PRINT_NAME(); LOGF("\t ctx = 0x%016" PRIx64 "\n" "\t port = 0x%016" PRIx64 "\n" @@ -907,21 +930,23 @@ int KYTY_SYSV_ABI AudioOut2UserDestroy(AudioOut2UserHandle handle) { PRINT_NAME(); LOGF("\t handle = 0x%016" PRIx64 "\n", static_cast(handle)); 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()) { return AUDIO_OUT2_ERROR_INVALID_PARAM; } - if (std::any_of(g_audioout2_ports.begin(), g_audioout2_ports.end(), - [handle](const auto& port) { return port.used && port.user == handle; })) { - return AUDIO_OUT2_ERROR_BUSY; + for (const auto& context: g_audioout2_contexts) { + if (std::any_of(context.ports.begin(), context.ports.end(), + [handle](const auto& port) { return port.used && port.user == handle; })) { + return AUDIO_OUT2_ERROR_BUSY; + } } g_audioout2_users.erase(it); return OK; } int KYTY_SYSV_ABI AudioOut2UserGetSupportedAttributes(AudioOut2UserHandle handle, - uint32_t* context_attributes, - uint32_t* port_attributes) { + uint32_t* context_attributes, + uint32_t* port_attributes) { if (context_attributes == nullptr || port_attributes == nullptr) { return AUDIO_OUT2_ERROR_INVALID_PARAM; } diff --git a/tests/AudioOut2PortTests.cpp b/tests/AudioOut2PortTests.cpp index 9c4bc0d6a..40a40df1e 100644 --- a/tests/AudioOut2PortTests.cpp +++ b/tests/AudioOut2PortTests.cpp @@ -111,8 +111,10 @@ PortParam MakeParam(uint32_t data_format = 0x200) { 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 {}; + param.max_ports = max_ports; param.max_object_ports = max_object_ports; param.queue_depth = queue_depth; param.num_grains = 512; @@ -281,13 +283,139 @@ void TestSlotReuse() { AudioOut2::AudioOut2ContextDestroy(context); } +void TestObjectPortCapacityPerContext() { + constexpr int port_full = static_cast(0x80268012u); + constexpr int invalid_port = static_cast(0x80268009u); + std::array contexts {}; + std::array 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 pcm; + pcm.fill(0.25f * static_cast(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(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(0x80268001u); + ContextParam param; + std::memset(¶m, 0xff, sizeof(param)); + Check(AudioOut2::AudioOut2ContextResetParam( + reinterpret_cast(¶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() { const auto context = CreateContext(); const auto param = MakeParam(); std::vector 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; Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port) == OK, "port table filled early"); @@ -324,6 +452,11 @@ void TestConcurrentCreates() { }); } WaitForDeviceOpens(thread_count); + AudioOut2::AudioOut2PortHandle overflow = 0; + Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &overflow) == + static_cast(0x80268012u) && + overflow == 0, + "pending creates did not reserve their channel capacity"); ReleaseDeviceOpens(); for (auto& thread: threads) { thread.join(); @@ -351,17 +484,31 @@ void TestContextDestroyCancelsPendingCreate() { [&]() { result = AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port); }); WaitForDeviceOpens(1); 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(); creator.join(); + replacement_creator.join(); 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"); } void TestSynchronousDevicePushBypassesModelledQueue() { - const auto context = CreateContext(1); - const auto param = MakeParam(); - AudioOut2::AudioOut2PortHandle port = 0; + const auto context = CreateContext(1); + const auto param = MakeParam(); + AudioOut2::AudioOut2PortHandle port = 0; Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port) == OK, "device port create failed"); @@ -778,6 +925,9 @@ int main() { Check(AudioOut2::AudioOut2UserCreate(1000, &g_user_handle) == OK, "test user create failed"); TestUserSupportedAttributes(); TestSlotReuse(); + TestObjectPortCapacityPerContext(); + TestContextPortLimits(); + TestContextCapacityParameters(); TestFullTableRecovers(); TestConcurrentCreates(); TestContextDestroyCancelsPendingCreate();