Phase 7 step 2
This commit is contained in:
@@ -168,6 +168,7 @@ set(APP_SOURCES
|
|||||||
"${APP_DIR}/videoio/VideoBackendLifecycle.cpp"
|
"${APP_DIR}/videoio/VideoBackendLifecycle.cpp"
|
||||||
"${APP_DIR}/videoio/VideoBackendLifecycle.h"
|
"${APP_DIR}/videoio/VideoBackendLifecycle.h"
|
||||||
"${APP_DIR}/videoio/VideoIOTypes.h"
|
"${APP_DIR}/videoio/VideoIOTypes.h"
|
||||||
|
"${APP_DIR}/videoio/VideoPlayoutPolicy.h"
|
||||||
"${APP_DIR}/videoio/VideoPlayoutScheduler.cpp"
|
"${APP_DIR}/videoio/VideoPlayoutScheduler.cpp"
|
||||||
"${APP_DIR}/videoio/VideoPlayoutScheduler.h"
|
"${APP_DIR}/videoio/VideoPlayoutScheduler.h"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,4 +7,3 @@ constexpr GLuint kDecodedVideoTextureUnit = 1;
|
|||||||
constexpr GLuint kSourceHistoryTextureUnitBase = 2;
|
constexpr GLuint kSourceHistoryTextureUnitBase = 2;
|
||||||
constexpr GLuint kPackedVideoTextureUnit = 2;
|
constexpr GLuint kPackedVideoTextureUnit = 2;
|
||||||
constexpr GLuint kGlobalParamsBindingPoint = 0;
|
constexpr GLuint kGlobalParamsBindingPoint = 0;
|
||||||
constexpr unsigned kPrerollFrameCount = 12;
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
enum class VideoUnderrunBehavior
|
||||||
|
{
|
||||||
|
ReuseLastCompletedFrame,
|
||||||
|
BlackFrame
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VideoPlayoutPolicy
|
||||||
|
{
|
||||||
|
unsigned outputFramePoolSize = 10;
|
||||||
|
unsigned targetPrerollFrames = 12;
|
||||||
|
unsigned targetReadyFrames = 2;
|
||||||
|
unsigned maxReadyFrames = 4;
|
||||||
|
unsigned minimumSpareDeviceFrames = 1;
|
||||||
|
uint64_t lateOrDropCatchUpFrames = 2;
|
||||||
|
VideoUnderrunBehavior underrunBehavior = VideoUnderrunBehavior::ReuseLastCompletedFrame;
|
||||||
|
bool adaptiveHeadroomEnabled = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline VideoPlayoutPolicy NormalizeVideoPlayoutPolicy(VideoPlayoutPolicy policy)
|
||||||
|
{
|
||||||
|
if (policy.outputFramePoolSize == 0)
|
||||||
|
policy.outputFramePoolSize = 1;
|
||||||
|
if (policy.targetPrerollFrames == 0)
|
||||||
|
policy.targetPrerollFrames = 1;
|
||||||
|
if (policy.targetReadyFrames == 0)
|
||||||
|
policy.targetReadyFrames = 1;
|
||||||
|
if (policy.maxReadyFrames < policy.targetReadyFrames)
|
||||||
|
policy.maxReadyFrames = policy.targetReadyFrames;
|
||||||
|
return policy;
|
||||||
|
}
|
||||||
@@ -1,9 +1,15 @@
|
|||||||
#include "VideoPlayoutScheduler.h"
|
#include "VideoPlayoutScheduler.h"
|
||||||
|
|
||||||
void VideoPlayoutScheduler::Configure(int64_t frameDuration, int64_t timeScale)
|
void VideoPlayoutScheduler::Configure(int64_t frameDuration, int64_t timeScale)
|
||||||
|
{
|
||||||
|
Configure(frameDuration, timeScale, VideoPlayoutPolicy());
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoPlayoutScheduler::Configure(int64_t frameDuration, int64_t timeScale, const VideoPlayoutPolicy& policy)
|
||||||
{
|
{
|
||||||
mFrameDuration = frameDuration;
|
mFrameDuration = frameDuration;
|
||||||
mTimeScale = timeScale;
|
mTimeScale = timeScale;
|
||||||
|
mPolicy = NormalizeVideoPlayoutPolicy(policy);
|
||||||
Reset();
|
Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +32,7 @@ VideoIOScheduleTime VideoPlayoutScheduler::NextScheduleTime()
|
|||||||
void VideoPlayoutScheduler::AccountForCompletionResult(VideoIOCompletionResult result)
|
void VideoPlayoutScheduler::AccountForCompletionResult(VideoIOCompletionResult result)
|
||||||
{
|
{
|
||||||
if (result == VideoIOCompletionResult::DisplayedLate || result == VideoIOCompletionResult::Dropped)
|
if (result == VideoIOCompletionResult::DisplayedLate || result == VideoIOCompletionResult::Dropped)
|
||||||
mScheduledFrameIndex += 2;
|
mScheduledFrameIndex += mPolicy.lateOrDropCatchUpFrames;
|
||||||
}
|
}
|
||||||
|
|
||||||
double VideoPlayoutScheduler::FrameBudgetMilliseconds() const
|
double VideoPlayoutScheduler::FrameBudgetMilliseconds() const
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "VideoIOTypes.h"
|
#include "VideoIOTypes.h"
|
||||||
|
#include "VideoPlayoutPolicy.h"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
@@ -8,15 +9,18 @@ class VideoPlayoutScheduler
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void Configure(int64_t frameDuration, int64_t timeScale);
|
void Configure(int64_t frameDuration, int64_t timeScale);
|
||||||
|
void Configure(int64_t frameDuration, int64_t timeScale, const VideoPlayoutPolicy& policy);
|
||||||
void Reset();
|
void Reset();
|
||||||
VideoIOScheduleTime NextScheduleTime();
|
VideoIOScheduleTime NextScheduleTime();
|
||||||
void AccountForCompletionResult(VideoIOCompletionResult result);
|
void AccountForCompletionResult(VideoIOCompletionResult result);
|
||||||
double FrameBudgetMilliseconds() const;
|
double FrameBudgetMilliseconds() const;
|
||||||
uint64_t ScheduledFrameIndex() const { return mScheduledFrameIndex; }
|
uint64_t ScheduledFrameIndex() const { return mScheduledFrameIndex; }
|
||||||
int64_t TimeScale() const { return mTimeScale; }
|
int64_t TimeScale() const { return mTimeScale; }
|
||||||
|
const VideoPlayoutPolicy& Policy() const { return mPolicy; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int64_t mFrameDuration = 0;
|
int64_t mFrameDuration = 0;
|
||||||
int64_t mTimeScale = 0;
|
int64_t mTimeScale = 0;
|
||||||
uint64_t mScheduledFrameIndex = 0;
|
uint64_t mScheduledFrameIndex = 0;
|
||||||
|
VideoPlayoutPolicy mPolicy;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#include "DeckLinkSession.h"
|
#include "DeckLinkSession.h"
|
||||||
|
|
||||||
#include "GlRenderConstants.h"
|
|
||||||
|
|
||||||
#include <atlbase.h>
|
#include <atlbase.h>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -210,7 +208,7 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM
|
|||||||
BMDTimeValue frameDuration = 0;
|
BMDTimeValue frameDuration = 0;
|
||||||
BMDTimeScale frameTimescale = 0;
|
BMDTimeScale frameTimescale = 0;
|
||||||
outputMode->GetFrameRate(&frameDuration, &frameTimescale);
|
outputMode->GetFrameRate(&frameDuration, &frameTimescale);
|
||||||
mScheduler.Configure(frameDuration, frameTimescale);
|
mScheduler.Configure(frameDuration, frameTimescale, mPlayoutPolicy);
|
||||||
mState.frameBudgetMilliseconds = mScheduler.FrameBudgetMilliseconds();
|
mState.frameBudgetMilliseconds = mScheduler.FrameBudgetMilliseconds();
|
||||||
|
|
||||||
mState.inputFrameRowBytes = mState.inputFrameSize.width * 2u;
|
mState.inputFrameRowBytes = mState.inputFrameSize.width * 2u;
|
||||||
@@ -379,7 +377,9 @@ bool DeckLinkSession::ConfigureOutput(OutputFrameCallback callback, const VideoF
|
|||||||
mState.statusMessage = "Selected DeckLink output supports external keying. Set enableExternalKeying to true in runtime-host.json to request it.";
|
mState.statusMessage = "Selected DeckLink output supports external keying. Set enableExternalKeying to true in runtime-host.json to request it.";
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++)
|
const VideoPlayoutPolicy policy = NormalizeVideoPlayoutPolicy(mPlayoutPolicy);
|
||||||
|
mPlayoutPolicy = policy;
|
||||||
|
for (unsigned i = 0; i < policy.outputFramePoolSize; i++)
|
||||||
{
|
{
|
||||||
CComPtr<IDeckLinkMutableVideoFrame> outputFrame;
|
CComPtr<IDeckLinkMutableVideoFrame> outputFrame;
|
||||||
|
|
||||||
@@ -523,7 +523,9 @@ bool DeckLinkSession::Start()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (unsigned i = 0; i < kPrerollFrameCount; i++)
|
const VideoPlayoutPolicy policy = NormalizeVideoPlayoutPolicy(mPlayoutPolicy);
|
||||||
|
mPlayoutPolicy = policy;
|
||||||
|
for (unsigned i = 0; i < policy.targetPrerollFrames; i++)
|
||||||
{
|
{
|
||||||
CComPtr<IDeckLinkMutableVideoFrame> outputVideoFrame;
|
CComPtr<IDeckLinkMutableVideoFrame> outputVideoFrame;
|
||||||
if (!AcquireNextOutputVideoFrame(outputVideoFrame))
|
if (!AcquireNextOutputVideoFrame(outputVideoFrame))
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "DeckLinkVideoIOFormat.h"
|
#include "DeckLinkVideoIOFormat.h"
|
||||||
#include "VideoIOFormat.h"
|
#include "VideoIOFormat.h"
|
||||||
#include "VideoIOTypes.h"
|
#include "VideoIOTypes.h"
|
||||||
|
#include "VideoPlayoutPolicy.h"
|
||||||
#include "VideoPlayoutScheduler.h"
|
#include "VideoPlayoutScheduler.h"
|
||||||
|
|
||||||
#include <atlbase.h>
|
#include <atlbase.h>
|
||||||
@@ -79,6 +80,7 @@ private:
|
|||||||
CComPtr<IDeckLinkKeyer> keyer;
|
CComPtr<IDeckLinkKeyer> keyer;
|
||||||
std::deque<CComPtr<IDeckLinkMutableVideoFrame>> outputVideoFrameQueue;
|
std::deque<CComPtr<IDeckLinkMutableVideoFrame>> outputVideoFrameQueue;
|
||||||
VideoIOState mState;
|
VideoIOState mState;
|
||||||
|
VideoPlayoutPolicy mPlayoutPolicy;
|
||||||
VideoPlayoutScheduler mScheduler;
|
VideoPlayoutScheduler mScheduler;
|
||||||
InputFrameCallback mInputFrameCallback;
|
InputFrameCallback mInputFrameCallback;
|
||||||
OutputFrameCallback mOutputFrameCallback;
|
OutputFrameCallback mOutputFrameCallback;
|
||||||
|
|||||||
@@ -9,13 +9,14 @@ Phase 5 clarified that live parameter layering stops at final render-state compo
|
|||||||
## Status
|
## Status
|
||||||
|
|
||||||
- Phase 7 design package: proposed.
|
- Phase 7 design package: proposed.
|
||||||
- Phase 7 implementation: Step 1 complete.
|
- Phase 7 implementation: Step 2 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 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:
|
Current backend footholds:
|
||||||
|
|
||||||
- `VideoBackend` wraps device discovery/configuration, start/stop, input callback handling, output completion handling, and telemetry publication.
|
- `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.
|
- `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.
|
||||||
- `VideoPlayoutScheduler` owns basic schedule time generation and simple late/drop skip-ahead behavior.
|
- `VideoPlayoutScheduler` owns basic schedule time generation and simple late/drop skip-ahead behavior.
|
||||||
- `OpenGLVideoIOBridge` is the current adapter between `VideoBackend` and `RenderEngine`.
|
- `OpenGLVideoIOBridge` is the current adapter between `VideoBackend` and `RenderEngine`.
|
||||||
- `HealthTelemetry` receives some signal, render, and pacing stats.
|
- `HealthTelemetry` receives some signal, render, and pacing stats.
|
||||||
@@ -223,9 +224,16 @@ Unify fixed constants and scheduler assumptions.
|
|||||||
|
|
||||||
Initial target:
|
Initial target:
|
||||||
|
|
||||||
- frame pool size derives from policy
|
- [x] frame pool size derives from policy
|
||||||
- preroll count derives from policy
|
- [x] preroll count derives from policy
|
||||||
- late/drop recovery reads policy
|
- [x] late/drop recovery reads policy
|
||||||
|
|
||||||
|
Current implementation:
|
||||||
|
|
||||||
|
- `VideoPlayoutPolicy` defines current output frame pool, preroll, ready-frame, spare-buffer, underrun, catch-up, and adaptive-headroom settings.
|
||||||
|
- `DeckLinkSession` uses the policy for output frame pool creation and preroll count.
|
||||||
|
- `VideoPlayoutScheduler` stores the policy and uses `lateOrDropCatchUpFrames` instead of a hard-coded `+2` recovery step.
|
||||||
|
- `VideoPlayoutSchedulerTests` cover default compatibility behavior, policy-driven catch-up, and policy normalization.
|
||||||
|
|
||||||
### Step 3. Add Ready Output Queue
|
### Step 3. Add Ready Output Queue
|
||||||
|
|
||||||
@@ -321,7 +329,7 @@ Backend lifecycle and playout queue are related, but either can grow large. Impl
|
|||||||
Phase 7 can be considered complete once the project can say:
|
Phase 7 can be considered complete once the project can say:
|
||||||
|
|
||||||
- [x] backend lifecycle states and transitions are explicit
|
- [x] backend lifecycle states and transitions are explicit
|
||||||
- [ ] playout policy owns preroll, pool size, headroom, and underrun behavior
|
- [x] playout policy owns preroll, pool size, headroom, and underrun behavior
|
||||||
- [ ] output callbacks no longer synchronously wait for render production
|
- [ ] output callbacks no longer synchronously wait for render production
|
||||||
- [ ] render produces completed output frames into a bounded queue
|
- [ ] render produces completed output frames into a bounded queue
|
||||||
- [ ] underrun behavior is explicit and observable
|
- [ ] underrun behavior is explicit and observable
|
||||||
|
|||||||
@@ -50,6 +50,33 @@ void TestLateAndDroppedSkipAhead()
|
|||||||
Expect(scheduler.NextScheduleTime().streamTime == 6000, "dropped completion preserves the existing two-frame skip policy");
|
Expect(scheduler.NextScheduleTime().streamTime == 6000, "dropped completion preserves the existing two-frame skip policy");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestLateAndDroppedRecoveryUsesPolicy()
|
||||||
|
{
|
||||||
|
VideoPlayoutPolicy policy;
|
||||||
|
policy.lateOrDropCatchUpFrames = 4;
|
||||||
|
|
||||||
|
VideoPlayoutScheduler scheduler;
|
||||||
|
scheduler.Configure(1000, 50000, policy);
|
||||||
|
|
||||||
|
(void)scheduler.NextScheduleTime();
|
||||||
|
scheduler.AccountForCompletionResult(VideoIOCompletionResult::Dropped);
|
||||||
|
Expect(scheduler.NextScheduleTime().streamTime == 5000, "drop recovery uses policy catch-up frame count");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestPolicyNormalization()
|
||||||
|
{
|
||||||
|
VideoPlayoutPolicy policy;
|
||||||
|
policy.outputFramePoolSize = 0;
|
||||||
|
policy.targetPrerollFrames = 0;
|
||||||
|
policy.targetReadyFrames = 5;
|
||||||
|
policy.maxReadyFrames = 2;
|
||||||
|
|
||||||
|
VideoPlayoutPolicy normalized = NormalizeVideoPlayoutPolicy(policy);
|
||||||
|
Expect(normalized.outputFramePoolSize == 1, "policy normalization keeps at least one output frame");
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
void TestFrameBudgets()
|
void TestFrameBudgets()
|
||||||
{
|
{
|
||||||
VideoPlayoutScheduler scheduler;
|
VideoPlayoutScheduler scheduler;
|
||||||
@@ -68,6 +95,8 @@ int main()
|
|||||||
{
|
{
|
||||||
TestScheduleAdvancesFromZero();
|
TestScheduleAdvancesFromZero();
|
||||||
TestLateAndDroppedSkipAhead();
|
TestLateAndDroppedSkipAhead();
|
||||||
|
TestLateAndDroppedRecoveryUsesPolicy();
|
||||||
|
TestPolicyNormalization();
|
||||||
TestFrameBudgets();
|
TestFrameBudgets();
|
||||||
|
|
||||||
if (gFailures != 0)
|
if (gFailures != 0)
|
||||||
|
|||||||
Reference in New Issue
Block a user