#include "call_audio_engine.h" #include "sound_manager.h" #include "../transport/gui_bridge.h" #include "miniaudio.h" extern "C" { #include "call/call_audio.h" #include "../../lib/debug_config.h" #include "../../lib/u_async.h" } #include #include static CallAudioEngine* g_callAudioEngine = nullptr; static void callAudioDataCallback(ma_device* dev, void* out, const void* in, ma_uint32 frames) { CallAudioEngine* e = (CallAudioEngine*)dev->pUserData; if (e) e->dataCallback(in, out, frames); } CallAudioEngine* CallAudioEngine::instance() { if (!g_callAudioEngine) g_callAudioEngine = new CallAudioEngine(); return g_callAudioEngine; } int CallAudioEngine::start(quint64 callId) { if (m_active) { if (m_callId == callId) return 0; stop(); } m_callId = callId; struct UTUN_INSTANCE* inst = gui_bridge_get_inst(); if (!inst) { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "CallAudio: no instance, cannot start"); return -1; } if (call_audio_init(inst) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "CallAudio: call_audio_init failed"); return -1; } if (call_audio_start(callId) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "CallAudio: call_audio_start failed call=%016llx", (unsigned long long)callId); return -1; } m_captureBuf.assign(CALL_AUDIO_FRAME_SAMPLES, 0); m_capturePos = 0; m_muted = false; if (initDevice() != 0) { call_audio_release(callId); return -1; } m_active = true; DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "CallAudio: started call=%016llx", (unsigned long long)callId); return 0; } int CallAudioEngine::initDevice() { ma_context* ctx = SoundManager::instance()->context(); if (!ctx) { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "CallAudio: no audio context"); return -1; } m_device = new ma_device; std::memset(m_device, 0, sizeof(*m_device)); ma_device_config config = ma_device_config_init(ma_device_type_duplex); config.capture.format = ma_format_s16; config.capture.channels = 1; config.playback.format = ma_format_s16; config.playback.channels = 1; config.sampleRate = CALL_AUDIO_SAMPLE_RATE; config.periodSizeInFrames = 480; config.dataCallback = callAudioDataCallback; config.pUserData = this; if (m_playbackDeviceIndex >= 0) { ma_device_info* pPlaybackDevices = nullptr; ma_uint32 playbackCount = 0; if (ma_context_get_devices(ctx, &pPlaybackDevices, &playbackCount, nullptr, nullptr) == MA_SUCCESS && (ma_uint32)m_playbackDeviceIndex < playbackCount) { config.playback.pDeviceID = &pPlaybackDevices[m_playbackDeviceIndex].id; DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "CallAudio: using playback device[%d]: %s", m_playbackDeviceIndex, pPlaybackDevices[m_playbackDeviceIndex].name); } else { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "CallAudio: playback device[%d] not found, using default", m_playbackDeviceIndex); } } if (ma_device_init(ctx, &config, m_device) != MA_SUCCESS) { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "CallAudio: device init failed, trying default"); config.playback.pDeviceID = nullptr; if (ma_device_init(ctx, &config, m_device) != MA_SUCCESS) { DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "CallAudio: default device init also failed"); delete m_device; m_device = nullptr; return -1; } } if (ma_device_start(m_device) != MA_SUCCESS) { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "CallAudio: device start failed"); ma_device_uninit(m_device); delete m_device; m_device = nullptr; return -1; } m_deviceStarted = true; m_capturePos = 0; return 0; } void CallAudioEngine::setMuted(bool muted) { if (m_muted.load() == muted) return; m_muted = muted; DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "CallAudio: mute %s", muted ? "on" : "off"); } void CallAudioEngine::setPlaybackDeviceIndex(int index) { if (index == m_playbackDeviceIndex) return; int prev = m_playbackDeviceIndex; m_playbackDeviceIndex = index; if (m_active) { DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "CallAudio: switch playback device %d -> %d", prev, index); teardownDevice(); if (initDevice() != 0) DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "CallAudio: switch playback device failed (kept index=%d)", index); } } void CallAudioEngine::beginEnd(quint64 callId) { if (!m_active || m_callId != callId) return; call_audio_begin_end(callId); /* старт тона завершения */ QTimer::singleShot(500, this, [this, callId]() { if (m_callId == callId) stop(); }); } void CallAudioEngine::stop() { if (!m_active && !m_device && !m_deviceStarted) return; m_active = false; m_muted = false; quint64 cid = m_callId.load(); teardownDevice(); call_audio_release(cid); m_callId = 0; DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "CallAudio: stopped"); } void CallAudioEngine::teardownDevice() { if (m_deviceStarted) { ma_device_stop(m_device); ma_device_uninit(m_device); m_deviceStarted = false; } if (m_device) { delete m_device; m_device = nullptr; } } void CallAudioEngine::dataCallback(const void* in, void* out, unsigned int frames) { const int16_t* in16 = (const int16_t*)in; int16_t* out16 = (int16_t*)out; /* диагностика (DEBUG_CATEGORY_DEBUG): фактические параметры miniaudio-коллбэка */ if (m_dbgFrames == 0) { m_dbgFrames = frames; DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "CallAudio: callback frames=%u in=%s out=%s", frames, in16 ? "yes" : "no", out16 ? "yes" : "no"); } m_dbgCbCount++; { uint64_t now = get_time_tb(); if (m_dbgLastTb == 0) m_dbgLastTb = now; else if (now - m_dbgLastTb >= 10000) { DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "CallAudio: cb=%u/s frames=%u", m_dbgCbCount, frames); m_dbgCbCount = 0; m_dbgLastTb = now; } } /* захват: копим 960 samples → encode+send (сервисный слой). mute → тишина. */ if (in16) { bool muted = m_muted.load(); for (unsigned int i = 0; i < frames; i++) { m_captureBuf[m_capturePos++] = muted ? 0 : in16[i]; if (m_capturePos >= (int)m_captureBuf.size()) { call_audio_feed_pcm(m_callId.load(), m_captureBuf.data(), (int)m_captureBuf.size()); m_capturePos = 0; } } } /* воспроизведение: финальный PCM из сервисного слоя (jitter+stretch+тоны) */ int n = call_audio_pull_pcm(m_callId.load(), out16, (int)frames); if (n < (int)frames) std::memset(out16 + n, 0, (size_t)(frames - n) * sizeof(int16_t)); }