videoIO seperation

This commit is contained in:
2026-05-22 14:58:42 +10:00
parent 35801601a5
commit b7e7452567
12 changed files with 401 additions and 187 deletions

View File

@@ -3,6 +3,7 @@
#include "../frames/InputFrameMailbox.h"
#include "DeckLinkAPI_h.h"
#include "DeckLinkDisplayMode.h"
#include "VideoIOEdges.h"
#include <atlbase.h>
@@ -19,16 +20,7 @@ struct DeckLinkInputConfig
VideoFormat videoFormat;
};
struct DeckLinkInputMetrics
{
uint64_t capturedFrames = 0;
uint64_t noInputSourceFrames = 0;
uint64_t unsupportedFrames = 0;
uint64_t submitMisses = 0;
double convertMilliseconds = 0.0;
double submitMilliseconds = 0.0;
const char* captureFormat = "none";
};
using DeckLinkInputMetrics = VideoInputEdgeMetrics;
class DeckLinkInput;
@@ -48,7 +40,7 @@ private:
std::atomic<ULONG> mRefCount{ 1 };
};
class DeckLinkInput
class DeckLinkInput : public IVideoInputEdge
{
public:
DeckLinkInput(InputFrameMailbox& mailbox);
@@ -57,14 +49,14 @@ public:
~DeckLinkInput();
bool Initialize(const DeckLinkInputConfig& config, std::string& error);
bool Start(std::string& error);
void Stop();
void ReleaseResources();
bool Start(std::string& error) override;
void Stop() override;
void ReleaseResources() override;
bool IsInitialized() const { return mInput != nullptr; }
bool IsRunning() const { return mRunning.load(std::memory_order_acquire); }
VideoIOPixelFormat CapturePixelFormat() const;
DeckLinkInputMetrics Metrics() const;
bool IsInitialized() const override { return mInput != nullptr; }
bool IsRunning() const override { return mRunning.load(std::memory_order_acquire); }
VideoIOPixelFormat CapturePixelFormat() const override;
DeckLinkInputMetrics Metrics() const override;
void HandleFrameArrived(IDeckLinkVideoInputFrame* inputFrame);
void HandleFormatChanged();

View File

@@ -2,6 +2,7 @@
#include "DeckLinkDisplayMode.h"
#include "DeckLinkSession.h"
#include "VideoIOEdges.h"
#include "VideoIOTypes.h"
#include <atomic>
@@ -18,28 +19,12 @@ struct DeckLinkOutputConfig
bool outputAlphaRequired = false;
};
struct DeckLinkOutputMetrics
{
uint64_t completions = 0;
uint64_t displayedLate = 0;
uint64_t dropped = 0;
uint64_t flushed = 0;
uint64_t scheduleFailures = 0;
bool actualBufferedFramesAvailable = false;
uint64_t actualBufferedFrames = 0;
double scheduleCallMilliseconds = 0.0;
bool scheduleLeadAvailable = false;
int64_t playbackStreamTime = 0;
uint64_t playbackFrameIndex = 0;
uint64_t nextScheduleFrameIndex = 0;
int64_t scheduleLeadFrames = 0;
uint64_t scheduleRealignmentCount = 0;
};
using DeckLinkOutputMetrics = VideoOutputEdgeMetrics;
class DeckLinkOutput
class DeckLinkOutput : public IVideoOutputEdge
{
public:
using CompletionCallback = std::function<void(const VideoIOCompletion&)>;
using CompletionCallback = IVideoOutputEdge::CompletionCallback;
DeckLinkOutput() = default;
DeckLinkOutput(const DeckLinkOutput&) = delete;
@@ -47,13 +32,13 @@ public:
~DeckLinkOutput();
bool Initialize(const DeckLinkOutputConfig& config, CompletionCallback completionCallback, std::string& error);
bool StartScheduledPlayback(std::string& error);
bool ScheduleFrame(const VideoIOOutputFrame& frame);
void Stop();
void ReleaseResources();
bool StartScheduledPlayback(std::string& error) override;
bool ScheduleFrame(const VideoIOOutputFrame& frame) override;
void Stop() override;
void ReleaseResources() override;
const VideoIOState& State() const;
DeckLinkOutputMetrics Metrics() const;
const VideoIOState& State() const override;
DeckLinkOutputMetrics Metrics() const override;
private:
void HandleCompletion(const VideoIOCompletion& completion);

View File

@@ -1,127 +1,12 @@
#pragma once
#include "../frames/SystemFrameTypes.h"
#include "DeckLinkOutput.h"
#include "VideoIOTypes.h"
#include <atomic>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <thread>
#include "VideoOutputThread.h"
namespace RenderCadenceCompositor
{
struct DeckLinkOutputThreadConfig
{
std::size_t targetBufferedFrames = 4;
std::chrono::milliseconds idleSleep = std::chrono::milliseconds(1);
};
struct DeckLinkOutputThreadMetrics
{
uint64_t scheduledFrames = 0;
uint64_t completedPollMisses = 0;
uint64_t scheduleFailures = 0;
};
using DeckLinkOutputThreadConfig = VideoOutputThreadConfig;
using DeckLinkOutputThreadMetrics = VideoOutputThreadMetrics;
template <typename SystemFrameExchange>
class DeckLinkOutputThread
{
public:
DeckLinkOutputThread(DeckLinkOutput& output, SystemFrameExchange& exchange, DeckLinkOutputThreadConfig config = DeckLinkOutputThreadConfig()) :
mOutput(output),
mExchange(exchange),
mConfig(config)
{
}
DeckLinkOutputThread(const DeckLinkOutputThread&) = delete;
DeckLinkOutputThread& operator=(const DeckLinkOutputThread&) = delete;
~DeckLinkOutputThread()
{
Stop();
}
bool Start()
{
if (mRunning)
return true;
mStopping = false;
mThread = std::thread([this]() { ThreadMain(); });
mRunning = true;
return true;
}
void Stop()
{
mStopping = true;
if (mThread.joinable())
mThread.join();
mRunning = false;
}
DeckLinkOutputThreadMetrics Metrics() const
{
DeckLinkOutputThreadMetrics metrics;
metrics.scheduledFrames = mScheduledFrames.load();
metrics.completedPollMisses = mCompletedPollMisses.load();
metrics.scheduleFailures = mScheduleFailures.load();
return metrics;
}
private:
void ThreadMain()
{
while (!mStopping)
{
const auto exchangeMetrics = mExchange.Metrics();
const auto outputMetrics = mOutput.Metrics();
const std::size_t bufferedFrames = outputMetrics.actualBufferedFramesAvailable
? static_cast<std::size_t>(outputMetrics.actualBufferedFrames)
: exchangeMetrics.scheduledCount;
if (bufferedFrames >= mConfig.targetBufferedFrames)
{
std::this_thread::sleep_for(mConfig.idleSleep);
continue;
}
SystemFrame frame;
if (!mExchange.ConsumeCompletedForSchedule(frame))
{
++mCompletedPollMisses;
std::this_thread::sleep_for(mConfig.idleSleep);
continue;
}
VideoIOOutputFrame outputFrame;
outputFrame.bytes = frame.bytes;
outputFrame.nativeBuffer = frame.bytes;
outputFrame.rowBytes = frame.rowBytes;
outputFrame.width = frame.width;
outputFrame.height = frame.height;
outputFrame.pixelFormat = frame.pixelFormat;
if (!mOutput.ScheduleFrame(outputFrame))
{
++mScheduleFailures;
mExchange.ReleaseScheduledByBytes(frame.bytes);
std::this_thread::sleep_for(mConfig.idleSleep);
continue;
}
++mScheduledFrames;
}
}
DeckLinkOutput& mOutput;
SystemFrameExchange& mExchange;
DeckLinkOutputThreadConfig mConfig;
std::thread mThread;
std::atomic<bool> mStopping{ false };
std::atomic<bool> mRunning{ false };
std::atomic<uint64_t> mScheduledFrames{ 0 };
std::atomic<uint64_t> mCompletedPollMisses{ 0 };
std::atomic<uint64_t> mScheduleFailures{ 0 };
};
using DeckLinkOutputThread = VideoOutputThread<SystemFrameExchange>;
}

69
src/video/VideoIOEdges.h Normal file
View File

@@ -0,0 +1,69 @@
#pragma once
#include "VideoIOFormat.h"
#include "VideoIOTypes.h"
#include <cstdint>
#include <functional>
#include <string>
namespace RenderCadenceCompositor
{
struct VideoInputEdgeMetrics
{
uint64_t capturedFrames = 0;
uint64_t noInputSourceFrames = 0;
uint64_t unsupportedFrames = 0;
uint64_t submitMisses = 0;
double convertMilliseconds = 0.0;
double submitMilliseconds = 0.0;
const char* captureFormat = "none";
};
class IVideoInputEdge
{
public:
virtual ~IVideoInputEdge() = default;
virtual bool Start(std::string& error) = 0;
virtual void Stop() = 0;
virtual void ReleaseResources() = 0;
virtual bool IsInitialized() const = 0;
virtual bool IsRunning() const = 0;
virtual VideoIOPixelFormat CapturePixelFormat() const = 0;
virtual VideoInputEdgeMetrics Metrics() const = 0;
};
struct VideoOutputEdgeMetrics
{
uint64_t completions = 0;
uint64_t displayedLate = 0;
uint64_t dropped = 0;
uint64_t flushed = 0;
uint64_t scheduleFailures = 0;
bool actualBufferedFramesAvailable = false;
uint64_t actualBufferedFrames = 0;
double scheduleCallMilliseconds = 0.0;
bool scheduleLeadAvailable = false;
int64_t playbackStreamTime = 0;
uint64_t playbackFrameIndex = 0;
uint64_t nextScheduleFrameIndex = 0;
int64_t scheduleLeadFrames = 0;
uint64_t scheduleRealignmentCount = 0;
};
class IVideoOutputEdge
{
public:
using CompletionCallback = std::function<void(const VideoIOCompletion&)>;
virtual ~IVideoOutputEdge() = default;
virtual bool StartScheduledPlayback(std::string& error) = 0;
virtual bool ScheduleFrame(const VideoIOOutputFrame& frame) = 0;
virtual void Stop() = 0;
virtual void ReleaseResources() = 0;
virtual const VideoIOState& State() const = 0;
virtual VideoOutputEdgeMetrics Metrics() const = 0;
};
}

View File

@@ -0,0 +1,127 @@
#pragma once
#include "../frames/SystemFrameTypes.h"
#include "VideoIOEdges.h"
#include "VideoIOTypes.h"
#include <atomic>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <thread>
namespace RenderCadenceCompositor
{
struct VideoOutputThreadConfig
{
std::size_t targetBufferedFrames = 4;
std::chrono::milliseconds idleSleep = std::chrono::milliseconds(1);
};
struct VideoOutputThreadMetrics
{
uint64_t scheduledFrames = 0;
uint64_t completedPollMisses = 0;
uint64_t scheduleFailures = 0;
};
template <typename SystemFrameExchange>
class VideoOutputThread
{
public:
VideoOutputThread(IVideoOutputEdge& output, SystemFrameExchange& exchange, VideoOutputThreadConfig config = VideoOutputThreadConfig()) :
mOutput(output),
mExchange(exchange),
mConfig(config)
{
}
VideoOutputThread(const VideoOutputThread&) = delete;
VideoOutputThread& operator=(const VideoOutputThread&) = delete;
~VideoOutputThread()
{
Stop();
}
bool Start()
{
if (mRunning)
return true;
mStopping = false;
mThread = std::thread([this]() { ThreadMain(); });
mRunning = true;
return true;
}
void Stop()
{
mStopping = true;
if (mThread.joinable())
mThread.join();
mRunning = false;
}
VideoOutputThreadMetrics Metrics() const
{
VideoOutputThreadMetrics metrics;
metrics.scheduledFrames = mScheduledFrames.load();
metrics.completedPollMisses = mCompletedPollMisses.load();
metrics.scheduleFailures = mScheduleFailures.load();
return metrics;
}
private:
void ThreadMain()
{
while (!mStopping)
{
const auto exchangeMetrics = mExchange.Metrics();
const auto outputMetrics = mOutput.Metrics();
const std::size_t bufferedFrames = outputMetrics.actualBufferedFramesAvailable
? static_cast<std::size_t>(outputMetrics.actualBufferedFrames)
: exchangeMetrics.scheduledCount;
if (bufferedFrames >= mConfig.targetBufferedFrames)
{
std::this_thread::sleep_for(mConfig.idleSleep);
continue;
}
SystemFrame frame;
if (!mExchange.ConsumeCompletedForSchedule(frame))
{
++mCompletedPollMisses;
std::this_thread::sleep_for(mConfig.idleSleep);
continue;
}
VideoIOOutputFrame outputFrame;
outputFrame.bytes = frame.bytes;
outputFrame.nativeBuffer = frame.bytes;
outputFrame.rowBytes = frame.rowBytes;
outputFrame.width = frame.width;
outputFrame.height = frame.height;
outputFrame.pixelFormat = frame.pixelFormat;
if (!mOutput.ScheduleFrame(outputFrame))
{
++mScheduleFailures;
mExchange.ReleaseScheduledByBytes(frame.bytes);
std::this_thread::sleep_for(mConfig.idleSleep);
continue;
}
++mScheduledFrames;
}
}
IVideoOutputEdge& mOutput;
SystemFrameExchange& mExchange;
VideoOutputThreadConfig mConfig;
std::thread mThread;
std::atomic<bool> mStopping{ false };
std::atomic<bool> mRunning{ false };
std::atomic<uint64_t> mScheduledFrames{ 0 };
std::atomic<uint64_t> mCompletedPollMisses{ 0 };
std::atomic<uint64_t> mScheduleFailures{ 0 };
};
}