diff --git a/tools/chatgui/src/sound_manager.cpp b/tools/chatgui/src/sound_manager.cpp index 36d8ec82..c2eedcb8 100644 --- a/tools/chatgui/src/sound_manager.cpp +++ b/tools/chatgui/src/sound_manager.cpp @@ -164,7 +164,8 @@ unsigned SoundManager::renderOutput(ma_engine* engine, const std::arraymix(destination, count, firstTimeUs + (int64_t)offset * 1000000 / 48000, measured); + for (auto* voice : voices) if (voice) + voice->mix(destination, count, firstTimeUs + (int64_t)offset * 1000000 / 48000, measured, frames); for (unsigned i = 0; i < count * 2; ++i) { float value = destination[i]; destination[i] = std::isfinite(value) ? std::clamp(value, -1.0f, 1.0f) : 0.0f; diff --git a/tools/chatgui/src/voice_audio_io.cpp b/tools/chatgui/src/voice_audio_io.cpp index e15ea6bb..bcb16f73 100644 --- a/tools/chatgui/src/voice_audio_io.cpp +++ b/tools/chatgui/src/voice_audio_io.cpp @@ -177,6 +177,7 @@ void VoiceAudioIo::playbackLoop() { unsigned missing = 0; VoicePlaybackBuffer buffering; PlaybackProgress lastProgress{}; + int64_t lastProgressUs = 0; call_clock_snapshot clock{}; if (m_kind == Kind::Call && call_audio_get_clock(m_session, voice_time_us(), &clock) != 0) DEBUG_WARN(DEBUG_CATEGORY_CALL, "voice-io: call clock unavailable session=%llu", (unsigned long long)m_session); @@ -210,6 +211,7 @@ void VoiceAudioIo::playbackLoop() { queueUs.add(int64_t(progress.queuedFrames) * 1000000 / 48000); missing += progress.missing; lastProgress = progress; + lastProgressUs = voice_time_us(); if (!progress.consumed) continue; int64_t now = voice_time_us(), remaining = progress.endTimeUs - now; if (m_kind == Kind::Call) @@ -259,7 +261,7 @@ void VoiceAudioIo::playbackLoop() { int64_t queuedUs = (position - lastProgress.position) * 1000000 / 48000; std::lock_guard lock(m_latencyMutex); m_outputLatency = {int(queuedUs / 1000), int((queuedUs + std::max(lastProgress.endTimeUs - now, int64_t(0))) / 1000), - lastProgress.measured, now}; + lastProgress.measured, lastProgressUs}; } } } diff --git a/tools/chatgui/tests/test_voice_audio_io.cpp b/tools/chatgui/tests/test_voice_audio_io.cpp index d140ade7..5fbc3c32 100644 --- a/tools/chatgui/tests/test_voice_audio_io.cpp +++ b/tools/chatgui/tests/test_voice_audio_io.cpp @@ -1,6 +1,7 @@ /* Настоящие workers/AEC; граница сервисного PCM заменена наблюдателями без сети/устройства. */ #include "../src/voice_audio_io.h" #include "../src/voice_capture_clock.h" +#include "../src/voice_playback_buffer.h" extern "C" { #include "call/call_audio.h" #include "radio/radio_audio.h" @@ -74,6 +75,18 @@ static void reset() { } int main() { debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); + // Резерв уменьшается только при стабильной работе; крупный callback и starvation повышают минимум. + VoicePlaybackBuffer buffering; + buffering.sample(1, 512, false); + buffering.sample(10000000, 512, false); REQUIRE(buffering.blocks == 2); + buffering.sample(10000001, 512, false); REQUIRE(buffering.blocks == 1); + buffering.sample(10020001, 512, true); REQUIRE(buffering.blocks == 2); + buffering.sample(10020002, 512, true); REQUIRE(buffering.blocks == 2); + buffering.sample(10520001, 512, true); REQUIRE(buffering.blocks == 3); + buffering.sample(20520001, 2048, false); REQUIRE(buffering.blocks == 3); + buffering.sample(30520001, 512, false); REQUIRE(buffering.blocks == 2); + buffering.sample(40520001, 512, false); REQUIRE(buffering.blocks == 1); + buffering.reset(); REQUIRE(buffering.blocks == 2); // Десять минут +/-300ppm дрейфа: связь с физическим временем сохраняется, шага >2мс нет. for (double drift : {-0.0003, 0.0003}) { VoiceCaptureClock clock; @@ -106,9 +119,12 @@ int main() { voice.mix(partial, 240, now_us() + 500000); await([] { return output_position.load() == 240; }); REQUIRE(output_delay > 4500); + auto latency = voice.outputLatency(); + REQUIRE(latency.outputMs >= latency.queueMs + 450); voice.mix(partial, 720, now_us()); await([] { return output_position.load() == 960; }); voice.stop(); + REQUIRE(voice.outputLatency().outputMs == -1); voice.start(VoiceAudioIo::Kind::Radio, 7); REQUIRE(voice.startCapture(1, false)); voice.capture(pcm.data(), 240, 0); voice.setPtt(true); voice.capture(pcm.data(), 360, 0); voice.setPtt(false); voice.capture(pcm.data(), 360, 0);