Gate startup on published PCM and test recovery beyond the initial reserve

This commit is contained in:
evgeny committed 2026-10-30 19:57:25 +01:00
1 parent b092e09b43
commit 0c20766cfe
3 files changed
+42 -4

No files matched your search

+9
View File
@@ -58,4 +58,13 @@ public:
uint64_t count = m_write.load(std::memory_order_acquire) - read;
return (size_t)(count > Capacity ? Capacity : count); // Приблизительный снимок, включая ещё не опубликованные слоты.
}
/* Только consumer: число подряд опубликованных элементов. Для порога старта PCM
* size() недостаточно: зарезервированный producer-ом блок ещё нельзя воспроизводить. */
size_t readable() const {
uint64_t position = m_read.load(std::memory_order_relaxed);
size_t count = 0;
while (count < Capacity && m_slots[(position + count) % Capacity].sequence.load(std::memory_order_acquire) == position + count + 1)
++count;
return count;
}
};
+1 -1
View File
@@ -157,7 +157,7 @@ void VoiceAudioIo::mix(float* stereo, unsigned frames, int64_t firstTimeUs, bool
for (int attempt = 0; m_playPosition == 960 && attempt < 12 && m_playback.pop(m_playCurrent); ++attempt)
if (m_playCurrent.epoch == epoch) m_playPosition = 0;
unsigned required = std::max(1920u, callbackFrames ? callbackFrames : frames);
unsigned ready = unsigned(m_playback.size() * 960) + (m_playCurrent.epoch == epoch ? 960 - m_playPosition : 0);
unsigned ready = unsigned(m_playback.readable() * 960) + (m_playCurrent.epoch == epoch ? 960 - m_playPosition : 0);
if (ready < required) {
// PREBUFFER: устройство работает, но речь ещё не началась. Это не starvation.
PlaybackProgress waiting{}; waiting.epoch = epoch; waiting.callbackFrames = callbackFrames ? callbackFrames : frames;
+32 -3
View File
@@ -146,32 +146,61 @@ static void test_startup_policy() {
p.observe(15101, 350, 1000, true); CHECK(p.reserve_ms == 350);
p.observe(15102, 20, 1000, true); CHECK(p.reserve_ms == 350);
for (bool abrupt : {false, true}) {
decoder d; auto* j = call_jitter_create(decode, &d, 1000);
int next = 0, packets = 0, batch = 4, nominal = 80, due = nominal;
int16_t pcm[480]; bool played = false;
int16_t pcm[480]; bool played = false; uint32_t previous_underruns = 0;
for (int now = 0; now < 20000; now += 10) {
if (now >= due) {
for (int i = 0; i < batch; i++, next++)
call_jitter_push(j, (uint8_t*)&next, sizeof(next), nominal, now, nullptr, i == 0);
if (++packets == 60) batch = 10;
++packets;
if (abrupt && packets == 60) batch = 10;
if (!abrupt && packets % 20 == 0 && batch < 10) ++batch;
nominal += batch * 20; due = nominal + (packets % 4 == 0 ? 120 : 0);
}
int n = call_jitter_pull(j, pcm, 480);
played |= n > 0;
if (n < 480) call_jitter_silence(j, 480 - n);
call_jitter_diagnostics state{}; call_jitter_get_diagnostics(j, &state, 0);
CHECK(state.underruns == 0);
if (state.underruns != previous_underruns) {
printf("startup shortage: now=%d next=%d batch=%d due=%d depth=%d encoded=%d reserve=%d tempo=%d\n",
now, next, batch, due, state.depth_ms, state.encoded_ms, state.reserve_ms, state.tempo_x100);
// Резкий рост 4->10 добавляет 120мс к пачке плюс 120мс джиттера: больше исходных 200мс.
CHECK(abrupt && now < 6000 && state.underruns == 1); previous_underruns = state.underruns;
}
if (now < 5000) CHECK(state.reserve_ms >= 200);
}
CHECK(played);
CHECK(previous_underruns == (abrupt ? 1u : 0u));
call_jitter_destroy(j);
}
puts("startup policy: cold start, held reserve, packet size change and jitter PASS");
}
static void test_render_readiness() {
const uint64_t id = 990;
CHECK(call_audio_start(id) == 0);
int16_t pcm[960] = {}; call_audio_render state{}; call_clock_snapshot clock{};
for (int i = 0; i < 10; i++) {
CHECK(call_audio_render_pcm(id, pcm, 960, &state) == 0);
CHECK(state.phase == CALL_AUDIO_WAITING && state.speech_samples == 0);
}
CHECK(call_audio_get_clock(id, 0, &clock) == 0 && clock.written_samples == 0);
auto* enc = opus_codec_encoder_create(48000, 1); CHECK(enc);
uint8_t wire[512]; int len = opus_codec_encode(enc, pcm, 960, wire, sizeof(wire)); CHECK(len > 0);
for (int i = 0; i < 12; i++) call_audio_on_media(&audio_owner, id, i, 0, wire, len, nullptr, 1);
CHECK(call_audio_render_pcm(id, pcm, 960, &state) == 960);
CHECK(state.phase == CALL_AUDIO_SPEECH && state.speech_samples == 960); // Закодированная тишина тоже настоящая речь.
CHECK(call_audio_get_clock(id, 0, &clock) == 0 && clock.written_samples == 960);
call_audio_release(id); opus_codec_encoder_destroy(enc);
}
int main() {
test_startup_policy();
test_packet_arrival_sampling();
CHECK(call_audio_init(&audio_owner) == 0);
test_render_readiness();
{
call_jitter_window w;
w.observe(0, 100); w.observe(500, 400);