Browse Source

Drive desktop call clock from rendered PCM and capture timing

stream-multiview
evgeny 1 week ago
parent
commit
39bfedca86
  1. 4
      src/call/call_audio.c
  2. 26
      tools/chatgui/src/voice_audio_io.cpp
  3. 4
      tools/chatgui/src/voice_audio_io.h

4
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)) if (call_tones_ended_pending(tones))
n = call_tones_render(tones, out, max_samples); 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 { } else {
n = call_jitter_pull(j, out, max_samples); n = call_jitter_pull(j, out, max_samples);
int previous_gap = g_gap_samples; int previous_gap = g_gap_samples;

26
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 становится референсом у владельца микшера. */ /* Выход всегда обслуживается независимо от 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(); uint64_t epoch = m_playEpoch.load();
if (!firstTimeUs) firstTimeUs = voice_time_us(); if (!firstTimeUs) firstTimeUs = voice_time_us();
unsigned consumed = 0; unsigned consumed = 0;
@ -140,9 +140,9 @@ void VoiceAudioIo::mix(float* stereo, unsigned frames, int64_t firstTimeUs) {
++m_playPosition; ++m_playPosition;
++consumed; ++consumed;
} }
if (m_kind == Kind::Radio && consumed) { if (consumed) {
PlaybackProgress progress{epoch, m_playCurrent.position + m_playPosition, 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); if (!m_playProgress.push(progress)) m_progressDropped.fetch_add(1, std::memory_order_relaxed);
} }
m_wake.notify_all(); m_wake.notify_all();
@ -151,11 +151,18 @@ void VoiceAudioIo::mix(float* stereo, unsigned frames, int64_t firstTimeUs) {
void VoiceAudioIo::playbackLoop() { void VoiceAudioIo::playbackLoop() {
uint64_t epoch = m_playEpoch.load(); uint64_t epoch = m_playEpoch.load();
uint64_t position = 0; 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); if (m_kind == Kind::Radio) radio_audio_playback_progress(m_session, 0, 0);
while (m_running) { while (m_running) {
uint64_t requested = m_playEpoch.load(); uint64_t requested = m_playEpoch.load();
if (requested != epoch) { 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); else radio_audio_reset_playback(m_session);
position = 0; position = 0;
epoch = requested; epoch = requested;
@ -163,11 +170,13 @@ void VoiceAudioIo::playbackLoop() {
PlaybackProgress progress{}; PlaybackProgress progress{};
for (unsigned count = 0; count < 32 && m_playProgress.pop(progress); ++count) { for (unsigned count = 0; count < 32 && m_playProgress.pop(progress); ++count) {
if (progress.epoch != epoch) continue; if (progress.epoch != epoch) continue;
int64_t remaining = progress.endTimeUs - voice_time_us(); int64_t now = voice_time_us(), remaining = progress.endTimeUs - now;
radio_audio_playback_progress(m_session, progress.position, remaining > 0 ? (remaining + 99) / 100 : 0); 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); 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); (unsigned long long)m_session, lost);
if (m_playback.size() < 2) { if (m_playback.size() < 2) {
Playback block{}; block.epoch = epoch; block.position = position; 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); if (mark < markerCount) applyMarker(markers[mark].kind, markers[mark].value);
begin = end; 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; filled = 0; markerCount = 0;
}; };
// Отчёт работает и при отсутствии PCM, иначе длительный провал захвата невидим. // Отчёт работает и при отсутствии PCM, иначе длительный провал захвата невидим.
@ -350,6 +359,7 @@ void VoiceAudioIo::captureLoop() {
filled = 0; markerCount = 0; filled = 0; markerCount = 0;
if (aec) speex_aec_reset(aec); if (aec) speex_aec_reset(aec);
if (m_kind == Kind::Radio) radio_audio_capture_discontinuity(m_session); if (m_kind == Kind::Radio) radio_audio_capture_discontinuity(m_session);
else call_audio_reset_io(m_session, 0);
} }
expectedSequence = event.sequence + event.frames; expectedSequence = event.sequence + event.frames;
captureRoute = event.route; captureRoute = event.route;

4
tools/chatgui/src/voice_audio_io.h

@ -27,7 +27,7 @@ public:
/* firstTimeUs — монотонное время первого физического PCM-фрейма (backend либо оценка). */ /* firstTimeUs — монотонное время первого физического PCM-фрейма (backend либо оценка). */
void capture(const int16_t* pcm, unsigned frames, int64_t firstTimeUs, uint64_t route = 0, bool missing = false); 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 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: private:
enum class EventKind { Pcm, Ptt, Mute, Aec, Stop }; enum class EventKind { Pcm, Ptt, Mute, Aec, Stop };
struct Event { struct Event {
@ -41,7 +41,7 @@ private:
int16_t pcm[1920]{}; int16_t pcm[1920]{};
}; };
struct Playback { uint64_t epoch = 0, position = 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; }; struct PlaybackProgress { uint64_t epoch = 0, position = 0; int64_t endTimeUs = 0; bool measured = false; };
AudioEventQueue<Event, 32> m_captureEvents, m_references; AudioEventQueue<Event, 32> m_captureEvents, m_references;
AudioEventQueue<Playback, 12> m_playback; AudioEventQueue<Playback, 12> m_playback;
AudioEventQueue<PlaybackProgress, 32> m_playProgress; AudioEventQueue<PlaybackProgress, 32> m_playProgress;

Loading…
Cancel
Save