diff --git a/doc/service_lifecycle.md b/doc/service_lifecycle.md index 83a6159f..f2605ef7 100644 --- a/doc/service_lifecycle.md +++ b/doc/service_lifecycle.md @@ -175,6 +175,20 @@ RX продолжает работать, рация снимает PTT, авт Сброс контекста и уничтожение владельца дожидаются worker до освобождения miniaudio-контекста. Штатное закрытие исправного устройства сохраняет синхронный порядок. +`AudioDevice` подстраивает буфер по подтверждённым backend XRUN: WASAPI DATA_DISCONTINUITY +на входе, ALSA EPIPE, PulseAudio capture hole/overflow и playback underflow. Notification +только увеличивает атомарный счётчик, решение и лог выполняются в GUI recovery timer. +Исходные параметры, включая Linux capture period=10мс, сохраняются до повторных сбоев. +Первая секунда после start исключена; нужны ошибки в двух опросах за 5с. Период увеличивается +на 5мс, не чаще раза в 5с, до общей запрошенной ёмкости 100мс с учётом числа периодов. +Фактический размер согласует backend; исходный или уже достигнутый больший размер не уменьшается. +При достижении предела повторные сбои логируются без новых перезапусков. Подобранный запрос +сохраняется при recovery, сбрасывается при смене устройства/сессии; стабильный звук не вызывает reopen. +Подстройка микрофона закрывает устройство асинхронно, останавливая TX worker и сохраняя RX. +Общая capture-сессия отсоединяет callbacks до close и заново подготавливает потребителей после init. +Новая подписка во время close откладывается, stop удаляет её без ожидания backend. +Первое переоткрытие после подстройки не использует backoff; при ошибке открытия действует обычный retry. + В desktop-видеозвонке Windows/Linux камера живёт в отдельном процессе того же `vibechat --call-camera-worker`. Служебный вход выполняется до конфига, БД и single-instance lock. Перечисление устройств, Qt/V4L2, преобразование и освобождение diff --git a/tools/chatgui/src/audio_device.cpp b/tools/chatgui/src/audio_device.cpp index d124f362..97d91777 100644 --- a/tools/chatgui/src/audio_device.cpp +++ b/tools/chatgui/src/audio_device.cpp @@ -215,7 +215,7 @@ bool AudioDevice::needsRecovery() { bool AudioDevice::retryReady() const { return !m_device && !closing() && m_clock.elapsed() >= m_retryAt; } void AudioDevice::retryLater() { - if (m_resizePending) { m_retryAt = m_clock.elapsed(); m_retryDelay = 250; return; } + if (m_resizePending) { m_resizePending = false; m_retryAt = m_clock.elapsed(); m_retryDelay = 250; return; } m_retryAt = m_clock.elapsed() + m_retryDelay; DEBUG_WARN(DEBUG_CATEGORY_CALL, "audio: %s retry in %dms", m_role, m_retryDelay); m_retryDelay = std::min(m_retryDelay * 2, 2000); diff --git a/tools/chatgui/tests/test_audio_recovery.cpp b/tools/chatgui/tests/test_audio_recovery.cpp index 8f6b4679..45603785 100644 --- a/tools/chatgui/tests/test_audio_recovery.cpp +++ b/tools/chatgui/tests/test_audio_recovery.cpp @@ -345,6 +345,9 @@ static void buffer_tuning_policy() { tuning.begin(now + 100, 33, 3); now += 5000; REQUIRE(tuning.observe(now, 1) == AudioBufferTuning::Unchanged); REQUIRE(tuning.observe(now + 200, 1) == AudioBufferTuning::Limit && tuning.requestedMs == 33); + tuning = {}; tuning.baseMs = 20; tuning.begin(0, 10, 3); // Backend может округлить исходный запрос вниз. + REQUIRE(tuning.observe(1100, 1) == AudioBufferTuning::Unchanged); + REQUIRE(tuning.observe(1300, 1) == AudioBufferTuning::Increase && tuning.requestedMs == 25); ma_context context{}; context.backend = ma_backend_pulseaudio; auto config = ma_device_config_init(ma_device_type_capture); config.periodSizeInMilliseconds = 20; AudioDevice::requestLowLatency(&context, config);