Сохрани накопленные исправления аудио, async и диагностики
This commit is contained in:
1 parent
b1e3193899
commit
bdcf8b5634
59 files changed
+4611
-1441
No files matched your search
@@ -1,5 +1,6 @@
|
||||
#include "radio_audio_engine.h"
|
||||
#include "sound_manager.h"
|
||||
#include "../transport/audio_diagnostics.h"
|
||||
#include "../transport/gui_bridge.h"
|
||||
|
||||
#include "miniaudio.h"
|
||||
@@ -8,162 +9,140 @@ extern "C" {
|
||||
#include "radio/radio_audio.h"
|
||||
#include "chat/chat_setting.h"
|
||||
#include "../../lib/debug_config.h"
|
||||
#include "../../lib/u_async.h"
|
||||
}
|
||||
|
||||
#include <cstring>
|
||||
|
||||
static RadioAudioEngine* g_radioAudioEngine = nullptr;
|
||||
|
||||
static void radioAudioCaptureCallback(ma_device* dev, void* out, const void* in, ma_uint32 frames) {
|
||||
(void)out;
|
||||
RadioAudioEngine* e = (RadioAudioEngine*)dev->pUserData;
|
||||
if (e) e->captureCallback(in, frames);
|
||||
}
|
||||
|
||||
static void radioAudioPlaybackCallback(ma_device* dev, void* out, const void* in, ma_uint32 frames) {
|
||||
(void)in;
|
||||
RadioAudioEngine* e = (RadioAudioEngine*)dev->pUserData;
|
||||
if (e) e->playbackCallback(out, frames);
|
||||
}
|
||||
#include <algorithm>
|
||||
|
||||
RadioAudioEngine* RadioAudioEngine::instance() {
|
||||
if (!g_radioAudioEngine) g_radioAudioEngine = new RadioAudioEngine();
|
||||
return g_radioAudioEngine;
|
||||
static auto* engine = new RadioAudioEngine();
|
||||
return engine;
|
||||
}
|
||||
|
||||
RadioAudioEngine::RadioAudioEngine() {
|
||||
m_captureId = SoundManager::instance()->captureDevice();
|
||||
m_playbackId = SoundManager::instance()->playbackDevice();
|
||||
connect(SoundManager::instance(), &SoundManager::devicesChanged, this, [this]() {
|
||||
auto* manager = SoundManager::instance();
|
||||
if (m_captureId != manager->captureDevice()) {
|
||||
m_captureId = manager->captureDevice();
|
||||
m_capture.close(); if (m_active) radio_audio_capture_stop(); m_capture.retryNow();
|
||||
}
|
||||
if (m_playbackId != manager->playbackDevice()) {
|
||||
m_playbackId = manager->playbackDevice();
|
||||
m_playback.close(); if (m_active) radio_audio_reset_playback(m_groupId.load()); m_playback.retryNow();
|
||||
}
|
||||
recoverDevices();
|
||||
});
|
||||
m_recoveryTimer.setInterval(200);
|
||||
connect(&m_recoveryTimer, &QTimer::timeout, this, &RadioAudioEngine::recoverDevices);
|
||||
connect(SoundManager::instance(), &SoundManager::contextAboutToReset, this, &RadioAudioEngine::pauseDevices);
|
||||
connect(SoundManager::instance(), &SoundManager::contextRestored, this, [this]() {
|
||||
m_capture.retryNow(); m_playback.retryNow();
|
||||
recoverDevices();
|
||||
});
|
||||
}
|
||||
|
||||
int RadioAudioEngine::start(quint64 groupId) {
|
||||
if (m_active) {
|
||||
if (m_groupId == groupId) return 0;
|
||||
stop();
|
||||
if (m_active && m_groupId == groupId) return 0;
|
||||
stop();
|
||||
auto* inst = gui_bridge_get_inst();
|
||||
if (!inst || radio_audio_start(groupId, 0) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "RadioAudio: cannot start group=%016llx", (unsigned long long)groupId);
|
||||
return -1;
|
||||
}
|
||||
m_groupId = groupId;
|
||||
|
||||
struct UTUN_INSTANCE* inst = gui_bridge_get_inst();
|
||||
if (!inst) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "RadioAudio: no instance, cannot start");
|
||||
return -1;
|
||||
}
|
||||
if (radio_audio_init(inst) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "RadioAudio: radio_audio_init failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* желаемые каналы захвата из настройки; initDevice() при недоступности откатывает на моно */
|
||||
m_captureChannels = chat_setting_get_int(inst, "radio_capture_stereo", 0) ? 2 : 1;
|
||||
|
||||
if (initDevice() != 0) {
|
||||
radio_audio_stop();
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (radio_audio_start(groupId, m_captureChannels) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "RadioAudio: radio_audio_start failed grp=%016llx", (unsigned long long)groupId);
|
||||
teardownDevice();
|
||||
radio_audio_stop();
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* VAD авто-PTT: в этом режиме захват фидится постоянно, решение «говорить» — в сервисном слое. */
|
||||
m_vadMode = radio_audio_vad_mode() ? true : false;
|
||||
if (m_vadMode)
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "RadioAudio: VAD auto-PTT enabled grp=%016llx", (unsigned long long)groupId);
|
||||
|
||||
m_captureBuf.assign((size_t)RADIO_AUDIO_FRAME_SAMPLES * m_captureChannels, 0);
|
||||
m_capturePos = 0;
|
||||
|
||||
m_wantStereo = chat_setting_get_int(inst, "radio_capture_stereo", 0) != 0;
|
||||
m_active = true;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "RadioAudio: started grp=%016llx captureCh=%d", (unsigned long long)groupId, m_captureChannels);
|
||||
m_capture.retryNow(); m_playback.retryNow();
|
||||
recoverDevices();
|
||||
m_recoveryTimer.start();
|
||||
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "RadioAudio: listening group=%016llx", (unsigned long long)groupId);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RadioAudioEngine::initDevice() {
|
||||
ma_context* ctx = SoundManager::instance()->context();
|
||||
if (!ctx) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "RadioAudio: no audio context");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ── capture-устройство (стерео → откат на моно → дефолтное устройство) ── */
|
||||
const int wantCh = m_captureChannels;
|
||||
int capCh = 0;
|
||||
for (int useDefault = 0; useDefault < 2 && capCh == 0; useDefault++) {
|
||||
for (int ch = wantCh; ch >= 1; ch--) {
|
||||
ma_device* dev = new ma_device;
|
||||
std::memset(dev, 0, sizeof(*dev));
|
||||
ma_device_config config = ma_device_config_init(ma_device_type_capture);
|
||||
config.capture.format = ma_format_s16;
|
||||
config.capture.channels = ch;
|
||||
config.sampleRate = RADIO_AUDIO_SAMPLE_RATE;
|
||||
config.periodSizeInFrames = 480;
|
||||
config.dataCallback = radioAudioCaptureCallback;
|
||||
config.pUserData = this;
|
||||
if (useDefault) config.capture.pDeviceID = nullptr;
|
||||
|
||||
if (ma_device_init(ctx, &config, dev) != MA_SUCCESS) { delete dev; continue; }
|
||||
if (ma_device_start(dev) != MA_SUCCESS) { ma_device_uninit(dev); delete dev; continue; }
|
||||
m_captureDevice = dev;
|
||||
m_captureStarted = true;
|
||||
capCh = ch;
|
||||
if (ch != wantCh)
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "RadioAudio: stereo capture unavailable — fallback to mono");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "RadioAudio: capture started ch=%d cap='%s'",
|
||||
ch, dev->capture.name ? dev->capture.name : "?");
|
||||
break;
|
||||
void RadioAudioEngine::openCapture() {
|
||||
auto* context = SoundManager::instance()->context();
|
||||
if (!context) return;
|
||||
for (int channels = m_wantStereo ? 2 : 1; channels >= 1; --channels) {
|
||||
ma_device_config config = ma_device_config_init(ma_device_type_capture);
|
||||
config.capture.format = ma_format_s16;
|
||||
config.capture.channels = channels;
|
||||
config.sampleRate = RADIO_AUDIO_SAMPLE_RATE;
|
||||
auto callback = [this](const void* in, void*, unsigned int frames) { captureCallback(in, frames); };
|
||||
ma_device_id id;
|
||||
bool selected = SoundManager::deviceId(m_captureId, &id);
|
||||
for (int attempt = 0; attempt < (selected ? 2 : 1); ++attempt) {
|
||||
config.capture.pDeviceID = selected && attempt == 0 ? &id : nullptr;
|
||||
if (attempt) DEBUG_WARN(DEBUG_CATEGORY_RADIO, "RadioAudio: selected input unavailable, trying default ch=%d", channels);
|
||||
if (!m_capture.init(context, config, "radio-capture", m_groupId.load(), callback)) continue;
|
||||
m_captureChannels = channels;
|
||||
m_captureBuf.assign(RADIO_AUDIO_FRAME_SAMPLES * channels, 0);
|
||||
m_capturePos = 0;
|
||||
auto* inst = gui_bridge_get_inst();
|
||||
int vad = chat_setting_get_int(inst, "radio_vad_enabled", 0);
|
||||
if (radio_audio_capture_start(m_groupId.load(), channels, vad) != 0) {
|
||||
m_capture.close(); m_capture.retryLater(); return;
|
||||
}
|
||||
m_vadMode = radio_audio_vad_mode() != 0;
|
||||
if (!m_capture.start()) { radio_audio_capture_stop(); continue; }
|
||||
if (m_talking) radio_audio_talk_begin(m_groupId.load());
|
||||
if (channels == 1 && m_wantStereo) DEBUG_WARN(DEBUG_CATEGORY_RADIO, "RadioAudio: stereo input unavailable, using mono");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (capCh == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "RadioAudio: capture device init failed");
|
||||
teardownDevice();
|
||||
return -1;
|
||||
}
|
||||
|
||||
void RadioAudioEngine::openPlayback() {
|
||||
auto* context = SoundManager::instance()->context();
|
||||
if (!context) return;
|
||||
ma_device_config config = ma_device_config_init(ma_device_type_playback);
|
||||
config.playback.format = ma_format_s16;
|
||||
config.playback.channels = 2;
|
||||
config.sampleRate = RADIO_AUDIO_SAMPLE_RATE;
|
||||
auto callback = [this](const void*, void* out, unsigned int frames) { playbackCallback(out, frames); };
|
||||
ma_device_id id;
|
||||
bool selected = SoundManager::deviceId(m_playbackId, &id);
|
||||
if (selected) config.playback.pDeviceID = &id;
|
||||
if (m_playback.init(context, config, "radio-playback", m_groupId.load(), callback) && m_playback.start()) return;
|
||||
if (!selected) return;
|
||||
DEBUG_WARN(DEBUG_CATEGORY_RADIO, "RadioAudio: selected output unavailable, trying default");
|
||||
config.playback.pDeviceID = nullptr;
|
||||
if (m_playback.init(context, config, "radio-playback", m_groupId.load(), callback)) m_playback.start();
|
||||
}
|
||||
|
||||
void RadioAudioEngine::recoverDevices() {
|
||||
if (!m_active) return;
|
||||
if (m_capture.needsRecovery()) {
|
||||
m_capture.close(); radio_audio_capture_stop();
|
||||
m_capturePos = 0; m_capture.retryLater();
|
||||
}
|
||||
m_captureChannels = capCh;
|
||||
|
||||
/* ── playback-устройство (всегда стерео) ── */
|
||||
m_playbackDevice = new ma_device;
|
||||
std::memset(m_playbackDevice, 0, sizeof(*m_playbackDevice));
|
||||
{
|
||||
ma_device_config config = ma_device_config_init(ma_device_type_playback);
|
||||
config.playback.format = ma_format_s16;
|
||||
config.playback.channels = 2;
|
||||
config.sampleRate = RADIO_AUDIO_SAMPLE_RATE;
|
||||
config.periodSizeInFrames = 480;
|
||||
config.dataCallback = radioAudioPlaybackCallback;
|
||||
config.pUserData = this;
|
||||
|
||||
if (ma_device_init(ctx, &config, m_playbackDevice) != MA_SUCCESS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "RadioAudio: playback device init failed");
|
||||
teardownDevice();
|
||||
return -1;
|
||||
}
|
||||
if (ma_device_start(m_playbackDevice) != MA_SUCCESS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "RadioAudio: playback device start failed");
|
||||
teardownDevice();
|
||||
return -1;
|
||||
}
|
||||
m_playbackStarted = true;
|
||||
if (m_playback.needsRecovery()) {
|
||||
m_playback.close(); radio_audio_reset_playback(m_groupId.load()); m_playback.retryLater();
|
||||
}
|
||||
if (m_playback.retryReady()) openPlayback();
|
||||
if (m_capture.retryReady()) openCapture();
|
||||
}
|
||||
|
||||
void RadioAudioEngine::pauseDevices() {
|
||||
m_capture.close(); m_playback.close();
|
||||
m_capturePos = 0;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "RadioAudio: playback started ch=2 pb='%s'",
|
||||
m_playbackDevice->playback.name ? m_playbackDevice->playback.name : "?");
|
||||
return 0;
|
||||
if (m_active) {
|
||||
radio_audio_capture_stop();
|
||||
radio_audio_reset_playback(m_groupId.load());
|
||||
}
|
||||
}
|
||||
|
||||
void RadioAudioEngine::stop() {
|
||||
if (!m_active && !m_captureDevice && !m_playbackDevice && !m_captureStarted && !m_playbackStarted) return;
|
||||
quint64 gid = m_groupId.load();
|
||||
m_recoveryTimer.stop();
|
||||
m_capture.close(); m_playback.close();
|
||||
uint64_t group = m_groupId.exchange(0);
|
||||
bool talking = m_talking.exchange(false);
|
||||
m_buttonTalking = m_hotkeyTalking = false;
|
||||
bool wasTalking = m_talking.exchange(false);
|
||||
if (wasTalking) radio_audio_talk_end(gid); /* честный FIN: пиры дренируют звук, а не ждут таймаут */
|
||||
m_active = false;
|
||||
|
||||
teardownDevice();
|
||||
radio_audio_stop();
|
||||
|
||||
m_groupId = 0;
|
||||
if (wasTalking) emit talkingChanged(false);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "RadioAudio: stopped");
|
||||
if (group) radio_audio_stop();
|
||||
if (talking) emit talkingChanged(false);
|
||||
if (group) DEBUG_INFO(DEBUG_CATEGORY_RADIO, "RadioAudio: stopped group=%016llx", (unsigned long long)group);
|
||||
}
|
||||
|
||||
void RadioAudioEngine::talkBegin() {
|
||||
@@ -192,21 +171,6 @@ void RadioAudioEngine::updateTalking() {
|
||||
emit talkingChanged(talking);
|
||||
}
|
||||
|
||||
void RadioAudioEngine::teardownDevice() {
|
||||
if (m_captureStarted && m_captureDevice) {
|
||||
ma_device_stop(m_captureDevice);
|
||||
ma_device_uninit(m_captureDevice);
|
||||
m_captureStarted = false;
|
||||
}
|
||||
if (m_captureDevice) { delete m_captureDevice; m_captureDevice = nullptr; }
|
||||
if (m_playbackStarted && m_playbackDevice) {
|
||||
ma_device_stop(m_playbackDevice);
|
||||
ma_device_uninit(m_playbackDevice);
|
||||
m_playbackStarted = false;
|
||||
}
|
||||
if (m_playbackDevice) { delete m_playbackDevice; m_playbackDevice = nullptr; }
|
||||
}
|
||||
|
||||
void RadioAudioEngine::captureCallback(const void* in, unsigned int frames) {
|
||||
const int16_t* in16 = (const int16_t*)in;
|
||||
if (!in16) return;
|
||||
@@ -219,7 +183,8 @@ void RadioAudioEngine::captureCallback(const void* in, unsigned int frames) {
|
||||
for (int i = 0; i < totalIn; i++) {
|
||||
m_captureBuf[m_capturePos++] = in16[i];
|
||||
if (m_capturePos >= (int)m_captureBuf.size()) {
|
||||
radio_audio_feed_pcm(m_groupId.load(), m_captureBuf.data(), (int)m_captureBuf.size());
|
||||
int result = radio_audio_feed_pcm(m_groupId.load(), m_captureBuf.data(), (int)m_captureBuf.size());
|
||||
if (result != 0) audio_diag_event(m_capture.device(), AUDIO_DIAG_ERROR, AUDIO_DIAG_FEED, 1, result);
|
||||
m_capturePos = 0;
|
||||
}
|
||||
}
|
||||
@@ -228,36 +193,12 @@ void RadioAudioEngine::captureCallback(const void* in, unsigned int frames) {
|
||||
void RadioAudioEngine::playbackCallback(void* out, unsigned int frames) {
|
||||
int16_t* out16 = (int16_t*)out;
|
||||
|
||||
/* диагностика (DEBUG_CATEGORY_DEBUG): cadence playback-коллбэка */
|
||||
uint64_t now = get_time_tb();
|
||||
if (m_dbgFrames == 0) {
|
||||
m_dbgFrames = frames;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "RadioAudio: playback frames=%u", frames);
|
||||
}
|
||||
m_dbgCbCount++;
|
||||
if (m_dbgPrevCbTb != 0) {
|
||||
uint64_t gap = now - m_dbgPrevCbTb;
|
||||
if (gap > m_dbgGapMax) m_dbgGapMax = gap;
|
||||
}
|
||||
m_dbgPrevCbTb = now;
|
||||
if (m_dbgLastTb == 0) {
|
||||
m_dbgLastTb = now;
|
||||
} else if (now - m_dbgLastTb >= 10000) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "RadioAudio: pb cb=%u/s frames=%u gapMax=%.1fms bodyMax=%.1fms",
|
||||
m_dbgCbCount, frames, (double)m_dbgGapMax / 10.0, (double)m_dbgBodyMax / 10.0);
|
||||
m_dbgCbCount = 0;
|
||||
m_dbgLastTb = now;
|
||||
m_dbgGapMax = 0;
|
||||
m_dbgBodyMax = 0;
|
||||
}
|
||||
|
||||
/* воспроизведение: микшированный PCM из сервисного слоя (всегда стерео) */
|
||||
int total = (int)frames * 2;
|
||||
int n = radio_audio_pull_pcm(m_groupId.load(), out16, total);
|
||||
if (n < total)
|
||||
std::memset(out16 + n, 0, (size_t)(total - n) * sizeof(int16_t));
|
||||
|
||||
uint64_t end = get_time_tb();
|
||||
uint64_t body = end - now;
|
||||
if (body > m_dbgBodyMax) m_dbgBodyMax = body;
|
||||
unsigned int offset = 0;
|
||||
while (offset < frames) {
|
||||
unsigned int count = std::min(frames - offset, (unsigned int)RADIO_AUDIO_FRAME_SAMPLES);
|
||||
int n = radio_audio_pull_pcm(m_groupId.load(), out16 + offset * 2, count * 2);
|
||||
if (n < (int)count * 2) std::memset(out16 + offset * 2 + n, 0, (count * 2 - n) * sizeof(*out16));
|
||||
offset += count;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user