diff --git a/tools/chatgui/src/call_audio_engine.cpp b/tools/chatgui/src/call_audio_engine.cpp index 9f374940..8e4ca381 100644 --- a/tools/chatgui/src/call_audio_engine.cpp +++ b/tools/chatgui/src/call_audio_engine.cpp @@ -8,6 +8,7 @@ extern "C" { #include "debug_config.h" } #include +#include CallAudioEngine* CallAudioEngine::instance() { static auto* engine = new CallAudioEngine(); @@ -32,7 +33,9 @@ CallAudioEngine::CallAudioEngine() { int CallAudioEngine::start(quint64 callId) { if (m_active && m_callId == callId && !m_ending) return 0; + if (m_capture.closing()) { DEBUG_WARN(DEBUG_CATEGORY_CALL, "CallAudio: previous microphone is still closing"); return -1; } stop(); + m_captureError.clear(); emit captureErrorChanged(); if (!m_microphone.acquire()) { DEBUG_WARN(DEBUG_CATEGORY_CALL, "CallAudio: microphone owned by another operation"); return -1; } @@ -82,9 +85,20 @@ bool CallAudioEngine::openCapture() { } void CallAudioEngine::recoverDevices() { - if (!m_active) return; + if (!m_active) { + if (!m_capture.closing()) { m_microphone.release(); m_recoveryTimer.stop(); } + return; + } auto* manager = SoundManager::instance(); if (!m_outputAttached && manager->context()) m_outputAttached = manager->attachVoice(&m_voice, m_playbackId); + if (!m_ending && m_captureError.isEmpty() && m_capture.captureTimedOut()) { + DEBUG_ERROR(DEBUG_CATEGORY_CALL, "CallAudio: microphone has no PCM for 1000ms call=%016llx; disabling capture", + (unsigned long long)m_callId.load()); + m_captureError = QCoreApplication::translate("ChatGui", "The microphone is not delivering audio. Select another microphone or restart."); + m_capture.closeAsync(); m_voice.stopCapture(); call_audio_reset_io(m_callId.load(), 0); + emit captureErrorChanged(); + } + if (!m_captureError.isEmpty() || m_capture.closing()) return; if (!m_ending && ((m_capture.initialized() && !m_voice.captureRunning()) || m_capture.needsRecovery())) { m_capture.close(); m_voice.stopCapture(); call_audio_reset_io(m_callId.load(), 0); m_capture.retryLater(); @@ -115,14 +129,15 @@ void CallAudioEngine::setPlaybackDevice(const QString& id) { void CallAudioEngine::setCaptureDevice(const QString& id) { if (id == m_captureId) return; m_captureId = id; - m_capture.close(); m_voice.stopCapture(); m_capture.retryNow(); + m_capture.closeAsync(); m_voice.stopCapture(); m_capture.retryNow(); + m_captureError.clear(); emit captureErrorChanged(); if (m_active) { call_audio_reset_io(m_callId.load(), 0); recoverDevices(); } } void CallAudioEngine::beginEnd(quint64 callId) { if (!m_active || m_callId != callId || m_ending) return; m_ending = true; - m_capture.close(); m_voice.stopCapture(); + m_capture.closeAsync(); m_voice.stopCapture(); call_audio_begin_end(callId); uint64_t generation = m_generation; QTimer::singleShot(500, this, [this, generation]() { if (m_generation == generation) stop(); }); @@ -130,10 +145,12 @@ void CallAudioEngine::beginEnd(quint64 callId) { void CallAudioEngine::stop() { ++m_generation; - m_recoveryTimer.stop(); m_active = false; - pauseDevices(); m_voice.stop(); - m_microphone.release(); + m_capture.closeAsync(); m_voice.stopCapture(); + SoundManager::instance()->detachVoice(&m_voice); m_outputAttached = false; + m_voice.stop(); + if (m_capture.closing()) m_recoveryTimer.start(); + else { m_microphone.release(); m_recoveryTimer.stop(); } uint64_t id = m_callId.exchange(0); if (id) call_audio_release(id); m_muted = false; diff --git a/tools/chatgui/src/call_audio_engine.h b/tools/chatgui/src/call_audio_engine.h index 07030fea..019806b4 100644 --- a/tools/chatgui/src/call_audio_engine.h +++ b/tools/chatgui/src/call_audio_engine.h @@ -34,6 +34,7 @@ public: bool isActive() const { return m_active.load(); } quint64 callId() const { return m_callId.load(); } VoiceAudioIo::OutputLatency outputLatency() const { return m_voice.outputLatency(); } + QString captureError() const { return m_captureError; } /* Mute: пир слышит тишину (отправляем нулевые кадры вместо захваченного PCM). */ void setMuted(bool muted); @@ -47,6 +48,9 @@ public: /* Hardware capture callback: без DSP и растущих буферов. */ void dataCallback(const void* in, void* out, unsigned int frames); +signals: + void captureErrorChanged(); + private: CallAudioEngine(); bool openCapture(); @@ -60,6 +64,7 @@ private: QString m_playbackId; /* только GUI-поток */ QString m_captureId; + QString m_captureError; AudioDevice m_capture; AudioCaptureLease m_microphone; VoiceAudioIo m_voice;