From 05d9e3f22e4d5cf0ba5ffe524719f4c24b2da425 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 14 Oct 2026 18:42:46 +0200 Subject: [PATCH] Check SoundTouch source time against identifiable audio samples --- src/call/call_video_rate.c | 3 +- tools/chatgui/tests/test_soundtouch_clock.cpp | 86 +++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 tools/chatgui/tests/test_soundtouch_clock.cpp diff --git a/src/call/call_video_rate.c b/src/call/call_video_rate.c index 39e0604a..a43d4f7c 100644 --- a/src/call/call_video_rate.c +++ b/src/call/call_video_rate.c @@ -44,7 +44,8 @@ int call_video_rate_step(struct call_video_rate *rate, const struct call_video_r rate->next_change_us = s->now_us + interval; } /* Запас RTT относится к inflight, два кадра — к пакетному выходу encoder. */ - uint64_t normal = result.delivery_bps * ((uint64_t)s->baseline_rtt_us + 50000) / 8000000; + uint64_t bounded_delivery = result.delivery_bps > MAX_BITRATE ? MAX_BITRATE : result.delivery_bps; + uint64_t normal = bounded_delivery * ((uint64_t)s->baseline_rtt_us + 50000) / 8000000; normal += (uint64_t)s->applied_bps * s->frame_interval_us * 2 / 8000000; result.excess_bytes = s->debt_bytes > normal ? s->debt_bytes - normal : 0; if (!result.excess_bytes || !s->valid || s->locally_held || s->probing) rate->high = 0; diff --git a/tools/chatgui/tests/test_soundtouch_clock.cpp b/tools/chatgui/tests/test_soundtouch_clock.cpp new file mode 100644 index 00000000..dc4adbbd --- /dev/null +++ b/tools/chatgui/tests/test_soundtouch_clock.cpp @@ -0,0 +1,86 @@ +/* Проверяем позицию по самому сигналу: каждый четырёхсэмпловый фрагмент уникален. + * Сравниваем номинальный feed/tempo с фактическим потреблением входа SoundTouch. */ +#include "soundtouch/SoundTouch.h" +#include "../../../lib/debug_config.h" +#include +#include +#include +#include +#include +#include +#include + +#define CHECK(x) do { if (!(x)) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "clock test line=%d: %s", __LINE__, #x); exit(1); } } while (0) + +struct Span { double first, last, sourceFirst, sourceLast; }; +static double position(const std::vector& spans, size_t& index, double output) { + while (index + 1 < spans.size() && spans[index].last <= output) index++; + const auto& s = spans[index]; + return s.sourceFirst + (s.sourceLast - s.sourceFirst) * (output - s.first) / (s.last - s.first); +} + +static void check_clock(unsigned mode) { + constexpr unsigned frames = 3000, block = 960; + std::vector source(frames * block), output; + std::unordered_multimap locations; + uint32_t seed = 123456789; + for (unsigned i = 0; i < source.size(); i++) { + seed ^= seed << 13; seed ^= seed >> 17; seed ^= seed << 5; + source[i] = (int32_t)(seed & 0xffffff) / 16777216.0f - 0.5f; + uint32_t bits; std::memcpy(&bits, &source[i], sizeof(bits)); locations.emplace(bits, i); + } + soundtouch::SoundTouch st; + st.setSampleRate(48000); st.setChannels(1); + st.setSetting(SETTING_USE_QUICKSEEK, 1); st.setSetting(SETTING_SEQUENCE_MS, 20); + st.setSetting(SETTING_SEEKWINDOW_MS, 10); st.setSetting(SETTING_OVERLAP_MS, 4); + st.setSetting(SETTING_USE_AA_FILTER, 0); + std::vector nominal, consumed; + double expected = 0, mappedEnd = 0; + unsigned produced = 0; + for (unsigned i = 0; i < frames; i++) { + double triangle = std::abs((int)(i % 100) - 50) / 50.0; + double tempo = mode == 0 ? 1.6 : mode == 1 ? 1.0 + 0.6 * triangle : (i % 2 ? 1.6 : 1.0); + st.setTempo(tempo); + nominal.push_back({expected, expected + block / tempo, (double)i * block, (double)(i + 1) * block}); + expected += block / tempo; + unsigned before = st.numSamples(); + st.putSamples(source.data() + i * block, block); + unsigned added = st.numSamples() - before; + if (added) { + // Середина seek-window компенсирует стартовый сдвиг WSOLA; звук не изменяем. + double end = (i + 1) * block - st.numUnprocessedSamples() + 240.0; + consumed.push_back({(double)produced, (double)produced + added, mappedEnd, end}); + mappedEnd = end; produced += added; + } + float pcm[480]; + for (;;) { + unsigned count = st.receiveSamples(pcm, 480); + if (!count) break; + output.insert(output.end(), pcm, pcm + count); + } + } + size_t nominalIndex = 0, consumedIndex = 0; + unsigned matches = 0; + double maxNominal = 0, maxConsumed = 0; + for (unsigned i = 0; i + 4 < output.size(); i += 97) { + uint32_t bits; std::memcpy(&bits, &output[i], sizeof(bits)); + auto found = locations.equal_range(bits); + for (auto it = found.first; it != found.second; ++it) { + unsigned at = it->second; + if (at + 4 > source.size() || std::memcmp(&source[at], &output[i], 4 * sizeof(float))) continue; + maxNominal = std::max(maxNominal, std::abs(position(nominal, nominalIndex, i) - at) / 48.0); + maxConsumed = std::max(maxConsumed, std::abs(position(consumed, consumedIndex, i) - at) / 48.0); + matches++; break; + } + } + DEBUG_INFO(DEBUG_CATEGORY_CALL, "SoundTouch clock mode=%u matches=%u nominal_error_ms=%.3f consumed_error_ms=%.3f", + mode, matches, maxNominal, maxConsumed); + CHECK(matches > 10000); + CHECK(maxConsumed < 20.0); +} + +int main() { + debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); + check_clock(0); check_clock(1); check_clock(2); + return 0; +}