#include "VideoOutputThread.h" #include #include #include #include namespace { int gFailures = 0; void Expect(bool condition, const char* message) { if (condition) return; std::cerr << "FAIL: " << message << "\n"; ++gFailures; } struct FakeExchangeMetrics { std::size_t scheduledCount = 0; }; class FakeExchange { public: FakeExchange() { mFrame.bytes = mBytes; mFrame.rowBytes = 8; mFrame.width = 2; mFrame.height = 2; mFrame.pixelFormat = VideoIOPixelFormat::Bgra8; } FakeExchangeMetrics Metrics() const { FakeExchangeMetrics metrics; metrics.scheduledCount = mScheduled.load(); return metrics; } bool ConsumeCompletedForSchedule(SystemFrame& frame) { bool expected = true; if (!mHasFrame.compare_exchange_strong(expected, false)) return false; frame = mFrame; mScheduled.fetch_add(1); return true; } void ReleaseScheduledByBytes(void*) { mReleased.fetch_add(1); } std::atomic mScheduled{ 0 }; std::atomic mReleased{ 0 }; private: std::atomic mHasFrame{ true }; unsigned char mBytes[16] = {}; SystemFrame mFrame; }; class FakeOutputEdge final : public RenderCadenceCompositor::IVideoOutputEdge { public: bool StartScheduledPlayback(std::string&) override { return true; } bool ScheduleFrame(const VideoIOOutputFrame& frame) override { mLastWidth = frame.width; mScheduled.fetch_add(1); return mScheduleResult; } void Stop() override {} void ReleaseResources() override {} const VideoIOState& State() const override { return mState; } RenderCadenceCompositor::VideoOutputEdgeMetrics Metrics() const override { return mMetrics; } bool mScheduleResult = true; std::atomic mScheduled{ 0 }; unsigned mLastWidth = 0; private: VideoIOState mState; RenderCadenceCompositor::VideoOutputEdgeMetrics mMetrics; }; void TestSchedulesCompletedFrameThroughGenericEdge() { FakeExchange exchange; FakeOutputEdge output; RenderCadenceCompositor::VideoOutputThreadConfig config; config.targetBufferedFrames = 1; config.idleSleep = std::chrono::milliseconds(1); RenderCadenceCompositor::VideoOutputThread thread(output, exchange, config); Expect(thread.Start(), "video output thread starts"); std::this_thread::sleep_for(std::chrono::milliseconds(20)); thread.Stop(); const auto metrics = thread.Metrics(); Expect(output.mScheduled.load() == 1, "generic output edge receives one scheduled frame"); Expect(output.mLastWidth == 2, "scheduled frame data comes from exchange"); Expect(metrics.scheduledFrames == 1, "thread scheduled frame metric increments"); Expect(metrics.scheduleFailures == 0, "no schedule failure is reported"); Expect(exchange.mReleased.load() == 0, "successful schedule keeps frame scheduled"); } void TestReleasesFrameWhenGenericEdgeRejectsSchedule() { FakeExchange exchange; FakeOutputEdge output; output.mScheduleResult = false; RenderCadenceCompositor::VideoOutputThreadConfig config; config.targetBufferedFrames = 1; config.idleSleep = std::chrono::milliseconds(1); RenderCadenceCompositor::VideoOutputThread thread(output, exchange, config); Expect(thread.Start(), "video output thread starts for failure path"); std::this_thread::sleep_for(std::chrono::milliseconds(20)); thread.Stop(); const auto metrics = thread.Metrics(); Expect(output.mScheduled.load() == 1, "generic output edge receives failed schedule attempt"); Expect(metrics.scheduledFrames == 0, "failed schedule does not increment scheduled frames"); Expect(metrics.scheduleFailures == 1, "schedule failure metric increments"); Expect(exchange.mReleased.load() == 1, "failed schedule releases the frame back to exchange"); } } int main() { TestSchedulesCompletedFrameThroughGenericEdge(); TestReleasesFrameWhenGenericEdgeRejectsSchedule(); if (gFailures != 0) { std::cerr << gFailures << " VideoOutputThread test failure(s).\n"; return 1; } std::cout << "VideoOutputThread tests passed.\n"; return 0; }