#include "app/AppConfig.h" #include "app/AppConfigProvider.h" #include "app/RenderCadenceApp.h" #include "frames/InputFrameMailbox.h" #include "frames/SystemFrameExchange.h" #include "logging/Logger.h" #include "render/RenderThread.h" #include "video/DeckLinkInput.h" #include "video/DeckLinkInputThread.h" #include "DeckLinkDisplayMode.h" #include "VideoIOFormat.h" #include #include #include #include #include #include namespace { 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; }; 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) { 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, system-memory exchange, DeckLink scheduled output, and telemetry. Press Enter to stop."); RenderCadenceCompositor::Log("app", "Loaded config from " + configProvider.SourcePath().string()); ComInitGuard com; if (!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; RenderCadenceCompositor::VideoFormatDimensions( appConfig.outputVideoFormat, frameExchangeConfig.width, frameExchangeConfig.height); frameExchangeConfig.pixelFormat = VideoIOPixelFormat::Bgra8; frameExchangeConfig.rowBytes = VideoIORowBytes(frameExchangeConfig.pixelFormat, frameExchangeConfig.width); frameExchangeConfig.capacity = 12; SystemFrameExchange frameExchange(frameExchangeConfig); InputFrameMailboxConfig inputMailboxConfig; RenderCadenceCompositor::VideoFormatDimensions( appConfig.inputVideoFormat, inputMailboxConfig.width, inputMailboxConfig.height); inputMailboxConfig.pixelFormat = VideoIOPixelFormat::Bgra8; inputMailboxConfig.rowBytes = VideoIORowBytes(inputMailboxConfig.pixelFormat, inputMailboxConfig.width); inputMailboxConfig.capacity = 4; InputFrameMailbox inputMailbox(inputMailboxConfig); VideoFormat inputVideoMode; std::string inputVideoModeError; const bool inputVideoModeResolved = ResolveConfiguredVideoFormat(appConfig.inputVideoFormat, appConfig.inputFrameRate, inputVideoMode); if (!inputVideoModeResolved) { inputVideoModeError = "Unsupported DeckLink inputVideoFormat/inputFrameRate in config/runtime-host.json: " + appConfig.inputVideoFormat + " / " + appConfig.inputFrameRate; RenderCadenceCompositor::LogWarning("app", inputVideoModeError); } 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 = 2; 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."); } RenderThread::Config renderConfig; renderConfig.width = frameExchangeConfig.width; renderConfig.height = frameExchangeConfig.height; renderConfig.frameDurationMilliseconds = RenderCadenceCompositor::FrameDurationMillisecondsFromRateString(appConfig.outputFrameRate); renderConfig.pboDepth = 6; RenderThread renderThread(frameExchange, &inputMailbox, renderConfig); RenderCadenceCompositor::RenderCadenceApp app(renderThread, frameExchange, appConfig); app.SetDeckLinkInputMetricsProvider([&deckLinkInput]() { return deckLinkInput.Metrics(); }); std::string error; if (!app.Start(error)) { RenderCadenceCompositor::LogError("app", "RenderCadenceCompositor start failed: " + error); if (deckLinkInputStarted) deckLinkInputThread.Stop(); RenderCadenceCompositor::Logger::Instance().Stop(); return 1; } std::string line; std::getline(std::cin, line); app.Stop(); if (deckLinkInputStarted) deckLinkInputThread.Stop(); RenderCadenceCompositor::Log("app", "RenderCadenceCompositor stopped."); RenderCadenceCompositor::Logger::Instance().Stop(); return 0; }