From 06c35839ef29428e7ef4ad643356d164ad763cd9 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 8 Oct 2026 14:27:05 +0200 Subject: [PATCH] chatgui: report radio output progress with device timing --- tools/chatgui/src/sound_manager.cpp | 2 +- tools/chatgui/src/voice_audio_io.cpp | 28 +++++++++++++++++++++++++--- tools/chatgui/src/voice_audio_io.h | 7 +++++-- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/tools/chatgui/src/sound_manager.cpp b/tools/chatgui/src/sound_manager.cpp index d92437d4..d38afcf0 100644 --- a/tools/chatgui/src/sound_manager.cpp +++ b/tools/chatgui/src/sound_manager.cpp @@ -163,7 +163,7 @@ unsigned SoundManager::renderOutput(ma_engine* engine, const std::arraymix(destination, count); + for (auto* voice : voices) if (voice) voice->mix(destination, count, firstTimeUs + (int64_t)offset * 1000000 / 48000); for (unsigned i = 0; i < count * 2; ++i) { float value = destination[i]; destination[i] = std::isfinite(value) ? std::clamp(value, -1.0f, 1.0f) : 0.0f; diff --git a/tools/chatgui/src/voice_audio_io.cpp b/tools/chatgui/src/voice_audio_io.cpp index 49234866..f89ddca7 100644 --- a/tools/chatgui/src/voice_audio_io.cpp +++ b/tools/chatgui/src/voice_audio_io.cpp @@ -21,7 +21,8 @@ VoiceAudioIo::~VoiceAudioIo() { stop(); } bool VoiceAudioIo::start(Kind kind, uint64_t session) { stop(); m_kind = kind; m_session = session; - m_captureEvents.reset(); m_references.reset(); m_playback.reset(); + m_captureEvents.reset(); m_references.reset(); m_playback.reset(); m_playProgress.reset(); + m_progressDropped = 0; m_playPosition = 960; m_playEpoch = 1; m_captureDropped = m_referenceDropped = m_playbackUnderruns = 0; m_running = true; @@ -121,8 +122,10 @@ void VoiceAudioIo::reference(const float* pcm, unsigned frames, int64_t firstTim } /* Выход всегда обслуживается независимо от capture/VAD. Уже потреблённый PCM становится референсом у владельца микшера. */ -void VoiceAudioIo::mix(float* stereo, unsigned frames) { +void VoiceAudioIo::mix(float* stereo, unsigned frames, int64_t firstTimeUs) { uint64_t epoch = m_playEpoch.load(); + if (!firstTimeUs) firstTimeUs = voice_time_us(); + unsigned consumed = 0; for (unsigned i = 0; i < frames; ++i) { if (m_playCurrent.epoch != epoch) m_playPosition = 960; if (m_playPosition == 960) { @@ -135,26 +138,45 @@ void VoiceAudioIo::mix(float* stereo, unsigned frames) { stereo[2*i] += m_playCurrent.pcm[2*m_playPosition] / 32768.0f; stereo[2*i+1] += m_playCurrent.pcm[2*m_playPosition+1] / 32768.0f; ++m_playPosition; + ++consumed; + } + if (m_kind == Kind::Radio && consumed) { + PlaybackProgress progress{epoch, m_playCurrent.position + m_playPosition, + firstTimeUs + (int64_t)consumed * 1000000 / 48000}; + if (!m_playProgress.push(progress)) m_progressDropped.fetch_add(1, std::memory_order_relaxed); } m_wake.notify_all(); } void VoiceAudioIo::playbackLoop() { uint64_t epoch = m_playEpoch.load(); + uint64_t position = 0; + if (m_kind == Kind::Radio) radio_audio_playback_progress(m_session, 0, 0); while (m_running) { uint64_t requested = m_playEpoch.load(); if (requested != epoch) { if (m_kind == Kind::Call) call_audio_reset_io(m_session, 1); else radio_audio_reset_playback(m_session); + position = 0; epoch = requested; } + PlaybackProgress progress{}; + for (unsigned count = 0; count < 32 && m_playProgress.pop(progress); ++count) { + if (progress.epoch != epoch) continue; + int64_t remaining = progress.endTimeUs - voice_time_us(); + radio_audio_playback_progress(m_session, progress.position, remaining > 0 ? (remaining + 99) / 100 : 0); + } + unsigned lost = m_progressDropped.exchange(0); + if (lost) DEBUG_WARN(DEBUG_CATEGORY_RADIO, "voice-io: playback progress overflow session=%llu dropped=%u", + (unsigned long long)m_session, lost); if (m_playback.size() < 2) { - Playback block{}; block.epoch = epoch; + Playback block{}; block.epoch = epoch; block.position = position; if (m_kind == Kind::Call) { int16_t mono[960]{}; int count = std::clamp(call_audio_pull_pcm(m_session, mono, 960), 0, 960); for (int i = 0; i < count; ++i) block.pcm[2*i] = block.pcm[2*i+1] = mono[i]; } else radio_audio_pull_pcm(m_session, block.pcm, 1920); + position += 960; if (!m_playback.push(block)) DEBUG_WARN(DEBUG_CATEGORY_CALL, "voice-io: playback queue contention"); } else { std::unique_lock lock(m_waitMutex); diff --git a/tools/chatgui/src/voice_audio_io.h b/tools/chatgui/src/voice_audio_io.h index 6d4654f7..b61bb3c9 100644 --- a/tools/chatgui/src/voice_audio_io.h +++ b/tools/chatgui/src/voice_audio_io.h @@ -27,7 +27,7 @@ public: /* firstTimeUs — монотонное время первого физического PCM-фрейма (backend либо оценка). */ void capture(const int16_t* pcm, unsigned frames, int64_t firstTimeUs, uint64_t route = 0, bool missing = false); void reference(const float* pcm, unsigned frames, int64_t firstTimeUs); - void mix(float* stereo, unsigned frames); + void mix(float* stereo, unsigned frames, int64_t firstTimeUs = 0); private: enum class EventKind { Pcm, Ptt, Mute, Aec, Stop }; struct Event { @@ -40,9 +40,12 @@ private: bool value = false; int16_t pcm[1920]{}; }; - struct Playback { uint64_t epoch = 0; int16_t pcm[1920]{}; }; + struct Playback { uint64_t epoch = 0, position = 0; int16_t pcm[1920]{}; }; + struct PlaybackProgress { uint64_t epoch = 0, position = 0; int64_t endTimeUs = 0; }; AudioEventQueue m_captureEvents, m_references; AudioEventQueue m_playback; + AudioEventQueue m_playProgress; + std::atomic m_progressDropped{0}; std::atomic m_running{false}, m_capturing{false}; std::atomic m_playEpoch{0}, m_captureDropped{0}, m_referenceDropped{0}, m_playbackUnderruns{0}; uint64_t m_captureSequence = 0; // Только capture callback; reset после его остановки.