#pragma once #include "audio_event_queue.h" #include #include #include #include #include /* Desktop: аппаратные callbacks только копируют PCM, render также выполняет готовое смешивание. * Capture worker — единственный владелец AEC и порядка PCM/PTT/mute. RX worker независим от VAD. * Управляющий поток закрывает capture и отсоединяет output до stop/destruction. * Финальный stereo render приходит с того же выходного устройства после всего микширования. */ class VoiceAudioIo { public: enum class Kind { Call, Radio }; VoiceAudioIo() = default; ~VoiceAudioIo(); bool start(Kind kind, uint64_t session); void stop(); bool startCapture(int channels, bool aecEnabled); void stopCapture(); bool captureRunning() const { return m_capturing.load(); } void setPtt(bool pressed); void setMuted(bool muted); void setAecEnabled(bool enabled); void resetPlayback(); /* firstTimeUs — монотонное время первого физического PCM-фрейма (backend либо оценка). */ void capture(const int16_t* pcm, unsigned frames, int64_t firstTimeUs, uint64_t route = 0, bool missing = false); void reference(const float* pcm, unsigned frames, int64_t firstTimeUs); void mix(float* stereo, unsigned frames); private: enum class EventKind { Pcm, Ptt, Mute, Aec, Stop }; struct Event { EventKind kind = EventKind::Pcm; unsigned frames = 0; int64_t time = 0; int64_t enqueued = 0; uint64_t sequence = 0; uint64_t route = 0; bool value = false; int16_t pcm[1920]{}; }; struct Playback { uint64_t epoch = 0; int16_t pcm[1920]{}; }; AudioEventQueue m_captureEvents, m_references; AudioEventQueue m_playback; std::atomic m_running{false}, m_capturing{false}; std::atomic m_playEpoch{0}, m_captureDropped{0}, m_referenceDropped{0}, m_playbackUnderruns{0}; uint64_t m_captureSequence = 0; // Только capture callback; reset после его остановки. Kind m_kind = Kind::Call; uint64_t m_session = 0; int m_channels = 1; std::atomic m_aecRequested{false}; std::thread m_captureThread, m_playbackThread; std::mutex m_waitMutex; std::condition_variable m_wake; Playback m_playCurrent{}; unsigned m_playPosition = 960; void command(EventKind kind, bool value = false); void captureLoop(); void playbackLoop(); };