Сохрани накопленные исправления аудио, async и диагностики
This commit is contained in:
1 parent
b1e3193899
commit
bdcf8b5634
59 files changed
+4611
-1441
No files matched your search
@@ -1,232 +1,164 @@
|
||||
#include "call_audio_engine.h"
|
||||
#include "sound_manager.h"
|
||||
#include "../transport/gui_bridge.h"
|
||||
|
||||
#include "miniaudio.h"
|
||||
|
||||
#include "../transport/audio_diagnostics.h"
|
||||
extern "C" {
|
||||
#include "call/call_audio.h"
|
||||
#include "../../lib/debug_config.h"
|
||||
#include "../../lib/u_async.h"
|
||||
#include "debug_config.h"
|
||||
}
|
||||
|
||||
#include <QTimer>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
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;
|
||||
static auto* engine = new CallAudioEngine();
|
||||
return engine;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "CallAudio: device backend=%s rate=%u capture='%s' rate=%u period=%u playback='%s' rate=%u period=%u",
|
||||
ma_get_backend_name(ctx->backend), m_device->sampleRate,
|
||||
m_device->capture.name, m_device->capture.internalSampleRate, m_device->capture.internalPeriodSizeInFrames,
|
||||
m_device->playback.name, m_device->playback.internalSampleRate, m_device->playback.internalPeriodSizeInFrames);
|
||||
m_capturePos = 0;
|
||||
m_dbg = {};
|
||||
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;
|
||||
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();
|
||||
CallAudioEngine::CallAudioEngine() {
|
||||
m_playbackId = SoundManager::instance()->playbackDevice();
|
||||
m_captureId = SoundManager::instance()->captureDevice();
|
||||
connect(SoundManager::instance(), &SoundManager::devicesChanged, this, [this]() {
|
||||
setPlaybackDevice(SoundManager::instance()->playbackDevice());
|
||||
setCaptureDevice(SoundManager::instance()->captureDevice());
|
||||
});
|
||||
m_recoveryTimer.setInterval(200);
|
||||
connect(&m_recoveryTimer, &QTimer::timeout, this, &CallAudioEngine::recoverDevices);
|
||||
connect(SoundManager::instance(), &SoundManager::contextAboutToReset, this, &CallAudioEngine::pauseDevices);
|
||||
connect(SoundManager::instance(), &SoundManager::contextRestored, this, [this]() {
|
||||
m_capture.retryNow(); m_playback.retryNow();
|
||||
if (m_active) recoverDevices();
|
||||
});
|
||||
}
|
||||
|
||||
void CallAudioEngine::stop() {
|
||||
if (!m_active && !m_device && !m_deviceStarted) return;
|
||||
m_active = false;
|
||||
int CallAudioEngine::start(quint64 callId) {
|
||||
if (m_active && m_callId == callId && !m_ending) return 0;
|
||||
stop();
|
||||
if (!gui_bridge_get_inst() || call_audio_start(callId) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "CallAudio: cannot start call=%016llx", (unsigned long long)callId);
|
||||
return -1;
|
||||
}
|
||||
++m_generation;
|
||||
m_callId = callId;
|
||||
m_captureBuf.assign(CALL_AUDIO_FRAME_SAMPLES, 0);
|
||||
m_capturePos = 0;
|
||||
m_muted = false;
|
||||
quint64 cid = m_callId.load();
|
||||
|
||||
teardownDevice();
|
||||
call_audio_release(cid);
|
||||
|
||||
m_callId = 0;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "CallAudio: stopped");
|
||||
m_ending = false;
|
||||
m_active = true;
|
||||
m_capture.retryNow(); m_playback.retryNow();
|
||||
recoverDevices();
|
||||
m_recoveryTimer.start();
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "CallAudio: session started call=%016llx", (unsigned long long)callId);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CallAudioEngine::teardownDevice() {
|
||||
if (m_deviceStarted) {
|
||||
ma_device_stop(m_device);
|
||||
ma_device_uninit(m_device);
|
||||
m_deviceStarted = false;
|
||||
bool CallAudioEngine::openDevice(bool playback) {
|
||||
AudioDevice& device = playback ? m_playback : m_capture;
|
||||
ma_context* context = SoundManager::instance()->context();
|
||||
if (!context) return false;
|
||||
ma_device_config config = ma_device_config_init(playback ? ma_device_type_playback : ma_device_type_capture);
|
||||
config.capture.format = config.playback.format = ma_format_s16;
|
||||
config.capture.channels = config.playback.channels = 1;
|
||||
config.sampleRate = CALL_AUDIO_SAMPLE_RATE;
|
||||
ma_device_id id;
|
||||
bool selected = SoundManager::deviceId(playback ? m_playbackId : m_captureId, &id);
|
||||
if (selected) {
|
||||
if (playback) config.playback.pDeviceID = &id;
|
||||
else config.capture.pDeviceID = &id;
|
||||
}
|
||||
if (m_device) { delete m_device; m_device = nullptr; }
|
||||
auto callback = [this](const void* in, void* out, unsigned int frames) { dataCallback(in, out, frames); };
|
||||
const char* role = playback ? "call-playback" : "call-capture";
|
||||
auto attempt = [&]() {
|
||||
if (!playback) m_capturePos = 0;
|
||||
bool ready = device.init(context, config, role, m_callId.load(), callback) && device.start();
|
||||
if (!ready) call_audio_reset_io(m_callId.load(), playback);
|
||||
return ready;
|
||||
};
|
||||
if (attempt()) return true;
|
||||
if (!selected) return false;
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CALL, "CallAudio: %s selected device unavailable, trying default", role);
|
||||
config.capture.pDeviceID = config.playback.pDeviceID = nullptr;
|
||||
return attempt();
|
||||
}
|
||||
|
||||
void CallAudioEngine::recoverDevices() {
|
||||
if (!m_active) return;
|
||||
if (m_playback.needsRecovery()) {
|
||||
m_playback.close();
|
||||
call_audio_reset_io(m_callId.load(), 1);
|
||||
m_playback.retryLater();
|
||||
}
|
||||
if (!m_ending && m_capture.needsRecovery()) {
|
||||
m_capture.close();
|
||||
m_capturePos = 0;
|
||||
call_audio_reset_io(m_callId.load(), 0);
|
||||
m_capture.retryLater();
|
||||
}
|
||||
if (m_playback.retryReady()) openDevice(true);
|
||||
if (!m_ending && m_capture.retryReady()) openDevice(false);
|
||||
}
|
||||
|
||||
void CallAudioEngine::pauseDevices() {
|
||||
m_capture.close(); m_playback.close();
|
||||
m_capturePos = 0;
|
||||
if (m_active) call_audio_reset_io(m_callId.load(), 1);
|
||||
}
|
||||
|
||||
void CallAudioEngine::setMuted(bool muted) {
|
||||
if (m_muted.exchange(muted) == muted) return;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "CallAudio: mute=%d", muted);
|
||||
}
|
||||
|
||||
void CallAudioEngine::setPlaybackDevice(const QString& id) {
|
||||
if (id == m_playbackId) return;
|
||||
m_playbackId = id;
|
||||
m_playback.close(); m_playback.retryNow();
|
||||
if (m_active) { call_audio_reset_io(m_callId.load(), 1); recoverDevices(); }
|
||||
}
|
||||
|
||||
void CallAudioEngine::setCaptureDevice(const QString& id) {
|
||||
if (id == m_captureId) return;
|
||||
m_captureId = id;
|
||||
m_capture.close(); m_capture.retryNow();
|
||||
m_capturePos = 0;
|
||||
if (m_active) { call_audio_reset_io(m_callId.load(), 0); recoverDevices(); }
|
||||
}
|
||||
|
||||
void CallAudioEngine::beginEnd(quint64 callId) {
|
||||
if (!m_active || m_callId != callId || m_ending) return;
|
||||
m_ending = true;
|
||||
m_capture.close();
|
||||
call_audio_begin_end(callId);
|
||||
uint64_t generation = m_generation;
|
||||
QTimer::singleShot(500, this, [this, generation]() { if (m_generation == generation) stop(); });
|
||||
}
|
||||
|
||||
void CallAudioEngine::stop() {
|
||||
++m_generation;
|
||||
m_recoveryTimer.stop();
|
||||
m_active = false;
|
||||
pauseDevices();
|
||||
uint64_t id = m_callId.exchange(0);
|
||||
if (id) call_audio_release(id);
|
||||
m_muted = false;
|
||||
m_ending = false;
|
||||
if (id) DEBUG_INFO(DEBUG_CATEGORY_CALL, "CallAudio: session stopped call=%016llx", (unsigned long long)id);
|
||||
}
|
||||
|
||||
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;
|
||||
uint64_t startTb = get_time_tb();
|
||||
uint64_t idleTb = m_dbg.lastEndTb ? startTb - m_dbg.lastEndTb : 0;
|
||||
if (!m_dbg.lastReportTb) m_dbg.lastReportTb = startTb;
|
||||
|
||||
/* диагностика (DEBUG_CATEGORY_DEBUG): фактические параметры miniaudio-коллбэка */
|
||||
if (m_dbg.frames != frames) {
|
||||
m_dbg.frames = frames;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "CallAudio: callback frames=%u in=%s out=%s",
|
||||
frames, in16 ? "yes" : "no", out16 ? "yes" : "no");
|
||||
}
|
||||
|
||||
/* захват: копим 960 samples → encode+send (сервисный слой). mute → тишина. */
|
||||
uint64_t captureStartTb = get_time_tb();
|
||||
if (in16) {
|
||||
if (in) {
|
||||
const auto* pcm = static_cast<const int16_t*>(in);
|
||||
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());
|
||||
for (unsigned int i = 0; i < frames; ++i) {
|
||||
m_captureBuf[m_capturePos++] = muted ? 0 : pcm[i];
|
||||
if (m_capturePos == CALL_AUDIO_FRAME_SAMPLES) {
|
||||
int result = call_audio_feed_pcm(m_callId.load(), m_captureBuf.data(), CALL_AUDIO_FRAME_SAMPLES);
|
||||
if (result != 0) audio_diag_event(m_capture.device(), AUDIO_DIAG_ERROR, AUDIO_DIAG_FEED, 1, result);
|
||||
m_capturePos = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
uint64_t captureEndTb = get_time_tb();
|
||||
|
||||
/* воспроизведение: финальный 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));
|
||||
uint64_t endTb = get_time_tb();
|
||||
uint64_t captureTb = captureEndTb - captureStartTb;
|
||||
uint64_t playbackTb = endTb - captureEndTb;
|
||||
m_dbg.callbacks++;
|
||||
m_dbg.captureFrames += in16 ? frames : 0;
|
||||
m_dbg.playbackFrames += out16 ? frames : 0;
|
||||
m_dbg.maxIdleTb = std::max(m_dbg.maxIdleTb, idleTb);
|
||||
m_dbg.maxCaptureTb = std::max(m_dbg.maxCaptureTb, captureTb);
|
||||
m_dbg.maxPlaybackTb = std::max(m_dbg.maxPlaybackTb, playbackTb);
|
||||
if (idleTb > 500 || endTb - startTb > 200) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "CallAudio: stall call=%016llx idle=%.1fms work=%.1fms capture=%.1fms playback=%.1fms frames=%u",
|
||||
(unsigned long long)m_callId.load(), idleTb / 10.0, (endTb - startTb) / 10.0,
|
||||
captureTb / 10.0, playbackTb / 10.0, frames);
|
||||
if (out) {
|
||||
auto* pcm = static_cast<int16_t*>(out);
|
||||
int n = call_audio_pull_pcm(m_callId.load(), pcm, (int)frames);
|
||||
if (n < (int)frames) std::memset(pcm + n, 0, (frames - n) * sizeof(*pcm));
|
||||
}
|
||||
uint64_t elapsedTb = endTb - m_dbg.lastReportTb;
|
||||
if (elapsedTb >= 10000) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG,
|
||||
"CallAudio: interval=%.1fms cb=%.1f/s capture=%.0f/s playback=%.0f/s max_idle=%.1fms max_capture=%.1fms max_playback=%.1fms",
|
||||
elapsedTb / 10.0, m_dbg.callbacks * 10000.0 / elapsedTb,
|
||||
m_dbg.captureFrames * 10000.0 / elapsedTb, m_dbg.playbackFrames * 10000.0 / elapsedTb,
|
||||
m_dbg.maxIdleTb / 10.0, m_dbg.maxCaptureTb / 10.0, m_dbg.maxPlaybackTb / 10.0);
|
||||
m_dbg.callbacks = 0;
|
||||
m_dbg.captureFrames = 0;
|
||||
m_dbg.playbackFrames = 0;
|
||||
m_dbg.maxIdleTb = 0;
|
||||
m_dbg.maxCaptureTb = 0;
|
||||
m_dbg.maxPlaybackTb = 0;
|
||||
m_dbg.lastReportTb = endTb;
|
||||
}
|
||||
m_dbg.lastEndTb = get_time_tb();
|
||||
}
|
||||
Reference in new issue
Block a user