You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

155 lines
6.4 KiB

#include "radio_audio_engine.h"
#include "sound_manager.h"
#include "../transport/audio_diagnostics.h"
#include "../transport/gui_bridge.h"
#include "miniaudio.h"
extern "C" {
#include "radio/radio_audio.h"
#include "chat/chat_setting.h"
#include "../../lib/debug_config.h"
}
#include <cstring>
#include <algorithm>
RadioAudioEngine* RadioAudioEngine::instance() {
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(); m_voice.stopCapture(); if (m_active) radio_audio_capture_stop(); m_capture.retryNow();
}
if (m_playbackId != manager->playbackDevice()) {
m_playbackId = manager->playbackDevice();
manager->detachVoice(&m_voice); m_outputAttached = false;
}
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();
recoverDevices();
});
}
int RadioAudioEngine::start(quint64 groupId) {
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;
if (!m_voice.start(VoiceAudioIo::Kind::Radio, groupId)) { radio_audio_stop(); m_groupId = 0; return -1; }
m_wantStereo = chat_setting_get_int(inst, "radio_capture_stereo", 0) != 0;
m_active = true;
m_capture.retryNow();
recoverDevices();
m_recoveryTimer.start();
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "RadioAudio: listening group=%016llx", (unsigned long long)groupId);
return 0;
}
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;
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;
}
if (!m_voice.startCapture(channels, chat_setting_get_int(inst, "aec_enabled", 1) != 0)) {
m_capture.close(); radio_audio_capture_stop(); return;
}
if (m_talking) m_voice.setPtt(true);
if (!m_capture.start()) { m_voice.stopCapture(); radio_audio_capture_stop(); continue; }
if (channels == 1 && m_wantStereo) DEBUG_WARN(DEBUG_CATEGORY_RADIO, "RadioAudio: stereo input unavailable, using mono");
return;
}
}
}
void RadioAudioEngine::recoverDevices() {
if (!m_active) return;
auto* manager = SoundManager::instance();
if (!m_outputAttached && manager->context()) m_outputAttached = manager->attachVoice(&m_voice, m_playbackId);
if ((m_capture.initialized() && !m_voice.captureRunning()) || m_capture.needsRecovery()) {
m_capture.close(); m_voice.stopCapture(); radio_audio_capture_stop(); m_capture.retryLater();
}
if (m_capture.retryReady()) openCapture();
m_voice.setAecEnabled(chat_setting_get_int(gui_bridge_get_inst(), "aec_enabled", 1) != 0);
}
void RadioAudioEngine::pauseDevices() {
m_capture.close(); m_voice.stopCapture();
SoundManager::instance()->detachVoice(&m_voice); m_outputAttached = false;
if (m_active) radio_audio_capture_stop();
m_voice.resetPlayback();
}
void RadioAudioEngine::stop() {
m_recoveryTimer.stop();
pauseDevices(); m_voice.stop();
uint64_t group = m_groupId.exchange(0);
bool talking = m_talking.exchange(false);
m_buttonTalking = m_hotkeyTalking = false;
m_active = false;
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() {
if (!m_active) return;
m_buttonTalking = true;
updateTalking();
}
void RadioAudioEngine::talkEnd() {
m_buttonTalking = false;
updateTalking();
}
void RadioAudioEngine::setHotkeyTalking(bool pressed) {
m_hotkeyTalking = pressed && m_active;
updateTalking();
}
void RadioAudioEngine::updateTalking() {
bool talking = m_active && (m_buttonTalking || m_hotkeyTalking);
if (m_talking.exchange(talking) == talking || !m_active) return;
m_voice.setPtt(talking);
DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "RadioAudio: PTT %s group=%016llx button=%d hotkey=%d",
talking ? "started" : "stopped", (unsigned long long)m_groupId.load(), m_buttonTalking, m_hotkeyTalking);
emit talkingChanged(talking);
}
void RadioAudioEngine::captureCallback(const void* in, unsigned int frames) {
if (in) m_voice.capture(static_cast<const int16_t*>(in), frames, m_capture.callbackTimeUs(), m_capture.route(), m_capture.captureMissing());
}