Files
video-shader-toys/tests/VideoPlayoutSchedulerTests.cpp
2026-05-12 00:52:33 +10:00

150 lines
5.6 KiB
C++

#include "VideoPlayoutScheduler.h"
#include <cmath>
#include <iostream>
namespace
{
int gFailures = 0;
void Expect(bool condition, const char* message)
{
if (condition)
return;
std::cerr << "FAIL: " << message << "\n";
++gFailures;
}
void ExpectNear(double actual, double expected, double tolerance, const char* message)
{
Expect(std::fabs(actual - expected) <= tolerance, message);
}
void TestScheduleAdvancesFromZero()
{
VideoPlayoutScheduler scheduler;
scheduler.Configure(1001, 60000);
const VideoIOScheduleTime first = scheduler.NextScheduleTime();
const VideoIOScheduleTime second = scheduler.NextScheduleTime();
const VideoIOScheduleTime third = scheduler.NextScheduleTime();
Expect(first.streamTime == 0, "first frame starts at stream time zero");
Expect(first.duration == 1001, "duration is preserved");
Expect(first.timeScale == 60000, "time scale is preserved");
Expect(second.streamTime == 1001, "second frame advances by one duration");
Expect(third.streamTime == 2002, "third frame advances by two durations");
}
void TestLateAndDroppedRecoveryUsesMeasuredPressure()
{
VideoPlayoutPolicy policy;
policy.lateOrDropCatchUpFrames = 2;
VideoPlayoutScheduler scheduler;
scheduler.Configure(1000, 50000, policy);
(void)scheduler.NextScheduleTime();
VideoPlayoutRecoveryDecision lateDecision = scheduler.AccountForCompletionResult(VideoIOCompletionResult::DisplayedLate, 2);
Expect(lateDecision.catchUpFrames == 1, "single late completion catches up by measured one-frame lag");
Expect(lateDecision.lateStreak == 1, "late completion increments late streak");
Expect(scheduler.NextScheduleTime().streamTime == 2000, "single late recovery advances by measured lag");
VideoPlayoutRecoveryDecision dropDecision = scheduler.AccountForCompletionResult(VideoIOCompletionResult::Dropped, 2);
Expect(dropDecision.catchUpFrames == 2, "dropped completion catches up by measured drop pressure");
Expect(dropDecision.lateStreak == 0, "dropped completion resets late streak");
Expect(dropDecision.dropStreak == 1, "dropped completion increments drop streak");
Expect(scheduler.NextScheduleTime().streamTime == 5000, "drop recovery advances by measured lag");
}
void TestDefaultPolicyReportsLagWithoutSkippingScheduleTime()
{
VideoPlayoutScheduler scheduler;
scheduler.Configure(1000, 50000);
(void)scheduler.NextScheduleTime();
VideoPlayoutRecoveryDecision decision = scheduler.AccountForCompletionResult(VideoIOCompletionResult::Dropped, 0);
Expect(decision.measuredLagFrames > 0, "default policy still measures dropped-frame lag");
Expect(decision.catchUpFrames == 0, "default policy does not skip schedule time");
Expect(scheduler.NextScheduleTime().streamTime == 1000, "default recovery keeps stream time continuous");
}
void TestMeasuredRecoveryIsCappedByPolicy()
{
VideoPlayoutPolicy policy;
policy.lateOrDropCatchUpFrames = 1;
VideoPlayoutScheduler scheduler;
scheduler.Configure(1000, 50000, policy);
(void)scheduler.NextScheduleTime();
VideoPlayoutRecoveryDecision decision = scheduler.AccountForCompletionResult(VideoIOCompletionResult::Dropped, 0);
Expect(decision.measuredLagFrames > decision.catchUpFrames, "policy caps measured recovery");
Expect(decision.catchUpFrames == 1, "drop recovery obeys policy cap");
Expect(scheduler.NextScheduleTime().streamTime == 2000, "capped recovery advances by one frame");
}
void TestCleanCompletionTracksCompletedIndexAndClearsStreaks()
{
VideoPlayoutScheduler scheduler;
scheduler.Configure(1000, 50000);
(void)scheduler.NextScheduleTime();
(void)scheduler.AccountForCompletionResult(VideoIOCompletionResult::DisplayedLate, 2);
VideoPlayoutRecoveryDecision decision = scheduler.AccountForCompletionResult(VideoIOCompletionResult::Completed, 2);
Expect(decision.completedFrameIndex == 2, "completion accounting tracks completed index");
Expect(decision.catchUpFrames == 0, "clean completion does not catch up");
Expect(decision.lateStreak == 0, "clean completion clears late streak");
Expect(decision.dropStreak == 0, "clean completion keeps drop streak clear");
}
void TestPolicyNormalization()
{
VideoPlayoutPolicy policy;
policy.outputFramePoolSize = 0;
policy.targetPrerollFrames = 0;
policy.targetReadyFrames = 5;
policy.maxReadyFrames = 2;
VideoPlayoutPolicy normalized = NormalizeVideoPlayoutPolicy(policy);
Expect(normalized.targetPrerollFrames == 1, "policy normalization keeps at least one preroll frame");
Expect(normalized.maxReadyFrames == normalized.targetReadyFrames, "policy normalization keeps max ready frames above target");
Expect(normalized.outputFramePoolSize >= normalized.targetPrerollFrames + normalized.maxReadyFrames + normalized.minimumSpareDeviceFrames,
"policy normalization keeps enough output frames for preroll and ready queue ownership");
}
void TestFrameBudgets()
{
VideoPlayoutScheduler scheduler;
scheduler.Configure(1000, 50000);
ExpectNear(scheduler.FrameBudgetMilliseconds(), 20.0, 0.0001, "50 fps budget");
scheduler.Configure(1001, 60000);
ExpectNear(scheduler.FrameBudgetMilliseconds(), 16.6833, 0.0001, "59.94 fps budget");
scheduler.Configure(1, 60);
ExpectNear(scheduler.FrameBudgetMilliseconds(), 16.6667, 0.0001, "60 fps budget");
}
}
int main()
{
TestScheduleAdvancesFromZero();
TestLateAndDroppedRecoveryUsesMeasuredPressure();
TestDefaultPolicyReportsLagWithoutSkippingScheduleTime();
TestMeasuredRecoveryIsCappedByPolicy();
TestCleanCompletionTracksCompletedIndexAndClearsStreaks();
TestPolicyNormalization();
TestFrameBudgets();
if (gFailures != 0)
{
std::cerr << gFailures << " VideoPlayoutScheduler test failure(s).\n";
return 1;
}
std::cout << "VideoPlayoutScheduler tests passed.\n";
return 0;
}