Account for full refresh-cycle GUI acknowledgement latency
This commit is contained in:
1 parent
d8d9f7c4a4
commit
0a73d140e6
2 files changed
+52
-11
No files matched your search
@@ -170,7 +170,9 @@ void call_video_playout_presented(struct call_video_playout *p, int64_t pts, int
|
||||
const struct call_video_playout_frame *f = &p->pending;
|
||||
int64_t av = p->pending_av, lead = now - p->selected_us;
|
||||
// GUI freeze не становится постоянным опережением dispatch для последующих кадров.
|
||||
if (lead <= f->interval_us / 2) p->lead_us = (p->lead_us * 7 + lead) / 8;
|
||||
// ACK TextureView/композитора может занимать целый период экрана. Его надо вычесть
|
||||
// из dispatch следующего кадра; иначе один ACK на 60 Hz искусственно снижает FPS.
|
||||
if (lead <= f->interval_us + f->interval_us / 4 + 1000) p->lead_us = (p->lead_us * 7 + lead) / 8;
|
||||
int64_t error = now - p->head_show_us;
|
||||
if (!p->anchored || error > f->interval_us / 2 || error < -(int64_t)f->interval_us / 2) p->last_deadline = now;
|
||||
else {
|
||||
|
||||
@@ -16,33 +16,35 @@ 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 selected = 0, shown = 0, available = 0, accelerated = 0;
|
||||
unsigned selected = 0, shown = 0, skipped = 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) {
|
||||
for (int64_t now = origin; now < origin + 22000000 && shown + skipped < 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);
|
||||
call_video_playout_presented(&p, frames[selected - 1].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)};
|
||||
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;
|
||||
int action = call_video_playout_poll(&p, f, now, &audio, available - shown - skipped, 1);
|
||||
if (now >= origin + 6000000 && shown) recovered_lag = audio.pts_us - p.last_show_pts;
|
||||
if (action == CALL_VIDEO_PLAYOUT_WAIT) continue;
|
||||
if (action == CALL_VIDEO_PLAYOUT_DROP_LATE) { ++selected; ++skipped; continue; }
|
||||
CHECK(action == CALL_VIDEO_PLAYOUT_SHOW && audio.valid && !ack_at);
|
||||
CHECK(f->pts_us - audio.pts_us <= 40000);
|
||||
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);
|
||||
available - shown - skipped, 1) == CALL_VIDEO_PLAYOUT_WAIT);
|
||||
}
|
||||
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);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "presentation recovery pause_ms=%u cpu=%d quantum_us=%lld audio_fast=%d shown=%u skipped=%u total=%u lag_us=%lld",
|
||||
pause_ms, cpu, (long long)quantum, accelerated_audio, shown, skipped, COUNT, (long long)recovered_lag);
|
||||
CHECK(shown + skipped == COUNT && accelerated);
|
||||
if (quantum < 2000 || pause_ms <= 100) CHECK(skipped == 0);
|
||||
CHECK(recovered_lag < (quantum < 2000 ? 100000 : 220000));
|
||||
}
|
||||
|
||||
@@ -129,6 +131,42 @@ static void render_latency_change(void) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS render latency 3->9ms: phase_us=%lld; no accumulated per-frame GUI delay", (long long)phase);
|
||||
}
|
||||
|
||||
/* Вход 60 fps, показ ограничен 60 Hz: после паузы ускориться физически нельзя.
|
||||
* Каждый вход считается decoded; теряется лишь показ, ACK остаётся единственным владельцем часов. */
|
||||
static void limited_display(unsigned pause_ms) {
|
||||
enum { COUNT = 600 };
|
||||
struct call_video_playout p; call_video_playout_init(&p, 14);
|
||||
unsigned consumed = 0, shown = 0, skipped = 0;
|
||||
int64_t ack_at = 0, pending_pts = 0, recovered = INT64_MAX;
|
||||
for (int64_t now = origin; now < origin + 12000000 && consumed < COUNT; now += 1000) {
|
||||
unsigned available = (unsigned)((now - origin) * 60 / 1000000) + 1;
|
||||
if (available > COUNT) available = COUNT;
|
||||
if (ack_at && now >= ack_at) {
|
||||
call_video_playout_presented(&p, pending_pts, now); ++shown; ack_at = 0;
|
||||
}
|
||||
if (now >= origin + 1000000 && now < origin + 1000000 + pause_ms * 1000ll) continue;
|
||||
if (consumed >= available) continue;
|
||||
struct call_video_playout_frame f = {origin + consumed * 1000000ll / 60, origin, 16666};
|
||||
struct call_clock_snapshot audio = {.pts_us = now, .rate_ppm = 1000000, .valid = 1};
|
||||
const int64_t before = p.last_show_us, deadline = p.last_deadline;
|
||||
int action = call_video_playout_poll(&p, &f, now, &audio, available - consumed + (ack_at != 0), 1);
|
||||
if (action == CALL_VIDEO_PLAYOUT_SHOW) {
|
||||
++consumed; pending_pts = f.pts_us;
|
||||
ack_at = origin + ((now - origin) * 60 / 1000000 + 1) * 1000000 / 60;
|
||||
} else if (action == CALL_VIDEO_PLAYOUT_DROP_LATE) {
|
||||
++consumed; ++skipped;
|
||||
CHECK(p.last_show_us == before && p.last_deadline == deadline && !p.awaiting);
|
||||
} else CHECK(action == CALL_VIDEO_PLAYOUT_WAIT);
|
||||
if (now > origin + 6000000) recovered = now - f.pts_us;
|
||||
}
|
||||
if (ack_at) { call_video_playout_presented(&p, pending_pts, ack_at); ++shown; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "60Hz recovery pause_ms=%u decoded=%u shown=%u skipped=%u lag_us=%lld",
|
||||
pause_ms, consumed, shown, skipped, (long long)recovered);
|
||||
CHECK(consumed == COUNT && shown + skipped == COUNT);
|
||||
if (pause_ms <= 100) CHECK(skipped == 0);
|
||||
else CHECK(skipped > 0 && recovered < 180000);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO);
|
||||
const unsigned pauses[] = {50, 100, 200, 500};
|
||||
@@ -137,5 +175,6 @@ int main(void) {
|
||||
simulate(pauses[i], cpu, 1000, 0);
|
||||
simulate(pauses[i], cpu, 15625, 1);
|
||||
}
|
||||
presentation_deadline(); queued_capture_gap(); slowing_with_queue(); render_latency_change(); return 0;
|
||||
presentation_deadline(); queued_capture_gap(); slowing_with_queue(); render_latency_change();
|
||||
limited_display(50); limited_display(100); limited_display(500); return 0;
|
||||
}
|
||||
Reference in new issue
Block a user