diff --git a/src/libs/libAudio2.cpp b/src/libs/libAudio2.cpp index a1cdd02a1..92703efb0 100644 --- a/src/libs/libAudio2.cpp +++ b/src/libs/libAudio2.cpp @@ -262,6 +262,11 @@ static AudioOut2Submission& audioout2_submission(const AudioOut2ContextState& co return g_audioout2_submissions[&context - g_audioout2_contexts.data()]; } +// Ticks of a sixth of a microsecond: one 48 kHz sample lasts exactly 125 of them. +static uint64_t audioout2_clock() { + return LibKernel::KernelGetProcessTime() * 6; +} + 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); @@ -331,25 +336,23 @@ static void audioout2_update_context_locked(AudioOut2ContextState* state) { return; } - const auto now = LibKernel::KernelGetProcessTime(); + const auto now = audioout2_clock(); 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) { + if (now <= state->last_update) { return; } - const auto elapsed = now - state->last_update; - const auto drained = std::min(state->queued, elapsed / grain_micros); + const auto grain = uint64_t {state->num_grains} * 125; + const auto drained = std::min(state->queued, (now - state->last_update) / grain); if (drained == 0) { return; } state->queued -= static_cast(drained); - state->last_update += drained * grain_micros; + state->last_update += drained * grain; if (state->queued == 0) { state->last_update = now; } @@ -527,7 +530,7 @@ int KYTY_SYSV_ABI AudioOut2ContextCreate(const AudioOut2ContextParam* params, vo state->handle = *ctx; state->queue_depth = params->queue_depth == 0 ? 4u : params->queue_depth; state->num_grains = num_grains; - state->last_update = LibKernel::KernelGetProcessTime(); + state->last_update = audioout2_clock(); state->object_audio_handle = object_audio_handle; g_audioout2_state_mutex.Unlock(); @@ -615,7 +618,7 @@ int KYTY_SYSV_ABI AudioOut2ContextPush(AudioOut2ContextHandle ctx, uint32_t bloc 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(); + state->last_update = audioout2_clock(); } if (state->queued < state->queue_depth) { state->queued++; diff --git a/tests/AudioOut2PortTests.cpp b/tests/AudioOut2PortTests.cpp index cc345bdb0..9c4bc0d6a 100644 --- a/tests/AudioOut2PortTests.cpp +++ b/tests/AudioOut2PortTests.cpp @@ -35,6 +35,8 @@ int g_open_waiters = 0; bool g_block_opens = false; bool g_block_outputs = false; std::vector g_blocked_output; +std::atomic_uint64_t g_process_time {0}; +std::atomic_bool g_process_time_frozen {false}; void Check(bool value, const char* text) { if (!value) { @@ -465,6 +467,27 @@ void TestPushConsumesPcm() { AudioOut2::AudioOut2ContextDestroy(context); } +void TestQueueDrainsAtExactGrainRate() { + // Three 512-sample grains play in exactly 32000 us. + const auto context = CreateContext(); + g_process_time_frozen = true; + const auto start = g_process_time.load(); + for (int push = 0; push < 3; push++) { + Check(AudioOut2::AudioOut2ContextPush(context, 0) == OK, "async push failed"); + } + const auto queued_after = [&](uint64_t micros) { + g_process_time = start + micros; + uint32_t queued = 0; + Check(AudioOut2::AudioOut2ContextGetQueueLevel(context, &queued, nullptr) == OK, + "queue-level query failed"); + return queued; + }; + Check(queued_after(31999) == 1, "the third grain drained before it finished playing"); + Check(queued_after(32000) == 0, "three grains did not drain in their playing time"); + g_process_time_frozen = false; + AudioOut2::AudioOut2ContextDestroy(context); +} + void TestPcmCopiedBeforeScratchBufferReuse() { const auto context = CreateContext(); const auto param = MakeParam(); @@ -746,8 +769,7 @@ uint32_t AudioOutOutputs(const OutputParam* params, uint32_t num, bool blocking) namespace Libs::LibKernel { uint64_t KYTY_SYSV_ABI KernelGetProcessTime() { - static std::atomic_uint64_t now {0}; - return now.fetch_add(1000); + return g_process_time_frozen ? g_process_time.load() : g_process_time.fetch_add(1000); } } // namespace Libs::LibKernel @@ -764,6 +786,7 @@ int main() { TestAsynchronousDevicePushKeepsQueueBounded(); TestHandleWithoutPcmDoesNotBypassQueue(); TestPushConsumesPcm(); + TestQueueDrainsAtExactGrainRate(); TestPcmCopiedBeforeScratchBufferReuse(); TestPortGainAndValidation(); TestObjectGainAndAmbisonics();