Decklink abstraction
This commit is contained in:
151
tests/VideoIODeviceFakeTests.cpp
Normal file
151
tests/VideoIODeviceFakeTests.cpp
Normal file
@@ -0,0 +1,151 @@
|
||||
#include "VideoIOTypes.h"
|
||||
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
|
||||
namespace
|
||||
{
|
||||
int gFailures = 0;
|
||||
|
||||
void Expect(bool condition, const char* message)
|
||||
{
|
||||
if (condition)
|
||||
return;
|
||||
|
||||
std::cerr << "FAIL: " << message << "\n";
|
||||
++gFailures;
|
||||
}
|
||||
|
||||
class FakeVideoIODevice : public VideoIODevice
|
||||
{
|
||||
public:
|
||||
void ReleaseResources() override {}
|
||||
|
||||
bool DiscoverDevicesAndModes(const VideoFormatSelection&, std::string&) override
|
||||
{
|
||||
mState.inputFrameSize = { 1920, 1080 };
|
||||
mState.outputFrameSize = { 1920, 1080 };
|
||||
mState.inputDisplayModeName = "fake 1080p";
|
||||
mState.outputModelName = "Fake Video IO";
|
||||
mState.hasInputDevice = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SelectPreferredFormats(const VideoFormatSelection&, std::string&) override
|
||||
{
|
||||
mState.inputPixelFormat = VideoIOPixelFormat::Uyvy8;
|
||||
mState.outputPixelFormat = VideoIOPixelFormat::Bgra8;
|
||||
mState.inputFrameRowBytes = VideoIORowBytes(mState.inputPixelFormat, mState.inputFrameSize.width);
|
||||
mState.outputFrameRowBytes = VideoIORowBytes(mState.outputPixelFormat, mState.outputFrameSize.width);
|
||||
mState.captureTextureWidth = PackedTextureWidthFromRowBytes(mState.inputFrameRowBytes);
|
||||
mState.outputPackTextureWidth = mState.outputFrameSize.width;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConfigureInput(InputFrameCallback callback, const VideoFormat&, std::string&) override
|
||||
{
|
||||
mInputCallback = callback;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConfigureOutput(OutputFrameCallback callback, const VideoFormat&, bool, std::string&) override
|
||||
{
|
||||
mOutputCallback = callback;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Start() override
|
||||
{
|
||||
mState.hasInputSource = true;
|
||||
VideoIOFrame input;
|
||||
input.bytes = mInputBytes.data();
|
||||
input.rowBytes = static_cast<long>(mState.inputFrameRowBytes);
|
||||
input.width = mState.inputFrameSize.width;
|
||||
input.height = mState.inputFrameSize.height;
|
||||
input.pixelFormat = mState.inputPixelFormat;
|
||||
if (mInputCallback)
|
||||
mInputCallback(input);
|
||||
if (mOutputCallback)
|
||||
mOutputCallback(VideoIOCompletion{ VideoIOCompletionResult::Completed });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Stop() override { return true; }
|
||||
const VideoIOState& State() const override { return mState; }
|
||||
VideoIOState& MutableState() override { return mState; }
|
||||
|
||||
bool BeginOutputFrame(VideoIOOutputFrame& frame) override
|
||||
{
|
||||
frame.bytes = mOutputBytes.data();
|
||||
frame.rowBytes = static_cast<long>(mState.outputFrameRowBytes);
|
||||
frame.width = mState.outputFrameSize.width;
|
||||
frame.height = mState.outputFrameSize.height;
|
||||
frame.pixelFormat = mState.outputPixelFormat;
|
||||
return true;
|
||||
}
|
||||
|
||||
void EndOutputFrame(VideoIOOutputFrame&) override {}
|
||||
|
||||
bool ScheduleOutputFrame(const VideoIOOutputFrame&) override
|
||||
{
|
||||
++mScheduledFrames;
|
||||
return true;
|
||||
}
|
||||
|
||||
void AccountForCompletionResult(VideoIOCompletionResult result) override
|
||||
{
|
||||
mLastCompletion = result;
|
||||
}
|
||||
|
||||
unsigned ScheduledFrames() const { return mScheduledFrames; }
|
||||
VideoIOCompletionResult LastCompletion() const { return mLastCompletion; }
|
||||
|
||||
private:
|
||||
VideoIOState mState;
|
||||
InputFrameCallback mInputCallback;
|
||||
OutputFrameCallback mOutputCallback;
|
||||
std::array<unsigned char, 3840> mInputBytes = {};
|
||||
std::array<unsigned char, 7680> mOutputBytes = {};
|
||||
unsigned mScheduledFrames = 0;
|
||||
VideoIOCompletionResult mLastCompletion = VideoIOCompletionResult::Unknown;
|
||||
};
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
FakeVideoIODevice device;
|
||||
VideoFormatSelection selection;
|
||||
std::string error;
|
||||
bool inputSeen = false;
|
||||
bool outputSeen = false;
|
||||
|
||||
Expect(device.DiscoverDevicesAndModes(selection, error), "fake discovery succeeds");
|
||||
Expect(device.SelectPreferredFormats(selection, error), "fake format selection succeeds");
|
||||
Expect(device.ConfigureInput([&](const VideoIOFrame& frame) {
|
||||
inputSeen = frame.bytes != nullptr && frame.width == 1920 && frame.pixelFormat == VideoIOPixelFormat::Uyvy8;
|
||||
}, selection.input, error), "fake input config succeeds");
|
||||
Expect(device.ConfigureOutput([&](const VideoIOCompletion& completion) {
|
||||
outputSeen = completion.result == VideoIOCompletionResult::Completed;
|
||||
}, selection.output, false, error), "fake output config succeeds");
|
||||
Expect(device.Start(), "fake device starts");
|
||||
|
||||
VideoIOOutputFrame outputFrame;
|
||||
Expect(device.BeginOutputFrame(outputFrame), "fake output frame can be acquired");
|
||||
device.EndOutputFrame(outputFrame);
|
||||
device.AccountForCompletionResult(VideoIOCompletionResult::Completed);
|
||||
Expect(device.ScheduleOutputFrame(outputFrame), "fake output frame can be scheduled");
|
||||
|
||||
Expect(inputSeen, "fake input callback emits generic frame");
|
||||
Expect(outputSeen, "fake output callback emits generic completion");
|
||||
Expect(device.ScheduledFrames() == 1, "fake backend schedules one frame");
|
||||
Expect(device.LastCompletion() == VideoIOCompletionResult::Completed, "fake backend records generic completion");
|
||||
|
||||
if (gFailures != 0)
|
||||
{
|
||||
std::cerr << gFailures << " VideoIODevice fake test failure(s).\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "VideoIODevice fake tests passed.\n";
|
||||
return 0;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "VideoIOFormat.h"
|
||||
#include "DeckLinkVideoIOFormat.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
@@ -22,6 +23,7 @@ void TestPreferredFormatSelection()
|
||||
Expect(ChoosePreferredVideoIOFormat(false) == VideoIOPixelFormat::Uyvy8, "8-bit is used as fallback");
|
||||
Expect(DeckLinkPixelFormatForVideoIO(VideoIOPixelFormat::V210) == bmdFormat10BitYUV, "v210 maps to DeckLink 10-bit YUV");
|
||||
Expect(DeckLinkPixelFormatForVideoIO(VideoIOPixelFormat::Uyvy8) == bmdFormat8BitYUV, "UYVY maps to DeckLink 8-bit YUV");
|
||||
Expect(DeckLinkPixelFormatForVideoIO(VideoIOPixelFormat::Bgra8) == bmdFormat8BitBGRA, "BGRA maps to DeckLink 8-bit BGRA");
|
||||
}
|
||||
|
||||
void TestRowByteHelpers()
|
||||
@@ -31,6 +33,9 @@ void TestRowByteHelpers()
|
||||
Expect(MinimumV210RowBytes(3840) == 10240, "3840-wide v210 active row bytes");
|
||||
Expect(PackedTextureWidthFromRowBytes(5120) == 1280, "packed texture width is row bytes divided into RGBA byte texels");
|
||||
Expect(ActiveV210WordsForWidth(1920) == 1280, "active v210 words match 1920 width");
|
||||
Expect(VideoIORowBytes(VideoIOPixelFormat::Uyvy8, 1920) == 3840, "UYVY row bytes");
|
||||
Expect(VideoIORowBytes(VideoIOPixelFormat::Bgra8, 1920) == 7680, "BGRA row bytes");
|
||||
Expect(VideoIORowBytes(VideoIOPixelFormat::V210, 1920) == 5120, "v210 row bytes");
|
||||
}
|
||||
|
||||
void TestV210PackUnpack()
|
||||
|
||||
81
tests/VideoPlayoutSchedulerTests.cpp
Normal file
81
tests/VideoPlayoutSchedulerTests.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#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 TestLateAndDroppedSkipAhead()
|
||||
{
|
||||
VideoPlayoutScheduler scheduler;
|
||||
scheduler.Configure(1000, 50000);
|
||||
|
||||
(void)scheduler.NextScheduleTime();
|
||||
scheduler.AccountForCompletionResult(VideoIOCompletionResult::DisplayedLate);
|
||||
Expect(scheduler.NextScheduleTime().streamTime == 3000, "late completion preserves the existing two-frame skip policy");
|
||||
|
||||
scheduler.AccountForCompletionResult(VideoIOCompletionResult::Dropped);
|
||||
Expect(scheduler.NextScheduleTime().streamTime == 6000, "dropped completion preserves the existing two-frame skip policy");
|
||||
}
|
||||
|
||||
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();
|
||||
TestLateAndDroppedSkipAhead();
|
||||
TestFrameBudgets();
|
||||
|
||||
if (gFailures != 0)
|
||||
{
|
||||
std::cerr << gFailures << " VideoPlayoutScheduler test failure(s).\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "VideoPlayoutScheduler tests passed.\n";
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user