diff --git a/src/libs/audio.h b/src/libs/audio.h index 2fd462d69..40706f5ff 100644 --- a/src/libs/audio.h +++ b/src/libs/audio.h @@ -70,6 +70,9 @@ int KYTY_SYSV_ABI AudioOut2PortGetState(AudioOut2PortHandle port, AudioOut2PortS int KYTY_SYSV_ABI AudioOut2GetSystemState(AudioOut2SystemState* state); int KYTY_SYSV_ABI AudioOut2UserCreate(uint32_t user_id, AudioOut2UserHandle* handle); int KYTY_SYSV_ABI AudioOut2UserDestroy(AudioOut2UserHandle handle); +int KYTY_SYSV_ABI AudioOut2UserGetSupportedAttributes(AudioOut2UserHandle handle, + uint32_t* context_attributes, + uint32_t* port_attributes); size_t KYTY_SYSV_ABI AudioOut2GetSpeakerArrayMemorySize(uint32_t num_speakers, uint8_t is_3d, uint8_t is_ambisonics); int KYTY_SYSV_ABI AudioOut2SpeakerArrayCreate(AudioOut2SpeakerArrayHandle* handle, diff --git a/src/libs/libAudio.cpp b/src/libs/libAudio.cpp index 8f9962ac6..704d2b29e 100644 --- a/src/libs/libAudio.cpp +++ b/src/libs/libAudio.cpp @@ -158,6 +158,7 @@ LIB_DEFINE(InitAudio_1_AudioOut2) { LIB_FUNC("bkBN+CMLwRc", AudioOut2::AudioOut2GetSystemState); LIB_FUNC("xywYcRB7nbQ", AudioOut2::AudioOut2UserCreate); LIB_FUNC("IaZXJ9M79uo", AudioOut2::AudioOut2UserDestroy); + LIB_FUNC("iE8trxPKnAg", AudioOut2::AudioOut2UserGetSupportedAttributes); LIB_FUNC("G1YOKDJYX2Y", AudioOut2::AudioOut2GetSpeakerArrayMemorySize); LIB_FUNC("+k91hoTuoA8", AudioOut2::AudioOut2SpeakerArrayCreate); LIB_FUNC("erCWQR5eKiQ", AudioOut2::AudioOut2SpeakerArrayDestroy); diff --git a/src/libs/libAudio2.cpp b/src/libs/libAudio2.cpp index 13b74f2c1..35f5e82a2 100644 --- a/src/libs/libAudio2.cpp +++ b/src/libs/libAudio2.cpp @@ -167,7 +167,7 @@ struct AudioOut2MasteringStatesV2 { 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}; +static AudioOut2UserHandle g_audioout2_next_user = 1; struct AudioOut2ContextState { bool used = false; @@ -182,6 +182,7 @@ struct AudioOut2PortStateEntry { bool used = false; AudioOut2PortHandle handle = 0; AudioOut2ContextHandle context = 0; + AudioOut2UserHandle user = 0; uint16_t port_type = 0; uint32_t data_format = 0; uint32_t sampling_freq = 48000; @@ -210,6 +211,7 @@ 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 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; @@ -218,6 +220,7 @@ 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_BUSY = -2144960505; /* 0x80268007 */ 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; @@ -589,6 +592,12 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut const auto samples_num = context_state->num_grains == 0 ? 512u : context_state->num_grains; g_audioout2_port_mutex.Lock(); + if (std::find(g_audioout2_users.begin(), g_audioout2_users.end(), params->user_handle) == + g_audioout2_users.end()) { + g_audioout2_port_mutex.Unlock(); + g_audioout2_context_mutex.Unlock(); + return AUDIO_OUT2_ERROR_INVALID_PARAM; + } AudioOut2PortStateEntry* port_state = nullptr; for (auto& candidate: g_audioout2_ports) { if (!candidate.used) { @@ -601,6 +610,7 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut 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; @@ -730,9 +740,12 @@ int KYTY_SYSV_ABI AudioOut2GetSystemState(AudioOut2SystemState* state) { 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)); + if (handle == nullptr) { + return AUDIO_OUT2_ERROR_INVALID_PARAM; + } + Common::LockGuard lock(g_audioout2_port_mutex); + *handle = g_audioout2_next_user++; + g_audioout2_users.push_back(*handle); LOGF("\t user_id = %" PRIu32 ", handle = 0x%016" PRIx64 "\n", user_id, static_cast(*handle)); return OK; @@ -741,6 +754,32 @@ int KYTY_SYSV_ABI AudioOut2UserCreate(uint32_t user_id, AudioOut2UserHandle* han int KYTY_SYSV_ABI AudioOut2UserDestroy(AudioOut2UserHandle handle) { PRINT_NAME(); LOGF("\t handle = 0x%016" PRIx64 "\n", static_cast(handle)); + Common::LockGuard lock(g_audioout2_port_mutex); + 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; + } + g_audioout2_users.erase(it); + return OK; +} + +int KYTY_SYSV_ABI AudioOut2UserGetSupportedAttributes(AudioOut2UserHandle handle, + uint32_t* context_attributes, + uint32_t* port_attributes) { + if (context_attributes == nullptr || port_attributes == nullptr) { + return AUDIO_OUT2_ERROR_INVALID_PARAM; + } + Common::LockGuard lock(g_audioout2_port_mutex); + if (std::find(g_audioout2_users.begin(), g_audioout2_users.end(), handle) == + g_audioout2_users.end()) { + return AUDIO_OUT2_ERROR_INVALID_PARAM; + } + *context_attributes = 0; + *port_attributes = 1u << AUDIO_OUT2_PORT_ATTRIBUTE_ID_PCM; return OK; } diff --git a/tests/AudioOut2PortTests.cpp b/tests/AudioOut2PortTests.cpp index d6f306f25..95e204d31 100644 --- a/tests/AudioOut2PortTests.cpp +++ b/tests/AudioOut2PortTests.cpp @@ -17,6 +17,7 @@ namespace { namespace AudioOut2 = Libs::Audio::AudioOut2; +AudioOut2::AudioOut2UserHandle g_user_handle = 0; std::mutex g_device_mutex; std::condition_variable g_device_cv; std::vector g_live_devices; @@ -97,6 +98,7 @@ PortParam MakeParam(uint32_t data_format = 0x200) { PortParam param {}; param.data_format = data_format; param.sampling_freq = 48000; + param.user_handle = g_user_handle; return param; } @@ -110,6 +112,56 @@ AudioOut2::AudioOut2ContextHandle CreateContext(uint32_t queue_depth = 4) { return context; } +void TestUserSupportedAttributes() { + constexpr int invalid_param = static_cast(0x80268001u); + constexpr int busy = static_cast(0x80268007u); + AudioOut2::AudioOut2UserHandle user = 0; + Check(AudioOut2::AudioOut2UserCreate(1000, nullptr) == invalid_param, + "null user handle output was accepted"); + Check(AudioOut2::AudioOut2UserCreate(1000, &user) == OK, "user create failed"); + uint32_t context_attributes = UINT32_MAX; + uint32_t port_attributes = UINT32_MAX; + Check(AudioOut2::AudioOut2UserGetSupportedAttributes(user, &context_attributes, + &port_attributes) == OK && + context_attributes == 0 && port_attributes == 1, + "user capabilities do not match implemented PCM support"); + + context_attributes = port_attributes = UINT32_MAX; + Check(AudioOut2::AudioOut2UserGetSupportedAttributes(user, nullptr, &port_attributes) == + invalid_param && + AudioOut2::AudioOut2UserGetSupportedAttributes(user, &context_attributes, nullptr) == + invalid_param && + context_attributes == UINT32_MAX && port_attributes == UINT32_MAX, + "null capability outputs were accepted or modified"); + for (const auto invalid: {AudioOut2::AudioOut2UserHandle {0}, UINTPTR_MAX}) { + Check(AudioOut2::AudioOut2UserGetSupportedAttributes(invalid, &context_attributes, + &port_attributes) == invalid_param && + context_attributes == UINT32_MAX && port_attributes == UINT32_MAX, + "invalid user capabilities succeeded or modified outputs"); + } + + const auto context = CreateContext(); + auto param = MakeParam(); + param.user_handle = user; + AudioOut2::AudioOut2PortHandle port = 0; + Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port) == OK, + "user port create failed"); + Check(AudioOut2::AudioOut2UserDestroy(user) == busy, + "user was destroyed while owning a port"); + AudioOut2::AudioOut2ContextDestroy(context); + Check(AudioOut2::AudioOut2UserDestroy(user) == OK, "unused user destroy failed"); + Check(AudioOut2::AudioOut2UserGetSupportedAttributes(user, &context_attributes, + &port_attributes) == invalid_param && + context_attributes == UINT32_MAX && port_attributes == UINT32_MAX && + AudioOut2::AudioOut2UserDestroy(user) == invalid_param, + "destroyed user remained valid"); + + const auto new_context = CreateContext(); + Check(AudioOut2::AudioOut2PortCreate(new_context, AsParam(¶m), &port) == invalid_param, + "destroyed user acquired a port"); + AudioOut2::AudioOut2ContextDestroy(new_context); +} + void BlockDeviceOpens() { std::lock_guard lock(g_device_mutex); g_open_waiters = 0; @@ -436,6 +488,8 @@ uint64_t KYTY_SYSV_ABI KernelGetProcessTime() { } // namespace Libs::LibKernel int main() { + Check(AudioOut2::AudioOut2UserCreate(1000, &g_user_handle) == OK, "test user create failed"); + TestUserSupportedAttributes(); TestSlotReuse(); TestFullTableRecovers(); TestConcurrentCreates(); @@ -445,6 +499,7 @@ int main() { TestAsynchronousDevicePushKeepsQueueBounded(); TestHandleWithoutPcmDoesNotBypassQueue(); TestPcmCopiedBeforeScratchBufferReuse(); + Check(AudioOut2::AudioOut2UserDestroy(g_user_handle) == OK, "test user destroy failed"); std::printf("AudioOut2PortTests: all cases passed\n"); return 0; }