legacy code cleanup
Some checks failed
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Failing after 2m31s
CI / Windows Release Package (push) Has been skipped

This commit is contained in:
2026-05-22 15:45:54 +10:00
parent d831b418d7
commit 2fdb1741f9
26 changed files with 27 additions and 3684 deletions

View File

@@ -4,7 +4,6 @@
#include "VideoMode.h"
#include <cstdint>
#include <functional>
#include <string>
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<void(const VideoIOFrame&)>;
using OutputFrameCallback = std::function<void(const VideoIOCompletion&)>;
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; }
};

View File

@@ -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");

View File

@@ -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
////////////////////////////////////////////

View File

@@ -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<IDeckLinkIterator> deckLinkIterator;
CComPtr<IDeckLinkDisplayMode> inputMode;
CComPtr<IDeckLinkDisplayMode> 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<void**>(&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<IDeckLinkDisplayModeIterator> 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<IDeckLinkDisplayModeIterator> outputDisplayModeIterator;
if (output->GetDisplayModeIterator(&outputDisplayModeIterator) != S_OK)
{
@@ -287,11 +237,7 @@ bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoM
}
mState.outputFrameSize = { static_cast<unsigned>(outputMode->GetWidth()), static_cast<unsigned>(outputMode->GetHeight()) };
mState.inputFrameSize = inputMode
? FrameSize{ static_cast<unsigned>(inputMode->GetWidth()), static_cast<unsigned>(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<IDeckLinkMutableVideoFrame> 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<IDeckLinkVideoBuffer> 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<unsigned>(inputFrame->GetWidth());
frame.height = static_cast<unsigned>(inputFrame->GetHeight());
frame.pixelFormat = mState.inputPixelFormat;
frame.hasNoInputSource = hasNoInputSource;
mInputFrameCallback(frame);
inputFrameBuffer->EndAccess(bmdBufferAccessRead);
}
void DeckLinkSession::HandlePlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completionResult)
{
RefreshBufferedVideoFrameCount();

View File

@@ -11,28 +11,28 @@
#include <atlbase.h>
#include <deque>
#include <functional>
#include <mutex>
#include <string>
#include <unordered_map>
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(const VideoIOCompletion&)>;
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> captureDelegate;
CComPtr<PlayoutDelegate> playoutDelegate;
CComPtr<IDeckLinkInput> input;
CComPtr<IDeckLinkOutput> output;
CComPtr<IDeckLinkKeyer> keyer;
std::deque<CComPtr<IDeckLinkMutableVideoFrame>> outputVideoFrameQueue;
@@ -97,6 +94,5 @@ private:
bool mScheduleRealignmentPending = false;
bool mScheduleRealignmentArmed = true;
bool mProactiveScheduleRealignmentArmed = true;
InputFrameCallback mInputFrameCallback;
OutputFrameCallback mOutputFrameCallback;
};

File diff suppressed because it is too large Load Diff

View File

@@ -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 <chrono>
#include <condition_variable>
#include <cstdint>
#include <deque>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
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<VideoIODevice> mVideoIODevice;
std::unique_ptr<OpenGLVideoIOBridge> mBridge;
std::mutex mOutputCompletionMutex;
std::condition_variable mOutputCompletionCondition;
std::deque<VideoIOCompletion> 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;
};

View File

@@ -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";
}
}

View File

@@ -1,43 +0,0 @@
#pragma once
#include <string>
enum class VideoBackendLifecycleState
{
Uninitialized,
Discovering,
Discovered,
Configuring,
Configured,
Prerolling,
Running,
Degraded,
Stopping,
Stopped,
Failed
};
struct VideoBackendLifecycleTransition
{
VideoBackendLifecycleState previous = VideoBackendLifecycleState::Uninitialized;
VideoBackendLifecycleState current = VideoBackendLifecycleState::Uninitialized;
bool accepted = false;
std::string reason;
std::string errorMessage;
};
class VideoBackendLifecycle
{
public:
VideoBackendLifecycleState State() const;
const std::string& FailureReason() const;
VideoBackendLifecycleTransition TransitionTo(VideoBackendLifecycleState next, const std::string& reason);
VideoBackendLifecycleTransition Fail(const std::string& reason);
static bool CanTransition(VideoBackendLifecycleState current, VideoBackendLifecycleState next);
static const char* StateName(VideoBackendLifecycleState state);
private:
VideoBackendLifecycleState mState = VideoBackendLifecycleState::Uninitialized;
std::string mFailureReason;
};

View File

@@ -1,89 +0,0 @@
#include "OutputProductionController.h"
#include <algorithm>
namespace
{
std::size_t ClampReadyLimit(unsigned value, std::size_t capacity)
{
const std::size_t requested = static_cast<std::size_t>(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<std::size_t>(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";
}
}

View File

@@ -1,46 +0,0 @@
#pragma once
#include "VideoPlayoutPolicy.h"
#include <cstddef>
#include <cstdint>
#include <string>
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);

View File

@@ -1,102 +0,0 @@
#include "RenderCadenceController.h"
#include <algorithm>
#include <cmath>
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<double>(lateness.count()) / static_cast<double>(mTargetFrameDuration.count());
if (lateFrames < mPolicy.skipThresholdFrames)
return 0;
const uint64_t elapsedTicks = static_cast<uint64_t>(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";
}
}

