Step 3
This commit is contained in:
@@ -168,6 +168,8 @@ set(APP_SOURCES
|
||||
"${APP_DIR}/videoio/VideoBackendLifecycle.cpp"
|
||||
"${APP_DIR}/videoio/VideoBackendLifecycle.h"
|
||||
"${APP_DIR}/videoio/VideoIOTypes.h"
|
||||
"${APP_DIR}/videoio/RenderOutputQueue.cpp"
|
||||
"${APP_DIR}/videoio/RenderOutputQueue.h"
|
||||
"${APP_DIR}/videoio/VideoPlayoutPolicy.h"
|
||||
"${APP_DIR}/videoio/VideoPlayoutScheduler.cpp"
|
||||
"${APP_DIR}/videoio/VideoPlayoutScheduler.h"
|
||||
@@ -541,6 +543,23 @@ endif()
|
||||
|
||||
add_test(NAME VideoPlayoutSchedulerTests COMMAND VideoPlayoutSchedulerTests)
|
||||
|
||||
add_executable(RenderOutputQueueTests
|
||||
"${APP_DIR}/videoio/RenderOutputQueue.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderOutputQueueTests.cpp"
|
||||
)
|
||||
|
||||
target_include_directories(RenderOutputQueueTests PRIVATE
|
||||
"${APP_DIR}"
|
||||
"${APP_DIR}/videoio"
|
||||
"${APP_DIR}/videoio/decklink"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(RenderOutputQueueTests PRIVATE /W3)
|
||||
endif()
|
||||
|
||||
add_test(NAME RenderOutputQueueTests COMMAND RenderOutputQueueTests)
|
||||
|
||||
add_executable(VideoBackendLifecycleTests
|
||||
"${APP_DIR}/videoio/VideoBackendLifecycle.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/VideoBackendLifecycleTests.cpp"
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#include "RenderOutputQueue.h"
|
||||
|
||||
RenderOutputQueue::RenderOutputQueue(const VideoPlayoutPolicy& policy) :
|
||||
mPolicy(NormalizeVideoPlayoutPolicy(policy))
|
||||
{
|
||||
}
|
||||
|
||||
void RenderOutputQueue::Configure(const VideoPlayoutPolicy& policy)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mPolicy = NormalizeVideoPlayoutPolicy(policy);
|
||||
while (mReadyFrames.size() > CapacityLocked())
|
||||
{
|
||||
mReadyFrames.pop_front();
|
||||
++mDroppedCount;
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderOutputQueue::Push(RenderOutputFrame frame)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (mReadyFrames.size() >= CapacityLocked())
|
||||
{
|
||||
mReadyFrames.pop_front();
|
||||
++mDroppedCount;
|
||||
}
|
||||
|
||||
mReadyFrames.push_back(frame);
|
||||
++mPushedCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderOutputQueue::TryPop(RenderOutputFrame& frame)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (mReadyFrames.empty())
|
||||
{
|
||||
++mUnderrunCount;
|
||||
return false;
|
||||
}
|
||||
|
||||
frame = mReadyFrames.front();
|
||||
mReadyFrames.pop_front();
|
||||
++mPoppedCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderOutputQueue::Clear()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mReadyFrames.clear();
|
||||
}
|
||||
|
||||
RenderOutputQueueMetrics RenderOutputQueue::GetMetrics() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
RenderOutputQueueMetrics metrics;
|
||||
metrics.depth = mReadyFrames.size();
|
||||
metrics.capacity = CapacityLocked();
|
||||
metrics.pushedCount = mPushedCount;
|
||||
metrics.poppedCount = mPoppedCount;
|
||||
metrics.droppedCount = mDroppedCount;
|
||||
metrics.underrunCount = mUnderrunCount;
|
||||
return metrics;
|
||||
}
|
||||
|
||||
std::size_t RenderOutputQueue::CapacityLocked() const
|
||||
{
|
||||
return static_cast<std::size_t>(mPolicy.maxReadyFrames);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include "VideoIOTypes.h"
|
||||
#include "VideoPlayoutPolicy.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
|
||||
struct RenderOutputFrame
|
||||
{
|
||||
VideoIOOutputFrame frame;
|
||||
uint64_t frameIndex = 0;
|
||||
bool stale = false;
|
||||
};
|
||||
|
||||
struct RenderOutputQueueMetrics
|
||||
{
|
||||
std::size_t depth = 0;
|
||||
std::size_t capacity = 0;
|
||||
uint64_t pushedCount = 0;
|
||||
uint64_t poppedCount = 0;
|
||||
uint64_t droppedCount = 0;
|
||||
uint64_t underrunCount = 0;
|
||||
};
|
||||
|
||||
class RenderOutputQueue
|
||||
{
|
||||
public:
|
||||
explicit RenderOutputQueue(const VideoPlayoutPolicy& policy = VideoPlayoutPolicy());
|
||||
|
||||
void Configure(const VideoPlayoutPolicy& policy);
|
||||
bool Push(RenderOutputFrame frame);
|
||||
bool TryPop(RenderOutputFrame& frame);
|
||||
void Clear();
|
||||
RenderOutputQueueMetrics GetMetrics() const;
|
||||
|
||||
private:
|
||||
std::size_t CapacityLocked() const;
|
||||
|
||||
mutable std::mutex mMutex;
|
||||
VideoPlayoutPolicy mPolicy;
|
||||
std::deque<RenderOutputFrame> mReadyFrames;
|
||||
uint64_t mPushedCount = 0;
|
||||
uint64_t mPoppedCount = 0;
|
||||
uint64_t mDroppedCount = 0;
|
||||
uint64_t mUnderrunCount = 0;
|
||||
};
|
||||
@@ -9,7 +9,7 @@ Phase 5 clarified that live parameter layering stops at final render-state compo
|
||||
## Status
|
||||
|
||||
- Phase 7 design package: proposed.
|
||||
- Phase 7 implementation: Step 2 complete.
|
||||
- Phase 7 implementation: Step 3 complete.
|
||||
- Current alignment: `VideoBackend`, `VideoIODevice`, `DeckLinkSession`, `VideoBackendLifecycle`, and `VideoPlayoutScheduler` exist. Phase 4 removed callback-thread GL ownership, but the DeckLink completion path still waits for render-thread output production.
|
||||
|
||||
Current backend footholds:
|
||||
@@ -17,6 +17,7 @@ Current backend footholds:
|
||||
- `VideoBackend` wraps device discovery/configuration, start/stop, input callback handling, output completion handling, and telemetry publication.
|
||||
- `DeckLinkSession` owns DeckLink device handles, frame pool creation, preroll, keyer configuration, and scheduled playback.
|
||||
- `VideoPlayoutPolicy` names current frame pool, preroll, ready-frame, underrun, and catch-up policy defaults.
|
||||
- `RenderOutputQueue` names the future bounded ready-output-frame handoff and has pure queue tests.
|
||||
- `VideoPlayoutScheduler` owns basic schedule time generation and simple late/drop skip-ahead behavior.
|
||||
- `OpenGLVideoIOBridge` is the current adapter between `VideoBackend` and `RenderEngine`.
|
||||
- `HealthTelemetry` receives some signal, render, and pacing stats.
|
||||
@@ -241,9 +242,17 @@ Introduce a bounded queue for completed output frames.
|
||||
|
||||
Initial target:
|
||||
|
||||
- pure queue tests
|
||||
- explicit depth/underrun metrics
|
||||
- no DeckLink dependency in queue tests
|
||||
- [x] pure queue tests
|
||||
- [x] explicit depth/underrun metrics
|
||||
- [x] no DeckLink dependency in queue tests
|
||||
|
||||
Current implementation:
|
||||
|
||||
- `RenderOutputQueue` owns a bounded FIFO of `RenderOutputFrame` values.
|
||||
- The queue is configured from `VideoPlayoutPolicy::maxReadyFrames`.
|
||||
- Queue metrics report depth, capacity, pushed, popped, dropped, and underrun counts.
|
||||
- Overflow drops the oldest ready frame, preserving the newest completed output for scheduling.
|
||||
- `RenderOutputQueueTests` cover ordering, bounded overflow, underrun counting, and capacity shrink behavior without DeckLink hardware.
|
||||
|
||||
### Step 4. Move Callback Toward Dequeue/Schedule
|
||||
|
||||
|
||||
110
tests/RenderOutputQueueTests.cpp
Normal file
110
tests/RenderOutputQueueTests.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
#include "RenderOutputQueue.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace
|
||||
{
|
||||
int gFailures = 0;
|
||||
|
||||
void Expect(bool condition, const char* message)
|
||||
{
|
||||
if (condition)
|
||||
return;
|
||||
|
||||
std::cerr << "FAIL: " << message << "\n";
|
||||
++gFailures;
|
||||
}
|
||||
|
||||
RenderOutputFrame MakeFrame(uint64_t index)
|
||||
{
|
||||
RenderOutputFrame frame;
|
||||
frame.frameIndex = index;
|
||||
frame.frame.nativeFrame = reinterpret_cast<void*>(static_cast<uintptr_t>(index + 1));
|
||||
return frame;
|
||||
}
|
||||
|
||||
void TestQueuePreservesOrdering()
|
||||
{
|
||||
VideoPlayoutPolicy policy;
|
||||
policy.maxReadyFrames = 3;
|
||||
RenderOutputQueue queue(policy);
|
||||
|
||||
Expect(queue.Push(MakeFrame(1)), "first ready frame pushes");
|
||||
Expect(queue.Push(MakeFrame(2)), "second ready frame pushes");
|
||||
|
||||
RenderOutputFrame frame;
|
||||
Expect(queue.TryPop(frame), "first ready frame pops");
|
||||
Expect(frame.frameIndex == 1, "queue pops first frame first");
|
||||
Expect(queue.TryPop(frame), "second ready frame pops");
|
||||
Expect(frame.frameIndex == 2, "queue pops second frame second");
|
||||
}
|
||||
|
||||
void TestBoundedQueueDropsOldestFrame()
|
||||
{
|
||||
VideoPlayoutPolicy policy;
|
||||
policy.maxReadyFrames = 2;
|
||||
RenderOutputQueue queue(policy);
|
||||
|
||||
queue.Push(MakeFrame(1));
|
||||
queue.Push(MakeFrame(2));
|
||||
queue.Push(MakeFrame(3));
|
||||
|
||||
RenderOutputQueueMetrics metrics = queue.GetMetrics();
|
||||
Expect(metrics.depth == 2, "bounded queue depth stays at capacity");
|
||||
Expect(metrics.droppedCount == 1, "bounded queue counts dropped oldest frame");
|
||||
|
||||
RenderOutputFrame frame;
|
||||
Expect(queue.TryPop(frame), "bounded queue pops after drop");
|
||||
Expect(frame.frameIndex == 2, "oldest frame was dropped when queue overflowed");
|
||||
}
|
||||
|
||||
void TestUnderrunIsCounted()
|
||||
{
|
||||
RenderOutputQueue queue;
|
||||
RenderOutputFrame frame;
|
||||
Expect(!queue.TryPop(frame), "empty queue reports underrun");
|
||||
|
||||
RenderOutputQueueMetrics metrics = queue.GetMetrics();
|
||||
Expect(metrics.underrunCount == 1, "empty pop increments underrun count");
|
||||
}
|
||||
|
||||
void TestConfigureShrinksDepthToNewCapacity()
|
||||
{
|
||||
VideoPlayoutPolicy policy;
|
||||
policy.maxReadyFrames = 4;
|
||||
RenderOutputQueue queue(policy);
|
||||
queue.Push(MakeFrame(1));
|
||||
queue.Push(MakeFrame(2));
|
||||
queue.Push(MakeFrame(3));
|
||||
|
||||
VideoPlayoutPolicy smallerPolicy;
|
||||
smallerPolicy.targetReadyFrames = 1;
|
||||
smallerPolicy.maxReadyFrames = 1;
|
||||
queue.Configure(smallerPolicy);
|
||||
|
||||
RenderOutputQueueMetrics metrics = queue.GetMetrics();
|
||||
Expect(metrics.depth == 1, "configure trims queue to new capacity");
|
||||
Expect(metrics.droppedCount == 2, "configure counts trimmed frames as drops");
|
||||
|
||||
RenderOutputFrame frame;
|
||||
Expect(queue.TryPop(frame), "trimmed queue still has newest frame");
|
||||
Expect(frame.frameIndex == 3, "configure keeps newest ready frame");
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
TestQueuePreservesOrdering();
|
||||
TestBoundedQueueDropsOldestFrame();
|
||||
TestUnderrunIsCounted();
|
||||
TestConfigureShrinksDepthToNewCapacity();
|
||||
|
||||
if (gFailures != 0)
|
||||
{
|
||||
std::cerr << gFailures << " render output queue test failure(s).\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "RenderOutputQueue tests passed.\n";
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user