Exercise acknowledged frame timing, lossless stalls and queue-dependent deceleration

This commit is contained in:
evgeny committed 2026-10-19 19:12:23 +02:00
1 parent 8eb869e41c
commit 5f45b38cfb
2 files changed
+66 -40

No files matched your search

+1 -1
View File
@@ -83,7 +83,7 @@ enum call_video_playout_action call_video_playout_poll(struct call_video_playout
if (target > VIDEO_MAX_SPEED_PPM) target = VIDEO_MAX_SPEED_PPM;
int64_t elapsed = p->rate_us ? now - p->rate_us : 0;
if (elapsed > 100000) elapsed = 100000;
if (elapsed > 0) p->speed_ppm += (target - p->speed_ppm) * elapsed / (200000 + elapsed);
if (elapsed > 0) p->speed_ppm = (uint32_t)((int64_t)p->speed_ppm + (target - p->speed_ppm) * elapsed / (200000 + elapsed));
p->rate_us = now;
int64_t delta = p->last_pts >= 0 ? f->pts_us - p->last_pts : 0;
int64_t step = (int64_t)f->interval_us * 1000000 / p->speed_ppm;
+65 -39
View File
@@ -1,4 +1,4 @@
/* Управляемое время: весь поток сохраняется при паузах сети/CPU и ускорении аудио. */
/* Управляемые arrival/presentation clocks: FIFO сохраняется при паузах сети/CPU/GUI. */
#include <stdlib.h>
#include "../src/call/call_video_playout.h"
#include "../lib/debug_config.h"
@@ -16,48 +16,62 @@ static void simulate(unsigned pause_ms, int cpu, int64_t quantum, int accelerate
arrival = origin + 1000000 + pause_ms * 1000ll;
frames[i] = (struct call_video_playout_frame){origin + i * 33333ll, arrival, 33333};
}
unsigned shown = 0, accelerated = 0;
int64_t last_show = 0, recovered_lag = INT64_MAX, audio_pts = origin - (accelerated_audio ? 150000 : 0);
for (int64_t now = origin; now < origin + 20000000 && shown < COUNT; now += quantum) {
unsigned selected = 0, shown = 0, available = 0, accelerated = 0;
int64_t last_show = 0, ack_at = 0, recovered_lag = INT64_MAX, audio_pts = origin;
for (int64_t now = origin; now < origin + 22000000 && shown < COUNT; now += quantum) {
uint32_t rate = accelerated_audio && now >= origin + 1500000 && now < origin + 2500000 ? 1100000 : 1000000;
if (now > origin) audio_pts += quantum * rate / 1000000;
if (cpu && now >= origin + 1000000 && now < origin + 1000000 + pause_ms * 1000ll) continue;
while (available < COUNT && frames[available].received_us <= now) ++available;
if (ack_at && now >= ack_at) {
call_video_playout_presented(&p, frames[shown].pts_us, now);
CHECK(!last_show || now > last_show); last_show = now; ++shown; ack_at = 0;
}
struct call_clock_snapshot audio = {.pts_us = audio_pts, .rate_ppm = rate,
.valid = !(now >= origin + 4000000 && now < origin + 4100000)};
struct call_video_playout_frame *f = frames + shown;
int action = call_video_playout_poll(&p, f->received_us <= now ? f : NULL, NULL, now, &audio);
const struct call_video_playout_frame *f = selected < available ? frames + selected : NULL;
int action = call_video_playout_poll(&p, f, now, &audio, available - shown, 1);
if (now >= origin + 6000000 && shown) recovered_lag = audio.pts_us - frames[shown - 1].pts_us;
if (action == CALL_VIDEO_PLAYOUT_WAIT) continue;
CHECK(action == CALL_VIDEO_PLAYOUT_SHOW && audio.valid);
CHECK(!last_show || now > last_show);
CHECK(action == CALL_VIDEO_PLAYOUT_SHOW && audio.valid && !ack_at);
CHECK(f->pts_us - audio.pts_us <= 40000);
if (p.speed_ppm > 1050000) accelerated++;
if (now >= origin + 6000000) recovered_lag = audio.pts_us - f->pts_us;
last_show = now; shown++;
// Даже готовая очередь после фриза не выдаётся целиком за один вызов worker.
if (shown < COUNT && frames[shown].received_us <= now)
CHECK(call_video_playout_poll(&p, frames + shown, NULL, now, &audio) == CALL_VIDEO_PLAYOUT_WAIT);
if (p.speed_ppm > 1050000) ++accelerated;
++selected; ack_at = now + 3000; // Не мгновенный GUI: кадр остаётся в общем запасе до ACK.
CHECK(call_video_playout_poll(&p, selected < available ? frames + selected : NULL, now, &audio,
available - shown, 1) == CALL_VIDEO_PLAYOUT_WAIT);
}
DEBUG_INFO(DEBUG_CATEGORY_CALL, "sequential recovery pause_ms=%u cpu=%d quantum_us=%lld audio_fast=%d shown=%u/%u lag_us=%lld",
DEBUG_INFO(DEBUG_CATEGORY_CALL, "presentation recovery pause_ms=%u cpu=%d quantum_us=%lld audio_fast=%d shown=%u/%u lag_us=%lld",
pause_ms, cpu, (long long)quantum, accelerated_audio, shown, COUNT, (long long)recovered_lag);
CHECK(shown == COUNT && accelerated && recovered_lag < 50000);
CHECK(shown == COUNT && accelerated);
CHECK(recovered_lag < (quantum < 2000 ? 100000 : 220000));
}
static void order_and_clock(void) {
static void presentation_deadline(void) {
struct call_video_playout p; call_video_playout_init(&p, 9);
struct call_video_playout_frame f = {origin, origin, 33333};
struct call_clock_snapshot audio = {.pts_us = origin, .rate_ppm = 1000000};
CHECK(call_video_playout_poll(&p, &f, NULL, origin, &audio) == CALL_VIDEO_PLAYOUT_WAIT);
audio.valid = 1;
CHECK(call_video_playout_poll(&p, &f, NULL, origin, &audio) == CALL_VIDEO_PLAYOUT_SHOW);
CHECK(call_video_playout_poll(&p, &f, NULL, origin + 1000, &audio) == CALL_VIDEO_PLAYOUT_DROP_ORDER);
f.pts_us += 33333;
audio.valid = 0;
CHECK(call_video_playout_poll(&p, &f, NULL, origin + 200000, &audio) == CALL_VIDEO_PLAYOUT_WAIT);
audio.valid = 1; audio.pts_us += 200000;
CHECK(call_video_playout_poll(&p, &f, NULL, origin + 200000, &audio) == CALL_VIDEO_PLAYOUT_SHOW);
struct call_clock_snapshot audio = {.pts_us = origin, .rate_ppm = 1000000, .valid = 1};
CHECK(call_video_playout_poll(&p, &f, origin, &audio, 2, 1) == CALL_VIDEO_PLAYOUT_SHOW);
CHECK(p.shown == 0 && p.awaiting); // Выбор кадра ещё не показ.
call_video_playout_presented(&p, f.pts_us, origin);
CHECK(p.shown == 1 && !p.awaiting);
CHECK(call_video_playout_poll(&p, NULL, origin + 5000, &audio, 0, 1) == CALL_VIDEO_PLAYOUT_WAIT);
f.pts_us += 33333; f.received_us = origin + 12000; audio.pts_us += 12000;
CHECK(call_video_playout_poll(&p, &f, origin + 12000, &audio, 1, 1) == CALL_VIDEO_PLAYOUT_WAIT);
int64_t deadline = p.deadline_us;
audio.pts_us += 12000;
CHECK(call_video_playout_poll(&p, &f, origin + 24000, &audio, 8, 1) == CALL_VIDEO_PLAYOUT_WAIT);
CHECK(p.deadline_us == deadline); // Приход пачки и движение audio clock не двигают уже назначенный срок.
CHECK(call_video_playout_poll(&p, &f, origin + 35000, &audio, 8, 1) == CALL_VIDEO_PLAYOUT_SHOW);
call_video_playout_presented(&p, f.pts_us, origin + 40000);
CHECK(p.last_show_us == origin + 40000);
CHECK(call_video_playout_poll(&p, &f, origin + 41000, &audio, 1, 1) == CALL_VIDEO_PLAYOUT_DROP_ORDER);
f.pts_us += 33333; audio.valid = 0;
CHECK(call_video_playout_poll(&p, &f, origin + 200000, &audio, 1, 1) == CALL_VIDEO_PLAYOUT_WAIT);
audio.valid = 1; audio.pts_us = origin + 200000;
CHECK(call_video_playout_poll(&p, &f, origin + 200000, &audio, 1, 1) == CALL_VIDEO_PLAYOUT_SHOW);
DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS presentation clock: 12ms starvation does not collapse cadence; ACK alone advances time");
}
/* Готовые кадры могут разделяться паузой raw capture, пока decoder lookahead не пустел. */
static void queued_capture_gap(void) {
const int64_t gaps[] = {500000, 2264933, 10000000};
const unsigned fps[] = {15, 30, 60};
@@ -66,20 +80,32 @@ static void queued_capture_gap(void) {
unsigned interval = 1000000 / fps[j];
struct call_video_playout_frame first = {origin, origin, interval}, next = {origin + gaps[i], origin, interval};
struct call_clock_snapshot audio = {.pts_us = next.pts_us + 1000000, .valid = 1, .rate_ppm = 1000000};
CHECK(call_video_playout_poll(&p, &first, &next, origin, &audio) == CALL_VIDEO_PLAYOUT_SHOW);
CHECK(call_video_playout_poll(&p, &next, NULL, origin, &audio) == CALL_VIDEO_PLAYOUT_WAIT);
CHECK(call_video_playout_poll(&p, &next, NULL, origin + interval, &audio) == CALL_VIDEO_PLAYOUT_SHOW);
CHECK(call_video_playout_poll(&p, &first, origin, &audio, 2, 1) == CALL_VIDEO_PLAYOUT_SHOW);
call_video_playout_presented(&p, first.pts_us, origin);
CHECK(call_video_playout_poll(&p, &next, origin, &audio, 1, 1) == CALL_VIDEO_PLAYOUT_WAIT);
CHECK(call_video_playout_poll(&p, &next, origin + interval + 2000, &audio, 1, 1) == CALL_VIDEO_PLAYOUT_SHOW);
CHECK(p.last_pts == next.pts_us && p.source_gaps == 1);
}
DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS queued capture gaps: 0.5/2.265/10s at 15/30/60fps, no repeated wait");
}
static void slowing_with_queue(void) {
struct call_video_playout p; call_video_playout_init(&p, 12);
struct call_video_playout_frame f = {origin, origin, 33333};
struct call_clock_snapshot audio = {.pts_us = origin, .valid = 1, .rate_ppm = 1000000};
CHECK(call_video_playout_poll(&p, &f, NULL, origin, &audio) == CALL_VIDEO_PLAYOUT_SHOW);
f.pts_us += 2000000;
CHECK(call_video_playout_poll(&p, &f, NULL, origin + 33333, &audio) == CALL_VIDEO_PLAYOUT_WAIT);
audio.pts_us = f.pts_us - 40000;
CHECK(call_video_playout_poll(&p, &f, NULL, origin + 50000, &audio) == CALL_VIDEO_PLAYOUT_SHOW);
DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS queued capture gaps: 0.5/2.265/10s at 15/30/60fps, no repeated wait, no audio lead violation");
struct call_video_playout_frame f = {origin, origin, 33333};
uint32_t fast = 0; unsigned shows = 0;
for (int64_t now = origin; now < origin + 2000000; now += 1000) {
audio.pts_us = now;
unsigned queue = now < origin + 1000000 ? 8 : 2;
int action = call_video_playout_poll(&p, &f, now, &audio, queue, 1);
if (action == CALL_VIDEO_PLAYOUT_SHOW) {
call_video_playout_presented(&p, f.pts_us, now);
f.pts_us += 33333; ++shows;
}
if (now == origin + 999000) fast = p.speed_ppm;
}
CHECK(shows && fast > 1500000 && p.speed_ppm < 1050000);
DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS queue governor: speed_ppm=%u -> %u as total frames fall 8 -> 2", fast, p.speed_ppm);
}
int main(void) {
@@ -90,5 +116,5 @@ int main(void) {
simulate(pauses[i], cpu, 1000, 0);
simulate(pauses[i], cpu, 15625, 1);
}
order_and_clock(); queued_capture_gap(); return 0;
presentation_deadline(); queued_capture_gap(); slowing_with_queue(); return 0;
}