Phase 7 step 1
This commit is contained in:
@@ -165,6 +165,8 @@ set(APP_SOURCES
|
|||||||
"${APP_DIR}/videoio/VideoIOFormat.h"
|
"${APP_DIR}/videoio/VideoIOFormat.h"
|
||||||
"${APP_DIR}/videoio/VideoBackend.cpp"
|
"${APP_DIR}/videoio/VideoBackend.cpp"
|
||||||
"${APP_DIR}/videoio/VideoBackend.h"
|
"${APP_DIR}/videoio/VideoBackend.h"
|
||||||
|
"${APP_DIR}/videoio/VideoBackendLifecycle.cpp"
|
||||||
|
"${APP_DIR}/videoio/VideoBackendLifecycle.h"
|
||||||
"${APP_DIR}/videoio/VideoIOTypes.h"
|
"${APP_DIR}/videoio/VideoIOTypes.h"
|
||||||
"${APP_DIR}/videoio/VideoPlayoutScheduler.cpp"
|
"${APP_DIR}/videoio/VideoPlayoutScheduler.cpp"
|
||||||
"${APP_DIR}/videoio/VideoPlayoutScheduler.h"
|
"${APP_DIR}/videoio/VideoPlayoutScheduler.h"
|
||||||
@@ -538,6 +540,22 @@ endif()
|
|||||||
|
|
||||||
add_test(NAME VideoPlayoutSchedulerTests COMMAND VideoPlayoutSchedulerTests)
|
add_test(NAME VideoPlayoutSchedulerTests COMMAND VideoPlayoutSchedulerTests)
|
||||||
|
|
||||||
|
add_executable(VideoBackendLifecycleTests
|
||||||
|
"${APP_DIR}/videoio/VideoBackendLifecycle.cpp"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/VideoBackendLifecycleTests.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(VideoBackendLifecycleTests PRIVATE
|
||||||
|
"${APP_DIR}"
|
||||||
|
"${APP_DIR}/videoio"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(VideoBackendLifecycleTests PRIVATE /W3)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_test(NAME VideoBackendLifecycleTests COMMAND VideoBackendLifecycleTests)
|
||||||
|
|
||||||
add_executable(VideoIODeviceFakeTests
|
add_executable(VideoIODeviceFakeTests
|
||||||
"${APP_DIR}/videoio/VideoIOFormat.cpp"
|
"${APP_DIR}/videoio/VideoIOFormat.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/VideoIODeviceFakeTests.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/VideoIODeviceFakeTests.cpp"
|
||||||
|
|||||||
@@ -26,46 +26,86 @@ void VideoBackend::ReleaseResources()
|
|||||||
{
|
{
|
||||||
if (mVideoIODevice)
|
if (mVideoIODevice)
|
||||||
mVideoIODevice->ReleaseResources();
|
mVideoIODevice->ReleaseResources();
|
||||||
|
if (!VideoBackendLifecycle::CanTransition(mLifecycle.State(), VideoBackendLifecycleState::Stopped))
|
||||||
|
ApplyLifecycleFailure("Video backend resources released before lifecycle completed.");
|
||||||
|
ApplyLifecycleTransition(VideoBackendLifecycleState::Stopped, "Video backend resources released.");
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoBackendLifecycleState VideoBackend::LifecycleState() const
|
||||||
|
{
|
||||||
|
return mLifecycle.State();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoBackend::DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error)
|
bool VideoBackend::DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error)
|
||||||
{
|
{
|
||||||
return mVideoIODevice->DiscoverDevicesAndModes(videoModes, error);
|
ApplyLifecycleTransition(VideoBackendLifecycleState::Discovering, "Discovering video backend devices and modes.");
|
||||||
|
if (mVideoIODevice->DiscoverDevicesAndModes(videoModes, error))
|
||||||
|
return ApplyLifecycleTransition(VideoBackendLifecycleState::Discovered, "Video backend devices and modes discovered.");
|
||||||
|
|
||||||
|
ApplyLifecycleFailure(error.empty() ? "Video backend discovery failed." : error);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoBackend::SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error)
|
bool VideoBackend::SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error)
|
||||||
{
|
{
|
||||||
return mVideoIODevice->SelectPreferredFormats(videoModes, outputAlphaRequired, error);
|
ApplyLifecycleTransition(VideoBackendLifecycleState::Configuring, "Selecting preferred video backend formats.");
|
||||||
|
if (mVideoIODevice->SelectPreferredFormats(videoModes, outputAlphaRequired, error))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
ApplyLifecycleFailure(error.empty() ? "Video backend format selection failed." : error);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoBackend::ConfigureInput(const VideoFormat& inputVideoMode, std::string& error)
|
bool VideoBackend::ConfigureInput(const VideoFormat& inputVideoMode, std::string& error)
|
||||||
{
|
{
|
||||||
return mVideoIODevice->ConfigureInput(
|
if (mLifecycle.State() != VideoBackendLifecycleState::Configuring)
|
||||||
|
ApplyLifecycleTransition(VideoBackendLifecycleState::Configuring, "Configuring video backend input.");
|
||||||
|
if (!mVideoIODevice->ConfigureInput(
|
||||||
[this](const VideoIOFrame& frame) { HandleInputFrame(frame); },
|
[this](const VideoIOFrame& frame) { HandleInputFrame(frame); },
|
||||||
inputVideoMode,
|
inputVideoMode,
|
||||||
error);
|
error))
|
||||||
|
{
|
||||||
|
ApplyLifecycleFailure(error.empty() ? "Video backend input configuration failed." : error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoBackend::ConfigureOutput(const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error)
|
bool VideoBackend::ConfigureOutput(const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error)
|
||||||
{
|
{
|
||||||
return mVideoIODevice->ConfigureOutput(
|
if (mLifecycle.State() != VideoBackendLifecycleState::Configuring)
|
||||||
|
ApplyLifecycleTransition(VideoBackendLifecycleState::Configuring, "Configuring video backend output.");
|
||||||
|
if (!mVideoIODevice->ConfigureOutput(
|
||||||
[this](const VideoIOCompletion& completion) { HandleOutputFrameCompletion(completion); },
|
[this](const VideoIOCompletion& completion) { HandleOutputFrameCompletion(completion); },
|
||||||
outputVideoMode,
|
outputVideoMode,
|
||||||
externalKeyingEnabled,
|
externalKeyingEnabled,
|
||||||
error);
|
error))
|
||||||
|
{
|
||||||
|
ApplyLifecycleFailure(error.empty() ? "Video backend output configuration failed." : error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return ApplyLifecycleTransition(VideoBackendLifecycleState::Configured, "Video backend configured.");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoBackend::Start()
|
bool VideoBackend::Start()
|
||||||
{
|
{
|
||||||
|
ApplyLifecycleTransition(VideoBackendLifecycleState::Prerolling, "Video backend preroll starting.");
|
||||||
const bool started = mVideoIODevice->Start();
|
const bool started = mVideoIODevice->Start();
|
||||||
PublishBackendStateChanged(started ? "started" : "start-failed", started ? "Video backend started." : StatusMessage());
|
if (started)
|
||||||
|
ApplyLifecycleTransition(VideoBackendLifecycleState::Running, "Video backend started.");
|
||||||
|
else
|
||||||
|
ApplyLifecycleFailure(StatusMessage().empty() ? "Video backend start failed." : StatusMessage());
|
||||||
return started;
|
return started;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoBackend::Stop()
|
bool VideoBackend::Stop()
|
||||||
{
|
{
|
||||||
|
ApplyLifecycleTransition(VideoBackendLifecycleState::Stopping, "Video backend stopping.");
|
||||||
const bool stopped = mVideoIODevice->Stop();
|
const bool stopped = mVideoIODevice->Stop();
|
||||||
PublishBackendStateChanged(stopped ? "stopped" : "stop-failed", stopped ? "Video backend stopped." : StatusMessage());
|
if (stopped)
|
||||||
|
ApplyLifecycleTransition(VideoBackendLifecycleState::Stopped, "Video backend stopped.");
|
||||||
|
else
|
||||||
|
ApplyLifecycleFailure(StatusMessage().empty() ? "Video backend stop failed." : StatusMessage());
|
||||||
return stopped;
|
return stopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,7 +238,7 @@ void VideoBackend::PublishStatus(bool externalKeyingConfigured, const std::strin
|
|||||||
externalKeyingConfigured,
|
externalKeyingConfigured,
|
||||||
ExternalKeyingActive(),
|
ExternalKeyingActive(),
|
||||||
StatusMessage());
|
StatusMessage());
|
||||||
PublishBackendStateChanged("status", StatusMessage());
|
PublishBackendStateChanged(VideoBackendLifecycle::StateName(mLifecycle.State()), StatusMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoBackend::ReportNoInputDeviceSignalStatus()
|
void VideoBackend::ReportNoInputDeviceSignalStatus()
|
||||||
@@ -240,7 +280,7 @@ void VideoBackend::HandleOutputFrameCompletion(const VideoIOCompletion& completi
|
|||||||
AccountForCompletionResult(completion.result);
|
AccountForCompletionResult(completion.result);
|
||||||
if (!rendered)
|
if (!rendered)
|
||||||
{
|
{
|
||||||
PublishBackendStateChanged("output-render-failed", "Output frame render request failed; skipping schedule for this frame.");
|
ApplyLifecycleTransition(VideoBackendLifecycleState::Degraded, "Output frame render request failed; skipping schedule for this frame.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,6 +323,32 @@ void VideoBackend::RecordFramePacing(VideoIOCompletionResult completionResult)
|
|||||||
PublishTimingSample("VideoBackend", "smoothedCompletionInterval", mSmoothedCompletionIntervalMilliseconds, "ms");
|
PublishTimingSample("VideoBackend", "smoothedCompletionInterval", mSmoothedCompletionIntervalMilliseconds, "ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool VideoBackend::ApplyLifecycleTransition(VideoBackendLifecycleState state, const std::string& message)
|
||||||
|
{
|
||||||
|
const VideoBackendLifecycleTransition transition = mLifecycle.TransitionTo(state, message);
|
||||||
|
if (!transition.accepted)
|
||||||
|
{
|
||||||
|
PublishBackendStateChanged(VideoBackendLifecycle::StateName(transition.current), transition.errorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PublishBackendStateChanged(VideoBackendLifecycle::StateName(transition.current), message);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VideoBackend::ApplyLifecycleFailure(const std::string& message)
|
||||||
|
{
|
||||||
|
const VideoBackendLifecycleTransition transition = mLifecycle.Fail(message);
|
||||||
|
if (!transition.accepted)
|
||||||
|
{
|
||||||
|
PublishBackendStateChanged(VideoBackendLifecycle::StateName(transition.current), transition.errorMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PublishBackendStateChanged(VideoBackendLifecycle::StateName(transition.current), message);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void VideoBackend::PublishBackendStateChanged(const std::string& state, const std::string& message)
|
void VideoBackend::PublishBackendStateChanged(const std::string& state, const std::string& message)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "VideoBackendLifecycle.h"
|
||||||
#include "VideoIOTypes.h"
|
#include "VideoIOTypes.h"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
@@ -20,6 +21,7 @@ public:
|
|||||||
~VideoBackend();
|
~VideoBackend();
|
||||||
|
|
||||||
void ReleaseResources();
|
void ReleaseResources();
|
||||||
|
VideoBackendLifecycleState LifecycleState() const;
|
||||||
bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error);
|
bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error);
|
||||||
bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error);
|
bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error);
|
||||||
bool ConfigureInput(const VideoFormat& inputVideoMode, std::string& error);
|
bool ConfigureInput(const VideoFormat& inputVideoMode, std::string& error);
|
||||||
@@ -58,6 +60,8 @@ private:
|
|||||||
void HandleInputFrame(const VideoIOFrame& frame);
|
void HandleInputFrame(const VideoIOFrame& frame);
|
||||||
void HandleOutputFrameCompletion(const VideoIOCompletion& completion);
|
void HandleOutputFrameCompletion(const VideoIOCompletion& completion);
|
||||||
void RecordFramePacing(VideoIOCompletionResult completionResult);
|
void RecordFramePacing(VideoIOCompletionResult completionResult);
|
||||||
|
bool ApplyLifecycleTransition(VideoBackendLifecycleState state, const std::string& message);
|
||||||
|
bool ApplyLifecycleFailure(const std::string& message);
|
||||||
void PublishBackendStateChanged(const std::string& state, const std::string& message);
|
void PublishBackendStateChanged(const std::string& state, const std::string& message);
|
||||||
void PublishInputSignalChanged(const VideoIOFrame& frame, const VideoIOState& state);
|
void PublishInputSignalChanged(const VideoIOFrame& frame, const VideoIOState& state);
|
||||||
void PublishInputFrameArrived(const VideoIOFrame& frame);
|
void PublishInputFrameArrived(const VideoIOFrame& frame);
|
||||||
@@ -69,6 +73,7 @@ private:
|
|||||||
|
|
||||||
HealthTelemetry& mHealthTelemetry;
|
HealthTelemetry& mHealthTelemetry;
|
||||||
RuntimeEventDispatcher& mRuntimeEventDispatcher;
|
RuntimeEventDispatcher& mRuntimeEventDispatcher;
|
||||||
|
VideoBackendLifecycle mLifecycle;
|
||||||
std::unique_ptr<VideoIODevice> mVideoIODevice;
|
std::unique_ptr<VideoIODevice> mVideoIODevice;
|
||||||
std::unique_ptr<OpenGLVideoIOBridge> mBridge;
|
std::unique_ptr<OpenGLVideoIOBridge> mBridge;
|
||||||
uint64_t mInputFrameIndex = 0;
|
uint64_t mInputFrameIndex = 0;
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
#include "VideoBackendLifecycle.h"
|
||||||
|
|
||||||
|
VideoBackendLifecycleState VideoBackendLifecycle::State() const
|
||||||
|
{
|
||||||
|
return mState;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& VideoBackendLifecycle::FailureReason() const
|
||||||
|
{
|
||||||
|
return mFailureReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoBackendLifecycleTransition VideoBackendLifecycle::TransitionTo(VideoBackendLifecycleState next, const std::string& reason)
|
||||||
|
{
|
||||||
|
VideoBackendLifecycleTransition transition;
|
||||||
|
transition.previous = mState;
|
||||||
|
transition.current = next;
|
||||||
|
transition.reason = reason;
|
||||||
|
transition.accepted = CanTransition(mState, next);
|
||||||
|
if (!transition.accepted)
|
||||||
|
{
|
||||||
|
transition.current = mState;
|
||||||
|
transition.errorMessage = std::string("Invalid video backend lifecycle transition from ") +
|
||||||
|
StateName(mState) + " to " + StateName(next) + ".";
|
||||||
|
return transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
mState = next;
|
||||||
|
transition.current = mState;
|
||||||
|
if (mState != VideoBackendLifecycleState::Failed)
|
||||||
|
mFailureReason.clear();
|
||||||
|
return transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoBackendLifecycleTransition VideoBackendLifecycle::Fail(const std::string& reason)
|
||||||
|
{
|
||||||
|
VideoBackendLifecycleTransition transition = TransitionTo(VideoBackendLifecycleState::Failed, reason);
|
||||||
|
if (transition.accepted)
|
||||||
|
mFailureReason = reason;
|
||||||
|
return transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VideoBackendLifecycle::CanTransition(VideoBackendLifecycleState current, VideoBackendLifecycleState next)
|
||||||
|
{
|
||||||
|
if (current == next)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
switch (current)
|
||||||
|
{
|
||||||
|
case VideoBackendLifecycleState::Uninitialized:
|
||||||
|
return next == VideoBackendLifecycleState::Discovering ||
|
||||||
|
next == VideoBackendLifecycleState::Stopped ||
|
||||||
|
next == VideoBackendLifecycleState::Failed;
|
||||||
|
case VideoBackendLifecycleState::Discovering:
|
||||||
|
return next == VideoBackendLifecycleState::Discovered ||
|
||||||
|
next == VideoBackendLifecycleState::Failed;
|
||||||
|
case VideoBackendLifecycleState::Discovered:
|
||||||
|
return next == VideoBackendLifecycleState::Configuring ||
|
||||||
|
next == VideoBackendLifecycleState::Stopped ||
|
||||||
|
next == VideoBackendLifecycleState::Failed;
|
||||||
|
case VideoBackendLifecycleState::Configuring:
|
||||||
|
return next == VideoBackendLifecycleState::Configured ||
|
||||||
|
next == VideoBackendLifecycleState::Failed;
|
||||||
|
case VideoBackendLifecycleState::Configured:
|
||||||
|
return next == VideoBackendLifecycleState::Prerolling ||
|
||||||
|
next == VideoBackendLifecycleState::Stopped ||
|
||||||
|
next == VideoBackendLifecycleState::Failed;
|
||||||
|
case VideoBackendLifecycleState::Prerolling:
|
||||||
|
return next == VideoBackendLifecycleState::Running ||
|
||||||
|
next == VideoBackendLifecycleState::Stopping ||
|
||||||
|
next == VideoBackendLifecycleState::Failed;
|
||||||
|
case VideoBackendLifecycleState::Running:
|
||||||
|
return next == VideoBackendLifecycleState::Degraded ||
|
||||||
|
next == VideoBackendLifecycleState::Stopping ||
|
||||||
|
next == VideoBackendLifecycleState::Failed;
|
||||||
|
case VideoBackendLifecycleState::Degraded:
|
||||||
|
return next == VideoBackendLifecycleState::Running ||
|
||||||
|
next == VideoBackendLifecycleState::Stopping ||
|
||||||
|
next == VideoBackendLifecycleState::Failed;
|
||||||
|
case VideoBackendLifecycleState::Stopping:
|
||||||
|
return next == VideoBackendLifecycleState::Stopped ||
|
||||||
|
next == VideoBackendLifecycleState::Failed;
|
||||||
|
case VideoBackendLifecycleState::Stopped:
|
||||||
|
return next == VideoBackendLifecycleState::Discovering ||
|
||||||
|
next == VideoBackendLifecycleState::Failed;
|
||||||
|
case VideoBackendLifecycleState::Failed:
|
||||||
|
return next == VideoBackendLifecycleState::Stopped ||
|
||||||
|
next == VideoBackendLifecycleState::Discovering;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* VideoBackendLifecycle::StateName(VideoBackendLifecycleState state)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case VideoBackendLifecycleState::Uninitialized:
|
||||||
|
return "uninitialized";
|
||||||
|
case VideoBackendLifecycleState::Discovering:
|
||||||
|
return "discovering";
|
||||||
|
case VideoBackendLifecycleState::Discovered:
|
||||||
|
return "discovered";
|
||||||
|
case VideoBackendLifecycleState::Configuring:
|
||||||
|
return "configuring";
|
||||||
|
case VideoBackendLifecycleState::Configured:
|
||||||
|
return "configured";
|
||||||
|
case VideoBackendLifecycleState::Prerolling:
|
||||||
|
return "prerolling";
|
||||||
|
case VideoBackendLifecycleState::Running:
|
||||||
|
return "running";
|
||||||
|
case VideoBackendLifecycleState::Degraded:
|
||||||
|
return "degraded";
|
||||||
|
case VideoBackendLifecycleState::Stopping:
|
||||||
|
return "stopping";
|
||||||
|
case VideoBackendLifecycleState::Stopped:
|
||||||
|
return "stopped";
|
||||||
|
case VideoBackendLifecycleState::Failed:
|
||||||
|
return "failed";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
enum class VideoBackendLifecycleState
|
||||||
|
{
|
||||||
|
Uninitialized,
|
||||||
|
Discovering,
|
||||||
|
Discovered,
|
||||||
|
Configuring,
|
||||||
|
Configured,
|
||||||
|
Prerolling,
|
||||||
|
Running,
|
||||||
|
Degraded,
|
||||||
|
Stopping,
|
||||||
|
Stopped,
|
||||||
|
Failed
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VideoBackendLifecycleTransition
|
||||||
|
{
|
||||||
|
VideoBackendLifecycleState previous = VideoBackendLifecycleState::Uninitialized;
|
||||||
|
VideoBackendLifecycleState current = VideoBackendLifecycleState::Uninitialized;
|
||||||
|
bool accepted = false;
|
||||||
|
std::string reason;
|
||||||
|
std::string errorMessage;
|
||||||
|
};
|
||||||
|
|
||||||
|
class VideoBackendLifecycle
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
VideoBackendLifecycleState State() const;
|
||||||
|
const std::string& FailureReason() const;
|
||||||
|
VideoBackendLifecycleTransition TransitionTo(VideoBackendLifecycleState next, const std::string& reason);
|
||||||
|
VideoBackendLifecycleTransition Fail(const std::string& reason);
|
||||||
|
|
||||||
|
static bool CanTransition(VideoBackendLifecycleState current, VideoBackendLifecycleState next);
|
||||||
|
static const char* StateName(VideoBackendLifecycleState state);
|
||||||
|
|
||||||
|
private:
|
||||||
|
VideoBackendLifecycleState mState = VideoBackendLifecycleState::Uninitialized;
|
||||||
|
std::string mFailureReason;
|
||||||
|
};
|
||||||
@@ -9,8 +9,8 @@ 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: not started.
|
- Phase 7 implementation: Step 1 complete.
|
||||||
- Current alignment: `VideoBackend`, `VideoIODevice`, `DeckLinkSession`, 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:
|
||||||
|
|
||||||
@@ -206,9 +206,16 @@ Introduce backend state enum and transition reporting without changing schedulin
|
|||||||
|
|
||||||
Initial target:
|
Initial target:
|
||||||
|
|
||||||
- state changes are explicit
|
- [x] state changes are explicit
|
||||||
- invalid transitions are detectable
|
- [x] invalid transitions are detectable
|
||||||
- tests cover allowed transitions
|
- [x] tests cover allowed transitions
|
||||||
|
|
||||||
|
Current implementation:
|
||||||
|
|
||||||
|
- `VideoBackendLifecycle` names backend states and validates allowed transitions.
|
||||||
|
- `VideoBackend` applies lifecycle transitions around discovery, configuration, start, stop, degradation, failure, and resource release.
|
||||||
|
- Existing `BackendStateChangedEvent` publication now uses lifecycle state names for backend lifecycle observations.
|
||||||
|
- `VideoBackendLifecycleTests` cover allowed transitions, rejected invalid transitions, failure reasons, retry, and stable state names.
|
||||||
|
|
||||||
### Step 2. Create Playout Policy Object
|
### Step 2. Create Playout Policy Object
|
||||||
|
|
||||||
@@ -313,7 +320,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:
|
||||||
|
|
||||||
- [ ] 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
|
- [ ] 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
|
||||||
|
|||||||
96
tests/VideoBackendLifecycleTests.cpp
Normal file
96
tests/VideoBackendLifecycleTests.cpp
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
#include "VideoBackendLifecycle.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
int gFailures = 0;
|
||||||
|
|
||||||
|
void Expect(bool condition, const char* message)
|
||||||
|
{
|
||||||
|
if (condition)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::cerr << "FAIL: " << message << "\n";
|
||||||
|
++gFailures;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestAllowedLifecycleTransitions()
|
||||||
|
{
|
||||||
|
VideoBackendLifecycle lifecycle;
|
||||||
|
Expect(lifecycle.State() == VideoBackendLifecycleState::Uninitialized, "lifecycle starts uninitialized");
|
||||||
|
|
||||||
|
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Discovering, "discover").accepted,
|
||||||
|
"uninitialized can transition to discovering");
|
||||||
|
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Discovered, "discovered").accepted,
|
||||||
|
"discovering can transition to discovered");
|
||||||
|
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Configuring, "configuring").accepted,
|
||||||
|
"discovered can transition to configuring");
|
||||||
|
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Configured, "configured").accepted,
|
||||||
|
"configuring can transition to configured");
|
||||||
|
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Prerolling, "preroll").accepted,
|
||||||
|
"configured can transition to prerolling");
|
||||||
|
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Running, "running").accepted,
|
||||||
|
"prerolling can transition to running");
|
||||||
|
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Degraded, "degraded").accepted,
|
||||||
|
"running can transition to degraded");
|
||||||
|
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Running, "recovered").accepted,
|
||||||
|
"degraded can transition back to running");
|
||||||
|
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Stopping, "stopping").accepted,
|
||||||
|
"running can transition to stopping");
|
||||||
|
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Stopped, "stopped").accepted,
|
||||||
|
"stopping can transition to stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestInvalidLifecycleTransitionIsRejected()
|
||||||
|
{
|
||||||
|
VideoBackendLifecycle lifecycle;
|
||||||
|
const VideoBackendLifecycleTransition transition =
|
||||||
|
lifecycle.TransitionTo(VideoBackendLifecycleState::Running, "skip setup");
|
||||||
|
Expect(!transition.accepted, "uninitialized cannot transition directly to running");
|
||||||
|
Expect(lifecycle.State() == VideoBackendLifecycleState::Uninitialized, "invalid transition leaves state unchanged");
|
||||||
|
Expect(transition.errorMessage.find("Invalid video backend lifecycle transition") != std::string::npos,
|
||||||
|
"invalid transition reports an error");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestFailureStateRecordsReasonAndCanRecover()
|
||||||
|
{
|
||||||
|
VideoBackendLifecycle lifecycle;
|
||||||
|
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Discovering, "discover").accepted,
|
||||||
|
"lifecycle can start discovery");
|
||||||
|
Expect(lifecycle.Fail("no device").accepted, "discovering can transition to failed");
|
||||||
|
Expect(lifecycle.State() == VideoBackendLifecycleState::Failed, "failure transition sets failed state");
|
||||||
|
Expect(lifecycle.FailureReason() == "no device", "failure reason is retained");
|
||||||
|
Expect(lifecycle.TransitionTo(VideoBackendLifecycleState::Discovering, "retry").accepted,
|
||||||
|
"failed lifecycle can retry discovery");
|
||||||
|
Expect(lifecycle.FailureReason().empty(), "successful non-failed transition clears failure reason");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestStateNamesAreStable()
|
||||||
|
{
|
||||||
|
Expect(std::string(VideoBackendLifecycle::StateName(VideoBackendLifecycleState::Uninitialized)) == "uninitialized",
|
||||||
|
"uninitialized state name is stable");
|
||||||
|
Expect(std::string(VideoBackendLifecycle::StateName(VideoBackendLifecycleState::Running)) == "running",
|
||||||
|
"running state name is stable");
|
||||||
|
Expect(std::string(VideoBackendLifecycle::StateName(VideoBackendLifecycleState::Failed)) == "failed",
|
||||||
|
"failed state name is stable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
TestAllowedLifecycleTransitions();
|
||||||
|
TestInvalidLifecycleTransitionIsRejected();
|
||||||
|
TestFailureStateRecordsReasonAndCanRecover();
|
||||||
|
TestStateNamesAreStable();
|
||||||
|
|
||||||
|
if (gFailures != 0)
|
||||||
|
{
|
||||||
|
std::cerr << gFailures << " video backend lifecycle test failure(s).\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "VideoBackendLifecycle tests passed.\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user