Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions nativelib/src/main/cpp/presentation_scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ constexpr int64_t kNanosecondsPerSecond = 1000000000LL;
constexpr int64_t kNanosecondsPerMicrosecond = 1000LL;
constexpr int64_t kSubmitLeadNs = 2000000LL;
constexpr int64_t kPtsQuantizationSlackNs = kNanosecondsPerMicrosecond;
constexpr double kHighRefreshFps = 60.0;
constexpr double kNtscTripleBurstFps = 119.88;
constexpr int64_t kDriftDeadbandNs = 2000000LL;
constexpr int64_t kMaxDriftCorrectionPerFrameNs = 20000LL;
constexpr int kSevereLateFramesBeforeRebuffer = 2;
Expand All @@ -33,10 +35,15 @@ void PtsPresentationScheduler::Configure(double fps) {
// for 120 Hz submission overhead.
submitLeadNs_ = kSubmitLeadNs;
initialLeadNs_ = submitLeadNs_;
// Decoder callbacks commonly arrive in pairs. Preserve one frame of PTS
// spacing before treating future lead as backlog. The extra microsecond
// covers integer PTS quantization at rates such as 120 and 59.94 FPS.
maxFutureLeadNs_ = initialLeadNs_ + frameIntervalNs_ +
// Preserve enough PTS spacing for the burst depth observed at each refresh
// tier without widening the latency budget for lower frame rates.
int64_t futureCadenceBudgetNs = frameIntervalNs_ / 2;
if (safeFps >= kNtscTripleBurstFps) {
futureCadenceBudgetNs = frameIntervalNs_ * 2;
} else if (safeFps > kHighRefreshFps) {
futureCadenceBudgetNs = frameIntervalNs_;
}
maxFutureLeadNs_ = initialLeadNs_ + futureCadenceBudgetNs +
kPtsQuantizationSlackNs;
discontinuityNs_ = std::max<int64_t>(250000000LL, frameIntervalNs_ * 12);
Reset();
Expand Down
2 changes: 1 addition & 1 deletion nativelib/src/main/cpp/presentation_scheduler.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class PtsPresentationScheduler {
int64_t frameIntervalNs_ = 16666667LL;
int64_t initialLeadNs_ = 2000000LL;
int64_t submitLeadNs_ = 2000000LL;
int64_t maxFutureLeadNs_ = 18667667LL;
int64_t maxFutureLeadNs_ = 10334333LL;
int64_t discontinuityNs_ = 250000000LL;

bool initialized_ = false;
Expand Down
80 changes: 75 additions & 5 deletions nativelib/src/test/cpp/presentation_scheduler_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,90 @@
namespace {
constexpr int64_t kMs = 1000000LL;

void Test120FpsPairBurstKeepsPtsCadence() {
void Test120FpsTripleBurstKeepsPtsCadence() {
PtsPresentationScheduler scheduler;
scheduler.Configure(120.0);

const PresentationPlan first = scheduler.PlanFrame(0, 1000 * kMs);
const PresentationPlan second = scheduler.PlanFrame(8334, 1000 * kMs);
const PresentationPlan third = scheduler.PlanFrame(16667, 1001 * kMs);
const PresentationPlan fourth = scheduler.PlanFrame(25000, 1001 * kMs);
const int64_t frameIntervalNs = static_cast<int64_t>(
std::llround(1000000000.0 / 120.0));

assert(first.action == PresentationAction::SCHEDULE);
assert(second.action == PresentationAction::SCHEDULE);
assert(third.action == PresentationAction::SCHEDULE);
assert(fourth.action == PresentationAction::SCHEDULE);
assert(second.event == PresentationEvent::NONE);
assert(third.event == PresentationEvent::CATCH_UP);
assert(third.event == PresentationEvent::NONE);
assert(fourth.event == PresentationEvent::CATCH_UP);
assert(first.targetTimeNs - 1000 * kMs == scheduler.GetInitialLeadNs());
assert(scheduler.GetInitialLeadNs() == 2 * kMs);
assert(scheduler.GetMaxFutureLeadNs() ==
scheduler.GetInitialLeadNs() + frameIntervalNs * 2 + 1000LL);
assert(second.targetTimeNs - first.targetTimeNs == 8334 * 1000LL);
assert(third.targetTimeNs - 1001 * kMs == scheduler.GetInitialLeadNs());
assert(third.targetTimeNs - second.targetTimeNs == 8333 * 1000LL);
assert(fourth.targetTimeNs - 1001 * kMs == scheduler.GetInitialLeadNs());
}

void Test90FpsPairBurstKeepsSingleFrameBudget() {
PtsPresentationScheduler scheduler;
scheduler.Configure(90.0);

const int64_t decodedAtNs = 1000 * kMs;
const PresentationPlan first = scheduler.PlanFrame(0, decodedAtNs);
const PresentationPlan second = scheduler.PlanFrame(11111, decodedAtNs);
const PresentationPlan third = scheduler.PlanFrame(22222, decodedAtNs + kMs);
const int64_t frameIntervalNs = static_cast<int64_t>(
std::llround(1000000000.0 / 90.0));

assert(first.action == PresentationAction::SCHEDULE);
assert(second.action == PresentationAction::SCHEDULE);
assert(third.action == PresentationAction::SCHEDULE);
assert(second.event == PresentationEvent::NONE);
assert(third.event == PresentationEvent::CATCH_UP);
assert(scheduler.GetMaxFutureLeadNs() ==
scheduler.GetInitialLeadNs() + frameIntervalNs + 1000LL);
}

void TestNtsc120FpsUsesTripleBurstBudget() {
PtsPresentationScheduler scheduler;
scheduler.Configure(119.88);

const int64_t frameIntervalNs = static_cast<int64_t>(
std::llround(1000000000.0 / 119.88));
assert(scheduler.GetMaxFutureLeadNs() ==
scheduler.GetInitialLeadNs() + frameIntervalNs * 2 + 1000LL);

scheduler.Configure(119.0);
const int64_t lowerFrameIntervalNs = static_cast<int64_t>(
std::llround(1000000000.0 / 119.0));
assert(scheduler.GetMaxFutureLeadNs() ==
scheduler.GetInitialLeadNs() + lowerFrameIntervalNs + 1000LL);
}

void AssertLowRefreshBurstCatchesUp(double fps, int64_t framePtsUs) {
PtsPresentationScheduler scheduler;
scheduler.Configure(fps);

const int64_t decodedAtNs = 1000 * kMs;
const PresentationPlan first = scheduler.PlanFrame(0, decodedAtNs);
const PresentationPlan burst = scheduler.PlanFrame(framePtsUs, decodedAtNs);
const int64_t frameIntervalNs = static_cast<int64_t>(
std::llround(1000000000.0 / fps));

assert(first.action == PresentationAction::SCHEDULE);
assert(burst.action == PresentationAction::SCHEDULE);
assert(burst.event == PresentationEvent::CATCH_UP);
assert(burst.targetTimeNs - decodedAtNs == scheduler.GetInitialLeadNs());
assert(scheduler.GetMaxFutureLeadNs() ==
scheduler.GetInitialLeadNs() + frameIntervalNs / 2 + 1000LL);
}

void TestLowRefreshBurstKeepsHalfFrameBudget() {
AssertLowRefreshBurstCatchesUp(30.0, 33333);
AssertLowRefreshBurstCatchesUp(60.0, 16667);
}

void TestSmallLateFrameShiftsWholeTimeline() {
Expand Down Expand Up @@ -153,7 +220,7 @@ void TestFractionalFpsLatencyBudget() {
assert(first.action == PresentationAction::SCHEDULE);
assert(first.targetTimeNs - decodedAtNs == expectedLeadNs);
assert(scheduler.GetMaxFutureLeadNs() ==
expectedLeadNs + frameIntervalNs + 1000LL);
expectedLeadNs + frameIntervalNs / 2 + 1000LL);
}

void TestSlowClockDriftStaysBounded() {
Expand Down Expand Up @@ -181,7 +248,10 @@ void TestSlowClockDriftStaysBounded() {
} // namespace

int main() {
Test120FpsPairBurstKeepsPtsCadence();
Test120FpsTripleBurstKeepsPtsCadence();
Test90FpsPairBurstKeepsSingleFrameBudget();
TestNtsc120FpsUsesTripleBurstBudget();
TestLowRefreshBurstKeepsHalfFrameBudget();
TestSmallLateFrameShiftsWholeTimeline();
TestSevereLateDropThenRebuffer();
TestAccumulatedPhaseShiftRecoversQuickly();
Expand Down
Loading