http
This commit is contained in:
@@ -15,6 +15,20 @@ AppConfig DefaultAppConfig()
|
||||
config.logging.writeToFile = true;
|
||||
config.logging.filePath = "logs/render-cadence-compositor.log";
|
||||
config.logging.maxQueuedMessages = 1024;
|
||||
config.http.preferredPort = 8080;
|
||||
config.http.portSearchCount = 20;
|
||||
config.http.idleSleep = std::chrono::milliseconds(10);
|
||||
config.shaderLibrary = "shaders";
|
||||
config.oscBindAddress = "0.0.0.0";
|
||||
config.oscPort = 9000;
|
||||
config.oscSmoothing = 0.18;
|
||||
config.inputVideoFormat = "1080p";
|
||||
config.inputFrameRate = "59.94";
|
||||
config.outputVideoFormat = "1080p";
|
||||
config.outputFrameRate = "59.94";
|
||||
config.autoReload = true;
|
||||
config.maxTemporalHistoryFrames = 12;
|
||||
config.previewFps = 30.0;
|
||||
config.warmupCompletedFrames = 4;
|
||||
config.warmupTimeout = std::chrono::seconds(3);
|
||||
config.prerollTimeout = std::chrono::seconds(3);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "../control/HttpControlServer.h"
|
||||
#include "../logging/Logger.h"
|
||||
#include "../telemetry/TelemetryPrinter.h"
|
||||
#include "../video/DeckLinkOutput.h"
|
||||
@@ -17,6 +18,18 @@ struct AppConfig
|
||||
DeckLinkOutputThreadConfig outputThread;
|
||||
TelemetryPrinterConfig telemetry;
|
||||
LoggerConfig logging;
|
||||
HttpControlServerConfig http;
|
||||
std::string shaderLibrary = "shaders";
|
||||
std::string oscBindAddress = "0.0.0.0";
|
||||
unsigned short oscPort = 9000;
|
||||
double oscSmoothing = 0.18;
|
||||
std::string inputVideoFormat = "1080p";
|
||||
std::string inputFrameRate = "59.94";
|
||||
std::string outputVideoFormat = "1080p";
|
||||
std::string outputFrameRate = "59.94";
|
||||
bool autoReload = true;
|
||||
std::size_t maxTemporalHistoryFrames = 12;
|
||||
double previewFps = 30.0;
|
||||
std::size_t warmupCompletedFrames = 4;
|
||||
std::chrono::milliseconds warmupTimeout = std::chrono::seconds(3);
|
||||
std::chrono::milliseconds prerollTimeout = std::chrono::seconds(3);
|
||||
|
||||
186
apps/RenderCadenceCompositor/app/AppConfigProvider.cpp
Normal file
186
apps/RenderCadenceCompositor/app/AppConfigProvider.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
#include "AppConfigProvider.h"
|
||||
|
||||
#include "RuntimeJson.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace RenderCadenceCompositor
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::string ReadTextFile(const std::filesystem::path& path, std::string& error)
|
||||
{
|
||||
std::ifstream input(path, std::ios::binary);
|
||||
if (!input)
|
||||
{
|
||||
error = "Could not open config file: " + path.string();
|
||||
return std::string();
|
||||
}
|
||||
|
||||
std::ostringstream buffer;
|
||||
buffer << input.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
const JsonValue* Find(const JsonValue& root, const char* key)
|
||||
{
|
||||
return root.find(key);
|
||||
}
|
||||
|
||||
void ApplyString(const JsonValue& root, const char* key, std::string& target)
|
||||
{
|
||||
const JsonValue* value = Find(root, key);
|
||||
if (value && value->isString())
|
||||
target = value->asString();
|
||||
}
|
||||
|
||||
void ApplyBool(const JsonValue& root, const char* key, bool& target)
|
||||
{
|
||||
const JsonValue* value = Find(root, key);
|
||||
if (value && value->isBoolean())
|
||||
target = value->asBoolean();
|
||||
}
|
||||
|
||||
void ApplyDouble(const JsonValue& root, const char* key, double& target)
|
||||
{
|
||||
const JsonValue* value = Find(root, key);
|
||||
if (value && value->isNumber())
|
||||
target = value->asNumber();
|
||||
}
|
||||
|
||||
void ApplySize(const JsonValue& root, const char* key, std::size_t& target)
|
||||
{
|
||||
const JsonValue* value = Find(root, key);
|
||||
if (value && value->isNumber() && value->asNumber() >= 0.0)
|
||||
target = static_cast<std::size_t>(value->asNumber());
|
||||
}
|
||||
|
||||
void ApplyPort(const JsonValue& root, const char* key, unsigned short& target)
|
||||
{
|
||||
const JsonValue* value = Find(root, key);
|
||||
if (!value || !value->isNumber())
|
||||
return;
|
||||
|
||||
const double port = value->asNumber();
|
||||
if (port >= 1.0 && port <= 65535.0)
|
||||
target = static_cast<unsigned short>(port);
|
||||
}
|
||||
}
|
||||
|
||||
AppConfigProvider::AppConfigProvider() :
|
||||
mConfig(DefaultAppConfig())
|
||||
{
|
||||
}
|
||||
|
||||
bool AppConfigProvider::Load(const std::filesystem::path& path, std::string& error)
|
||||
{
|
||||
mConfig = DefaultAppConfig();
|
||||
mSourcePath = path;
|
||||
mLoadedFromFile = false;
|
||||
|
||||
std::string fileError;
|
||||
const std::string text = ReadTextFile(path, fileError);
|
||||
if (!fileError.empty())
|
||||
{
|
||||
error = fileError;
|
||||
return false;
|
||||
}
|
||||
|
||||
JsonValue root;
|
||||
std::string parseError;
|
||||
if (!ParseJson(text, root, parseError) || !root.isObject())
|
||||
{
|
||||
error = parseError.empty() ? "Config root must be a JSON object." : parseError;
|
||||
return false;
|
||||
}
|
||||
|
||||
ApplyString(root, "shaderLibrary", mConfig.shaderLibrary);
|
||||
ApplyPort(root, "serverPort", mConfig.http.preferredPort);
|
||||
ApplyString(root, "oscBindAddress", mConfig.oscBindAddress);
|
||||
ApplyPort(root, "oscPort", mConfig.oscPort);
|
||||
ApplyDouble(root, "oscSmoothing", mConfig.oscSmoothing);
|
||||
ApplyString(root, "inputVideoFormat", mConfig.inputVideoFormat);
|
||||
ApplyString(root, "inputFrameRate", mConfig.inputFrameRate);
|
||||
ApplyString(root, "outputVideoFormat", mConfig.outputVideoFormat);
|
||||
ApplyString(root, "outputFrameRate", mConfig.outputFrameRate);
|
||||
ApplyBool(root, "autoReload", mConfig.autoReload);
|
||||
ApplySize(root, "maxTemporalHistoryFrames", mConfig.maxTemporalHistoryFrames);
|
||||
ApplyDouble(root, "previewFps", mConfig.previewFps);
|
||||
ApplyBool(root, "enableExternalKeying", mConfig.deckLink.externalKeyingEnabled);
|
||||
|
||||
mLoadedFromFile = true;
|
||||
error.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
void AppConfigProvider::ApplyCommandLine(int argc, char** argv)
|
||||
{
|
||||
for (int index = 1; index < argc; ++index)
|
||||
{
|
||||
const std::string argument = argv[index];
|
||||
if (argument == "--shader" && index + 1 < argc)
|
||||
{
|
||||
mConfig.runtimeShaderId = argv[++index];
|
||||
continue;
|
||||
}
|
||||
if (argument == "--no-shader")
|
||||
{
|
||||
mConfig.runtimeShaderId.clear();
|
||||
continue;
|
||||
}
|
||||
if (argument == "--port" && index + 1 < argc)
|
||||
{
|
||||
const int port = std::atoi(argv[++index]);
|
||||
if (port >= 1 && port <= 65535)
|
||||
mConfig.http.preferredPort = static_cast<unsigned short>(port);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double FrameDurationMillisecondsFromRateString(const std::string& rateText, double fallbackRate)
|
||||
{
|
||||
double rate = fallbackRate;
|
||||
try
|
||||
{
|
||||
rate = std::stod(rateText);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
rate = fallbackRate;
|
||||
}
|
||||
|
||||
if (rate <= 0.0)
|
||||
rate = fallbackRate;
|
||||
return 1000.0 / rate;
|
||||
}
|
||||
|
||||
void VideoFormatDimensions(const std::string& formatName, unsigned& width, unsigned& height)
|
||||
{
|
||||
std::string normalized = formatName;
|
||||
std::transform(normalized.begin(), normalized.end(), normalized.begin(), [](unsigned char character) {
|
||||
return static_cast<char>(std::tolower(character));
|
||||
});
|
||||
|
||||
if (normalized == "720p")
|
||||
{
|
||||
width = 1280;
|
||||
height = 720;
|
||||
return;
|
||||
}
|
||||
|
||||
if (normalized == "2160p" || normalized == "4k" || normalized == "uhd")
|
||||
{
|
||||
width = 3840;
|
||||
height = 2160;
|
||||
return;
|
||||
}
|
||||
|
||||
width = 1920;
|
||||
height = 1080;
|
||||
}
|
||||
}
|
||||
30
apps/RenderCadenceCompositor/app/AppConfigProvider.h
Normal file
30
apps/RenderCadenceCompositor/app/AppConfigProvider.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppConfig.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace RenderCadenceCompositor
|
||||
{
|
||||
class AppConfigProvider
|
||||
{
|
||||
public:
|
||||
AppConfigProvider();
|
||||
|
||||
bool Load(const std::filesystem::path& path, std::string& error);
|
||||
void ApplyCommandLine(int argc, char** argv);
|
||||
|
||||
const AppConfig& Config() const { return mConfig; }
|
||||
const std::filesystem::path& SourcePath() const { return mSourcePath; }
|
||||
bool LoadedFromFile() const { return mLoadedFromFile; }
|
||||
|
||||
private:
|
||||
AppConfig mConfig;
|
||||
std::filesystem::path mSourcePath;
|
||||
bool mLoadedFromFile = false;
|
||||
};
|
||||
|
||||
double FrameDurationMillisecondsFromRateString(const std::string& rateText, double fallbackRate = 59.94);
|
||||
void VideoFormatDimensions(const std::string& formatName, unsigned& width, unsigned& height);
|
||||
}
|
||||
@@ -1,13 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppConfig.h"
|
||||
#include "AppConfigProvider.h"
|
||||
#include "../json/JsonWriter.h"
|
||||
#include "../logging/Logger.h"
|
||||
#include "../runtime/RuntimeShaderBridge.h"
|
||||
#include "../telemetry/CadenceTelemetryJson.h"
|
||||
#include "../telemetry/TelemetryPrinter.h"
|
||||
#include "../video/DeckLinkOutput.h"
|
||||
#include "../video/DeckLinkOutputThread.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
@@ -122,6 +126,7 @@ public:
|
||||
}
|
||||
|
||||
mTelemetry.Start(mFrameExchange, mOutput, mOutputThread, mRenderThread);
|
||||
StartHttpServer();
|
||||
Log("app", "RenderCadenceCompositor started.");
|
||||
mStarted = true;
|
||||
return true;
|
||||
@@ -129,6 +134,7 @@ public:
|
||||
|
||||
void Stop()
|
||||
{
|
||||
mHttpServer.Stop();
|
||||
mTelemetry.Stop();
|
||||
mOutputThread.Stop();
|
||||
mOutput.Stop();
|
||||
@@ -144,6 +150,85 @@ public:
|
||||
const DeckLinkOutput& Output() const { return mOutput; }
|
||||
|
||||
private:
|
||||
void StartHttpServer()
|
||||
{
|
||||
HttpControlServerCallbacks callbacks;
|
||||
callbacks.getStateJson = [this]() {
|
||||
return BuildStateJson();
|
||||
};
|
||||
|
||||
std::string error;
|
||||
if (!mHttpServer.Start(std::filesystem::current_path() / "docs", mConfig.http, callbacks, error))
|
||||
{
|
||||
LogWarning("http", "HTTP control server did not start: " + error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::string BuildStateJson()
|
||||
{
|
||||
CadenceTelemetrySnapshot telemetry = mHttpTelemetry.Sample(mFrameExchange, mOutput, mOutputThread, mRenderThread);
|
||||
|
||||
JsonWriter writer;
|
||||
writer.BeginObject();
|
||||
writer.Key("app");
|
||||
writer.BeginObject();
|
||||
writer.KeyUInt("serverPort", mHttpServer.Port());
|
||||
writer.KeyUInt("oscPort", mConfig.oscPort);
|
||||
writer.KeyString("oscBindAddress", mConfig.oscBindAddress);
|
||||
writer.KeyDouble("oscSmoothing", mConfig.oscSmoothing);
|
||||
writer.KeyBool("autoReload", mConfig.autoReload);
|
||||
writer.KeyUInt("maxTemporalHistoryFrames", static_cast<uint64_t>(mConfig.maxTemporalHistoryFrames));
|
||||
writer.KeyDouble("previewFps", mConfig.previewFps);
|
||||
writer.KeyBool("enableExternalKeying", mConfig.deckLink.externalKeyingEnabled);
|
||||
writer.KeyString("inputVideoFormat", mConfig.inputVideoFormat);
|
||||
writer.KeyString("inputFrameRate", mConfig.inputFrameRate);
|
||||
writer.KeyString("outputVideoFormat", mConfig.outputVideoFormat);
|
||||
writer.KeyString("outputFrameRate", mConfig.outputFrameRate);
|
||||
writer.EndObject();
|
||||
|
||||
writer.Key("runtime");
|
||||
writer.BeginObject();
|
||||
writer.KeyUInt("layerCount", 0);
|
||||
writer.KeyBool("compileSucceeded", true);
|
||||
writer.KeyString("compileMessage", "Runtime state is not ported into RenderCadenceCompositor yet.");
|
||||
writer.EndObject();
|
||||
|
||||
writer.KeyNull("video");
|
||||
writer.KeyNull("decklink");
|
||||
writer.KeyNull("videoIO");
|
||||
|
||||
writer.Key("performance");
|
||||
writer.BeginObject();
|
||||
writer.KeyDouble("frameBudgetMs", FrameDurationMillisecondsFromRateString(mConfig.outputFrameRate));
|
||||
writer.KeyNull("renderMs");
|
||||
writer.KeyNull("smoothedRenderMs");
|
||||
writer.KeyNull("budgetUsedPercent");
|
||||
writer.KeyNull("completionIntervalMs");
|
||||
writer.KeyNull("smoothedCompletionIntervalMs");
|
||||
writer.KeyNull("maxCompletionIntervalMs");
|
||||
writer.KeyUInt("lateFrameCount", telemetry.displayedLate);
|
||||
writer.KeyUInt("droppedFrameCount", telemetry.dropped);
|
||||
writer.KeyNull("flushedFrameCount");
|
||||
writer.Key("cadence");
|
||||
WriteCadenceTelemetryJson(writer, telemetry);
|
||||
writer.EndObject();
|
||||
|
||||
writer.KeyNull("backendPlayout");
|
||||
writer.KeyNull("runtimeEvents");
|
||||
writer.Key("shaders");
|
||||
writer.BeginArray();
|
||||
writer.EndArray();
|
||||
writer.Key("stackPresets");
|
||||
writer.BeginArray();
|
||||
writer.EndArray();
|
||||
writer.Key("layers");
|
||||
writer.BeginArray();
|
||||
writer.EndArray();
|
||||
writer.EndObject();
|
||||
return writer.StringValue();
|
||||
}
|
||||
|
||||
bool WaitForPreroll() const
|
||||
{
|
||||
const auto deadline = std::chrono::steady_clock::now() + mConfig.prerollTimeout;
|
||||
@@ -186,6 +271,8 @@ private:
|
||||
DeckLinkOutput mOutput;
|
||||
DeckLinkOutputThread<SystemFrameExchange> mOutputThread;
|
||||
TelemetryPrinter mTelemetry;
|
||||
CadenceTelemetry mHttpTelemetry;
|
||||
HttpControlServer mHttpServer;
|
||||
RuntimeShaderBridge mShaderBridge;
|
||||
bool mStarted = false;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user