#pragma once #include #include #include #include #include #include "audio_device.h" #include "voice_audio_io.h" struct ma_device; /* Desktop-контур звонка: GUI владеет capture device и жизненным циклом. * Hardware callback копирует PCM в ограниченную очередь VoiceAudioIo. * Capture worker выполняет AEC и упорядочивает PCM/PTT/mute; независимый RX worker * получает готовый голос из сервисного слоя. SoundManager смешивает его со всеми * звуками приложения на том же выходе и возвращает итоговый stereo reference. * Stop: close capture → join capture worker → detach output → join RX → release core. */ class CallAudioEngine : public QObject { Q_OBJECT public: static CallAudioEngine* instance(); /* Запустить полнодуплекс для звонка (после ACCEPTED). 0 = успех. */ int start(quint64 callId); /* По CALL_ENDED: тон завершения (~450мс), затем авто-teardown. */ void beginEnd(quint64 callId); /* Немедленный teardown (device + освобождение call_audio). */ void stop(); bool isActive() const { return m_active.load(); } quint64 callId() const { return m_callId.load(); } /* Mute: пир слышит тишину (отправляем нулевые кадры вместо захваченного PCM). */ void setMuted(bool muted); bool isMuted() const { return m_muted.load(); } /* Backend ID (пустой = default). Смена пересоздаёт только соответствующее устройство. */ void setPlaybackDevice(const QString& id); QString playbackDevice() const { return m_playbackId; } void setCaptureDevice(const QString& id); /* Hardware capture callback: без DSP и растущих буферов. */ void dataCallback(const void* in, void* out, unsigned int frames); private: CallAudioEngine(); bool openCapture(); void recoverDevices(); void pauseDevices(); std::atomic m_active{false}; std::atomic m_callId{0}; std::atomic m_muted{false}; QString m_playbackId; /* только GUI-поток */ QString m_captureId; AudioDevice m_capture; VoiceAudioIo m_voice; bool m_outputAttached = false; QTimer m_recoveryTimer; uint64_t m_generation = 0; bool m_ending = false; };