View File

@@ -1,68 +0,0 @@
#pragma once
#include <chrono>
#include <cstdint>
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);

View File

@@ -1,93 +0,0 @@
#include "RenderOutputQueue.h"
RenderOutputQueue::RenderOutputQueue(const VideoPlayoutPolicy& policy) :
mPolicy(NormalizeVideoPlayoutPolicy(policy))
{
}
void RenderOutputQueue::Configure(const VideoPlayoutPolicy& policy)
{
std::lock_guard<std::mutex> lock(mMutex);
mPolicy = NormalizeVideoPlayoutPolicy(policy);
while (mReadyFrames.size() > CapacityLocked())
{
ReleaseFrame(mReadyFrames.front());
mReadyFrames.pop_front();
++mDroppedCount;
}
}
bool RenderOutputQueue::Push(RenderOutputFrame frame)
{
std::lock_guard<std::mutex> 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<std::mutex> lock(mMutex);
if (mReadyFrames.empty())
{
++mUnderrunCount;
return false;
}
frame = mReadyFrames.front();
mReadyFrames.pop_front();
++mPoppedCount;
return true;
}
bool RenderOutputQueue::DropOldestFrame()
{
std::lock_guard<std::mutex> lock(mMutex);
if (mReadyFrames.empty())
return false;
ReleaseFrame(mReadyFrames.front());
mReadyFrames.pop_front();
++mDroppedCount;
return true;
}
void RenderOutputQueue::Clear()
{
std::lock_guard<std::mutex> lock(mMutex);
for (RenderOutputFrame& frame : mReadyFrames)
ReleaseFrame(frame);
mReadyFrames.clear();
}
RenderOutputQueueMetrics RenderOutputQueue::GetMetrics() const
{
std::lock_guard<std::mutex> lock(mMutex);
RenderOutputQueueMetrics metrics;
metrics.depth = mReadyFrames.size();
metrics.capacity = CapacityLocked();
metrics.pushedCount = mPushedCount;
metrics.poppedCount = mPoppedCount;
metrics.droppedCount = mDroppedCount;
metrics.underrunCount = mUnderrunCount;
return metrics;
}
std::size_t RenderOutputQueue::CapacityLocked() const
{
return static_cast<std::size_t>(mPolicy.maxReadyFrames);
}
void RenderOutputQueue::ReleaseFrame(RenderOutputFrame& frame)
{
if (frame.releaseFrame)
frame.releaseFrame(frame.frame);
frame.releaseFrame = {};
}

View File

@@ -1,52 +0,0 @@
#pragma once
#include "VideoIOTypes.h"
#include "VideoPlayoutPolicy.h"
#include <cstdint>
#include <deque>
#include <functional>
#include <mutex>
struct RenderOutputFrame
{
VideoIOOutputFrame frame;
uint64_t frameIndex = 0;
bool stale = false;
std::function<void(VideoIOOutputFrame& frame)> 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<RenderOutputFrame> mReadyFrames;
uint64_t mPushedCount = 0;
uint64_t mPoppedCount = 0;
uint64_t mDroppedCount = 0;
uint64_t mUnderrunCount = 0;
};

View File

@@ -1,260 +0,0 @@
#include "SystemOutputFramePool.h"
#include <algorithm>
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<std::mutex> 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<std::mutex> lock(mMutex);
return mConfig;
}
bool SystemOutputFramePool::AcquireFreeSlot(OutputFrameSlot& slot)
{
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lock(mMutex);
return TransitionSlotLocked(slot, OutputFrameSlotState::Scheduled, OutputFrameSlotState::Free);
}
bool SystemOutputFramePool::ReleaseSlotByBuffer(void* bytes)
{
if (bytes == nullptr)
return false;
std::lock_guard<std::mutex> 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<std::mutex> lock(mMutex);
mReadySlots.clear();
for (StoredSlot& slot : mSlots)
{
slot.state = OutputFrameSlotState::Free;
++slot.generation;
}
}
SystemOutputFramePoolMetrics SystemOutputFramePool::GetMetrics() const
{
std::lock_guard<std::mutex> 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<long>(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<std::size_t>(mConfig.rowBytes) * static_cast<std::size_t>(mConfig.height);
}

View File

@@ -1,94 +0,0 @@
#pragma once
#include "VideoIOTypes.h"
#include <cstddef>
#include <cstdint>
#include <deque>
#include <mutex>
#include <vector>
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<unsigned char> 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<StoredSlot> mSlots;
std::deque<std::size_t> mReadySlots;
uint64_t mAcquireMissCount = 0;
uint64_t mReadyUnderrunCount = 0;
};