diff --git a/doc/call_audio_graphs.txt b/doc/call_audio_graphs.txt index e459d756..b0e11308 100644 --- a/doc/call_audio_graphs.txt +++ b/doc/call_audio_graphs.txt @@ -46,6 +46,8 @@ diagnostic_wire_tx_bytes увеличивается только при прин его очередь; это не подтверждение фактического выхода в эфир или доставки пиру. Байт/с = разность счётчиков / время за последние <=1с. Переключение peer/epoch, падение счётчика и пробел измерений >150мс не образуют скачок скорости. +Скорость вычисляется при записи измерения и хранится вместе с ним. При сдвиге +графика старые значения не пересчитываются: левый край не теряет свою предысторию. Снимки и ресурсы --------------- diff --git a/src/call/call_history_store.h b/src/call/call_history_store.h index 1dc1be1a..7d022615 100644 --- a/src/call/call_history_store.h +++ b/src/call/call_history_store.h @@ -30,7 +30,8 @@ public: p.rendered_samples += samples; p.missing_samples += std::min(samples, missing); b.bins[std::min(CALL_HISTORY_BINS - 1, std::max(0, depth) / CALL_HISTORY_BIN_MS)] += samples; } - // Все значения принадлежат одному снимку uasync; никаких чтений живых очередей из UI. + // Все значения принадлежат одному снимку uasync. Скорость фиксируется здесь: уход старых + // точек из видимого окна не должен менять секундное среднее уже записанных измерений. void transmit(const call_history_tx& p) { unsigned index = (txNext + CALL_HISTORY_POINTS - 1) % CALL_HISTORY_POINTS; if (txUsed && p.at_ms < tx[index].at_ms) { @@ -41,6 +42,17 @@ public: txUsed = std::min(txUsed + 1, (unsigned)CALL_HISTORY_POINTS); } tx[index] = p; + auto& current = tx[index]; current.wire_bytes_per_sec = -1; + unsigned first = index; + for (unsigned i = 1; i < txUsed && current.peer_id; ++i) { + unsigned previous = (first + CALL_HISTORY_POINTS - 1) % CALL_HISTORY_POINTS; + if (tx[previous].peer_id != current.peer_id || tx[previous].tx_epoch != current.tx_epoch + || current.at_ms - tx[previous].at_ms > 1000 || tx[first].at_ms - tx[previous].at_ms > 150 + || tx[first].wire_tx_bytes < tx[previous].wire_tx_bytes) break; + first = previous; + } + if (first != index && current.at_ms > tx[first].at_ms) + current.wire_bytes_per_sec = (current.wire_tx_bytes - tx[first].wire_tx_bytes) * 1000.0 / (current.at_ms - tx[first].at_ms); } // out — независимая копия. Пустые/старые участки не интерполируются и не становятся нулевыми измерениями. void snapshot(uint64_t now, call_audio_history& out) const { @@ -59,14 +71,5 @@ public: const auto& p = tx[(txNext + CALL_HISTORY_POINTS - txUsed + i) % CALL_HISTORY_POINTS]; if (p.at_ms <= now && now - p.at_ms < CALL_HISTORY_MS) out.tx[out.tx_count++] = p; } - for (unsigned i = 0; i < out.tx_count; ++i) { - auto& p = out.tx[i]; unsigned first = i; p.wire_bytes_per_sec = -1; - while (first && p.peer_id && out.tx[first - 1].peer_id == p.peer_id && out.tx[first - 1].tx_epoch == p.tx_epoch - && p.at_ms - out.tx[first - 1].at_ms <= 1000 && out.tx[first].at_ms - out.tx[first - 1].at_ms <= 150) - --first; - const auto& previous = out.tx[first]; - if (first < i && p.at_ms > previous.at_ms && p.wire_tx_bytes >= previous.wire_tx_bytes) - p.wire_bytes_per_sec = (p.wire_tx_bytes - previous.wire_tx_bytes) * 1000.0 / (p.at_ms - previous.at_ms); - } } }; diff --git a/tools/chatgui/tests/test_call_jitter.cpp b/tools/chatgui/tests/test_call_jitter.cpp index abfb12e7..b294906f 100644 --- a/tools/chatgui/tests/test_call_jitter.cpp +++ b/tools/chatgui/tests/test_call_jitter.cpp @@ -229,6 +229,22 @@ static void test_audio_history() { CHECK(snapshot.tx[22].wire_bytes_per_sec == 2000); traffic.transmit({2400, 0, 0, 0, 43, 8, 900200}); traffic.snapshot(2400, snapshot); CHECK(snapshot.tx[23].wire_bytes_per_sec == -1); // Пропущенные измерения не интерполируются. + traffic.transmit({2450, 0, 0, 0, 43, 8, 10}); traffic.snapshot(2450, snapshot); + CHECK(snapshot.tx[24].wire_bytes_per_sec == -1); + traffic.transmit({2500, 0, 0, 0, 43, 8, 110}); traffic.snapshot(2500, snapshot); + CHECK(snapshot.tx[25].wire_bytes_per_sec == 2000); // После сброса счётчика начинаем новый интервал. + CallHistoryStore scrolling; + uint64_t sent = 0; + for (unsigned i = 0; i <= 700; ++i) { + if (i % 7 == 0) sent += 700; + scrolling.transmit({i * 50, 0, 0, 0, 42, 7, sent}); + } + scrolling.snapshot(35000, snapshot); + const auto retained = snapshot.tx[40]; // Точка с полным секундным средним, после оборота кольца. + CHECK(retained.wire_bytes_per_sec > 0); + scrolling.snapshot(retained.at_ms + CALL_HISTORY_MS - 1, snapshot); + CHECK(snapshot.tx[0].at_ms == retained.at_ms); + CHECK(snapshot.tx[0].wire_bytes_per_sec == retained.wire_bytes_per_sec); // Левый край не переписывает прошлое. puts("audio history: time weights, startup exclusion, gaps, overflow bin and ring expiry PASS"); }