#pragma once #include #include #include #include #include "audio_device.h" #include "voice_audio_io.h" #include #include #include 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 RadioAudioEngine : public QObject { Q_OBJECT public: static RadioAudioEngine* instance(); /* Включить прослушивание канала (после toggle рации). 0 = успех. */ int start(quint64 groupId); /* Выключить прослушивание канала (toggle рации выключен). */ void stop(); /* PTT: зажата / отпущена (только когда радио канала включено). */ void talkBegin(); void talkEnd(); void setHotkeyTalking(bool pressed); // независимый владелец PTT — глобальная клавиатура /* Настройки читаются/применяются в uasync; после подтверждения GUI перезапускает только capture. */ void loadSettings(const QString& configPath); bool applySettings(const QMap& values); int setting(const QString& name) const; bool settingsReady() const { return m_settingsReady; } bool settingsBusy() const { return m_settingsBusy; } bool autoEnabled() const { return m_settings[0] != 0; } int autoMode() const { return m_settings[6]; } float inputLevel() const { return m_inputLevel; } bool inputLevelValid() const { return m_inputLevelValid; } float vadProbability() const { return m_vadProbability; } bool vadValid() const { return m_vadValid; } float vadGain() const { return m_vadGain; } bool vadCompressorEnabled() const { return m_vadCompressorEnabled; } bool autoBlocked() const { return m_autoBlocked; } bool autoAvailable() const { return m_autoAvailable; } bool captureAvailable() const { return m_captureAvailable; } bool isTransmitting() const { return m_transmitting; } QString settingsError() const { return m_settingsError; } bool isTalking() const { return m_talking.load(); } bool isActive() const { return m_active.load(); } quint64 groupId() const { return m_groupId.load(); } /* Hardware capture callback: без DSP и растущих буферов. */ void captureCallback(const void* in, unsigned int frames); signals: void talkingChanged(bool talking); // GUI-поток, суммарное состояние кнопки и хоткея void inputLevelChanged(); // снимок измерителя, не чаще 10Гц void stateChanged(); // фактический TX, доступность capture/VAD и настройки; GUI-поток private: RadioAudioEngine(); void updateTalking(); bool m_buttonTalking = false, m_hotkeyTalking = false; // GUI-поток void pauseDevices(); void recoverDevices(); void openCapture(); void updateState(); void reconfigureCapture(); void postSettings(const std::array& values, bool apply); std::array m_settings{0, 50, 200, 1, 15, 15, 1, -35, -45}; QString m_configPath; QString m_settingsError; bool m_settingsReady = false, m_settingsBusy = false; bool m_transmitting = false, m_autoAvailable = false, m_captureAvailable = false; float m_inputLevel = -120; bool m_inputLevelValid = false, m_autoBlocked = false; float m_vadProbability = 0, m_vadGain = 0; bool m_vadValid = false, m_vadCompressorEnabled = false; std::atomic m_active{false}; std::atomic m_talking{false}; std::atomic m_groupId{0}; AudioDevice m_capture; VoiceAudioIo m_voice; bool m_outputAttached = false; QTimer m_recoveryTimer; bool m_wantStereo = false; QString m_captureId, m_playbackId; int m_captureChannels = 1; };