#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/core/VideoIOFormat.h" #include "video/core/VideoMode.h" #include #include #include #include #include namespace { constexpr std::size_t kDeckLinkTargetBufferedFrames = 4; constexpr std::size_t kReadbackDepth = 6; constexpr std::size_t kWritableOutputReserveFrames = kReadbackDepth + 2; class ComInitGuard { public: ~ComInitGuard() { if (mInitialized) CoUninitialize(); } bool Initialize() { const HRESULT result = CoInitialize(nullptr); mInitialized = SUCCEEDED(result); mResult = result; return mInitialized; } HRESULT Result() const { return mResult; } private: bool mInitialized = false; HRESULT mResult = S_OK; }; } int main(int argc, char** argv) { RenderCadenceCompositor::AppConfigProvider configProvider; std::string configError; if (!configProvider.LoadDefault(configError)) { RenderCadenceCompositor::Logger::Instance().Start(RenderCadenceCompositor::DefaultAppConfig().logging); RenderCadenceCompositor::LogError("app", "Config load failed: " + configError); RenderCadenceCompositor::Logger::Instance().Stop(); return 1; } configProvider.ApplyCommandLine(argc, argv); RenderCadenceCompositor::AppConfig appConfig = configProvider.Config(); RenderCadenceCompositor::Logger::Instance().Start(appConfig.logging); RenderCadenceCompositor::Log( "app", "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 (RenderCadenceCompositor::VideoBackendsRequireCom(appConfig) && !com.Initialize()) { std::ostringstream message; message << "COM initialization failed: 0x" << std::hex << com.Result(); RenderCadenceCompositor::LogError("app", message.str()); RenderCadenceCompositor::Logger::Instance().Stop(); return 1; } SystemFrameExchangeConfig frameExchangeConfig; VideoFormatDimensions( appConfig.output.resolution, frameExchangeConfig.width, frameExchangeConfig.height); frameExchangeConfig.pixelFormat = VideoIOPixelFormat::Bgra8; frameExchangeConfig.rowBytes = VideoIORowBytes(frameExchangeConfig.pixelFormat, frameExchangeConfig.width); frameExchangeConfig.capacity = appConfig.warmupCompletedFrames + kDeckLinkTargetBufferedFrames + kWritableOutputReserveFrames; frameExchangeConfig.maxCompletedFrames = appConfig.warmupCompletedFrames; SystemFrameExchange frameExchange(frameExchangeConfig); InputFrameMailboxConfig inputMailboxConfig; VideoFormatDimensions( appConfig.input.resolution, inputMailboxConfig.width, inputMailboxConfig.height); inputMailboxConfig.pixelFormat = VideoIOPixelFormat::Bgra8; inputMailboxConfig.rowBytes = VideoIORowBytes(inputMailboxConfig.pixelFormat, inputMailboxConfig.width); inputMailboxConfig.capacity = 4; inputMailboxConfig.maxReadyFrames = 3; InputFrameMailbox inputMailbox(inputMailboxConfig); VideoFormat inputVideoMode; VideoFormat outputVideoMode; std::string inputVideoModeError; const bool inputVideoModeResolved = ResolveConfiguredVideoFormat(appConfig.input.resolution, appConfig.input.frameRate, inputVideoMode); const bool outputVideoModeResolved = ResolveConfiguredVideoFormat(appConfig.output.resolution, appConfig.output.frameRate, outputVideoMode); if (!inputVideoModeResolved) { inputVideoModeError = "Unsupported input resolution/frameRate in config/runtime-host.json: " + appConfig.input.resolution + " / " + appConfig.input.frameRate; RenderCadenceCompositor::LogWarning("app", inputVideoModeError); } if (!outputVideoModeResolved) { RenderCadenceCompositor::LogWarning( "app", "Unsupported output resolution/frameRate in config/runtime-host.json; render cadence will use parsed frame-rate fallback: " + appConfig.output.resolution + " / " + appConfig.output.frameRate); } else { appConfig.output.videoMode = outputVideoMode; } auto inputBackend = RenderCadenceCompositor::StartVideoInputBackend( appConfig, inputMailbox, inputMailboxConfig, inputVideoMode, inputVideoModeResolved); RenderThread::Config renderConfig; renderConfig.width = frameExchangeConfig.width; renderConfig.height = frameExchangeConfig.height; const double fallbackFrameDurationMilliseconds = FrameDurationMillisecondsFromRateString(appConfig.output.frameRate); renderConfig.frameDurationMilliseconds = outputVideoModeResolved ? outputVideoMode.frameDurationMilliseconds : fallbackFrameDurationMilliseconds; renderConfig.pboDepth = kReadbackDepth; RenderThread renderThread(frameExchange, &inputMailbox, renderConfig); auto outputBackend = RenderCadenceCompositor::CreateVideoOutputBackend(appConfig); RenderCadenceCompositor::RenderCadenceApp 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 (inputBackend) inputBackend->Stop(); RenderCadenceCompositor::Logger::Instance().Stop(); return 1; } std::string line; std::getline(std::cin, line); app.Stop(); if (inputBackend) inputBackend->Stop(); RenderCadenceCompositor::Log("app", "RenderCadenceCompositor stopped."); RenderCadenceCompositor::Logger::Instance().Stop(); return 0; }