#include "call_audio_engine.h" #include "sound_manager.h" #include "../transport/gui_bridge.h" #include "../transport/audio_diagnostics.h" extern "C" { #include "call/call_audio.h" #include "chat/chat_setting.h" #include "debug_config.h" } #include CallAudioEngine* CallAudioEngine::instance() { static auto* engine = new CallAudioEngine(); return engine; } CallAudioEngine::CallAudioEngine() { m_playbackId = SoundManager::instance()->playbackDevice(); m_captureId = SoundManager::instance()->captureDevice(); connect(SoundManager::instance(), &SoundManager::devicesChanged, this, [this]() { setPlaybackDevice(SoundManager::instance()->playbackDevice()); setCaptureDevice(SoundManager::instance()->captureDevice()); }); m_recoveryTimer.setInterval(200); connect(&m_recoveryTimer, &QTimer::timeout, this, &CallAudioEngine::recoverDevices); connect(SoundManager::instance(), &SoundManager::contextAboutToReset, this, &CallAudioEngine::pauseDevices); connect(SoundManager::instance(), &SoundManager::contextRestored, this, [this]() { m_capture.retryNow(); if (m_active) recoverDevices(); }); } int CallAudioEngine::start(quint64 callId) { if (m_active && m_callId == callId && !m_ending) return 0; stop(); if (!gui_bridge_get_inst() || call_audio_start_prepared(callId) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "CallAudio: cannot start call=%016llx", (unsigned long long)callId); return -1; } ++m_generation; m_callId = callId; if (!m_voice.start(VoiceAudioIo::Kind::Call, callId)) { call_audio_release(callId); m_callId = 0; return -1; } m_muted = false; m_ending = false; m_active = true; m_capture.retryNow(); recoverDevices(); m_recoveryTimer.start(); DEBUG_INFO(DEBUG_CATEGORY_CALL, "CallAudio: session started call=%016llx", (unsigned long long)callId); return 0; } bool CallAudioEngine::openCapture() { auto* context = SoundManager::instance()->context(); if (!context) return false; ma_device_config config = ma_device_config_init(ma_device_type_capture); config.capture.format = ma_format_s16; config.capture.channels = 1; config.sampleRate = CALL_AUDIO_SAMPLE_RATE; ma_device_id id; bool selected = SoundManager::deviceId(m_captureId, &id); if (selected) config.capture.pDeviceID = &id; auto callback = [this](const void* in, void*, unsigned frames) { dataCallback(in, nullptr, frames); }; auto attempt = [&]() { if (!m_capture.init(context, config, "call-capture", m_callId.load(), callback)) return false; bool aec = chat_setting_get_int(gui_bridge_get_inst(), "aec_enabled", 1) != 0; if (!m_voice.startCapture(1, aec)) { m_capture.close(); return false; } m_voice.setMuted(m_muted.load()); if (m_capture.start()) return true; m_voice.stopCapture(); call_audio_reset_io(m_callId.load(), 0); return false; }; if (attempt()) return true; if (!selected) return false; DEBUG_WARN(DEBUG_CATEGORY_CALL, "CallAudio: selected input unavailable, trying default"); config.capture.pDeviceID = nullptr; return attempt(); } void CallAudioEngine::recoverDevices() { if (!m_active) return; auto* manager = SoundManager::instance(); if (!m_outputAttached && manager->context()) m_outputAttached = manager->attachVoice(&m_voice, m_playbackId); 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(); } if (!m_ending && m_capture.retryReady()) openCapture(); if (!m_ending) m_voice.setAecEnabled(chat_setting_get_int(gui_bridge_get_inst(), "aec_enabled", 1) != 0); } void CallAudioEngine::pauseDevices() { m_capture.close(); m_voice.stopCapture(); SoundManager::instance()->detachVoice(&m_voice); m_outputAttached = false; m_voice.resetPlayback(); } void CallAudioEngine::setMuted(bool muted) { if (m_muted.exchange(muted) == muted) return; m_voice.setMuted(muted); DEBUG_INFO(DEBUG_CATEGORY_CALL, "CallAudio: mute=%d", muted); } void CallAudioEngine::setPlaybackDevice(const QString& id) { if (id == m_playbackId) return; m_playbackId = id; SoundManager::instance()->detachVoice(&m_voice); m_outputAttached = false; if (m_active) recoverDevices(); } void CallAudioEngine::setCaptureDevice(const QString& id) { if (id == m_captureId) return; m_captureId = id; m_capture.close(); m_voice.stopCapture(); m_capture.retryNow(); 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(); call_audio_begin_end(callId); uint64_t generation = m_generation; QTimer::singleShot(500, this, [this, generation]() { if (m_generation == generation) stop(); }); } void CallAudioEngine::stop() { ++m_generation; m_recoveryTimer.stop(); m_active = false; pauseDevices(); m_voice.stop(); uint64_t id = m_callId.exchange(0); if (id) call_audio_release(id); m_muted = false; m_ending = false; if (id) DEBUG_INFO(DEBUG_CATEGORY_CALL, "CallAudio: session stopped call=%016llx", (unsigned long long)id); } void CallAudioEngine::dataCallback(const void* in, void*, unsigned int frames) { if (in) m_voice.capture(static_cast(in), frames, m_capture.callbackTimeUs(), m_capture.route(), m_capture.captureMissing()); }