Adapt desktop call PCM reserve to output period and underruns
This commit is contained in:
1 parent
cb9852e8d5
commit
cfa3ed1f61
3 files changed
+70
-6
No files matched your search
@@ -1,5 +1,6 @@
|
|||||||
#include "voice_audio_io.h"
|
#include "voice_audio_io.h"
|
||||||
#include "voice_capture_clock.h"
|
#include "voice_capture_clock.h"
|
||||||
|
#include "voice_playback_buffer.h"
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "call/call_audio.h"
|
#include "call/call_audio.h"
|
||||||
#include "radio/radio_audio.h"
|
#include "radio/radio_audio.h"
|
||||||
@@ -30,11 +31,17 @@ struct VoiceLatencyRange {
|
|||||||
|
|
||||||
VoiceAudioIo::~VoiceAudioIo() { stop(); }
|
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) {
|
bool VoiceAudioIo::start(Kind kind, uint64_t session) {
|
||||||
stop();
|
stop();
|
||||||
m_kind = kind; m_session = session;
|
m_kind = kind; m_session = session;
|
||||||
m_captureEvents.reset(); m_references.reset(); m_playback.reset(); m_playProgress.reset();
|
m_captureEvents.reset(); m_references.reset(); m_playback.reset(); m_playProgress.reset();
|
||||||
m_progressDropped = 0;
|
m_progressDropped = 0;
|
||||||
|
{ std::lock_guard lock(m_latencyMutex); m_outputLatency = {}; }
|
||||||
m_playPosition = 960; m_playEpoch = 1;
|
m_playPosition = 960; m_playEpoch = 1;
|
||||||
m_captureDropped = m_referenceDropped = m_playbackUnderruns = 0;
|
m_captureDropped = m_referenceDropped = m_playbackUnderruns = 0;
|
||||||
m_running = true;
|
m_running = true;
|
||||||
@@ -43,7 +50,7 @@ bool VoiceAudioIo::start(Kind kind, uint64_t session) {
|
|||||||
m_running = false;
|
m_running = false;
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "voice-io: RX worker start failed reason=%s", error.what()); return 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");
|
(unsigned long long)session, kind == Kind::Call ? "call" : "radio");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -134,7 +141,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, bool measured) {
|
void VoiceAudioIo::mix(float* stereo, unsigned frames, int64_t firstTimeUs, bool measured, unsigned callbackFrames) {
|
||||||
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;
|
||||||
@@ -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;
|
unsigned remaining = m_playCurrent.epoch == epoch ? 960 - m_playPosition : 0;
|
||||||
PlaybackProgress progress{epoch, m_playCurrent.position + m_playPosition,
|
PlaybackProgress progress{epoch, m_playCurrent.position + m_playPosition,
|
||||||
firstTimeUs + (int64_t)consumed * 1000000 / 48000, measured,
|
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);
|
if (!m_playProgress.push(progress)) m_progressDropped.fetch_add(1, std::memory_order_relaxed);
|
||||||
m_wake.notify_all();
|
m_wake.notify_all();
|
||||||
}
|
}
|
||||||
@@ -167,6 +175,8 @@ void VoiceAudioIo::playbackLoop() {
|
|||||||
int64_t lastClockReport = 0;
|
int64_t lastClockReport = 0;
|
||||||
VoiceLatencyRange queueUs, pullUs;
|
VoiceLatencyRange queueUs, pullUs;
|
||||||
unsigned missing = 0;
|
unsigned missing = 0;
|
||||||
|
VoicePlaybackBuffer buffering;
|
||||||
|
PlaybackProgress lastProgress{};
|
||||||
call_clock_snapshot clock{};
|
call_clock_snapshot clock{};
|
||||||
if (m_kind == Kind::Call && call_audio_get_clock(m_session, voice_time_us(), &clock) != 0)
|
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);
|
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);
|
else radio_audio_reset_playback(m_session);
|
||||||
position = 0;
|
position = 0;
|
||||||
epoch = requested;
|
epoch = requested;
|
||||||
|
buffering.reset(); lastProgress = {};
|
||||||
|
{ std::lock_guard lock(m_latencyMutex); m_outputLatency = {}; }
|
||||||
}
|
}
|
||||||
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;
|
||||||
|
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);
|
queueUs.add(int64_t(progress.queuedFrames) * 1000000 / 48000);
|
||||||
missing += progress.missing;
|
missing += progress.missing;
|
||||||
|
lastProgress = progress;
|
||||||
if (!progress.consumed) continue;
|
if (!progress.consumed) continue;
|
||||||
int64_t now = voice_time_us(), remaining = progress.endTimeUs - now;
|
int64_t now = voice_time_us(), remaining = progress.endTimeUs - now;
|
||||||
if (m_kind == Kind::Call)
|
if (m_kind == Kind::Call)
|
||||||
@@ -200,6 +222,8 @@ void VoiceAudioIo::playbackLoop() {
|
|||||||
int64_t reportTime = voice_time_us();
|
int64_t reportTime = voice_time_us();
|
||||||
if (m_kind == Kind::Call && reportTime - lastClockReport >= 1000000) {
|
if (m_kind == Kind::Call && reportTime - lastClockReport >= 1000000) {
|
||||||
lastClockReport = reportTime;
|
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)
|
if (queueUs.count || pullUs.count)
|
||||||
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "voice-io: playback latency session=%llu samples=%u pulls=%u "
|
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",
|
"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,
|
(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);
|
(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;
|
Playback block{}; block.epoch = epoch; block.position = position;
|
||||||
int64_t pullAt = voice_time_us();
|
int64_t pullAt = voice_time_us();
|
||||||
if (m_kind == Kind::Call) {
|
if (m_kind == Kind::Call) {
|
||||||
@@ -230,6 +254,13 @@ void VoiceAudioIo::playbackLoop() {
|
|||||||
std::unique_lock lock(m_waitMutex);
|
std::unique_lock lock(m_waitMutex);
|
||||||
m_wake.wait_for(lock, std::chrono::milliseconds(5));
|
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};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
class VoiceAudioIo {
|
class VoiceAudioIo {
|
||||||
public:
|
public:
|
||||||
enum class Kind { Call, Radio };
|
enum class Kind { Call, Radio };
|
||||||
|
struct OutputLatency { int queueMs = -1, outputMs = -1; bool measured = false; int64_t sampledUs = 0; };
|
||||||
|
OutputLatency outputLatency() const;
|
||||||
VoiceAudioIo() = default;
|
VoiceAudioIo() = default;
|
||||||
~VoiceAudioIo();
|
~VoiceAudioIo();
|
||||||
bool start(Kind kind, uint64_t session);
|
bool start(Kind kind, uint64_t session);
|
||||||
@@ -27,7 +29,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, bool measured = false);
|
void mix(float* stereo, unsigned frames, int64_t firstTimeUs = 0, bool measured = false, unsigned callbackFrames = 0);
|
||||||
private:
|
private:
|
||||||
enum class EventKind { Pcm, Ptt, Mute, Aec, Stop };
|
enum class EventKind { Pcm, Ptt, Mute, Aec, Stop };
|
||||||
struct Event {
|
struct Event {
|
||||||
@@ -45,7 +47,7 @@ private:
|
|||||||
uint64_t epoch = 0, position = 0;
|
uint64_t epoch = 0, position = 0;
|
||||||
int64_t endTimeUs = 0;
|
int64_t endTimeUs = 0;
|
||||||
bool measured = false;
|
bool measured = false;
|
||||||
unsigned consumed = 0, missing = 0, queuedFrames = 0;
|
unsigned consumed = 0, missing = 0, queuedFrames = 0, callbackFrames = 0;
|
||||||
};
|
};
|
||||||
AudioEventQueue<Event, 32> m_captureEvents, m_references;
|
AudioEventQueue<Event, 32> m_captureEvents, m_references;
|
||||||
AudioEventQueue<Playback, 12> m_playback;
|
AudioEventQueue<Playback, 12> m_playback;
|
||||||
@@ -60,6 +62,8 @@ private:
|
|||||||
std::atomic<bool> m_aecRequested{false};
|
std::atomic<bool> m_aecRequested{false};
|
||||||
std::thread m_captureThread, m_playbackThread;
|
std::thread m_captureThread, m_playbackThread;
|
||||||
std::mutex m_waitMutex;
|
std::mutex m_waitMutex;
|
||||||
|
mutable std::mutex m_latencyMutex; // Только worker и GUI; callback не берёт lock.
|
||||||
|
OutputLatency m_outputLatency;
|
||||||
std::condition_variable m_wake;
|
std::condition_variable m_wake;
|
||||||
Playback m_playCurrent{};
|
Playback m_playCurrent{};
|
||||||
unsigned m_playPosition = 960;
|
unsigned m_playPosition = 960;
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
/* Резерв готового PCM desktop. Размер callback задаёт нижнюю границу; недостача добавляет
|
||||||
|
* один блок, 10 секунд стабильного вывода убирают один. Частично сыгранный блок — сверх резерва.
|
||||||
|
* Только RX worker; время монотонное. Четыре PCM-слота доступны в AudioEventQueue<Playback, 12>. */
|
||||||
|
class VoicePlaybackBuffer {
|
||||||
|
public:
|
||||||
|
unsigned blocks = 2;
|
||||||
|
unsigned callbackFrames = 960;
|
||||||
|
void reset() { blocks = 2; callbackFrames = 960; stableSince = 0; lastGrowth = 0; }
|
||||||
|
void sample(int64_t nowUs, unsigned frames, bool missing) {
|
||||||
|
callbackFrames = frames;
|
||||||
|
unsigned minimum = std::clamp((frames + 959) / 960, 1u, 4u);
|
||||||
|
if (!stableSince) stableSince = nowUs;
|
||||||
|
if (minimum > blocks) { blocks = minimum; stableSince = nowUs; }
|
||||||
|
if (missing) {
|
||||||
|
stableSince = nowUs;
|
||||||
|
if (!lastGrowth || nowUs - lastGrowth >= 500000) {
|
||||||
|
blocks = std::min(blocks + 1, 4u); lastGrowth = nowUs;
|
||||||
|
}
|
||||||
|
} else if (blocks > minimum && nowUs - stableSince >= 10000000) {
|
||||||
|
--blocks; stableSince = nowUs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
int64_t stableSince = 0, lastGrowth = 0;
|
||||||
|
};
|
||||||
Reference in new issue
Block a user