diff --git a/src/call/call_audio.c b/src/call/call_audio.c index c85b9e08..67a6def0 100644 --- a/src/call/call_audio.c +++ b/src/call/call_audio.c @@ -492,7 +492,9 @@ int call_audio_pull_pcm(uint64_t call_id, int16_t* out, int max_samples) { } if (call_tones_ended_pending(tones)) n = call_tones_render(tones, out, max_samples); - call_jitter_silence(j, (unsigned)n); + memset(out + n, 0, (size_t)(max_samples - n) * sizeof(*out)); + call_jitter_silence(j, (unsigned)max_samples); + n = max_samples; } else { n = call_jitter_pull(j, out, max_samples); int previous_gap = g_gap_samples; diff --git a/tools/chatgui/src/voice_audio_io.cpp b/tools/chatgui/src/voice_audio_io.cpp index 71f77443..d882e691 100644 --- a/tools/chatgui/src/voice_audio_io.cpp +++ b/tools/chatgui/src/voice_audio_io.cpp @@ -122,7 +122,7 @@ void VoiceAudioIo::reference(const float* pcm, unsigned frames, int64_t firstTim } /* Выход всегда обслуживается независимо от capture/VAD. Уже потреблённый PCM становится референсом у владельца микшера. */ -void VoiceAudioIo::mix(float* stereo, unsigned frames, int64_t firstTimeUs) { +void VoiceAudioIo::mix(float* stereo, unsigned frames, int64_t firstTimeUs, bool measured) { uint64_t epoch = m_playEpoch.load(); if (!firstTimeUs) firstTimeUs = voice_time_us(); unsigned consumed = 0; @@ -140,9 +140,9 @@ void VoiceAudioIo::mix(float* stereo, unsigned frames, int64_t firstTimeUs) { ++m_playPosition; ++consumed; } - if (m_kind == Kind::Radio && consumed) { + if (consumed) { PlaybackProgress progress{epoch, m_playCurrent.position + m_playPosition, - firstTimeUs + (int64_t)consumed * 1000000 / 48000}; + firstTimeUs + (int64_t)consumed * 1000000 / 48000, measured}; if (!m_playProgress.push(progress)) m_progressDropped.fetch_add(1, std::memory_order_relaxed); } m_wake.notify_all(); @@ -151,11 +151,18 @@ void VoiceAudioIo::mix(float* stereo, unsigned frames, int64_t firstTimeUs) { void VoiceAudioIo::playbackLoop() { uint64_t epoch = m_playEpoch.load(); uint64_t position = 0; + call_clock_snapshot clock{}; + if (m_kind == Kind::Call && call_audio_get_clock(m_session, voice_time_us(), &clock) != 0) + DEBUG_WARN(DEBUG_CATEGORY_CALL, "voice-io: call clock unavailable session=%llu", (unsigned long long)m_session); 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); + if (m_kind == Kind::Call) { + call_audio_reset_io(m_session, 1); + if (call_audio_get_clock(m_session, voice_time_us(), &clock) != 0) + DEBUG_WARN(DEBUG_CATEGORY_CALL, "voice-io: reset call clock unavailable session=%llu", (unsigned long long)m_session); + } else radio_audio_reset_playback(m_session); position = 0; epoch = requested; @@ -163,11 +170,13 @@ void VoiceAudioIo::playbackLoop() { 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); + int64_t now = voice_time_us(), remaining = progress.endTimeUs - now; + if (m_kind == Kind::Call) + call_audio_playback_progress(m_session, clock.epoch, progress.position, progress.endTimeUs, now, progress.measured); + else 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", + if (lost) DEBUG_WARN(DEBUG_CATEGORY_CALL, "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; block.position = position; @@ -282,7 +291,7 @@ void VoiceAudioIo::captureLoop() { if (mark < markerCount) applyMarker(markers[mark].kind, markers[mark].value); begin = end; } - if (m_kind == Kind::Call && frames == 960) call_audio_feed_prepared_pcm(m_session, cleaned, muteMask); + if (m_kind == Kind::Call && frames == 960) call_audio_feed_prepared_pcm(m_session, cleaned, muteMask, frameTime); filled = 0; markerCount = 0; }; // Отчёт работает и при отсутствии PCM, иначе длительный провал захвата невидим. @@ -350,6 +359,7 @@ void VoiceAudioIo::captureLoop() { filled = 0; markerCount = 0; if (aec) speex_aec_reset(aec); if (m_kind == Kind::Radio) radio_audio_capture_discontinuity(m_session); + else call_audio_reset_io(m_session, 0); } expectedSequence = event.sequence + event.frames; captureRoute = event.route; diff --git a/tools/chatgui/src/voice_audio_io.h b/tools/chatgui/src/voice_audio_io.h index b61bb3c9..7a96ce16 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, int64_t firstTimeUs = 0); + void mix(float* stereo, unsigned frames, int64_t firstTimeUs = 0, bool measured = false); private: enum class EventKind { Pcm, Ptt, Mute, Aec, Stop }; struct Event { @@ -41,7 +41,7 @@ private: 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; }; + struct PlaybackProgress { uint64_t epoch = 0, position = 0; int64_t endTimeUs = 0; bool measured = false; }; AudioEventQueue m_captureEvents, m_references; AudioEventQueue m_playback; AudioEventQueue m_playProgress;