#include "libs/audio.h" #include "libs/audio_internal.h" #include "libs/errno.h" #include #include #include #include #include #include #include #include #include #include #include #include #include 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; std::vector g_device_backed_handles; std::vector g_output_blocking; std::vector> g_output_pcm; std::vector> g_output_gains; size_t g_capture_bytes = 0; int g_next_device = 1; int g_open_waiters = 0; bool g_block_opens = false; void Check(bool value, const char* text) { if (!value) { std::fprintf(stderr, "AudioOut2PortTests: failed: %s\n", text); std::abort(); } } struct PortParam { uint16_t port_type; uint16_t pad; uint32_t data_format; uint32_t sampling_freq; uint32_t flags; uint64_t user_handle; uint32_t reserved[10]; }; struct ContextParam { 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 PortState { 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 Attribute { uint32_t attribute_id; int32_t reserved; const void* value; size_t value_size; }; struct Pcm { const void* data; }; const auto* AsParam(const PortParam* param) { return reinterpret_cast(param); } const auto* AsParam(const ContextParam* param) { return reinterpret_cast(param); } auto* AsState(PortState* state) { return reinterpret_cast(state); } const auto* AsAttribute(const Attribute* attribute) { return reinterpret_cast(attribute); } 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; } AudioOut2::AudioOut2ContextHandle CreateContext(uint32_t queue_depth = 4, uint32_t max_object_ports = 0) { ContextParam param {}; param.max_object_ports = max_object_ports; param.queue_depth = queue_depth; param.num_grains = 512; AudioOut2::AudioOut2ContextHandle context = 0; Check(AudioOut2::AudioOut2ContextCreate(AsParam(¶m), nullptr, 0, &context) == OK, "context create failed"); return context; } AudioOut2::AudioOut2PortHandle CreatePort(AudioOut2::AudioOut2ContextHandle context, const PortParam& param) { AudioOut2::AudioOut2PortHandle port = 0; Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port) == OK, "port create failed"); return port; } 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 == 0x103, "user capabilities do not match implemented attributes"); 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; g_block_opens = true; } void WaitForDeviceOpens(int count) { std::unique_lock lock(g_device_mutex); g_device_cv.wait(lock, [count]() { return g_open_waiters >= count; }); } void ReleaseDeviceOpens() { std::lock_guard lock(g_device_mutex); g_block_opens = false; g_device_cv.notify_all(); } int LiveDeviceCount() { std::lock_guard lock(g_device_mutex); return static_cast(g_live_devices.size()); } void SetPcm(AudioOut2::AudioOut2PortHandle port, const void* data) { const Pcm pcm {data}; const Attribute attribute {0, 0, &pcm, sizeof(pcm)}; Check(AudioOut2::AudioOut2PortSetAttributes(port, AsAttribute(&attribute), 1) == OK, "setting PCM failed"); } template void SetAttribute(AudioOut2::AudioOut2PortHandle port, uint32_t id, const T& value) { const Attribute attribute {id, 0, &value, sizeof(value)}; Check(AudioOut2::AudioOut2PortSetAttributes(port, AsAttribute(&attribute), 1) == OK, "setting attribute failed"); } void ResetOutputCalls() { std::lock_guard lock(g_device_mutex); g_output_blocking.clear(); } std::vector OutputCalls() { std::lock_guard lock(g_device_mutex); return g_output_blocking; } void CaptureOutputPcm(size_t bytes) { std::lock_guard lock(g_device_mutex); g_capture_bytes = bytes; g_output_pcm.clear(); } std::vector> OutputPcm() { std::lock_guard lock(g_device_mutex); return g_output_pcm; } std::vector> PushPcm(AudioOut2::AudioOut2ContextHandle context, size_t bytes) { CaptureOutputPcm(bytes); Check(AudioOut2::AudioOut2ContextPush(context, 1) == OK, "PCM push failed"); auto output = OutputPcm(); CaptureOutputPcm(0); return output; } void CheckSamples(const std::vector& pcm, std::initializer_list frame, const char* message) { Check(!pcm.empty() && pcm.size() % (frame.size() * sizeof(float)) == 0, "incorrect PCM size"); for (size_t i = 0; i < pcm.size() / sizeof(float); i++) { float sample; std::memcpy(&sample, pcm.data() + i * sizeof(float), sizeof(float)); Check(std::abs(sample - *(frame.begin() + i % frame.size())) < 0.000001f, message); } } void TestSlotReuse() { const auto context = CreateContext(); const auto param = MakeParam(); for (int i = 0; i < 300; i++) { AudioOut2::AudioOut2PortHandle port = 0; Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port) == OK, "port slot was not reusable"); Check(port != 0, "port handle is zero"); AudioOut2::AudioOut2PortDestroy(port); } AudioOut2::AudioOut2ContextDestroy(context); } void TestFullTableRecovers() { const auto context = CreateContext(); const auto param = MakeParam(); std::vector ports; ports.reserve(256); for (int i = 0; i < 256; i++) { AudioOut2::AudioOut2PortHandle port = 0; Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port) == OK, "port table filled early"); ports.push_back(port); } AudioOut2::AudioOut2PortHandle overflow = 0; Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &overflow) != OK, "full port table accepted another port"); for (auto port: ports) { AudioOut2::AudioOut2PortDestroy(port); } AudioOut2::AudioOut2PortHandle port = 0; Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port) == OK, "port table did not recover"); AudioOut2::AudioOut2PortDestroy(port); AudioOut2::AudioOut2ContextDestroy(context); } void TestConcurrentCreates() { constexpr int thread_count = 8; const auto context = CreateContext(); const auto param = MakeParam(0x800); std::vector ports(thread_count); std::vector results(thread_count); std::vector threads; BlockDeviceOpens(); for (int i = 0; i < thread_count; i++) { threads.emplace_back([&, i]() { results[i] = AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &ports[i]); }); } WaitForDeviceOpens(thread_count); ReleaseDeviceOpens(); for (auto& thread: threads) { thread.join(); } for (int i = 0; i < thread_count; i++) { Check(results[i] == OK, "concurrent port create failed"); PortState state {}; AudioOut2::AudioOut2PortGetState(ports[i], AsState(&state)); Check(state.num_channels == 8, "concurrent create lost its reserved slot"); AudioOut2::AudioOut2PortDestroy(ports[i]); } Check(LiveDeviceCount() == 0, "concurrent create leaked a device"); AudioOut2::AudioOut2ContextDestroy(context); } void TestContextDestroyCancelsPendingCreate() { const auto context = CreateContext(); const auto param = MakeParam(); AudioOut2::AudioOut2PortHandle port = 0; int result = OK; BlockDeviceOpens(); std::thread creator( [&]() { result = AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port); }); WaitForDeviceOpens(1); AudioOut2::AudioOut2ContextDestroy(context); ReleaseDeviceOpens(); creator.join(); Check(result != OK, "destroyed context retained a pending port create"); Check(LiveDeviceCount() == 0, "cancelled port create leaked a device"); } void TestSynchronousDevicePushBypassesModelledQueue() { 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"); uint32_t pcm[512] {}; SetPcm(port, pcm); ResetOutputCalls(); Check(AudioOut2::AudioOut2ContextPush(context, 1) == OK, "first sync push failed"); Check(AudioOut2::AudioOut2ContextPush(context, 1) == OK, "device-paced sync push was blocked by modelled queue"); const auto calls = OutputCalls(); Check(calls.size() == 2, "sync pushes did not reach the device backend"); Check(calls[0] && calls[1], "sync pushes lost their blocking mode"); AudioOut2::AudioOut2PortDestroy(port); AudioOut2::AudioOut2ContextDestroy(context); } void TestFloat12ChannelPortOutputsPcm() { const auto context = CreateContext(); const auto param = MakeParam(0x0c00); AudioOut2::AudioOut2PortHandle port = 0; Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &port) == OK, "12-channel port create failed"); Check(LiveDeviceCount() == 1, "12-channel port did not open an audio device"); PortState state {}; Check(AudioOut2::AudioOut2PortGetState(port, AsState(&state)) == OK, "12-channel port state query failed"); Check(state.num_channels == 12, "12-channel port lost its guest channel count"); float pcm[512 * 12] {}; SetPcm(port, pcm); ResetOutputCalls(); Check(AudioOut2::AudioOut2ContextPush(context, 1) == OK, "12-channel PCM push failed"); const auto calls = OutputCalls(); Check(calls.size() == 1 && calls[0], "12-channel PCM did not reach the device backend"); AudioOut2::AudioOut2PortDestroy(port); Check(LiveDeviceCount() == 0, "12-channel port leaked its audio device"); AudioOut2::AudioOut2ContextDestroy(context); } void TestAsynchronousDevicePushKeepsQueueBounded() { 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"); uint32_t pcm[512] {}; SetPcm(port, pcm); ResetOutputCalls(); Check(AudioOut2::AudioOut2ContextPush(context, 0) == OK, "first async push failed"); Check(AudioOut2::AudioOut2ContextPush(context, 0) != OK, "full async queue accepted another buffer"); const auto calls = OutputCalls(); Check(calls.size() == 1 && !calls[0], "rejected async push reached the device backend"); uint32_t queued = 0; uint32_t available = 0; Check(AudioOut2::AudioOut2ContextGetQueueLevel(context, &queued, &available) == OK, "queue-level query failed"); Check(queued == 1 && available == 0, "async queue level does not match accepted pushes"); AudioOut2::AudioOut2PortDestroy(port); AudioOut2::AudioOut2ContextDestroy(context); } void TestHandleWithoutPcmDoesNotBypassQueue() { 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"); ResetOutputCalls(); Check(AudioOut2::AudioOut2ContextPush(context, 1) == OK, "empty sync push failed"); Check(AudioOut2::AudioOut2ContextPush(context, 0) != OK, "handle without PCM bypassed queue backpressure"); Check(OutputCalls().empty(), "empty push reached the device backend"); AudioOut2::AudioOut2PortDestroy(port); AudioOut2::AudioOut2ContextDestroy(context); } void TestPcmCopiedBeforeScratchBufferReuse() { const auto context = CreateContext(); const auto param = MakeParam(); AudioOut2::AudioOut2PortHandle first = 0; AudioOut2::AudioOut2PortHandle second = 0; Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &first) == OK, "first port create failed"); Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &second) == OK, "second port create failed"); std::vector scratch(512 * 2); std::vector first_pcm(scratch.size(), 0.25f); std::vector second_pcm(scratch.size(), -0.5f); std::copy(first_pcm.begin(), first_pcm.end(), scratch.begin()); SetPcm(first, scratch.data()); std::copy(second_pcm.begin(), second_pcm.end(), scratch.begin()); SetPcm(second, scratch.data()); std::fill(scratch.begin(), scratch.end(), 0.0f); const auto pcm_bytes = scratch.size() * sizeof(float); CaptureOutputPcm(pcm_bytes); Check(AudioOut2::AudioOut2ContextPush(context, 1) == OK, "shared-buffer push failed"); const auto output = OutputPcm(); Check(output.size() == 2, "shared-buffer push did not output both ports"); Check(std::memcmp(output[0].data(), first_pcm.data(), pcm_bytes) == 0, "first port lost PCM when scratch buffer was reused"); Check(std::memcmp(output[1].data(), second_pcm.data(), pcm_bytes) == 0, "second port lost PCM when scratch buffer was reused"); CaptureOutputPcm(0); AudioOut2::AudioOut2PortDestroy(first); AudioOut2::AudioOut2PortDestroy(second); AudioOut2::AudioOut2ContextDestroy(context); } void TestPortGainAndValidation() { const auto context = CreateContext(); const auto port = CreatePort(context, MakeParam()); std::vector pcm(512 * 2, 0.25f); SetPcm(port, pcm.data()); auto check_output = [&](const std::array& gain, float expected_pcm) { CheckSamples(PushPcm(context, pcm.size() * sizeof(float)).at(0), {expected_pcm}, "gain modified stored PCM"); std::lock_guard lock(g_device_mutex); Check(g_output_gains.size() == 1 && g_output_gains[0] == gain, "incorrect per-channel gain forwarded to backend"); }; check_output({1.0f, 1.0f}, 0.25f); for (const std::array gain: {std::array {2.0f, 0.5f}, {0.0f, 0.0f}, {1.0f, 1.0f}}) { SetAttribute(port, 1, gain); check_output(gain, 0.25f); check_output(gain, 0.25f); } const std::array gain {0.5f, 0.25f}; SetAttribute(port, 1, gain); std::fill(pcm.begin(), pcm.end(), 0.75f); SetPcm(port, pcm.data()); check_output(gain, 0.75f); std::fill(pcm.begin(), pcm.end(), -0.25f); const Pcm replacement {pcm.data()}; for (const float invalid: {-1.0f, std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN()}) { const std::array invalid_gain {1.0f, invalid}; const Attribute attributes[] {{0, 0, &replacement, sizeof(replacement)}, {1, 0, &invalid_gain, sizeof(invalid_gain)}}; Check(AudioOut2::AudioOut2PortSetAttributes(port, AsAttribute(attributes), 2) == static_cast(0x80268001u), "invalid gain was accepted"); check_output(gain, 0.75f); } for (const Attribute attribute: {Attribute {1, 0, nullptr, sizeof(gain)}, Attribute {1, 0, &gain, sizeof(float)}}) { Check(AudioOut2::AudioOut2PortSetAttributes(port, AsAttribute(&attribute), 1) == static_cast(0x80268001u), "malformed gain was accepted"); } const Attribute duplicates[] {{0, 0, &replacement, sizeof(replacement)}, {1, 0, &gain, sizeof(gain)}, {1, 0, &gain, sizeof(gain)}}; Check(AudioOut2::AudioOut2PortSetAttributes(port, AsAttribute(duplicates), 3) == static_cast(0x80268001u), "duplicate gain attributes were accepted"); check_output(gain, 0.75f); AudioOut2::AudioOut2ContextDestroy(context); } void TestObjectGainAndAmbisonics() { constexpr float half_power = 0.70710678f; struct Coefficients { uint32_t channel; float left; float right; }; constexpr Coefficients cases[] {{UINT32_MAX, half_power, half_power}, {0, 1.0f, 1.0f}, {1, 0.0f, 0.0f}, {2, half_power, -half_power}, {3, 0.0f, 0.0f}, {64, half_power, half_power}, {65, half_power, -half_power}, {66, 0.0f, 0.0f}, {99, 0.0f, 0.0f}}; for (const auto format: {0x100u, 0x101u}) { const auto context = CreateContext(1, 1); auto param = MakeParam(format); param.port_type = 0x100; const auto object = CreatePort(context, param); Check(LiveDeviceCount() == 1, "object-only context did not create one output device"); std::vector floating(512, 0.5f); std::vector integer(512, 16384); const void* pcm = format == 0x100 ? static_cast(floating.data()) : integer.data(); constexpr auto output_bytes = 512 * 2 * sizeof(float); SetPcm(object, pcm); std::fill(floating.begin(), floating.end(), 0.0f); std::fill(integer.begin(), integer.end(), 0); SetAttribute(object, 1, 0.0f); SetAttribute(object, 8, 65u); for (int push = 0; push < 2; push++) { const auto output = PushPcm(context, output_bytes); Check(output.size() == 1, "object-only push did not output exactly once"); CheckSamples(output[0], {0.5f * half_power, 0.5f * half_power}, "object PCM ownership, default gain, or attribute freeze failed"); } std::fill(floating.begin(), floating.end(), 0.5f); std::fill(integer.begin(), integer.end(), 16384); for (const auto& test: cases) { Check(AudioOut2::AudioOut2ContextAdvance(context) == OK, "context advance failed"); const Pcm input {pcm}; const float gain = 0.5f; const Attribute attributes[] {{0, 0, &input, sizeof(input)}, {1, 0, &gain, sizeof(gain)}, {8, 0, &test.channel, sizeof(test.channel)}}; Check(AudioOut2::AudioOut2PortSetAttributes(object, AsAttribute(attributes), 3) == OK, "object attributes could not be set atomically with PCM first"); for (int push = 0; push < 2; push++) { CheckSamples(PushPcm(context, output_bytes).at(0), {0.25f * test.left, 0.25f * test.right}, "object gain or ambisonics decoded incorrectly or accumulated across pushes"); } } AudioOut2::AudioOut2ContextAdvance(context); SetAttribute(object, 8, UINT32_MAX); SetAttribute(object, 1, 0.0f); SetPcm(object, pcm); CheckSamples(PushPcm(context, output_bytes).at(0), {0.0f}, "zero gain did not mute object"); AudioOut2::AudioOut2ContextAdvance(context); SetPcm(object, pcm); CheckSamples(PushPcm(context, output_bytes).at(0), {0.0f}, "object gain did not persist"); AudioOut2::AudioOut2ContextDestroy(context); Check(LiveDeviceCount() == 0, "object context leaked its output device"); } } void TestObjectBusRouting() { const auto context = CreateContext(1, 2); const auto other_context = CreateContext(1, 1); AudioOut2::AudioOut2UserHandle other_user = 0; Check(AudioOut2::AudioOut2UserCreate(2000, &other_user) == OK, "second user create failed"); const auto first = CreatePort(context, MakeParam()); const auto second = CreatePort(context, MakeParam()); std::vector bed(512 * 2, 0.125f); SetPcm(first, bed.data()); SetPcm(second, bed.data()); auto param = MakeParam(0x100); param.port_type = 0x100; std::vector object_pcm(512, 0.25f); SetPcm(CreatePort(context, param), object_pcm.data()); param.user_handle = other_user; SetPcm(CreatePort(context, param), object_pcm.data()); std::fill(object_pcm.begin(), object_pcm.end(), 1.0f); SetPcm(CreatePort(other_context, param), object_pcm.data()); constexpr auto bytes = 512 * 2 * sizeof(float); for (int push = 0; push < 2; push++) { const auto output = PushPcm(context, bytes); Check(output.size() == 3, "object bus was missing or queued more than once"); CheckSamples(output[0], {0.125f}, "objects modified first bed"); CheckSamples(output[1], {0.125f}, "objects modified second bed"); CheckSamples(output[2], {0.35355339f}, "MAIN objects failed to mix across users or leaked contexts"); } SetPcm(first, nullptr); SetPcm(second, nullptr); CheckSamples(PushPcm(context, bytes).at(0), {0.35355339f}, "object output depended on bed PCM"); AudioOut2::AudioOut2ContextDestroy(context); Check(LiveDeviceCount() == 1, "context destroy closed another context's object device"); CheckSamples(PushPcm(other_context, bytes).at(0), {0.70710678f}, "other context lost its object PCM"); AudioOut2::AudioOut2ContextDestroy(other_context); Check(AudioOut2::AudioOut2UserDestroy(other_user) == OK, "second user destroy failed"); Check(LiveDeviceCount() == 0, "object bus device leaked"); } } // namespace namespace Libs::Audio::AudioInternal { int AudioOutOpen(int type, uint32_t /*samples_num*/, uint32_t /*freq*/, Format /*format*/) { std::unique_lock lock(g_device_mutex); const int handle = g_next_device++; g_live_devices.push_back(handle); if (type != 10) { g_device_backed_handles.push_back(handle); } g_open_waiters++; g_device_cv.notify_all(); g_device_cv.wait(lock, []() { return !g_block_opens; }); return handle; } void AudioOutClose(int handle) { std::lock_guard lock(g_device_mutex); const auto it = std::find(g_live_devices.begin(), g_live_devices.end(), handle); if (it != g_live_devices.end()) { g_live_devices.erase(it); } const auto device_it = std::find(g_device_backed_handles.begin(), g_device_backed_handles.end(), handle); if (device_it != g_device_backed_handles.end()) { g_device_backed_handles.erase(device_it); } } bool AudioOutHasDevice(int handle) { std::lock_guard lock(g_device_mutex); return std::find(g_device_backed_handles.begin(), g_device_backed_handles.end(), handle) != g_device_backed_handles.end(); } uint32_t AudioOutOutputs(const OutputParam* params, uint32_t num, bool blocking) { std::lock_guard lock(g_device_mutex); g_output_blocking.push_back(blocking); g_output_gains.clear(); if (g_capture_bytes != 0) { for (uint32_t i = 0; i < num; i++) { const auto* bytes = static_cast(params[i].data); g_output_pcm.emplace_back(bytes, bytes + g_capture_bytes); g_output_gains.push_back(params[i].gains != nullptr ? std::array {params[i].gains[0], params[i].gains[1]} : std::array {1.0f, 1.0f}); } } return 0; } } // namespace Libs::Audio::AudioInternal namespace Libs::LibKernel { uint64_t KYTY_SYSV_ABI KernelGetProcessTime() { static std::atomic_uint64_t now {0}; return now.fetch_add(1000); } } // namespace Libs::LibKernel int main() { Check(AudioOut2::AudioOut2UserCreate(1000, &g_user_handle) == OK, "test user create failed"); TestUserSupportedAttributes(); TestSlotReuse(); TestFullTableRecovers(); TestConcurrentCreates(); TestContextDestroyCancelsPendingCreate(); TestSynchronousDevicePushBypassesModelledQueue(); TestFloat12ChannelPortOutputsPcm(); TestAsynchronousDevicePushKeepsQueueBounded(); TestHandleWithoutPcmDoesNotBypassQueue(); TestPcmCopiedBeforeScratchBufferReuse(); TestPortGainAndValidation(); TestObjectGainAndAmbisonics(); TestObjectBusRouting(); Check(AudioOut2::AudioOut2UserDestroy(g_user_handle) == OK, "test user destroy failed"); std::printf("AudioOut2PortTests: all cases passed\n"); return 0; }