#pragma once #include "miniaudio.h" #include #include #include /* Один владелец в GUI-потоке. Callback публикует только прогресс и уведомления; * close() дожидается worker перед освобождением callback и его состояния. */ class AudioDevice { public: using Callback = std::function; AudioDevice(); ~AudioDevice(); AudioDevice(const AudioDevice&) = delete; AudioDevice& operator=(const AudioDevice&) = delete; bool init(ma_context* context, ma_device_config config, const char* role, uint64_t session, Callback callback); bool start(); void pause(); // GUI: завершает текущий callback, сохраняя backend stream и его физический route. void close(); bool needsRecovery(); bool retryReady() const; void retryLater(); void retryNow(); bool initialized() const { return m_device != nullptr; } ma_device* device() const { return m_device; } int64_t callbackTimeUs() const { return m_callbackTime.load(std::memory_order_relaxed); } bool timingMeasured() const { return m_timingMeasured.load(std::memory_order_relaxed); } uint64_t route() const { return m_route.load(std::memory_order_relaxed); } bool captureMissing() const { return m_captureMissing.load(std::memory_order_relaxed); } private: static void data(ma_device* device, void* out, const void* in, ma_uint32 frames); static void notification(const ma_device_notification* event); ma_device* m_device = nullptr; Callback m_callback; const char* m_role = "audio"; QElapsedTimer m_clock; std::atomic m_frames{0}; std::atomic m_callbackTime{0}; std::atomic m_timingMeasured{false}; std::atomic m_route{0}; std::atomic m_captureMissing{false}; uint64_t m_reportedRoute = 0; int m_reportedTiming = -1; std::atomic m_suspended{false}; uint64_t m_seenFrames = 0; qint64 m_progressAt = 0, m_retryAt = 0; int m_retryDelay = 250; };