From a978bb7b06208960f52e28d1317372dddd4af16a Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 14 Oct 2026 19:36:51 +0200 Subject: [PATCH] Exercise call clocks through Opus and jitter recovery --- tools/chatgui/tests/test_call_jitter.cpp | 53 ++++++++++++++++++++++ tools/chatgui/tests/test_call_timeline.cpp | 25 +++++++++- 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/tools/chatgui/tests/test_call_jitter.cpp b/tools/chatgui/tests/test_call_jitter.cpp index fc8f89ae..208c51f2 100644 --- a/tools/chatgui/tests/test_call_jitter.cpp +++ b/tools/chatgui/tests/test_call_jitter.cpp @@ -28,6 +28,58 @@ static int decode_bad(void* arg, const uint8_t*, int, int16_t*, int) { static void push(call_jitter* j, int id, int64_t received_ms = -1) { call_jitter_push(j, (const uint8_t*)&id, sizeof(id), (uint32_t)(id * 20), received_ms < 0 ? id * 20 : received_ms, nullptr); } + +static void test_audio_clock() { + const uint64_t id = 987; + CHECK(call_audio_start(id) == 0); + auto* encoder = opus_codec_encoder_create(48000, 1); + CHECK(encoder); + int delay = 0; CHECK(opus_codec_encoder_delay(encoder, &delay) == 0); + int16_t voice[960], output[480]; uint8_t packet[512]; + int sent = 0, valid = 0; + uint64_t written = 0; + int64_t previous = -1; + call_clock_snapshot clock{}; + CHECK(call_audio_get_clock(id, 0, &clock) == 0 && !clock.valid); + uint64_t epoch = clock.epoch; + // Начальный burst заставляет настоящий jitter менять темп; PTS не зависит от send_ts. + for (int tick = 0; tick < 4000; tick++) { + int count = tick == 0 ? 30 : (tick % 2 == 0 ? 1 : 0); + for (int i = 0; i < count; i++, sent++) { + for (int s = 0; s < 960; s++) voice[s] = (int16_t)(8000 * std::sin((sent * 960ll + s) * 0.09)); + int len = opus_codec_encode(encoder, voice, 960, packet, sizeof(packet)); CHECK(len > 0); + call_capture_time timing{1000000 + sent * 20000ll, 9, (uint32_t)delay}; + call_audio_on_media(nullptr, id, (uint16_t)sent, 0, packet, len, &timing); + } + CHECK(call_audio_pull_pcm(id, output, 480) == 480); written += 480; + int64_t now = tick * 10000ll; + call_audio_playback_progress(id, epoch, written, now, now, 1); + CHECK(call_audio_get_clock(id, now, &clock) == 0 && clock.written_samples == written); + if (clock.valid) { + valid++; + CHECK(clock.source_epoch == 9 && clock.pts_us >= previous); + CHECK(clock.pts_us <= 1000000 + sent * 20000ll); + CHECK(clock.pts_us > 1000000 + sent * 20000ll - 700000); + previous = clock.pts_us; + } + } + CHECK(valid > 3900); + // Underrun и местные тоны продолжают device-position, но удерживают source-PTS. + int64_t held = -1; + for (int tick = 4000; tick < 4300; tick++) { + CHECK(call_audio_pull_pcm(id, output, 480) == 480); written += 480; + call_audio_playback_progress(id, epoch, written, tick * 10000ll, tick * 10000ll, 1); + CHECK(call_audio_get_clock(id, tick * 10000ll, &clock) == 0 && clock.valid); + if (tick == 4200) held = clock.pts_us; + if (tick > 4200) CHECK(clock.pts_us == held); + } + call_audio_reset_io(id, 1); + CHECK(call_audio_get_clock(id, 43000000, &clock) == 0 && clock.epoch != epoch && !clock.valid); + call_audio_playback_progress(id, epoch, written, 43000000, 43000000, 1); + CHECK(call_audio_get_clock(id, 43000000, &clock) == 0 && !clock.valid); + call_audio_stop(); opus_codec_encoder_destroy(encoder); + std::printf("audio clock: real Opus, burst/tempo, device progress, local tones and reset checked\n"); +} int main() { { call_jitter_window w; @@ -58,6 +110,7 @@ int main() { } debug_set_level(DEBUG_LEVEL_WARN); debug_enable_console(1); + test_audio_clock(); int16_t pcm[480]; int depth, tempo; uint32_t dropped, under; diff --git a/tools/chatgui/tests/test_call_timeline.cpp b/tools/chatgui/tests/test_call_timeline.cpp index 684d27f5..ad960e9d 100644 --- a/tools/chatgui/tests/test_call_timeline.cpp +++ b/tools/chatgui/tests/test_call_timeline.cpp @@ -83,8 +83,31 @@ static void test_long_run() { DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS timeline: 200s actual SoundTouch, tempo jumps, bounded rings, monotonic clock"); } +static void test_failure_and_recovery() { + CallTimeline map; + auto start = [&] { + map.reset(5); map.feed(1000000, 960, 1); + map.source({SourceKind::Copy, SourceError::None, 0, 0, 0, 960}); map.read(960); + map.progress(5, 480, 10000, 10000, true); + CHECK(map.clock(10000).valid); + }; + start(); map.progress(5, 240, 20000, 20000, true); + CHECK(!map.clock(20000).valid); + map.progress(5, 960, 30000, 30000, true); CHECK(!map.clock(30000).valid); + start(); map.progress(5, 961, 20000, 20000, true); CHECK(!map.clock(20000).valid); + start(); map.read(1); CHECK(!map.clock(10000).valid); + start(); // Переполнение карты без device-progress не портит звук и не даёт ложный PTS. + for (unsigned i = 1; i < 2200; i++) { + map.feed(1000000 + i * 20000ll, 960, 1); + map.source({SourceKind::Copy, SourceError::None, i * 960, i * 960, 0, 960}); map.read(960); + } + CHECK(!map.clock(10000).valid); + start(); + DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS timeline: regressing/impossible device head, overread, bounded overflow, reset recovery"); +} + int main() { debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); - test_boundaries(); test_mix(); test_long_run(); + test_boundaries(); test_mix(); test_long_run(); test_failure_and_recovery(); return 0; }