Browse Source

chatgui: report radio output progress with device timing

master
evgeny 2 days ago
parent
commit
06c35839ef
  1. 2
      tools/chatgui/src/sound_manager.cpp
  2. 28
      tools/chatgui/src/voice_audio_io.cpp
  3. 7
      tools/chatgui/src/voice_audio_io.h

2
tools/chatgui/src/sound_manager.cpp

@ -163,7 +163,7 @@ unsigned SoundManager::renderOutput(ma_engine* engine, const std::array<VoiceAud
if (engine && ma_engine_read_pcm_frames(engine, destination, count, nullptr) != MA_SUCCESS) {
++errors; std::fill_n(destination, count * 2, 0.0f);
}
for (auto* voice : voices) if (voice) voice->mix(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;

28
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);

7
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<Event, 32> m_captureEvents, m_references;
AudioEventQueue<Playback, 12> m_playback;
AudioEventQueue<PlaybackProgress, 32> m_playProgress;
std::atomic<unsigned> m_progressDropped{0};
std::atomic<bool> m_running{false}, m_capturing{false};
std::atomic<uint64_t> m_playEpoch{0}, m_captureDropped{0}, m_referenceDropped{0}, m_playbackUnderruns{0};
uint64_t m_captureSequence = 0; // Только capture callback; reset после его остановки.

Loading…
Cancel
Save