164 lines
6.1 KiB
C++
164 lines
6.1 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 TestScheduleCursorCanAlignToPlaybackClock()
|
|
{
|
|
VideoPlayoutScheduler scheduler;
|
|
scheduler.Configure(1000, 50000);
|
|
|
|
(void)scheduler.NextScheduleTime();
|
|
scheduler.AlignNextScheduleTimeToPlayback(10000, 4);
|
|
Expect(scheduler.NextScheduleTime().streamTime == 14000, "schedule cursor skips stale stream time after underfeed");
|
|
|
|
scheduler.AlignNextScheduleTimeToPlayback(11000, 1);
|
|
Expect(scheduler.NextScheduleTime().streamTime == 15000, "schedule cursor does not move backward");
|
|
}
|
|
|
|
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();
|
|
TestScheduleCursorCanAlignToPlaybackClock();
|
|
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;
|
|
}
|