From 2fdb1741f959ada42d64128b4dac4ac48b3e4c48 Mon Sep 17 00:00:00 2001 From: Aiden Date: Fri, 22 May 2026 15:45:54 +1000 Subject: [PATCH] legacy code cleanup --- CMakeLists.txt | 20 - README.md | 1 - src/video/core/VideoIOTypes.h | 61 - src/video/decklink/DeckLinkFrameTransfer.cpp | 48 +- src/video/decklink/DeckLinkFrameTransfer.h | 20 - src/video/decklink/DeckLinkSession.cpp | 230 +--- src/video/decklink/DeckLinkSession.h | 38 +- src/video/legacy/VideoBackend.cpp | 1095 ----------------- src/video/legacy/VideoBackend.h | 161 --- src/video/legacy/VideoBackendLifecycle.cpp | 123 -- src/video/legacy/VideoBackendLifecycle.h | 43 - .../playout/OutputProductionController.cpp | 89 -- .../playout/OutputProductionController.h | 46 - src/video/playout/RenderCadenceController.cpp | 102 -- src/video/playout/RenderCadenceController.h | 68 - src/video/playout/RenderOutputQueue.cpp | 93 -- src/video/playout/RenderOutputQueue.h | 52 - src/video/playout/SystemOutputFramePool.cpp | 260 ---- src/video/playout/SystemOutputFramePool.h | 94 -- tests/CMakeLists.txt | 31 - tests/OutputProductionControllerTests.cpp | 142 --- tests/RenderCadenceControllerTests.cpp | 172 --- tests/RenderOutputQueueTests.cpp | 209 ---- tests/SystemOutputFramePoolTests.cpp | 231 ---- tests/VideoBackendLifecycleTests.cpp | 96 -- tests/VideoIODeviceFakeTests.cpp | 186 --- 26 files changed, 27 insertions(+), 3684 deletions(-) delete mode 100644 src/video/legacy/VideoBackend.cpp delete mode 100644 src/video/legacy/VideoBackend.h delete mode 100644 src/video/legacy/VideoBackendLifecycle.cpp delete mode 100644 src/video/legacy/VideoBackendLifecycle.h delete mode 100644 src/video/playout/OutputProductionController.cpp delete mode 100644 src/video/playout/OutputProductionController.h delete mode 100644 src/video/playout/RenderCadenceController.cpp delete mode 100644 src/video/playout/RenderCadenceController.h delete mode 100644 src/video/playout/RenderOutputQueue.cpp delete mode 100644 src/video/playout/RenderOutputQueue.h delete mode 100644 src/video/playout/SystemOutputFramePool.cpp delete mode 100644 src/video/playout/SystemOutputFramePool.h delete mode 100644 tests/OutputProductionControllerTests.cpp delete mode 100644 tests/RenderCadenceControllerTests.cpp delete mode 100644 tests/RenderOutputQueueTests.cpp delete mode 100644 tests/SystemOutputFramePoolTests.cpp delete mode 100644 tests/VideoBackendLifecycleTests.cpp delete mode 100644 tests/VideoIODeviceFakeTests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 86b5b04..9c7dcda 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,7 +38,6 @@ set(VIDEO_SHADER_INCLUDE_DIRS "${SRC_DIR}/video" "${SRC_DIR}/video/core" "${SRC_DIR}/video/decklink" - "${SRC_DIR}/video/legacy" "${SRC_DIR}/video/playout" ) @@ -114,21 +113,6 @@ set(VIDEO_FORMAT_SOURCES "${SRC_DIR}/video/core/VideoIOFormat.cpp" ) -set(LEGACY_VIDEO_BACKEND_SOURCES - "${SRC_DIR}/video/legacy/VideoBackend.cpp" - "${SRC_DIR}/video/legacy/VideoBackend.h" - "${SRC_DIR}/video/legacy/VideoBackendLifecycle.cpp" - "${SRC_DIR}/video/legacy/VideoBackendLifecycle.h" - "${SRC_DIR}/video/playout/OutputProductionController.cpp" - "${SRC_DIR}/video/playout/OutputProductionController.h" - "${SRC_DIR}/video/playout/RenderCadenceController.cpp" - "${SRC_DIR}/video/playout/RenderCadenceController.h" - "${SRC_DIR}/video/playout/RenderOutputQueue.cpp" - "${SRC_DIR}/video/playout/RenderOutputQueue.h" - "${SRC_DIR}/video/playout/SystemOutputFramePool.cpp" - "${SRC_DIR}/video/playout/SystemOutputFramePool.h" -) - set(SLANG_RUNTIME_FILES "${SLANG_ROOT}/bin/slangc.exe" "${SLANG_ROOT}/bin/slang-compiler.dll" @@ -164,10 +148,6 @@ else() "${SRC_DIR}/*.cpp" "${SRC_DIR}/*.h" ) - list(REMOVE_ITEM RENDER_CADENCE_APP_SOURCES - ${LEGACY_VIDEO_BACKEND_SOURCES} - ) - add_executable(RenderCadenceCompositor ${RENDER_CADENCE_APP_SOURCES}) video_shader_target_defaults(RenderCadenceCompositor) target_link_libraries(RenderCadenceCompositor PRIVATE diff --git a/README.md b/README.md index f92b47e..4ad01cd 100644 --- a/README.md +++ b/README.md @@ -293,7 +293,6 @@ If `SLANG_ROOT` or `MSDF_ATLAS_GEN_ROOT` is not set, the workflow falls back to - Add more video I/O backends now that the DeckLink path is isolated under `src/video/decklink/`. - Support a separate sound shader `.slang` file in shader packages. (https://www.shadertoy.com/view/XsBXWt) - Add WebView2 for an embedded native control surface. -- MSDF typography rasterisation - More shader-library organisation and filtering as the built-in library grows. - Optional linear-light compositing mode. - compute shaders or a small 1x1 or nx1 RGBA16f render target for arbitrary data storage diff --git a/src/video/core/VideoIOTypes.h b/src/video/core/VideoIOTypes.h index 1b31ee1..88d472c 100644 --- a/src/video/core/VideoIOTypes.h +++ b/src/video/core/VideoIOTypes.h @@ -4,7 +4,6 @@ #include "VideoMode.h" #include -#include #include enum class VideoIOBackend @@ -21,13 +20,6 @@ enum class VideoIOCompletionResult Unknown }; -struct VideoIOConfig -{ - VideoFormatSelection videoModes; - bool externalKeyingEnabled = false; - bool preferTenBit = true; -}; - struct VideoIOState { FrameSize inputFrameSize; @@ -109,56 +101,3 @@ struct VideoPlayoutRecoveryDecision uint64_t lateStreak = 0; uint64_t dropStreak = 0; }; - -class VideoIODevice -{ -public: - using InputFrameCallback = std::function; - using OutputFrameCallback = std::function; - - virtual ~VideoIODevice() = default; - virtual void ReleaseResources() = 0; - virtual bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error) = 0; - virtual bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error) = 0; - virtual bool ConfigureInput(InputFrameCallback callback, const VideoFormat& inputVideoMode, std::string& error) = 0; - virtual bool ConfigureOutput(OutputFrameCallback callback, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error) = 0; - virtual bool PrepareOutputSchedule() = 0; - virtual bool StartInputStreams() = 0; - virtual bool StartScheduledPlayback() = 0; - virtual bool Start() = 0; - virtual bool Stop() = 0; - virtual const VideoIOState& State() const = 0; - virtual VideoIOState& MutableState() = 0; - virtual bool BeginOutputFrame(VideoIOOutputFrame& frame) = 0; - virtual void EndOutputFrame(VideoIOOutputFrame& frame) = 0; - virtual bool ScheduleOutputFrame(const VideoIOOutputFrame& frame) = 0; - virtual VideoPlayoutRecoveryDecision AccountForCompletionResult(VideoIOCompletionResult result, uint64_t readyQueueDepth) = 0; - - bool HasInputDevice() const { return State().hasInputDevice; } - bool HasInputSource() const { return State().hasInputSource; } - bool InputOutputDimensionsDiffer() const { return State().inputFrameSize != State().outputFrameSize; } - const FrameSize& InputFrameSize() const { return State().inputFrameSize; } - const FrameSize& OutputFrameSize() const { return State().outputFrameSize; } - unsigned InputFrameWidth() const { return State().inputFrameSize.width; } - unsigned InputFrameHeight() const { return State().inputFrameSize.height; } - unsigned OutputFrameWidth() const { return State().outputFrameSize.width; } - unsigned OutputFrameHeight() const { return State().outputFrameSize.height; } - VideoIOPixelFormat InputPixelFormat() const { return State().inputPixelFormat; } - VideoIOPixelFormat OutputPixelFormat() const { return State().outputPixelFormat; } - bool InputIsTenBit() const { return VideoIOPixelFormatIsTenBit(State().inputPixelFormat); } - bool OutputIsTenBit() const { return VideoIOPixelFormatIsTenBit(State().outputPixelFormat); } - unsigned InputFrameRowBytes() const { return State().inputFrameRowBytes; } - unsigned OutputFrameRowBytes() const { return State().outputFrameRowBytes; } - unsigned CaptureTextureWidth() const { return State().captureTextureWidth; } - unsigned OutputPackTextureWidth() const { return State().outputPackTextureWidth; } - const std::string& FormatStatusMessage() const { return State().formatStatusMessage; } - const std::string& InputDisplayModeName() const { return State().inputDisplayModeName; } - const std::string& OutputModelName() const { return State().outputModelName; } - bool SupportsInternalKeying() const { return State().supportsInternalKeying; } - bool SupportsExternalKeying() const { return State().supportsExternalKeying; } - bool KeyerInterfaceAvailable() const { return State().keyerInterfaceAvailable; } - bool ExternalKeyingActive() const { return State().externalKeyingActive; } - const std::string& StatusMessage() const { return State().statusMessage; } - double FrameBudgetMilliseconds() const { return State().frameBudgetMilliseconds; } - void SetStatusMessage(const std::string& message) { MutableState().statusMessage = message; } -}; diff --git a/src/video/decklink/DeckLinkFrameTransfer.cpp b/src/video/decklink/DeckLinkFrameTransfer.cpp index d293c01..ecf8f96 100644 --- a/src/video/decklink/DeckLinkFrameTransfer.cpp +++ b/src/video/decklink/DeckLinkFrameTransfer.cpp @@ -2,52 +2,6 @@ #include "DeckLinkSession.h" -//////////////////////////////////////////// -// DeckLink Capture Delegate Class -//////////////////////////////////////////// -CaptureDelegate::CaptureDelegate(DeckLinkSession* pOwner) : - m_pOwner(pOwner), - mRefCount(1) -{ -} - -HRESULT CaptureDelegate::QueryInterface(REFIID, LPVOID* ppv) -{ - *ppv = NULL; - return E_NOINTERFACE; -} - -ULONG CaptureDelegate::AddRef() -{ - return InterlockedIncrement(&mRefCount); -} - -ULONG CaptureDelegate::Release() -{ - int newCount = InterlockedDecrement(&mRefCount); - if (newCount == 0) - delete this; - return newCount; -} - -HRESULT CaptureDelegate::VideoInputFrameArrived(IDeckLinkVideoInputFrame* inputFrame, IDeckLinkAudioInputPacket*) -{ - if (!inputFrame) - { - // It's possible to receive a NULL inputFrame, but a valid audioPacket. Ignore audio-only frame. - return S_OK; - } - - bool hasNoInputSource = (inputFrame->GetFlags() & bmdFrameHasNoInputSource) == bmdFrameHasNoInputSource; - m_pOwner->HandleVideoInputFrame(inputFrame, hasNoInputSource); - return S_OK; -} - -HRESULT CaptureDelegate::VideoInputFormatChanged(BMDVideoInputFormatChangedEvents, IDeckLinkDisplayMode*, BMDDetectedVideoInputFormatFlags) -{ - return S_OK; -} - //////////////////////////////////////////// // DeckLink Playout Delegate Class //////////////////////////////////////////// @@ -84,7 +38,7 @@ HRESULT PlayoutDelegate::ScheduledFrameCompleted(IDeckLinkVideoFrame* completedF case bmdOutputFrameDropped: case bmdOutputFrameCompleted: case bmdOutputFrameFlushed: - // Late/drop counts are recorded by VideoBackend; keep this callback lean. + // Late/drop counts are recorded by the output edge; keep this callback lean. break; default: OutputDebugStringA("ScheduledFrameCompleted() frame did not complete: Unknown error\n"); diff --git a/src/video/decklink/DeckLinkFrameTransfer.h b/src/video/decklink/DeckLinkFrameTransfer.h index de79944..b0da759 100644 --- a/src/video/decklink/DeckLinkFrameTransfer.h +++ b/src/video/decklink/DeckLinkFrameTransfer.h @@ -8,26 +8,6 @@ class DeckLinkSession; -//////////////////////////////////////////// -// Capture Delegate Class -//////////////////////////////////////////// -class CaptureDelegate : public IDeckLinkInputCallback -{ - DeckLinkSession* m_pOwner; - LONG mRefCount; - -public: - CaptureDelegate(DeckLinkSession* pOwner); - - // IUnknown needs only a dummy implementation - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID* ppv); - virtual ULONG STDMETHODCALLTYPE AddRef(); - virtual ULONG STDMETHODCALLTYPE Release(); - - virtual HRESULT STDMETHODCALLTYPE VideoInputFrameArrived(IDeckLinkVideoInputFrame* videoFrame, IDeckLinkAudioInputPacket* audioPacket); - virtual HRESULT STDMETHODCALLTYPE VideoInputFormatChanged(BMDVideoInputFormatChangedEvents notificationEvents, IDeckLinkDisplayMode* newDisplayMode, BMDDetectedVideoInputFormatFlags detectedSignalFlags); -}; - //////////////////////////////////////////// // Render Delegate Class //////////////////////////////////////////// diff --git a/src/video/decklink/DeckLinkSession.cpp b/src/video/decklink/DeckLinkSession.cpp index 8ce8383..63cae9d 100644 --- a/src/video/decklink/DeckLinkSession.cpp +++ b/src/video/decklink/DeckLinkSession.cpp @@ -100,24 +100,6 @@ std::string BstrToUtf8(BSTR value) return std::string(utf8Name.data()); } -bool InputSupportsFormat(IDeckLinkInput* input, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat) -{ - if (input == nullptr) - return false; - - BOOL supported = FALSE; - BMDDisplayMode actualMode = bmdModeUnknown; - const HRESULT result = input->DoesSupportVideoMode( - bmdVideoConnectionUnspecified, - displayMode, - pixelFormat, - bmdNoVideoInputConversion, - bmdSupportedVideoModeDefault, - &actualMode, - &supported); - return result == S_OK && supported != FALSE; -} - bool OutputSupportsFormat(IDeckLinkOutput* output, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat) { if (output == nullptr) @@ -144,11 +126,6 @@ DeckLinkSession::~DeckLinkSession() void DeckLinkSession::ReleaseResources() { - if (input != nullptr) - input->SetCallback(nullptr); - captureDelegate.Release(); - input.Release(); - if (output != nullptr) output->SetScheduledFrameCompletionCallback(nullptr); @@ -167,24 +144,17 @@ void DeckLinkSession::ReleaseResources() bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error) { CComPtr deckLinkIterator; - CComPtr inputMode; CComPtr outputMode; - BMDDisplayMode inputDisplayMode = bmdModeUnknown; BMDDisplayMode outputDisplayMode = bmdModeUnknown; - if (!DeckLinkDisplayModeForVideoFormat(videoModes.input, inputDisplayMode)) - { - error = "Cannot map configured input mode to DeckLink BMDDisplayMode: " + videoModes.input.displayName; - return false; - } if (!DeckLinkDisplayModeForVideoFormat(videoModes.output, outputDisplayMode)) { error = "Cannot map configured output mode to DeckLink BMDDisplayMode: " + videoModes.output.displayName; return false; } - mState.inputDisplayModeName = videoModes.input.displayName; mState.outputDisplayModeName = videoModes.output.displayName; + mState.inputDisplayModeName = "No input - output session"; HRESULT result = CoCreateInstance(CLSID_CDeckLinkIterator, nullptr, CLSCTX_ALL, IID_IDeckLinkIterator, reinterpret_cast(&deckLinkIterator)); if (FAILED(result)) @@ -226,11 +196,7 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM continue; } - bool inputUsed = false; - if (!input && deckLink->QueryInterface(IID_IDeckLinkInput, (void**)&input) == S_OK) - inputUsed = true; - - if (!output && (!inputUsed || (duplexMode == bmdDuplexFull))) + if (!output) { if (deckLink->QueryInterface(IID_IDeckLinkOutput, (void**)&output) != S_OK) output.Release(); @@ -244,7 +210,7 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM deckLink.Release(); - if (output && input) + if (output) break; } @@ -255,22 +221,6 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM return false; } - CComPtr inputDisplayModeIterator; - if (input && input->GetDisplayModeIterator(&inputDisplayModeIterator) != S_OK) - { - error = "Cannot get input Display Mode Iterator."; - ReleaseResources(); - return false; - } - - if (input && !FindDeckLinkDisplayMode(inputDisplayModeIterator, inputDisplayMode, &inputMode)) - { - error = "Cannot get specified input BMDDisplayMode for configured mode: " + videoModes.input.displayName; - ReleaseResources(); - return false; - } - inputDisplayModeIterator.Release(); - CComPtr outputDisplayModeIterator; if (output->GetDisplayModeIterator(&outputDisplayModeIterator) != S_OK) { @@ -287,11 +237,7 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM } mState.outputFrameSize = { static_cast(outputMode->GetWidth()), static_cast(outputMode->GetHeight()) }; - mState.inputFrameSize = inputMode - ? FrameSize{ static_cast(inputMode->GetWidth()), static_cast(inputMode->GetHeight()) } - : mState.outputFrameSize; - if (!input) - mState.inputDisplayModeName = "No input - black frame"; + mState.inputFrameSize = mState.outputFrameSize; BMDTimeValue frameDuration = 0; BMDTimeScale frameTimescale = 0; outputMode->GetFrameRate(&frameDuration, &frameTimescale); @@ -302,7 +248,7 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM mState.outputFrameRowBytes = mState.outputFrameSize.width * 4u; mState.captureTextureWidth = mState.inputFrameSize.width / 2u; mState.outputPackTextureWidth = mState.outputFrameSize.width; - mState.hasInputDevice = input != nullptr; + mState.hasInputDevice = false; mState.hasInputSource = false; return true; @@ -317,19 +263,14 @@ bool DeckLinkSession::SelectPreferredFormats(const VideoFormatSelection& videoMo } mState.formatStatusMessage.clear(); - BMDDisplayMode inputDisplayMode = bmdModeUnknown; BMDDisplayMode outputDisplayMode = bmdModeUnknown; - if (!DeckLinkDisplayModeForVideoFormat(videoModes.input, inputDisplayMode) || - !DeckLinkDisplayModeForVideoFormat(videoModes.output, outputDisplayMode)) + if (!DeckLinkDisplayModeForVideoFormat(videoModes.output, outputDisplayMode)) { - error = "DeckLink format selection failed while mapping configured video modes."; + error = "DeckLink format selection failed while mapping the configured output mode."; return false; } - const bool inputTenBitSupported = input != nullptr && InputSupportsFormat(input, inputDisplayMode, bmdFormat10BitYUV); - mState.inputPixelFormat = input != nullptr ? ChoosePreferredVideoIOFormat(inputTenBitSupported) : VideoIOPixelFormat::Uyvy8; - if (input != nullptr && !inputTenBitSupported) - mState.formatStatusMessage += "DeckLink input does not report 10-bit YUV support for the configured mode; using 8-bit capture. "; + mState.inputPixelFormat = VideoIOPixelFormat::Uyvy8; const bool outputTenBitSupported = OutputSupportsFormat(output, outputDisplayMode, bmdFormat10BitYUV); const bool outputTenBitYuvaSupported = OutputSupportsFormat(output, outputDisplayMode, bmdFormat10BitYUVA); @@ -371,75 +312,13 @@ bool DeckLinkSession::SelectPreferredFormats(const VideoFormatSelection& videoMo : mState.inputFrameSize.width / 2u; std::ostringstream status; - status << "DeckLink formats: capture " << (input ? VideoIOPixelFormatName(mState.inputPixelFormat) : "none") - << ", output " << VideoIOPixelFormatName(mState.outputPixelFormat) << "."; + status << "DeckLink formats: input none, output " << VideoIOPixelFormatName(mState.outputPixelFormat) << "."; if (!mState.formatStatusMessage.empty()) status << " " << mState.formatStatusMessage; mState.formatStatusMessage = status.str(); return true; } -bool DeckLinkSession::ConfigureInput(InputFrameCallback callback, const VideoFormat& inputVideoMode, std::string& error) -{ - mInputFrameCallback = std::move(callback); - - if (!input) - { - mState.hasInputSource = false; - mState.inputDisplayModeName = "No input - black frame"; - return true; - } - - BMDDisplayMode inputDisplayMode = bmdModeUnknown; - if (!DeckLinkDisplayModeForVideoFormat(inputVideoMode, inputDisplayMode)) - { - error = "DeckLink input setup failed while mapping " + inputVideoMode.displayName + " to a DeckLink display mode."; - return false; - } - const BMDPixelFormat deckLinkInputPixelFormat = DeckLinkPixelFormatForVideoIO(mState.inputPixelFormat); - if (input->EnableVideoInput(inputDisplayMode, deckLinkInputPixelFormat, bmdVideoInputFlagDefault) != S_OK) - { - if (mState.inputPixelFormat == VideoIOPixelFormat::V210) - { - OutputDebugStringA("DeckLink 10-bit input could not be enabled; falling back to 8-bit capture.\n"); - mState.inputPixelFormat = VideoIOPixelFormat::Uyvy8; - mState.inputFrameRowBytes = mState.inputFrameSize.width * 2u; - mState.captureTextureWidth = mState.inputFrameSize.width / 2u; - if (input->EnableVideoInput(inputDisplayMode, bmdFormat8BitYUV, bmdVideoInputFlagDefault) == S_OK) - { - std::ostringstream status; - status << "DeckLink formats: capture " << VideoIOPixelFormatName(mState.inputPixelFormat) - << ", output " << VideoIOPixelFormatName(mState.outputPixelFormat) - << ". DeckLink 10-bit input enable failed; using 8-bit capture."; - mState.formatStatusMessage = status.str(); - goto input_enabled; - } - } - - OutputDebugStringA("DeckLink input could not be enabled; continuing in output-only black-frame mode.\n"); - input.Release(); - mState.hasInputDevice = false; - mState.hasInputSource = false; - mState.inputDisplayModeName = "No input - black frame"; - return true; - } - -input_enabled: - captureDelegate.Attach(new (std::nothrow) CaptureDelegate(this)); - if (captureDelegate == nullptr) - { - error = "DeckLink input setup failed while creating the capture callback."; - return false; - } - if (input->SetCallback(captureDelegate) != S_OK) - { - error = "DeckLink input setup failed while installing the capture callback."; - return false; - } - - return true; -} - bool DeckLinkSession::ConfigureOutput(OutputFrameCallback callback, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error) { mOutputFrameCallback = std::move(callback); @@ -772,19 +651,6 @@ bool DeckLinkSession::PrepareOutputSchedule() return output != nullptr; } -bool DeckLinkSession::StartInputStreams() -{ - if (!input) - return true; - - if (input->StartStreams() != S_OK) - { - MessageBoxA(NULL, "Could not start the DeckLink input stream.", "DeckLink start failed", MB_OK | MB_ICONERROR); - return false; - } - return true; -} - bool DeckLinkSession::StartScheduledPlayback() { if (!output) @@ -802,42 +668,6 @@ bool DeckLinkSession::StartScheduledPlayback() return true; } -bool DeckLinkSession::Start() -{ - if (!output) - { - MessageBoxA(NULL, "Cannot start playout because no DeckLink output device is available.", "DeckLink start failed", MB_OK | MB_ICONERROR); - return false; - } - if (outputVideoFrameQueue.empty()) - { - MessageBoxA(NULL, "Cannot start playout because the output frame queue is empty.", "DeckLink start failed", MB_OK | MB_ICONERROR); - return false; - } - - const VideoPlayoutPolicy policy = NormalizeVideoPlayoutPolicy(mPlayoutPolicy); - mPlayoutPolicy = policy; - if (!PrepareOutputSchedule()) - return false; - - for (unsigned i = 0; i < policy.targetPrerollFrames; i++) - { - CComPtr outputVideoFrame; - if (!AcquireNextOutputVideoFrame(outputVideoFrame)) - { - MessageBoxA(NULL, "Could not acquire a preroll output frame.", "DeckLink start failed", MB_OK | MB_ICONERROR); - return false; - } - if (!ScheduleBlackFrame(outputVideoFrame)) - { - MessageBoxA(NULL, "Could not schedule a preroll output frame.", "DeckLink start failed", MB_OK | MB_ICONERROR); - return false; - } - } - - return StartInputStreams() && StartScheduledPlayback(); -} - bool DeckLinkSession::Stop() { if (keyer != nullptr) @@ -846,12 +676,6 @@ bool DeckLinkSession::Stop() mState.externalKeyingActive = false; } - if (input) - { - input->StopStreams(); - input->DisableVideoInput(); - } - if (output) { output->StopScheduledPlayback(0, NULL, 0); @@ -861,42 +685,6 @@ bool DeckLinkSession::Stop() return true; } -void DeckLinkSession::HandleVideoInputFrame(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource) -{ - mState.hasInputSource = !hasNoInputSource; - if (hasNoInputSource || mInputFrameCallback == nullptr) - { - VideoIOFrame frame; - frame.width = mState.inputFrameSize.width; - frame.height = mState.inputFrameSize.height; - frame.pixelFormat = mState.inputPixelFormat; - frame.hasNoInputSource = hasNoInputSource; - if (mInputFrameCallback) - mInputFrameCallback(frame); - return; - } - - CComPtr inputFrameBuffer; - void* videoPixels = nullptr; - if (inputFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&inputFrameBuffer) != S_OK) - return; - if (inputFrameBuffer->StartAccess(bmdBufferAccessRead) != S_OK) - return; - - inputFrameBuffer->GetBytes(&videoPixels); - - VideoIOFrame frame; - frame.bytes = videoPixels; - frame.rowBytes = inputFrame->GetRowBytes(); - frame.width = static_cast(inputFrame->GetWidth()); - frame.height = static_cast(inputFrame->GetHeight()); - frame.pixelFormat = mState.inputPixelFormat; - frame.hasNoInputSource = hasNoInputSource; - mInputFrameCallback(frame); - - inputFrameBuffer->EndAccess(bmdBufferAccessRead); -} - void DeckLinkSession::HandlePlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completionResult) { RefreshBufferedVideoFrameCount(); diff --git a/src/video/decklink/DeckLinkSession.h b/src/video/decklink/DeckLinkSession.h index e1442cc..7a82ba2 100644 --- a/src/video/decklink/DeckLinkSession.h +++ b/src/video/decklink/DeckLinkSession.h @@ -11,28 +11,28 @@ #include #include +#include #include #include #include class OpenGLComposite; -class DeckLinkSession : public VideoIODevice +class DeckLinkSession { public: DeckLinkSession() = default; ~DeckLinkSession(); - void ReleaseResources() override; - bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error) override; - bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error) override; - bool ConfigureInput(InputFrameCallback callback, const VideoFormat& inputVideoMode, std::string& error) override; - bool ConfigureOutput(OutputFrameCallback callback, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error) override; - bool PrepareOutputSchedule() override; - bool StartInputStreams() override; - bool StartScheduledPlayback() override; - bool Start() override; - bool Stop() override; + using OutputFrameCallback = std::function; + + void ReleaseResources(); + bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error); + bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error); + bool ConfigureOutput(OutputFrameCallback callback, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error); + bool PrepareOutputSchedule(); + bool StartScheduledPlayback(); + bool Stop(); bool HasInputDevice() const { return mState.hasInputDevice; } bool HasInputSource() const { return mState.hasInputSource; } @@ -61,14 +61,13 @@ public: bool ExternalKeyingActive() const { return mState.externalKeyingActive; } const std::string& StatusMessage() const { return mState.statusMessage; } void SetStatusMessage(const std::string& message) { mState.statusMessage = message; } - const VideoIOState& State() const override { return mState; } - VideoIOState& MutableState() override { return mState; } + const VideoIOState& State() const { return mState; } + VideoIOState& MutableState() { return mState; } double FrameBudgetMilliseconds() const; - VideoPlayoutRecoveryDecision AccountForCompletionResult(VideoIOCompletionResult completionResult, uint64_t readyQueueDepth) override; - bool BeginOutputFrame(VideoIOOutputFrame& frame) override; - void EndOutputFrame(VideoIOOutputFrame& frame) override; - bool ScheduleOutputFrame(const VideoIOOutputFrame& frame) override; - void HandleVideoInputFrame(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource); + VideoPlayoutRecoveryDecision AccountForCompletionResult(VideoIOCompletionResult completionResult, uint64_t readyQueueDepth); + bool BeginOutputFrame(VideoIOOutputFrame& frame); + void EndOutputFrame(VideoIOOutputFrame& frame); + bool ScheduleOutputFrame(const VideoIOOutputFrame& frame); void HandlePlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completionResult); private: @@ -83,9 +82,7 @@ private: void RefreshBufferedVideoFrameCount(); static VideoIOCompletionResult TranslateCompletionResult(BMDOutputFrameCompletionResult completionResult); - CComPtr captureDelegate; CComPtr playoutDelegate; - CComPtr input; CComPtr output; CComPtr keyer; std::deque> outputVideoFrameQueue; @@ -97,6 +94,5 @@ private: bool mScheduleRealignmentPending = false; bool mScheduleRealignmentArmed = true; bool mProactiveScheduleRealignmentArmed = true; - InputFrameCallback mInputFrameCallback; OutputFrameCallback mOutputFrameCallback; }; diff --git a/src/video/legacy/VideoBackend.cpp b/src/video/legacy/VideoBackend.cpp deleted file mode 100644 index af03541..0000000 --- a/src/video/legacy/VideoBackend.cpp +++ /dev/null @@ -1,1095 +0,0 @@ -#include "VideoBackend.h" - -#include "DeckLinkSession.h" -#include "OpenGLVideoIOBridge.h" -#include "HealthTelemetry.h" -#include "RenderEngine.h" -#include "RuntimeEventDispatcher.h" - -#include -#include -#include -#include -#include -#include - -VideoBackend::VideoBackend(RenderEngine& renderEngine, HealthTelemetry& healthTelemetry, RuntimeEventDispatcher& runtimeEventDispatcher) : - mHealthTelemetry(healthTelemetry), - mRuntimeEventDispatcher(runtimeEventDispatcher), - mPlayoutPolicy(NormalizeVideoPlayoutPolicy(VideoPlayoutPolicy())), - mOutputProductionController(mPlayoutPolicy), - mReadyOutputQueue(mPlayoutPolicy), - mVideoIODevice(std::make_unique()), - mBridge(std::make_unique(renderEngine)), - mInputCaptureDisabled(IsEnvironmentFlagEnabled("VST_DISABLE_INPUT_CAPTURE")) -{ -} - -VideoBackend::~VideoBackend() -{ - ReleaseResources(); -} - -void VideoBackend::ReleaseResources() -{ - StopOutputCompletionWorker(); - mReadyOutputQueue.Clear(); - if (mVideoIODevice) - mVideoIODevice->ReleaseResources(); - mSystemOutputFramePool.Clear(); - 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) -{ - 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) -{ - 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) -{ - if (mLifecycle.State() != VideoBackendLifecycleState::Configuring) - ApplyLifecycleTransition(VideoBackendLifecycleState::Configuring, "Configuring video backend input."); - if (mInputCaptureDisabled) - { - MutableState().hasInputSource = false; - MutableState().statusMessage = "DeckLink input capture disabled by VST_DISABLE_INPUT_CAPTURE for output timing isolation."; - return true; - } - if (!mVideoIODevice->ConfigureInput( - [this](const VideoIOFrame& frame) { HandleInputFrame(frame); }, - inputVideoMode, - 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) -{ - mPlayoutPolicy = NormalizeVideoPlayoutPolicy(mPlayoutPolicy); - mOutputProductionController.Configure(mPlayoutPolicy); - mReadyOutputQueue.Configure(mPlayoutPolicy); - if (mLifecycle.State() != VideoBackendLifecycleState::Configuring) - ApplyLifecycleTransition(VideoBackendLifecycleState::Configuring, "Configuring video backend output."); - if (!mVideoIODevice->ConfigureOutput( - [this](const VideoIOCompletion& completion) { HandleOutputFrameCompletion(completion); }, - outputVideoMode, - externalKeyingEnabled, - error)) - { - ApplyLifecycleFailure(error.empty() ? "Video backend output configuration failed." : error); - return false; - } - SystemOutputFramePoolConfig poolConfig; - poolConfig.width = mVideoIODevice->OutputFrameWidth(); - poolConfig.height = mVideoIODevice->OutputFrameHeight(); - poolConfig.pixelFormat = mVideoIODevice->OutputPixelFormat(); - poolConfig.rowBytes = mVideoIODevice->OutputFrameRowBytes(); - poolConfig.capacity = mPlayoutPolicy.outputFramePoolSize; - mSystemOutputFramePool.Configure(poolConfig); - RecordSystemMemoryPlayoutStats(); - return ApplyLifecycleTransition(VideoBackendLifecycleState::Configured, "Video backend configured."); -} - -bool VideoBackend::Start() -{ - ApplyLifecycleTransition(VideoBackendLifecycleState::Prerolling, "Video backend preroll starting."); - if (!mVideoIODevice->PrepareOutputSchedule()) - { - ApplyLifecycleFailure(StatusMessage().empty() ? "Video backend output schedule preparation failed." : StatusMessage()); - return false; - } - - StartOutputCompletionWorker(); - StartOutputProducerWorker(); - - if (!WarmupOutputPreroll()) - { - StopOutputProducerWorker(); - StopOutputCompletionWorker(); - ApplyLifecycleFailure(StatusMessage().empty() ? "Video backend preroll warmup failed." : StatusMessage()); - return false; - } - - if (!mInputCaptureDisabled && !mVideoIODevice->StartInputStreams()) - { - StopOutputProducerWorker(); - StopOutputCompletionWorker(); - ApplyLifecycleFailure(StatusMessage().empty() ? "Video backend input stream start failed." : StatusMessage()); - return false; - } - - if (!mVideoIODevice->StartScheduledPlayback()) - { - StopOutputProducerWorker(); - mVideoIODevice->Stop(); - StopOutputCompletionWorker(); - ApplyLifecycleFailure(StatusMessage().empty() ? "Video backend scheduled playback start failed." : StatusMessage()); - return false; - } - - ApplyLifecycleTransition(VideoBackendLifecycleState::Running, "Video backend started."); - return true; -} - -bool VideoBackend::Stop() -{ - ApplyLifecycleTransition(VideoBackendLifecycleState::Stopping, "Video backend stopping."); - StopOutputProducerWorker(); - const bool stopped = mVideoIODevice->Stop(); - StopOutputCompletionWorker(); - if (stopped) - ApplyLifecycleTransition(VideoBackendLifecycleState::Stopped, "Video backend stopped."); - else - ApplyLifecycleFailure(StatusMessage().empty() ? "Video backend stop failed." : StatusMessage()); - return stopped; -} - -const VideoIOState& VideoBackend::State() const -{ - return mVideoIODevice->State(); -} - -VideoIOState& VideoBackend::MutableState() -{ - return mVideoIODevice->MutableState(); -} - -bool VideoBackend::BeginOutputFrame(VideoIOOutputFrame& frame) -{ - return mVideoIODevice->BeginOutputFrame(frame); -} - -void VideoBackend::EndOutputFrame(VideoIOOutputFrame& frame) -{ - mVideoIODevice->EndOutputFrame(frame); -} - -bool VideoBackend::ScheduleOutputFrame(const VideoIOOutputFrame& frame) -{ - return mVideoIODevice->ScheduleOutputFrame(frame); -} - -VideoPlayoutRecoveryDecision VideoBackend::AccountForCompletionResult(VideoIOCompletionResult result, uint64_t readyQueueDepth) -{ - return mVideoIODevice->AccountForCompletionResult(result, readyQueueDepth); -} - -bool VideoBackend::HasInputDevice() const -{ - return mVideoIODevice->HasInputDevice(); -} - -bool VideoBackend::HasInputSource() const -{ - if (mInputCaptureDisabled) - return false; - return mVideoIODevice->HasInputSource(); -} - -unsigned VideoBackend::InputFrameWidth() const -{ - return mVideoIODevice->InputFrameWidth(); -} - -unsigned VideoBackend::InputFrameHeight() const -{ - return mVideoIODevice->InputFrameHeight(); -} - -unsigned VideoBackend::OutputFrameWidth() const -{ - return mVideoIODevice->OutputFrameWidth(); -} - -unsigned VideoBackend::OutputFrameHeight() const -{ - return mVideoIODevice->OutputFrameHeight(); -} - -unsigned VideoBackend::CaptureTextureWidth() const -{ - return mVideoIODevice->CaptureTextureWidth(); -} - -unsigned VideoBackend::OutputPackTextureWidth() const -{ - return mVideoIODevice->OutputPackTextureWidth(); -} - -VideoIOPixelFormat VideoBackend::InputPixelFormat() const -{ - return mVideoIODevice->InputPixelFormat(); -} - -const std::string& VideoBackend::InputDisplayModeName() const -{ - return mVideoIODevice->InputDisplayModeName(); -} - -const std::string& VideoBackend::OutputModelName() const -{ - return mVideoIODevice->OutputModelName(); -} - -bool VideoBackend::SupportsInternalKeying() const -{ - return mVideoIODevice->SupportsInternalKeying(); -} - -bool VideoBackend::SupportsExternalKeying() const -{ - return mVideoIODevice->SupportsExternalKeying(); -} - -bool VideoBackend::KeyerInterfaceAvailable() const -{ - return mVideoIODevice->KeyerInterfaceAvailable(); -} - -bool VideoBackend::ExternalKeyingActive() const -{ - return mVideoIODevice->ExternalKeyingActive(); -} - -const std::string& VideoBackend::StatusMessage() const -{ - return mVideoIODevice->StatusMessage(); -} - -bool VideoBackend::ShouldPrioritizeOutputOverPreview() const -{ - const RenderOutputQueueMetrics metrics = mReadyOutputQueue.GetMetrics(); - return metrics.depth < static_cast(mPlayoutPolicy.targetReadyFrames); -} - -void VideoBackend::SetStatusMessage(const std::string& message) -{ - mVideoIODevice->SetStatusMessage(message); -} - -void VideoBackend::PublishStatus(bool externalKeyingConfigured, const std::string& statusMessage) -{ - if (!statusMessage.empty()) - SetStatusMessage(statusMessage); - - mHealthTelemetry.ReportVideoIOStatus( - "decklink", - OutputModelName(), - SupportsInternalKeying(), - SupportsExternalKeying(), - KeyerInterfaceAvailable(), - externalKeyingConfigured, - ExternalKeyingActive(), - StatusMessage()); - PublishBackendStateChanged(VideoBackendLifecycle::StateName(mLifecycle.State()), StatusMessage()); -} - -void VideoBackend::ReportNoInputDeviceSignalStatus() -{ - mHealthTelemetry.ReportSignalStatus( - false, - InputFrameWidth(), - InputFrameHeight(), - InputDisplayModeName()); - PublishBackendStateChanged("no-input-device", "No input device is available."); -} - -void VideoBackend::HandleInputFrame(const VideoIOFrame& frame) -{ - if (mInputCaptureDisabled) - return; - - const VideoIOState& state = mVideoIODevice->State(); - mHealthTelemetry.TryReportSignalStatus(!frame.hasNoInputSource, state.inputFrameSize.width, state.inputFrameSize.height, state.inputDisplayModeName); - PublishInputSignalChanged(frame, state); - PublishInputFrameArrived(frame); - - if (mBridge) - mBridge->UploadInputFrame(frame, state); -} - -void VideoBackend::HandleOutputFrameCompletion(const VideoIOCompletion& completion) -{ - { - std::lock_guard lock(mOutputCompletionMutex); - if (!mOutputCompletionWorkerRunning || mOutputCompletionWorkerStopping) - return; - mPendingOutputCompletions.push_back(completion); - } - mOutputCompletionCondition.notify_one(); -} - -void VideoBackend::StartOutputCompletionWorker() -{ - { - std::lock_guard lock(mOutputCompletionMutex); - if (mOutputCompletionWorkerRunning) - return; - - mPendingOutputCompletions.clear(); - mReadyOutputQueue.Clear(); - mNextReadyOutputFrameIndex = 0; - mHasReadyQueueDepthBaseline = false; - mMinReadyQueueDepth = 0; - mMaxReadyQueueDepth = 0; - mReadyQueueZeroDepthCount = 0; - mOutputRenderMilliseconds = 0.0; - mSmoothedOutputRenderMilliseconds = 0.0; - mMaxOutputRenderMilliseconds = 0.0; - mOutputFrameAcquireMilliseconds = 0.0; - mOutputFrameRenderRequestMilliseconds = 0.0; - mOutputFrameEndAccessMilliseconds = 0.0; - mLastLateStreak = 0; - mLastDropStreak = 0; - mOutputCompletionWorkerStopping = false; - mOutputCompletionWorkerRunning = true; - mOutputCompletionWorker = std::thread(&VideoBackend::OutputCompletionWorkerMain, this); - } -} - -void VideoBackend::StopOutputCompletionWorker() -{ - StopOutputProducerWorker(); - - bool shouldJoin = false; - { - std::lock_guard lock(mOutputCompletionMutex); - if (mOutputCompletionWorkerRunning) - mOutputCompletionWorkerStopping = true; - shouldJoin = mOutputCompletionWorker.joinable(); - } - mOutputCompletionCondition.notify_one(); - - if (shouldJoin) - mOutputCompletionWorker.join(); -} - -void VideoBackend::StartOutputProducerWorker() -{ - std::lock_guard lock(mOutputProducerMutex); - if (mOutputProducerWorkerRunning) - return; - - const double frameBudgetMilliseconds = State().frameBudgetMilliseconds; - const auto frameDuration = frameBudgetMilliseconds > 0.0 - ? std::chrono::duration_cast( - std::chrono::duration(frameBudgetMilliseconds)) - : std::chrono::milliseconds(16); - mRenderCadenceController.Configure(frameDuration, std::chrono::steady_clock::now()); - mLastOutputProductionCompletion = VideoIOCompletion(); - mLastOutputProductionTime = std::chrono::steady_clock::time_point(); - mOutputProducerWorkerStopping = false; - mOutputProducerWorkerRunning = true; - mOutputProducerWorker = std::thread(&VideoBackend::OutputProducerWorkerMain, this); - mOutputProducerCondition.notify_one(); -} - -void VideoBackend::StopOutputProducerWorker() -{ - bool shouldJoin = false; - { - std::lock_guard lock(mOutputProducerMutex); - if (mOutputProducerWorkerRunning) - mOutputProducerWorkerStopping = true; - shouldJoin = mOutputProducerWorker.joinable(); - } - mOutputProducerCondition.notify_one(); - - if (shouldJoin) - mOutputProducerWorker.join(); -} - -void VideoBackend::NotifyOutputProducer() -{ - mOutputProducerCondition.notify_one(); -} - -bool VideoBackend::WarmupOutputPreroll() -{ - const VideoPlayoutPolicy policy = NormalizeVideoPlayoutPolicy(mPlayoutPolicy); - const std::size_t targetPrerollFrames = static_cast(policy.targetPrerollFrames); - if (targetPrerollFrames == 0) - return true; - - const double frameBudgetMilliseconds = State().frameBudgetMilliseconds > 0.0 ? State().frameBudgetMilliseconds : 16.0; - const auto estimatedCadenceTime = std::chrono::duration_cast( - std::chrono::duration(frameBudgetMilliseconds * static_cast(targetPrerollFrames + 2))); - const auto timeout = (std::max)(std::chrono::milliseconds(1000), estimatedCadenceTime + std::chrono::milliseconds(500)); - const auto deadline = std::chrono::steady_clock::now() + timeout; - - while (std::chrono::steady_clock::now() < deadline) - { - ScheduleReadyOutputFramesToTarget(); - const SystemOutputFramePoolMetrics metrics = mSystemOutputFramePool.GetMetrics(); - RecordSystemMemoryPlayoutStats(); - if (metrics.scheduledCount >= targetPrerollFrames) - return true; - - NotifyOutputProducer(); - const auto waitDuration = (std::min)(OutputProducerWakeInterval(), std::chrono::milliseconds(5)); - std::unique_lock lock(mOutputProducerMutex); - mOutputProducerCondition.wait_for(lock, waitDuration); - if (mOutputProducerWorkerStopping) - return false; - } - - SetStatusMessage("Timed out warming up DeckLink preroll from rendered system-memory frames."); - return false; -} - -void VideoBackend::OutputCompletionWorkerMain() -{ - for (;;) - { - VideoIOCompletion completion; - { - std::unique_lock lock(mOutputCompletionMutex); - mOutputCompletionCondition.wait(lock, [this]() { - return mOutputCompletionWorkerStopping || !mPendingOutputCompletions.empty(); - }); - - if (mPendingOutputCompletions.empty()) - { - if (mOutputCompletionWorkerStopping) - { - mOutputCompletionWorkerRunning = false; - return; - } - continue; - } - - completion = mPendingOutputCompletions.front(); - mPendingOutputCompletions.pop_front(); - } - - ProcessOutputFrameCompletion(completion); - } -} - -void VideoBackend::OutputProducerWorkerMain() -{ - for (;;) - { - { - std::lock_guard lock(mOutputProducerMutex); - if (mOutputProducerWorkerStopping) - { - mOutputProducerWorkerRunning = false; - return; - } - } - - ScheduleReadyOutputFramesToTarget(); - - const RenderOutputQueueMetrics metrics = mReadyOutputQueue.GetMetrics(); - RecordReadyQueueDepthSample(metrics); - - const auto now = std::chrono::steady_clock::now(); - RenderCadenceDecision cadenceDecision = mRenderCadenceController.Tick(now); - if (cadenceDecision.action == RenderCadenceAction::Wait) - { - const auto waitDuration = (std::min)( - std::chrono::duration_cast(cadenceDecision.waitDuration), - OutputProducerWakeInterval()); - std::unique_lock lock(mOutputProducerMutex); - mOutputProducerCondition.wait_for(lock, waitDuration); - if (mOutputProducerWorkerStopping) - { - mOutputProducerWorkerRunning = false; - return; - } - continue; - } - - VideoIOCompletion completion; - { - std::lock_guard lock(mOutputProducerMutex); - if (mOutputProducerWorkerStopping) - continue; - completion = mLastOutputProductionCompletion; - } - - const std::size_t producedFrames = ProduceReadyOutputFrames(completion, 1); - if (producedFrames > 0) - { - mLastOutputProductionTime = std::chrono::steady_clock::now(); - ScheduleReadyOutputFramesToTarget(); - continue; - } - - { - std::unique_lock lock(mOutputProducerMutex); - mOutputProducerCondition.wait_for(lock, OutputProducerWakeInterval()); - if (mOutputProducerWorkerStopping) - { - mOutputProducerWorkerRunning = false; - return; - } - } - } -} - -std::chrono::milliseconds VideoBackend::OutputProducerWakeInterval() const -{ - const double frameBudgetMilliseconds = State().frameBudgetMilliseconds; - if (frameBudgetMilliseconds <= 0.0) - return std::chrono::milliseconds(8); - - const int intervalMilliseconds = (std::max)(1, static_cast(std::floor(frameBudgetMilliseconds * 0.75))); - return std::chrono::milliseconds(intervalMilliseconds); -} - -void VideoBackend::ProcessOutputFrameCompletion(const VideoIOCompletion& completion) -{ - if (completion.outputFrameBuffer != nullptr) - mSystemOutputFramePool.ReleaseSlotByBuffer(completion.outputFrameBuffer); - RecordFramePacing(completion.result); - PublishOutputFrameCompleted(completion); - const RenderOutputQueueMetrics initialQueueMetrics = mReadyOutputQueue.GetMetrics(); - RecordReadyQueueDepthSample(initialQueueMetrics); - const VideoPlayoutRecoveryDecision recoveryDecision = AccountForCompletionResult(completion.result, initialQueueMetrics.depth); - { - std::lock_guard lock(mOutputMetricsMutex); - mLastLateStreak = recoveryDecision.lateStreak; - mLastDropStreak = recoveryDecision.dropStreak; - } - { - std::lock_guard lock(mOutputProducerMutex); - mLastOutputProductionCompletion = completion; - } - NotifyOutputProducer(); - - RecordBackendPlayoutHealth(completion.result, recoveryDecision); - RecordSystemMemoryPlayoutStats(); -} - -std::size_t VideoBackend::ScheduleReadyOutputFramesToTarget() -{ - const std::size_t targetScheduledFrames = static_cast(mPlayoutPolicy.targetPrerollFrames); - std::size_t scheduledFrames = 0; - for (;;) - { - const SystemOutputFramePoolMetrics poolMetrics = mSystemOutputFramePool.GetMetrics(); - if (poolMetrics.scheduledCount >= targetScheduledFrames) - break; - if (!ScheduleReadyOutputFrame()) - break; - ++scheduledFrames; - } - return scheduledFrames; -} - -void VideoBackend::RecordBackendPlayoutHealth(VideoIOCompletionResult result, const VideoPlayoutRecoveryDecision& recoveryDecision) -{ - const RenderOutputQueueMetrics queueMetrics = mReadyOutputQueue.GetMetrics(); - std::size_t minReadyQueueDepth = 0; - std::size_t maxReadyQueueDepth = 0; - uint64_t readyQueueZeroDepthCount = 0; - double outputRenderMilliseconds = 0.0; - double smoothedOutputRenderMilliseconds = 0.0; - double maxOutputRenderMilliseconds = 0.0; - double outputFrameAcquireMilliseconds = 0.0; - double outputFrameRenderRequestMilliseconds = 0.0; - double outputFrameEndAccessMilliseconds = 0.0; - { - std::lock_guard lock(mOutputMetricsMutex); - minReadyQueueDepth = mMinReadyQueueDepth; - maxReadyQueueDepth = mMaxReadyQueueDepth; - readyQueueZeroDepthCount = mReadyQueueZeroDepthCount; - outputRenderMilliseconds = mOutputRenderMilliseconds; - smoothedOutputRenderMilliseconds = mSmoothedOutputRenderMilliseconds; - maxOutputRenderMilliseconds = mMaxOutputRenderMilliseconds; - outputFrameAcquireMilliseconds = mOutputFrameAcquireMilliseconds; - outputFrameRenderRequestMilliseconds = mOutputFrameRenderRequestMilliseconds; - outputFrameEndAccessMilliseconds = mOutputFrameEndAccessMilliseconds; - } - - mHealthTelemetry.TryRecordBackendPlayoutHealth( - VideoBackendLifecycle::StateName(mLifecycle.State()), - CompletionResultName(result), - queueMetrics.depth, - queueMetrics.capacity, - queueMetrics.pushedCount, - minReadyQueueDepth, - maxReadyQueueDepth, - readyQueueZeroDepthCount, - queueMetrics.poppedCount, - queueMetrics.droppedCount, - queueMetrics.underrunCount, - outputRenderMilliseconds, - smoothedOutputRenderMilliseconds, - maxOutputRenderMilliseconds, - outputFrameAcquireMilliseconds, - outputFrameRenderRequestMilliseconds, - outputFrameEndAccessMilliseconds, - recoveryDecision.completedFrameIndex, - recoveryDecision.scheduledFrameIndex, - recoveryDecision.scheduledLeadFrames, - recoveryDecision.measuredLagFrames, - recoveryDecision.catchUpFrames, - recoveryDecision.lateStreak, - recoveryDecision.dropStreak, - mLateFrameCount, - mDroppedFrameCount, - mFlushedFrameCount, - mLifecycle.State() == VideoBackendLifecycleState::Degraded, - StatusMessage()); -} - -std::size_t VideoBackend::ProduceReadyOutputFrames(const VideoIOCompletion& completion, std::size_t maxFrames) -{ - if (maxFrames == 0) - return 0; - - std::lock_guard productionLock(mOutputProductionMutex); - RenderOutputQueueMetrics metrics = mReadyOutputQueue.GetMetrics(); - std::size_t producedFrames = 0; - while (producedFrames < maxFrames) - { - if (!RenderReadyOutputFrame(mVideoIODevice->State(), completion)) - break; - ++producedFrames; - metrics = mReadyOutputQueue.GetMetrics(); - RecordReadyQueueDepthSample(metrics); - } - return producedFrames; -} - -OutputProductionPressure VideoBackend::BuildOutputProductionPressure(const RenderOutputQueueMetrics& metrics) const -{ - OutputProductionPressure pressure; - pressure.readyQueueDepth = metrics.depth; - pressure.readyQueueCapacity = metrics.capacity; - pressure.readyQueueUnderrunCount = metrics.underrunCount; - { - std::lock_guard lock(mOutputMetricsMutex); - pressure.lateStreak = mLastLateStreak; - pressure.dropStreak = mLastDropStreak; - } - return pressure; -} - -bool VideoBackend::RenderReadyOutputFrame(const VideoIOState& state, const VideoIOCompletion& completion) -{ - const auto renderStart = std::chrono::steady_clock::now(); - OutputFrameSlot outputSlot; - VideoIOOutputFrame outputFrame; - const auto acquireStart = std::chrono::steady_clock::now(); - if (!mSystemOutputFramePool.AcquireFreeSlot(outputSlot)) - { - if (!mReadyOutputQueue.DropOldestFrame() || !mSystemOutputFramePool.AcquireFreeSlot(outputSlot)) - return false; - } - outputFrame = outputSlot.frame; - const auto acquireEnd = std::chrono::steady_clock::now(); - - bool rendered = true; - const auto renderRequestStart = std::chrono::steady_clock::now(); - if (mBridge) - rendered = mBridge->RenderScheduledFrame(state, completion, outputFrame); - const auto renderRequestEnd = std::chrono::steady_clock::now(); - - const auto endAccessStart = std::chrono::steady_clock::now(); - const bool publishedReady = mSystemOutputFramePool.PublishReadySlot(outputSlot); - const auto endAccessEnd = std::chrono::steady_clock::now(); - const double acquireMilliseconds = std::chrono::duration_cast>(acquireEnd - acquireStart).count(); - const double renderRequestMilliseconds = std::chrono::duration_cast>(renderRequestEnd - renderRequestStart).count(); - const double endAccessMilliseconds = std::chrono::duration_cast>(endAccessEnd - endAccessStart).count(); - - if (!rendered) - { - mSystemOutputFramePool.ReleaseSlot(outputSlot); - ApplyLifecycleTransition(VideoBackendLifecycleState::Degraded, "Output frame render request failed; skipping schedule for this frame."); - const double renderMilliseconds = std::chrono::duration_cast>( - std::chrono::steady_clock::now() - renderStart).count(); - RecordOutputRenderDuration(renderMilliseconds, acquireMilliseconds, renderRequestMilliseconds, endAccessMilliseconds); - return false; - } - - if (!publishedReady) - { - mSystemOutputFramePool.ReleaseSlot(outputSlot); - return false; - } - - const double renderMilliseconds = std::chrono::duration_cast>( - std::chrono::steady_clock::now() - renderStart).count(); - RecordOutputRenderDuration(renderMilliseconds, acquireMilliseconds, renderRequestMilliseconds, endAccessMilliseconds); - - RenderOutputFrame readyFrame; - readyFrame.frame = outputFrame; - readyFrame.frameIndex = ++mNextReadyOutputFrameIndex; - readyFrame.releaseFrame = [this](VideoIOOutputFrame& frame) { - mSystemOutputFramePool.ReleaseSlotByBuffer(frame.bytes); - }; - const bool pushed = mReadyOutputQueue.Push(readyFrame); - if (!pushed) - mSystemOutputFramePool.ReleaseSlot(outputSlot); - RecordSystemMemoryPlayoutStats(); - return pushed; -} - -bool VideoBackend::ScheduleReadyOutputFrame() -{ - std::lock_guard schedulingLock(mOutputSchedulingMutex); - RenderOutputFrame readyFrame; - if (!mReadyOutputQueue.TryPop(readyFrame)) - return false; - RecordReadyQueueDepthSample(mReadyOutputQueue.GetMetrics()); - - if (!mSystemOutputFramePool.MarkScheduledByBuffer(readyFrame.frame.bytes)) - { - if (readyFrame.releaseFrame) - readyFrame.releaseFrame(readyFrame.frame); - return false; - } - - if (!ScheduleOutputFrame(readyFrame.frame)) - { - RecordDeckLinkBufferTelemetry(); - mSystemOutputFramePool.ReleaseSlotByBuffer(readyFrame.frame.bytes); - return false; - } - - RecordDeckLinkBufferTelemetry(); - PublishOutputFrameScheduled(readyFrame.frame); - RecordSystemMemoryPlayoutStats(); - return true; -} - -bool VideoBackend::ScheduleBlackUnderrunFrame() -{ - VideoIOOutputFrame outputFrame; - if (!BeginOutputFrame(outputFrame)) - { - ApplyLifecycleTransition(VideoBackendLifecycleState::Degraded, "Output underrun: no output frame was available for fallback scheduling."); - return false; - } - - if (outputFrame.bytes != nullptr && outputFrame.rowBytes > 0 && outputFrame.height > 0) - std::memset(outputFrame.bytes, 0, static_cast(outputFrame.rowBytes) * outputFrame.height); - EndOutputFrame(outputFrame); - - if (!ScheduleOutputFrame(outputFrame)) - { - RecordDeckLinkBufferTelemetry(); - ApplyLifecycleTransition(VideoBackendLifecycleState::Degraded, "Output underrun: black fallback frame scheduling failed."); - return false; - } - - RecordDeckLinkBufferTelemetry(); - ApplyLifecycleTransition(VideoBackendLifecycleState::Degraded, "Output underrun: scheduled black fallback frame."); - PublishOutputFrameScheduled(outputFrame); - return true; -} - -void VideoBackend::RecordFramePacing(VideoIOCompletionResult completionResult) -{ - const auto now = std::chrono::steady_clock::now(); - if (mLastPlayoutCompletionTime != std::chrono::steady_clock::time_point()) - { - mCompletionIntervalMilliseconds = std::chrono::duration_cast>(now - mLastPlayoutCompletionTime).count(); - if (mSmoothedCompletionIntervalMilliseconds <= 0.0) - mSmoothedCompletionIntervalMilliseconds = mCompletionIntervalMilliseconds; - else - mSmoothedCompletionIntervalMilliseconds = mSmoothedCompletionIntervalMilliseconds * 0.9 + mCompletionIntervalMilliseconds * 0.1; - if (mCompletionIntervalMilliseconds > mMaxCompletionIntervalMilliseconds) - mMaxCompletionIntervalMilliseconds = mCompletionIntervalMilliseconds; - } - mLastPlayoutCompletionTime = now; - - if (completionResult == VideoIOCompletionResult::DisplayedLate) - ++mLateFrameCount; - else if (completionResult == VideoIOCompletionResult::Dropped) - ++mDroppedFrameCount; - else if (completionResult == VideoIOCompletionResult::Flushed) - ++mFlushedFrameCount; - - mHealthTelemetry.TryRecordFramePacingStats( - mCompletionIntervalMilliseconds, - mSmoothedCompletionIntervalMilliseconds, - mMaxCompletionIntervalMilliseconds, - mLateFrameCount, - mDroppedFrameCount, - mFlushedFrameCount); - PublishTimingSample("VideoBackend", "completionInterval", mCompletionIntervalMilliseconds, "ms"); - PublishTimingSample("VideoBackend", "smoothedCompletionInterval", mSmoothedCompletionIntervalMilliseconds, "ms"); -} - -void VideoBackend::RecordReadyQueueDepthSample(const RenderOutputQueueMetrics& metrics) -{ - std::lock_guard lock(mOutputMetricsMutex); - if (!mHasReadyQueueDepthBaseline) - { - mHasReadyQueueDepthBaseline = true; - mMinReadyQueueDepth = metrics.depth; - mMaxReadyQueueDepth = metrics.depth; - } - else - { - mMinReadyQueueDepth = (std::min)(mMinReadyQueueDepth, metrics.depth); - mMaxReadyQueueDepth = (std::max)(mMaxReadyQueueDepth, metrics.depth); - } - - if (metrics.depth == 0) - ++mReadyQueueZeroDepthCount; -} - -void VideoBackend::RecordDeckLinkBufferTelemetry() -{ - if (!mVideoIODevice) - return; - - const VideoIOState& state = mVideoIODevice->State(); - mHealthTelemetry.TryRecordDeckLinkBufferTelemetry( - state.actualDeckLinkBufferedFramesAvailable, - state.actualDeckLinkBufferedFrames, - static_cast(mPlayoutPolicy.targetPrerollFrames), - state.deckLinkScheduleCallMilliseconds, - state.deckLinkScheduleFailureCount); -} - -void VideoBackend::RecordSystemMemoryPlayoutStats() -{ - const SystemOutputFramePoolMetrics poolMetrics = mSystemOutputFramePool.GetMetrics(); - const RenderOutputQueueMetrics queueMetrics = mReadyOutputQueue.GetMetrics(); - RecordDeckLinkBufferTelemetry(); - mHealthTelemetry.TryRecordSystemMemoryPlayoutStats( - poolMetrics.freeCount, - poolMetrics.readyCount, - poolMetrics.scheduledCount, - poolMetrics.readyUnderrunCount, - 0, - queueMetrics.droppedCount, - 0.0, - 0.0); -} - -void VideoBackend::RecordOutputRenderDuration(double renderMilliseconds, double acquireMilliseconds, double renderRequestMilliseconds, double endAccessMilliseconds) -{ - std::lock_guard lock(mOutputMetricsMutex); - mOutputRenderMilliseconds = (std::max)(renderMilliseconds, 0.0); - if (mSmoothedOutputRenderMilliseconds <= 0.0) - mSmoothedOutputRenderMilliseconds = mOutputRenderMilliseconds; - else - mSmoothedOutputRenderMilliseconds = mSmoothedOutputRenderMilliseconds * 0.9 + mOutputRenderMilliseconds * 0.1; - mMaxOutputRenderMilliseconds = (std::max)(mMaxOutputRenderMilliseconds, mOutputRenderMilliseconds); - mOutputFrameAcquireMilliseconds = (std::max)(acquireMilliseconds, 0.0); - mOutputFrameRenderRequestMilliseconds = (std::max)(renderRequestMilliseconds, 0.0); - mOutputFrameEndAccessMilliseconds = (std::max)(endAccessMilliseconds, 0.0); - - PublishTimingSample("VideoBackend", "outputRender", mOutputRenderMilliseconds, "ms"); - PublishTimingSample("VideoBackend", "smoothedOutputRender", mSmoothedOutputRenderMilliseconds, "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) -{ - try - { - BackendStateChangedEvent event; - event.backendName = "decklink"; - event.state = state; - event.message = message; - if (!mRuntimeEventDispatcher.PublishPayload(event, "VideoBackend")) - OutputDebugStringA("BackendStateChanged event publish failed.\n"); - } - catch (...) - { - OutputDebugStringA("BackendStateChanged event publish threw.\n"); - } -} - -void VideoBackend::PublishInputSignalChanged(const VideoIOFrame& frame, const VideoIOState& state) -{ - const bool hasSignal = !frame.hasNoInputSource; - const unsigned width = state.inputFrameSize.width; - const unsigned height = state.inputFrameSize.height; - if (mHasLastInputSignal && - mLastInputSignal == hasSignal && - mLastInputSignalWidth == width && - mLastInputSignalHeight == height && - mLastInputSignalModeName == state.inputDisplayModeName) - { - return; - } - - mHasLastInputSignal = true; - mLastInputSignal = hasSignal; - mLastInputSignalWidth = width; - mLastInputSignalHeight = height; - mLastInputSignalModeName = state.inputDisplayModeName; - - try - { - InputSignalChangedEvent event; - event.hasSignal = hasSignal; - event.width = width; - event.height = height; - event.modeName = state.inputDisplayModeName; - if (!mRuntimeEventDispatcher.PublishPayload(event, "VideoBackend")) - OutputDebugStringA("InputSignalChanged event publish failed.\n"); - } - catch (...) - { - OutputDebugStringA("InputSignalChanged event publish threw.\n"); - } -} - -void VideoBackend::PublishInputFrameArrived(const VideoIOFrame& frame) -{ - try - { - InputFrameArrivedEvent event; - event.frameIndex = ++mInputFrameIndex; - event.width = frame.width; - event.height = frame.height; - event.rowBytes = frame.rowBytes; - event.pixelFormat = PixelFormatName(frame.pixelFormat); - event.hasNoInputSource = frame.hasNoInputSource; - if (!mRuntimeEventDispatcher.PublishPayload(event, "VideoBackend")) - OutputDebugStringA("InputFrameArrived event publish failed.\n"); - } - catch (...) - { - OutputDebugStringA("InputFrameArrived event publish threw.\n"); - } -} - -void VideoBackend::PublishOutputFrameScheduled(const VideoIOOutputFrame& frame) -{ - try - { - OutputFrameScheduledEvent event; - event.frameIndex = ++mOutputFrameScheduleIndex; - (void)frame; - if (!mRuntimeEventDispatcher.PublishPayload(event, "VideoBackend")) - OutputDebugStringA("OutputFrameScheduled event publish failed.\n"); - } - catch (...) - { - OutputDebugStringA("OutputFrameScheduled event publish threw.\n"); - } -} - -void VideoBackend::PublishOutputFrameCompleted(const VideoIOCompletion& completion) -{ - try - { - OutputFrameCompletedEvent event; - event.frameIndex = ++mOutputFrameCompletionIndex; - event.result = CompletionResultName(completion.result); - if (!mRuntimeEventDispatcher.PublishPayload(event, "VideoBackend")) - OutputDebugStringA("OutputFrameCompleted event publish failed.\n"); - } - catch (...) - { - OutputDebugStringA("OutputFrameCompleted event publish threw.\n"); - } -} - -void VideoBackend::PublishTimingSample(const std::string& subsystem, const std::string& metric, double value, const std::string& unit) -{ - try - { - TimingSampleRecordedEvent event; - event.subsystem = subsystem; - event.metric = metric; - event.value = value; - event.unit = unit; - if (!mRuntimeEventDispatcher.PublishPayload(event, "HealthTelemetry")) - OutputDebugStringA("TimingSampleRecorded event publish failed.\n"); - } - catch (...) - { - OutputDebugStringA("TimingSampleRecorded event publish threw.\n"); - } -} - -std::string VideoBackend::CompletionResultName(VideoIOCompletionResult result) -{ - switch (result) - { - case VideoIOCompletionResult::Completed: - return "Completed"; - case VideoIOCompletionResult::DisplayedLate: - return "DisplayedLate"; - case VideoIOCompletionResult::Dropped: - return "Dropped"; - case VideoIOCompletionResult::Flushed: - return "Flushed"; - case VideoIOCompletionResult::Unknown: - default: - return "Unknown"; - } -} - -std::string VideoBackend::PixelFormatName(VideoIOPixelFormat pixelFormat) -{ - return std::string(VideoIOPixelFormatName(pixelFormat)); -} - -bool VideoBackend::IsEnvironmentFlagEnabled(const char* name) -{ - if (name == nullptr || name[0] == '\0') - return false; - - char* value = nullptr; - std::size_t valueSize = 0; - if (_dupenv_s(&value, &valueSize, name) != 0 || value == nullptr) - return false; - - const std::string flag(value); - std::free(value); - return flag == "1" || flag == "true" || flag == "TRUE" || flag == "yes" || flag == "on"; -} diff --git a/src/video/legacy/VideoBackend.h b/src/video/legacy/VideoBackend.h deleted file mode 100644 index 81eed7f..0000000 --- a/src/video/legacy/VideoBackend.h +++ /dev/null @@ -1,161 +0,0 @@ -#pragma once - -#include "OutputProductionController.h" -#include "RenderCadenceController.h" -#include "RenderOutputQueue.h" -#include "SystemOutputFramePool.h" -#include "VideoBackendLifecycle.h" -#include "VideoIOTypes.h" -#include "VideoPlayoutPolicy.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -class HealthTelemetry; -class OpenGLVideoIOBridge; -class RenderEngine; -class RuntimeEventDispatcher; -class VideoIODevice; - -class VideoBackend -{ -public: - VideoBackend(RenderEngine& renderEngine, HealthTelemetry& healthTelemetry, RuntimeEventDispatcher& runtimeEventDispatcher); - ~VideoBackend(); - - void ReleaseResources(); - VideoBackendLifecycleState LifecycleState() const; - bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error); - bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error); - bool ConfigureInput(const VideoFormat& inputVideoMode, std::string& error); - bool ConfigureOutput(const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error); - bool Start(); - bool Stop(); - - const VideoIOState& State() const; - VideoIOState& MutableState(); - bool BeginOutputFrame(VideoIOOutputFrame& frame); - void EndOutputFrame(VideoIOOutputFrame& frame); - bool ScheduleOutputFrame(const VideoIOOutputFrame& frame); - VideoPlayoutRecoveryDecision AccountForCompletionResult(VideoIOCompletionResult result, uint64_t readyQueueDepth); - void RecordBackendPlayoutHealth(VideoIOCompletionResult result, const VideoPlayoutRecoveryDecision& recoveryDecision); - - bool HasInputDevice() const; - bool HasInputSource() const; - unsigned InputFrameWidth() const; - unsigned InputFrameHeight() const; - unsigned OutputFrameWidth() const; - unsigned OutputFrameHeight() const; - unsigned CaptureTextureWidth() const; - unsigned OutputPackTextureWidth() const; - VideoIOPixelFormat InputPixelFormat() const; - const std::string& InputDisplayModeName() const; - const std::string& OutputModelName() const; - bool SupportsInternalKeying() const; - bool SupportsExternalKeying() const; - bool KeyerInterfaceAvailable() const; - bool ExternalKeyingActive() const; - const std::string& StatusMessage() const; - bool ShouldPrioritizeOutputOverPreview() const; - void SetStatusMessage(const std::string& message); - void PublishStatus(bool externalKeyingConfigured, const std::string& statusMessage = std::string()); - void ReportNoInputDeviceSignalStatus(); - -private: - void HandleInputFrame(const VideoIOFrame& frame); - void HandleOutputFrameCompletion(const VideoIOCompletion& completion); - void StartOutputCompletionWorker(); - void StopOutputCompletionWorker(); - void OutputCompletionWorkerMain(); - void StartOutputProducerWorker(); - void StopOutputProducerWorker(); - void OutputProducerWorkerMain(); - void NotifyOutputProducer(); - bool WarmupOutputPreroll(); - std::chrono::milliseconds OutputProducerWakeInterval() const; - void ProcessOutputFrameCompletion(const VideoIOCompletion& completion); - std::size_t ProduceReadyOutputFrames(const VideoIOCompletion& completion, std::size_t maxFrames); - OutputProductionPressure BuildOutputProductionPressure(const RenderOutputQueueMetrics& metrics) const; - bool RenderReadyOutputFrame(const VideoIOState& state, const VideoIOCompletion& completion); - std::size_t ScheduleReadyOutputFramesToTarget(); - bool ScheduleReadyOutputFrame(); - bool ScheduleBlackUnderrunFrame(); - void RecordFramePacing(VideoIOCompletionResult completionResult); - void RecordReadyQueueDepthSample(const RenderOutputQueueMetrics& metrics); - void RecordDeckLinkBufferTelemetry(); - void RecordSystemMemoryPlayoutStats(); - void RecordOutputRenderDuration(double renderMilliseconds, double acquireMilliseconds, double renderRequestMilliseconds, double endAccessMilliseconds); - 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 PublishInputSignalChanged(const VideoIOFrame& frame, const VideoIOState& state); - void PublishInputFrameArrived(const VideoIOFrame& frame); - void PublishOutputFrameScheduled(const VideoIOOutputFrame& frame); - void PublishOutputFrameCompleted(const VideoIOCompletion& completion); - void PublishTimingSample(const std::string& subsystem, const std::string& metric, double value, const std::string& unit); - static std::string CompletionResultName(VideoIOCompletionResult result); - static std::string PixelFormatName(VideoIOPixelFormat pixelFormat); - static bool IsEnvironmentFlagEnabled(const char* name); - - HealthTelemetry& mHealthTelemetry; - RuntimeEventDispatcher& mRuntimeEventDispatcher; - VideoBackendLifecycle mLifecycle; - VideoPlayoutPolicy mPlayoutPolicy; - OutputProductionController mOutputProductionController; - RenderCadenceController mRenderCadenceController; - RenderOutputQueue mReadyOutputQueue; - SystemOutputFramePool mSystemOutputFramePool; - std::unique_ptr mVideoIODevice; - std::unique_ptr mBridge; - std::mutex mOutputCompletionMutex; - std::condition_variable mOutputCompletionCondition; - std::deque mPendingOutputCompletions; - std::thread mOutputCompletionWorker; - std::mutex mOutputProducerMutex; - std::condition_variable mOutputProducerCondition; - std::thread mOutputProducerWorker; - VideoIOCompletion mLastOutputProductionCompletion; - std::chrono::steady_clock::time_point mLastOutputProductionTime; - std::mutex mOutputProductionMutex; - std::mutex mOutputSchedulingMutex; - mutable std::mutex mOutputMetricsMutex; - bool mOutputCompletionWorkerRunning = false; - bool mOutputCompletionWorkerStopping = false; - bool mOutputProducerWorkerRunning = false; - bool mOutputProducerWorkerStopping = false; - bool mInputCaptureDisabled = false; - uint64_t mNextReadyOutputFrameIndex = 0; - uint64_t mInputFrameIndex = 0; - uint64_t mOutputFrameScheduleIndex = 0; - uint64_t mOutputFrameCompletionIndex = 0; - bool mHasLastInputSignal = false; - bool mLastInputSignal = false; - unsigned mLastInputSignalWidth = 0; - unsigned mLastInputSignalHeight = 0; - std::string mLastInputSignalModeName; - std::chrono::steady_clock::time_point mLastPlayoutCompletionTime; - double mCompletionIntervalMilliseconds = 0.0; - double mSmoothedCompletionIntervalMilliseconds = 0.0; - double mMaxCompletionIntervalMilliseconds = 0.0; - bool mHasReadyQueueDepthBaseline = false; - std::size_t mMinReadyQueueDepth = 0; - std::size_t mMaxReadyQueueDepth = 0; - uint64_t mReadyQueueZeroDepthCount = 0; - double mOutputRenderMilliseconds = 0.0; - double mSmoothedOutputRenderMilliseconds = 0.0; - double mMaxOutputRenderMilliseconds = 0.0; - double mOutputFrameAcquireMilliseconds = 0.0; - double mOutputFrameRenderRequestMilliseconds = 0.0; - double mOutputFrameEndAccessMilliseconds = 0.0; - uint64_t mLastLateStreak = 0; - uint64_t mLastDropStreak = 0; - uint64_t mLateFrameCount = 0; - uint64_t mDroppedFrameCount = 0; - uint64_t mFlushedFrameCount = 0; -}; diff --git a/src/video/legacy/VideoBackendLifecycle.cpp b/src/video/legacy/VideoBackendLifecycle.cpp deleted file mode 100644 index 5bfd763..0000000 --- a/src/video/legacy/VideoBackendLifecycle.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#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"; - } -} diff --git a/src/video/legacy/VideoBackendLifecycle.h b/src/video/legacy/VideoBackendLifecycle.h deleted file mode 100644 index bc9a5d6..0000000 --- a/src/video/legacy/VideoBackendLifecycle.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include - -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; -}; diff --git a/src/video/playout/OutputProductionController.cpp b/src/video/playout/OutputProductionController.cpp deleted file mode 100644 index 64e4148..0000000 --- a/src/video/playout/OutputProductionController.cpp +++ /dev/null @@ -1,89 +0,0 @@ -#include "OutputProductionController.h" - -#include - -namespace -{ -std::size_t ClampReadyLimit(unsigned value, std::size_t capacity) -{ - const std::size_t requested = static_cast(value); - if (capacity == 0) - return requested; - return (std::min)(requested, capacity); -} -} - -OutputProductionController::OutputProductionController(const VideoPlayoutPolicy& policy) : - mPolicy(NormalizeVideoPlayoutPolicy(policy)) -{ -} - -void OutputProductionController::Configure(const VideoPlayoutPolicy& policy) -{ - mPolicy = NormalizeVideoPlayoutPolicy(policy); -} - -OutputProductionDecision OutputProductionController::Decide(const OutputProductionPressure& pressure) const -{ - OutputProductionDecision decision; - - const std::size_t configuredMaxReadyFrames = static_cast(mPolicy.maxReadyFrames); - const std::size_t effectiveMaxReadyFrames = pressure.readyQueueCapacity > 0 - ? (std::min)(configuredMaxReadyFrames, pressure.readyQueueCapacity) - : configuredMaxReadyFrames; - const std::size_t effectiveTargetReadyFrames = (std::min)( - ClampReadyLimit(mPolicy.targetReadyFrames, pressure.readyQueueCapacity), - effectiveMaxReadyFrames); - - decision.targetReadyFrames = effectiveTargetReadyFrames; - decision.maxReadyFrames = effectiveMaxReadyFrames; - - if (effectiveMaxReadyFrames == 0) - { - decision.action = OutputProductionAction::Throttle; - decision.reason = "no-ready-frame-capacity"; - return decision; - } - - if (pressure.readyQueueDepth >= effectiveMaxReadyFrames) - { - decision.action = OutputProductionAction::Throttle; - decision.reason = "ready-queue-full"; - return decision; - } - - if (pressure.readyQueueDepth < effectiveTargetReadyFrames) - { - decision.action = OutputProductionAction::Produce; - decision.requestedFrames = effectiveTargetReadyFrames - pressure.readyQueueDepth; - decision.reason = "ready-queue-below-target"; - return decision; - } - - if ((pressure.lateStreak > 0 || pressure.dropStreak > 0 || pressure.readyQueueUnderrunCount > 0) && - pressure.readyQueueDepth < effectiveMaxReadyFrames) - { - decision.action = OutputProductionAction::Produce; - decision.requestedFrames = 1; - decision.reason = "playout-pressure"; - return decision; - } - - decision.action = OutputProductionAction::Wait; - decision.reason = "ready-queue-at-target"; - return decision; -} - -const char* OutputProductionActionName(OutputProductionAction action) -{ - switch (action) - { - case OutputProductionAction::Produce: - return "Produce"; - case OutputProductionAction::Throttle: - return "Throttle"; - case OutputProductionAction::Wait: - default: - return "Wait"; - } -} diff --git a/src/video/playout/OutputProductionController.h b/src/video/playout/OutputProductionController.h deleted file mode 100644 index 13a016c..0000000 --- a/src/video/playout/OutputProductionController.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include "VideoPlayoutPolicy.h" - -#include -#include -#include - -enum class OutputProductionAction -{ - Produce, - Wait, - Throttle -}; - -struct OutputProductionPressure -{ - std::size_t readyQueueDepth = 0; - std::size_t readyQueueCapacity = 0; - uint64_t readyQueueUnderrunCount = 0; - uint64_t lateStreak = 0; - uint64_t dropStreak = 0; -}; - -struct OutputProductionDecision -{ - OutputProductionAction action = OutputProductionAction::Wait; - std::size_t requestedFrames = 0; - std::size_t targetReadyFrames = 0; - std::size_t maxReadyFrames = 0; - std::string reason; -}; - -class OutputProductionController -{ -public: - explicit OutputProductionController(const VideoPlayoutPolicy& policy = VideoPlayoutPolicy()); - - void Configure(const VideoPlayoutPolicy& policy); - OutputProductionDecision Decide(const OutputProductionPressure& pressure) const; - -private: - VideoPlayoutPolicy mPolicy; -}; - -const char* OutputProductionActionName(OutputProductionAction action); diff --git a/src/video/playout/RenderCadenceController.cpp b/src/video/playout/RenderCadenceController.cpp deleted file mode 100644 index 7981a36..0000000 --- a/src/video/playout/RenderCadenceController.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include "RenderCadenceController.h" - -#include -#include - -void RenderCadenceController::Configure(Duration targetFrameDuration, TimePoint firstRenderTime, const RenderCadencePolicy& policy) -{ - mTargetFrameDuration = IsPositive(targetFrameDuration) ? targetFrameDuration : std::chrono::milliseconds(1); - mPolicy = policy; - if (mPolicy.skipThresholdFrames < 1.0) - mPolicy.skipThresholdFrames = 1.0; - Reset(firstRenderTime); -} - -void RenderCadenceController::Reset(TimePoint firstRenderTime) -{ - mNextRenderTime = firstRenderTime; - mNextFrameIndex = 0; - mMetrics = RenderCadenceMetrics(); -} - -RenderCadenceDecision RenderCadenceController::Tick(TimePoint now) -{ - RenderCadenceDecision decision; - decision.frameIndex = mNextFrameIndex; - decision.renderTargetTime = mNextRenderTime; - decision.nextRenderTime = mNextRenderTime; - - if (now < mNextRenderTime) - { - decision.action = RenderCadenceAction::Wait; - decision.waitDuration = mNextRenderTime - now; - decision.reason = "waiting-for-next-render-tick"; - return decision; - } - - const Duration lateness = now - mNextRenderTime; - const uint64_t skippedTicks = SkippedTicksForLateness(lateness); - if (skippedTicks > 0) - { - decision.skippedTicks = skippedTicks; - decision.frameIndex = mNextFrameIndex + skippedTicks; - decision.renderTargetTime = mNextRenderTime + (mTargetFrameDuration * skippedTicks); - decision.reason = "late-skip-render-ticks"; - mMetrics.skippedTickCount += skippedTicks; - } - else - { - decision.reason = IsPositive(lateness) ? "late-render-now" : "on-time-render"; - } - - decision.action = RenderCadenceAction::Render; - decision.lateness = now > decision.renderTargetTime - ? now - decision.renderTargetTime - : Duration::zero(); - mNextFrameIndex = decision.frameIndex + 1; - mNextRenderTime = decision.renderTargetTime + mTargetFrameDuration; - decision.nextRenderTime = mNextRenderTime; - - ++mMetrics.renderedFrameCount; - mMetrics.nextFrameIndex = mNextFrameIndex; - mMetrics.lastLateness = decision.lateness; - if (IsPositive(decision.lateness)) - { - ++mMetrics.lateFrameCount; - mMetrics.maxLateness = (std::max)(mMetrics.maxLateness, decision.lateness); - } - - return decision; -} - -uint64_t RenderCadenceController::SkippedTicksForLateness(Duration lateness) const -{ - if (!mPolicy.skipLateTicks || !IsPositive(lateness) || !IsPositive(mTargetFrameDuration)) - return 0; - - const double lateFrames = static_cast(lateness.count()) / static_cast(mTargetFrameDuration.count()); - if (lateFrames < mPolicy.skipThresholdFrames) - return 0; - - const uint64_t elapsedTicks = static_cast(std::floor(lateFrames)); - if (elapsedTicks == 0) - return 0; - return (std::min)(elapsedTicks, mPolicy.maxSkippedTicksPerDecision); -} - -bool RenderCadenceController::IsPositive(Duration duration) -{ - return duration > Duration::zero(); -} - -const char* RenderCadenceActionName(RenderCadenceAction action) -{ - switch (action) - { - case RenderCadenceAction::Render: - return "Render"; - case RenderCadenceAction::Wait: - default: - return "Wait"; - } -} diff --git a/src/video/playout/RenderCadenceController.h b/src/video/playout/RenderCadenceController.h deleted file mode 100644 index 8eba0ef..0000000 --- a/src/video/playout/RenderCadenceController.h +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -#include -#include - -enum class RenderCadenceAction -{ - Wait, - Render -}; - -struct RenderCadencePolicy -{ - bool skipLateTicks = true; - uint64_t maxSkippedTicksPerDecision = 4; - double skipThresholdFrames = 2.0; -}; - -struct RenderCadenceDecision -{ - RenderCadenceAction action = RenderCadenceAction::Wait; - uint64_t frameIndex = 0; - uint64_t skippedTicks = 0; - std::chrono::steady_clock::time_point renderTargetTime; - std::chrono::steady_clock::time_point nextRenderTime; - std::chrono::steady_clock::duration waitDuration = std::chrono::steady_clock::duration::zero(); - std::chrono::steady_clock::duration lateness = std::chrono::steady_clock::duration::zero(); - const char* reason = "waiting-for-next-render-tick"; -}; - -struct RenderCadenceMetrics -{ - uint64_t nextFrameIndex = 0; - uint64_t renderedFrameCount = 0; - uint64_t skippedTickCount = 0; - uint64_t lateFrameCount = 0; - std::chrono::steady_clock::duration lastLateness = std::chrono::steady_clock::duration::zero(); - std::chrono::steady_clock::duration maxLateness = std::chrono::steady_clock::duration::zero(); -}; - -class RenderCadenceController -{ -public: - using Clock = std::chrono::steady_clock; - using TimePoint = Clock::time_point; - using Duration = Clock::duration; - - void Configure(Duration targetFrameDuration, TimePoint firstRenderTime, const RenderCadencePolicy& policy = RenderCadencePolicy()); - void Reset(TimePoint firstRenderTime); - RenderCadenceDecision Tick(TimePoint now); - - Duration TargetFrameDuration() const { return mTargetFrameDuration; } - TimePoint NextRenderTime() const { return mNextRenderTime; } - uint64_t NextFrameIndex() const { return mNextFrameIndex; } - const RenderCadenceMetrics& Metrics() const { return mMetrics; } - -private: - uint64_t SkippedTicksForLateness(Duration lateness) const; - static bool IsPositive(Duration duration); - - Duration mTargetFrameDuration = std::chrono::milliseconds(16); - TimePoint mNextRenderTime; - uint64_t mNextFrameIndex = 0; - RenderCadencePolicy mPolicy; - RenderCadenceMetrics mMetrics; -}; - -const char* RenderCadenceActionName(RenderCadenceAction action); diff --git a/src/video/playout/RenderOutputQueue.cpp b/src/video/playout/RenderOutputQueue.cpp deleted file mode 100644 index c68660b..0000000 --- a/src/video/playout/RenderOutputQueue.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include "RenderOutputQueue.h" - -RenderOutputQueue::RenderOutputQueue(const VideoPlayoutPolicy& policy) : - mPolicy(NormalizeVideoPlayoutPolicy(policy)) -{ -} - -void RenderOutputQueue::Configure(const VideoPlayoutPolicy& policy) -{ - std::lock_guard lock(mMutex); - mPolicy = NormalizeVideoPlayoutPolicy(policy); - while (mReadyFrames.size() > CapacityLocked()) - { - ReleaseFrame(mReadyFrames.front()); - mReadyFrames.pop_front(); - ++mDroppedCount; - } -} - -bool RenderOutputQueue::Push(RenderOutputFrame frame) -{ - std::lock_guard lock(mMutex); - if (mReadyFrames.size() >= CapacityLocked()) - { - ReleaseFrame(mReadyFrames.front()); - mReadyFrames.pop_front(); - ++mDroppedCount; - } - - mReadyFrames.push_back(frame); - ++mPushedCount; - return true; -} - -bool RenderOutputQueue::TryPop(RenderOutputFrame& frame) -{ - std::lock_guard lock(mMutex); - if (mReadyFrames.empty()) - { - ++mUnderrunCount; - return false; - } - - frame = mReadyFrames.front(); - mReadyFrames.pop_front(); - ++mPoppedCount; - return true; -} - -bool RenderOutputQueue::DropOldestFrame() -{ - std::lock_guard lock(mMutex); - if (mReadyFrames.empty()) - return false; - - ReleaseFrame(mReadyFrames.front()); - mReadyFrames.pop_front(); - ++mDroppedCount; - return true; -} - -void RenderOutputQueue::Clear() -{ - std::lock_guard lock(mMutex); - for (RenderOutputFrame& frame : mReadyFrames) - ReleaseFrame(frame); - mReadyFrames.clear(); -} - -RenderOutputQueueMetrics RenderOutputQueue::GetMetrics() const -{ - std::lock_guard 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(mPolicy.maxReadyFrames); -} - -void RenderOutputQueue::ReleaseFrame(RenderOutputFrame& frame) -{ - if (frame.releaseFrame) - frame.releaseFrame(frame.frame); - frame.releaseFrame = {}; -} diff --git a/src/video/playout/RenderOutputQueue.h b/src/video/playout/RenderOutputQueue.h deleted file mode 100644 index 0a9109a..0000000 --- a/src/video/playout/RenderOutputQueue.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include "VideoIOTypes.h" -#include "VideoPlayoutPolicy.h" - -#include -#include -#include -#include - -struct RenderOutputFrame -{ - VideoIOOutputFrame frame; - uint64_t frameIndex = 0; - bool stale = false; - std::function releaseFrame; -}; - -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); - bool DropOldestFrame(); - void Clear(); - RenderOutputQueueMetrics GetMetrics() const; - -private: - std::size_t CapacityLocked() const; - static void ReleaseFrame(RenderOutputFrame& frame); - - mutable std::mutex mMutex; - VideoPlayoutPolicy mPolicy; - std::deque mReadyFrames; - uint64_t mPushedCount = 0; - uint64_t mPoppedCount = 0; - uint64_t mDroppedCount = 0; - uint64_t mUnderrunCount = 0; -}; diff --git a/src/video/playout/SystemOutputFramePool.cpp b/src/video/playout/SystemOutputFramePool.cpp deleted file mode 100644 index 8318d28..0000000 --- a/src/video/playout/SystemOutputFramePool.cpp +++ /dev/null @@ -1,260 +0,0 @@ -#include "SystemOutputFramePool.h" - -#include - -namespace -{ -SystemOutputFramePoolConfig NormalizeConfig(SystemOutputFramePoolConfig config) -{ - if (config.rowBytes == 0) - config.rowBytes = VideoIORowBytes(config.pixelFormat, config.width); - return config; -} -} - -SystemOutputFramePool::SystemOutputFramePool(const SystemOutputFramePoolConfig& config) -{ - Configure(config); -} - -void SystemOutputFramePool::Configure(const SystemOutputFramePoolConfig& config) -{ - std::lock_guard lock(mMutex); - mConfig = NormalizeConfig(config); - mReadySlots.clear(); - mSlots.clear(); - mSlots.resize(mConfig.capacity); - - const std::size_t byteCount = FrameByteCount(); - for (StoredSlot& slot : mSlots) - { - slot.bytes.resize(byteCount); - slot.state = OutputFrameSlotState::Free; - ++slot.generation; - } - - mAcquireMissCount = 0; - mReadyUnderrunCount = 0; -} - -SystemOutputFramePoolConfig SystemOutputFramePool::Config() const -{ - std::lock_guard lock(mMutex); - return mConfig; -} - -bool SystemOutputFramePool::AcquireFreeSlot(OutputFrameSlot& slot) -{ - std::lock_guard lock(mMutex); - for (std::size_t index = 0; index < mSlots.size(); ++index) - { - if (mSlots[index].state != OutputFrameSlotState::Free) - continue; - - mSlots[index].state = OutputFrameSlotState::Rendering; - ++mSlots[index].generation; - FillOutputSlotLocked(index, slot); - return true; - } - - slot = OutputFrameSlot(); - ++mAcquireMissCount; - return false; -} - -bool SystemOutputFramePool::AcquireRenderingSlot(OutputFrameSlot& slot) -{ - return AcquireFreeSlot(slot); -} - -bool SystemOutputFramePool::PublishReadySlot(const OutputFrameSlot& slot) -{ - std::lock_guard lock(mMutex); - if (!TransitionSlotLocked(slot, OutputFrameSlotState::Rendering, OutputFrameSlotState::Completed)) - return false; - - mReadySlots.push_back(slot.index); - return true; -} - -bool SystemOutputFramePool::PublishCompletedSlot(const OutputFrameSlot& slot) -{ - return PublishReadySlot(slot); -} - -bool SystemOutputFramePool::ConsumeReadySlot(OutputFrameSlot& slot) -{ - std::lock_guard lock(mMutex); - while (!mReadySlots.empty()) - { - const std::size_t index = mReadySlots.front(); - mReadySlots.pop_front(); - if (index >= mSlots.size() || mSlots[index].state != OutputFrameSlotState::Completed) - continue; - - FillOutputSlotLocked(index, slot); - return true; - } - - slot = OutputFrameSlot(); - ++mReadyUnderrunCount; - return false; -} - -bool SystemOutputFramePool::ConsumeCompletedSlot(OutputFrameSlot& slot) -{ - return ConsumeReadySlot(slot); -} - -bool SystemOutputFramePool::MarkScheduled(const OutputFrameSlot& slot) -{ - std::lock_guard lock(mMutex); - if (!IsValidSlotLocked(slot)) - return false; - if (mSlots[slot.index].state != OutputFrameSlotState::Completed) - return false; - - RemoveReadyIndexLocked(slot.index); - mSlots[slot.index].state = OutputFrameSlotState::Scheduled; - return true; -} - -bool SystemOutputFramePool::MarkScheduledByBuffer(void* bytes) -{ - if (bytes == nullptr) - return false; - - std::lock_guard lock(mMutex); - for (std::size_t index = 0; index < mSlots.size(); ++index) - { - if (mSlots[index].bytes.empty() || mSlots[index].bytes.data() != bytes) - continue; - if (mSlots[index].state != OutputFrameSlotState::Completed) - return false; - - RemoveReadyIndexLocked(index); - mSlots[index].state = OutputFrameSlotState::Scheduled; - return true; - } - return false; -} - -bool SystemOutputFramePool::ReleaseSlot(const OutputFrameSlot& slot) -{ - std::lock_guard lock(mMutex); - if (!IsValidSlotLocked(slot) || mSlots[slot.index].state == OutputFrameSlotState::Free) - return false; - - return ReleaseSlotByIndexLocked(slot.index); -} - -bool SystemOutputFramePool::ReleaseScheduledSlot(const OutputFrameSlot& slot) -{ - std::lock_guard lock(mMutex); - return TransitionSlotLocked(slot, OutputFrameSlotState::Scheduled, OutputFrameSlotState::Free); -} - -bool SystemOutputFramePool::ReleaseSlotByBuffer(void* bytes) -{ - if (bytes == nullptr) - return false; - - std::lock_guard lock(mMutex); - for (std::size_t index = 0; index < mSlots.size(); ++index) - { - if (!mSlots[index].bytes.empty() && mSlots[index].bytes.data() == bytes) - return ReleaseSlotByIndexLocked(index); - } - return false; -} - -void SystemOutputFramePool::Clear() -{ - std::lock_guard lock(mMutex); - mReadySlots.clear(); - for (StoredSlot& slot : mSlots) - { - slot.state = OutputFrameSlotState::Free; - ++slot.generation; - } -} - -SystemOutputFramePoolMetrics SystemOutputFramePool::GetMetrics() const -{ - std::lock_guard lock(mMutex); - SystemOutputFramePoolMetrics metrics; - metrics.capacity = mSlots.size(); - metrics.readyCount = mReadySlots.size(); - metrics.acquireMissCount = mAcquireMissCount; - metrics.readyUnderrunCount = mReadyUnderrunCount; - - for (const StoredSlot& slot : mSlots) - { - switch (slot.state) - { - case OutputFrameSlotState::Free: - ++metrics.freeCount; - break; - case OutputFrameSlotState::Rendering: - ++metrics.renderingCount; - ++metrics.acquiredCount; - break; - case OutputFrameSlotState::Completed: - ++metrics.completedCount; - break; - case OutputFrameSlotState::Scheduled: - ++metrics.scheduledCount; - break; - } - } - - return metrics; -} - -bool SystemOutputFramePool::IsValidSlotLocked(const OutputFrameSlot& slot) const -{ - return slot.index < mSlots.size() && mSlots[slot.index].generation == slot.generation; -} - -bool SystemOutputFramePool::TransitionSlotLocked(const OutputFrameSlot& slot, OutputFrameSlotState expectedState, OutputFrameSlotState nextState) -{ - if (!IsValidSlotLocked(slot) || mSlots[slot.index].state != expectedState) - return false; - - mSlots[slot.index].state = nextState; - return true; -} - -void SystemOutputFramePool::FillOutputSlotLocked(std::size_t index, OutputFrameSlot& slot) -{ - StoredSlot& storedSlot = mSlots[index]; - slot.index = index; - slot.generation = storedSlot.generation; - slot.frame.bytes = storedSlot.bytes.empty() ? nullptr : storedSlot.bytes.data(); - slot.frame.rowBytes = static_cast(mConfig.rowBytes); - slot.frame.width = mConfig.width; - slot.frame.height = mConfig.height; - slot.frame.pixelFormat = mConfig.pixelFormat; - slot.frame.nativeFrame = nullptr; - slot.frame.nativeBuffer = slot.frame.bytes; -} - -void SystemOutputFramePool::RemoveReadyIndexLocked(std::size_t index) -{ - mReadySlots.erase(std::remove(mReadySlots.begin(), mReadySlots.end(), index), mReadySlots.end()); -} - -bool SystemOutputFramePool::ReleaseSlotByIndexLocked(std::size_t index) -{ - if (index >= mSlots.size() || mSlots[index].state == OutputFrameSlotState::Free) - return false; - - RemoveReadyIndexLocked(index); - mSlots[index].state = OutputFrameSlotState::Free; - return true; -} - -std::size_t SystemOutputFramePool::FrameByteCount() const -{ - return static_cast(mConfig.rowBytes) * static_cast(mConfig.height); -} diff --git a/src/video/playout/SystemOutputFramePool.h b/src/video/playout/SystemOutputFramePool.h deleted file mode 100644 index f0ec666..0000000 --- a/src/video/playout/SystemOutputFramePool.h +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once - -#include "VideoIOTypes.h" - -#include -#include -#include -#include -#include - -enum class OutputFrameSlotState -{ - Free, - Rendering, - Completed, - Scheduled -}; - -struct SystemOutputFramePoolConfig -{ - unsigned width = 0; - unsigned height = 0; - VideoIOPixelFormat pixelFormat = VideoIOPixelFormat::Bgra8; - unsigned rowBytes = 0; - std::size_t capacity = 0; -}; - -struct OutputFrameSlot -{ - VideoIOOutputFrame frame; - std::size_t index = 0; - uint64_t generation = 0; -}; - -struct SystemOutputFramePoolMetrics -{ - std::size_t capacity = 0; - std::size_t freeCount = 0; - std::size_t renderingCount = 0; - std::size_t completedCount = 0; - std::size_t scheduledCount = 0; - std::size_t acquiredCount = 0; - std::size_t readyCount = 0; - std::size_t consumedCount = 0; - uint64_t acquireMissCount = 0; - uint64_t readyUnderrunCount = 0; -}; - -class SystemOutputFramePool -{ -public: - SystemOutputFramePool() = default; - explicit SystemOutputFramePool(const SystemOutputFramePoolConfig& config); - - void Configure(const SystemOutputFramePoolConfig& config); - SystemOutputFramePoolConfig Config() const; - - bool AcquireFreeSlot(OutputFrameSlot& slot); - bool AcquireRenderingSlot(OutputFrameSlot& slot); - bool PublishReadySlot(const OutputFrameSlot& slot); - bool PublishCompletedSlot(const OutputFrameSlot& slot); - bool ConsumeReadySlot(OutputFrameSlot& slot); - bool ConsumeCompletedSlot(OutputFrameSlot& slot); - bool MarkScheduled(const OutputFrameSlot& slot); - bool MarkScheduledByBuffer(void* bytes); - bool ReleaseSlot(const OutputFrameSlot& slot); - bool ReleaseScheduledSlot(const OutputFrameSlot& slot); - bool ReleaseSlotByBuffer(void* bytes); - void Clear(); - - SystemOutputFramePoolMetrics GetMetrics() const; - -private: - struct StoredSlot - { - std::vector bytes; - OutputFrameSlotState state = OutputFrameSlotState::Free; - uint64_t generation = 1; - }; - - bool IsValidSlotLocked(const OutputFrameSlot& slot) const; - bool TransitionSlotLocked(const OutputFrameSlot& slot, OutputFrameSlotState expectedState, OutputFrameSlotState nextState); - void FillOutputSlotLocked(std::size_t index, OutputFrameSlot& slot); - void RemoveReadyIndexLocked(std::size_t index); - bool ReleaseSlotByIndexLocked(std::size_t index); - std::size_t FrameByteCount() const; - - mutable std::mutex mMutex; - SystemOutputFramePoolConfig mConfig; - std::vector mSlots; - std::deque mReadySlots; - uint64_t mAcquireMissCount = 0; - uint64_t mReadyUnderrunCount = 0; -}; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 12cf7ba..133379f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -146,37 +146,6 @@ add_video_shader_test(VideoOutputThreadTests "${TEST_DIR}/VideoOutputThreadTests.cpp" ) -add_video_shader_test(OutputProductionControllerTests - "${SRC_DIR}/video/playout/OutputProductionController.cpp" - "${TEST_DIR}/OutputProductionControllerTests.cpp" -) - -add_video_shader_test(RenderOutputQueueTests - "${SRC_DIR}/video/playout/RenderOutputQueue.cpp" - "${TEST_DIR}/RenderOutputQueueTests.cpp" -) - -add_video_shader_test(RenderCadenceControllerTests - "${SRC_DIR}/video/playout/RenderCadenceController.cpp" - "${TEST_DIR}/RenderCadenceControllerTests.cpp" -) - -add_video_shader_test(SystemOutputFramePoolTests - "${SRC_DIR}/video/playout/SystemOutputFramePool.cpp" - ${VIDEO_FORMAT_SOURCES} - "${TEST_DIR}/SystemOutputFramePoolTests.cpp" -) - -add_video_shader_test(VideoBackendLifecycleTests - "${SRC_DIR}/video/legacy/VideoBackendLifecycle.cpp" - "${TEST_DIR}/VideoBackendLifecycleTests.cpp" -) - -add_video_shader_test(VideoIODeviceFakeTests - ${VIDEO_FORMAT_SOURCES} - "${TEST_DIR}/VideoIODeviceFakeTests.cpp" -) - set_tests_properties(RenderCadenceCompositorLoggerTests PROPERTIES ENVIRONMENT "VIDEO_SHADER_TEST_LOG_TO_CONSOLE=0" ) diff --git a/tests/OutputProductionControllerTests.cpp b/tests/OutputProductionControllerTests.cpp deleted file mode 100644 index 3a06c4d..0000000 --- a/tests/OutputProductionControllerTests.cpp +++ /dev/null @@ -1,142 +0,0 @@ -#include "OutputProductionController.h" - -#include -#include - -namespace -{ -int gFailures = 0; - -void Expect(bool condition, const char* message) -{ - if (condition) - return; - - std::cerr << "FAIL: " << message << "\n"; - ++gFailures; -} - -void TestLowQueueRequestsProductionToTarget() -{ - VideoPlayoutPolicy policy; - policy.targetReadyFrames = 3; - policy.maxReadyFrames = 5; - OutputProductionController controller(policy); - - OutputProductionPressure pressure; - pressure.readyQueueDepth = 1; - pressure.readyQueueCapacity = 5; - - const OutputProductionDecision decision = controller.Decide(pressure); - Expect(decision.action == OutputProductionAction::Produce, "low ready queue requests production"); - Expect(decision.requestedFrames == 2, "low ready queue requests enough frames to reach target"); - Expect(decision.targetReadyFrames == 3, "decision reports effective target"); - Expect(decision.maxReadyFrames == 5, "decision reports effective max"); - Expect(decision.reason == "ready-queue-below-target", "low queue decision names reason"); -} - -void TestFullQueueThrottles() -{ - VideoPlayoutPolicy policy; - policy.targetReadyFrames = 2; - policy.maxReadyFrames = 4; - OutputProductionController controller(policy); - - OutputProductionPressure pressure; - pressure.readyQueueDepth = 4; - pressure.readyQueueCapacity = 4; - - const OutputProductionDecision decision = controller.Decide(pressure); - Expect(decision.action == OutputProductionAction::Throttle, "full ready queue throttles production"); - Expect(decision.requestedFrames == 0, "full ready queue requests no frames"); - Expect(decision.reason == "ready-queue-full", "full queue decision names reason"); -} - -void TestAtTargetWaitsWithoutPressure() -{ - VideoPlayoutPolicy policy; - policy.targetReadyFrames = 2; - policy.maxReadyFrames = 4; - OutputProductionController controller(policy); - - OutputProductionPressure pressure; - pressure.readyQueueDepth = 2; - pressure.readyQueueCapacity = 4; - - const OutputProductionDecision decision = controller.Decide(pressure); - Expect(decision.action == OutputProductionAction::Wait, "ready queue at target waits without pressure"); - Expect(decision.requestedFrames == 0, "wait decision requests no frames"); - Expect(decision.reason == "ready-queue-at-target", "wait decision names reason"); -} - -void TestLateDropPressureRequestsHeadroom() -{ - VideoPlayoutPolicy policy; - policy.targetReadyFrames = 2; - policy.maxReadyFrames = 4; - OutputProductionController controller(policy); - - OutputProductionPressure pressure; - pressure.readyQueueDepth = 2; - pressure.readyQueueCapacity = 4; - pressure.lateStreak = 1; - - OutputProductionDecision decision = controller.Decide(pressure); - Expect(decision.action == OutputProductionAction::Produce, "late pressure requests extra headroom"); - Expect(decision.requestedFrames == 1, "late pressure requests one frame"); - Expect(decision.reason == "playout-pressure", "late pressure decision names reason"); - - pressure.lateStreak = 0; - pressure.dropStreak = 2; - decision = controller.Decide(pressure); - Expect(decision.action == OutputProductionAction::Produce, "drop pressure requests extra headroom"); - - pressure.dropStreak = 0; - pressure.readyQueueUnderrunCount = 1; - decision = controller.Decide(pressure); - Expect(decision.action == OutputProductionAction::Produce, "underrun pressure requests extra headroom"); -} - -void TestPolicyNormalizesAndClampsToCapacity() -{ - VideoPlayoutPolicy policy; - policy.targetReadyFrames = 0; - policy.maxReadyFrames = 8; - OutputProductionController controller(policy); - - OutputProductionPressure pressure; - pressure.readyQueueDepth = 1; - pressure.readyQueueCapacity = 3; - - const OutputProductionDecision decision = controller.Decide(pressure); - Expect(decision.action == OutputProductionAction::Wait, "normalized target at current depth waits"); - Expect(decision.targetReadyFrames == 1, "target normalizes to at least one frame"); - Expect(decision.maxReadyFrames == 3, "max ready frames clamps to queue capacity"); -} - -void TestActionNames() -{ - Expect(OutputProductionActionName(OutputProductionAction::Produce) == std::string("Produce"), "produce action has name"); - Expect(OutputProductionActionName(OutputProductionAction::Wait) == std::string("Wait"), "wait action has name"); - Expect(OutputProductionActionName(OutputProductionAction::Throttle) == std::string("Throttle"), "throttle action has name"); -} -} - -int main() -{ - TestLowQueueRequestsProductionToTarget(); - TestFullQueueThrottles(); - TestAtTargetWaitsWithoutPressure(); - TestLateDropPressureRequestsHeadroom(); - TestPolicyNormalizesAndClampsToCapacity(); - TestActionNames(); - - if (gFailures != 0) - { - std::cerr << gFailures << " OutputProductionController test failure(s).\n"; - return 1; - } - - std::cout << "OutputProductionController tests passed.\n"; - return 0; -} diff --git a/tests/RenderCadenceControllerTests.cpp b/tests/RenderCadenceControllerTests.cpp deleted file mode 100644 index c619b23..0000000 --- a/tests/RenderCadenceControllerTests.cpp +++ /dev/null @@ -1,172 +0,0 @@ -#include "RenderCadenceController.h" - -#include -#include -#include - -namespace -{ -int gFailures = 0; - -using Clock = RenderCadenceController::Clock; -using Duration = RenderCadenceController::Duration; -using TimePoint = RenderCadenceController::TimePoint; - -void Expect(bool condition, const char* message) -{ - if (condition) - return; - - std::cerr << "FAIL: " << message << "\n"; - ++gFailures; -} - -Duration Ms(int64_t value) -{ - return std::chrono::duration_cast(std::chrono::milliseconds(value)); -} - -void TestExactCadenceAdvancesFrameIndexAndNextTick() -{ - RenderCadenceController controller; - const TimePoint start = Clock::time_point(Ms(1000)); - controller.Configure(Ms(20), start); - - RenderCadenceDecision first = controller.Tick(start); - Expect(first.action == RenderCadenceAction::Render, "first exact tick renders"); - Expect(first.frameIndex == 0, "first exact tick renders frame zero"); - Expect(first.renderTargetTime == start, "first exact target is configured start"); - Expect(first.nextRenderTime == start + Ms(20), "first exact tick advances next render time"); - Expect(first.skippedTicks == 0, "first exact tick skips no ticks"); - Expect(first.lateness == Duration::zero(), "first exact tick records no lateness"); - - RenderCadenceDecision second = controller.Tick(start + Ms(20)); - Expect(second.action == RenderCadenceAction::Render, "second exact tick renders"); - Expect(second.frameIndex == 1, "second exact tick renders frame one"); - Expect(controller.NextFrameIndex() == 2, "controller tracks next frame index after exact ticks"); - Expect(controller.Metrics().renderedFrameCount == 2, "metrics count exact rendered frames"); -} - -void TestEarlyTickWaitsWithoutAdvancing() -{ - RenderCadenceController controller; - const TimePoint start = Clock::time_point(Ms(0)); - controller.Configure(Ms(20), start); - (void)controller.Tick(start); - - RenderCadenceDecision decision = controller.Tick(start + Ms(10)); - Expect(decision.action == RenderCadenceAction::Wait, "early tick waits"); - Expect(decision.waitDuration == Ms(10), "early tick reports wait duration"); - Expect(decision.frameIndex == 1, "early tick reports next pending frame"); - Expect(controller.NextFrameIndex() == 1, "early tick does not advance frame index"); - Expect(controller.NextRenderTime() == start + Ms(20), "early tick does not advance next render time"); -} - -void TestSlightLatenessRendersAndRecordsMetrics() -{ - RenderCadencePolicy policy; - policy.skipThresholdFrames = 3.0; - - RenderCadenceController controller; - const TimePoint start = Clock::time_point(Ms(0)); - controller.Configure(Ms(20), start, policy); - - RenderCadenceDecision decision = controller.Tick(start + Ms(5)); - Expect(decision.action == RenderCadenceAction::Render, "slightly late tick renders"); - Expect(decision.frameIndex == 0, "slightly late tick keeps pending frame"); - Expect(decision.skippedTicks == 0, "slightly late tick skips no ticks"); - Expect(decision.lateness == Ms(5), "slightly late tick reports lateness"); - Expect(controller.Metrics().lateFrameCount == 1, "metrics count late rendered frame"); - Expect(controller.Metrics().lastLateness == Ms(5), "metrics keep last lateness"); - Expect(controller.Metrics().maxLateness == Ms(5), "metrics keep max lateness"); -} - -void TestLargeLatenessSkipsTicksAccordingToPolicy() -{ - RenderCadencePolicy policy; - policy.skipLateTicks = true; - policy.skipThresholdFrames = 2.0; - policy.maxSkippedTicksPerDecision = 8; - - RenderCadenceController controller; - const TimePoint start = Clock::time_point(Ms(0)); - controller.Configure(Ms(20), start, policy); - - RenderCadenceDecision decision = controller.Tick(start + Ms(70)); - Expect(decision.action == RenderCadenceAction::Render, "large late tick renders newest allowed frame"); - Expect(decision.skippedTicks == 3, "large late tick skips elapsed render ticks"); - Expect(decision.frameIndex == 3, "large late tick renders skipped-to frame"); - Expect(decision.renderTargetTime == start + Ms(60), "large late tick targets newest elapsed tick"); - Expect(decision.lateness == Ms(10), "large late tick measures residual lateness"); - Expect(controller.NextFrameIndex() == 4, "large late tick advances past rendered frame"); - Expect(controller.NextRenderTime() == start + Ms(80), "large late tick advances to following cadence"); - Expect(controller.Metrics().skippedTickCount == 3, "metrics count skipped ticks"); -} - -void TestSkipPolicyCanDisableOrCapSkippedTicks() -{ - const TimePoint start = Clock::time_point(Ms(0)); - - RenderCadencePolicy disabledPolicy; - disabledPolicy.skipLateTicks = false; - RenderCadenceController disabledController; - disabledController.Configure(Ms(20), start, disabledPolicy); - RenderCadenceDecision disabled = disabledController.Tick(start + Ms(90)); - Expect(disabled.skippedTicks == 0, "disabled skip policy renders pending frame"); - Expect(disabled.frameIndex == 0, "disabled skip policy preserves pending frame index"); - - RenderCadencePolicy cappedPolicy; - cappedPolicy.skipThresholdFrames = 1.0; - cappedPolicy.maxSkippedTicksPerDecision = 2; - RenderCadenceController cappedController; - cappedController.Configure(Ms(20), start, cappedPolicy); - RenderCadenceDecision capped = cappedController.Tick(start + Ms(90)); - Expect(capped.skippedTicks == 2, "skip policy caps skipped ticks"); - Expect(capped.frameIndex == 2, "capped skip renders capped frame index"); -} - -void TestResetRestartsCadenceAndMetrics() -{ - RenderCadenceController controller; - const TimePoint start = Clock::time_point(Ms(0)); - controller.Configure(Ms(20), start); - (void)controller.Tick(start + Ms(50)); - - const TimePoint restarted = start + Ms(200); - controller.Reset(restarted); - - Expect(controller.NextFrameIndex() == 0, "reset restarts frame index"); - Expect(controller.NextRenderTime() == restarted, "reset restarts next render time"); - Expect(controller.Metrics().renderedFrameCount == 0, "reset clears rendered metrics"); - - RenderCadenceDecision decision = controller.Tick(restarted); - Expect(decision.action == RenderCadenceAction::Render, "reset cadence renders at new start"); - Expect(decision.frameIndex == 0, "reset cadence renders frame zero"); -} - -void TestActionNames() -{ - Expect(RenderCadenceActionName(RenderCadenceAction::Render) == std::string("Render"), "render action has name"); - Expect(RenderCadenceActionName(RenderCadenceAction::Wait) == std::string("Wait"), "wait action has name"); -} -} - -int main() -{ - TestExactCadenceAdvancesFrameIndexAndNextTick(); - TestEarlyTickWaitsWithoutAdvancing(); - TestSlightLatenessRendersAndRecordsMetrics(); - TestLargeLatenessSkipsTicksAccordingToPolicy(); - TestSkipPolicyCanDisableOrCapSkippedTicks(); - TestResetRestartsCadenceAndMetrics(); - TestActionNames(); - - if (gFailures != 0) - { - std::cerr << gFailures << " RenderCadenceController test failure(s).\n"; - return 1; - } - - std::cout << "RenderCadenceController tests passed.\n"; - return 0; -} diff --git a/tests/RenderOutputQueueTests.cpp b/tests/RenderOutputQueueTests.cpp deleted file mode 100644 index 698f647..0000000 --- a/tests/RenderOutputQueueTests.cpp +++ /dev/null @@ -1,209 +0,0 @@ -#include "RenderOutputQueue.h" - -#include - -namespace -{ -int gFailures = 0; -int gReleasedFrames = 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(static_cast(index + 1)); - return frame; -} - -void CountReleasedFrame(VideoIOOutputFrame& frame) -{ - if (frame.nativeFrame != nullptr) - { - ++gReleasedFrames; - frame.nativeFrame = nullptr; - } -} - -RenderOutputFrame MakeOwnedFrame(uint64_t index) -{ - RenderOutputFrame frame = MakeFrame(index); - frame.releaseFrame = CountReleasedFrame; - 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 TestOverflowReleasesDroppedFrame() -{ - gReleasedFrames = 0; - VideoPlayoutPolicy policy; - policy.targetReadyFrames = 1; - policy.maxReadyFrames = 1; - RenderOutputQueue queue(policy); - - queue.Push(MakeOwnedFrame(1)); - queue.Push(MakeOwnedFrame(2)); - - Expect(gReleasedFrames == 1, "overflow releases dropped ready frame"); - - RenderOutputFrame frame; - Expect(queue.TryPop(frame), "newest owned frame remains queued"); - Expect(frame.frameIndex == 2, "overflow keeps newest owned frame"); - Expect(gReleasedFrames == 1, "pop transfers ownership without releasing"); -} - -void TestDropOldestFrameReleasesFrame() -{ - gReleasedFrames = 0; - VideoPlayoutPolicy policy; - policy.maxReadyFrames = 2; - RenderOutputQueue queue(policy); - - queue.Push(MakeOwnedFrame(1)); - queue.Push(MakeOwnedFrame(2)); - - Expect(queue.DropOldestFrame(), "oldest ready frame can be explicitly dropped"); - Expect(gReleasedFrames == 1, "explicit drop releases oldest frame"); - - RenderOutputQueueMetrics metrics = queue.GetMetrics(); - Expect(metrics.depth == 1, "explicit drop reduces queue depth"); - Expect(metrics.droppedCount == 1, "explicit drop increments dropped count"); - - RenderOutputFrame frame; - Expect(queue.TryPop(frame), "newest frame remains after explicit drop"); - Expect(frame.frameIndex == 2, "explicit drop keeps newest frame"); - Expect(!queue.DropOldestFrame(), "empty queue cannot drop a frame"); -} - -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"); -} - -void TestConfigureReleasesTrimmedFrames() -{ - gReleasedFrames = 0; - VideoPlayoutPolicy policy; - policy.maxReadyFrames = 3; - RenderOutputQueue queue(policy); - queue.Push(MakeOwnedFrame(1)); - queue.Push(MakeOwnedFrame(2)); - queue.Push(MakeOwnedFrame(3)); - - VideoPlayoutPolicy smallerPolicy; - smallerPolicy.targetReadyFrames = 1; - smallerPolicy.maxReadyFrames = 1; - queue.Configure(smallerPolicy); - - Expect(gReleasedFrames == 2, "configure releases trimmed ready frames"); - - RenderOutputFrame frame; - Expect(queue.TryPop(frame), "trimmed owned queue still has newest frame"); - Expect(frame.frameIndex == 3, "configure keeps newest owned frame after release"); -} - -void TestClearReleasesQueuedFrames() -{ - gReleasedFrames = 0; - RenderOutputQueue queue; - queue.Push(MakeOwnedFrame(1)); - queue.Push(MakeOwnedFrame(2)); - - queue.Clear(); - - RenderOutputQueueMetrics metrics = queue.GetMetrics(); - Expect(metrics.depth == 0, "clear empties ready queue"); - Expect(gReleasedFrames == 2, "clear releases queued ready frames"); -} -} - -int main() -{ - TestQueuePreservesOrdering(); - TestBoundedQueueDropsOldestFrame(); - TestOverflowReleasesDroppedFrame(); - TestDropOldestFrameReleasesFrame(); - TestUnderrunIsCounted(); - TestConfigureShrinksDepthToNewCapacity(); - TestConfigureReleasesTrimmedFrames(); - TestClearReleasesQueuedFrames(); - - if (gFailures != 0) - { - std::cerr << gFailures << " render output queue test failure(s).\n"; - return 1; - } - - std::cout << "RenderOutputQueue tests passed.\n"; - return 0; -} diff --git a/tests/SystemOutputFramePoolTests.cpp b/tests/SystemOutputFramePoolTests.cpp deleted file mode 100644 index c649a46..0000000 --- a/tests/SystemOutputFramePoolTests.cpp +++ /dev/null @@ -1,231 +0,0 @@ -#include "SystemOutputFramePool.h" - -#include -#include - -namespace -{ -int gFailures = 0; - -void Expect(bool condition, const char* message) -{ - if (condition) - return; - - std::cerr << "FAIL: " << message << "\n"; - ++gFailures; -} - -SystemOutputFramePoolConfig MakeConfig(std::size_t capacity = 2) -{ - SystemOutputFramePoolConfig config; - config.width = 4; - config.height = 3; - config.pixelFormat = VideoIOPixelFormat::Bgra8; - config.capacity = capacity; - return config; -} - -void TestAcquireHonorsCapacityAndFrameShape() -{ - SystemOutputFramePool pool(MakeConfig(2)); - - OutputFrameSlot first; - OutputFrameSlot second; - OutputFrameSlot third; - Expect(pool.AcquireFreeSlot(first), "first slot can be acquired"); - Expect(pool.AcquireFreeSlot(second), "second slot can be acquired"); - Expect(!pool.AcquireFreeSlot(third), "fixed capacity rejects third acquire"); - - Expect(first.frame.bytes != nullptr, "acquired slot has system memory"); - Expect(first.frame.nativeBuffer == first.frame.bytes, "native buffer points at system memory"); - Expect(first.frame.nativeFrame == nullptr, "system frame has no native frame"); - Expect(first.frame.width == 4, "frame width is configured"); - Expect(first.frame.height == 3, "frame height is configured"); - Expect(first.frame.rowBytes == 16, "BGRA8 row bytes are inferred"); - Expect(first.frame.pixelFormat == VideoIOPixelFormat::Bgra8, "BGRA8 is the default output format"); - Expect(first.frame.bytes != second.frame.bytes, "each slot owns distinct memory"); - - SystemOutputFramePoolMetrics metrics = pool.GetMetrics(); - Expect(metrics.freeCount == 0, "all slots are in use"); - Expect(metrics.renderingCount == 2, "rendering slots are counted"); - Expect(metrics.acquiredCount == 2, "acquired slots are counted"); - Expect(metrics.acquireMissCount == 1, "capacity miss is counted"); -} - -void TestPhase77StateContract() -{ - SystemOutputFramePool pool(MakeConfig(1)); - - SystemOutputFramePoolMetrics metrics = pool.GetMetrics(); - Expect(metrics.freeCount == 1, "new pool starts with one free slot"); - Expect(metrics.renderingCount == 0, "new pool starts with no rendering slots"); - Expect(metrics.completedCount == 0, "new pool starts with no completed slots"); - Expect(metrics.scheduledCount == 0, "new pool starts with no scheduled slots"); - - OutputFrameSlot slot; - Expect(pool.AcquireRenderingSlot(slot), "free slot moves to rendering"); - metrics = pool.GetMetrics(); - Expect(metrics.freeCount == 0, "rendering slot leaves free pool"); - Expect(metrics.renderingCount == 1, "rendering slot is counted"); - - Expect(pool.PublishCompletedSlot(slot), "rendering slot moves to completed"); - metrics = pool.GetMetrics(); - Expect(metrics.renderingCount == 0, "completed slot leaves rendering"); - Expect(metrics.completedCount == 1, "completed slot is counted"); - Expect(metrics.readyCount == 1, "completed slot is available to scheduler"); - - OutputFrameSlot completed; - Expect(pool.ConsumeCompletedSlot(completed), "completed slot can be dequeued for scheduling"); - metrics = pool.GetMetrics(); - Expect(metrics.completedCount == 1, "dequeued completed slot remains completed until scheduled"); - Expect(metrics.readyCount == 0, "dequeued completed slot leaves ready queue"); - - Expect(pool.MarkScheduled(completed), "completed slot moves to scheduled"); - metrics = pool.GetMetrics(); - Expect(metrics.completedCount == 0, "scheduled slot leaves completed state"); - Expect(metrics.scheduledCount == 1, "scheduled slot is counted"); - - Expect(pool.ReleaseScheduledSlot(completed), "scheduled slot returns to free"); - metrics = pool.GetMetrics(); - Expect(metrics.freeCount == 1, "released scheduled slot returns to free"); - Expect(metrics.scheduledCount == 0, "released scheduled slot leaves scheduled state"); -} - -void TestReadySlotsAreConsumedFifo() -{ - SystemOutputFramePool pool(MakeConfig(2)); - - OutputFrameSlot first; - OutputFrameSlot second; - Expect(pool.AcquireFreeSlot(first), "first FIFO slot can be acquired"); - Expect(pool.AcquireFreeSlot(second), "second FIFO slot can be acquired"); - Expect(pool.PublishReadySlot(first), "first FIFO slot can be published"); - Expect(pool.PublishReadySlot(second), "second FIFO slot can be published"); - - OutputFrameSlot consumed; - Expect(pool.ConsumeReadySlot(consumed), "first ready slot can be consumed"); - Expect(consumed.index == first.index, "first published slot is consumed first"); - Expect(pool.MarkScheduled(consumed), "consumed slot can be marked scheduled"); - Expect(pool.ReleaseScheduledSlot(consumed), "scheduled slot can be released"); - - Expect(pool.ConsumeReadySlot(consumed), "second ready slot can be consumed"); - Expect(consumed.index == second.index, "second published slot is consumed second"); - Expect(pool.ReleaseSlot(consumed), "consumed slot can be released without scheduling"); - - SystemOutputFramePoolMetrics metrics = pool.GetMetrics(); - Expect(metrics.freeCount == 2, "released slots return to free pool"); - Expect(metrics.readyCount == 0, "ready queue is empty after consumption"); -} - -void TestCompletedSlotCannotBeAcquiredUntilReleased() -{ - SystemOutputFramePool pool(MakeConfig(1)); - - OutputFrameSlot slot; - OutputFrameSlot extra; - Expect(pool.AcquireRenderingSlot(slot), "single slot can be acquired for rendering"); - Expect(pool.PublishCompletedSlot(slot), "single slot can be published completed"); - Expect(!pool.AcquireRenderingSlot(extra), "completed slot is not available for rendering"); - - OutputFrameSlot completed; - Expect(pool.ConsumeCompletedSlot(completed), "completed slot can be dequeued"); - Expect(!pool.AcquireRenderingSlot(extra), "dequeued completed slot is still not free"); - Expect(pool.MarkScheduled(completed), "dequeued completed slot can be scheduled"); - Expect(!pool.AcquireRenderingSlot(extra), "scheduled slot is still not free"); - Expect(pool.ReleaseScheduledSlot(completed), "scheduled slot can be released"); - Expect(pool.AcquireRenderingSlot(extra), "released slot can be acquired again"); -} - -void TestReadySlotCanBeScheduledByBuffer() -{ - SystemOutputFramePool pool(MakeConfig(1)); - - OutputFrameSlot slot; - Expect(pool.AcquireFreeSlot(slot), "buffer schedule slot can be acquired"); - void* bytes = slot.frame.bytes; - Expect(pool.PublishReadySlot(slot), "buffer schedule slot can be published"); - Expect(pool.MarkScheduledByBuffer(bytes), "ready slot can be marked scheduled by buffer"); - - SystemOutputFramePoolMetrics metrics = pool.GetMetrics(); - Expect(metrics.readyCount == 0, "scheduled-by-buffer removes slot from ready queue"); - Expect(metrics.scheduledCount == 1, "scheduled-by-buffer counts scheduled slot"); - - Expect(pool.ReleaseSlotByBuffer(bytes), "scheduled slot can be released by buffer"); - metrics = pool.GetMetrics(); - Expect(metrics.freeCount == 1, "released-by-buffer slot returns to free pool"); -} - -void TestInvalidTransitionsAreRejected() -{ - SystemOutputFramePool pool(MakeConfig(1)); - - OutputFrameSlot slot; - Expect(pool.AcquireFreeSlot(slot), "transition slot can be acquired"); - Expect(!pool.MarkScheduled(slot), "acquired slot cannot be marked scheduled"); - Expect(pool.PublishReadySlot(slot), "acquired slot can be published"); - Expect(!pool.PublishReadySlot(slot), "ready slot cannot be published twice"); - Expect(pool.ReleaseSlot(slot), "ready slot can be released to free"); - Expect(!pool.ReleaseSlot(slot), "free slot cannot be released again"); - - OutputFrameSlot next; - Expect(pool.AcquireFreeSlot(next), "slot can be reacquired after release"); - Expect(next.index == slot.index, "same storage slot can be reused"); - Expect(next.generation != slot.generation, "stale handles are invalidated on reacquire"); - Expect(!pool.PublishReadySlot(slot), "stale handle cannot publish reacquired slot"); -} - -void TestPixelFormatAwareSizing() -{ - SystemOutputFramePoolConfig config; - config.width = 7; - config.height = 2; - config.pixelFormat = VideoIOPixelFormat::V210; - config.capacity = 1; - - SystemOutputFramePool pool(config); - OutputFrameSlot slot; - Expect(pool.AcquireFreeSlot(slot), "v210 slot can be acquired"); - Expect(slot.frame.pixelFormat == VideoIOPixelFormat::V210, "slot keeps configured pixel format"); - Expect(slot.frame.rowBytes == static_cast(MinimumV210RowBytes(config.width)), "v210 row bytes are inferred"); - - SystemOutputFramePoolConfig explicitConfig = config; - explicitConfig.pixelFormat = VideoIOPixelFormat::Uyvy8; - explicitConfig.rowBytes = 64; - pool.Configure(explicitConfig); - Expect(pool.AcquireFreeSlot(slot), "explicit row-byte slot can be acquired"); - Expect(slot.frame.pixelFormat == VideoIOPixelFormat::Uyvy8, "slot keeps reconfigured pixel format"); - Expect(slot.frame.rowBytes == 64, "explicit row bytes are preserved"); -} - -void TestEmptyReadyQueueUnderrunIsCounted() -{ - SystemOutputFramePool pool(MakeConfig(1)); - OutputFrameSlot slot; - Expect(!pool.ConsumeReadySlot(slot), "empty ready queue cannot be consumed"); - - SystemOutputFramePoolMetrics metrics = pool.GetMetrics(); - Expect(metrics.readyUnderrunCount == 1, "ready underrun is counted"); -} -} - -int main() -{ - TestAcquireHonorsCapacityAndFrameShape(); - TestPhase77StateContract(); - TestReadySlotsAreConsumedFifo(); - TestCompletedSlotCannotBeAcquiredUntilReleased(); - TestReadySlotCanBeScheduledByBuffer(); - TestInvalidTransitionsAreRejected(); - TestPixelFormatAwareSizing(); - TestEmptyReadyQueueUnderrunIsCounted(); - - if (gFailures != 0) - { - std::cerr << gFailures << " system output frame pool test failure(s).\n"; - return 1; - } - - std::cout << "SystemOutputFramePool tests passed.\n"; - return 0; -} diff --git a/tests/VideoBackendLifecycleTests.cpp b/tests/VideoBackendLifecycleTests.cpp deleted file mode 100644 index 0a46248..0000000 --- a/tests/VideoBackendLifecycleTests.cpp +++ /dev/null @@ -1,96 +0,0 @@ -#include "VideoBackendLifecycle.h" - -#include -#include - -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; -} diff --git a/tests/VideoIODeviceFakeTests.cpp b/tests/VideoIODeviceFakeTests.cpp deleted file mode 100644 index 28d9693..0000000 --- a/tests/VideoIODeviceFakeTests.cpp +++ /dev/null @@ -1,186 +0,0 @@ -#include "VideoIOTypes.h" - -#include -#include - -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&, bool, 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 PrepareOutputSchedule() override - { - mPreparedOutputSchedule = true; - return true; - } - - bool StartInputStreams() override - { - mInputStreamsStarted = true; - mState.hasInputSource = true; - VideoIOFrame input; - input.bytes = mInputBytes.data(); - input.rowBytes = static_cast(mState.inputFrameRowBytes); - input.width = mState.inputFrameSize.width; - input.height = mState.inputFrameSize.height; - input.pixelFormat = mState.inputPixelFormat; - if (mInputCallback) - mInputCallback(input); - return true; - } - - bool StartScheduledPlayback() override - { - mScheduledPlaybackStarted = true; - if (mOutputCallback) - mOutputCallback(VideoIOCompletion{ VideoIOCompletionResult::Completed }); - return true; - } - - bool Start() override - { - return PrepareOutputSchedule() && StartInputStreams() && StartScheduledPlayback(); - } - - 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(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; - } - - VideoPlayoutRecoveryDecision AccountForCompletionResult(VideoIOCompletionResult result, uint64_t readyQueueDepth) override - { - mLastCompletion = result; - mLastReadyQueueDepth = readyQueueDepth; - VideoPlayoutRecoveryDecision decision; - decision.result = result; - decision.readyQueueDepth = readyQueueDepth; - return decision; - } - - unsigned ScheduledFrames() const { return mScheduledFrames; } - bool PreparedOutputSchedule() const { return mPreparedOutputSchedule; } - bool InputStreamsStarted() const { return mInputStreamsStarted; } - bool ScheduledPlaybackStarted() const { return mScheduledPlaybackStarted; } - VideoIOCompletionResult LastCompletion() const { return mLastCompletion; } - uint64_t LastReadyQueueDepth() const { return mLastReadyQueueDepth; } - -private: - VideoIOState mState; - InputFrameCallback mInputCallback; - OutputFrameCallback mOutputCallback; - std::array mInputBytes = {}; - std::array mOutputBytes = {}; - unsigned mScheduledFrames = 0; - bool mPreparedOutputSchedule = false; - bool mInputStreamsStarted = false; - bool mScheduledPlaybackStarted = false; - VideoIOCompletionResult mLastCompletion = VideoIOCompletionResult::Unknown; - uint64_t mLastReadyQueueDepth = 0; -}; -} - -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, false, 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, 2); - 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.PreparedOutputSchedule(), "fake output schedule was prepared"); - Expect(device.InputStreamsStarted(), "fake input streams started"); - Expect(device.ScheduledPlaybackStarted(), "fake scheduled playback started"); - Expect(device.ScheduledFrames() == 1, "fake backend schedules one frame"); - Expect(device.LastCompletion() == VideoIOCompletionResult::Completed, "fake backend records generic completion"); - Expect(device.LastReadyQueueDepth() == 2, "fake backend records ready queue depth"); - - if (gFailures != 0) - { - std::cerr << gFailures << " VideoIODevice fake test failure(s).\n"; - return 1; - } - - std::cout << "VideoIODevice fake tests passed.\n"; - return 0; -}