From 8e3ec4cb84c4e47dad05d5066395415d151d12b0 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 13 Oct 2026 22:04:16 +0200 Subject: [PATCH] Test adaptive playback against a delayed audio device queue --- src/stream/stream_playback_clock.c | 4 +- src/stream/stream_playback_clock.h | 1 + tests/test_stream_playback.c | 77 ++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 tests/test_stream_playback.c diff --git a/src/stream/stream_playback_clock.c b/src/stream/stream_playback_clock.c index 8b2d51e3..ae468c35 100644 --- a/src/stream/stream_playback_clock.c +++ b/src/stream/stream_playback_clock.c @@ -80,7 +80,9 @@ int stream_playback_clock_update(struct stream_playback_clock *c, int64_t now, i int increase = allowance >= 50000 ? 100 : (int)(allowance * 100 / 50000); target += increase; } - int step = (int)(elapsed > STREAM_PLAYBACK_RAMP_US ? 100 : elapsed * 100 / STREAM_PLAYBACK_RAMP_US); + c->ramp_remainder += (elapsed > STREAM_PLAYBACK_RAMP_US ? STREAM_PLAYBACK_RAMP_US : elapsed) * 100; + int step = (int)(c->ramp_remainder / STREAM_PLAYBACK_RAMP_US); + c->ramp_remainder %= STREAM_PLAYBACK_RAMP_US; if (target > c->tempo_milli) c->tempo_milli += target - c->tempo_milli < step ? target - c->tempo_milli : step; else c->tempo_milli -= c->tempo_milli - target < step ? c->tempo_milli - target : step; if (c->waiting) c->tempo_milli = 1000; diff --git a/src/stream/stream_playback_clock.h b/src/stream/stream_playback_clock.h index 21741678..a0f568ca 100644 --- a/src/stream/stream_playback_clock.h +++ b/src/stream/stream_playback_clock.h @@ -24,6 +24,7 @@ struct stream_playback_clock { struct stream_playback_bucket history[STREAM_PLAYBACK_HISTORY]; uint64_t id, pauses; int64_t first_us, last_us, last_position_us, correction_us, last_bad_us, pause_us, report_us; + int64_t ramp_remainder; int tempo_milli, initialized, waiting; }; diff --git a/tests/test_stream_playback.c b/tests/test_stream_playback.c new file mode 100644 index 00000000..980af604 --- /dev/null +++ b/tests/test_stream_playback.c @@ -0,0 +1,77 @@ +/* Виртуальное устройство сохраняет уже поставленные темпы: проверка догона и истории без реального времени. */ +#include +#include + +#include "../src/stream/stream_playback_clock.h" +#include "../lib/debug_config.h" + +#define CHECK(x) do { if (!(x)) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "playback test line=%d: %s", __LINE__, #x); exit(1); } } while (0) + +static void test_history_correction(void) { + struct stream_playback_clock clock; struct stream_playback_state state; + stream_playback_clock_reset(&clock, 1); + CHECK(!stream_playback_clock_update(&clock, 0, 0, 120000, 20000, 0, 0, &state)); + CHECK(!stream_playback_clock_update(&clock, 1000000, 1000000, 1300000, 20000, 0, 0, &state)); + CHECK(state.minimum_us == 120000 && state.correction_us == 0); + CHECK(!stream_playback_clock_update(&clock, 1080000, 1000000, 1300000, 20000, 0, 1, &state)); + CHECK(state.minimum_us == 200000 && state.correction_us == 80000 && state.waiting); + CHECK(!stream_playback_clock_update(&clock, 1090000, 1010000, 1310000, 20000, 0, 0, &state)); + CHECK(!state.waiting && state.tempo_milli == 1000); + CHECK(!stream_playback_clock_update(&clock, 2090000, 2110000, 2410000, 20000, 0, 0, &state)); + CHECK(state.minimum_us == 100000 && state.correction_us == -20000); + DEBUG_INFO(DEBUG_CATEGORY_STREAM, "[PASS] history: pause shifts all minima +80ms, actual x1.1 shifts -100ms"); +} + +static void test_catchup_device_queue(void) { + struct stream_playback_clock clock; struct stream_playback_state state; + stream_playback_clock_reset(&clock, 2); + int queued[100]; for (unsigned i = 0; i < 100; i++) queued[i] = 1000; + int64_t position = 0; + int accelerated = 0, maximum = 1000; + for (int64_t tick = 0; tick <= 120000; tick++) { + unsigned slot = (unsigned)(tick % 100); + if (tick) position += queued[slot]; + int64_t pending_gain = 0; + for (unsigned i = 0; i < 100; i++) pending_gain += queued[i] - 1000; + CHECK(!stream_playback_clock_update(&clock, tick * 1000, position, tick * 1000 + 300000, + 30000, pending_gain, 0, &state)); + CHECK(state.tempo_milli >= 1000 && state.tempo_milli <= 1100); + if (tick < 60000) CHECK(state.tempo_milli == 1000); + if (state.tempo_milli > 1000) accelerated = 1; + if (state.tempo_milli > maximum) maximum = state.tempo_milli; + CHECK(state.minimum_us >= 29000); + queued[slot] = state.tempo_milli; + } + CHECK(accelerated && maximum == 1100 && state.history_ready); + CHECK(state.reserve_us >= 29000 && state.reserve_us < 33000 && state.tempo_milli == 1000); + /* Короткая остановка, попавшая между секундными отсчётами, блокирует догон на полную минуту. */ + int64_t now = 120000000; + CHECK(!stream_playback_clock_update(&clock, now + 10000, position, position, 30000, 0, 1, &state)); + CHECK(!stream_playback_clock_update(&clock, now + 20000, position, position + 500000, 30000, 0, 0, &state)); + for (int64_t tick = 1; tick < 60000; tick++) { + CHECK(!stream_playback_clock_update(&clock, now + 20000 + tick * 1000, position + tick * 1000, + position + tick * 1000 + 500000, 30000, 0, 0, &state)); + CHECK(state.tempo_milli == 1000); + } + DEBUG_INFO(DEBUG_CATEGORY_STREAM, "[PASS] catchup: healthy minute, smooth x1.1, queued audio, safe residual and pause cooldown"); +} + +static void test_invalid_and_sampling_gap(void) { + struct stream_playback_clock clock; struct stream_playback_state state, before; + stream_playback_clock_reset(&clock, 3); memset(&state, 0xa5, sizeof(state)); before = state; + CHECK(stream_playback_clock_update(&clock, -1, 0, 0, 0, 0, 0, &state) < 0 && !memcmp(&state, &before, sizeof(state))); + CHECK(!stream_playback_clock_update(&clock, 0, 0, 200000, 20000, 0, 0, &state)); + CHECK(!stream_playback_clock_update(&clock, 2000000, 2000000, 2200000, 20000, 0, 0, &state)); + CHECK(!state.stable_us && state.tempo_milli == 1000 && !state.history_ready); + before = state; + CHECK(stream_playback_clock_update(&clock, 2000001, 1000000, 2200000, 20000, 0, 0, &state) < 0); + CHECK(!memcmp(&state, &before, sizeof(state))); + DEBUG_INFO(DEBUG_CATEGORY_STREAM, "[PASS] clock: invalid/backward positions preserve result, missed samples reset healthy minute"); +} + +int main(void) { + debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); + debug_set_category_level(DEBUG_CATEGORY_STREAM, DEBUG_LEVEL_INFO); + test_history_correction(); test_catchup_device_queue(); test_invalid_and_sampling_gap(); + return 0; +}