|
|
|
|
@ -1,5 +1,6 @@
|
|
|
|
|
#include "voice_audio_io.h" |
|
|
|
|
#include "voice_capture_clock.h" |
|
|
|
|
#include "voice_playback_buffer.h" |
|
|
|
|
extern "C" { |
|
|
|
|
#include "call/call_audio.h" |
|
|
|
|
#include "radio/radio_audio.h" |
|
|
|
|
@ -30,11 +31,17 @@ struct VoiceLatencyRange {
|
|
|
|
|
|
|
|
|
|
VoiceAudioIo::~VoiceAudioIo() { stop(); } |
|
|
|
|
|
|
|
|
|
VoiceAudioIo::OutputLatency VoiceAudioIo::outputLatency() const { |
|
|
|
|
std::lock_guard lock(m_latencyMutex); |
|
|
|
|
return m_running && voice_time_us() - m_outputLatency.sampledUs <= 500000 ? m_outputLatency : OutputLatency{}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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_playProgress.reset(); |
|
|
|
|
m_progressDropped = 0; |
|
|
|
|
{ std::lock_guard lock(m_latencyMutex); m_outputLatency = {}; } |
|
|
|
|
m_playPosition = 960; m_playEpoch = 1; |
|
|
|
|
m_captureDropped = m_referenceDropped = m_playbackUnderruns = 0; |
|
|
|
|
m_running = true; |
|
|
|
|
@ -43,7 +50,7 @@ bool VoiceAudioIo::start(Kind kind, uint64_t session) {
|
|
|
|
|
m_running = false; |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "voice-io: RX worker start failed reason=%s", error.what()); return false; |
|
|
|
|
} |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CALL, "voice-io: started session=%llu kind=%s PCM reserve<=40ms", |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CALL, "voice-io: started session=%llu kind=%s PCM ready=40ms plus partial block; calls adaptive=20..80ms", |
|
|
|
|
(unsigned long long)session, kind == Kind::Call ? "call" : "radio"); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
@ -134,7 +141,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, bool measured) { |
|
|
|
|
void VoiceAudioIo::mix(float* stereo, unsigned frames, int64_t firstTimeUs, bool measured, unsigned callbackFrames) { |
|
|
|
|
uint64_t epoch = m_playEpoch.load(); |
|
|
|
|
if (!firstTimeUs) firstTimeUs = voice_time_us(); |
|
|
|
|
unsigned consumed = 0; |
|
|
|
|
@ -156,7 +163,8 @@ void VoiceAudioIo::mix(float* stereo, unsigned frames, int64_t firstTimeUs, bool
|
|
|
|
|
unsigned remaining = m_playCurrent.epoch == epoch ? 960 - m_playPosition : 0; |
|
|
|
|
PlaybackProgress progress{epoch, m_playCurrent.position + m_playPosition, |
|
|
|
|
firstTimeUs + (int64_t)consumed * 1000000 / 48000, measured, |
|
|
|
|
consumed, frames - consumed, unsigned(m_playback.size() * 960) + remaining}; |
|
|
|
|
consumed, frames - consumed, unsigned(m_playback.size() * 960) + remaining, |
|
|
|
|
callbackFrames ? callbackFrames : frames}; |
|
|
|
|
if (!m_playProgress.push(progress)) m_progressDropped.fetch_add(1, std::memory_order_relaxed); |
|
|
|
|
m_wake.notify_all(); |
|
|
|
|
} |
|
|
|
|
@ -167,6 +175,8 @@ void VoiceAudioIo::playbackLoop() {
|
|
|
|
|
int64_t lastClockReport = 0; |
|
|
|
|
VoiceLatencyRange queueUs, pullUs; |
|
|
|
|
unsigned missing = 0; |
|
|
|
|
VoicePlaybackBuffer buffering; |
|
|
|
|
PlaybackProgress lastProgress{}; |
|
|
|
|
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); |
|
|
|
|
@ -182,12 +192,24 @@ void VoiceAudioIo::playbackLoop() {
|
|
|
|
|
else radio_audio_reset_playback(m_session); |
|
|
|
|
position = 0; |
|
|
|
|
epoch = requested; |
|
|
|
|
buffering.reset(); lastProgress = {}; |
|
|
|
|
{ std::lock_guard lock(m_latencyMutex); m_outputLatency = {}; } |
|
|
|
|
} |
|
|
|
|
PlaybackProgress progress{}; |
|
|
|
|
for (unsigned count = 0; count < 32 && m_playProgress.pop(progress); ++count) { |
|
|
|
|
if (progress.epoch != epoch) continue; |
|
|
|
|
if (m_kind == Kind::Call) { |
|
|
|
|
unsigned previous = buffering.blocks; |
|
|
|
|
if (progress.callbackFrames > 3840 && progress.callbackFrames != buffering.callbackFrames) |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "voice-io: output callback exceeds PCM capacity frames=%u", progress.callbackFrames); |
|
|
|
|
buffering.sample(voice_time_us(), progress.callbackFrames, progress.missing != 0); |
|
|
|
|
if (buffering.blocks != previous) |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CALL, "voice-io: PCM reserve session=%llu blocks=%u->%u callback=%u missing=%u", |
|
|
|
|
(unsigned long long)m_session, previous, buffering.blocks, progress.callbackFrames, progress.missing); |
|
|
|
|
} |
|
|
|
|
queueUs.add(int64_t(progress.queuedFrames) * 1000000 / 48000); |
|
|
|
|
missing += progress.missing; |
|
|
|
|
lastProgress = progress; |
|
|
|
|
if (!progress.consumed) continue; |
|
|
|
|
int64_t now = voice_time_us(), remaining = progress.endTimeUs - now; |
|
|
|
|
if (m_kind == Kind::Call) |
|
|
|
|
@ -200,6 +222,8 @@ void VoiceAudioIo::playbackLoop() {
|
|
|
|
|
int64_t reportTime = voice_time_us(); |
|
|
|
|
if (m_kind == Kind::Call && reportTime - lastClockReport >= 1000000) { |
|
|
|
|
lastClockReport = reportTime; |
|
|
|
|
if (missing) DEBUG_WARN(DEBUG_CATEGORY_CALL, "voice-io: output starved session=%llu missing_samples=%u ready_blocks=%u", |
|
|
|
|
(unsigned long long)m_session, missing, buffering.blocks); |
|
|
|
|
if (queueUs.count || pullUs.count) |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "voice-io: playback latency session=%llu samples=%u pulls=%u " |
|
|
|
|
"pcm_after_mix_us=%lld/%lld/%lld pull_us=%lld/%lld/%lld missing_samples=%u queue_estimate=1", |
|
|
|
|
@ -215,7 +239,7 @@ void VoiceAudioIo::playbackLoop() {
|
|
|
|
|
(unsigned long long)snapshot.played_samples, (unsigned long long)snapshot.written_samples, |
|
|
|
|
(long long)snapshot.pts_us, snapshot.valid, snapshot.measured, snapshot.held_back_us); |
|
|
|
|
} |
|
|
|
|
if (m_playback.size() < 2) { |
|
|
|
|
if (m_playback.size() < (m_kind == Kind::Call ? buffering.blocks : 2)) { |
|
|
|
|
Playback block{}; block.epoch = epoch; block.position = position; |
|
|
|
|
int64_t pullAt = voice_time_us(); |
|
|
|
|
if (m_kind == Kind::Call) { |
|
|
|
|
@ -230,6 +254,13 @@ void VoiceAudioIo::playbackLoop() {
|
|
|
|
|
std::unique_lock lock(m_waitMutex); |
|
|
|
|
m_wake.wait_for(lock, std::chrono::milliseconds(5)); |
|
|
|
|
} |
|
|
|
|
if (lastProgress.epoch == epoch && lastProgress.consumed && position >= lastProgress.position) { |
|
|
|
|
int64_t now = voice_time_us(); |
|
|
|
|
int64_t queuedUs = (position - lastProgress.position) * 1000000 / 48000; |
|
|
|
|
std::lock_guard lock(m_latencyMutex); |
|
|
|
|
m_outputLatency = {int(queuedUs / 1000), int((queuedUs + std::max(lastProgress.endTimeUs - now, int64_t(0))) / 1000), |
|
|
|
|
lastProgress.measured, now}; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|