decklink start up to separate factory
This commit is contained in:
@@ -204,12 +204,16 @@ Currently consumed fields:
|
||||
- `inputFrameRate`
|
||||
- `outputVideoFormat`
|
||||
- `outputFrameRate`
|
||||
- `videoInputBackend`
|
||||
- `videoOutputBackend`
|
||||
- `autoReload`
|
||||
- `maxTemporalHistoryFrames`
|
||||
- `previewEnabled`
|
||||
- `previewFps`
|
||||
- `enableExternalKeying`
|
||||
|
||||
`videoInputBackend` and `videoOutputBackend` currently support `decklink` and `none`. Backend creation is routed through the app-side video backend factory, so new concrete backends can be added without making `main` or the render cadence path own their startup details.
|
||||
|
||||
When `previewEnabled` is true, the preview window runs on `PreviewWindowThread`. It paints BGRA8 system-memory frames with Win32/GDI after render readback has already completed, so it does not bind GL and does not consume frames from DeckLink output. `previewFps` controls the preview repaint cadence; the default is 60 fps and `config/runtime-host.json` tracks the shipped 59.94 output cadence.
|
||||
|
||||
The loaded config is treated as a read-only startup snapshot. Subsystems that need config should receive this snapshot or a narrowed config struct from app orchestration; they should not reload files independently.
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
#include "app/AppConfig.h"
|
||||
#include "app/AppConfigProvider.h"
|
||||
#include "app/RenderCadenceApp.h"
|
||||
#include "app/VideoBackendFactory.h"
|
||||
#include "frames/InputFrameMailbox.h"
|
||||
#include "frames/SystemFrameExchange.h"
|
||||
#include "logging/Logger.h"
|
||||
#include "render/thread/RenderThread.h"
|
||||
#include "video/decklink/DeckLinkInput.h"
|
||||
#include "video/decklink/DeckLinkInputThread.h"
|
||||
#include "video/decklink/DeckLinkDisplayMode.h"
|
||||
#include "video/core/VideoIOFormat.h"
|
||||
#include "video/core/VideoMode.h"
|
||||
|
||||
@@ -17,7 +15,6 @@
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -49,18 +46,6 @@ private:
|
||||
HRESULT mResult = S_OK;
|
||||
};
|
||||
|
||||
bool WaitForInputWarmup(InputFrameMailbox& mailbox, std::size_t targetReadyFrames, std::chrono::milliseconds timeout)
|
||||
{
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
while (std::chrono::steady_clock::now() - start < timeout)
|
||||
{
|
||||
const InputFrameMailboxMetrics metrics = mailbox.Metrics();
|
||||
if (metrics.readyCount >= targetReadyFrames)
|
||||
return true;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
@@ -80,11 +65,11 @@ int main(int argc, char** argv)
|
||||
RenderCadenceCompositor::Logger::Instance().Start(appConfig.logging);
|
||||
RenderCadenceCompositor::Log(
|
||||
"app",
|
||||
"RenderCadenceCompositor starting. Starts render cadence, system-memory exchange, DeckLink scheduled output, and telemetry. Press Enter to stop.");
|
||||
"RenderCadenceCompositor starting. Starts render cadence, configured video I/O backends, and telemetry. Press Enter to stop.");
|
||||
RenderCadenceCompositor::Log("app", "Loaded config from " + configProvider.SourcePath().string());
|
||||
|
||||
ComInitGuard com;
|
||||
if (!com.Initialize())
|
||||
if (RenderCadenceCompositor::VideoBackendsRequireCom(appConfig) && !com.Initialize())
|
||||
{
|
||||
std::ostringstream message;
|
||||
message << "COM initialization failed: 0x" << std::hex << com.Result();
|
||||
@@ -126,7 +111,7 @@ int main(int argc, char** argv)
|
||||
const bool outputVideoModeResolved = ResolveConfiguredVideoFormat(appConfig.outputVideoFormat, appConfig.outputFrameRate, outputVideoMode);
|
||||
if (!inputVideoModeResolved)
|
||||
{
|
||||
inputVideoModeError = "Unsupported DeckLink inputVideoFormat/inputFrameRate in config/runtime-host.json: " +
|
||||
inputVideoModeError = "Unsupported inputVideoFormat/inputFrameRate in config/runtime-host.json: " +
|
||||
appConfig.inputVideoFormat + " / " + appConfig.inputFrameRate;
|
||||
RenderCadenceCompositor::LogWarning("app", inputVideoModeError);
|
||||
}
|
||||
@@ -134,7 +119,7 @@ int main(int argc, char** argv)
|
||||
{
|
||||
RenderCadenceCompositor::LogWarning(
|
||||
"app",
|
||||
"Unsupported DeckLink outputVideoFormat/outputFrameRate in config/runtime-host.json; render cadence will use parsed frame-rate fallback: " +
|
||||
"Unsupported outputVideoFormat/outputFrameRate in config/runtime-host.json; render cadence will use parsed frame-rate fallback: " +
|
||||
appConfig.outputVideoFormat + " / " + appConfig.outputFrameRate);
|
||||
}
|
||||
else
|
||||
@@ -142,55 +127,12 @@ int main(int argc, char** argv)
|
||||
appConfig.deckLink.outputVideoMode = outputVideoMode;
|
||||
}
|
||||
|
||||
RenderCadenceCompositor::DeckLinkInput deckLinkInput(inputMailbox);
|
||||
RenderCadenceCompositor::DeckLinkInputThread deckLinkInputThread(deckLinkInput);
|
||||
bool deckLinkInputStarted = false;
|
||||
if (inputVideoModeResolved)
|
||||
{
|
||||
RenderCadenceCompositor::DeckLinkInputConfig deckLinkInputConfig;
|
||||
deckLinkInputConfig.videoFormat = inputVideoMode;
|
||||
std::string deckLinkInputError;
|
||||
if (deckLinkInput.Initialize(deckLinkInputConfig, deckLinkInputError))
|
||||
{
|
||||
inputMailboxConfig.pixelFormat = deckLinkInput.CapturePixelFormat();
|
||||
inputMailboxConfig.rowBytes = VideoIORowBytes(inputMailboxConfig.pixelFormat, inputMailboxConfig.width);
|
||||
inputMailbox.Configure(inputMailboxConfig);
|
||||
}
|
||||
|
||||
if (deckLinkInput.IsInitialized() && deckLinkInputThread.Start(deckLinkInputError))
|
||||
{
|
||||
deckLinkInputStarted = true;
|
||||
RenderCadenceCompositor::Log("app", "DeckLink input edge started for " + inputVideoMode.displayName + ".");
|
||||
RenderCadenceCompositor::Log("app", "Waiting for DeckLink input warmup frames.");
|
||||
constexpr std::size_t kInputStartupBufferedFrames = 3;
|
||||
constexpr std::chrono::milliseconds kInputWarmupTimeout(1000);
|
||||
if (WaitForInputWarmup(inputMailbox, kInputStartupBufferedFrames, kInputWarmupTimeout))
|
||||
{
|
||||
const InputFrameMailboxMetrics metrics = inputMailbox.Metrics();
|
||||
RenderCadenceCompositor::Log(
|
||||
"app",
|
||||
"DeckLink input warmup complete. ready=" + std::to_string(metrics.readyCount) +
|
||||
" submitted=" + std::to_string(metrics.submittedFrames) + ".");
|
||||
}
|
||||
else
|
||||
{
|
||||
const InputFrameMailboxMetrics metrics = inputMailbox.Metrics();
|
||||
RenderCadenceCompositor::LogWarning(
|
||||
"app",
|
||||
"DeckLink input warmup timed out; starting render cadence with current input buffer. ready=" +
|
||||
std::to_string(metrics.readyCount) + " submitted=" + std::to_string(metrics.submittedFrames) + ".");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RenderCadenceCompositor::LogWarning("app", "DeckLink input edge unavailable; runtime shaders will use fallback input until a real input edge is available. " + deckLinkInputError);
|
||||
deckLinkInput.ReleaseResources();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RenderCadenceCompositor::LogWarning("app", "DeckLink input mode was not resolved; runtime shaders will use fallback input until a real input edge is available.");
|
||||
}
|
||||
auto inputBackend = RenderCadenceCompositor::StartVideoInputBackend(
|
||||
appConfig,
|
||||
inputMailbox,
|
||||
inputMailboxConfig,
|
||||
inputVideoMode,
|
||||
inputVideoModeResolved);
|
||||
|
||||
RenderThread::Config renderConfig;
|
||||
renderConfig.width = frameExchangeConfig.width;
|
||||
@@ -203,17 +145,22 @@ int main(int argc, char** argv)
|
||||
|
||||
RenderThread renderThread(frameExchange, &inputMailbox, renderConfig);
|
||||
|
||||
RenderCadenceCompositor::RenderCadenceApp<RenderThread, SystemFrameExchange> app(renderThread, frameExchange, appConfig);
|
||||
app.SetVideoInputMetricsProvider([&deckLinkInput]() {
|
||||
return deckLinkInput.Metrics();
|
||||
auto outputBackend = RenderCadenceCompositor::CreateVideoOutputBackend(appConfig);
|
||||
RenderCadenceCompositor::RenderCadenceApp<RenderThread, SystemFrameExchange> app(
|
||||
renderThread,
|
||||
frameExchange,
|
||||
appConfig,
|
||||
std::move(outputBackend));
|
||||
app.SetVideoInputMetricsProvider([inputBackend = inputBackend.get()]() {
|
||||
return inputBackend ? inputBackend->Metrics() : RenderCadenceCompositor::VideoInputEdgeMetrics();
|
||||
});
|
||||
|
||||
std::string error;
|
||||
if (!app.Start(error))
|
||||
{
|
||||
RenderCadenceCompositor::LogError("app", "RenderCadenceCompositor start failed: " + error);
|
||||
if (deckLinkInputStarted)
|
||||
deckLinkInputThread.Stop();
|
||||
if (inputBackend)
|
||||
inputBackend->Stop();
|
||||
RenderCadenceCompositor::Logger::Instance().Stop();
|
||||
return 1;
|
||||
}
|
||||
@@ -221,8 +168,8 @@ int main(int argc, char** argv)
|
||||
std::string line;
|
||||
std::getline(std::cin, line);
|
||||
app.Stop();
|
||||
if (deckLinkInputStarted)
|
||||
deckLinkInputThread.Stop();
|
||||
if (inputBackend)
|
||||
inputBackend->Stop();
|
||||
RenderCadenceCompositor::Log("app", "RenderCadenceCompositor stopped.");
|
||||
RenderCadenceCompositor::Logger::Instance().Stop();
|
||||
return 0;
|
||||
|
||||
@@ -26,6 +26,8 @@ AppConfig DefaultAppConfig()
|
||||
config.inputFrameRate = "59.94";
|
||||
config.outputVideoFormat = "1080p";
|
||||
config.outputFrameRate = "59.94";
|
||||
config.videoInputBackend = "decklink";
|
||||
config.videoOutputBackend = "decklink";
|
||||
config.autoReload = true;
|
||||
config.maxTemporalHistoryFrames = 12;
|
||||
config.previewEnabled = false;
|
||||
|
||||
@@ -28,6 +28,8 @@ struct AppConfig
|
||||
std::string inputFrameRate = "59.94";
|
||||
std::string outputVideoFormat = "1080p";
|
||||
std::string outputFrameRate = "59.94";
|
||||
std::string videoInputBackend = "decklink";
|
||||
std::string videoOutputBackend = "decklink";
|
||||
bool autoReload = true;
|
||||
std::size_t maxTemporalHistoryFrames = 12;
|
||||
bool previewEnabled = false;
|
||||
|
||||
@@ -128,6 +128,8 @@ bool AppConfigProvider::Load(const std::filesystem::path& path, std::string& err
|
||||
ApplyString(root, "inputFrameRate", mConfig.inputFrameRate);
|
||||
ApplyString(root, "outputVideoFormat", mConfig.outputVideoFormat);
|
||||
ApplyString(root, "outputFrameRate", mConfig.outputFrameRate);
|
||||
ApplyString(root, "videoInputBackend", mConfig.videoInputBackend);
|
||||
ApplyString(root, "videoOutputBackend", mConfig.videoOutputBackend);
|
||||
ApplyBool(root, "autoReload", mConfig.autoReload);
|
||||
ApplySize(root, "maxTemporalHistoryFrames", mConfig.maxTemporalHistoryFrames);
|
||||
ApplyBool(root, "previewEnabled", mConfig.previewEnabled);
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "../control/RuntimeStateJson.h"
|
||||
#include "../preview/PreviewWindowThread.h"
|
||||
#include "../telemetry/TelemetryHealthMonitor.h"
|
||||
#include "DeckLinkOutput.h"
|
||||
#include "VideoIOEdges.h"
|
||||
#include "VideoOutputThread.h"
|
||||
|
||||
@@ -53,11 +52,15 @@ template <typename RenderThread, typename SystemFrameExchange>
|
||||
class RenderCadenceApp
|
||||
{
|
||||
public:
|
||||
RenderCadenceApp(RenderThread& renderThread, SystemFrameExchange& frameExchange, AppConfig config = DefaultAppConfig()) :
|
||||
RenderCadenceApp(
|
||||
RenderThread& renderThread,
|
||||
SystemFrameExchange& frameExchange,
|
||||
AppConfig config,
|
||||
std::unique_ptr<IVideoOutputEdge> output) :
|
||||
mRenderThread(renderThread),
|
||||
mFrameExchange(frameExchange),
|
||||
mConfig(config),
|
||||
mOutput(std::make_unique<DeckLinkOutput>()),
|
||||
mOutput(std::move(output)),
|
||||
mOutputThread(*mOutput, mFrameExchange, VideoOutputThreadConfig{
|
||||
mConfig.outputThread.targetBufferedFrames,
|
||||
mConfig.outputThread.idleSleep
|
||||
@@ -134,8 +137,16 @@ public:
|
||||
private:
|
||||
void StartOptionalVideoOutput()
|
||||
{
|
||||
if (mConfig.videoOutputBackend == "none")
|
||||
{
|
||||
mVideoOutputEnabled = false;
|
||||
mVideoOutputStatus = "Video output backend disabled by config.";
|
||||
Log("app", mVideoOutputStatus);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string outputError;
|
||||
Log("app", "Initializing optional DeckLink output.");
|
||||
Log("app", "Initializing optional video output backend: " + mConfig.videoOutputBackend + ".");
|
||||
if (!mOutput->Initialize(
|
||||
mConfig.deckLink,
|
||||
[this](const VideoIOCompletion& completion) {
|
||||
@@ -143,7 +154,7 @@ private:
|
||||
},
|
||||
outputError))
|
||||
{
|
||||
DisableVideoOutput("DeckLink output unavailable: " + outputError);
|
||||
DisableVideoOutput("Video output unavailable: " + outputError);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -154,26 +165,26 @@ private:
|
||||
return;
|
||||
}
|
||||
|
||||
Log("app", "Waiting for DeckLink preroll frames.");
|
||||
Log("app", "Waiting for video output preroll frames.");
|
||||
if (!WaitForPreroll())
|
||||
{
|
||||
DisableVideoOutput("Timed out waiting for DeckLink preroll frames.");
|
||||
return;
|
||||
}
|
||||
|
||||
Log("app", "Starting DeckLink scheduled playback.");
|
||||
Log("app", "Starting scheduled video playback.");
|
||||
if (!mOutput->StartScheduledPlayback(outputError))
|
||||
{
|
||||
DisableVideoOutput("DeckLink scheduled playback failed: " + outputError);
|
||||
DisableVideoOutput("Scheduled video playback failed: " + outputError);
|
||||
return;
|
||||
}
|
||||
|
||||
mVideoOutputEnabled = true;
|
||||
mVideoOutputStatus = "DeckLink scheduled output running.";
|
||||
mVideoOutputStatus = mConfig.videoOutputBackend + " scheduled output running.";
|
||||
Log("app", mVideoOutputStatus);
|
||||
Log(
|
||||
"app",
|
||||
"DeckLink output mode: " + mOutput->State().outputDisplayModeName +
|
||||
"Video output mode: " + mOutput->State().outputDisplayModeName +
|
||||
", frame budget " + std::to_string(mOutput->State().frameBudgetMilliseconds) + " ms.");
|
||||
}
|
||||
|
||||
@@ -313,6 +324,6 @@ private:
|
||||
uint64_t mLastInputCapturedFrames = 0;
|
||||
bool mStarted = false;
|
||||
bool mVideoOutputEnabled = false;
|
||||
std::string mVideoOutputStatus = "DeckLink output not started.";
|
||||
std::string mVideoOutputStatus = "Video output not started.";
|
||||
};
|
||||
}
|
||||
|
||||
229
src/app/VideoBackendFactory.cpp
Normal file
229
src/app/VideoBackendFactory.cpp
Normal file
@@ -0,0 +1,229 @@
|
||||
#include "VideoBackendFactory.h"
|
||||
|
||||
#include "logging/Logger.h"
|
||||
#include "video/core/VideoIOFormat.h"
|
||||
#include "video/decklink/DeckLinkInput.h"
|
||||
#include "video/decklink/DeckLinkInputThread.h"
|
||||
#include "video/decklink/DeckLinkOutput.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
namespace RenderCadenceCompositor
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr std::size_t kInputStartupBufferedFrames = 3;
|
||||
constexpr std::chrono::milliseconds kInputWarmupTimeout(1000);
|
||||
|
||||
std::string NormalizeBackendName(std::string name)
|
||||
{
|
||||
std::transform(name.begin(), name.end(), name.begin(), [](unsigned char ch) {
|
||||
return static_cast<char>(std::tolower(ch));
|
||||
});
|
||||
return name;
|
||||
}
|
||||
|
||||
bool IsDeckLinkBackend(const std::string& name)
|
||||
{
|
||||
return NormalizeBackendName(name) == "decklink";
|
||||
}
|
||||
|
||||
bool IsNoneBackend(const std::string& name)
|
||||
{
|
||||
const std::string normalized = NormalizeBackendName(name);
|
||||
return normalized == "none" || normalized == "disabled" || normalized == "off";
|
||||
}
|
||||
|
||||
bool WaitForInputWarmup(InputFrameMailbox& mailbox, std::size_t targetReadyFrames, std::chrono::milliseconds timeout)
|
||||
{
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
while (std::chrono::steady_clock::now() - start < timeout)
|
||||
{
|
||||
const InputFrameMailboxMetrics metrics = mailbox.Metrics();
|
||||
if (metrics.readyCount >= targetReadyFrames)
|
||||
return true;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
class DisabledVideoOutputEdge final : public IVideoOutputEdge
|
||||
{
|
||||
public:
|
||||
explicit DisabledVideoOutputEdge(std::string reason) :
|
||||
mReason(std::move(reason))
|
||||
{
|
||||
mState.statusMessage = mReason;
|
||||
}
|
||||
|
||||
bool Initialize(const VideoOutputEdgeConfig&, CompletionCallback, std::string& error) override
|
||||
{
|
||||
error = mReason;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StartScheduledPlayback(std::string& error) override
|
||||
{
|
||||
error = mReason;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ScheduleFrame(const VideoIOOutputFrame&) override { return false; }
|
||||
void Stop() override {}
|
||||
void ReleaseResources() override {}
|
||||
const VideoIOState& State() const override { return mState; }
|
||||
VideoOutputEdgeMetrics Metrics() const override { return VideoOutputEdgeMetrics(); }
|
||||
|
||||
private:
|
||||
std::string mReason;
|
||||
VideoIOState mState;
|
||||
};
|
||||
|
||||
class NoInputBackendSession final : public VideoInputBackendSession
|
||||
{
|
||||
public:
|
||||
explicit NoInputBackendSession(std::string backendName) :
|
||||
mBackendName(std::move(backendName))
|
||||
{
|
||||
}
|
||||
|
||||
void Stop() override {}
|
||||
bool Started() const override { return false; }
|
||||
VideoInputEdgeMetrics Metrics() const override { return VideoInputEdgeMetrics(); }
|
||||
const std::string& BackendName() const override { return mBackendName; }
|
||||
|
||||
private:
|
||||
std::string mBackendName;
|
||||
};
|
||||
|
||||
class DeckLinkInputBackendSession final : public VideoInputBackendSession
|
||||
{
|
||||
public:
|
||||
DeckLinkInputBackendSession(InputFrameMailbox& mailbox) :
|
||||
mInput(mailbox),
|
||||
mThread(mInput)
|
||||
{
|
||||
}
|
||||
|
||||
~DeckLinkInputBackendSession() override
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool Start(
|
||||
InputFrameMailbox& mailbox,
|
||||
InputFrameMailboxConfig& mailboxConfig,
|
||||
const VideoFormat& inputVideoMode,
|
||||
std::string& error)
|
||||
{
|
||||
DeckLinkInputConfig inputConfig;
|
||||
inputConfig.videoFormat = inputVideoMode;
|
||||
if (mInput.Initialize(inputConfig, error))
|
||||
{
|
||||
mailboxConfig.pixelFormat = mInput.CapturePixelFormat();
|
||||
mailboxConfig.rowBytes = VideoIORowBytes(mailboxConfig.pixelFormat, mailboxConfig.width);
|
||||
mailbox.Configure(mailboxConfig);
|
||||
}
|
||||
|
||||
if (!mInput.IsInitialized() || !mThread.Start(error))
|
||||
{
|
||||
mInput.ReleaseResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
mStarted = true;
|
||||
Log("app", "DeckLink input edge started for " + inputVideoMode.displayName + ".");
|
||||
Log("app", "Waiting for DeckLink input warmup frames.");
|
||||
if (WaitForInputWarmup(mailbox, kInputStartupBufferedFrames, kInputWarmupTimeout))
|
||||
{
|
||||
const InputFrameMailboxMetrics metrics = mailbox.Metrics();
|
||||
Log(
|
||||
"app",
|
||||
"DeckLink input warmup complete. ready=" + std::to_string(metrics.readyCount) +
|
||||
" submitted=" + std::to_string(metrics.submittedFrames) + ".");
|
||||
}
|
||||
else
|
||||
{
|
||||
const InputFrameMailboxMetrics metrics = mailbox.Metrics();
|
||||
LogWarning(
|
||||
"app",
|
||||
"DeckLink input warmup timed out; starting render cadence with current input buffer. ready=" +
|
||||
std::to_string(metrics.readyCount) + " submitted=" + std::to_string(metrics.submittedFrames) + ".");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Stop() override
|
||||
{
|
||||
if (mStarted)
|
||||
mThread.Stop();
|
||||
mInput.ReleaseResources();
|
||||
mStarted = false;
|
||||
}
|
||||
|
||||
bool Started() const override { return mStarted; }
|
||||
VideoInputEdgeMetrics Metrics() const override { return mInput.Metrics(); }
|
||||
const std::string& BackendName() const override { return mBackendName; }
|
||||
|
||||
private:
|
||||
std::string mBackendName = "decklink";
|
||||
DeckLinkInput mInput;
|
||||
DeckLinkInputThread mThread;
|
||||
bool mStarted = false;
|
||||
};
|
||||
}
|
||||
|
||||
bool VideoBackendsRequireCom(const AppConfig& config)
|
||||
{
|
||||
return IsDeckLinkBackend(config.videoInputBackend) || IsDeckLinkBackend(config.videoOutputBackend);
|
||||
}
|
||||
|
||||
std::unique_ptr<IVideoOutputEdge> CreateVideoOutputBackend(const AppConfig& config)
|
||||
{
|
||||
if (IsDeckLinkBackend(config.videoOutputBackend))
|
||||
return std::make_unique<DeckLinkOutput>();
|
||||
|
||||
if (IsNoneBackend(config.videoOutputBackend))
|
||||
return std::make_unique<DisabledVideoOutputEdge>("Video output backend is disabled by config.");
|
||||
|
||||
return std::make_unique<DisabledVideoOutputEdge>("Unsupported videoOutputBackend: " + config.videoOutputBackend);
|
||||
}
|
||||
|
||||
std::unique_ptr<VideoInputBackendSession> StartVideoInputBackend(
|
||||
const AppConfig& config,
|
||||
InputFrameMailbox& mailbox,
|
||||
InputFrameMailboxConfig& mailboxConfig,
|
||||
const VideoFormat& inputVideoMode,
|
||||
bool inputVideoModeResolved)
|
||||
{
|
||||
if (IsNoneBackend(config.videoInputBackend))
|
||||
{
|
||||
Log("app", "Video input backend disabled by config.");
|
||||
return std::make_unique<NoInputBackendSession>("none");
|
||||
}
|
||||
|
||||
if (!IsDeckLinkBackend(config.videoInputBackend))
|
||||
{
|
||||
LogWarning("app", "Unsupported videoInputBackend '" + config.videoInputBackend + "'; runtime shaders will use fallback input.");
|
||||
return std::make_unique<NoInputBackendSession>(config.videoInputBackend);
|
||||
}
|
||||
|
||||
if (!inputVideoModeResolved)
|
||||
{
|
||||
LogWarning("app", "DeckLink input mode was not resolved; runtime shaders will use fallback input until a real input edge is available.");
|
||||
return std::make_unique<NoInputBackendSession>("decklink");
|
||||
}
|
||||
|
||||
auto session = std::make_unique<DeckLinkInputBackendSession>(mailbox);
|
||||
std::string error;
|
||||
if (!session->Start(mailbox, mailboxConfig, inputVideoMode, error))
|
||||
{
|
||||
LogWarning("app", "DeckLink input edge unavailable; runtime shaders will use fallback input until a real input edge is available. " + error);
|
||||
return std::make_unique<NoInputBackendSession>("decklink");
|
||||
}
|
||||
return session;
|
||||
}
|
||||
}
|
||||
31
src/app/VideoBackendFactory.h
Normal file
31
src/app/VideoBackendFactory.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppConfig.h"
|
||||
#include "frames/InputFrameMailbox.h"
|
||||
#include "video/core/VideoIOEdges.h"
|
||||
#include "video/core/VideoMode.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace RenderCadenceCompositor
|
||||
{
|
||||
class VideoInputBackendSession
|
||||
{
|
||||
public:
|
||||
virtual ~VideoInputBackendSession() = default;
|
||||
virtual void Stop() = 0;
|
||||
virtual bool Started() const = 0;
|
||||
virtual VideoInputEdgeMetrics Metrics() const = 0;
|
||||
virtual const std::string& BackendName() const = 0;
|
||||
};
|
||||
|
||||
bool VideoBackendsRequireCom(const AppConfig& config);
|
||||
std::unique_ptr<IVideoOutputEdge> CreateVideoOutputBackend(const AppConfig& config);
|
||||
std::unique_ptr<VideoInputBackendSession> StartVideoInputBackend(
|
||||
const AppConfig& config,
|
||||
InputFrameMailbox& mailbox,
|
||||
InputFrameMailboxConfig& mailboxConfig,
|
||||
const VideoFormat& inputVideoMode,
|
||||
bool inputVideoModeResolved);
|
||||
}
|
||||
@@ -251,6 +251,8 @@ inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
|
||||
writer.KeyUInt("maxTemporalHistoryFrames", static_cast<uint64_t>(input.config.maxTemporalHistoryFrames));
|
||||
writer.KeyDouble("previewFps", input.config.previewFps);
|
||||
writer.KeyBool("enableExternalKeying", input.config.deckLink.externalKeyingEnabled);
|
||||
writer.KeyString("videoInputBackend", input.config.videoInputBackend);
|
||||
writer.KeyString("videoOutputBackend", input.config.videoOutputBackend);
|
||||
writer.KeyString("inputVideoFormat", input.config.inputVideoFormat);
|
||||
writer.KeyString("inputFrameRate", input.config.inputFrameRate);
|
||||
writer.KeyString("outputVideoFormat", input.config.outputVideoFormat);
|
||||
|
||||
Reference in New Issue
Block a user