Gate startup on published PCM and test recovery beyond the initial reserve
This commit is contained in:
1 parent
b092e09b43
commit
0c20766cfe
3 files changed
+42
-4
No files matched your search
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user