202 lines
6.9 KiB
C++
202 lines
6.9 KiB
C++
#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 <QTimer>
|
|
#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;
|
|
}
|
|
|
|
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));
|
|
}
|