You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

102 lines
4.5 KiB

#pragma once
#include <QObject>
#include <atomic>
#include <cstdint>
#include <vector>
#include "audio_device.h"
#include "voice_audio_io.h"
#include <QTimer>
#include <QMap>
#include <array>
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<QString, int>& 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<int, 9>& values, bool apply);
std::array<int, 9> 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<bool> m_active{false};
std::atomic<bool> m_talking{false};
std::atomic<uint64_t> 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;
};