#include "audio_device.h" #include "../transport/audio_diagnostics.h" #include "debug_config.h" #include #include AudioDevice::AudioDevice() { m_clock.start(); } AudioDevice::~AudioDevice() { close(); } bool AudioDevice::init(ma_context* context, ma_device_config config, const char* role, uint64_t session, Callback callback) { close(); m_role = role; m_callback = std::move(callback); if (!context) { retryLater(); return false; } auto* device = new ma_device{}; config.dataCallback = data; config.notificationCallback = notification; config.pUserData = this; ma_result result = ma_device_init(context, &config, device); if (result != MA_SUCCESS) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "audio: %s init failed result=%d", role, result); delete device; retryLater(); return false; } m_device = device; m_frames = 0; m_seenFrames = 0; m_suspended = false; audio_diag_label(device, session, role); DEBUG_INFO(DEBUG_CATEGORY_CALL, "audio: %s initialized backend=%s rate=%u capture='%s' playback='%s' period=%u/%u", role, ma_get_backend_name(context->backend), device->sampleRate, device->capture.name, device->playback.name, device->capture.internalPeriodSizeInFrames, device->playback.internalPeriodSizeInFrames); return true; } bool AudioDevice::start() { if (!m_device) return false; m_progressAt = m_clock.elapsed(); ma_result result = ma_device_start(m_device); if (result != MA_SUCCESS) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "audio: %s start failed result=%d", m_role, result); close(); retryLater(); return false; } DEBUG_INFO(DEBUG_CATEGORY_CALL, "audio: %s started", m_role); return true; } void AudioDevice::close() { if (m_device) { ma_device_uninit(m_device); /* uninit включает stop/join, в том числе после неудачного start. */ delete m_device; m_device = nullptr; DEBUG_INFO(DEBUG_CATEGORY_CALL, "audio: %s closed", m_role); } m_callback = {}; } void AudioDevice::data(ma_device* device, void* out, const void* in, ma_uint32 frames) { auto* self = static_cast(device->pUserData); if (self->m_callback) self->m_callback(in, out, frames); self->m_frames.fetch_add(frames, std::memory_order_relaxed); } void AudioDevice::notification(const ma_device_notification* event) { auto* self = static_cast(event->pDevice->pUserData); if (event->type == ma_device_notification_type_interruption_began) self->m_suspended = true; if (event->type == ma_device_notification_type_interruption_ended) self->m_suspended = false; } bool AudioDevice::needsRecovery() { if (!m_device) return false; uint64_t frames = m_frames.load(std::memory_order_relaxed); qint64 now = m_clock.elapsed(); if (frames != m_seenFrames || m_suspended.load()) { m_seenFrames = frames; m_progressAt = now; if (frames) m_retryDelay = 250; } bool stopped = ma_device_get_state(m_device) == ma_device_state_stopped; if (!stopped && now - m_progressAt < 1000) return false; DEBUG_WARN(DEBUG_CATEGORY_CALL, "audio: %s recovery reason=%s idle=%lldms frames=%llu suspended=%d", m_role, stopped ? "stopped" : "no-progress", (long long)(now - m_progressAt), (unsigned long long)frames, m_suspended.load()); return true; } bool AudioDevice::retryReady() const { return !m_device && m_clock.elapsed() >= m_retryAt; } void AudioDevice::retryLater() { m_retryAt = m_clock.elapsed() + m_retryDelay; DEBUG_WARN(DEBUG_CATEGORY_CALL, "audio: %s retry in %dms", m_role, m_retryDelay); m_retryDelay = std::min(m_retryDelay * 2, 2000); } void AudioDevice::retryNow() { m_retryAt = 0; m_retryDelay = 250; }